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 10from enum import Enum, auto 11 12from sqlglot import exp 13from sqlglot._typing import F 14from sqlglot.errors import ( 15 ErrorLevel, 16 ParseError, 17 TokenError, 18 concat_messages, 19 highlight_sql, 20 merge_errors, 21) 22from sqlglot.expressions import apply_index_offset 23from sqlglot.helper import ensure_list, i64, seq_get 24from sqlglot.optimizer.scope import find_in_scope 25from sqlglot.time import format_time 26from sqlglot.tokens import Token, Tokenizer, TokenType 27from sqlglot.trie import TrieResult, in_trie, new_trie 28 29if t.TYPE_CHECKING: 30 from re import Pattern 31 32 from sqlglot._typing import BuilderArgs, E 33 from sqlglot.dialects.dialect import Dialect, DialectType 34 from sqlglot.expressions import ExpOrStr 35 36 T = t.TypeVar("T") 37 TCeilFloor = t.TypeVar("TCeilFloor", exp.Ceil, exp.Floor) 38 39logger = logging.getLogger("sqlglot") 40 41OPTIONS_TYPE = dict[str, Sequence[t.Union[Sequence[str], str]]] 42 43# Excludes bare strings, which are also collections of strings, so that a single keyword 44# can't accidentally be matched with substring semantics (e.g. _match_texts("FOO")) 45TEXTS_TYPE = t.Union[tuple[str, ...], list[str], t.AbstractSet[str], t.Mapping[str, t.Any]] 46 47# Used to detect alphabetical characters and +/- in timestamp literals 48TIME_ZONE_RE: Pattern[str] = re.compile(r":.*?[a-zA-Z\+\-]") 49 50 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)) 62 63 64def build_like(args: BuilderArgs) -> exp.Escape | exp.Like: 65 like = exp.Like(this=seq_get(args, 1), expression=seq_get(args, 0)) 66 return exp.Escape(this=like, expression=seq_get(args, 2)) if len(args) > 2 else like 67 68 69def build_sort_array(args: BuilderArgs) -> exp.ArraySort: 70 # SORT_ARRAY puts NULLs first exactly when ascending 71 asc = seq_get(args, 1) 72 nulls_first = exp.true() if asc is None else asc.copy() 73 return exp.ArraySort(this=seq_get(args, 0), asc=asc, nulls_first=nulls_first) 74 75 76def build_array_sort_nulls_last(args: BuilderArgs) -> exp.ArraySort: 77 comparator = seq_get(args, 1) 78 return exp.ArraySort( 79 this=seq_get(args, 0), 80 expression=comparator, 81 nulls_first=None if comparator else exp.false(), 82 ) 83 84 85def binary_range_parser( 86 expr_type: Type[exp.Expr], reverse_args: bool = False 87) -> t.Callable[[Parser, exp.Expr | None], exp.Expr | None]: 88 def _parse_binary_range(self: Parser, this: exp.Expr | None) -> exp.Expr | None: 89 expression = self._parse_bitwise() 90 if reverse_args: 91 this, expression = expression, this 92 return self._parse_escape(self.expression(expr_type(this=this, expression=expression))) 93 94 return _parse_binary_range 95 96 97def build_logarithm(args: BuilderArgs, dialect: Dialect) -> exp.Func: 98 # Default argument order is base, expression 99 this = seq_get(args, 0) 100 expression = seq_get(args, 1) 101 102 if expression: 103 if not dialect.LOG_BASE_FIRST: 104 this, expression = expression, this 105 return exp.Log(this=this, expression=expression) 106 107 return (exp.Ln if dialect.parser_class.LOG_DEFAULTS_TO_LN else exp.Log)(this=this) 108 109 110def build_hex(args: BuilderArgs, dialect: Dialect) -> exp.Hex | exp.LowerHex: 111 arg = seq_get(args, 0) 112 return exp.LowerHex(this=arg) if dialect.HEX_LOWERCASE else exp.Hex(this=arg) 113 114 115def build_lower(args: BuilderArgs) -> exp.Lower | exp.Hex: 116 # LOWER(HEX(..)) can be simplified to LowerHex to simplify its transpilation 117 arg = seq_get(args, 0) 118 return exp.LowerHex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Lower(this=arg) 119 120 121def build_upper(args: BuilderArgs) -> exp.Upper | exp.Hex: 122 # UPPER(HEX(..)) can be simplified to Hex to simplify its transpilation 123 arg = seq_get(args, 0) 124 return exp.Hex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Upper(this=arg) 125 126 127def build_extract_json_with_path( 128 expr_type: Type[E], 129) -> t.Callable[[BuilderArgs, Dialect], E]: 130 def _builder(args: BuilderArgs, dialect: Dialect) -> E: 131 expression = expr_type( 132 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 133 ) 134 if len(args) > 2 and expr_type is exp.JSONExtract: 135 expression.set("expressions", args[2:]) 136 if expr_type is exp.JSONExtractScalar: 137 expression.set("scalar_only", dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY) 138 139 return expression 140 141 return _builder 142 143 144def build_mod(args: BuilderArgs) -> exp.Mod: 145 this = seq_get(args, 0) 146 expression = seq_get(args, 1) 147 148 # Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7 149 this = exp.Paren(this=this) if isinstance(this, exp.Binary) else this 150 expression = exp.Paren(this=expression) if isinstance(expression, exp.Binary) else expression 151 152 return exp.Mod(this=this, expression=expression) 153 154 155def build_pad(args: BuilderArgs, is_left: bool = True): 156 return exp.Pad( 157 this=seq_get(args, 0), 158 expression=seq_get(args, 1), 159 fill_pattern=seq_get(args, 2), 160 is_left=is_left, 161 ) 162 163 164def build_array_constructor( 165 exp_class: Type[E], args: list[t.Any], bracket_kind: TokenType, dialect: Dialect 166) -> exp.Expr: 167 array_exp = exp_class(expressions=args) 168 169 if exp_class == exp.Array: 170 array_exp.set("value_constructor", len(args) == 1 and isinstance(args[0], exp.Query)) 171 if dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS: 172 array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET) 173 174 return array_exp 175 176 177def build_convert_timezone( 178 args: BuilderArgs, default_source_tz: str | None = None 179) -> exp.ConvertTimezone | exp.Anonymous: 180 if len(args) == 2: 181 source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None 182 return exp.ConvertTimezone( 183 source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1) 184 ) 185 186 return exp.ConvertTimezone.from_arg_list(args) 187 188 189def build_trim(args: BuilderArgs, is_left: bool = True, reverse_args: bool = False) -> exp.Trim: 190 this, expression = seq_get(args, 0), seq_get(args, 1) 191 192 if expression and reverse_args: 193 this, expression = expression, this 194 195 return exp.Trim(this=this, expression=expression, position="LEADING" if is_left else "TRAILING") 196 197 198def build_coalesce( 199 args: BuilderArgs, is_nvl: bool | None = None, is_null: bool | None = None 200) -> exp.Coalesce: 201 return exp.Coalesce(this=seq_get(args, 0), expressions=args[1:], is_nvl=is_nvl, is_null=is_null) 202 203 204def build_locate_strposition(args: BuilderArgs) -> exp.StrPosition: 205 return exp.StrPosition( 206 this=seq_get(args, 1), 207 substr=seq_get(args, 0), 208 position=seq_get(args, 2), 209 ) 210 211 212def build_array_append(args: BuilderArgs, dialect: Dialect) -> exp.ArrayAppend: 213 """ 214 Builds ArrayAppend with NULL propagation semantics based on the dialect configuration. 215 216 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 217 Others (DuckDB, PostgreSQL) create a new single-element array instead. 218 219 Args: 220 args: Function arguments [array, element] 221 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 222 223 Returns: 224 ArrayAppend expression with appropriate null_propagation flag 225 """ 226 return exp.ArrayAppend( 227 this=seq_get(args, 0), 228 expression=seq_get(args, 1), 229 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 230 ) 231 232 233def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend: 234 """ 235 Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration. 236 237 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 238 Others (DuckDB, PostgreSQL) create a new single-element array instead. 239 240 Args: 241 args: Function arguments [array, element] 242 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 243 244 Returns: 245 ArrayPrepend expression with appropriate null_propagation flag 246 """ 247 return exp.ArrayPrepend( 248 this=seq_get(args, 0), 249 expression=seq_get(args, 1), 250 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 251 ) 252 253 254def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat: 255 """ 256 Builds ArrayConcat with NULL propagation semantics based on the dialect configuration. 257 258 Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. 259 Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation. 260 261 Args: 262 args: Function arguments [array1, array2, ...] (variadic) 263 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 264 265 Returns: 266 ArrayConcat expression with appropriate null_propagation flag 267 """ 268 return exp.ArrayConcat( 269 this=seq_get(args, 0), 270 expressions=args[1:], 271 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 272 ) 273 274 275def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove: 276 """ 277 Builds ArrayRemove with NULL propagation semantics based on the dialect configuration. 278 279 Some dialects (Snowflake) return NULL when the removal value is NULL. 280 Others (DuckDB) may return empty array due to NULL comparison semantics. 281 282 Args: 283 args: Function arguments [array, value_to_remove] 284 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 285 286 Returns: 287 ArrayRemove expression with appropriate null_propagation flag 288 """ 289 return exp.ArrayRemove( 290 this=seq_get(args, 0), 291 expression=seq_get(args, 1), 292 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 293 ) 294 295 296def _resolve_dialect(dialect: DialectType) -> Dialect: 297 from sqlglot.dialects.dialect import Dialect 298 299 return Dialect.get_or_raise(dialect) 300 301 302def _unpivot_target(expr: exp.Expr) -> exp.Expr: 303 # UNPIVOT's pre-FOR values and FOR field are new output names, not column references. 304 if isinstance(expr, exp.Column) and not expr.table: 305 return expr.this 306 if isinstance(expr, exp.Tuple): 307 expr.set("expressions", [_unpivot_target(e) for e in expr.expressions]) 308 return expr 309 310 311# Builders for the JSON `->` / `->>` / `#>` / `#>>` / `?` operators, shared between 312# COLUMN_OPERATORS (accessor-tier dialects) and JSON_OPERATORS (Postgres/DuckDB's 313# binary-operator tier). 314def build_json_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONExtract: 315 return self.expression( 316 exp.JSONExtract( 317 this=this, 318 expression=self.dialect.to_json_path(path), 319 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 320 ) 321 ) 322 323 324def build_json_extract_scalar( 325 self: Parser, this: exp.Expr, path: exp.Expr 326) -> exp.JSONExtractScalar: 327 return self.expression( 328 exp.JSONExtractScalar( 329 this=this, 330 expression=self.dialect.to_json_path(path), 331 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 332 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 333 ) 334 ) 335 336 337def build_jsonb_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONBExtract: 338 return self.expression(exp.JSONBExtract(this=this, expression=path)) 339 340 341def build_jsonb_extract_scalar( 342 self: Parser, this: exp.Expr, path: exp.Expr 343) -> exp.JSONBExtractScalar: 344 return self.expression(exp.JSONBExtractScalar(this=this, expression=path)) 345 346 347def build_jsonb_contains_top_key( 348 self: Parser, this: exp.Expr, key: exp.Expr 349) -> exp.JSONBContainsTopKey: 350 return self.expression(exp.JSONBContainsTopKey(this=this, expression=key)) 351 352 353SENTINEL_NONE: Token = Token(TokenType.SENTINEL, "SENTINEL") 354 355 356class IsPredicatePrecedence(Enum): 357 HIGH = auto() 358 COMPARISON = auto() 359 EQUALITY = auto() 360 LOW = auto() 361 362 363class Parser: 364 """ 365 Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree. 366 367 Args: 368 error_level: The desired error level. 369 Default: ErrorLevel.IMMEDIATE 370 error_message_context: The amount of context to capture from a query string when displaying 371 the error message (in number of characters). 372 Default: 100 373 max_errors: Maximum number of error messages to include in a raised ParseError. 374 This is only relevant if error_level is ErrorLevel.RAISE. 375 Default: 3 376 max_nodes: Maximum number of AST nodes to prevent memory exhaustion. 377 Set to -1 (default) to disable the check. 378 """ 379 380 __slots__ = ( 381 "error_level", 382 "error_message_context", 383 "max_errors", 384 "max_nodes", 385 "dialect", 386 "sql", 387 "errors", 388 "_tokens", 389 "_index", 390 "_curr", 391 "_next", 392 "_prev", 393 "_prev_comments", 394 "_pipe_cte_counter", 395 "_chunks", 396 "_chunk_index", 397 "_tokens_size", 398 "_node_count", 399 ) 400 401 FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = { 402 **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()}, 403 **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce), 404 "ARRAY": lambda args, dialect: exp.Array(expressions=args), 405 "SORT_ARRAY": build_sort_array, 406 "ARRAYAGG": lambda args, dialect: exp.ArrayAgg( 407 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 408 ), 409 "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg( 410 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 411 ), 412 "ARRAY_APPEND": build_array_append, 413 "ARRAY_CAT": build_array_concat, 414 "ARRAY_CONCAT": build_array_concat, 415 "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args), 416 "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args), 417 "ARRAY_PREPEND": build_array_prepend, 418 "ARRAY_REMOVE": build_array_remove, 419 "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True), 420 "CONCAT": lambda args, dialect: exp.Concat( 421 expressions=args, 422 safe=not dialect.STRICT_STRING_CONCAT, 423 coalesce=dialect.CONCAT_COALESCE, 424 ), 425 "CONCAT_WS": lambda args, dialect: exp.ConcatWs( 426 expressions=args, 427 safe=not dialect.STRICT_STRING_CONCAT, 428 coalesce=dialect.CONCAT_WS_COALESCE, 429 ), 430 "CONVERT_TIMEZONE": build_convert_timezone, 431 "DATE_TO_DATE_STR": lambda args: exp.Cast( 432 this=seq_get(args, 0), 433 to=exp.DataType(this=exp.DType.TEXT), 434 ), 435 "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray( 436 start=seq_get(args, 0), 437 end=seq_get(args, 1), 438 step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")), 439 ), 440 "GENERATE_UUID": lambda args, dialect: exp.Uuid( 441 is_string=dialect.UUID_IS_STRING_TYPE or None 442 ), 443 "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)), 444 "GREATEST": lambda args, dialect: exp.Greatest( 445 this=seq_get(args, 0), 446 expressions=args[1:], 447 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 448 ), 449 "LEAST": lambda args, dialect: exp.Least( 450 this=seq_get(args, 0), 451 expressions=args[1:], 452 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 453 ), 454 "HEX": build_hex, 455 "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract), 456 "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar), 457 "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar), 458 "JSON_KEYS": lambda args, dialect: exp.JSONKeys( 459 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 460 ), 461 "LIKE": build_like, 462 "LOG": build_logarithm, 463 "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)), 464 "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)), 465 "LOWER": build_lower, 466 "LPAD": lambda args: build_pad(args), 467 "LEFTPAD": lambda args: build_pad(args), 468 "LTRIM": lambda args: build_trim(args), 469 "MOD": build_mod, 470 "RIGHTPAD": lambda args: build_pad(args, is_left=False), 471 "RPAD": lambda args: build_pad(args, is_left=False), 472 "RTRIM": lambda args: build_trim(args, is_left=False), 473 "SCOPE_RESOLUTION": lambda args: ( 474 exp.ScopeResolution(expression=seq_get(args, 0)) 475 if len(args) != 2 476 else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1)) 477 ), 478 "STRPOS": exp.StrPosition.from_arg_list, 479 "CHARINDEX": lambda args: build_locate_strposition(args), 480 "INSTR": exp.StrPosition.from_arg_list, 481 "LOCATE": lambda args: build_locate_strposition(args), 482 "TIME_TO_TIME_STR": lambda args: exp.Cast( 483 this=seq_get(args, 0), 484 to=exp.DataType(this=exp.DType.TEXT), 485 ), 486 "TO_HEX": build_hex, 487 "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring( 488 this=exp.Cast( 489 this=seq_get(args, 0), 490 to=exp.DataType(this=exp.DType.TEXT), 491 ), 492 start=exp.Literal.number(1), 493 length=exp.Literal.number(10), 494 ), 495 "UNNEST": lambda args: exp.Unnest(expressions=args), 496 "UPPER": build_upper, 497 "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None), 498 "UUID_STRING": lambda args, dialect: exp.Uuid( 499 this=seq_get(args, 0), 500 name=seq_get(args, 1), 501 is_string=dialect.UUID_IS_STRING_TYPE or None, 502 ), 503 "VAR_MAP": build_var_map, 504 } 505 506 NO_PAREN_FUNCTIONS: t.ClassVar[dict] = { 507 TokenType.CURRENT_DATE: exp.CurrentDate, 508 TokenType.CURRENT_DATETIME: exp.CurrentDate, 509 TokenType.CURRENT_TIME: exp.CurrentTime, 510 TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp, 511 TokenType.CURRENT_USER: exp.CurrentUser, 512 TokenType.CURRENT_ROLE: exp.CurrentRole, 513 } 514 515 STRUCT_TYPE_TOKENS: t.ClassVar = { 516 TokenType.NESTED, 517 TokenType.OBJECT, 518 TokenType.STRUCT, 519 TokenType.UNION, 520 } 521 522 NESTED_TYPE_TOKENS: t.ClassVar = { 523 TokenType.ARRAY, 524 TokenType.LIST, 525 TokenType.LOWCARDINALITY, 526 TokenType.MAP, 527 TokenType.NULLABLE, 528 TokenType.RANGE, 529 *STRUCT_TYPE_TOKENS, 530 } 531 532 ENUM_TYPE_TOKENS: t.ClassVar = { 533 TokenType.DYNAMIC, 534 TokenType.ENUM, 535 TokenType.ENUM8, 536 TokenType.ENUM16, 537 } 538 539 AGGREGATE_TYPE_TOKENS: t.ClassVar = { 540 TokenType.AGGREGATEFUNCTION, 541 TokenType.SIMPLEAGGREGATEFUNCTION, 542 } 543 544 TYPE_TOKENS: t.ClassVar = { 545 TokenType.BIT, 546 TokenType.BOOLEAN, 547 TokenType.TINYINT, 548 TokenType.UTINYINT, 549 TokenType.SMALLINT, 550 TokenType.USMALLINT, 551 TokenType.INT, 552 TokenType.UINT, 553 TokenType.BIGINT, 554 TokenType.UBIGINT, 555 TokenType.BIGNUM, 556 TokenType.INT128, 557 TokenType.UINT128, 558 TokenType.INT256, 559 TokenType.UINT256, 560 TokenType.MEDIUMINT, 561 TokenType.UMEDIUMINT, 562 TokenType.FIXEDSTRING, 563 TokenType.FLOAT, 564 TokenType.DOUBLE, 565 TokenType.UDOUBLE, 566 TokenType.CHAR, 567 TokenType.NCHAR, 568 TokenType.VARCHAR, 569 TokenType.NVARCHAR, 570 TokenType.BPCHAR, 571 TokenType.TEXT, 572 TokenType.MEDIUMTEXT, 573 TokenType.LONGTEXT, 574 TokenType.BLOB, 575 TokenType.MEDIUMBLOB, 576 TokenType.LONGBLOB, 577 TokenType.BINARY, 578 TokenType.VARBINARY, 579 TokenType.JSON, 580 TokenType.JSONB, 581 TokenType.INTERVAL, 582 TokenType.TINYBLOB, 583 TokenType.TINYTEXT, 584 TokenType.TIME, 585 TokenType.TIMETZ, 586 TokenType.TIME_NS, 587 TokenType.TIMESTAMP, 588 TokenType.TIMESTAMP_S, 589 TokenType.TIMESTAMP_MS, 590 TokenType.TIMESTAMP_NS, 591 TokenType.TIMESTAMPTZ, 592 TokenType.TIMESTAMPLTZ, 593 TokenType.TIMESTAMPNTZ, 594 TokenType.DATETIME, 595 TokenType.DATETIME2, 596 TokenType.DATETIME64, 597 TokenType.SMALLDATETIME, 598 TokenType.DATE, 599 TokenType.DATE32, 600 TokenType.INT4RANGE, 601 TokenType.INT4MULTIRANGE, 602 TokenType.INT8RANGE, 603 TokenType.INT8MULTIRANGE, 604 TokenType.NUMRANGE, 605 TokenType.NUMMULTIRANGE, 606 TokenType.TSRANGE, 607 TokenType.TSMULTIRANGE, 608 TokenType.TSTZRANGE, 609 TokenType.TSTZMULTIRANGE, 610 TokenType.DATERANGE, 611 TokenType.DATEMULTIRANGE, 612 TokenType.DECIMAL, 613 TokenType.DECIMAL32, 614 TokenType.DECIMAL64, 615 TokenType.DECIMAL128, 616 TokenType.DECIMAL256, 617 TokenType.DECFLOAT, 618 TokenType.UDECIMAL, 619 TokenType.BIGDECIMAL, 620 TokenType.UUID, 621 TokenType.GEOGRAPHY, 622 TokenType.GEOGRAPHYPOINT, 623 TokenType.GEOMETRY, 624 TokenType.POINT, 625 TokenType.RING, 626 TokenType.LINESTRING, 627 TokenType.MULTILINESTRING, 628 TokenType.POLYGON, 629 TokenType.MULTIPOLYGON, 630 TokenType.HLLSKETCH, 631 TokenType.HSTORE, 632 TokenType.PSEUDO_TYPE, 633 TokenType.SUPER, 634 TokenType.SERIAL, 635 TokenType.SMALLSERIAL, 636 TokenType.BIGSERIAL, 637 TokenType.XML, 638 TokenType.YEAR, 639 TokenType.USERDEFINED, 640 TokenType.MONEY, 641 TokenType.SMALLMONEY, 642 TokenType.ROWVERSION, 643 TokenType.IMAGE, 644 TokenType.VARIANT, 645 TokenType.VECTOR, 646 TokenType.VOID, 647 TokenType.OBJECT, 648 TokenType.OBJECT_IDENTIFIER, 649 TokenType.INET, 650 TokenType.IPADDRESS, 651 TokenType.IPPREFIX, 652 TokenType.IPV4, 653 TokenType.IPV6, 654 TokenType.UNKNOWN, 655 TokenType.NOTHING, 656 TokenType.NULL, 657 TokenType.NAME, 658 TokenType.TDIGEST, 659 TokenType.DYNAMIC, 660 *ENUM_TYPE_TOKENS, 661 *NESTED_TYPE_TOKENS, 662 *AGGREGATE_TYPE_TOKENS, 663 } 664 665 SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = { 666 TokenType.BIGINT: TokenType.UBIGINT, 667 TokenType.INT: TokenType.UINT, 668 TokenType.MEDIUMINT: TokenType.UMEDIUMINT, 669 TokenType.SMALLINT: TokenType.USMALLINT, 670 TokenType.TINYINT: TokenType.UTINYINT, 671 TokenType.DECIMAL: TokenType.UDECIMAL, 672 TokenType.DOUBLE: TokenType.UDOUBLE, 673 } 674 675 SUBQUERY_PREDICATES: t.ClassVar = { 676 TokenType.ANY: exp.Any, 677 TokenType.ALL: exp.All, 678 TokenType.EXISTS: exp.Exists, 679 TokenType.SOME: exp.Any, 680 } 681 682 SUBQUERY_TOKENS: t.ClassVar = { 683 TokenType.SELECT, 684 TokenType.WITH, 685 TokenType.FROM, 686 } 687 688 RESERVED_TOKENS: t.ClassVar = { 689 *Tokenizer.SINGLE_TOKENS.values(), 690 TokenType.SELECT, 691 } - {TokenType.IDENTIFIER} 692 693 # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers, 694 # string literals), so they must never be treated as keywords when matching by text 695 TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset( 696 { 697 TokenType.BIT_STRING, 698 TokenType.BYTE_STRING, 699 TokenType.HEREDOC_STRING, 700 TokenType.HEX_STRING, 701 TokenType.IDENTIFIER, 702 TokenType.NATIONAL_RAW_STRING, 703 TokenType.NATIONAL_STRING, 704 TokenType.RAW_STRING, 705 TokenType.STRING, 706 TokenType.UNICODE_STRING, 707 } 708 ) 709 710 DB_CREATABLES: t.ClassVar = { 711 TokenType.DATABASE, 712 TokenType.DICTIONARY, 713 TokenType.FILE_FORMAT, 714 TokenType.MODEL, 715 TokenType.NAMESPACE, 716 TokenType.SCHEMA, 717 TokenType.SEMANTIC_VIEW, 718 TokenType.SEQUENCE, 719 TokenType.SINK, 720 TokenType.SOURCE, 721 TokenType.STAGE, 722 TokenType.STORAGE_INTEGRATION, 723 TokenType.STREAMLIT, 724 TokenType.TABLE, 725 TokenType.TAG, 726 TokenType.VIEW, 727 TokenType.WAREHOUSE, 728 } 729 730 CREATABLES: t.ClassVar = { 731 TokenType.COLUMN, 732 TokenType.CONSTRAINT, 733 TokenType.FOREIGN_KEY, 734 TokenType.FUNCTION, 735 TokenType.INDEX, 736 TokenType.PROCEDURE, 737 TokenType.TRIGGER, 738 TokenType.TYPE, 739 *DB_CREATABLES, 740 } 741 742 TRIGGER_EVENTS: t.ClassVar = { 743 TokenType.INSERT, 744 TokenType.UPDATE, 745 TokenType.DELETE, 746 TokenType.TRUNCATE, 747 } 748 749 ALTERABLES: t.ClassVar = { 750 TokenType.INDEX, 751 TokenType.TABLE, 752 TokenType.VIEW, 753 TokenType.SESSION, 754 } 755 756 # Tokens that can represent identifiers 757 ID_VAR_TOKENS: t.ClassVar[set] = { 758 TokenType.ALL, 759 TokenType.ANALYZE, 760 TokenType.ATTACH, 761 TokenType.VAR, 762 TokenType.ANTI, 763 TokenType.APPLY, 764 TokenType.ASC, 765 TokenType.ASOF, 766 TokenType.AUTO_INCREMENT, 767 TokenType.BEGIN, 768 TokenType.BPCHAR, 769 TokenType.CACHE, 770 TokenType.CASE, 771 TokenType.COLLATE, 772 TokenType.COMMAND, 773 TokenType.COMMENT, 774 TokenType.COMMIT, 775 TokenType.CONSTRAINT, 776 TokenType.COPY, 777 TokenType.CUBE, 778 TokenType.CURRENT_SCHEMA, 779 TokenType.DECLARE, 780 TokenType.DEFAULT, 781 TokenType.DELETE, 782 TokenType.DESC, 783 TokenType.DESCRIBE, 784 TokenType.DETACH, 785 TokenType.DICTIONARY, 786 TokenType.DIV, 787 TokenType.END, 788 TokenType.EXECUTE, 789 TokenType.EXPORT, 790 TokenType.ESCAPE, 791 TokenType.FALSE, 792 TokenType.FIRST, 793 TokenType.FILE, 794 TokenType.FILTER, 795 TokenType.FINAL, 796 TokenType.FORMAT, 797 TokenType.FULL, 798 TokenType.GET, 799 TokenType.IDENTIFIER, 800 TokenType.INOUT, 801 TokenType.IS, 802 TokenType.ISNULL, 803 TokenType.INTERVAL, 804 TokenType.KEEP, 805 TokenType.KILL, 806 TokenType.LEFT, 807 TokenType.LIMIT, 808 TokenType.LOAD, 809 TokenType.LOCK, 810 TokenType.MATCH, 811 TokenType.MERGE, 812 TokenType.NATURAL, 813 TokenType.NEXT, 814 TokenType.OFFSET, 815 TokenType.OPERATOR, 816 TokenType.ORDINALITY, 817 TokenType.OUT, 818 TokenType.OVER, 819 TokenType.OVERLAPS, 820 TokenType.OVERWRITE, 821 TokenType.PARTITION, 822 TokenType.PASTE, 823 TokenType.PERCENT, 824 TokenType.PIVOT, 825 TokenType.PROJECTION, 826 TokenType.PRAGMA, 827 TokenType.PUT, 828 TokenType.RANGE, 829 TokenType.RECURSIVE, 830 TokenType.REFERENCES, 831 TokenType.REFRESH, 832 TokenType.RENAME, 833 TokenType.REPLACE, 834 TokenType.RIGHT, 835 TokenType.ROLLUP, 836 TokenType.ROW, 837 TokenType.ROWS, 838 TokenType.SEMI, 839 TokenType.SET, 840 TokenType.SETTINGS, 841 TokenType.SHOW, 842 TokenType.STREAM, 843 TokenType.STREAMLIT, 844 TokenType.TEMPORARY, 845 TokenType.TOP, 846 TokenType.TRUE, 847 TokenType.TRUNCATE, 848 TokenType.UNIQUE, 849 TokenType.UNNEST, 850 TokenType.UNPIVOT, 851 TokenType.UPDATE, 852 TokenType.USE, 853 TokenType.VOLATILE, 854 TokenType.WINDOW, 855 TokenType.CURRENT_CATALOG, 856 TokenType.LOCALTIME, 857 TokenType.LOCALTIMESTAMP, 858 TokenType.SESSION_USER, 859 TokenType.STRAIGHT_JOIN, 860 *ALTERABLES, 861 *CREATABLES, 862 *SUBQUERY_PREDICATES, 863 *TYPE_TOKENS, 864 *NO_PAREN_FUNCTIONS, 865 } - {TokenType.UNION} 866 867 TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - { 868 TokenType.ANTI, 869 TokenType.ASOF, 870 TokenType.FULL, 871 TokenType.LEFT, 872 TokenType.LOCK, 873 TokenType.NATURAL, 874 TokenType.RIGHT, 875 TokenType.SEMI, 876 TokenType.WINDOW, 877 } 878 879 ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS 880 881 COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS 882 883 ARRAY_CONSTRUCTORS: t.ClassVar = { 884 "ARRAY": exp.Array, 885 "LIST": exp.List, 886 } 887 888 COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS} 889 890 UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET} 891 892 TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"} 893 894 # Tokens that indicate a simple column reference 895 IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER}) 896 897 BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE}) 898 899 # Postfix tokens that prevent the bare column fast path 900 COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 901 { 902 TokenType.L_PAREN, 903 TokenType.L_BRACKET, 904 TokenType.L_BRACE, 905 TokenType.COLON, 906 TokenType.JOIN_MARKER, 907 } 908 ) 909 910 TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 911 { 912 TokenType.L_PAREN, 913 TokenType.L_BRACKET, 914 TokenType.L_BRACE, 915 TokenType.PIVOT, 916 TokenType.UNPIVOT, 917 TokenType.TABLE_SAMPLE, 918 } 919 ) 920 921 FUNC_TOKENS: t.ClassVar = { 922 TokenType.COLLATE, 923 TokenType.COMMAND, 924 TokenType.CURRENT_DATE, 925 TokenType.CURRENT_DATETIME, 926 TokenType.CURRENT_SCHEMA, 927 TokenType.CURRENT_TIMESTAMP, 928 TokenType.CURRENT_TIME, 929 TokenType.CURRENT_USER, 930 TokenType.CURRENT_CATALOG, 931 TokenType.DECLARE, 932 TokenType.FILTER, 933 TokenType.FIRST, 934 TokenType.FORMAT, 935 TokenType.GET, 936 TokenType.GLOB, 937 TokenType.IDENTIFIER, 938 TokenType.INDEX, 939 TokenType.ISNULL, 940 TokenType.ILIKE, 941 TokenType.INSERT, 942 TokenType.LIKE, 943 TokenType.LOCALTIME, 944 TokenType.LOCALTIMESTAMP, 945 TokenType.MERGE, 946 TokenType.NEXT, 947 TokenType.OFFSET, 948 TokenType.PRIMARY_KEY, 949 TokenType.RANGE, 950 TokenType.REPLACE, 951 TokenType.RLIKE, 952 TokenType.ROW, 953 TokenType.SESSION_USER, 954 TokenType.UNNEST, 955 TokenType.VAR, 956 TokenType.LEFT, 957 TokenType.RIGHT, 958 TokenType.SEQUENCE, 959 TokenType.DATE, 960 TokenType.DATETIME, 961 TokenType.TABLE, 962 TokenType.TIMESTAMP, 963 TokenType.TIMESTAMPTZ, 964 TokenType.TRUNCATE, 965 TokenType.UTC_DATE, 966 TokenType.UTC_TIME, 967 TokenType.UTC_TIMESTAMP, 968 TokenType.WINDOW, 969 TokenType.XOR, 970 *TYPE_TOKENS, 971 *SUBQUERY_PREDICATES, 972 } 973 974 CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 975 TokenType.AND: exp.And, 976 } 977 978 ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 979 TokenType.COLON_EQ: exp.PropertyEQ, 980 } 981 982 DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 983 TokenType.OR: exp.Or, 984 } 985 986 EQUALITY: t.ClassVar = { 987 TokenType.EQ: exp.EQ, 988 TokenType.NEQ: exp.NEQ, 989 TokenType.NULLSAFE_EQ: exp.NullSafeEQ, 990 } 991 992 COMPARISON: t.ClassVar = { 993 TokenType.GT: exp.GT, 994 TokenType.GTE: exp.GTE, 995 TokenType.LT: exp.LT, 996 TokenType.LTE: exp.LTE, 997 } 998 999 BITWISE: t.ClassVar = { 1000 TokenType.AMP: exp.BitwiseAnd, 1001 TokenType.CARET: exp.BitwiseXor, 1002 TokenType.PIPE: exp.BitwiseOr, 1003 } 1004 1005 TERM: t.ClassVar = { 1006 TokenType.DASH: exp.Sub, 1007 TokenType.PLUS: exp.Add, 1008 TokenType.COLLATE: exp.Collate, 1009 } 1010 1011 FACTOR: t.ClassVar = { 1012 TokenType.DIV: exp.IntDiv, 1013 TokenType.LR_ARROW: exp.Distance, 1014 TokenType.LLRR_ARROW: exp.DistanceNd, 1015 TokenType.MOD: exp.Mod, 1016 TokenType.SLASH: exp.Div, 1017 TokenType.STAR: exp.Mul, 1018 } 1019 1020 EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {} 1021 1022 TIMES: t.ClassVar = { 1023 TokenType.TIME, 1024 TokenType.TIMETZ, 1025 } 1026 1027 TIMESTAMPS: t.ClassVar = { 1028 TokenType.TIMESTAMP, 1029 TokenType.TIMESTAMPNTZ, 1030 TokenType.TIMESTAMPTZ, 1031 TokenType.TIMESTAMPLTZ, 1032 *TIMES, 1033 } 1034 1035 SET_OPERATIONS: t.ClassVar = { 1036 TokenType.UNION, 1037 TokenType.INTERSECT, 1038 TokenType.EXCEPT, 1039 } 1040 1041 JOIN_METHODS: t.ClassVar = { 1042 TokenType.ASOF, 1043 TokenType.NATURAL, 1044 TokenType.POSITIONAL, 1045 } 1046 1047 JOIN_SIDES: t.ClassVar = { 1048 TokenType.LEFT, 1049 TokenType.RIGHT, 1050 TokenType.FULL, 1051 } 1052 1053 JOIN_KINDS: t.ClassVar = { 1054 TokenType.ANTI, 1055 TokenType.CROSS, 1056 TokenType.INNER, 1057 TokenType.OUTER, 1058 TokenType.SEMI, 1059 TokenType.STRAIGHT_JOIN, 1060 } 1061 1062 JOIN_HINTS: t.ClassVar[set[str]] = set() 1063 1064 # Tokens that unambiguously end a table reference on the fast path 1065 TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset( 1066 { 1067 TokenType.COMMA, 1068 TokenType.GROUP_BY, 1069 TokenType.HAVING, 1070 TokenType.JOIN, 1071 TokenType.LIMIT, 1072 TokenType.ON, 1073 TokenType.ORDER_BY, 1074 TokenType.R_PAREN, 1075 TokenType.SEMICOLON, 1076 TokenType.SENTINEL, 1077 TokenType.WHERE, 1078 *SET_OPERATIONS, 1079 *JOIN_KINDS, 1080 *JOIN_METHODS, 1081 *JOIN_SIDES, 1082 } 1083 ) 1084 1085 LAMBDAS: t.ClassVar = { 1086 TokenType.ARROW: lambda self, expressions: self.expression( 1087 exp.Lambda( 1088 this=self._replace_lambda( 1089 self._parse_disjunction(), 1090 expressions, 1091 ), 1092 expressions=expressions, 1093 ) 1094 ), 1095 TokenType.FARROW: lambda self, expressions: self.expression( 1096 exp.Kwarg( 1097 this=exp.var(expressions[0].name), 1098 expression=self._parse_disjunction() or self._parse_select(), 1099 ) 1100 ), 1101 } 1102 1103 # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake 1104 TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False 1105 1106 LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN}) 1107 1108 COLUMN_OPERATORS: t.ClassVar = { 1109 TokenType.DOT: None, 1110 TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)), 1111 TokenType.DCOLON: lambda self, this, to: self.build_cast( 1112 strict=self.STRICT_CAST, this=this, to=to 1113 ), 1114 TokenType.ARROW: lambda self, this, path: self.expression( 1115 exp.JSONExtract( 1116 this=this, 1117 expression=self.dialect.to_json_path(path), 1118 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1119 ) 1120 ), 1121 TokenType.DARROW: lambda self, this, path: self.expression( 1122 exp.JSONExtractScalar( 1123 this=this, 1124 expression=self.dialect.to_json_path(path), 1125 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1126 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 1127 ) 1128 ), 1129 TokenType.HASH_ARROW: lambda self, this, path: self.expression( 1130 exp.JSONBExtract(this=this, expression=path) 1131 ), 1132 TokenType.DHASH_ARROW: lambda self, this, path: self.expression( 1133 exp.JSONBExtractScalar(this=this, expression=path) 1134 ), 1135 TokenType.PLACEHOLDER: lambda self, this, key: self.expression( 1136 exp.JSONBContainsTopKey(this=this, expression=key) 1137 ), 1138 } 1139 1140 # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator" 1141 # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs). 1142 JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {} 1143 1144 CAST_COLUMN_OPERATORS: t.ClassVar = { 1145 TokenType.DOTCOLON, 1146 TokenType.DCOLON, 1147 } 1148 1149 EXPRESSION_PARSERS: t.ClassVar = { 1150 exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), 1151 exp.Column: lambda self: self._parse_column(), 1152 exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()), 1153 exp.Condition: lambda self: self._parse_disjunction(), 1154 exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True), 1155 exp.Expr: lambda self: self._parse_expression(), 1156 exp.From: lambda self: self._parse_from(joins=True), 1157 exp.GrantPrincipal: lambda self: self._parse_grant_principal(), 1158 exp.GrantPrivilege: lambda self: self._parse_grant_privilege(), 1159 exp.Group: lambda self: self._parse_group(), 1160 exp.Having: lambda self: self._parse_having(), 1161 exp.Hint: lambda self: self._parse_hint_body(), 1162 exp.Identifier: lambda self: self._parse_id_var(), 1163 exp.Join: lambda self: self._parse_join(), 1164 exp.Lambda: lambda self: self._parse_lambda(), 1165 exp.Lateral: lambda self: self._parse_lateral(), 1166 exp.Limit: lambda self: self._parse_limit(), 1167 exp.Offset: lambda self: self._parse_offset(), 1168 exp.Order: lambda self: self._parse_order(), 1169 exp.Ordered: lambda self: self._parse_ordered(), 1170 exp.Properties: lambda self: self._parse_properties(), 1171 exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(), 1172 exp.Qualify: lambda self: self._parse_qualify(), 1173 exp.Returning: lambda self: self._parse_returning(), 1174 exp.Select: lambda self: self._parse_select(), 1175 exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), 1176 exp.Table: lambda self: self._parse_table_parts(), 1177 exp.TableAlias: lambda self: self._parse_table_alias(), 1178 exp.Tuple: lambda self: self._parse_value(values=False), 1179 exp.Whens: lambda self: self._parse_when_matched(), 1180 exp.Where: lambda self: self._parse_where(), 1181 exp.Window: lambda self: self._parse_named_window(), 1182 exp.With: lambda self: self._parse_with(), 1183 } 1184 1185 STATEMENT_PARSERS: t.ClassVar = { 1186 TokenType.ALTER: lambda self: self._parse_alter(), 1187 TokenType.ANALYZE: lambda self: self._parse_analyze(), 1188 TokenType.BEGIN: lambda self: self._parse_transaction(), 1189 TokenType.CACHE: lambda self: self._parse_cache(), 1190 TokenType.COMMENT: lambda self: self._parse_comment(), 1191 TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(), 1192 TokenType.COPY: lambda self: self._parse_copy(), 1193 TokenType.CREATE: lambda self: self._parse_create(), 1194 TokenType.DECLARE: lambda self: self._parse_declare(), 1195 TokenType.DELETE: lambda self: self._parse_delete(), 1196 TokenType.DESC: lambda self: self._parse_describe(), 1197 TokenType.DESCRIBE: lambda self: self._parse_describe(), 1198 TokenType.DROP: lambda self: self._parse_drop(), 1199 TokenType.GRANT: lambda self: self._parse_grant(), 1200 TokenType.REVOKE: lambda self: self._parse_revoke(), 1201 TokenType.INSERT: lambda self: self._parse_insert(), 1202 TokenType.KILL: lambda self: self._parse_kill(), 1203 TokenType.LOAD: lambda self: self._parse_load(), 1204 TokenType.MERGE: lambda self: self._parse_merge(), 1205 TokenType.PIVOT: lambda self: self._parse_simplified_pivot(), 1206 TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())), 1207 TokenType.REFRESH: lambda self: self._parse_refresh(), 1208 TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(), 1209 TokenType.SET: lambda self: self._parse_set(), 1210 TokenType.TRUNCATE: lambda self: self._parse_truncate_table(), 1211 TokenType.UNCACHE: lambda self: self._parse_uncache(), 1212 TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True), 1213 TokenType.UPDATE: lambda self: self._parse_update(), 1214 TokenType.USE: lambda self: self._parse_use(), 1215 TokenType.SEMICOLON: lambda self: exp.Semicolon(), 1216 } 1217 1218 UNARY_PARSERS: t.ClassVar = { 1219 TokenType.PLUS: lambda self: self._parse_unary(), # Unary + is handled as a no-op 1220 TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())), 1221 TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())), 1222 TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())), 1223 TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())), 1224 TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())), 1225 } 1226 1227 STRING_PARSERS: t.ClassVar = { 1228 TokenType.HEREDOC_STRING: lambda self, token: self.expression( 1229 exp.RawString(this=token.text), token 1230 ), 1231 **{ 1232 token_type: lambda self, token: self.expression(exp.National(this=token.text), token) 1233 for token_type in (TokenType.NATIONAL_STRING, TokenType.NATIONAL_RAW_STRING) 1234 }, 1235 TokenType.RAW_STRING: lambda self, token: self.expression( 1236 exp.RawString(this=token.text), token 1237 ), 1238 TokenType.STRING: lambda self, token: self.expression( 1239 exp.Literal(this=token.text, is_string=True), token 1240 ), 1241 TokenType.UNICODE_STRING: lambda self, token: self.expression( 1242 exp.UnicodeString( 1243 this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string() 1244 ), 1245 token, 1246 ), 1247 } 1248 1249 NUMERIC_PARSERS: t.ClassVar = { 1250 TokenType.BIT_STRING: lambda self, token: self.expression( 1251 exp.BitString(this=token.text), token 1252 ), 1253 TokenType.BYTE_STRING: lambda self, token: self.expression( 1254 exp.ByteString( 1255 this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None 1256 ), 1257 token, 1258 ), 1259 TokenType.HEX_STRING: lambda self, token: self.expression( 1260 exp.HexString( 1261 this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None 1262 ), 1263 token, 1264 ), 1265 TokenType.NUMBER: lambda self, token: self.expression( 1266 exp.Literal(this=token.text, is_string=False), token 1267 ), 1268 } 1269 1270 PRIMARY_PARSERS: t.ClassVar = { 1271 **STRING_PARSERS, 1272 **NUMERIC_PARSERS, 1273 TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token), 1274 TokenType.NULL: lambda self, _: self.expression(exp.Null()), 1275 TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)), 1276 TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)), 1277 TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(), 1278 TokenType.STAR: lambda self, _: self._parse_star_ops(), 1279 } 1280 1281 PLACEHOLDER_PARSERS: t.ClassVar = { 1282 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()), 1283 TokenType.PARAMETER: lambda self: self._parse_parameter(), 1284 TokenType.COLON: lambda self: ( 1285 self.expression(exp.Placeholder(this=self._prev.text)) 1286 if self._match_set(self.COLON_PLACEHOLDER_TOKENS) 1287 else None 1288 ), 1289 } 1290 1291 RANGE_PARSERS: t.ClassVar = { 1292 TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll), 1293 TokenType.BETWEEN: lambda self, this: self._parse_between(this), 1294 TokenType.GLOB: binary_range_parser(exp.Glob), 1295 TokenType.ILIKE: binary_range_parser(exp.ILike), 1296 TokenType.IN: lambda self, this: self._parse_in(this), 1297 TokenType.IRLIKE: binary_range_parser(exp.RegexpILike), 1298 TokenType.IS: lambda self, this: self._parse_is(this), 1299 TokenType.LIKE: binary_range_parser(exp.Like), 1300 TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy), 1301 TokenType.OVERLAPS: binary_range_parser(exp.Overlaps), 1302 TokenType.RLIKE: binary_range_parser(exp.RegexpLike), 1303 TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo), 1304 TokenType.FOR: lambda self, this: self._parse_comprehension(this), 1305 TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys), 1306 TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys), 1307 TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath), 1308 TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists), 1309 TokenType.ADJACENT: binary_range_parser(exp.Adjacent), 1310 TokenType.OPERATOR: lambda self, this: self._parse_operator(this), 1311 TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft), 1312 TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight), 1313 TokenType.CARET_AT: binary_range_parser(exp.StartsWith), 1314 } 1315 1316 PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = { 1317 "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query), 1318 "AS": lambda self, query: self._build_pipe_cte( 1319 query, [exp.Star()], self._parse_table_alias() 1320 ), 1321 "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False), 1322 "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query), 1323 "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query), 1324 "ORDER BY": lambda self, query: query.order_by( 1325 self._parse_order(), append=False, copy=False 1326 ), 1327 "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1328 "SELECT": lambda self, query: self._parse_pipe_syntax_select(query), 1329 "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query), 1330 "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1331 "WHERE": lambda self, query: query.where(self._parse_where(), copy=False), 1332 } 1333 1334 PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1335 "ALLOWED_VALUES": lambda self: self.expression( 1336 exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary)) 1337 ), 1338 "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty), 1339 "AUTO": lambda self: self._parse_auto_property(), 1340 "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty), 1341 "BACKUP": lambda self: self.expression( 1342 exp.BackupProperty(this=self._parse_var(any_token=True)) 1343 ), 1344 "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(), 1345 "CALLED": lambda self: self._parse_called_on_null_input_property(), 1346 "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs), 1347 "CHECKSUM": lambda self: self._parse_checksum(), 1348 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1349 "CLUSTERED": lambda self: self._parse_clustered_by(), 1350 "COLLATE": lambda self, **kwargs: self._parse_property_assignment( 1351 exp.CollateProperty, **kwargs 1352 ), 1353 "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty), 1354 "CONTAINS": lambda self: self._parse_contains_property(), 1355 "COPY": lambda self: self._parse_copy_property(), 1356 "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs), 1357 "DATA_DELETION": lambda self: self._parse_data_deletion_property(), 1358 "DEFINER": lambda self: self._parse_definer(), 1359 "DETERMINISTIC": lambda self: self.expression( 1360 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1361 ), 1362 "DISTRIBUTED": lambda self: self._parse_distributed_property(), 1363 "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty), 1364 "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()), 1365 "DISTKEY": lambda self: self._parse_distkey(), 1366 "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty), 1367 "EMPTY": lambda self: self.expression(exp.EmptyProperty()), 1368 "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty), 1369 "ENVIRONMENT": lambda self: self.expression( 1370 exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment)) 1371 ), 1372 "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty), 1373 "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty), 1374 "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()), 1375 "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs), 1376 "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1377 "FREESPACE": lambda self: self._parse_freespace(), 1378 "GLOBAL": lambda self: self.expression(exp.GlobalProperty()), 1379 "HEAP": lambda self: self.expression(exp.HeapProperty()), 1380 "ICEBERG": lambda self: self.expression(exp.IcebergProperty()), 1381 "IMMUTABLE": lambda self: self.expression( 1382 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1383 ), 1384 "INHERITS": lambda self: self.expression( 1385 exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table)) 1386 ), 1387 "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())), 1388 "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs), 1389 "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty), 1390 "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"), 1391 "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"), 1392 "LIKE": lambda self: self._parse_create_like(), 1393 "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty), 1394 "LOCK": lambda self: self._parse_locking(), 1395 "LOCKING": lambda self: self._parse_locking(), 1396 "LOG": lambda self, **kwargs: self._parse_log(**kwargs), 1397 "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()), 1398 "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs), 1399 "MODIFIES": lambda self: self._parse_modifies_property(), 1400 "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)), 1401 "NO": lambda self: self._parse_no_property(), 1402 "ON": lambda self: self._parse_on_property(), 1403 "ORDER BY": lambda self: self._parse_order(skip_order_token=True), 1404 "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())), 1405 "PARTITION": lambda self: self._parse_partitioned_of(), 1406 "PARTITION BY": lambda self: self._parse_partitioned_by(), 1407 "PARTITIONED BY": lambda self: self._parse_partitioned_by(), 1408 "PARTITIONED_BY": lambda self: self._parse_partitioned_by(), 1409 "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True), 1410 "RANGE": lambda self: self._parse_dict_range(this="RANGE"), 1411 "READS": lambda self: self._parse_reads_property(), 1412 "REMOTE": lambda self: self._parse_remote_with_connection(), 1413 "RETURNS": lambda self: self._parse_returns(), 1414 "STRICT": lambda self: self.expression(exp.StrictProperty()), 1415 "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()), 1416 "ROW": lambda self: self._parse_row(), 1417 "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty), 1418 "SAMPLE": lambda self: self.expression( 1419 exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise()) 1420 ), 1421 "SECURE": lambda self: self.expression(exp.SecureProperty()), 1422 "SECURITY": lambda self: self._parse_sql_security(), 1423 "SQL SECURITY": lambda self: self._parse_sql_security(), 1424 "SET": lambda self: self.expression(exp.SetProperty(multi=False)), 1425 "SETTINGS": lambda self: self._parse_settings_property(), 1426 "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty), 1427 "SORTKEY": lambda self: self._parse_sortkey(), 1428 "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"), 1429 "STABLE": lambda self: self.expression( 1430 exp.StabilityProperty(this=exp.Literal.string("STABLE")) 1431 ), 1432 "STORED": lambda self: self._parse_stored(), 1433 "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(), 1434 "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(), 1435 "TEMP": lambda self: self.expression(exp.TemporaryProperty()), 1436 "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()), 1437 "TO": lambda self: self._parse_to_table(), 1438 "TRANSIENT": lambda self: self.expression(exp.TransientProperty()), 1439 "TRANSFORM": lambda self: self.expression( 1440 exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression)) 1441 ), 1442 "TTL": lambda self: self._parse_ttl(), 1443 "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1444 "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()), 1445 "VOLATILE": lambda self: self._parse_volatile_property(), 1446 "WITH": lambda self: self._parse_with_property(), 1447 } 1448 1449 CONSTRAINT_PARSERS: t.ClassVar = { 1450 "AUTOINCREMENT": lambda self: self._parse_auto_increment(), 1451 "AUTO_INCREMENT": lambda self: self._parse_auto_increment(), 1452 "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)), 1453 "CHECK": lambda self: self._parse_check_constraint(), 1454 "COLLATE": lambda self: self.expression( 1455 exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column()) 1456 ), 1457 "COMMENT": lambda self: self.expression( 1458 exp.CommentColumnConstraint(this=self._parse_string()) 1459 ), 1460 "COMPRESS": lambda self: self._parse_compress(), 1461 "CLUSTERED": lambda self: self.expression( 1462 exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1463 ), 1464 "NONCLUSTERED": lambda self: self.expression( 1465 exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1466 ), 1467 "DEFAULT": lambda self: self.expression( 1468 exp.DefaultColumnConstraint(this=self._parse_bitwise()) 1469 ), 1470 "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())), 1471 "EPHEMERAL": lambda self: self.expression( 1472 exp.EphemeralColumnConstraint(this=self._parse_bitwise()) 1473 ), 1474 "EXCLUDE": lambda self: self.expression( 1475 exp.ExcludeColumnConstraint(this=self._parse_index_params()) 1476 ), 1477 "FOREIGN KEY": lambda self: self._parse_foreign_key(), 1478 "FORMAT": lambda self: self.expression( 1479 exp.DateFormatColumnConstraint(this=self._parse_var_or_string()) 1480 ), 1481 "GENERATED": lambda self: self._parse_generated_as_identity(), 1482 "IDENTITY": lambda self: self._parse_auto_increment(), 1483 "INLINE": lambda self: self._parse_inline(), 1484 "LIKE": lambda self: self._parse_create_like(), 1485 "NOT": lambda self: self._parse_not_constraint(), 1486 "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)), 1487 "ON": lambda self: ( 1488 ( 1489 self._match(TokenType.UPDATE) 1490 and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function())) 1491 ) 1492 or self.expression(exp.OnProperty(this=self._parse_id_var())) 1493 ), 1494 "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())), 1495 "PERIOD": lambda self: self._parse_period_for_system_time(), 1496 "PRIMARY KEY": lambda self: self._parse_primary_key(), 1497 "REFERENCES": lambda self: self._parse_references(match=False), 1498 "TITLE": lambda self: self.expression( 1499 exp.TitleColumnConstraint(this=self._parse_var_or_string()) 1500 ), 1501 "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])), 1502 "UNIQUE": lambda self: self._parse_unique(), 1503 "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()), 1504 "WITH": lambda self: self.expression( 1505 exp.Properties(expressions=self._parse_wrapped_properties()) 1506 ), 1507 "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1508 "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1509 } 1510 1511 def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None: 1512 if not self._match(TokenType.L_PAREN, advance=False): 1513 # Partitioning by bucket or truncate follows the syntax: 1514 # PARTITION BY (BUCKET(..) | TRUNCATE(..)) 1515 # If we don't have parenthesis after each keyword, we should instead parse this as an identifier 1516 self._retreat(self._index - 1) 1517 return None 1518 1519 klass = ( 1520 exp.PartitionedByBucket 1521 if self._prev.text.upper() == "BUCKET" 1522 else exp.PartitionByTruncate 1523 ) 1524 1525 args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column()) 1526 this, expression = seq_get(args, 0), seq_get(args, 1) 1527 1528 if isinstance(this, exp.Literal): 1529 # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order 1530 # - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)` 1531 # - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)` 1532 # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)` 1533 # 1534 # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning 1535 # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties 1536 this, expression = expression, this 1537 1538 return self.expression(klass(this=this, expression=expression)) 1539 1540 ALTER_PARSERS: t.ClassVar = { 1541 "ADD": lambda self: self._parse_alter_table_add(), 1542 "AS": lambda self: self._parse_select(), 1543 "ALTER": lambda self: self._parse_alter_table_alter(), 1544 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1545 "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())), 1546 "DROP": lambda self: self._parse_alter_table_drop(), 1547 "RENAME": lambda self: self._parse_alter_table_rename(), 1548 "SET": lambda self: self._parse_alter_table_set(), 1549 "SWAP": lambda self: self.expression( 1550 exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True)) 1551 ), 1552 } 1553 1554 ALTER_ALTER_PARSERS: t.ClassVar = { 1555 "DISTKEY": lambda self: self._parse_alter_diststyle(), 1556 "DISTSTYLE": lambda self: self._parse_alter_diststyle(), 1557 "SORTKEY": lambda self: self._parse_alter_sortkey(), 1558 "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True), 1559 } 1560 1561 SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = { 1562 "CHECK", 1563 "EXCLUDE", 1564 "FOREIGN KEY", 1565 "LIKE", 1566 "PERIOD", 1567 "PRIMARY KEY", 1568 "UNIQUE", 1569 "BUCKET", 1570 "TRUNCATE", 1571 } 1572 1573 # Kinds that can't be a constraint name, so `CONSTRAINT` directly followed by one is unnamed 1574 RESERVED_CONSTRAINT_KINDS: t.ClassVar[set[str]] = set() 1575 1576 NO_PAREN_FUNCTION_PARSERS: t.ClassVar = { 1577 "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())), 1578 "CASE": lambda self: self._parse_case(), 1579 "CONNECT_BY_ROOT": lambda self: self.expression( 1580 exp.ConnectByRoot(this=self._parse_column()) 1581 ), 1582 "IF": lambda self: self._parse_if(), 1583 } 1584 1585 INVALID_FUNC_NAME_TOKENS: t.ClassVar = { 1586 TokenType.IDENTIFIER, 1587 TokenType.STRING, 1588 } 1589 1590 FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"} 1591 1592 KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice) 1593 1594 FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1595 **{ 1596 name: lambda self: self._parse_distinct_arg_function(exp.ArgMax) 1597 for name in exp.ArgMax.sql_names() 1598 }, 1599 **{ 1600 name: lambda self: self._parse_distinct_arg_function(exp.ArgMin) 1601 for name in exp.ArgMin.sql_names() 1602 }, 1603 "CAST": lambda self: self._parse_cast(self.STRICT_CAST), 1604 "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), 1605 "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), 1606 "CHAR": lambda self: self._parse_char(), 1607 "CHR": lambda self: self._parse_char(), 1608 "DECODE": lambda self: self._parse_decode(), 1609 "EXTRACT": lambda self: self._parse_extract(), 1610 "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), 1611 "GAP_FILL": lambda self: self._parse_gap_fill(), 1612 "INITCAP": lambda self: self._parse_initcap(), 1613 "JSON_OBJECT": lambda self: self._parse_json_object(), 1614 "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True), 1615 "JSON_TABLE": lambda self: self._parse_json_table(), 1616 "MATCH": lambda self: self._parse_match_against(), 1617 "NORMALIZE": lambda self: self._parse_normalize(), 1618 "OPENJSON": lambda self: self._parse_open_json(), 1619 "OVERLAY": lambda self: self._parse_overlay(), 1620 "POSITION": lambda self: self._parse_position(), 1621 "SAFE_CAST": lambda self: self._parse_cast(False, safe=True), 1622 "STRING_AGG": lambda self: self._parse_string_agg(), 1623 "SUBSTRING": lambda self: self._parse_substring(), 1624 "TRIM": lambda self: self._parse_trim(), 1625 "TRY_CAST": lambda self: self._parse_cast(False, safe=True), 1626 "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True), 1627 "WEIGHT_STRING": lambda self: self._parse_weight_string(), 1628 "XMLELEMENT": lambda self: self._parse_xml_element(), 1629 "XMLTABLE": lambda self: self._parse_xml_table(), 1630 } 1631 1632 QUERY_MODIFIER_PARSERS: t.ClassVar = { 1633 TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()), 1634 TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()), 1635 TokenType.WHERE: lambda self: ("where", self._parse_where()), 1636 TokenType.GROUP_BY: lambda self: ("group", self._parse_group()), 1637 TokenType.HAVING: lambda self: ("having", self._parse_having()), 1638 TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()), 1639 TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()), 1640 TokenType.ORDER_BY: lambda self: ("order", self._parse_order()), 1641 TokenType.LIMIT: lambda self: ("limit", self._parse_limit()), 1642 TokenType.FETCH: lambda self: ("limit", self._parse_limit()), 1643 TokenType.OFFSET: lambda self: ("offset", self._parse_offset()), 1644 TokenType.FOR: lambda self: ("locks", self._parse_locks()), 1645 TokenType.LOCK: lambda self: ("locks", self._parse_locks()), 1646 TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1647 TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1648 TokenType.CLUSTER_BY: lambda self: ( 1649 "cluster", 1650 self._parse_cluster(), 1651 ), 1652 TokenType.DISTRIBUTE_BY: lambda self: ( 1653 "distribute", 1654 self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), 1655 ), 1656 TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)), 1657 TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)), 1658 } 1659 QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS) 1660 1661 SET_PARSERS: t.ClassVar = { 1662 "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"), 1663 "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"), 1664 "SESSION": lambda self: self._parse_set_item_assignment("SESSION"), 1665 "TRANSACTION": lambda self: self._parse_set_transaction(), 1666 } 1667 1668 SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {} 1669 1670 TYPE_LITERAL_PARSERS: t.ClassVar = { 1671 exp.DType.JSON: lambda self, this, _: self.expression( 1672 exp.ParseJSON(this=this, is_literal=True) 1673 ), 1674 } 1675 1676 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1677 1678 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1679 1680 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1681 1682 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1683 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1684 "ISOLATION": ( 1685 ("LEVEL", "REPEATABLE", "READ"), 1686 ("LEVEL", "READ", "COMMITTED"), 1687 ("LEVEL", "READ", "UNCOMITTED"), 1688 ("LEVEL", "SERIALIZABLE"), 1689 ), 1690 "READ": ("WRITE", "ONLY"), 1691 } 1692 1693 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1694 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1695 "DO": ("NOTHING", "UPDATE"), 1696 } 1697 1698 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1699 "INSTEAD": (("OF",),), 1700 "BEFORE": tuple(), 1701 "AFTER": tuple(), 1702 } 1703 1704 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1705 "NOT": (("DEFERRABLE",),), 1706 "DEFERRABLE": tuple(), 1707 } 1708 1709 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1710 "SCALE": ("EXTEND", "NOEXTEND"), 1711 "SHARD": ("EXTEND", "NOEXTEND"), 1712 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1713 **dict.fromkeys( 1714 ( 1715 "SESSION", 1716 "GLOBAL", 1717 "KEEP", 1718 "NOKEEP", 1719 "ORDER", 1720 "NOORDER", 1721 "NOCACHE", 1722 "CYCLE", 1723 "NOCYCLE", 1724 "NOMINVALUE", 1725 "NOMAXVALUE", 1726 "NOSCALE", 1727 "NOSHARD", 1728 ), 1729 tuple(), 1730 ), 1731 } 1732 1733 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1734 1735 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1736 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1737 ) 1738 1739 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1740 1741 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1742 "TYPE": ("EVOLUTION",), 1743 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1744 } 1745 1746 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1747 1748 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1749 ("CALLER", "SELF", "OWNER"), tuple() 1750 ) 1751 1752 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1753 "NOT": ("ENFORCED",), 1754 "MATCH": ( 1755 "FULL", 1756 "PARTIAL", 1757 "SIMPLE", 1758 ), 1759 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1760 "USING": ( 1761 "BTREE", 1762 "HASH", 1763 ), 1764 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1765 } 1766 1767 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1768 "NO": ("OTHERS",), 1769 "CURRENT": ("ROW",), 1770 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1771 } 1772 1773 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1774 1775 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1776 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1777 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1778 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1779 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1780 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1781 ("FOR", "VERSION"): "VERSION", 1782 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1783 ("VERSION", "AS", "OF"): "VERSION", 1784 } 1785 1786 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1787 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1788 1789 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1790 1791 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1792 1793 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1794 1795 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1796 1797 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1798 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1799 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1800 1801 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1802 1803 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1804 1805 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1806 TokenType.CONSTRAINT, 1807 TokenType.FOREIGN_KEY, 1808 TokenType.INDEX, 1809 TokenType.KEY, 1810 TokenType.PRIMARY_KEY, 1811 TokenType.UNIQUE, 1812 } 1813 1814 # Constraint kinds that start an ALTER ... ADD action but aren't tokens of their own 1815 ADD_CONSTRAINT_KEYWORDS: t.ClassVar[set[str]] = set() 1816 1817 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1818 1819 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1820 1821 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1822 1823 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1824 "FILE_FORMAT", 1825 "COPY_OPTIONS", 1826 "FORMAT_OPTIONS", 1827 "CREDENTIAL", 1828 } 1829 1830 # IS binds above comparisons (high), alongside ordering comparisons (comparison), 1831 # alongside equality (equality), or below both (low). Preserve the historical 1832 # high precedence unless the dialect specifies otherwise. 1833 IS_PREDICATE_PRECEDENCE: t.ClassVar[IsPredicatePrecedence] = IsPredicatePrecedence.HIGH 1834 IS_PREDICATE_TOKENS: t.ClassVar = { 1835 TokenType.IS, 1836 TokenType.ISNULL, 1837 TokenType.NOTNULL, 1838 TokenType.NOT, 1839 } 1840 1841 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1842 1843 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1844 1845 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1846 1847 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1848 1849 # The style options for the DESCRIBE statement 1850 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1851 1852 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1853 1854 # The style options for the ANALYZE statement 1855 ANALYZE_STYLES: t.ClassVar = { 1856 "BUFFER_USAGE_LIMIT", 1857 "FULL", 1858 "LOCAL", 1859 "NO_WRITE_TO_BINLOG", 1860 "SAMPLE", 1861 "SKIP_LOCKED", 1862 "VERBOSE", 1863 } 1864 1865 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1866 "ALL": lambda self: self._parse_analyze_columns(), 1867 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1868 "DELETE": lambda self: self._parse_analyze_delete(), 1869 "DROP": lambda self: self._parse_analyze_histogram(), 1870 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1871 "LIST": lambda self: self._parse_analyze_list(), 1872 "PREDICATE": lambda self: self._parse_analyze_columns(), 1873 "UPDATE": lambda self: self._parse_analyze_histogram(), 1874 "VALIDATE": lambda self: self._parse_analyze_validate(), 1875 } 1876 1877 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1878 1879 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1880 1881 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1882 1883 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1884 1885 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1886 1887 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1888 1889 STRICT_CAST: t.ClassVar = True 1890 1891 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1892 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1893 # Whether an UNPIVOT outputs its value column(s) before the name column 1894 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1895 # Controls when an aggregation's name is included in a pivoted column's name: 1896 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1897 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1898 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1899 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1900 1901 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1902 1903 # Whether the table sample clause expects CSV syntax 1904 TABLESAMPLE_CSV: t.ClassVar = False 1905 1906 # The default method used for table sampling 1907 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1908 1909 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1910 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1911 1912 # Whether the TRIM function expects the characters to trim as its first argument 1913 TRIM_PATTERN_FIRST: t.ClassVar = False 1914 1915 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1916 STRING_ALIASES: t.ClassVar = False 1917 1918 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1919 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1920 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"} 1921 1922 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1923 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1924 1925 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1926 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1927 1928 # Whether the `:` operator is used to extract a value from a VARIANT column 1929 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1930 1931 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1932 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1933 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1934 1935 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1936 # If this is True and '(' is not found, the keyword will be treated as an identifier 1937 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1938 1939 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1940 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1941 1942 # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery) 1943 SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False 1944 1945 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1946 INTERVAL_SPANS: t.ClassVar = True 1947 1948 # Whether a PARTITION clause can follow a table reference 1949 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1950 1951 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1952 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1953 1954 # Whether the 'AS' keyword is optional in the CTE definition syntax 1955 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1956 1957 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1958 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1959 1960 # Whether dropping a column with an ALTER statement requires the presence of the COLUMN keyword 1961 ALTER_DROP_REQUIRES_COLUMN: t.ClassVar = True 1962 1963 # Whether Alter statements are allowed to contain Partition specifications 1964 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1965 1966 # Whether an ALTER can combine different actions, e.g. ADD COLUMN a INT, DROP COLUMN b 1967 ALTER_TABLE_MIXED_ACTIONS: t.ClassVar = False 1968 1969 # Whether an ALTER TABLE needs an action, e.g. MySQL's ALTER TABLE t ENGINE=InnoDB has none 1970 ALTER_TABLE_REQUIRES_ACTION: t.ClassVar = True 1971 1972 # Whether changing a column's type with ALTER COLUMN requires the TYPE keyword 1973 ALTER_COLUMN_TYPE_REQUIRES_KEYWORD: t.ClassVar = False 1974 1975 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1976 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1977 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1978 # as BigQuery, where all joins have the same precedence. 1979 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1980 1981 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1982 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1983 1984 # Whether map literals support arbitrary expressions as keys. 1985 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1986 # When False, keys are typically restricted to identifiers. 1987 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1988 1989 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1990 # is true for Snowflake but not for BigQuery which can also process strings 1991 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1992 1993 # Dialects like Databricks support JOINS without join criteria 1994 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 1995 ADD_JOIN_ON_TRUE: t.ClassVar = False 1996 1997 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 1998 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 1999 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 2000 2001 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 2002 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 2003 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 2004 2005 # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause, 2006 # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake) 2007 SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False 2008 2009 # Type names that denote a different type when they're quoted, so quoting has to be 2010 # preserved instead of resolving them into the built-in type of the same name. These 2011 # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR 2012 QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set() 2013 2014 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 2015 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 2016 2017 def __init__( 2018 self, 2019 error_level: ErrorLevel | None = None, 2020 error_message_context: int = 100, 2021 max_errors: int = 3, 2022 max_nodes: int = -1, 2023 dialect: DialectType = None, 2024 ): 2025 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 2026 self.error_message_context: int = error_message_context 2027 self.max_errors: int = max_errors 2028 self.max_nodes: int = max_nodes 2029 self.dialect: t.Any = _resolve_dialect(dialect) 2030 self.sql: str = "" 2031 self.errors: list[ParseError] = [] 2032 self._tokens: list[Token] = [] 2033 self._tokens_size: i64 = 0 2034 self._index: i64 = 0 2035 self._curr: Token = SENTINEL_NONE 2036 self._next: Token = SENTINEL_NONE 2037 self._prev: Token = SENTINEL_NONE 2038 self._prev_comments: list[str] = [] 2039 self._pipe_cte_counter: int = 0 2040 self._chunks: list[list[Token]] = [] 2041 self._chunk_index: i64 = 0 2042 self._node_count: int = 0 2043 2044 def reset(self) -> None: 2045 self.sql = "" 2046 self.errors = [] 2047 self._tokens = [] 2048 self._tokens_size = 0 2049 self._index = 0 2050 self._curr = SENTINEL_NONE 2051 self._next = SENTINEL_NONE 2052 self._prev = SENTINEL_NONE 2053 self._prev_comments = [] 2054 self._pipe_cte_counter = 0 2055 self._chunks = [] 2056 self._chunk_index = 0 2057 self._node_count = 0 2058 2059 def _advance(self, times: i64 = 1) -> None: 2060 index = self._index + times 2061 self._index = index 2062 tokens = self._tokens 2063 size = self._tokens_size 2064 self._curr = tokens[index] if index < size else SENTINEL_NONE 2065 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 2066 2067 if index > 0: 2068 prev = tokens[index - 1] 2069 self._prev = prev 2070 self._prev_comments = prev.comments 2071 else: 2072 self._prev = SENTINEL_NONE 2073 self._prev_comments = [] 2074 2075 def _advance_chunk(self) -> None: 2076 self._index = -1 2077 self._tokens = self._chunks[self._chunk_index] 2078 self._tokens_size = i64(len(self._tokens)) 2079 self._chunk_index += 1 2080 self._advance() 2081 2082 def _retreat(self, index: i64) -> None: 2083 if index != self._index: 2084 self._advance(index - self._index) 2085 2086 def _add_comments(self, expression: exp.Expr | None) -> None: 2087 if expression and self._prev_comments: 2088 expression.add_comments(self._prev_comments) 2089 self._prev_comments = [] 2090 2091 def _match( 2092 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2093 ) -> bool: 2094 if self._curr.token_type == token_type: 2095 if advance: 2096 self._advance() 2097 self._add_comments(expression) 2098 return True 2099 return False 2100 2101 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2102 if self._curr.token_type in types: 2103 if advance: 2104 self._advance() 2105 return True 2106 return False 2107 2108 def _match_pair( 2109 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2110 ) -> bool: 2111 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2112 if advance: 2113 self._advance(2) 2114 return True 2115 return False 2116 2117 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2118 if ( 2119 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2120 and self._curr.text.upper() in texts 2121 ): 2122 if advance: 2123 self._advance() 2124 return True 2125 return False 2126 2127 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2128 index = self._index 2129 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2130 for text in texts: 2131 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2132 self._advance() 2133 else: 2134 self._retreat(index) 2135 return False 2136 2137 if not advance: 2138 self._retreat(index) 2139 2140 return True 2141 2142 def _is_connected(self) -> bool: 2143 prev = self._prev 2144 curr = self._curr 2145 return bool(prev and curr and prev.end + 1 == curr.start) 2146 2147 def _find_sql(self, start: Token, end: Token) -> str: 2148 return self.sql[start.start : end.end + 1] 2149 2150 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2151 token = token or self._curr or self._prev or Token.string("") 2152 formatted_sql, start_context, highlight, end_context = highlight_sql( 2153 sql=self.sql, 2154 positions=[(token.start, token.end)], 2155 context_length=self.error_message_context, 2156 ) 2157 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2158 2159 error = ParseError.new( 2160 formatted_message, 2161 description=message, 2162 line=token.line, 2163 col=token.col, 2164 start_context=start_context, 2165 highlight=highlight, 2166 end_context=end_context, 2167 ) 2168 2169 if self.error_level == ErrorLevel.IMMEDIATE: 2170 raise error 2171 2172 self.errors.append(error) 2173 2174 def validate_expression(self, expression: E, args: list | None = None) -> E: 2175 if self.max_nodes > -1: 2176 self._node_count += 1 2177 if self._node_count > self.max_nodes: 2178 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2179 if self.error_level != ErrorLevel.IGNORE: 2180 for error_message in expression.error_messages(args): 2181 self.raise_error(error_message) 2182 return expression 2183 2184 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2185 index = self._index 2186 error_level = self.error_level 2187 this: T | None = None 2188 2189 self.error_level = ErrorLevel.IMMEDIATE 2190 try: 2191 this = parse_method() 2192 except ParseError: 2193 this = None 2194 finally: 2195 if not this or retreat: 2196 self._retreat(index) 2197 self.error_level = error_level 2198 2199 return this 2200 2201 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2202 """ 2203 Parses a list of tokens and returns a list of syntax trees, one tree 2204 per parsed SQL statement. 2205 2206 Args: 2207 raw_tokens: The list of tokens. 2208 sql: The original SQL string. 2209 2210 Returns: 2211 The list of the produced syntax trees. 2212 """ 2213 return self._parse( 2214 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2215 ) 2216 2217 def parse_into( 2218 self, 2219 expression_types: exp.IntoType, 2220 raw_tokens: list[Token], 2221 sql: str | None = None, 2222 ) -> list[exp.Expr | None]: 2223 """ 2224 Parses a list of tokens into a given Expr type. If a collection of Expr 2225 types is given instead, this method will try to parse the token list into each one 2226 of them, stopping at the first for which the parsing succeeds. 2227 2228 Args: 2229 expression_types: The expression type(s) to try and parse the token list into. 2230 raw_tokens: The list of tokens. 2231 sql: The original SQL string, used to produce helpful debug messages. 2232 2233 Returns: 2234 The target Expr. 2235 """ 2236 errors = [] 2237 for expression_type in ensure_list(expression_types): 2238 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2239 if not parser: 2240 raise TypeError(f"No parser registered for {expression_type}") 2241 2242 try: 2243 return self._parse(parser, raw_tokens, sql) 2244 except ParseError as e: 2245 e.errors[0]["into_expression"] = expression_type 2246 errors.append(e) 2247 2248 raise ParseError( 2249 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2250 errors=merge_errors(errors), 2251 ) from errors[-1] 2252 2253 def check_errors(self) -> None: 2254 """Logs or raises any found errors, depending on the chosen error level setting.""" 2255 if self.error_level == ErrorLevel.WARN: 2256 for error in self.errors: 2257 logger.error(str(error)) 2258 elif self.error_level == ErrorLevel.RAISE and self.errors: 2259 raise ParseError( 2260 concat_messages(self.errors, self.max_errors), 2261 errors=merge_errors(self.errors), 2262 ) 2263 2264 def expression( 2265 self, 2266 instance: E, 2267 token: Token | None = None, 2268 comments: list[str] | None = None, 2269 ) -> E: 2270 if token: 2271 instance.update_positions(token) 2272 instance.add_comments(comments) if comments else self._add_comments(instance) 2273 if not instance.is_primitive: 2274 instance = self.validate_expression(instance) 2275 return instance 2276 2277 def _parse_batch_statements( 2278 self, 2279 parse_method: t.Callable[[Parser], exp.Expr | None], 2280 sep_first_statement: bool = True, 2281 ) -> list[exp.Expr | None]: 2282 expressions = [] 2283 2284 # Chunkification binds if/while statements with the first statement of the body 2285 if sep_first_statement: 2286 self._match(TokenType.BEGIN) 2287 expressions.append(parse_method(self)) 2288 2289 chunks_length = len(self._chunks) 2290 while self._chunk_index < chunks_length: 2291 self._advance_chunk() 2292 2293 if self._match(TokenType.ELSE, advance=False): 2294 return expressions 2295 2296 if expressions and not self._next and self._match(TokenType.END): 2297 expressions.append(exp.EndStatement()) 2298 continue 2299 2300 expressions.append(parse_method(self)) 2301 2302 if self._index < self._tokens_size: 2303 self.raise_error("Invalid expression / Unexpected token") 2304 2305 self.check_errors() 2306 2307 return expressions 2308 2309 def _parse( 2310 self, 2311 parse_method: t.Callable[[Parser], exp.Expr | None], 2312 raw_tokens: list[Token], 2313 sql: str | None = None, 2314 ) -> list[exp.Expr | None]: 2315 self.reset() 2316 self.sql = sql or "" 2317 2318 total = len(raw_tokens) 2319 chunks: list[list[Token]] = [[]] 2320 2321 for i, token in enumerate(raw_tokens): 2322 if token.token_type == TokenType.SEMICOLON: 2323 if token.comments: 2324 chunks.append([token]) 2325 2326 if i < total - 1: 2327 chunks.append([]) 2328 else: 2329 chunks[-1].append(token) 2330 2331 self._chunks = chunks 2332 2333 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2334 2335 def _warn_unsupported(self) -> None: 2336 if self._tokens_size <= 1: 2337 return 2338 2339 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2340 # interested in emitting a warning for the one being currently processed. 2341 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2342 2343 logger.warning( 2344 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2345 ) 2346 2347 def _parse_command(self) -> exp.Command: 2348 self._warn_unsupported() 2349 comments = self._prev_comments 2350 return self.expression( 2351 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2352 comments=comments, 2353 ) 2354 2355 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2356 start = self._prev 2357 exists = self._parse_exists() if allow_exists else None 2358 2359 self._match(TokenType.ON) 2360 2361 materialized = self._match_text_seq("MATERIALIZED") 2362 kind = self._match_set(self.CREATABLES) and self._prev 2363 if not kind: 2364 return self._parse_as_command(start) 2365 2366 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2367 this = self._parse_user_defined_function(kind=kind.token_type) 2368 elif kind.token_type == TokenType.TABLE: 2369 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2370 elif kind.token_type == TokenType.COLUMN: 2371 this = self._parse_column() 2372 else: 2373 this = self._parse_table_parts(schema=True) 2374 2375 self._match(TokenType.IS) 2376 2377 return self.expression( 2378 exp.Comment( 2379 this=this, 2380 kind=kind.text, 2381 expression=self._parse_string(), 2382 exists=exists, 2383 materialized=materialized, 2384 ) 2385 ) 2386 2387 def _parse_to_table( 2388 self, 2389 ) -> exp.ToTableProperty: 2390 table = self._parse_table_parts(schema=True) 2391 return self.expression(exp.ToTableProperty(this=table)) 2392 2393 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2394 def _parse_ttl(self) -> exp.Expr: 2395 def _parse_ttl_action() -> exp.Expr | None: 2396 this = self._parse_bitwise() 2397 2398 if self._match_text_seq("DELETE"): 2399 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2400 if self._match_text_seq("RECOMPRESS"): 2401 return self.expression( 2402 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2403 ) 2404 if self._match_text_seq("TO", "DISK"): 2405 return self.expression( 2406 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2407 ) 2408 if self._match_text_seq("TO", "VOLUME"): 2409 return self.expression( 2410 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2411 ) 2412 2413 return this 2414 2415 expressions = self._parse_csv(_parse_ttl_action) 2416 where = self._parse_where() 2417 group = self._parse_group() 2418 2419 aggregates = None 2420 if group and self._match(TokenType.SET): 2421 aggregates = self._parse_csv(self._parse_set_item) 2422 2423 return self.expression( 2424 exp.MergeTreeTTL( 2425 expressions=expressions, where=where, group=group, aggregates=aggregates 2426 ) 2427 ) 2428 2429 def _parse_condition(self) -> exp.Expr | None: 2430 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2431 2432 def _parse_block(self) -> exp.Block: 2433 return self.expression( 2434 exp.Block( 2435 expressions=self._parse_batch_statements( 2436 parse_method=lambda self: self._parse_statement() 2437 ) 2438 ) 2439 ) 2440 2441 def _parse_whileblock(self) -> exp.WhileBlock: 2442 return self.expression( 2443 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2444 ) 2445 2446 def _parse_statement(self) -> exp.Expr | None: 2447 if not self._curr: 2448 return None 2449 2450 if self._match_set(self.STATEMENT_PARSERS): 2451 comments = self._prev_comments 2452 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2453 stmt.add_comments(comments, prepend=True) 2454 return stmt 2455 2456 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2457 return self._parse_command() 2458 2459 if self._match_text_seq("WHILE"): 2460 return self._parse_whileblock() 2461 2462 expression = self._parse_expression() 2463 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2464 2465 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2466 expression = self._parse_pipe_syntax_query(expression) 2467 2468 return self._parse_query_modifiers(expression) 2469 2470 def _parse_drop(self, exists: bool = False, kind: str | None = None) -> exp.Drop | exp.Command: 2471 start = self._prev 2472 2473 if not kind: 2474 temporary = self._match(TokenType.TEMPORARY) 2475 materialized = self._match_text_seq("MATERIALIZED") 2476 iceberg = self._match_text_seq("ICEBERG") 2477 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 2478 else: 2479 temporary = False 2480 materialized = False 2481 iceberg = False 2482 2483 if not kind or (iceberg and kind != "TABLE"): 2484 return self._parse_as_command(start) 2485 2486 concurrently = ( 2487 kind == "INDEX" 2488 and self._next.token_type in self.ID_VAR_TOKENS 2489 and self._match_text_seq("CONCURRENTLY") 2490 ) 2491 2492 if_exists = exists or self._parse_exists() 2493 2494 tables: exp.Expr | list[exp.Expr] | None 2495 if kind == "COLUMN": 2496 # A dropped column named after a keyword, like `any` or `charset`, is only a name 2497 name = ( 2498 None 2499 if self._curr.text.upper() in self.NO_PAREN_FUNCTION_PARSERS 2500 else self._parse_column() 2501 ) or self._parse_id_var() 2502 tables = ( 2503 self.expression(exp.Column(this=name)) if isinstance(name, exp.Identifier) else name 2504 ) 2505 elif kind in ("TABLE", "VIEW"): 2506 tables = self._parse_csv(lambda: self._parse_table_parts(schema=True)) 2507 else: 2508 tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2509 2510 cluster: exp.Expr | None = None 2511 if self._match(TokenType.ON): 2512 if kind == "INDEX": 2513 cluster = self.expression(exp.OnProperty(this=self._parse_table_parts())) 2514 else: 2515 cluster = self._parse_on_property() 2516 2517 if self._match(TokenType.L_PAREN, advance=False): 2518 expressions = self._parse_wrapped_csv(self._parse_types) 2519 else: 2520 expressions = None 2521 2522 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2523 2524 return self.expression( 2525 exp.Drop( 2526 exists=if_exists, 2527 tables=ensure_list(tables), 2528 expressions=expressions, 2529 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2530 temporary=temporary, 2531 materialized=materialized, 2532 cascade=cascade_or_restrict == "CASCADE", 2533 restrict=cascade_or_restrict == "RESTRICT", 2534 constraints=self._match_text_seq("CONSTRAINTS"), 2535 purge=self._match_text_seq("PURGE"), 2536 cluster=cluster, 2537 concurrently=concurrently, 2538 sync=self._match_text_seq("SYNC"), 2539 iceberg=iceberg, 2540 force=self._match_text_seq("FORCE"), 2541 ) 2542 ) 2543 2544 def _parse_exists(self, not_: bool = False) -> bool | None: 2545 if not_: 2546 return self._match_text_seq("IF", "NOT", "EXISTS") 2547 return self._match_text_seq("IF", "EXISTS") 2548 2549 def _parse_create(self) -> exp.Create | exp.Command: 2550 # Note: this can't be None because we've matched a statement parser 2551 start = self._prev 2552 2553 replace = ( 2554 start.token_type == TokenType.REPLACE 2555 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2556 or self._match_pair(TokenType.OR, TokenType.ALTER) 2557 ) 2558 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2559 2560 unique = self._match(TokenType.UNIQUE) 2561 2562 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2563 clustered = True 2564 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2565 "COLUMNSTORE" 2566 ): 2567 clustered = False 2568 else: 2569 clustered = None 2570 2571 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2572 self._advance() 2573 2574 properties = None 2575 create_token = self._match_set(self.CREATABLES) and self._prev 2576 2577 if not create_token: 2578 # exp.Properties.Location.POST_CREATE 2579 properties = self._parse_properties() 2580 create_token = self._match_set(self.CREATABLES) and self._prev 2581 2582 if not properties or not create_token: 2583 return self._parse_as_command(start) 2584 2585 create_token_type = t.cast(Token, create_token).token_type 2586 2587 concurrently = self._match_text_seq("CONCURRENTLY") 2588 exists = self._parse_exists(not_=True) 2589 this = None 2590 expression: exp.Expr | None = None 2591 indexes = None 2592 no_schema_binding = None 2593 begin = None 2594 clone = None 2595 2596 def extend_props(temp_props: exp.Properties | None) -> None: 2597 nonlocal properties 2598 if properties and temp_props: 2599 properties.expressions.extend(temp_props.expressions) 2600 elif temp_props: 2601 properties = temp_props 2602 2603 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2604 this = self._parse_user_defined_function(kind=create_token_type) 2605 2606 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2607 extend_props(self._parse_properties()) 2608 2609 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2610 2611 if ( 2612 not expression 2613 and create_token_type == TokenType.FUNCTION 2614 and isinstance(this, exp.UserDefinedFunction) 2615 and this.args.get("wrapped") 2616 ): 2617 pre_table_index = self._index 2618 is_table = self._match(TokenType.TABLE) 2619 2620 expression = self._parse_expression() 2621 overload_mode = bool( 2622 expression 2623 and self._curr.token_type == TokenType.COMMA 2624 and self._next.token_type == TokenType.L_PAREN 2625 ) 2626 if not overload_mode: 2627 self._retreat(pre_table_index) 2628 is_table = False 2629 expression = None 2630 else: 2631 is_table = False 2632 overload_mode = False 2633 2634 extend_props(self._parse_function_properties()) 2635 2636 if not expression: 2637 if self._match(TokenType.COMMAND): 2638 expression = self._parse_as_command(self._prev) 2639 else: 2640 begin = self._match(TokenType.BEGIN) 2641 return_ = self._match_text_seq("RETURN") 2642 2643 if self._match(TokenType.STRING, advance=False): 2644 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2645 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2646 expression = self._parse_string() 2647 extend_props(self._parse_properties()) 2648 else: 2649 expression = ( 2650 self._parse_user_defined_function_expression() 2651 if create_token_type == TokenType.FUNCTION 2652 else self._parse_block() 2653 ) 2654 2655 if return_: 2656 expression = self.expression(exp.Return(this=expression)) 2657 2658 if overload_mode and expression: 2659 expression = self._parse_macro_overloads( 2660 t.cast(exp.UserDefinedFunction, this), expression, is_table 2661 ) 2662 elif create_token_type == TokenType.INDEX: 2663 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2664 if not self._match(TokenType.ON): 2665 index = self._parse_id_var() 2666 anonymous = False 2667 else: 2668 index = None 2669 anonymous = True 2670 2671 this = self._parse_index(index=index, anonymous=anonymous) 2672 elif ( 2673 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2674 ) or create_token_type == TokenType.TRIGGER: 2675 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2676 create_token = self._prev 2677 2678 trigger_name = self._parse_id_var() 2679 if not trigger_name: 2680 return self._parse_as_command(start) 2681 2682 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2683 timing = timing_var.this if timing_var else None 2684 if not timing: 2685 return self._parse_as_command(start) 2686 2687 events = self._parse_trigger_events() 2688 if not self._match(TokenType.ON): 2689 self.raise_error("Expected ON in trigger definition") 2690 2691 table = self._parse_table_parts() 2692 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2693 deferrable, initially = self._parse_trigger_deferrable() 2694 referencing = self._parse_trigger_referencing() 2695 for_each = self._parse_trigger_for_each() 2696 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2697 self._parse_disjunction, optional=True 2698 ) 2699 execute = self._parse_trigger_execute() 2700 2701 if execute is None: 2702 return self._parse_as_command(start) 2703 2704 trigger_props = self.expression( 2705 exp.TriggerProperties( 2706 table=table, 2707 timing=timing, 2708 events=events, 2709 execute=execute, 2710 constraint=is_constraint, 2711 referenced_table=referenced_table, 2712 deferrable=deferrable, 2713 initially=initially, 2714 referencing=referencing, 2715 for_each=for_each, 2716 when=when, 2717 ) 2718 ) 2719 2720 this = trigger_name 2721 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2722 elif create_token_type == TokenType.TYPE: 2723 this = self._parse_table_parts(schema=True) 2724 if not this or not self._match(TokenType.ALIAS): 2725 return self._parse_as_command(start) 2726 2727 if self._match(TokenType.ENUM): 2728 expression = exp.DataType( 2729 this=exp.DType.ENUM, 2730 expressions=self._parse_wrapped_csv(self._parse_string), 2731 ) 2732 elif self._match(TokenType.L_PAREN, advance=False): 2733 expression = self._parse_schema() 2734 else: 2735 return self._parse_as_command(start) 2736 elif create_token_type in self.DB_CREATABLES: 2737 table_parts = self._parse_table_parts( 2738 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2739 ) 2740 2741 # exp.Properties.Location.POST_NAME 2742 self._match(TokenType.COMMA) 2743 extend_props(self._parse_properties(before=True)) 2744 2745 this = self._parse_schema(this=table_parts) 2746 2747 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2748 extend_props(self._parse_properties()) 2749 2750 has_alias = self._match(TokenType.ALIAS) 2751 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2752 # exp.Properties.Location.POST_ALIAS 2753 extend_props(self._parse_properties()) 2754 2755 if create_token_type == TokenType.SEQUENCE: 2756 expression = self._parse_types() 2757 props = self._parse_properties() 2758 if props: 2759 sequence_props = exp.SequenceProperties() 2760 options = [] 2761 for prop in props: 2762 if isinstance(prop, exp.SequenceProperties): 2763 for arg, value in prop.args.items(): 2764 if arg == "options": 2765 options.extend(value) 2766 else: 2767 sequence_props.set(arg, value) 2768 prop.pop() 2769 2770 if options: 2771 sequence_props.set("options", options) 2772 2773 props.append("expressions", sequence_props) 2774 extend_props(props) 2775 else: 2776 expression = self._parse_ddl_select() 2777 2778 # Some dialects also support using a table as an alias instead of a SELECT. 2779 # Here we fallback to this as an alternative. 2780 if not expression and has_alias: 2781 expression = self._try_parse(self._parse_table_parts) 2782 2783 if create_token_type == TokenType.TABLE: 2784 # exp.Properties.Location.POST_EXPRESSION 2785 extend_props(self._parse_properties()) 2786 2787 indexes = [] 2788 while True: 2789 index = self._parse_index() 2790 2791 # exp.Properties.Location.POST_INDEX 2792 extend_props(self._parse_properties()) 2793 if not index: 2794 break 2795 else: 2796 self._match(TokenType.COMMA) 2797 indexes.append(index) 2798 elif create_token_type == TokenType.VIEW: 2799 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2800 no_schema_binding = True 2801 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2802 extend_props(self._parse_properties()) 2803 2804 shallow = self._match_text_seq("SHALLOW") 2805 2806 if self._match_texts(self.CLONE_KEYWORDS): 2807 copy = self._prev.text.lower() == "copy" 2808 clone = self.expression( 2809 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2810 ) 2811 2812 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2813 return self._parse_as_command(start) 2814 2815 create_kind_text = create_token.text.upper() 2816 return self.expression( 2817 exp.Create( 2818 this=this, 2819 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2820 replace=replace, 2821 refresh=refresh, 2822 unique=unique, 2823 expression=expression, 2824 exists=exists, 2825 properties=properties, 2826 indexes=indexes, 2827 no_schema_binding=no_schema_binding, 2828 begin=begin, 2829 clone=clone, 2830 concurrently=concurrently, 2831 clustered=clustered, 2832 ) 2833 ) 2834 2835 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2836 seq = exp.SequenceProperties() 2837 2838 options = [] 2839 index = self._index 2840 2841 while self._curr: 2842 self._match(TokenType.COMMA) 2843 if self._match_text_seq("INCREMENT"): 2844 self._match_text_seq("BY") 2845 self._match_text_seq("=") 2846 seq.set("increment", self._parse_term()) 2847 elif self._match_text_seq("MINVALUE"): 2848 seq.set("minvalue", self._parse_term()) 2849 elif self._match_text_seq("MAXVALUE"): 2850 seq.set("maxvalue", self._parse_term()) 2851 elif self._match_text_seq("START"): 2852 self._match_text_seq("WITH") 2853 self._match_text_seq("=") 2854 seq.set("start", self._parse_term()) 2855 elif self._match_text_seq("CACHE"): 2856 # T-SQL allows empty CACHE which is initialized dynamically 2857 seq.set("cache", self._parse_number() or True) 2858 elif self._match_text_seq("OWNED", "BY"): 2859 # "OWNED BY NONE" is the default 2860 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2861 else: 2862 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2863 if opt: 2864 options.append(opt) 2865 else: 2866 break 2867 2868 seq.set("options", options if options else None) 2869 return None if self._index == index else seq 2870 2871 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2872 events = [] 2873 2874 while True: 2875 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2876 2877 if not event_type: 2878 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2879 2880 columns = ( 2881 self._parse_csv(self._parse_column) 2882 if event_type == "UPDATE" and self._match_text_seq("OF") 2883 else None 2884 ) 2885 2886 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2887 2888 if not self._match(TokenType.OR): 2889 break 2890 2891 return events 2892 2893 def _parse_trigger_deferrable( 2894 self, 2895 ) -> tuple[str | None, str | None]: 2896 deferrable_var = self._parse_var_from_options( 2897 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2898 ) 2899 deferrable = deferrable_var.this if deferrable_var else None 2900 2901 initially = None 2902 if deferrable and self._match_text_seq("INITIALLY"): 2903 initially = ( 2904 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2905 ) 2906 2907 return deferrable, initially 2908 2909 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2910 if not self._match_text_seq(keyword): 2911 return None 2912 if not self._match_text_seq("TABLE"): 2913 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2914 self._match_text_seq("AS") 2915 return self._parse_id_var() 2916 2917 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2918 if not self._match_text_seq("REFERENCING"): 2919 return None 2920 2921 old_alias = None 2922 new_alias = None 2923 2924 while True: 2925 if alias := self._parse_trigger_referencing_clause("OLD"): 2926 if old_alias is not None: 2927 self.raise_error("Duplicate OLD clause in REFERENCING") 2928 old_alias = alias 2929 elif alias := self._parse_trigger_referencing_clause("NEW"): 2930 if new_alias is not None: 2931 self.raise_error("Duplicate NEW clause in REFERENCING") 2932 new_alias = alias 2933 else: 2934 break 2935 2936 if old_alias is None and new_alias is None: 2937 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2938 2939 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2940 2941 def _parse_trigger_for_each(self) -> str | None: 2942 if not self._match_text_seq("FOR", "EACH"): 2943 return None 2944 2945 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2946 2947 def _parse_trigger_execute(self) -> exp.TriggerExecute | None: 2948 if not self._match(TokenType.EXECUTE): 2949 return None 2950 2951 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2952 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2953 2954 func_call = self._parse_column() 2955 return self.expression(exp.TriggerExecute(this=func_call)) 2956 2957 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2958 # only used for teradata currently 2959 self._match(TokenType.COMMA) 2960 2961 kwargs = { 2962 "no": self._match_text_seq("NO"), 2963 "dual": self._match_text_seq("DUAL"), 2964 "before": self._match_text_seq("BEFORE"), 2965 "default": self._match_text_seq("DEFAULT"), 2966 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2967 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2968 "after": self._match_text_seq("AFTER"), 2969 "minimum": self._match_texts(("MIN", "MINIMUM")), 2970 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2971 } 2972 2973 if self._match_texts(self.PROPERTY_PARSERS): 2974 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2975 try: 2976 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2977 except TypeError: 2978 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2979 2980 if self._match_pair(TokenType.CHAR, TokenType.SET): 2981 return self._parse_character_set(default=bool(kwargs["default"])) 2982 2983 return None 2984 2985 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2986 return self._parse_wrapped_csv(self._parse_property) 2987 2988 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2989 if self._match_texts(self.PROPERTY_PARSERS): 2990 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2991 2992 if self._match_pair(TokenType.CHAR, TokenType.SET): 2993 return self._parse_character_set() 2994 2995 if self._match(TokenType.DEFAULT): 2996 if self._match_texts(self.PROPERTY_PARSERS): 2997 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 2998 2999 if self._match_pair(TokenType.CHAR, TokenType.SET): 3000 return self._parse_character_set(default=True) 3001 3002 if self._match_text_seq("COMPOUND", "SORTKEY"): 3003 return self._parse_sortkey(compound=True) 3004 3005 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 3006 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 3007 3008 if self._match_text_seq("NOT", "DETERMINISTIC"): 3009 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3010 3011 index = self._index 3012 3013 seq_props = self._parse_sequence_properties() 3014 if seq_props: 3015 return seq_props 3016 3017 self._retreat(index) 3018 return self._parse_key_value_property() 3019 3020 def _parse_key_value_property( 3021 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 3022 ) -> exp.Property | None: 3023 index = self._index 3024 key = self._parse_column() 3025 3026 if not self._match(TokenType.EQ): 3027 self._retreat(index) 3028 return None 3029 3030 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 3031 if isinstance(key, exp.Column): 3032 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 3033 3034 value = ( 3035 parse_value() 3036 if parse_value 3037 else self._parse_bitwise() or self._parse_var(any_token=True) 3038 ) 3039 3040 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 3041 if isinstance(value, exp.Column): 3042 value = exp.var(value.name) 3043 3044 return self.expression(exp.Property(this=key, value=value)) 3045 3046 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 3047 if self._match_text_seq("BY"): 3048 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 3049 3050 self._match(TokenType.ALIAS) 3051 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 3052 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 3053 3054 return self.expression( 3055 exp.FileFormatProperty( 3056 this=( 3057 self.expression( 3058 exp.InputOutputFormat( 3059 input_format=input_format, output_format=output_format 3060 ) 3061 ) 3062 if input_format or output_format 3063 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 3064 ), 3065 hive_format=True, 3066 ) 3067 ) 3068 3069 def _parse_unquoted_field(self) -> exp.Expr | None: 3070 field = self._parse_field() 3071 if isinstance(field, exp.Identifier) and not field.quoted: 3072 field = exp.var(field) 3073 3074 return field 3075 3076 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 3077 self._match(TokenType.EQ) 3078 self._match(TokenType.ALIAS) 3079 3080 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 3081 3082 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 3083 properties = [] 3084 while True: 3085 if before: 3086 prop = self._parse_property_before() 3087 else: 3088 prop = self._parse_property() 3089 if not prop: 3090 break 3091 for p in ensure_list(prop): 3092 properties.append(p) 3093 3094 self._match(TokenType.COMMA, expression=properties[-1]) 3095 3096 if properties: 3097 return self.expression(exp.Properties(expressions=properties)) 3098 3099 return None 3100 3101 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3102 return self.expression( 3103 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3104 ) 3105 3106 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3107 return self.expression( 3108 exp.SqlSecurityProperty( 3109 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3110 ) 3111 ) 3112 3113 def _parse_settings_property(self) -> exp.SettingsProperty: 3114 return self.expression( 3115 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3116 ) 3117 3118 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3119 if not self._match_text_seq("ON", "NULL", "INPUT"): 3120 self._retreat(self._index - 1) 3121 return None 3122 3123 return self.expression(exp.CalledOnNullInputProperty()) 3124 3125 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3126 if self._index >= 2: 3127 pre_volatile_token = self._tokens[self._index - 2] 3128 else: 3129 pre_volatile_token = None 3130 3131 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3132 return exp.VolatileProperty() 3133 3134 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3135 3136 def _parse_retention_period(self) -> exp.Var: 3137 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3138 number = self._parse_number() 3139 number_str = f"{number} " if number else "" 3140 unit = self._parse_var(any_token=True) 3141 return exp.var(f"{number_str}{unit}") 3142 3143 def _parse_system_versioning_property( 3144 self, with_: bool = False 3145 ) -> exp.WithSystemVersioningProperty: 3146 self._match(TokenType.EQ) 3147 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3148 3149 if self._match_text_seq("OFF"): 3150 prop.set("on", False) 3151 return prop 3152 3153 self._match(TokenType.ON) 3154 if self._match(TokenType.L_PAREN): 3155 while self._curr and not self._match(TokenType.R_PAREN): 3156 if self._match_text_seq("HISTORY_TABLE", "="): 3157 prop.set("this", self._parse_table_parts()) 3158 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3159 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3160 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3161 prop.set("retention_period", self._parse_retention_period()) 3162 3163 self._match(TokenType.COMMA) 3164 3165 return prop 3166 3167 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3168 self._match(TokenType.EQ) 3169 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3170 prop = self.expression(exp.DataDeletionProperty(on=on)) 3171 3172 if self._match(TokenType.L_PAREN): 3173 while self._curr and not self._match(TokenType.R_PAREN): 3174 if self._match_text_seq("FILTER_COLUMN", "="): 3175 prop.set("filter_column", self._parse_column()) 3176 elif self._match_text_seq("RETENTION_PERIOD", "="): 3177 prop.set("retention_period", self._parse_retention_period()) 3178 3179 self._match(TokenType.COMMA) 3180 3181 return prop 3182 3183 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3184 kind = "HASH" 3185 expressions: list[exp.Expr] | None = None 3186 if self._match_text_seq("BY", "HASH"): 3187 expressions = self._parse_wrapped_csv(self._parse_id_var) 3188 elif self._match_text_seq("BY", "RANDOM"): 3189 kind = "RANDOM" 3190 3191 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3192 buckets: exp.Expr | None = None 3193 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3194 buckets = self._parse_number() 3195 3196 return self.expression( 3197 exp.DistributedByProperty( 3198 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3199 ) 3200 ) 3201 3202 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3203 self._match_text_seq("KEY") 3204 expressions = self._parse_wrapped_id_vars() 3205 return self.expression(expr_type(expressions=expressions)) 3206 3207 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3208 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3209 prop = self._parse_system_versioning_property(with_=True) 3210 self._match_r_paren() 3211 return prop 3212 3213 if self._match(TokenType.L_PAREN, advance=False): 3214 result: list[exp.Expr] = [] 3215 for i in self._parse_wrapped_properties(): 3216 result.extend(i) if isinstance(i, list) else result.append(i) 3217 return result 3218 3219 if self._match_text_seq("JOURNAL"): 3220 return self._parse_withjournaltable() 3221 3222 if self._match_texts(self.VIEW_ATTRIBUTES): 3223 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3224 3225 if self._match_text_seq("DATA"): 3226 return self._parse_withdata(no=False) 3227 elif self._match_text_seq("NO", "DATA"): 3228 return self._parse_withdata(no=True) 3229 3230 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3231 return self._parse_serde_properties(with_=True) 3232 3233 if self._match(TokenType.SCHEMA): 3234 return self.expression( 3235 exp.WithSchemaBindingProperty( 3236 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3237 ) 3238 ) 3239 3240 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3241 return self.expression( 3242 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3243 ) 3244 3245 if not self._next: 3246 return None 3247 3248 return self._parse_withisolatedloading() 3249 3250 def _parse_procedure_option(self) -> exp.Expr | None: 3251 if self._match_text_seq("EXECUTE", "AS"): 3252 return self.expression( 3253 exp.ExecuteAsProperty( 3254 this=self._parse_var_from_options( 3255 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3256 ) 3257 or self._parse_string() 3258 ) 3259 ) 3260 3261 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3262 3263 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3264 def _parse_definer(self) -> exp.DefinerProperty | None: 3265 self._match(TokenType.EQ) 3266 3267 user = self._parse_id_var() 3268 self._match(TokenType.PARAMETER) 3269 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3270 3271 if not user or not host: 3272 return None 3273 3274 return exp.DefinerProperty(this=f"{user}@{host}") 3275 3276 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3277 self._match(TokenType.TABLE) 3278 self._match(TokenType.EQ) 3279 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3280 3281 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3282 return self.expression(exp.LogProperty(no=no)) 3283 3284 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3285 return self.expression(exp.JournalProperty(**kwargs)) 3286 3287 def _parse_checksum(self) -> exp.ChecksumProperty: 3288 self._match(TokenType.EQ) 3289 3290 on = None 3291 if self._match(TokenType.ON): 3292 on = True 3293 elif self._match_text_seq("OFF"): 3294 on = False 3295 3296 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3297 3298 def _parse_cluster(self) -> exp.Cluster: 3299 self._match(TokenType.CLUSTER_BY) 3300 return self.expression( 3301 exp.Cluster( 3302 expressions=self._parse_csv(self._parse_column), 3303 ) 3304 ) 3305 3306 def _parse_cluster_property(self) -> exp.ClusterProperty: 3307 return self.expression( 3308 exp.ClusterProperty( 3309 expressions=self._parse_wrapped_csv(self._parse_column), 3310 ) 3311 ) 3312 3313 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3314 self._match_text_seq("BY") 3315 3316 self._match_l_paren() 3317 expressions = self._parse_csv(self._parse_column) 3318 self._match_r_paren() 3319 3320 if self._match_text_seq("SORTED", "BY"): 3321 self._match_l_paren() 3322 sorted_by = self._parse_csv(self._parse_ordered) 3323 self._match_r_paren() 3324 else: 3325 sorted_by = None 3326 3327 self._match(TokenType.INTO) 3328 buckets = self._parse_number() 3329 self._match_text_seq("BUCKETS") 3330 3331 return self.expression( 3332 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3333 ) 3334 3335 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3336 if not self._match_text_seq("GRANTS"): 3337 self._retreat(self._index - 1) 3338 return None 3339 3340 return self.expression(exp.CopyGrantsProperty()) 3341 3342 def _parse_freespace(self) -> exp.FreespaceProperty: 3343 self._match(TokenType.EQ) 3344 return self.expression( 3345 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3346 ) 3347 3348 def _parse_mergeblockratio( 3349 self, no: bool = False, default: bool = False 3350 ) -> exp.MergeBlockRatioProperty: 3351 if self._match(TokenType.EQ): 3352 return self.expression( 3353 exp.MergeBlockRatioProperty( 3354 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3355 ) 3356 ) 3357 3358 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3359 3360 def _parse_datablocksize( 3361 self, 3362 default: bool | None = None, 3363 minimum: bool | None = None, 3364 maximum: bool | None = None, 3365 ) -> exp.DataBlocksizeProperty: 3366 self._match(TokenType.EQ) 3367 size = self._parse_number() 3368 3369 units = None 3370 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3371 units = self._prev.text 3372 3373 return self.expression( 3374 exp.DataBlocksizeProperty( 3375 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3376 ) 3377 ) 3378 3379 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3380 self._match(TokenType.EQ) 3381 always = self._match_text_seq("ALWAYS") 3382 manual = self._match_text_seq("MANUAL") 3383 never = self._match_text_seq("NEVER") 3384 default = self._match_text_seq("DEFAULT") 3385 3386 autotemp = None 3387 if self._match_text_seq("AUTOTEMP"): 3388 autotemp = self._parse_schema() 3389 3390 return self.expression( 3391 exp.BlockCompressionProperty( 3392 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3393 ) 3394 ) 3395 3396 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3397 index = self._index 3398 no = self._match_text_seq("NO") 3399 concurrent = self._match_text_seq("CONCURRENT") 3400 3401 if not self._match_text_seq("ISOLATED", "LOADING"): 3402 self._retreat(index) 3403 return None 3404 3405 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3406 return self.expression( 3407 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3408 ) 3409 3410 def _parse_locking(self) -> exp.LockingProperty: 3411 if self._match(TokenType.TABLE): 3412 kind = "TABLE" 3413 elif self._match(TokenType.VIEW): 3414 kind = "VIEW" 3415 elif self._match(TokenType.ROW): 3416 kind = "ROW" 3417 elif self._match_text_seq("DATABASE"): 3418 kind = "DATABASE" 3419 else: 3420 kind = None 3421 3422 if kind in ("DATABASE", "TABLE", "VIEW"): 3423 this = self._parse_table_parts() 3424 else: 3425 this = None 3426 3427 if self._match(TokenType.FOR): 3428 for_or_in = "FOR" 3429 elif self._match(TokenType.IN): 3430 for_or_in = "IN" 3431 else: 3432 for_or_in = None 3433 3434 if self._match_text_seq("ACCESS"): 3435 lock_type = "ACCESS" 3436 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3437 lock_type = "EXCLUSIVE" 3438 elif self._match_text_seq("SHARE"): 3439 lock_type = "SHARE" 3440 elif self._match_text_seq("READ"): 3441 lock_type = "READ" 3442 elif self._match_text_seq("WRITE"): 3443 lock_type = "WRITE" 3444 elif self._match_text_seq("CHECKSUM"): 3445 lock_type = "CHECKSUM" 3446 else: 3447 lock_type = None 3448 3449 override = self._match_text_seq("OVERRIDE") 3450 3451 return self.expression( 3452 exp.LockingProperty( 3453 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3454 ) 3455 ) 3456 3457 def _parse_partition_by(self) -> list[exp.Expr]: 3458 if not self._match(TokenType.PARTITION_BY): 3459 return [] 3460 3461 if self._next.token_type in self.ID_VAR_TOKENS and self._match_text_seq("EXCLUDING"): 3462 return [ 3463 self.expression( 3464 exp.PartitionByExcluding(expressions=self._parse_csv(self._parse_disjunction)) 3465 ) 3466 ] 3467 3468 return self._parse_csv(self._parse_disjunction) 3469 3470 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3471 def _parse_partition_bound_expr() -> exp.Expr | None: 3472 if self._match_text_seq("MINVALUE"): 3473 return exp.var("MINVALUE") 3474 if self._match_text_seq("MAXVALUE"): 3475 return exp.var("MAXVALUE") 3476 return self._parse_bitwise() 3477 3478 this: exp.Expr | list[exp.Expr] | None = None 3479 expression = None 3480 from_expressions = None 3481 to_expressions = None 3482 3483 if self._match(TokenType.IN): 3484 this = self._parse_wrapped_csv(self._parse_bitwise) 3485 elif self._match(TokenType.FROM): 3486 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3487 self._match_text_seq("TO") 3488 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3489 elif self._match_text_seq("WITH", "(", "MODULUS"): 3490 this = self._parse_number() 3491 self._match_text_seq(",", "REMAINDER") 3492 expression = self._parse_number() 3493 self._match_r_paren() 3494 else: 3495 self.raise_error("Failed to parse partition bound spec.") 3496 3497 return self.expression( 3498 exp.PartitionBoundSpec( 3499 this=this, 3500 expression=expression, 3501 from_expressions=from_expressions, 3502 to_expressions=to_expressions, 3503 ) 3504 ) 3505 3506 # https://www.postgresql.org/docs/current/sql-createtable.html 3507 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3508 if not self._match_text_seq("OF"): 3509 self._retreat(self._index - 1) 3510 return None 3511 3512 this = self._parse_table(schema=True) 3513 3514 if self._match(TokenType.DEFAULT): 3515 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3516 elif self._match_text_seq("FOR", "VALUES"): 3517 expression = self._parse_partition_bound_spec() 3518 else: 3519 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3520 3521 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3522 3523 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3524 self._match(TokenType.EQ) 3525 return self.expression( 3526 exp.PartitionedByProperty( 3527 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3528 ) 3529 ) 3530 3531 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3532 if self._match_text_seq("AND", "STATISTICS"): 3533 statistics = True 3534 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3535 statistics = False 3536 else: 3537 statistics = None 3538 3539 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3540 3541 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3542 if self._match_text_seq("SQL"): 3543 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3544 return None 3545 3546 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3547 if self._match_text_seq("SQL", "DATA"): 3548 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3549 return None 3550 3551 def _parse_no_property(self) -> exp.Expr | None: 3552 if self._match_text_seq("PRIMARY", "INDEX"): 3553 return exp.NoPrimaryIndexProperty() 3554 if self._match_text_seq("SQL"): 3555 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3556 return None 3557 3558 def _parse_on_property(self) -> exp.Expr | None: 3559 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3560 return exp.OnCommitProperty() 3561 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3562 return exp.OnCommitProperty(delete=True) 3563 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3564 3565 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3566 if self._match_text_seq("SQL", "DATA"): 3567 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3568 return None 3569 3570 def _parse_distkey(self) -> exp.DistKeyProperty: 3571 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3572 3573 def _parse_create_like(self) -> exp.LikeProperty | None: 3574 table = self._parse_table(schema=True) 3575 3576 options = [] 3577 while self._match_texts(("INCLUDING", "EXCLUDING")): 3578 this = self._prev.text.upper() 3579 3580 id_var = self._parse_id_var() 3581 if not id_var: 3582 return None 3583 3584 options.append( 3585 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3586 ) 3587 3588 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3589 3590 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3591 return self.expression( 3592 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3593 ) 3594 3595 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3596 self._match(TokenType.EQ) 3597 return self.expression( 3598 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3599 ) 3600 3601 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3602 self._match_text_seq("WITH", "CONNECTION") 3603 return self.expression( 3604 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3605 ) 3606 3607 def _parse_returns(self) -> exp.ReturnsProperty: 3608 value: exp.Expr | None 3609 null = None 3610 is_table = self._match(TokenType.TABLE) 3611 3612 if is_table: 3613 if self._match(TokenType.LT): 3614 value = self.expression( 3615 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3616 ) 3617 if not self._match(TokenType.GT): 3618 self.raise_error("Expecting >") 3619 else: 3620 value = self._parse_schema(exp.var("TABLE")) 3621 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3622 null = True 3623 value = None 3624 else: 3625 value = self._parse_types() 3626 3627 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3628 3629 def _parse_describe(self) -> exp.Describe: 3630 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3631 style: str | None = ( 3632 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3633 ) 3634 if self._match(TokenType.DOT): 3635 style = None 3636 self._retreat(self._index - 2) 3637 3638 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3639 3640 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3641 this = self._parse_statement() 3642 else: 3643 this = self._parse_table(schema=True) 3644 3645 properties = self._parse_properties() 3646 expressions = properties.expressions if properties else None 3647 partition = self._parse_partition() 3648 return self.expression( 3649 exp.Describe( 3650 this=this, 3651 style=style, 3652 kind=kind, 3653 expressions=expressions, 3654 partition=partition, 3655 format=format, 3656 as_json=self._match_text_seq("AS", "JSON"), 3657 ) 3658 ) 3659 3660 def _parse_multitable_inserts( 3661 self, comments: list[str] | None, kind: str | None = None 3662 ) -> exp.MultitableInserts: 3663 kind = kind or self._prev.text.upper() 3664 expressions = [] 3665 3666 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3667 if self._match(TokenType.WHEN): 3668 expression = self._parse_disjunction() 3669 self._match(TokenType.THEN) 3670 else: 3671 expression = None 3672 3673 else_ = self._match(TokenType.ELSE) 3674 3675 if not self._match(TokenType.INTO): 3676 return None 3677 3678 return self.expression( 3679 exp.ConditionalInsert( 3680 this=self.expression( 3681 exp.Insert( 3682 this=self._parse_table(schema=True), 3683 expression=self._parse_derived_table_values(), 3684 ) 3685 ), 3686 expression=expression, 3687 else_=else_, 3688 ) 3689 ) 3690 3691 expression = parse_conditional_insert() 3692 while expression is not None: 3693 expressions.append(expression) 3694 expression = parse_conditional_insert() 3695 3696 return self.expression( 3697 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3698 comments=comments, 3699 ) 3700 3701 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3702 comments: list[str] = [] 3703 hint = self._parse_hint() 3704 overwrite = self._match(TokenType.OVERWRITE) 3705 ignore = self._match(TokenType.IGNORE) 3706 local = self._match_text_seq("LOCAL") 3707 alternative = None 3708 is_function = None 3709 3710 if self._match_text_seq("DIRECTORY"): 3711 this: exp.Expr | None = self.expression( 3712 exp.Directory( 3713 this=self._parse_var_or_string(), 3714 local=local, 3715 row_format=self._parse_row_format(match_row=True), 3716 ) 3717 ) 3718 else: 3719 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3720 comments += ensure_list(self._prev_comments) 3721 return self._parse_multitable_inserts(comments) 3722 3723 # Oracle's conditional form defaults to ALL when the keyword is omitted 3724 if self._match(TokenType.WHEN, advance=False): 3725 return self._parse_multitable_inserts(comments, kind="ALL") 3726 3727 if self._match(TokenType.OR): 3728 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3729 3730 self._match(TokenType.INTO) 3731 comments += ensure_list(self._prev_comments) 3732 self._match(TokenType.TABLE) 3733 is_function = self._match(TokenType.FUNCTION) 3734 3735 this = self._parse_function() if is_function else self._parse_insert_table() 3736 3737 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3738 set_values = None 3739 if self._match(TokenType.SET): 3740 columns = [] 3741 values = [] 3742 3743 def _parse_set_assignment() -> exp.Expr | None: 3744 target = self._parse_column() 3745 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3746 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3747 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3748 else: 3749 value = self._parse_disjunction() 3750 3751 if value: 3752 columns.append(target.this) 3753 values.append(value) 3754 return value 3755 3756 self.raise_error("Expected column assignment in INSERT ... SET") 3757 return None 3758 3759 self._parse_csv(_parse_set_assignment) 3760 3761 this = self.expression(exp.Schema(this=this, expressions=columns)) 3762 set_values = self.expression( 3763 exp.Values( 3764 expressions=[exp.Tuple(expressions=values)], 3765 alias=self._parse_table_alias(), 3766 ) 3767 ) 3768 3769 returning = self._parse_returning() # TSQL allows RETURNING before source 3770 3771 stored = self._match_text_seq("STORED") and self._parse_stored() 3772 by_name = self._match_text_seq("BY", "NAME") 3773 exists = self._parse_exists() 3774 replace_where = None 3775 replace_using = None 3776 3777 if self._match(TokenType.REPLACE): 3778 if self._match(TokenType.WHERE): 3779 replace_where = self._parse_disjunction() 3780 elif self._match(TokenType.USING): 3781 replace_using = self._parse_using_identifiers() 3782 3783 return self.expression( 3784 exp.Insert( 3785 hint=hint, 3786 is_function=is_function, 3787 this=this, 3788 stored=stored, 3789 by_name=by_name, 3790 exists=exists, 3791 where=replace_where, 3792 using=replace_using, 3793 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3794 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3795 default=self._match_text_seq("DEFAULT", "VALUES"), 3796 expression=set_values 3797 or self._parse_derived_table_values(allow_value_synonym=True) 3798 or self._parse_ddl_select(), 3799 conflict=self._parse_on_conflict(), 3800 returning=returning or self._parse_returning(), 3801 overwrite=overwrite, 3802 alternative=alternative, 3803 ignore=ignore, 3804 source=self._match(TokenType.TABLE) and self._parse_table(), 3805 ), 3806 comments=comments, 3807 ) 3808 3809 def _parse_insert_table(self) -> exp.Expr | None: 3810 this = self._parse_table(schema=True, parse_partition=True) 3811 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3812 this.set("alias", self._parse_table_alias()) 3813 return this 3814 3815 def _parse_kill(self) -> exp.Kill: 3816 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3817 3818 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3819 3820 def _parse_on_conflict(self) -> exp.OnConflict | None: 3821 conflict = self._match_text_seq("ON", "CONFLICT") 3822 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3823 3824 if not conflict and not duplicate: 3825 return None 3826 3827 conflict_keys = None 3828 constraint = None 3829 3830 if conflict: 3831 if self._match_text_seq("ON", "CONSTRAINT"): 3832 constraint = self._parse_id_var() 3833 elif self._match(TokenType.L_PAREN): 3834 conflict_keys = self._parse_csv(self._parse_indexed_column) 3835 self._match_r_paren() 3836 3837 index_predicate = self._parse_where() 3838 3839 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3840 if self._prev.token_type == TokenType.UPDATE: 3841 self._match(TokenType.SET) 3842 expressions = self._parse_csv(self._parse_update_assignment) 3843 else: 3844 expressions = None 3845 3846 return self.expression( 3847 exp.OnConflict( 3848 duplicate=duplicate, 3849 expressions=expressions, 3850 action=action, 3851 conflict_keys=conflict_keys, 3852 index_predicate=index_predicate, 3853 constraint=constraint, 3854 where=self._parse_where(), 3855 ) 3856 ) 3857 3858 def _parse_returning(self) -> exp.Returning | None: 3859 if not self._match(TokenType.RETURNING): 3860 return None 3861 return self.expression( 3862 exp.Returning( 3863 expressions=self._parse_csv(self._parse_expression), 3864 into=self._match(TokenType.INTO) and self._parse_table_part(), 3865 ) 3866 ) 3867 3868 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3869 if not self._match(TokenType.FORMAT): 3870 return None 3871 return self._parse_row_format() 3872 3873 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3874 index = self._index 3875 with_ = with_ or self._match_text_seq("WITH") 3876 3877 if not self._match(TokenType.SERDE_PROPERTIES): 3878 self._retreat(index) 3879 return None 3880 return self.expression( 3881 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3882 ) 3883 3884 def _parse_row_format( 3885 self, match_row: bool = False 3886 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3887 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3888 return None 3889 3890 if self._match_text_seq("SERDE"): 3891 this = self._parse_string() 3892 3893 serde_properties = self._parse_serde_properties() 3894 3895 return self.expression( 3896 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3897 ) 3898 3899 self._match_text_seq("DELIMITED") 3900 3901 kwargs = {} 3902 3903 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3904 kwargs["fields"] = self._parse_string() 3905 if self._match_text_seq("ESCAPED", "BY"): 3906 kwargs["escaped"] = self._parse_string() 3907 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3908 kwargs["collection_items"] = self._parse_string() 3909 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3910 kwargs["map_keys"] = self._parse_string() 3911 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3912 kwargs["lines"] = self._parse_string() 3913 if self._match_text_seq("NULL", "DEFINED", "AS"): 3914 kwargs["null"] = self._parse_string() 3915 3916 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3917 3918 def _parse_load(self) -> exp.LoadData | exp.Command: 3919 if self._match_text_seq("DATA"): 3920 local = self._match_text_seq("LOCAL") 3921 self._match_text_seq("INPATH") 3922 inpath = self._parse_string() 3923 overwrite = self._match(TokenType.OVERWRITE) 3924 temp: bool | None = None 3925 if self._match(TokenType.INTO): 3926 temp = self._match(TokenType.TEMPORARY) 3927 self._match(TokenType.TABLE) 3928 3929 return self.expression( 3930 exp.LoadData( 3931 this=self._parse_table(schema=True), 3932 local=local, 3933 overwrite=overwrite, 3934 temp=temp, 3935 inpath=inpath, 3936 files=self._match_text_seq("FROM", "FILES") 3937 and exp.Properties(expressions=self._parse_wrapped_properties()), 3938 partition=self._parse_partition(), 3939 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3940 serde=self._match_text_seq("SERDE") and self._parse_string(), 3941 ) 3942 ) 3943 return self._parse_as_command(self._prev) 3944 3945 def _parse_delete(self) -> exp.Delete: 3946 hint = self._parse_hint() 3947 3948 # This handles MySQL's "Multiple-Table Syntax" 3949 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3950 tables = None 3951 if not self._match(TokenType.FROM, advance=False): 3952 tables = self._parse_csv(self._parse_table) or None 3953 3954 returning = self._parse_returning() 3955 3956 return self.expression( 3957 exp.Delete( 3958 hint=hint, 3959 tables=tables, 3960 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3961 using=self._match(TokenType.USING) 3962 and self._parse_csv(lambda: self._parse_table(joins=True)), 3963 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3964 where=self._parse_where(), 3965 returning=returning or self._parse_returning(), 3966 order=self._parse_order(), 3967 limit=self._parse_limit(), 3968 ) 3969 ) 3970 3971 def _parse_update_assignment(self) -> exp.Expr | None: 3972 this = self._parse_comparison() 3973 if self._match(TokenType.EQ): 3974 comments = self._prev_comments 3975 this = self.expression( 3976 exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments 3977 ) 3978 return this 3979 3980 def _parse_update(self) -> exp.Update: 3981 hint = self._parse_hint() 3982 kwargs: dict[str, object] = { 3983 "hint": hint, 3984 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3985 } 3986 while self._curr: 3987 if self._match(TokenType.SET): 3988 kwargs["expressions"] = self._parse_csv(self._parse_update_assignment) 3989 elif self._match(TokenType.RETURNING, advance=False): 3990 kwargs["returning"] = self._parse_returning() 3991 elif self._match(TokenType.FROM, advance=False): 3992 from_ = self._parse_from(joins=True) 3993 table = from_.this if from_ else None 3994 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3995 table.set("joins", list(self._parse_joins()) or None) 3996 3997 kwargs["from_"] = from_ 3998 elif self._match(TokenType.WHERE, advance=False): 3999 kwargs["where"] = self._parse_where() 4000 elif self._match(TokenType.ORDER_BY, advance=False): 4001 kwargs["order"] = self._parse_order() 4002 elif self._match(TokenType.LIMIT, advance=False): 4003 kwargs["limit"] = self._parse_limit() 4004 else: 4005 break 4006 4007 return self.expression(exp.Update(**kwargs)) 4008 4009 def _parse_use(self) -> exp.Use: 4010 return self.expression( 4011 exp.Use( 4012 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 4013 this=self._parse_table(schema=False), 4014 ) 4015 ) 4016 4017 def _parse_uncache(self) -> exp.Uncache: 4018 if not self._match(TokenType.TABLE): 4019 self.raise_error("Expecting TABLE after UNCACHE") 4020 4021 return self.expression( 4022 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 4023 ) 4024 4025 def _parse_cache(self) -> exp.Cache: 4026 lazy = self._match_text_seq("LAZY") 4027 self._match(TokenType.TABLE) 4028 table = self._parse_table(schema=True) 4029 4030 options = [] 4031 if self._match_text_seq("OPTIONS"): 4032 self._match_l_paren() 4033 k = self._parse_string() 4034 self._match(TokenType.EQ) 4035 v = self._parse_string() 4036 options = [k, v] 4037 self._match_r_paren() 4038 4039 self._match(TokenType.ALIAS) 4040 return self.expression( 4041 exp.Cache( 4042 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 4043 ) 4044 ) 4045 4046 def _parse_partition(self) -> exp.Partition | None: 4047 if not self._match_texts(self.PARTITION_KEYWORDS): 4048 return None 4049 4050 return self.expression( 4051 exp.Partition( 4052 subpartition=self._prev.text.upper() == "SUBPARTITION", 4053 expressions=self._parse_wrapped_csv(self._parse_disjunction), 4054 ) 4055 ) 4056 4057 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 4058 def _parse_value_expression() -> exp.Expr | None: 4059 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 4060 return exp.var(self._prev.text.upper()) 4061 return self._parse_expression() 4062 4063 if self._match(TokenType.L_PAREN): 4064 expressions = self._parse_csv(_parse_value_expression) 4065 self._match_r_paren() 4066 return self.expression(exp.Tuple(expressions=expressions)) 4067 4068 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 4069 expression = self._parse_expression() 4070 if expression: 4071 return self.expression(exp.Tuple(expressions=[expression])) 4072 return None 4073 4074 def _parse_projections( 4075 self, 4076 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 4077 return self._parse_expressions(), None 4078 4079 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 4080 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 4081 this: exp.Expr | None = self._parse_simplified_pivot( 4082 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 4083 ) 4084 else: 4085 # Support parentheses for duckdb FROM-first syntax 4086 this = ( 4087 self._parse_table(consume_pipe=True) 4088 if table and not self._match(TokenType.FROM, advance=False) 4089 else self._parse_select(nested=True, parse_set_operation=False) 4090 ) 4091 4092 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 4093 # in case a modifier (e.g. join) is following 4094 if table and isinstance(this, exp.Values) and this.alias: 4095 alias = this.args["alias"].pop() 4096 this = exp.Table(this=this, alias=alias) 4097 4098 this = self._parse_query_modifiers(self._parse_set_operations(this)) 4099 4100 return this 4101 4102 def _parse_select( 4103 self, 4104 nested: bool = False, 4105 table: bool = False, 4106 parse_subquery_alias: bool = True, 4107 parse_set_operation: bool = True, 4108 consume_pipe: bool = True, 4109 ) -> exp.Expr | None: 4110 query = self._parse_select_query( 4111 nested=nested, 4112 table=table, 4113 parse_subquery_alias=parse_subquery_alias, 4114 parse_set_operation=parse_set_operation, 4115 ) 4116 4117 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4118 if isinstance(query, exp.Query): 4119 query = self._parse_pipe_syntax_query(query) 4120 query = query.subquery(copy=False) if query and table else query 4121 4122 return query 4123 4124 def _parse_select_query( 4125 self, 4126 nested: bool = False, 4127 table: bool = False, 4128 parse_subquery_alias: bool = True, 4129 parse_set_operation: bool = True, 4130 ) -> exp.Expr | None: 4131 cte = self._parse_with() 4132 4133 if cte: 4134 this = self._parse_statement() 4135 4136 if not this: 4137 self.raise_error("Failed to parse any statement following CTE") 4138 return cte 4139 4140 while isinstance(this, exp.Subquery) and this.is_wrapper: 4141 this = this.this 4142 4143 assert this is not None 4144 if "with_" in this.arg_types: 4145 if inner_cte := this.args.get("with_"): 4146 cte.set("expressions", cte.expressions + inner_cte.expressions) 4147 if inner_cte.args.get("recursive"): 4148 cte.set("recursive", True) 4149 this.set("with_", cte) 4150 else: 4151 self.raise_error(f"{this.key} does not support CTE") 4152 this = cte 4153 4154 return this 4155 4156 # duckdb supports leading with FROM x 4157 from_ = ( 4158 self._parse_from(joins=True, consume_pipe=True) 4159 if self._match(TokenType.FROM, advance=False) 4160 else None 4161 ) 4162 laterals = list(iter(self._parse_lateral, None)) if from_ else None 4163 4164 # Hive supports multi-table inserts: FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 4165 if from_ and self._match(TokenType.INSERT, advance=False): 4166 source = from_.this 4167 source.set("laterals", laterals or None) 4168 4169 inserts = [] 4170 while self._match(TokenType.INSERT, advance=False): 4171 inserts.append(self._parse_statement()) 4172 4173 return self.expression( 4174 exp.MultitableInserts(expressions=inserts, source=source), 4175 comments=from_.comments, 4176 ) 4177 4178 if self._match(TokenType.SELECT): 4179 comments = self._prev_comments 4180 4181 hint = self._parse_hint() 4182 4183 if self._next and not self._next.token_type == TokenType.DOT: 4184 all_ = self._match(TokenType.ALL) 4185 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4186 else: 4187 all_, matched_distinct = None, False 4188 4189 kind = ( 4190 self._prev.text.upper() 4191 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4192 else None 4193 ) 4194 4195 distinct: exp.Expr | None = ( 4196 self.expression( 4197 exp.Distinct( 4198 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4199 ) 4200 ) 4201 if matched_distinct 4202 else None 4203 ) 4204 4205 operation_modifiers = [] 4206 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4207 operation_modifiers.append(exp.var(self._prev.text.upper())) 4208 4209 limit = self._parse_limit(top=True) 4210 4211 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4212 if limit and not matched_distinct and not all_: 4213 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4214 if matched_distinct: 4215 distinct = self.expression( 4216 exp.Distinct( 4217 on=self._parse_value(values=False) 4218 if self._match(TokenType.ON) 4219 else None 4220 ) 4221 ) 4222 else: 4223 all_ = self._match(TokenType.ALL) 4224 4225 if all_ and distinct: 4226 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4227 4228 projections, exclude = self._parse_projections() 4229 4230 this = self.expression( 4231 exp.Select( 4232 kind=kind, 4233 hint=hint, 4234 distinct=distinct, 4235 expressions=projections, 4236 limit=limit, 4237 exclude=exclude, 4238 operation_modifiers=operation_modifiers or None, 4239 ) 4240 ) 4241 this.comments = comments 4242 4243 into = self._parse_into() 4244 if into: 4245 this.set("into", into) 4246 4247 if not from_: 4248 from_ = self._parse_from() 4249 4250 if from_: 4251 this.set("from_", from_) 4252 4253 this = self._parse_query_modifiers(this) 4254 elif (table or nested) and self._match(TokenType.L_PAREN): 4255 comments = self._prev_comments 4256 this = self._parse_wrapped_select(table=table) 4257 4258 if this: 4259 this.add_comments(comments, prepend=True) 4260 4261 # We return early here so that the UNION isn't attached to the subquery by the 4262 # following call to _parse_set_operations, but instead becomes the parent node 4263 self._match_r_paren() 4264 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4265 elif self._match(TokenType.VALUES, advance=False): 4266 this = self._parse_derived_table_values() 4267 elif from_: 4268 this = exp.select("*").from_(from_.this, copy=False) 4269 this = self._parse_query_modifiers(this) 4270 elif self._match(TokenType.SUMMARIZE): 4271 table = self._match(TokenType.TABLE) 4272 this = self._parse_select() or self._parse_string() or self._parse_table() 4273 return self.expression(exp.Summarize(this=this, table=table)) 4274 elif self._match(TokenType.DESCRIBE): 4275 this = self._parse_describe() 4276 else: 4277 this = None 4278 4279 if laterals and isinstance(this, exp.Select): 4280 this.set("laterals", [*laterals, *(this.args.get("laterals") or [])]) 4281 4282 return self._parse_set_operations(this) if parse_set_operation else this 4283 4284 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4285 self._match_text_seq("SEARCH") 4286 4287 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4288 4289 if not kind: 4290 return None 4291 4292 self._match_text_seq("FIRST", "BY") 4293 4294 return self.expression( 4295 exp.RecursiveWithSearch( 4296 kind=kind, 4297 this=self._parse_id_var(), 4298 expression=self._match_text_seq("SET") and self._parse_id_var(), 4299 to=self._match_text_seq("TO") and self._parse_bitwise(), 4300 default=self._match(TokenType.DEFAULT) and self._parse_bitwise(), 4301 using=self._match_text_seq("USING") and self._parse_id_var(), 4302 ) 4303 ) 4304 4305 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4306 if not skip_with_token and not self._match(TokenType.WITH): 4307 return None 4308 4309 comments = self._prev_comments 4310 recursive = self._match(TokenType.RECURSIVE) 4311 4312 last_comments = None 4313 expressions = [] 4314 udfs = [] 4315 while True: 4316 cte = self._parse_cte() 4317 if cte: 4318 if isinstance(cte, exp.FunctionSpecification): 4319 udfs.append(cte) 4320 else: 4321 expressions.append(cte) 4322 4323 if last_comments: 4324 cte.add_comments(last_comments) 4325 4326 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4327 break 4328 else: 4329 self._match(TokenType.WITH) 4330 recursive = self._match(TokenType.RECURSIVE) or recursive 4331 4332 last_comments = self._prev_comments 4333 4334 search = self._parse_recursive_with_search() 4335 4336 # A SEARCH clause can be followed by a CYCLE clause 4337 cycle = ( 4338 self._parse_recursive_with_search() 4339 if search 4340 and search.args.get("kind") != "CYCLE" 4341 and self._match_text_seq("CYCLE", advance=False) 4342 else None 4343 ) 4344 4345 return self.expression( 4346 exp.With( 4347 expressions=expressions, 4348 recursive=recursive or None, 4349 search=search, 4350 cycle=cycle, 4351 udfs=udfs or None, 4352 ), 4353 comments=comments, 4354 ) 4355 4356 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4357 index = self._index 4358 4359 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4360 if not alias or not alias.this: 4361 self.raise_error("Expected CTE to have alias") 4362 4363 key_expressions = ( 4364 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4365 ) 4366 4367 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4368 self._retreat(index) 4369 return None 4370 4371 comments = self._prev_comments 4372 4373 if self._match_text_seq("NOT", "MATERIALIZED"): 4374 materialized = False 4375 elif self._match_text_seq("MATERIALIZED"): 4376 materialized = True 4377 else: 4378 materialized = None 4379 4380 cte = self.expression( 4381 exp.CTE( 4382 this=self._parse_wrapped(self._parse_statement), 4383 alias=alias, 4384 materialized=materialized, 4385 key_expressions=key_expressions, 4386 ), 4387 comments=comments, 4388 ) 4389 4390 values = cte.this 4391 if isinstance(values, exp.Values): 4392 cte.set("this", self._values_to_select(values)) 4393 4394 return cte 4395 4396 def _values_to_select(self, values: exp.Values) -> exp.Select: 4397 if values.alias: 4398 return exp.select("*").from_(values) 4399 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4400 4401 def _parse_table_alias( 4402 self, alias_tokens: t.Collection[TokenType] | None = None 4403 ) -> exp.TableAlias | None: 4404 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4405 # so this section tries to parse the clause version and if it fails, it treats the token 4406 # as an identifier (alias) 4407 if self._can_parse_limit_or_offset(): 4408 return None 4409 4410 # START is never treated as an implicit alias when followed by WITH, since that 4411 # would swallow the beginning of a START WITH ... CONNECT BY clause 4412 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4413 return None 4414 4415 any_token = self._match(TokenType.ALIAS) 4416 alias = ( 4417 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4418 or self._parse_string_as_identifier() 4419 ) 4420 4421 index = self._index 4422 if self._match(TokenType.L_PAREN): 4423 columns = self._parse_csv(self._parse_function_parameter) 4424 self._match_r_paren() if columns else self._retreat(index) 4425 else: 4426 columns = None 4427 4428 if not alias and not columns: 4429 return None 4430 4431 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4432 4433 # We bubble up comments from the Identifier to the TableAlias 4434 if isinstance(alias, exp.Identifier): 4435 table_alias.add_comments(alias.pop_comments()) 4436 4437 return table_alias 4438 4439 def _parse_subquery( 4440 self, this: exp.Expr | None, parse_alias: bool = True 4441 ) -> exp.Subquery | None: 4442 if not this: 4443 return None 4444 4445 return self.expression( 4446 exp.Subquery( 4447 this=this, 4448 pivots=self._parse_pivots(), 4449 alias=self._parse_table_alias() if parse_alias else None, 4450 sample=self._parse_table_sample(), 4451 ) 4452 ) 4453 4454 def _implicit_unnests_to_explicit(self, this: E) -> E: 4455 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4456 4457 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4458 for i, join in enumerate(this.args.get("joins") or []): 4459 table = join.this 4460 normalized_table = table.copy() 4461 normalized_table.meta["maybe_column"] = True 4462 normalized_table = _norm(normalized_table, dialect=self.dialect) 4463 4464 if isinstance(table, exp.Table) and not join.args.get("on"): 4465 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4466 table_as_column = table.to_column() 4467 unnest = exp.Unnest(expressions=[table_as_column]) 4468 4469 # Table.to_column creates a parent Alias node that we want to convert to 4470 # a TableAlias and attach to the Unnest, so it matches the parser's output 4471 if isinstance(table.args.get("alias"), exp.TableAlias): 4472 table_as_column.replace(table_as_column.this) 4473 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4474 4475 table.replace(unnest) 4476 4477 refs.add(normalized_table.alias_or_name) 4478 4479 return this 4480 4481 @t.overload 4482 def _parse_query_modifiers(self, this: E) -> E: ... 4483 4484 @t.overload 4485 def _parse_query_modifiers(self, this: None) -> None: ... 4486 4487 def _parse_query_modifiers(self, this): 4488 if isinstance(this, self.MODIFIABLES): 4489 for join in self._parse_joins(): 4490 this.append("joins", join) 4491 for lateral in iter(self._parse_lateral, None): 4492 this.append("laterals", lateral) 4493 4494 while True: 4495 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4496 modifier_token = self._curr 4497 4498 # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result 4499 # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union 4500 if ( 4501 modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH) 4502 and (limit := this.args.get("limit")) 4503 and limit.meta.get("top") 4504 ): 4505 break 4506 4507 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4508 key, expression = parser(self) 4509 4510 if expression: 4511 if this.args.get(key): 4512 self.raise_error( 4513 f"Found multiple '{modifier_token.text.upper()}' clauses", 4514 token=modifier_token, 4515 ) 4516 4517 this.set(key, expression) 4518 if key == "limit": 4519 offset = expression.args.get("offset") 4520 expression.set("offset", None) 4521 4522 if offset: 4523 if this.args.get("offset"): 4524 self.raise_error( 4525 "Found multiple 'OFFSET' clauses", token=modifier_token 4526 ) 4527 4528 offset = exp.Offset(expression=offset) 4529 this.set("offset", offset) 4530 4531 limit_by_expressions = expression.expressions 4532 expression.set("expressions", None) 4533 offset.set("expressions", limit_by_expressions) 4534 continue 4535 4536 if self._curr.text.upper() == "START": 4537 modifier_token = self._curr 4538 connect = self._parse_connect() 4539 if connect: 4540 if this.args.get("connect"): 4541 self.raise_error( 4542 "Found multiple 'START WITH' clauses", token=modifier_token 4543 ) 4544 4545 this.set("connect", connect) 4546 continue 4547 break 4548 4549 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4550 this = self._implicit_unnests_to_explicit(this) 4551 4552 return this 4553 4554 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4555 start = self._curr 4556 while self._curr: 4557 self._advance() 4558 4559 end = self._tokens[self._index - 1] 4560 return exp.Hint(expressions=[self._find_sql(start, end)]) 4561 4562 def _parse_hint_function_call(self) -> exp.Expr | None: 4563 return self._parse_function_call() 4564 4565 def _parse_hint_body(self) -> exp.Hint | None: 4566 start_index = self._index 4567 should_fallback_to_string = False 4568 4569 hints = [] 4570 try: 4571 for hint in iter( 4572 lambda: self._parse_csv( 4573 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4574 ), 4575 [], 4576 ): 4577 hints.extend(hint) 4578 except ParseError: 4579 should_fallback_to_string = True 4580 4581 if should_fallback_to_string or self._curr: 4582 self._retreat(start_index) 4583 return self._parse_hint_fallback_to_string() 4584 4585 return self.expression(exp.Hint(expressions=hints)) 4586 4587 def _parse_hint(self) -> exp.Hint | None: 4588 if self._match(TokenType.HINT) and self._prev_comments: 4589 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4590 4591 return None 4592 4593 def _parse_into(self) -> exp.Into | None: 4594 if not self._match(TokenType.INTO): 4595 return None 4596 4597 temp = self._match(TokenType.TEMPORARY) 4598 unlogged = self._match_text_seq("UNLOGGED") 4599 self._match(TokenType.TABLE) 4600 4601 return self.expression( 4602 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4603 ) 4604 4605 def _parse_from( 4606 self, 4607 joins: bool = False, 4608 skip_from_token: bool = False, 4609 consume_pipe: bool = False, 4610 ) -> exp.From | None: 4611 if not skip_from_token and not self._match(TokenType.FROM): 4612 return None 4613 4614 comments = self._prev_comments 4615 return self.expression( 4616 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4617 comments=comments, 4618 ) 4619 4620 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4621 return self.expression( 4622 exp.MatchRecognizeMeasure( 4623 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4624 this=self._parse_expression(), 4625 ) 4626 ) 4627 4628 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4629 if not self._match(TokenType.MATCH_RECOGNIZE): 4630 return None 4631 4632 self._match_l_paren() 4633 4634 partition = self._parse_partition_by() 4635 order = self._parse_order() 4636 4637 measures = ( 4638 self._parse_csv(self._parse_match_recognize_measure) 4639 if self._match_text_seq("MEASURES") 4640 else None 4641 ) 4642 4643 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4644 rows = exp.var("ONE ROW PER MATCH") 4645 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4646 text = "ALL ROWS PER MATCH" 4647 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4648 text += " SHOW EMPTY MATCHES" 4649 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4650 text += " OMIT EMPTY MATCHES" 4651 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4652 text += " WITH UNMATCHED ROWS" 4653 rows = exp.var(text) 4654 else: 4655 rows = None 4656 4657 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4658 text = "AFTER MATCH SKIP" 4659 if self._match_text_seq("PAST", "LAST", "ROW"): 4660 text += " PAST LAST ROW" 4661 elif self._match_text_seq("TO", "NEXT", "ROW"): 4662 text += " TO NEXT ROW" 4663 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4664 direction = self._prev.text.upper() 4665 pattern_var = self._advance_any() 4666 if not pattern_var: 4667 self.raise_error( 4668 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4669 ) 4670 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4671 after = exp.var(text) 4672 else: 4673 after = None 4674 4675 if self._match_text_seq("PATTERN"): 4676 self._match_l_paren() 4677 4678 if not self._curr: 4679 self.raise_error("Expecting )", self._curr) 4680 4681 paren = 1 4682 start = self._curr 4683 4684 while self._curr and paren > 0: 4685 if self._curr.token_type == TokenType.L_PAREN: 4686 paren += 1 4687 if self._curr.token_type == TokenType.R_PAREN: 4688 paren -= 1 4689 4690 end = self._prev 4691 self._advance() 4692 4693 if paren > 0: 4694 self.raise_error("Expecting )", self._curr) 4695 4696 pattern = exp.var(self._find_sql(start, end)) 4697 else: 4698 pattern = None 4699 4700 define = ( 4701 self._parse_csv(self._parse_name_as_expression) 4702 if self._match_text_seq("DEFINE") 4703 else None 4704 ) 4705 4706 self._match_r_paren() 4707 4708 return self.expression( 4709 exp.MatchRecognize( 4710 partition_by=partition, 4711 order=order, 4712 measures=measures, 4713 rows=rows, 4714 after=after, 4715 pattern=pattern, 4716 define=define, 4717 alias=self._parse_table_alias(), 4718 ) 4719 ) 4720 4721 def _parse_lateral(self) -> exp.Lateral | None: 4722 cross_apply: bool | None = None 4723 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4724 cross_apply = True 4725 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4726 cross_apply = False 4727 4728 if cross_apply is not None: 4729 this = self._parse_select(table=True) 4730 view = None 4731 outer = None 4732 elif self._match(TokenType.LATERAL): 4733 this = self._parse_select(table=True) 4734 view = self._match(TokenType.VIEW) 4735 outer = self._match(TokenType.OUTER) 4736 else: 4737 return None 4738 4739 if not this: 4740 this = ( 4741 self._parse_unnest() 4742 or self._parse_function() 4743 or self._parse_id_var(any_token=False) 4744 ) 4745 4746 while self._match(TokenType.DOT): 4747 this = exp.Dot( 4748 this=this, 4749 expression=self._parse_function() or self._parse_id_var(any_token=False), 4750 ) 4751 4752 ordinality: bool | None = None 4753 4754 if view: 4755 table = self._parse_id_var(any_token=False) 4756 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4757 table_alias: exp.TableAlias | None = self.expression( 4758 exp.TableAlias(this=table, columns=columns) 4759 ) 4760 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4761 # We move the alias from the lateral's child node to the lateral itself 4762 table_alias = this.args["alias"].pop() 4763 else: 4764 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4765 table_alias = self._parse_table_alias() 4766 4767 return self.expression( 4768 exp.Lateral( 4769 this=this, 4770 view=view, 4771 outer=outer, 4772 alias=table_alias, 4773 cross_apply=cross_apply, 4774 ordinality=ordinality, 4775 ) 4776 ) 4777 4778 def _parse_stream(self) -> exp.Stream | None: 4779 index = self._index 4780 if self._match(TokenType.STREAM): 4781 if this := self._try_parse(self._parse_table): 4782 return self.expression(exp.Stream(this=this)) 4783 self._retreat(index) 4784 return None 4785 4786 def _parse_join_parts( 4787 self, 4788 ) -> tuple[Token | None, Token | None, Token | None]: 4789 return ( 4790 self._prev if self._match_set(self.JOIN_METHODS) else None, 4791 self._prev if self._match_set(self.JOIN_SIDES) else None, 4792 self._prev if self._match_set(self.JOIN_KINDS) else None, 4793 ) 4794 4795 def _parse_using_identifiers(self) -> list[exp.Expr]: 4796 def _parse_column_as_identifier() -> exp.Expr | None: 4797 this = self._parse_column() 4798 if isinstance(this, exp.Column): 4799 return this.this 4800 return this 4801 4802 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4803 4804 def _parse_join( 4805 self, 4806 skip_join_token: bool = False, 4807 parse_bracket: bool = False, 4808 alias_tokens: t.Collection[TokenType] | None = None, 4809 ) -> exp.Join | None: 4810 if self._match(TokenType.COMMA): 4811 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4812 cross_join = self.expression(exp.Join(this=table)) if table else None 4813 4814 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4815 cross_join.set("kind", "CROSS") 4816 4817 return cross_join 4818 4819 index = self._index 4820 method, side, kind = self._parse_join_parts() 4821 directed = self._match_text_seq("DIRECTED") 4822 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4823 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4824 join_comments = self._prev_comments 4825 4826 if not skip_join_token and not join: 4827 self._retreat(index) 4828 kind = None 4829 method = None 4830 side = None 4831 4832 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4833 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4834 4835 if not skip_join_token and not join and not outer_apply and not cross_apply: 4836 return None 4837 4838 kwargs: dict[str, t.Any] = { 4839 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4840 } 4841 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4842 kwargs["expressions"] = self._parse_csv( 4843 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4844 ) 4845 4846 if method: 4847 kwargs["method"] = method.text.upper() 4848 if side: 4849 kwargs["side"] = side.text.upper() 4850 if kind: 4851 kwargs["kind"] = kind.text.upper() 4852 if hint: 4853 kwargs["hint"] = hint 4854 4855 if self._match(TokenType.MATCH_CONDITION): 4856 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4857 4858 if self._match(TokenType.ON): 4859 kwargs["on"] = self._parse_disjunction() 4860 elif self._match(TokenType.USING): 4861 kwargs["using"] = self._parse_using_identifiers() 4862 elif ( 4863 not method 4864 and not (outer_apply or cross_apply) 4865 and not isinstance(kwargs["this"], exp.Unnest) 4866 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4867 ): 4868 index = self._index 4869 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4870 4871 if joins and self._match(TokenType.ON): 4872 kwargs["on"] = self._parse_disjunction() 4873 elif joins and self._match(TokenType.USING): 4874 kwargs["using"] = self._parse_using_identifiers() 4875 else: 4876 joins = None 4877 self._retreat(index) 4878 4879 kwargs["this"].set("joins", joins if joins else None) 4880 4881 kwargs["pivots"] = self._parse_pivots() 4882 4883 comments = [c for token in (method, side, kind) if token for c in token.comments] 4884 comments = (join_comments or []) + comments 4885 4886 if ( 4887 self.ADD_JOIN_ON_TRUE 4888 and not kwargs.get("on") 4889 and not kwargs.get("using") 4890 and not kwargs.get("method") 4891 and kwargs.get("kind") in (None, "INNER", "OUTER") 4892 ): 4893 kwargs["on"] = exp.true() 4894 4895 if directed: 4896 kwargs["directed"] = directed 4897 4898 return self.expression(exp.Join(**kwargs), comments=comments) 4899 4900 def _parse_opclass(self) -> exp.Expr | None: 4901 this = self._parse_disjunction() 4902 4903 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4904 return this 4905 4906 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4907 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4908 4909 return this 4910 4911 def _parse_index_params(self) -> exp.IndexParameters: 4912 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4913 4914 if self._match(TokenType.L_PAREN, advance=False): 4915 columns = self._parse_wrapped_csv(self._parse_with_operator) 4916 else: 4917 columns = None 4918 4919 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4920 partition_by = self._parse_partition_by() 4921 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4922 tablespace = ( 4923 self._parse_var(any_token=True) 4924 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4925 else None 4926 ) 4927 where = self._parse_where() 4928 4929 on = self._parse_field() if self._match(TokenType.ON) else None 4930 4931 return self.expression( 4932 exp.IndexParameters( 4933 using=using, 4934 columns=columns, 4935 include=include, 4936 partition_by=partition_by, 4937 where=where, 4938 with_storage=with_storage, 4939 tablespace=tablespace, 4940 on=on, 4941 ) 4942 ) 4943 4944 def _parse_index( 4945 self, index: exp.Expr | None = None, anonymous: bool = False 4946 ) -> exp.Index | None: 4947 if index or anonymous: 4948 unique = None 4949 primary = None 4950 amp = None 4951 4952 self._match(TokenType.ON) 4953 self._match(TokenType.TABLE) # hive 4954 table = self._parse_table_parts(schema=True) 4955 else: 4956 unique = self._match(TokenType.UNIQUE) 4957 primary = self._match_text_seq("PRIMARY") 4958 amp = self._match_text_seq("AMP") 4959 4960 if not self._match(TokenType.INDEX): 4961 return None 4962 4963 index = self._parse_id_var() 4964 table = None 4965 4966 params = self._parse_index_params() 4967 4968 return self.expression( 4969 exp.Index( 4970 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4971 ) 4972 ) 4973 4974 def _parse_table_hints(self) -> list[exp.Expr] | None: 4975 hints: list[exp.Expr] = [] 4976 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4977 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4978 hints.append( 4979 self.expression( 4980 exp.WithTableHint( 4981 expressions=self._parse_csv( 4982 lambda: self._parse_function() or self._parse_var(any_token=True) 4983 ) 4984 ) 4985 ) 4986 ) 4987 self._match_r_paren() 4988 else: 4989 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4990 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4991 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4992 4993 self._match_set((TokenType.INDEX, TokenType.KEY)) 4994 if self._match(TokenType.FOR): 4995 hint.set("target", self._advance_any() and self._prev.text.upper()) 4996 4997 hint.set("expressions", self._parse_wrapped_id_vars()) 4998 hints.append(hint) 4999 5000 return hints or None 5001 5002 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 5003 return ( 5004 (not schema and self._parse_function(optional_parens=False)) 5005 or self._parse_id_var(any_token=False) 5006 or self._parse_string_as_identifier() 5007 or self._parse_placeholder() 5008 ) 5009 5010 def _parse_table_parts_fast(self) -> exp.Table | None: 5011 index = self._index 5012 parts: list[exp.Identifier] | None = None 5013 all_comments: list[str] | None = None 5014 5015 while self._match_set(self.IDENTIFIER_TOKENS): 5016 token = self._prev 5017 comments = self._prev_comments 5018 5019 has_dot = self._match(TokenType.DOT) 5020 curr_tt = self._curr.token_type 5021 5022 if not has_dot: 5023 if curr_tt in self.TABLE_POSTFIX_TOKENS: 5024 self._retreat(index) 5025 return None 5026 elif curr_tt not in self.IDENTIFIER_TOKENS: 5027 self._retreat(index) 5028 return None 5029 5030 if parts is None: 5031 parts = [] 5032 5033 if comments: 5034 if all_comments is None: 5035 all_comments = [] 5036 all_comments.extend(comments) 5037 self._prev_comments = [] 5038 5039 parts.append( 5040 self.expression( 5041 exp.Identifier( 5042 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 5043 ), 5044 token, 5045 ) 5046 ) 5047 5048 if not has_dot: 5049 break 5050 5051 if parts is None: 5052 return None 5053 5054 n = len(parts) 5055 5056 if n == 1: 5057 table: exp.Table = exp.Table(this=parts[0]) 5058 elif n == 2: 5059 table = exp.Table(this=parts[1], db=parts[0]) 5060 elif n >= 3: 5061 this: exp.Identifier | exp.Dot = parts[2] 5062 for i in range(3, n): 5063 this = exp.Dot(this=this, expression=parts[i]) 5064 5065 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 5066 5067 if table is None: 5068 self._retreat(index) 5069 elif all_comments: 5070 table.add_comments(all_comments) 5071 return table 5072 5073 def _parse_table_parts( 5074 self, 5075 schema: bool = False, 5076 is_db_reference: bool = False, 5077 wildcard: bool = False, 5078 fast: bool = False, 5079 ) -> exp.Table | exp.Dot | None: 5080 if fast: 5081 return self._parse_table_parts_fast() 5082 5083 catalog: exp.Expr | str | None = None 5084 db: exp.Expr | str | None = None 5085 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 5086 5087 while self._match(TokenType.DOT): 5088 if catalog: 5089 # This allows nesting the table in arbitrarily many dot expressions if needed 5090 table = self.expression( 5091 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 5092 ) 5093 else: 5094 catalog = db 5095 db = table 5096 # "" used for tsql FROM a..b case 5097 table = self._parse_table_part(schema=schema) or "" 5098 5099 if ( 5100 wildcard 5101 and self._is_connected() 5102 and (isinstance(table, exp.Identifier) or not table) 5103 and self._match(TokenType.STAR) 5104 ): 5105 if isinstance(table, exp.Identifier): 5106 table.args["this"] += "*" 5107 else: 5108 table = exp.Identifier(this="*") 5109 5110 if is_db_reference: 5111 catalog = db 5112 db = table 5113 table = None 5114 5115 if not table and not is_db_reference: 5116 self.raise_error(f"Expected table name but got {self._curr}") 5117 if not db and is_db_reference: 5118 self.raise_error(f"Expected database name but got {self._curr}") 5119 5120 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 5121 5122 # Bubble up comments from identifier parts to the Table 5123 comments = [] 5124 for part in table.parts: 5125 if part_comments := part.pop_comments(): 5126 comments.extend(part_comments) 5127 if comments: 5128 table.add_comments(comments) 5129 5130 changes = self._parse_changes() 5131 if changes: 5132 table.set("changes", changes) 5133 5134 at_before = self._parse_historical_data() 5135 if at_before: 5136 table.set("when", at_before) 5137 5138 pivots = self._parse_pivots() 5139 if pivots: 5140 table.set("pivots", pivots) 5141 5142 return table 5143 5144 def _parse_table( 5145 self, 5146 schema: bool = False, 5147 joins: bool = False, 5148 alias_tokens: t.Collection[TokenType] | None = None, 5149 parse_bracket: bool = False, 5150 is_db_reference: bool = False, 5151 parse_partition: bool = False, 5152 consume_pipe: bool = False, 5153 ) -> exp.Expr | None: 5154 if not schema and not is_db_reference and not consume_pipe and not joins: 5155 index = self._index 5156 table = self._parse_table_parts(fast=True) 5157 5158 if table is not None: 5159 curr_tt = self._curr.token_type 5160 next_tt = self._next.token_type 5161 5162 fast_terminators = self.TABLE_TERMINATORS 5163 5164 # only return the table if we're sure there are no other operators 5165 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5166 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5167 return table 5168 5169 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5170 5171 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5172 if alias := self._parse_table_alias( 5173 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5174 ): 5175 table.set("alias", alias) 5176 5177 if self._curr.token_type in fast_terminators: 5178 return table 5179 5180 self._retreat(index) 5181 5182 if stream := self._parse_stream(): 5183 return stream 5184 5185 if lateral := self._parse_lateral(): 5186 return lateral 5187 5188 if unnest := self._parse_unnest(): 5189 return unnest 5190 5191 if values := self._parse_derived_table_values(): 5192 return values 5193 5194 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5195 if not subquery.args.get("pivots"): 5196 subquery.set("pivots", self._parse_pivots()) 5197 if joins: 5198 for join in self._parse_joins(): 5199 subquery.append("joins", join) 5200 return subquery 5201 5202 bracket = parse_bracket and self._parse_bracket(None) 5203 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5204 5205 rows_from_tables = ( 5206 self._parse_wrapped_csv(self._parse_table) 5207 if self._match_text_seq("ROWS", "FROM") 5208 else None 5209 ) 5210 rows_from = ( 5211 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5212 ) 5213 5214 only = self._match(TokenType.ONLY) 5215 5216 this = t.cast( 5217 exp.Expr, 5218 bracket 5219 or rows_from 5220 or self._parse_bracket( 5221 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5222 ), 5223 ) 5224 5225 if only: 5226 this.set("only", only) 5227 5228 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5229 self._match(TokenType.STAR) 5230 5231 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5232 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5233 this.set("partition", self._parse_partition()) 5234 5235 if schema: 5236 return self._parse_schema(this=this) 5237 5238 if self.dialect.ALIAS_POST_VERSION: 5239 this.set("version", self._parse_version()) 5240 5241 if self.dialect.ALIAS_POST_TABLESAMPLE: 5242 this.set("sample", self._parse_table_sample()) 5243 if ( 5244 this.args.get("sample") 5245 and self.dialect.ALIAS_POST_VERSION 5246 and not this.args.get("version") 5247 ): 5248 this.set("version", self._parse_version()) 5249 5250 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5251 if alias: 5252 this.set("alias", alias) 5253 5254 # DuckDB requires the time-travel clause to come after the alias, e.g. 5255 # SELECT * FROM t AS a AT (VERSION => 1) 5256 if isinstance(this, exp.Table) and not this.args.get("when"): 5257 this.set("when", self._parse_historical_data()) 5258 5259 if self._match(TokenType.INDEXED_BY): 5260 this.set("indexed", self._parse_table_parts()) 5261 elif self._match_text_seq("NOT", "INDEXED"): 5262 this.set("indexed", False) 5263 5264 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5265 return self.expression( 5266 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5267 ) 5268 5269 this.set("hints", self._parse_table_hints()) 5270 5271 if not this.args.get("pivots"): 5272 this.set("pivots", self._parse_pivots()) 5273 5274 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5275 this.set("sample", self._parse_table_sample()) 5276 5277 if not self.dialect.ALIAS_POST_VERSION: 5278 this.set("version", self._parse_version()) 5279 5280 if joins: 5281 for join in self._parse_joins(alias_tokens=alias_tokens): 5282 this.append("joins", join) 5283 5284 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5285 this.set("ordinality", True) 5286 this.set("alias", self._parse_table_alias()) 5287 5288 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5289 # hoist the table args onto the latter and return it instead 5290 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5291 table_from_rows = this.this 5292 for arg in exp.TableFromRows.arg_types: 5293 if arg != "this": 5294 table_from_rows.set(arg, this.args.get(arg)) 5295 5296 this = table_from_rows 5297 5298 return this 5299 5300 def _parse_version(self) -> exp.Version | None: 5301 for phrase, this in self.VERSION_PHRASES.items(): 5302 if self._match_text_seq(*phrase): 5303 break 5304 else: 5305 return None 5306 5307 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5308 kind = self._prev.text.upper() 5309 start = self._parse_bitwise() 5310 self._match_texts(("TO", "AND")) 5311 end = self._parse_bitwise() 5312 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5313 elif self._match_text_seq("CONTAINED", "IN"): 5314 kind = "CONTAINED IN" 5315 expression = self.expression( 5316 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5317 ) 5318 elif self._match(TokenType.ALL): 5319 kind = "ALL" 5320 expression = None 5321 else: 5322 self._match_text_seq("AS", "OF") 5323 kind = "AS OF" 5324 expression = self._parse_bitwise() 5325 5326 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5327 5328 def _parse_historical_data(self) -> exp.HistoricalData | None: 5329 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5330 index = self._index 5331 historical_data = None 5332 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5333 this = self._prev.text.upper() 5334 kind = ( 5335 self._match(TokenType.L_PAREN) 5336 and self._match_texts(self.HISTORICAL_DATA_KIND) 5337 and self._prev.text.upper() 5338 ) 5339 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5340 5341 if expression: 5342 self._match_r_paren() 5343 historical_data = self.expression( 5344 exp.HistoricalData(this=this, kind=kind, expression=expression) 5345 ) 5346 else: 5347 self._retreat(index) 5348 5349 return historical_data 5350 5351 def _parse_changes(self) -> exp.Changes | None: 5352 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5353 return None 5354 5355 information = self._parse_var(any_token=True) 5356 self._match_r_paren() 5357 5358 return self.expression( 5359 exp.Changes( 5360 information=information, 5361 at_before=self._parse_historical_data(), 5362 end=self._parse_historical_data(), 5363 ) 5364 ) 5365 5366 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5367 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5368 return None 5369 5370 self._advance() 5371 5372 expressions = self._parse_wrapped_csv(self._parse_equality) 5373 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5374 5375 alias = self._parse_table_alias() if with_alias else None 5376 5377 if alias: 5378 if self.dialect.UNNEST_COLUMN_ONLY: 5379 if alias.args.get("columns"): 5380 self.raise_error("Unexpected extra column alias in unnest.") 5381 5382 alias.set("columns", [alias.this]) 5383 alias.set("this", None) 5384 5385 columns = alias.args.get("columns") or [] 5386 if offset and len(expressions) < len(columns): 5387 offset = columns.pop() 5388 5389 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5390 self._match(TokenType.ALIAS) 5391 offset = self._parse_id_var( 5392 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5393 ) or exp.to_identifier("offset") 5394 5395 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5396 5397 def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None: 5398 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5399 if not is_derived and not ( 5400 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5401 self._match_text_seq("VALUES") 5402 or self._match_text_seq("FORMAT", "VALUES") 5403 # MySQL accepts VALUE as a synonym for VALUES in INSERT statements. 5404 or (allow_value_synonym and self._match_text_seq("VALUE")) 5405 ): 5406 return None 5407 5408 expressions = self._parse_csv(self._parse_value) 5409 alias = self._parse_table_alias() 5410 5411 if is_derived: 5412 self._match_r_paren() 5413 5414 return self.expression( 5415 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5416 ) 5417 5418 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5419 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5420 as_modifier and self._match_text_seq("USING", "SAMPLE") 5421 ): 5422 return None 5423 5424 bucket_numerator = None 5425 bucket_denominator = None 5426 bucket_field = None 5427 percent = None 5428 size = None 5429 seed = None 5430 5431 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5432 matched_l_paren = self._match(TokenType.L_PAREN) 5433 5434 if self.TABLESAMPLE_CSV: 5435 num = None 5436 expressions = self._parse_csv(self._parse_primary) 5437 else: 5438 expressions = None 5439 num = ( 5440 self._parse_factor(parse_mod=False) 5441 if self._match(TokenType.NUMBER, advance=False) 5442 else self._parse_primary() or self._parse_placeholder() 5443 ) 5444 5445 if self._match_text_seq("BUCKET"): 5446 bucket_numerator = self._parse_number() 5447 self._match_text_seq("OUT", "OF") 5448 bucket_denominator = bucket_denominator = self._parse_number() 5449 self._match(TokenType.ON) 5450 bucket_field = self._parse_field() 5451 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5452 percent = num 5453 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5454 size = num 5455 else: 5456 percent = num 5457 5458 if matched_l_paren: 5459 self._match_r_paren() 5460 5461 if self._match(TokenType.L_PAREN): 5462 method = self._parse_var(upper=True) 5463 seed = self._match(TokenType.COMMA) and self._parse_number() 5464 self._match_r_paren() 5465 elif self._match_texts(("SEED", "REPEATABLE")): 5466 seed = self._parse_wrapped(self._parse_number) 5467 5468 if not method and self.DEFAULT_SAMPLING_METHOD: 5469 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5470 5471 return self.expression( 5472 exp.TableSample( 5473 expressions=expressions, 5474 method=method, 5475 bucket_numerator=bucket_numerator, 5476 bucket_denominator=bucket_denominator, 5477 bucket_field=bucket_field, 5478 percent=percent, 5479 size=size, 5480 seed=seed, 5481 ) 5482 ) 5483 5484 def _parse_pivots(self) -> list[exp.Pivot] | None: 5485 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5486 return None 5487 return list(iter(self._parse_pivot, None)) or None 5488 5489 def _parse_joins( 5490 self, alias_tokens: t.Collection[TokenType] | None = None 5491 ) -> t.Iterator[exp.Join]: 5492 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5493 5494 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5495 if not self._match(TokenType.INTO): 5496 return None 5497 5498 return self.expression( 5499 exp.UnpivotColumns( 5500 this=self._match_text_seq("NAME") and self._parse_column(), 5501 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5502 ) 5503 ) 5504 5505 # https://duckdb.org/docs/sql/statements/pivot 5506 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5507 def _parse_on() -> exp.Expr | None: 5508 this = self._parse_bitwise() 5509 5510 if self._match(TokenType.IN): 5511 # PIVOT ... ON col IN (row_val1, row_val2) 5512 return self._parse_in(this) 5513 if self._match(TokenType.ALIAS, advance=False): 5514 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5515 return self._parse_alias(this) 5516 5517 return this 5518 5519 this = self._parse_table() 5520 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5521 into = self._parse_unpivot_columns() 5522 using = self._match(TokenType.USING) and self._parse_csv( 5523 lambda: self._parse_alias(self._parse_column()) 5524 ) 5525 group = self._parse_group() 5526 5527 return self.expression( 5528 exp.Pivot( 5529 this=this, 5530 expressions=expressions, 5531 using=using, 5532 group=group, 5533 unpivot=is_unpivot, 5534 into=into, 5535 ) 5536 ) 5537 5538 def _parse_pivot_in(self) -> exp.In: 5539 def _parse_aliased_expression() -> exp.Expr | None: 5540 this = self._parse_select_or_expression() 5541 5542 self._match(TokenType.ALIAS) 5543 alias = self._parse_bitwise() 5544 if alias: 5545 if isinstance(alias, exp.Column) and not alias.db: 5546 alias = alias.this 5547 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5548 5549 return this 5550 5551 value = self._parse_column() 5552 5553 if not self._match(TokenType.IN): 5554 self.raise_error("Expecting IN") 5555 5556 if self._match(TokenType.L_PAREN): 5557 if self._match(TokenType.ANY): 5558 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5559 else: 5560 exprs = self._parse_csv(_parse_aliased_expression) 5561 self._match_r_paren() 5562 return self.expression(exp.In(this=value, expressions=exprs)) 5563 5564 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5565 5566 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5567 func = self._parse_function() 5568 if not func: 5569 if self._prev.token_type == TokenType.COMMA: 5570 return None 5571 self.raise_error("Expecting an aggregation function in PIVOT") 5572 5573 return self._parse_alias(func) 5574 5575 def _parse_pivot(self) -> exp.Pivot | None: 5576 index = self._index 5577 include_nulls = None 5578 5579 if self._match(TokenType.PIVOT): 5580 unpivot = False 5581 elif self._match(TokenType.UNPIVOT): 5582 unpivot = True 5583 5584 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5585 if self._match_text_seq("INCLUDE", "NULLS"): 5586 include_nulls = True 5587 elif self._match_text_seq("EXCLUDE", "NULLS"): 5588 include_nulls = False 5589 else: 5590 return None 5591 5592 expressions = [] 5593 5594 if not self._match(TokenType.L_PAREN): 5595 self._retreat(index) 5596 return None 5597 5598 if unpivot: 5599 expressions = self._parse_csv(self._parse_column) 5600 else: 5601 expressions = self._parse_csv(self._parse_pivot_aggregation) 5602 5603 if not expressions: 5604 self.raise_error("Failed to parse PIVOT's aggregation list") 5605 5606 if not self._match(TokenType.FOR): 5607 self.raise_error("Expecting FOR") 5608 5609 fields = [] 5610 while True: 5611 field = self._try_parse(self._parse_pivot_in) 5612 if not field: 5613 break 5614 fields.append(field) 5615 5616 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5617 self._parse_bitwise 5618 ) 5619 5620 group = self._parse_group() 5621 5622 self._match_r_paren() 5623 5624 pivot = self.expression( 5625 exp.Pivot( 5626 expressions=expressions, 5627 fields=fields, 5628 unpivot=unpivot, 5629 include_nulls=include_nulls, 5630 default_on_null=default_on_null, 5631 group=group, 5632 ) 5633 ) 5634 5635 if unpivot: 5636 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5637 for pivot_field in pivot.fields: 5638 if isinstance(pivot_field, exp.In): 5639 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5640 5641 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5642 5643 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5644 pivot.set("alias", self._parse_table_alias()) 5645 5646 if not unpivot: 5647 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5648 5649 columns: list[exp.Expr] = [] 5650 all_fields = [] 5651 for pivot_field in pivot.fields: 5652 pivot_field_expressions = pivot_field.expressions 5653 5654 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5655 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5656 continue 5657 5658 all_fields.append( 5659 [ 5660 # An explicit `<field> AS <alias>` names the output column directly, 5661 # so it wins over the dialect's string-identifying convention 5662 fld.sql() 5663 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5664 else fld.alias_or_name 5665 for fld in pivot_field_expressions 5666 ] 5667 ) 5668 5669 if all_fields: 5670 if names: 5671 all_fields.append(names) 5672 5673 # Generate all possible combinations of the pivot columns 5674 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5675 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5676 for fld_parts_tuple in itertools.product(*all_fields): 5677 fld_parts = list(fld_parts_tuple) 5678 5679 if names and self.PREFIXED_PIVOT_COLUMNS: 5680 # Move the "name" to the front of the list 5681 fld_parts.insert(0, fld_parts.pop(-1)) 5682 5683 columns.append(exp.to_identifier("_".join(fld_parts))) 5684 5685 pivot.set("columns", columns) 5686 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5687 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5688 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5689 5690 return pivot 5691 5692 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5693 return [agg.alias for agg in aggregations if agg.alias] 5694 5695 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5696 if not skip_where_token and not self._match(TokenType.PREWHERE): 5697 return None 5698 5699 comments = self._prev_comments 5700 return self.expression( 5701 exp.PreWhere(this=self._parse_disjunction()), 5702 comments=comments, 5703 ) 5704 5705 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5706 if not skip_where_token and not self._match(TokenType.WHERE): 5707 return None 5708 5709 comments = self._prev_comments 5710 return self.expression( 5711 exp.Where(this=self._parse_disjunction()), 5712 comments=comments, 5713 ) 5714 5715 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5716 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5717 return None 5718 comments = self._prev_comments 5719 5720 elements: dict[str, t.Any] = defaultdict(list) 5721 5722 if self._match(TokenType.ALL): 5723 elements["all"] = True 5724 elif self._match(TokenType.DISTINCT): 5725 elements["all"] = False 5726 5727 while True: 5728 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5729 # which are also valid identifiers 5730 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5731 break 5732 5733 elements["expressions"].extend( 5734 self._parse_csv( 5735 lambda: ( 5736 self._parse_grouping_sets() 5737 or self._parse_cube_or_rollup() 5738 or self._parse_disjunction() 5739 ) 5740 ) 5741 ) 5742 5743 before_with_index = self._index 5744 5745 if self._match(TokenType.WITH) and ( 5746 cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True) 5747 ): 5748 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5749 elements[key].append(cube_or_rollup) 5750 elif grouping_sets := self._parse_grouping_sets(): 5751 # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...) 5752 elements["grouping_sets"].append(grouping_sets) 5753 break 5754 elif self._match_text_seq("TOTALS"): 5755 elements["totals"] = True # type: ignore 5756 5757 if before_with_index <= self._index <= before_with_index + 1: 5758 self._retreat(before_with_index) 5759 break 5760 5761 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5762 5763 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5764 if self._match(TokenType.CUBE): 5765 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5766 elif self._match(TokenType.ROLLUP): 5767 kind = exp.Rollup 5768 else: 5769 return None 5770 5771 return self.expression( 5772 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5773 ) 5774 5775 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5776 if self._match(TokenType.GROUPING_SETS): 5777 return self.expression( 5778 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5779 ) 5780 return None 5781 5782 def _parse_grouping_set(self) -> exp.Expr | None: 5783 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5784 5785 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5786 if not skip_having_token and not self._match(TokenType.HAVING): 5787 return None 5788 comments = self._prev_comments 5789 return self.expression( 5790 exp.Having(this=self._parse_disjunction()), 5791 comments=comments, 5792 ) 5793 5794 def _parse_qualify(self) -> exp.Qualify | None: 5795 if not self._match(TokenType.QUALIFY): 5796 return None 5797 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5798 5799 def _parse_connect_with_prior(self) -> exp.Expr | None: 5800 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5801 exp.Prior(this=self._parse_bitwise()) 5802 ) 5803 connect = self._parse_disjunction() 5804 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5805 return connect 5806 5807 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5808 if skip_start_token: 5809 start = None 5810 elif self._match_text_seq("START", "WITH"): 5811 start = self._parse_disjunction() 5812 else: 5813 return None 5814 5815 self._match(TokenType.CONNECT_BY) 5816 nocycle = self._match_text_seq("NOCYCLE") 5817 connect = self._parse_connect_with_prior() 5818 5819 if not start and self._match_text_seq("START", "WITH"): 5820 start = self._parse_disjunction() 5821 5822 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5823 5824 def _parse_name_as_expression(self) -> exp.Expr | None: 5825 this = self._parse_id_var(any_token=True) 5826 if self._match(TokenType.ALIAS): 5827 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5828 return this 5829 5830 def _parse_interpolate(self) -> list[exp.Expr] | None: 5831 if self._match_text_seq("INTERPOLATE"): 5832 return self._parse_wrapped_csv(self._parse_name_as_expression) 5833 return None 5834 5835 def _parse_order( 5836 self, this: exp.Expr | None = None, skip_order_token: bool = False 5837 ) -> exp.Expr | None: 5838 siblings = None 5839 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5840 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5841 return this 5842 5843 siblings = True 5844 5845 comments = self._prev_comments 5846 return self.expression( 5847 exp.Order( 5848 this=this, 5849 expressions=self._parse_csv(self._parse_ordered), 5850 siblings=siblings, 5851 ), 5852 comments=comments, 5853 ) 5854 5855 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5856 if not self._match(token): 5857 return None 5858 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5859 5860 def _parse_ordered( 5861 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5862 ) -> exp.Ordered | None: 5863 this = parse_method() if parse_method else self._parse_disjunction() 5864 if not this: 5865 return None 5866 5867 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5868 this = exp.var("ALL") 5869 5870 asc = self._match(TokenType.ASC) 5871 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5872 5873 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5874 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5875 5876 nulls_first = is_nulls_first or False 5877 explicitly_null_ordered = is_nulls_first or is_nulls_last 5878 5879 if ( 5880 not explicitly_null_ordered 5881 and ( 5882 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5883 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5884 ) 5885 and self.dialect.NULL_ORDERING != "nulls_are_last" 5886 ): 5887 nulls_first = True 5888 5889 if self._match_text_seq("WITH", "FILL"): 5890 with_fill = self.expression( 5891 exp.WithFill( 5892 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5893 to=self._match_text_seq("TO") and self._parse_bitwise(), 5894 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5895 interpolate=self._parse_interpolate(), 5896 ) 5897 ) 5898 else: 5899 with_fill = None 5900 5901 return self.expression( 5902 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5903 ) 5904 5905 def _parse_limit_options(self) -> exp.LimitOptions | None: 5906 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5907 rows = self._match_texts(("ROW", "ROWS")) 5908 self._match_text_seq("ONLY") 5909 with_ties = self._match_text_seq("WITH", "TIES") 5910 5911 if not (percent or rows or with_ties): 5912 return None 5913 5914 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5915 5916 def _parse_limit( 5917 self, 5918 this: exp.Expr | None = None, 5919 top: bool = False, 5920 skip_limit_token: bool = False, 5921 ) -> exp.Expr | None: 5922 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5923 comments = self._prev_comments 5924 if top: 5925 limit_paren = self._match(TokenType.L_PAREN) 5926 expression = ( 5927 self._parse_term() or self._parse_select() 5928 if limit_paren 5929 else self._parse_number() 5930 ) 5931 5932 if limit_paren: 5933 self._match_r_paren() 5934 5935 else: 5936 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5937 expression = exp.var("ALL") 5938 else: 5939 expression = self._parse_term(parse_mod=False) 5940 limit_options = self._parse_limit_options() 5941 5942 if self._match(TokenType.COMMA): 5943 offset = expression 5944 expression = self._parse_term() 5945 else: 5946 offset = None 5947 5948 limit_exp = self.expression( 5949 exp.Limit( 5950 this=this, 5951 expression=expression, 5952 offset=offset, 5953 limit_options=limit_options, 5954 expressions=self._parse_limit_by(), 5955 ), 5956 comments=comments, 5957 ) 5958 5959 if top: 5960 limit_exp.meta["top"] = True 5961 5962 return limit_exp 5963 5964 if self._match(TokenType.FETCH): 5965 direction = ( 5966 self._prev.text.upper() 5967 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5968 else "FIRST" 5969 ) 5970 5971 count = ( 5972 None 5973 if self._match_texts(("ROW", "ROWS"), advance=False) 5974 else self._parse_field(tokens=self.FETCH_TOKENS) 5975 ) 5976 5977 return self.expression( 5978 exp.Fetch( 5979 direction=direction, count=count, limit_options=self._parse_limit_options() 5980 ) 5981 ) 5982 5983 return this 5984 5985 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5986 if not self._match(TokenType.OFFSET): 5987 return this 5988 5989 count = self._parse_term() 5990 self._match_texts(("ROW", "ROWS")) 5991 5992 return self.expression( 5993 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5994 ) 5995 5996 def _can_parse_limit_or_offset(self) -> bool: 5997 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5998 return False 5999 6000 index = self._index 6001 result = bool( 6002 self._try_parse(self._parse_limit, retreat=True) 6003 or self._try_parse(self._parse_offset, retreat=True) 6004 ) 6005 self._retreat(index) 6006 6007 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 6008 if self._next.token_type == TokenType.MATCH_CONDITION: 6009 result = False 6010 6011 return result 6012 6013 def _can_parse_named_window(self) -> bool: 6014 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 6015 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 6016 if not self._match(TokenType.WINDOW, advance=False): 6017 return False 6018 6019 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 6020 if name is None or name.token_type not in self.ID_VAR_TOKENS: 6021 return False 6022 6023 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 6024 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 6025 return False 6026 6027 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 6028 return body is not None and body.token_type == TokenType.L_PAREN 6029 6030 def _parse_limit_by(self) -> list[exp.Expr] | None: 6031 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 6032 6033 def _parse_locks(self) -> list[exp.Lock]: 6034 locks = [] 6035 while True: 6036 update, key = None, None 6037 if self._match_text_seq("FOR", "UPDATE"): 6038 update = True 6039 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 6040 "LOCK", "IN", "SHARE", "MODE" 6041 ): 6042 update = False 6043 elif self._match_text_seq("FOR", "KEY", "SHARE"): 6044 update, key = False, True 6045 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 6046 update, key = True, True 6047 else: 6048 break 6049 6050 expressions = None 6051 if self._match_text_seq("OF"): 6052 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 6053 6054 wait: bool | exp.Expr | None = None 6055 if self._match_text_seq("NOWAIT"): 6056 wait = True 6057 elif self._match_text_seq("WAIT"): 6058 wait = self._parse_primary() 6059 elif self._match_text_seq("SKIP", "LOCKED"): 6060 wait = False 6061 6062 locks.append( 6063 self.expression( 6064 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 6065 ) 6066 ) 6067 6068 return locks 6069 6070 def parse_set_operation( 6071 self, this: exp.Expr | None, consume_pipe: bool = False 6072 ) -> exp.Expr | None: 6073 start = self._index 6074 _, side_token, kind_token = self._parse_join_parts() 6075 6076 side = side_token.text if side_token else None 6077 kind = kind_token.text if kind_token else None 6078 6079 if not self._match_set(self.SET_OPERATIONS): 6080 self._retreat(start) 6081 return None 6082 6083 token_type = self._prev.token_type 6084 6085 if token_type == TokenType.UNION: 6086 operation: type[exp.SetOperation] = exp.Union 6087 elif token_type == TokenType.EXCEPT: 6088 operation = exp.Except 6089 else: 6090 operation = exp.Intersect 6091 6092 comments = self._prev.comments 6093 6094 if self._match(TokenType.DISTINCT): 6095 distinct: bool | None = True 6096 elif self._match(TokenType.ALL): 6097 distinct = False 6098 else: 6099 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 6100 if distinct is None: 6101 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 6102 6103 by_name = ( 6104 self._match_text_seq("BY", "NAME") 6105 or self._match_text_seq("STRICT", "CORRESPONDING") 6106 or None 6107 ) 6108 if self._match_text_seq("CORRESPONDING"): 6109 by_name = True 6110 if not side and not kind: 6111 kind = "INNER" 6112 6113 on_column_list = None 6114 if by_name and self._match_texts(("ON", "BY")): 6115 on_column_list = self._parse_wrapped_csv(self._parse_column) 6116 6117 expression = self._parse_select( 6118 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 6119 ) 6120 6121 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 6122 # in _parse_cte and so that alias pushdown can reach into set operation branches 6123 if isinstance(this, exp.Values): 6124 this = self._values_to_select(this) 6125 if isinstance(expression, exp.Values): 6126 expression = self._values_to_select(expression) 6127 6128 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 6129 subquery = this.this 6130 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 6131 subquery.add_comments(this.pop_comments()) 6132 this = subquery 6133 6134 return self.expression( 6135 operation( 6136 this=this, 6137 distinct=distinct, 6138 by_name=by_name, 6139 expression=expression, 6140 side=side, 6141 kind=kind, 6142 on=on_column_list, 6143 ), 6144 comments=comments, 6145 ) 6146 6147 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 6148 while this: 6149 setop = self.parse_set_operation(this) 6150 if not setop: 6151 break 6152 this = setop 6153 6154 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6155 expression = this.expression 6156 6157 if expression: 6158 for arg in self.SET_OP_MODIFIERS: 6159 expr = expression.args.get(arg) 6160 if expr and not (arg == "limit" and expr.meta.get("top")): 6161 expression.set(arg, None) 6162 this.set(arg, expr) 6163 6164 # A trailing LIMIT/FETCH can coexist with TOP on the final operand. 6165 if self._curr.token_type in (TokenType.LIMIT, TokenType.FETCH): 6166 this = self._parse_query_modifiers(this) 6167 6168 return this 6169 6170 def _parse_expression(self) -> exp.Expr | None: 6171 return self._parse_alias(self._parse_assignment()) 6172 6173 def _parse_assignment(self) -> exp.Expr | None: 6174 this = self._parse_disjunction() 6175 if not this and self._next.token_type in self.ASSIGNMENT: 6176 # This allows us to parse <non-identifier token> := <expr> 6177 this = exp.column( 6178 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6179 ) 6180 6181 while self._match_set(self.ASSIGNMENT): 6182 if isinstance(this, exp.Column) and len(this.parts) == 1: 6183 this = this.this 6184 6185 comments = self._prev_comments 6186 this = self.expression( 6187 self.ASSIGNMENT[self._prev.token_type]( 6188 this=this, expression=self._parse_assignment() 6189 ), 6190 comments=comments, 6191 ) 6192 6193 return this 6194 6195 def _parse_disjunction(self) -> exp.Expr | None: 6196 this = self._parse_conjunction() 6197 while self._match_set(self.DISJUNCTION): 6198 comments = self._prev_comments 6199 this = self.expression( 6200 self.DISJUNCTION[self._prev.token_type]( 6201 this=this, expression=self._parse_conjunction() 6202 ), 6203 comments=comments, 6204 ) 6205 return this 6206 6207 def _parse_conjunction(self) -> exp.Expr | None: 6208 this = self._parse_equality() 6209 while self._match_set(self.CONJUNCTION): 6210 comments = self._prev_comments 6211 this = self.expression( 6212 self.CONJUNCTION[self._prev.token_type]( 6213 this=this, expression=self._parse_equality() 6214 ), 6215 comments=comments, 6216 ) 6217 return this 6218 6219 def _build_predicate( 6220 self, 6221 klass: Type[exp.Binary], 6222 this: exp.Expr | None, 6223 expression: exp.Expr | None, 6224 comments: list[str] | None = None, 6225 negate: bool = False, 6226 ) -> exp.Expr: 6227 is_predicates = (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ) 6228 if klass in is_predicates: 6229 if ( 6230 isinstance(this, (exp.Connector, exp.Not, exp.NullSafeNEQ)) 6231 or type(this) in self.EQUALITY.values() 6232 or type(this) in self.COMPARISON.values() 6233 or (klass is not exp.Is and isinstance(this, exp.Is)) 6234 ): 6235 this = exp.Paren(this=this) 6236 if isinstance(expression, (exp.Predicate, exp.Connector, exp.Not, exp.Escape)): 6237 expression = exp.Paren(this=expression) 6238 else: 6239 if isinstance(this.this if isinstance(this, exp.Not) else this, is_predicates): 6240 this = exp.Paren(this=this) 6241 if isinstance( 6242 expression.this if isinstance(expression, exp.Not) else expression, is_predicates 6243 ): 6244 expression = exp.Paren(this=expression) 6245 6246 predicate = klass(this=this, expression=expression) 6247 if negate: 6248 predicate.set("negate", True) 6249 return self.expression(predicate, comments=comments) 6250 6251 def _parse_equality(self, parse_is: bool = True) -> exp.Expr | None: 6252 this = self._parse_comparison() 6253 while True: 6254 if self._match_set(self.EQUALITY): 6255 comments = self._prev_comments 6256 this = self._build_predicate( 6257 self.EQUALITY[self._prev.token_type], 6258 this, 6259 self._parse_comparison(), 6260 comments=comments, 6261 ) 6262 elif parse_is and self._is_predicate_ahead(): 6263 index = self._index 6264 # A postfix predicate can itself be an operand, e.g. x IS NULL = FALSE. 6265 this = self._parse_comparison(self._parse_range(this, parse_is=True)) 6266 if self._index == index: 6267 break 6268 else: 6269 break 6270 return this 6271 6272 def _parse_comparison(self, this: exp.Expr | None = None) -> exp.Expr | None: 6273 this = this or self._parse_range() 6274 while self._match_set(self.COMPARISON): 6275 comments = self._prev_comments 6276 this = self._build_predicate( 6277 self.COMPARISON[self._prev.token_type], 6278 this, 6279 self._parse_range(), 6280 comments=comments, 6281 ) 6282 return this 6283 6284 def _is_predicate_ahead(self) -> bool: 6285 if ( 6286 self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.HIGH 6287 or self._curr.token_type not in self.IS_PREDICATE_TOKENS 6288 ): 6289 return False 6290 return self._curr.token_type != TokenType.NOT or self._next.token_type == TokenType.NULL 6291 6292 def _parse_range(self, this: exp.Expr | None = None, parse_is: bool = False) -> exp.Expr | None: 6293 this = this or self._parse_bitwise() 6294 6295 while True: 6296 if not parse_is and self._is_predicate_ahead(): 6297 break 6298 negate = self._match(TokenType.NOT) 6299 if self._match_set(self.RANGE_PARSERS): 6300 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6301 if not expression: 6302 return this 6303 6304 this = expression 6305 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6306 this = self._build_predicate(exp.Is, this, exp.Null()) 6307 elif self._match(TokenType.NOTNULL): 6308 # Postgres supports ISNULL and NOTNULL for conditions. 6309 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6310 if self.dialect.NORMALIZE_NOT_NULL: 6311 this = self._build_predicate(exp.Is, this, exp.Null()) 6312 this = self.expression(exp.Not(this=this)) 6313 else: 6314 this = self._build_predicate(exp.Is, this, exp.Null(), negate=True) 6315 else: 6316 if negate: 6317 self._retreat(self._index - 1) 6318 break 6319 6320 if negate: 6321 this = self._negate_range(this) 6322 if (negate or (isinstance(this, exp.Not) and isinstance(this.this, exp.Is))) and ( 6323 self._curr.token_type == TokenType.NOT 6324 or self._curr.token_type in self.RANGE_PARSERS 6325 ): 6326 this = self.expression(exp.Paren(this=this)) 6327 6328 return this 6329 6330 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6331 if not this: 6332 return this 6333 6334 expression = this.this if isinstance(this, exp.Escape) else this 6335 if isinstance(expression, (exp.Like, exp.ILike)): 6336 expression.set("negate", True) 6337 return this 6338 6339 return self.expression(exp.Not(this=this)) 6340 6341 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6342 index = self._index - 1 6343 negate = self._match(TokenType.NOT) 6344 6345 if self._match_text_seq("DISTINCT", "FROM"): 6346 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6347 return self._build_predicate(klass, this, self._parse_is_rhs(distinct=True)) 6348 6349 if self._match(TokenType.JSON): 6350 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6351 6352 if self._match_text_seq("WITH"): 6353 _with = True 6354 elif self._match_text_seq("WITHOUT"): 6355 _with = False 6356 else: 6357 _with = None 6358 6359 unique = self._match(TokenType.UNIQUE) 6360 self._match_text_seq("KEYS") 6361 expression: exp.Expr | None = self.expression( 6362 exp.JSON(this=kind, with_=_with, unique=unique) 6363 ) 6364 else: 6365 expression = self._parse_is_rhs() 6366 if not expression: 6367 self._retreat(index) 6368 return None 6369 6370 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6371 this = self._build_predicate(exp.Is, this, expression, negate=True) 6372 else: 6373 this = self._build_predicate(exp.Is, this, expression) 6374 this = self.expression(exp.Not(this=this)) if negate else this 6375 6376 return self._parse_column_ops(this) 6377 6378 def _parse_is_rhs(self, distinct: bool = False) -> exp.Expr | None: 6379 if distinct: 6380 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.LOW: 6381 return self._parse_equality(parse_is=False) 6382 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.EQUALITY: 6383 return self._parse_comparison() 6384 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.COMPARISON: 6385 return self._parse_range() 6386 return self._parse_bitwise() 6387 return self._parse_null() or self._parse_bitwise() 6388 6389 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6390 unnest = self._parse_unnest(with_alias=False) 6391 if unnest: 6392 this = self.expression(exp.In(this=this, unnest=unnest)) 6393 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6394 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6395 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6396 6397 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6398 this = self.expression( 6399 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6400 ) 6401 else: 6402 this = self.expression(exp.In(this=this, expressions=expressions)) 6403 6404 if matched_l_paren: 6405 self._match_r_paren(this) 6406 elif not self._match(TokenType.R_BRACKET, expression=this): 6407 self.raise_error("Expecting ]") 6408 else: 6409 this = self.expression(exp.In(this=this, field=self._parse_column())) 6410 6411 return this 6412 6413 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6414 # Preserve grouping when BETWEEN is rewritten into comparisons. 6415 if isinstance(this, (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ)): 6416 this = exp.Paren(this=this) 6417 6418 symmetric = None 6419 if self._match_text_seq("SYMMETRIC"): 6420 symmetric = True 6421 elif self._match_text_seq("ASYMMETRIC"): 6422 symmetric = False 6423 6424 low = self._parse_bitwise() 6425 self._match(TokenType.AND) 6426 high = self._parse_bitwise() 6427 6428 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6429 6430 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6431 if not self._match(TokenType.ESCAPE): 6432 return this 6433 return self.expression( 6434 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6435 ) 6436 6437 def _parse_interval_span( 6438 self, this: exp.Expr, parse_function_unit: bool = True 6439 ) -> exp.Interval: 6440 # handle day-time format interval span with omitted units: 6441 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6442 interval_span_units_omitted = None 6443 if ( 6444 this 6445 and this.is_string 6446 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6447 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6448 ): 6449 index = self._index 6450 6451 # Var "TO" Var 6452 first_unit = self._parse_var(any_token=True, upper=True) 6453 second_unit = None 6454 if first_unit and self._match_text_seq("TO"): 6455 second_unit = self._parse_var(any_token=True, upper=True) 6456 6457 interval_span_units_omitted = not (first_unit and second_unit) 6458 6459 self._retreat(index) 6460 6461 unit_index = self._index 6462 if interval_span_units_omitted: 6463 unit = None 6464 else: 6465 # Only attempt to parse a unit if the current token can actually be one, so that a 6466 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6467 is_unit = self._curr is not None and ( 6468 self._curr.token_type == TokenType.VAR 6469 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6470 ) 6471 unit = self._parse_function() if parse_function_unit and is_unit else None 6472 if not unit and is_unit: 6473 unit = self._parse_var(any_token=True, upper=True) 6474 6475 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6476 # each INTERVAL expression into this canonical form so it's easy to transpile 6477 if this and this.is_number: 6478 try: 6479 this = exp.Literal.string(this.to_py()) 6480 except ValueError: 6481 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6482 elif this and this.is_string: 6483 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6484 if parts and unit: 6485 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6486 unit = None 6487 self._retreat(unit_index) 6488 6489 if len(parts) == 1: 6490 this = exp.Literal.string(parts[0][0]) 6491 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6492 6493 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6494 unit = self.expression( 6495 exp.IntervalSpan( 6496 this=unit, 6497 expression=self._parse_function() 6498 or self._parse_var(any_token=True, upper=True), 6499 ) 6500 ) 6501 6502 return self.expression(exp.Interval(this=this, unit=unit)) 6503 6504 def _parse_interval( 6505 self, require_interval: bool = True, parse_function_unit: bool = True 6506 ) -> exp.Add | exp.Interval | None: 6507 index = self._index 6508 6509 if not self._match(TokenType.INTERVAL) and require_interval: 6510 return None 6511 6512 if self._match(TokenType.STRING, advance=False): 6513 this = self._parse_primary() 6514 else: 6515 this = self._parse_term() 6516 6517 if not this or ( 6518 isinstance(this, exp.Column) 6519 and not this.table 6520 and not this.this.quoted 6521 and self._curr 6522 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6523 ): 6524 self._retreat(index) 6525 return None 6526 6527 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6528 6529 index = self._index 6530 self._match(TokenType.PLUS) 6531 6532 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6533 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6534 return self.expression( 6535 exp.Add( 6536 this=interval, 6537 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6538 ) 6539 ) 6540 6541 self._retreat(index) 6542 return interval 6543 6544 def _parse_bitwise(self) -> exp.Expr | None: 6545 this = self._parse_term() 6546 6547 while True: 6548 if self._match_set(self.BITWISE): 6549 this = self.expression( 6550 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6551 ) 6552 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6553 this = self.expression( 6554 exp.DPipe( 6555 this=this, 6556 expression=self._parse_term(), 6557 safe=not self.dialect.STRICT_STRING_CONCAT, 6558 ) 6559 ) 6560 elif self._match(TokenType.DQMARK): 6561 this = self.expression( 6562 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6563 ) 6564 elif self._match_pair(TokenType.LT, TokenType.LT): 6565 this = self.expression( 6566 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6567 ) 6568 elif self._match_pair(TokenType.GT, TokenType.GT): 6569 this = self.expression( 6570 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6571 ) 6572 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6573 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6574 else: 6575 break 6576 6577 return this 6578 6579 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6580 this = self._parse_factor(parse_mod=parse_mod) 6581 6582 while self._match_set(self.TERM): 6583 klass = self.TERM[self._prev.token_type] 6584 comments = self._prev_comments 6585 expression = self._parse_factor(parse_mod=parse_mod) 6586 6587 this = self.expression(klass(this=this, expression=expression), comments=comments) 6588 6589 if isinstance(this, exp.Collate): 6590 self._normalize_collate(this) 6591 6592 return this 6593 6594 def _normalize_collate(self, collate: exp.Collate) -> None: 6595 expr = collate.expression 6596 6597 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6598 # fallback to Identifier / Var 6599 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6600 ident = expr.this 6601 if isinstance(ident, exp.Identifier): 6602 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6603 6604 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6605 parse_method = self._parse_factor_operand 6606 this = self._parse_at_time_zone(parse_method()) 6607 6608 while self._match_set(self.FACTOR, advance=False): 6609 if not parse_mod and self._curr.token_type == TokenType.MOD: 6610 break 6611 6612 self._advance() 6613 klass = self.FACTOR[self._prev.token_type] 6614 comments = self._prev_comments 6615 expression = parse_method() 6616 6617 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6618 self._retreat(self._index - 1) 6619 return this 6620 6621 this = self.expression(klass(this=this, expression=expression), comments=comments) 6622 6623 if isinstance(this, exp.Div): 6624 this.set("typed", self.dialect.TYPED_DIVISION) 6625 this.set("safe", self.dialect.SAFE_DIVISION) 6626 6627 return this 6628 6629 def _parse_factor_operand(self) -> exp.Expr | None: 6630 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6631 6632 def _parse_exponent(self) -> exp.Expr | None: 6633 this = self._parse_unary() 6634 while self._match_set(self.EXPONENT): 6635 comments = self._prev_comments 6636 this = self.expression( 6637 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6638 comments=comments, 6639 ) 6640 return this 6641 6642 def _parse_unary(self) -> exp.Expr | None: 6643 if self._match_set(self.UNARY_PARSERS): 6644 return self.UNARY_PARSERS[self._prev.token_type](self) 6645 return self._parse_type() 6646 6647 def _parse_type( 6648 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6649 ) -> exp.Expr | None: 6650 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6651 return atom 6652 6653 if interval := parse_interval and self._parse_interval(): 6654 return self._parse_column_ops(interval) 6655 6656 index = self._index 6657 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6658 6659 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6660 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6661 if isinstance(data_type, exp.Cast): 6662 # This constructor can contain ops directly after it, for instance struct unnesting: 6663 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6664 return self._parse_column_ops(data_type) 6665 6666 if data_type: 6667 index2 = self._index 6668 this = self._parse_primary() 6669 6670 if isinstance(this, exp.Literal): 6671 literal = this.name 6672 this = self._parse_column_ops(this) 6673 6674 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6675 if data_type.is_type(exp.DType.TIMESTAMP): 6676 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6677 elif data_type.is_type(exp.DType.TIME): 6678 data_type = exp.DType.TIMETZ.into_expr() 6679 6680 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6681 if parser: 6682 return parser(self, this, data_type) 6683 6684 return self.expression(exp.Cast(this=this, to=data_type)) 6685 6686 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6687 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6688 # 6689 # If the index difference here is greater than 1, that means the parser itself must have 6690 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6691 # 6692 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6693 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6694 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6695 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6696 # 6697 # In these cases, we don't really want to return the converted type, but instead retreat 6698 # and try to parse a Column or Identifier in the section below. 6699 if data_type.expressions and index2 - index > 1: 6700 self._retreat(index2) 6701 return self._parse_column_ops(data_type) 6702 6703 self._retreat(index) 6704 6705 if fallback_to_identifier: 6706 return self._parse_id_var() 6707 6708 return self._parse_column() 6709 6710 def _parse_type_size(self) -> exp.DataTypeParam | None: 6711 this = self._parse_type() 6712 if not this: 6713 return None 6714 6715 if isinstance(this, exp.Column) and not this.table: 6716 this = exp.var(this.name.upper()) 6717 6718 return self.expression( 6719 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6720 ) 6721 6722 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6723 type_name = identifier.name 6724 6725 while self._match(TokenType.DOT): 6726 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6727 6728 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6729 6730 def _parse_types( 6731 self, 6732 check_func: bool = False, 6733 schema: bool = False, 6734 allow_identifiers: bool = True, 6735 with_collation: bool = False, 6736 ) -> exp.Expr | None: 6737 index = self._index 6738 this: exp.Expr | None = None 6739 6740 if self._match_set(self.TYPE_TOKENS): 6741 type_token = self._prev.token_type 6742 else: 6743 type_token = None 6744 identifier = allow_identifiers and self._parse_id_var( 6745 any_token=False, tokens=(TokenType.VAR,) 6746 ) 6747 if isinstance(identifier, exp.Identifier): 6748 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6749 this = exp.DataType.build(identifier, udt=True) 6750 else: 6751 try: 6752 tokens = self.dialect.tokenize(identifier.name) 6753 except TokenError: 6754 tokens = None 6755 6756 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6757 if len(tokens) > 1: 6758 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6759 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6760 this = self._parse_user_defined_type(identifier) 6761 else: 6762 self._retreat(self._index - 1) 6763 return None 6764 else: 6765 return None 6766 6767 if type_token == TokenType.PSEUDO_TYPE: 6768 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6769 6770 if type_token == TokenType.OBJECT_IDENTIFIER: 6771 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6772 6773 # https://materialize.com/docs/sql/types/map/ 6774 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6775 key_type = self._parse_types( 6776 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6777 ) 6778 if not self._match(TokenType.FARROW): 6779 self._retreat(index) 6780 return None 6781 6782 value_type = self._parse_types( 6783 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6784 ) 6785 if not self._match(TokenType.R_BRACKET): 6786 self._retreat(index) 6787 return None 6788 6789 return exp.DataType( 6790 this=exp.DType.MAP, 6791 expressions=[key_type, value_type], 6792 nested=True, 6793 ) 6794 6795 nested = type_token in self.NESTED_TYPE_TOKENS 6796 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6797 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6798 expressions = None 6799 maybe_func = False 6800 6801 if self._match(TokenType.L_PAREN): 6802 if is_struct: 6803 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6804 elif nested: 6805 expressions = self._parse_csv( 6806 lambda: self._parse_types( 6807 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6808 ) 6809 ) 6810 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6811 this = expressions[0] 6812 this.set("nullable", True) 6813 self._match_r_paren() 6814 return this 6815 elif type_token in self.ENUM_TYPE_TOKENS: 6816 expressions = self._parse_csv(self._parse_equality) 6817 elif type_token == TokenType.JSON: 6818 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6819 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6820 expressions = self._parse_csv(self._parse_json_type_arg) 6821 elif is_aggregate: 6822 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6823 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6824 ) 6825 if not func_or_ident: 6826 return None 6827 expressions = [func_or_ident] 6828 if self._match(TokenType.COMMA): 6829 expressions.extend( 6830 self._parse_csv( 6831 lambda: self._parse_types( 6832 check_func=check_func, 6833 schema=schema, 6834 allow_identifiers=allow_identifiers, 6835 ) 6836 ) 6837 ) 6838 else: 6839 expressions = self._parse_csv(self._parse_type_size) 6840 6841 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6842 if type_token == TokenType.VECTOR and len(expressions) == 2: 6843 expressions = self._parse_vector_expressions(expressions) 6844 6845 if not self._match(TokenType.R_PAREN): 6846 self._retreat(index) 6847 return None 6848 6849 maybe_func = True 6850 6851 values: list[exp.Expr] | None = None 6852 6853 if nested and self._match(TokenType.LT): 6854 if is_struct: 6855 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6856 else: 6857 expressions = self._parse_csv( 6858 lambda: self._parse_types( 6859 check_func=check_func, 6860 schema=schema, 6861 allow_identifiers=allow_identifiers, 6862 with_collation=True, 6863 ) 6864 ) 6865 6866 if not self._match(TokenType.GT): 6867 self.raise_error("Expecting >") 6868 6869 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6870 values = self._parse_csv(self._parse_disjunction) 6871 if not values and is_struct: 6872 values = None 6873 self._retreat(self._index - 1) 6874 else: 6875 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6876 6877 if type_token in self.TIMESTAMPS: 6878 if self._match_text_seq("WITH", "TIME", "ZONE"): 6879 maybe_func = False 6880 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6881 this = exp.DataType(this=tz_type, expressions=expressions) 6882 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6883 maybe_func = False 6884 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6885 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6886 maybe_func = False 6887 elif type_token == TokenType.INTERVAL: 6888 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6889 unit = self._parse_var(upper=True) 6890 if self._match_text_seq("TO"): 6891 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6892 6893 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6894 else: 6895 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6896 elif type_token == TokenType.VOID: 6897 this = exp.DataType(this=exp.DType.NULL) 6898 6899 if maybe_func and check_func: 6900 index2 = self._index 6901 peek = self._parse_string() 6902 6903 if not peek: 6904 self._retreat(index) 6905 return None 6906 6907 self._retreat(index2) 6908 6909 if not this: 6910 assert type_token is not None 6911 if self._match_text_seq("UNSIGNED"): 6912 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6913 if not unsigned_type_token: 6914 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6915 6916 type_token = unsigned_type_token or type_token 6917 6918 # NULLABLE without parentheses can be a column (Presto/Trino) 6919 if type_token == TokenType.NULLABLE and not expressions: 6920 self._retreat(index) 6921 return None 6922 6923 this = exp.DataType( 6924 this=exp.DType[type_token.name], 6925 expressions=expressions, 6926 nested=nested, 6927 ) 6928 6929 # Empty arrays/structs are allowed 6930 if values is not None: 6931 cls = exp.Struct if is_struct else exp.Array 6932 this = exp.cast(cls(expressions=values), this, copy=False) 6933 6934 elif expressions: 6935 this.set("expressions", expressions) 6936 6937 # https://materialize.com/docs/sql/types/list/#type-name 6938 while self._match(TokenType.LIST): 6939 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6940 6941 index = self._index 6942 6943 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6944 matched_array = self._match(TokenType.ARRAY) 6945 6946 while self._curr: 6947 datatype_token = self._prev.token_type 6948 matched_l_bracket = self._match(TokenType.L_BRACKET) 6949 6950 if (not matched_l_bracket and not matched_array) or ( 6951 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6952 ): 6953 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6954 # not to be confused with the fixed size array parsing 6955 break 6956 6957 matched_array = False 6958 values = self._parse_csv(self._parse_disjunction) or None 6959 if ( 6960 values 6961 and not schema 6962 and ( 6963 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6964 or datatype_token == TokenType.ARRAY 6965 or not self._match(TokenType.R_BRACKET, advance=False) 6966 ) 6967 ): 6968 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6969 # 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 6970 self._retreat(index) 6971 break 6972 6973 this = exp.DataType( 6974 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6975 ) 6976 self._match(TokenType.R_BRACKET) 6977 6978 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6979 converter = self.TYPE_CONVERTERS.get(this.this) 6980 if converter: 6981 this = converter(t.cast(exp.DataType, this)) 6982 6983 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6984 this.set("collate", self._parse_identifier() or self._parse_column()) 6985 6986 return this 6987 6988 def _parse_json_type_arg(self) -> exp.Expr | None: 6989 """Parse a single argument to ClickHouse's JSON type.""" 6990 6991 # SKIP col or SKIP REGEXP 'pattern' 6992 if self._match_text_seq("SKIP"): 6993 regexp = self._match(TokenType.RLIKE) 6994 arg = self._parse_column() 6995 if isinstance(arg, exp.Column): 6996 arg = arg.to_dot() 6997 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6998 6999 param_or_col = self._parse_column() 7000 if not isinstance(param_or_col, exp.Column): 7001 return None 7002 7003 # Parameter: name=value (e.g., max_dynamic_paths=2) 7004 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 7005 param = param_or_col.name 7006 value = self._parse_primary() 7007 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 7008 7009 # Column type hint: col_name Type 7010 col = param_or_col.to_dot() 7011 kind = self._parse_types(check_func=False, allow_identifiers=False) 7012 return self.expression(exp.ColumnDef(this=col, kind=kind)) 7013 7014 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 7015 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 7016 7017 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 7018 index = self._index 7019 7020 if ( 7021 self._curr 7022 and self._next 7023 and self._curr.token_type in self.TYPE_TOKENS 7024 and self._next.token_type in self.TYPE_TOKENS 7025 ): 7026 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 7027 # type token. Without this, the list will be parsed as a type and we'll eventually crash 7028 this = self._parse_id_var() 7029 else: 7030 this = ( 7031 self._parse_type(parse_interval=False, fallback_to_identifier=True) 7032 or self._parse_id_var() 7033 ) 7034 7035 self._match(TokenType.COLON) 7036 7037 if ( 7038 type_required 7039 and not isinstance(this, exp.DataType) 7040 and not self._match_set(self.TYPE_TOKENS, advance=False) 7041 ): 7042 self._retreat(index) 7043 return self._parse_types() 7044 7045 return self._parse_column_def(this) 7046 7047 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 7048 if not self._match_text_seq("AT", "TIME", "ZONE"): 7049 return this 7050 return self._parse_at_time_zone( 7051 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 7052 ) 7053 7054 def _parse_atom(self) -> exp.Expr | None: 7055 if ( 7056 self._curr.token_type in self.IDENTIFIER_TOKENS 7057 and (column := self._parse_column()) is not None 7058 ): 7059 return column 7060 7061 token = self._curr 7062 token_type = token.token_type 7063 7064 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 7065 return None 7066 7067 next_type = self._next.token_type 7068 7069 if ( 7070 next_type in self.COLUMN_OPERATORS 7071 or next_type in self.COLUMN_POSTFIX_TOKENS 7072 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 7073 ): 7074 return None 7075 7076 self._advance() 7077 return primary_parser(self, token) 7078 7079 def _parse_column(self) -> exp.Expr | None: 7080 column: exp.Expr | None = self._parse_column_parts_fast() 7081 if column is None: 7082 this = self._parse_column_reference() 7083 if not this: 7084 this = self._parse_bracket(this) 7085 column = self._parse_column_ops(this) if this else this 7086 7087 if column: 7088 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS and self._match(TokenType.JOIN_MARKER): 7089 column.set("join_mark", True) 7090 if self.COLON_IS_VARIANT_EXTRACT: 7091 column = self._parse_colon_as_variant_extract(column) 7092 7093 return column 7094 7095 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 7096 """Fast path for simple column and dot references (a, a.b, ...). 7097 7098 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 7099 that nothing complex follows. If it does, retreats and returns None so 7100 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 7101 """ 7102 index = self._index 7103 parts: list[exp.Identifier] | None = None 7104 all_comments: list[str] | None = None 7105 7106 while self._match_set(self.IDENTIFIER_TOKENS): 7107 token = self._prev 7108 comments = self._prev_comments 7109 7110 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 7111 self._retreat(index) 7112 return None 7113 7114 has_dot = self._match(TokenType.DOT) 7115 curr_tt = self._curr.token_type 7116 7117 if not has_dot: 7118 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 7119 self._retreat(index) 7120 return None 7121 elif curr_tt not in self.IDENTIFIER_TOKENS: 7122 self._retreat(index) 7123 return None 7124 7125 if parts is None: 7126 parts = [] 7127 7128 if comments: 7129 if all_comments is None: 7130 all_comments = [] 7131 all_comments.extend(comments) 7132 self._prev_comments = [] 7133 7134 parts.append( 7135 self.expression( 7136 exp.Identifier( 7137 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 7138 ), 7139 token, 7140 ) 7141 ) 7142 7143 if not has_dot: 7144 break 7145 7146 if parts is None: 7147 return None 7148 7149 n = len(parts) 7150 7151 if n == 1: 7152 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 7153 elif n == 2: 7154 column = exp.Column(this=parts[1], table=parts[0]) 7155 elif n == 3: 7156 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 7157 else: 7158 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 7159 7160 for i in range(4, n): 7161 column = exp.Dot(this=column, expression=parts[i]) 7162 7163 if all_comments: 7164 column.add_comments(all_comments) 7165 7166 return column 7167 7168 def _parse_column_reference(self) -> exp.Expr | None: 7169 this = self._parse_field() 7170 if ( 7171 not this 7172 and self._match(TokenType.VALUES, advance=False) 7173 and self.VALUES_FOLLOWED_BY_PAREN 7174 and (not self._next or self._next.token_type != TokenType.L_PAREN) 7175 ): 7176 this = self._parse_id_var() 7177 7178 if isinstance(this, exp.Identifier): 7179 # We bubble up comments from the Identifier to the Column 7180 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 7181 7182 return this 7183 7184 def _build_json_extract( 7185 self, 7186 this: exp.Expr | None, 7187 path_parts: list[exp.JSONPathPart], 7188 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 7189 if len(path_parts) > 1: 7190 this = self.expression( 7191 exp.JSONExtract( 7192 this=this, 7193 expression=exp.JSONPath(expressions=path_parts), 7194 variant_extract=True, 7195 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 7196 ) 7197 ) 7198 path_parts = [exp.JSONPathRoot()] 7199 7200 return this, path_parts 7201 7202 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 7203 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 7204 7205 while self._match(TokenType.COLON): 7206 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 7207 this, path_parts = self._build_json_extract(this, path_parts) 7208 7209 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7210 7211 if key: 7212 quoted = isinstance(key, exp.Identifier) and key.quoted 7213 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 7214 7215 while True: 7216 if self._match(TokenType.DOT): 7217 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7218 7219 if next_key: 7220 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 7221 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 7222 elif self._match(TokenType.L_BRACKET): 7223 bracket_expr = self._parse_bracket_key_value() 7224 7225 if not self._match(TokenType.R_BRACKET): 7226 self.raise_error("Expected ]") 7227 7228 if bracket_expr: 7229 if bracket_expr.is_string: 7230 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7231 elif bracket_expr.is_star: 7232 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7233 elif bracket_expr.is_number: 7234 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7235 else: 7236 this, path_parts = self._build_json_extract(this, path_parts) 7237 7238 this = self.expression( 7239 exp.Bracket( 7240 this=this, expressions=[bracket_expr], json_access=True 7241 ), 7242 ) 7243 7244 elif self._match(TokenType.DCOLON): 7245 this, path_parts = self._build_json_extract(this, path_parts) 7246 7247 cast_type = self._parse_types() 7248 if cast_type: 7249 this = self.expression(exp.Cast(this=this, to=cast_type)) 7250 else: 7251 self.raise_error("Expected type after '::'") 7252 else: 7253 break 7254 7255 this, _ = self._build_json_extract(this, path_parts) 7256 7257 return this 7258 7259 def _parse_dcolon(self) -> exp.Expr | None: 7260 return self._parse_types() 7261 7262 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7263 while self._curr.token_type in self.BRACKETS: 7264 this = self._parse_bracket(this) 7265 7266 column_operators = self.COLUMN_OPERATORS 7267 cast_column_operators = self.CAST_COLUMN_OPERATORS 7268 while self._curr: 7269 op_token = self._curr.token_type 7270 7271 if op_token not in column_operators: 7272 break 7273 op = column_operators[op_token] 7274 self._advance() 7275 7276 if op_token in cast_column_operators: 7277 field = self._parse_dcolon() 7278 if not field: 7279 self.raise_error("Expected type") 7280 elif op and self._curr: 7281 field = self._parse_column_reference() or self._parse_bitwise() 7282 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7283 field = self._parse_column_ops(field) 7284 else: 7285 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7286 field = self._parse_field(any_token=True, anonymous_func=True) 7287 7288 # In t.true, t.null we should produce an Identifier node 7289 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7290 field = self.expression( 7291 exp.Identifier(this=self._prev.text), 7292 comments=field.comments, 7293 ) 7294 7295 # Function calls can be qualified, e.g., x.y.FOO() 7296 # This converts the final AST to a series of Dots leading to the function call 7297 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7298 if isinstance(field, (exp.Func, exp.Window)) and this: 7299 this = this.transform( 7300 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7301 ) 7302 7303 if op: 7304 this = op(self, this, field) 7305 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7306 this = self.expression( 7307 exp.Column( 7308 this=field, 7309 table=this.this, 7310 db=this.args.get("table"), 7311 catalog=this.args.get("db"), 7312 ), 7313 comments=this.comments, 7314 ) 7315 elif isinstance(field, exp.Window): 7316 # Move the exp.Dot's to the window's function 7317 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7318 field.set("this", window_func) 7319 this = field 7320 else: 7321 this = self.expression(exp.Dot(this=this, expression=field)) 7322 7323 if field and field.comments: 7324 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7325 7326 this = self._parse_bracket(this) 7327 7328 return this 7329 7330 def _parse_paren(self) -> exp.Expr | None: 7331 if not self._match(TokenType.L_PAREN): 7332 return None 7333 7334 comments = self._prev_comments 7335 query = self._parse_select() 7336 7337 if query: 7338 expressions = [query] 7339 else: 7340 expressions = self._parse_expressions() 7341 7342 this = seq_get(expressions, 0) 7343 7344 if not this and self._match(TokenType.R_PAREN, advance=False): 7345 this = self.expression(exp.Tuple()) 7346 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7347 this = self.expression(exp.Tuple(expressions=expressions)) 7348 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7349 this = self._parse_subquery(this=this, parse_alias=False) 7350 elif isinstance(this, (exp.Subquery, exp.Values)): 7351 this = self._parse_subquery( 7352 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7353 parse_alias=False, 7354 ) 7355 else: 7356 this = self.expression(exp.Paren(this=this)) 7357 7358 if this: 7359 this.add_comments(comments) 7360 7361 self._match_r_paren(expression=this) 7362 7363 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7364 return self._parse_window(this) 7365 7366 return this 7367 7368 def _parse_primary(self) -> exp.Expr | None: 7369 if self._match_set(self.PRIMARY_PARSERS): 7370 token_type = self._prev.token_type 7371 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7372 7373 if token_type == TokenType.STRING: 7374 expressions = [primary] 7375 while self._match(TokenType.STRING, advance=False): 7376 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7377 self.raise_error( 7378 "Adjacent string literals need to be separated by whitespace or comments" 7379 ) 7380 7381 self._advance() 7382 expressions.append(exp.Literal.string(self._prev.text)) 7383 7384 if len(expressions) > 1: 7385 return self.expression( 7386 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7387 ) 7388 7389 return primary 7390 7391 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7392 return exp.Literal.number(f"0.{self._prev.text}") 7393 7394 return self._parse_paren() 7395 7396 def _parse_field( 7397 self, 7398 any_token: bool = False, 7399 tokens: t.Collection[TokenType] | None = None, 7400 anonymous_func: bool = False, 7401 ) -> exp.Expr | None: 7402 after_dot = ( 7403 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7404 ) 7405 7406 if anonymous_func: 7407 field = ( 7408 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7409 or self._parse_primary() 7410 ) 7411 else: 7412 field = self._parse_primary() or self._parse_function( 7413 anonymous=anonymous_func, any_token=any_token 7414 ) 7415 7416 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7417 7418 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7419 name = field.name 7420 if self._is_connected() and self._parse_var(any_token=True): 7421 name += self._prev.text 7422 7423 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7424 7425 return field 7426 7427 def _parse_function( 7428 self, 7429 functions: dict[str, t.Callable] | None = None, 7430 anonymous: bool = False, 7431 optional_parens: bool = True, 7432 any_token: bool = False, 7433 ) -> exp.Expr | None: 7434 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7435 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7436 fn_syntax = False 7437 if ( 7438 self._match(TokenType.L_BRACE, advance=False) 7439 and self._next 7440 and self._next.text.upper() == "FN" 7441 ): 7442 self._advance(2) 7443 fn_syntax = True 7444 7445 func = self._parse_function_call( 7446 functions=functions, 7447 anonymous=anonymous, 7448 optional_parens=optional_parens, 7449 any_token=any_token, 7450 ) 7451 7452 if fn_syntax: 7453 self._match(TokenType.R_BRACE) 7454 7455 return func 7456 7457 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7458 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7459 7460 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7461 args = self._parse_function_args(alias=False) 7462 if not args: 7463 self.raise_error("Expected at least one argument") 7464 7465 # Wrapped so the connector keeps its precedence in the parent context 7466 return exp.Paren(this=connector(*args, copy=False)) 7467 7468 def _parse_function_call( 7469 self, 7470 functions: dict[str, t.Callable] | None = None, 7471 anonymous: bool = False, 7472 optional_parens: bool = True, 7473 any_token: bool = False, 7474 ) -> exp.Expr | None: 7475 if not self._curr: 7476 return None 7477 7478 comments = self._curr.comments 7479 prev = self._prev 7480 token = self._curr 7481 token_type = self._curr.token_type 7482 this: str | exp.Expr = self._curr.text 7483 upper = self._curr.text.upper() 7484 7485 after_dot = prev.token_type == TokenType.DOT 7486 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7487 if ( 7488 optional_parens 7489 and parser 7490 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7491 and not after_dot 7492 ): 7493 self._advance() 7494 return self._parse_window(parser(self)) 7495 7496 if self._next.token_type != TokenType.L_PAREN: 7497 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7498 self._advance() 7499 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7500 7501 return None 7502 7503 if any_token: 7504 if token_type in self.RESERVED_TOKENS: 7505 return None 7506 elif token_type not in self.FUNC_TOKENS: 7507 return None 7508 7509 self._advance(2) 7510 7511 parser = self.FUNCTION_PARSERS.get(upper) 7512 if parser and not anonymous: 7513 result = parser(self) 7514 else: 7515 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7516 7517 if subquery_predicate: 7518 expr = None 7519 if self._curr.token_type in self.SUBQUERY_TOKENS: 7520 expr = self._parse_select() 7521 self._match_r_paren() 7522 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7523 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7524 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7525 self._advance(-1) 7526 expr = self._parse_bitwise() 7527 7528 if expr: 7529 return self.expression(subquery_predicate(this=expr), comments=comments) 7530 7531 if functions is None: 7532 functions = self.FUNCTIONS 7533 7534 function = functions.get(upper) 7535 known_function = function and not anonymous 7536 7537 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7538 args = self._parse_function_args(alias) 7539 7540 post_func_comments = self._curr.comments if self._curr else None 7541 if known_function and post_func_comments: 7542 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7543 # call we'll construct it as exp.Anonymous, even if it's "known" 7544 if any( 7545 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7546 for comment in post_func_comments 7547 ): 7548 known_function = False 7549 7550 if alias and known_function: 7551 args = self._kv_to_prop_eq(args) 7552 7553 if known_function: 7554 func_builder = t.cast(t.Callable, function) 7555 7556 # mypyc compiled functions don't have __code__, so we use 7557 # try/except to check if func_builder accepts 'dialect'. 7558 try: 7559 func = func_builder(args) 7560 except TypeError: 7561 func = func_builder(args, dialect=self.dialect) 7562 7563 func = self.validate_expression(func, args) 7564 if name_key := self.dialect.ORIGINAL_NAME_META_KEY: 7565 func.meta[name_key] = this 7566 7567 result = func 7568 else: 7569 if token_type == TokenType.IDENTIFIER: 7570 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7571 7572 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7573 7574 result = result.update_positions(token) 7575 7576 if isinstance(result, exp.Expr): 7577 result.add_comments(comments) 7578 7579 if parser: 7580 self._match(TokenType.R_PAREN, expression=result) 7581 else: 7582 self._match_r_paren(result) 7583 return self._parse_window(result) 7584 7585 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7586 return expression 7587 7588 def _kv_to_prop_eq( 7589 self, expressions: list[exp.Expr], parse_map: bool = False 7590 ) -> list[exp.Expr]: 7591 transformed = [] 7592 7593 for index, e in enumerate(expressions): 7594 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7595 if isinstance(e, exp.Alias): 7596 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7597 7598 if not isinstance(e, exp.PropertyEQ): 7599 e = self.expression( 7600 exp.PropertyEQ( 7601 this=e.this if parse_map else exp.to_identifier(e.this.name), 7602 expression=e.expression, 7603 ) 7604 ) 7605 7606 if not parse_map and isinstance(e.this, exp.Column): 7607 e.this.replace(e.this.this) 7608 else: 7609 e = self._to_prop_eq(e, index) 7610 7611 transformed.append(e) 7612 7613 return transformed 7614 7615 def _parse_function_properties(self) -> exp.Properties | None: 7616 # Skip the generic `key = value` fallback in _parse_property since this 7617 # runs post-AS where a function body like `name = expr` can be misread 7618 # as a property. 7619 properties = [] 7620 while True: 7621 if self._match_texts(self.PROPERTY_PARSERS): 7622 keyword = self._prev.text.upper() 7623 prop = self.PROPERTY_PARSERS[keyword](self) 7624 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7625 keyword = self._prev.text.upper() 7626 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7627 else: 7628 break 7629 if not prop: 7630 self.raise_error(f"Failed to parse property '{keyword}'") 7631 break 7632 for p in ensure_list(prop): 7633 properties.append(p) 7634 7635 return self.expression(exp.Properties(expressions=properties)) if properties else None 7636 7637 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7638 return self._parse_statement() 7639 7640 def _parse_function_parameter(self) -> exp.Expr | None: 7641 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7642 7643 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7644 this = self._parse_table_parts(schema=True) 7645 7646 if not self._match(TokenType.L_PAREN): 7647 return this 7648 7649 expressions = self._parse_csv(self._parse_function_parameter) 7650 self._match_r_paren() 7651 return self.expression( 7652 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7653 ) 7654 7655 def _parse_macro_overloads( 7656 self, 7657 this: exp.UserDefinedFunction, 7658 first_body: exp.Expr, 7659 first_is_table: bool = False, 7660 ) -> exp.MacroOverloads: 7661 overloads = [ 7662 self.expression( 7663 exp.MacroOverload( 7664 this=first_body, 7665 expressions=this.expressions or None, 7666 is_table=first_is_table, 7667 ) 7668 ) 7669 ] 7670 this.set("expressions", None) 7671 this.set("wrapped", False) 7672 7673 while self._match(TokenType.COMMA): 7674 if not self._match(TokenType.L_PAREN): 7675 break 7676 7677 params = self._parse_csv(self._parse_function_parameter) 7678 self._match_r_paren() 7679 7680 if not self._match(TokenType.ALIAS): 7681 break 7682 7683 is_table = self._match(TokenType.TABLE) 7684 body = self._parse_expression() 7685 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7686 overloads.append(self.expression(macro)) 7687 7688 return self.expression(exp.MacroOverloads(expressions=overloads)) 7689 7690 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7691 literal = self._parse_primary() 7692 if literal: 7693 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7694 7695 return self._identifier_expression(token) 7696 7697 def _parse_session_parameter(self) -> exp.SessionParameter: 7698 kind = None 7699 this = self._parse_id_var() or self._parse_primary() 7700 7701 if this and self._match(TokenType.DOT): 7702 kind = this.name 7703 this = self._parse_var() or self._parse_primary() 7704 7705 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7706 7707 def _parse_lambda_arg(self) -> exp.Expr | None: 7708 return self._parse_id_var() 7709 7710 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7711 next_token_type = self._next.token_type 7712 7713 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7714 if ( 7715 next_token_type in self.LAMBDA_ARG_TERMINATORS 7716 and (atom := self._parse_atom()) is not None 7717 ): 7718 return atom 7719 7720 index = self._index 7721 7722 if self._match(TokenType.L_PAREN): 7723 expressions = t.cast( 7724 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7725 ) 7726 7727 if not self._match(TokenType.R_PAREN): 7728 self._retreat(index) 7729 elif self._match_set(self.LAMBDAS): 7730 return self.LAMBDAS[self._prev.token_type](self, expressions) 7731 else: 7732 self._retreat(index) 7733 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7734 expressions = [self._parse_lambda_arg()] 7735 7736 if self._match_set(self.LAMBDAS): 7737 return self.LAMBDAS[self._prev.token_type](self, expressions) 7738 7739 self._retreat(index) 7740 7741 this: exp.Expr | None 7742 7743 if self._match(TokenType.DISTINCT): 7744 this = self.expression( 7745 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7746 ) 7747 else: 7748 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7749 this = self._parse_select_or_expression(alias=alias) 7750 7751 return self._parse_limit( 7752 self._parse_respect_or_ignore_nulls( 7753 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7754 ) 7755 ) 7756 7757 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7758 index = self._index 7759 if not self._match(TokenType.L_PAREN): 7760 return this 7761 7762 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7763 # expr can be of both types 7764 if self._match_set(self.SELECT_START_TOKENS): 7765 self._retreat(index) 7766 return this 7767 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7768 self._match_r_paren() 7769 return self.expression(exp.Schema(this=this, expressions=args)) 7770 7771 def _parse_field_def(self) -> exp.Expr | None: 7772 return self._parse_column_def(self._parse_field(any_token=True)) 7773 7774 def _parse_column_def( 7775 self, this: exp.Expr | None, computed_column: bool = True 7776 ) -> exp.Expr | None: 7777 # column defs are not really columns, they're identifiers 7778 if isinstance(this, exp.Column): 7779 this = this.this 7780 7781 if not computed_column: 7782 self._match(TokenType.ALIAS) 7783 7784 kind = self._parse_types(schema=True) 7785 7786 if self._match_text_seq("FOR", "ORDINALITY"): 7787 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7788 7789 constraints: list[exp.Expr] = [] 7790 7791 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7792 ("ALIAS", "MATERIALIZED") 7793 ): 7794 # Match storage before _parse_types so STORED is not treated as a data type 7795 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7796 persisted = self._prev.text.upper() == "MATERIALIZED" 7797 expression = self._parse_disjunction() 7798 if not persisted: 7799 if self._match_text_seq("PERSISTED"): 7800 persisted = True 7801 elif self._match_texts(("STORED", "VIRTUAL")): 7802 persisted = self._prev.text.upper() == "STORED" 7803 constraint_kind = exp.ComputedColumnConstraint( 7804 this=expression, 7805 persisted=persisted, 7806 data_type=exp.Var(this="AUTO") 7807 if self._match_text_seq("AUTO") 7808 else self._parse_types(), 7809 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7810 ) 7811 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7812 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7813 in_out_constraint = self.expression( 7814 exp.InOutColumnConstraint( 7815 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7816 ) 7817 ) 7818 constraints.append(in_out_constraint) 7819 kind = self._parse_types() 7820 elif ( 7821 kind 7822 and self._match(TokenType.ALIAS, advance=False) 7823 and ( 7824 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7825 or self._next.token_type == TokenType.L_PAREN 7826 ) 7827 ): 7828 self._advance() 7829 constraints.append( 7830 self.expression( 7831 exp.ColumnConstraint( 7832 kind=exp.ComputedColumnConstraint( 7833 this=self._parse_disjunction(), 7834 persisted=self._match_texts(("STORED", "VIRTUAL")) 7835 and self._prev.text.upper() == "STORED", 7836 ) 7837 ) 7838 ) 7839 ) 7840 7841 while True: 7842 constraint = self._parse_column_constraint() 7843 if not constraint: 7844 break 7845 constraints.append(constraint) 7846 7847 if not kind and not constraints: 7848 return this 7849 7850 position = None 7851 if self._match_texts(("FIRST", "AFTER")): 7852 pos = self._prev.text 7853 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7854 7855 return self.expression( 7856 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7857 ) 7858 7859 def _parse_auto_increment( 7860 self, 7861 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7862 start = None 7863 increment = None 7864 order = None 7865 7866 if self._match(TokenType.L_PAREN, advance=False): 7867 args = self._parse_wrapped_csv(self._parse_bitwise) 7868 start = seq_get(args, 0) 7869 increment = seq_get(args, 1) 7870 7871 # The remaining parts form an unordered bag and any of them can be omitted, in which 7872 # case the engine falls back to its own default, so they're parsed independently. 7873 while True: 7874 if self._match_text_seq("START"): 7875 start = self._parse_bitwise() 7876 elif self._match_text_seq("INCREMENT"): 7877 increment = self._parse_bitwise() 7878 elif self._match_text_seq("ORDER"): 7879 order = True 7880 elif self._match_text_seq("NOORDER"): 7881 order = False 7882 else: 7883 break 7884 7885 if start or increment or order is not None: 7886 return exp.GeneratedAsIdentityColumnConstraint( 7887 start=start, increment=increment, this=False, order=order 7888 ) 7889 7890 return exp.AutoIncrementColumnConstraint() 7891 7892 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7893 if not self._match(TokenType.L_PAREN, advance=False): 7894 return None 7895 7896 return self.expression( 7897 exp.CheckColumnConstraint( 7898 this=self._parse_wrapped(self._parse_assignment), 7899 enforced=self._match_text_seq("ENFORCED"), 7900 ) 7901 ) 7902 7903 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7904 if not self._match_text_seq("REFRESH"): 7905 self._retreat(self._index - 1) 7906 return None 7907 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7908 7909 def _parse_compress(self) -> exp.CompressColumnConstraint: 7910 if self._match(TokenType.L_PAREN, advance=False): 7911 return self.expression( 7912 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7913 ) 7914 7915 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7916 7917 def _parse_generated_as_identity( 7918 self, 7919 ) -> ( 7920 exp.GeneratedAsIdentityColumnConstraint 7921 | exp.ComputedColumnConstraint 7922 | exp.GeneratedAsRowColumnConstraint 7923 ): 7924 if self._match_text_seq("BY", "DEFAULT"): 7925 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7926 this = self.expression( 7927 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7928 ) 7929 else: 7930 self._match_text_seq("ALWAYS") 7931 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7932 7933 self._match(TokenType.ALIAS) 7934 7935 if self._match_text_seq("ROW"): 7936 start = self._match_text_seq("START") 7937 if not start: 7938 self._match(TokenType.END) 7939 hidden = self._match_text_seq("HIDDEN") 7940 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7941 7942 identity = self._match_text_seq("IDENTITY") 7943 7944 if self._match(TokenType.L_PAREN): 7945 if self._match_text_seq("START", "WITH"): 7946 this.set("start", self._parse_bitwise()) 7947 if self._match_text_seq("INCREMENT", "BY"): 7948 this.set("increment", self._parse_bitwise()) 7949 if self._match_text_seq("MINVALUE"): 7950 this.set("minvalue", self._parse_bitwise()) 7951 if self._match_text_seq("MAXVALUE"): 7952 this.set("maxvalue", self._parse_bitwise()) 7953 7954 if self._match_text_seq("CYCLE"): 7955 this.set("cycle", True) 7956 elif self._match_text_seq("NO", "CYCLE"): 7957 this.set("cycle", False) 7958 7959 if not identity: 7960 this.set("expression", self._parse_disjunction()) 7961 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7962 args = self._parse_csv(self._parse_bitwise) 7963 this.set("start", seq_get(args, 0)) 7964 this.set("increment", seq_get(args, 1)) 7965 7966 self._match_r_paren() 7967 7968 return this 7969 7970 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7971 self._match_text_seq("LENGTH") 7972 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7973 7974 def _parse_not_constraint(self) -> exp.Expr | None: 7975 if self._match_text_seq("NULL"): 7976 return self.expression(exp.NotNullColumnConstraint()) 7977 if self._match_text_seq("CASESPECIFIC"): 7978 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7979 if self._match_text_seq("FOR", "REPLICATION"): 7980 return self.expression(exp.NotForReplicationColumnConstraint()) 7981 7982 # Unconsume the `NOT` token 7983 self._retreat(self._index - 1) 7984 return None 7985 7986 def _parse_column_constraint(self) -> exp.Expr | None: 7987 this = ( 7988 self._parse_id_var() 7989 if self._match(TokenType.CONSTRAINT) 7990 and not self._match_texts(self.RESERVED_CONSTRAINT_KINDS, advance=False) 7991 else None 7992 ) 7993 7994 procedure_option_follows = ( 7995 self._match(TokenType.WITH, advance=False) 7996 and self._next 7997 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7998 ) 7999 8000 index = self._index 8001 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 8002 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 8003 if not constraint: 8004 self._retreat(index) 8005 return None 8006 8007 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 8008 8009 if self._match(TokenType.CHARACTER_SET) or self._match_pair(TokenType.CHAR, TokenType.SET): 8010 return self.expression( 8011 exp.ColumnConstraint( 8012 this=this, 8013 kind=self.expression( 8014 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 8015 ), 8016 ) 8017 ) 8018 8019 return this 8020 8021 def _parse_constraint(self) -> exp.Expr | None: 8022 if not self._match(TokenType.CONSTRAINT) or self._match_texts( 8023 self.RESERVED_CONSTRAINT_KINDS, advance=False 8024 ): 8025 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 8026 8027 return self.expression( 8028 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 8029 ) 8030 8031 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 8032 constraints = [] 8033 while True: 8034 constraint = self._parse_unnamed_constraint() or self._parse_function() 8035 if not constraint: 8036 break 8037 constraints.append(constraint) 8038 8039 return constraints 8040 8041 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 8042 index = self._index 8043 8044 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 8045 constraints or self.CONSTRAINT_PARSERS 8046 ): 8047 return None 8048 8049 constraint_key = self._prev.text.upper() 8050 if constraint_key not in self.CONSTRAINT_PARSERS: 8051 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 8052 8053 result = self.CONSTRAINT_PARSERS[constraint_key](self) 8054 if not result: 8055 self._retreat(index) 8056 8057 return result 8058 8059 def _parse_unique_key(self) -> exp.Expr | None: 8060 if ( 8061 self._curr 8062 and self._curr.token_type != TokenType.IDENTIFIER 8063 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 8064 ): 8065 return None 8066 return self._parse_id_var(any_token=False) 8067 8068 def _parse_unique(self) -> exp.UniqueColumnConstraint: 8069 self._match_texts(("KEY", "INDEX")) 8070 return self.expression( 8071 exp.UniqueColumnConstraint( 8072 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 8073 this=self._parse_schema(self._parse_unique_key()), 8074 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 8075 on_conflict=self._parse_on_conflict(), 8076 options=self._parse_key_constraint_options(), 8077 ) 8078 ) 8079 8080 def _parse_key_constraint_options(self) -> list[str]: 8081 options = [] 8082 while True: 8083 if not self._curr: 8084 break 8085 8086 if self._match(TokenType.ON): 8087 action = None 8088 on = self._advance_any() and self._prev.text 8089 8090 if self._match_text_seq("NO", "ACTION"): 8091 action = "NO ACTION" 8092 elif self._match_text_seq("CASCADE"): 8093 action = "CASCADE" 8094 elif self._match_text_seq("RESTRICT"): 8095 action = "RESTRICT" 8096 elif self._match_pair(TokenType.SET, TokenType.NULL): 8097 action = "SET NULL" 8098 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 8099 action = "SET DEFAULT" 8100 else: 8101 self.raise_error("Invalid key constraint") 8102 8103 options.append(f"ON {on} {action}") 8104 else: 8105 var = self._parse_var_from_options( 8106 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 8107 ) 8108 if not var: 8109 break 8110 options.append(var.name) 8111 8112 return options 8113 8114 def _parse_references(self, match: bool = True) -> exp.Reference | None: 8115 if match and not self._match(TokenType.REFERENCES): 8116 return None 8117 8118 expressions: list | None = None 8119 this = self._parse_table(schema=True) 8120 options = self._parse_key_constraint_options() 8121 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 8122 8123 def _parse_foreign_key(self) -> exp.ForeignKey: 8124 expressions = ( 8125 self._parse_wrapped_id_vars() 8126 if not self._match(TokenType.REFERENCES, advance=False) 8127 else None 8128 ) 8129 reference = self._parse_references() 8130 on_options = {} 8131 8132 while self._match(TokenType.ON): 8133 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 8134 self.raise_error("Expected DELETE or UPDATE") 8135 8136 kind = self._prev.text.lower() 8137 8138 if self._match_text_seq("NO", "ACTION"): 8139 action = "NO ACTION" 8140 elif self._match(TokenType.SET): 8141 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 8142 action = "SET " + self._prev.text.upper() 8143 else: 8144 self._advance() 8145 action = self._prev.text.upper() 8146 8147 on_options[kind] = action 8148 8149 return self.expression( 8150 exp.ForeignKey( 8151 expressions=expressions, 8152 reference=reference, 8153 options=self._parse_key_constraint_options(), 8154 **on_options, 8155 ) 8156 ) 8157 8158 def _parse_primary_key_part(self) -> exp.Expr | None: 8159 return self._parse_field() 8160 8161 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 8162 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 8163 self._retreat(self._index - 1) 8164 return None 8165 8166 id_vars = self._parse_wrapped_id_vars() 8167 return self.expression( 8168 exp.PeriodForSystemTimeConstraint( 8169 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 8170 ) 8171 ) 8172 8173 def _parse_primary_key( 8174 self, 8175 wrapped_optional: bool = False, 8176 in_props: bool = False, 8177 named_primary_key: bool = False, 8178 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 8179 desc = ( 8180 self._prev.token_type == TokenType.DESC 8181 if self._match_set((TokenType.ASC, TokenType.DESC)) 8182 else None 8183 ) 8184 8185 this = None 8186 if ( 8187 named_primary_key 8188 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 8189 and self._next 8190 and self._next.token_type == TokenType.L_PAREN 8191 ): 8192 this = self._parse_id_var() 8193 8194 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 8195 return self.expression( 8196 exp.PrimaryKeyColumnConstraint( 8197 desc=desc, options=self._parse_key_constraint_options() 8198 ) 8199 ) 8200 8201 expressions = self._parse_wrapped_csv( 8202 self._parse_primary_key_part, optional=wrapped_optional 8203 ) 8204 8205 return self.expression( 8206 exp.PrimaryKey( 8207 this=this, 8208 expressions=expressions, 8209 include=self._parse_index_params(), 8210 options=self._parse_key_constraint_options(), 8211 ) 8212 ) 8213 8214 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 8215 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 8216 8217 def _parse_odbc_datetime_literal(self) -> exp.Expr: 8218 """ 8219 Parses a datetime column in ODBC format. We parse the column into the corresponding 8220 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 8221 same as we did for `DATE('yyyy-mm-dd')`. 8222 8223 Reference: 8224 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 8225 """ 8226 self._match(TokenType.VAR) 8227 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 8228 expression = self.expression(exp_class(this=self._parse_string())) 8229 if not self._match(TokenType.R_BRACE): 8230 self.raise_error("Expected }") 8231 return expression 8232 8233 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 8234 if not self._match_set(self.BRACKETS): 8235 return this 8236 8237 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8238 map_token = seq_get(self._tokens, self._index - 2) 8239 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8240 else: 8241 parse_map = False 8242 8243 bracket_kind = self._prev.token_type 8244 if ( 8245 bracket_kind == TokenType.L_BRACE 8246 and self._curr 8247 and self._curr.token_type == TokenType.VAR 8248 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8249 ): 8250 return self._parse_odbc_datetime_literal() 8251 8252 expressions = self._parse_csv( 8253 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8254 ) 8255 8256 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8257 self.raise_error("Expected ]") 8258 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8259 self.raise_error("Expected }") 8260 8261 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8262 if bracket_kind == TokenType.L_BRACE: 8263 this = self.expression( 8264 exp.Struct( 8265 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8266 ) 8267 ) 8268 elif not this: 8269 this = build_array_constructor( 8270 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8271 ) 8272 else: 8273 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8274 if constructor_type: 8275 return build_array_constructor( 8276 constructor_type, 8277 args=expressions, 8278 bracket_kind=bracket_kind, 8279 dialect=self.dialect, 8280 ) 8281 8282 expressions = apply_index_offset( 8283 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8284 ) 8285 this = self.expression( 8286 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8287 ) 8288 8289 self._add_comments(this) 8290 return self._parse_bracket(this) 8291 8292 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8293 if not self._match(TokenType.COLON): 8294 return this 8295 8296 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8297 self._advance() 8298 end: exp.Expr | None = -exp.Literal.number("1") 8299 else: 8300 end = self._parse_assignment() 8301 step = self._parse_unary() if self._match(TokenType.COLON) else None 8302 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8303 8304 def _parse_case(self) -> exp.Expr | None: 8305 if self._match(TokenType.DOT, advance=False): 8306 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8307 self._retreat(self._index - 1) 8308 return None 8309 8310 ifs = [] 8311 default = None 8312 8313 comments = self._prev_comments 8314 expression = self._parse_disjunction() 8315 8316 while self._match(TokenType.WHEN): 8317 this = self._parse_disjunction() 8318 self._match(TokenType.THEN) 8319 then = self._parse_disjunction() 8320 ifs.append(self.expression(exp.If(this=this, true=then))) 8321 8322 if self._match(TokenType.ELSE): 8323 default = self._parse_disjunction() 8324 8325 if not self._match(TokenType.END): 8326 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8327 default = exp.column("interval") 8328 else: 8329 self.raise_error("Expected END after CASE", self._prev) 8330 8331 return self.expression( 8332 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8333 ) 8334 8335 def _parse_if(self) -> exp.Expr | None: 8336 if self._match(TokenType.L_PAREN): 8337 args = self._parse_csv( 8338 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8339 ) 8340 this = self.validate_expression(exp.If.from_arg_list(args), args) 8341 self._match_r_paren() 8342 else: 8343 index = self._index - 1 8344 8345 if self.NO_PAREN_IF_COMMANDS and index == 0: 8346 return self._parse_as_command(self._prev) 8347 8348 condition = self._parse_disjunction() 8349 8350 if not condition or not self._match(TokenType.THEN): 8351 self._retreat(index) 8352 return None 8353 8354 true = self._parse_disjunction() 8355 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8356 self._match(TokenType.END) 8357 this = self.expression(exp.If(this=condition, true=true, false=false)) 8358 8359 return this 8360 8361 def _parse_next_value_for(self) -> exp.Expr | None: 8362 if not self._match_text_seq("VALUE", "FOR"): 8363 self._retreat(self._index - 1) 8364 return None 8365 8366 return self.expression( 8367 exp.NextValueFor( 8368 this=self._parse_column(), 8369 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8370 ) 8371 ) 8372 8373 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8374 this = self._parse_function() or self._parse_var_or_string(upper=True) 8375 8376 if self._match(TokenType.FROM): 8377 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8378 8379 if not self._match(TokenType.COMMA): 8380 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8381 8382 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8383 8384 def _parse_gap_fill(self) -> exp.GapFill: 8385 self._match(TokenType.TABLE) 8386 this = self._parse_table() 8387 8388 self._match(TokenType.COMMA) 8389 args = [this, *self._parse_csv(self._parse_lambda)] 8390 8391 gap_fill = exp.GapFill.from_arg_list(args) 8392 return self.validate_expression(gap_fill, args) 8393 8394 def _parse_char(self) -> exp.Chr: 8395 return self.expression( 8396 exp.Chr( 8397 expressions=self._parse_csv(self._parse_assignment), 8398 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8399 ) 8400 ) 8401 8402 def _parse_charset_name(self) -> exp.Expr | None: 8403 """ 8404 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8405 for specific name shapes override this. 8406 """ 8407 return self._parse_var( 8408 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8409 ) 8410 8411 def _parse_weight_string(self) -> exp.WeightString | exp.Anonymous: 8412 name = self._tokens[self._index - 2].text 8413 args = self._parse_csv(self._parse_assignment) 8414 8415 # MySQL also accepts an undocumented WEIGHT_STRING(str, n, n, n) form 8416 if len(args) != 1: 8417 return self.expression(exp.Anonymous(this=name, expressions=args)) 8418 8419 return self.expression( 8420 exp.WeightString(this=args[0], to=self._match(TokenType.ALIAS) and self._parse_types()) 8421 ) 8422 8423 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8424 this = self._parse_assignment() 8425 8426 if not self._match(TokenType.ALIAS): 8427 if self._match(TokenType.COMMA): 8428 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8429 8430 self.raise_error("Expected AS after CAST") 8431 8432 fmt = None 8433 to = self._parse_types(with_collation=True) 8434 8435 default = None 8436 if self._match(TokenType.DEFAULT): 8437 default = self._parse_bitwise() 8438 self._match_text_seq("ON", "CONVERSION", "ERROR") 8439 8440 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8441 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8442 fmt = self._parse_at_time_zone(fmt_string) 8443 8444 if not to: 8445 to = exp.DType.UNKNOWN.into_expr() 8446 if to.this in exp.DataType.TEMPORAL_TYPES: 8447 this = self.expression( 8448 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8449 this=this, 8450 format=exp.Literal.string( 8451 format_time( 8452 fmt_string.this if fmt_string else "", 8453 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8454 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8455 ) 8456 ), 8457 safe=safe, 8458 ) 8459 ) 8460 8461 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8462 this.set("zone", fmt.args["zone"]) 8463 return this 8464 elif not to: 8465 self.raise_error("Expected TYPE after CAST") 8466 elif isinstance(to, exp.Identifier): 8467 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8468 else: 8469 to = self._parse_char_charset(to) 8470 8471 return self.build_cast( 8472 strict=strict, 8473 this=this, 8474 to=to, 8475 format=fmt, 8476 safe=safe, 8477 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8478 default=default, 8479 ) 8480 8481 def _parse_string_agg(self) -> exp.GroupConcat: 8482 if self._match(TokenType.DISTINCT): 8483 args: list[exp.Expr | None] = [ 8484 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8485 ] 8486 if self._match(TokenType.COMMA): 8487 args.extend(self._parse_csv(self._parse_disjunction)) 8488 else: 8489 args = self._parse_csv(self._parse_disjunction) # type: ignore 8490 8491 if self._match_text_seq("ON", "OVERFLOW"): 8492 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8493 if self._match_text_seq("ERROR"): 8494 on_overflow: exp.Expr | None = exp.var("ERROR") 8495 else: 8496 self._match_text_seq("TRUNCATE") 8497 on_overflow = self.expression( 8498 exp.OverflowTruncateBehavior( 8499 this=self._parse_string(), 8500 with_count=( 8501 self._match_text_seq("WITH", "COUNT") 8502 or not self._match_text_seq("WITHOUT", "COUNT") 8503 ), 8504 ) 8505 ) 8506 else: 8507 on_overflow = None 8508 8509 index = self._index 8510 if not self._match(TokenType.R_PAREN) and args: 8511 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8512 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8513 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8514 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8515 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8516 8517 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8518 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8519 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8520 if not self._match_text_seq("WITHIN", "GROUP"): 8521 self._retreat(index) 8522 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8523 8524 # The corresponding match_r_paren will be called in parse_function (caller) 8525 self._match_l_paren() 8526 8527 return self.expression( 8528 exp.GroupConcat( 8529 this=self._parse_order(this=seq_get(args, 0)), 8530 separator=seq_get(args, 1), 8531 on_overflow=on_overflow, 8532 ) 8533 ) 8534 8535 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8536 this = self._parse_bitwise() 8537 8538 if self._match(TokenType.USING): 8539 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8540 elif self._match(TokenType.COMMA): 8541 to = self._parse_char_charset(self._parse_types()) 8542 else: 8543 to = None 8544 8545 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8546 8547 def _parse_char_charset(self, to: exp.Expr | None) -> exp.Expr | None: 8548 # MySQL: CHAR[(N)] CHARACTER SET charset_name, the length is kept 8549 if ( 8550 isinstance(to, exp.DataType) 8551 and to.this == exp.DType.CHAR 8552 and (self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")) 8553 ): 8554 to.set("this", exp.DType.CHARACTER_SET) 8555 to.set("kind", self._parse_var_or_string()) 8556 return to 8557 8558 def _parse_xml_element(self) -> exp.XMLElement: 8559 if self._match_text_seq("EVALNAME"): 8560 evalname = True 8561 this = self._parse_bitwise() 8562 else: 8563 evalname = None 8564 self._match_text_seq("NAME") 8565 this = self._parse_id_var() 8566 8567 return self.expression( 8568 exp.XMLElement( 8569 this=this, 8570 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8571 evalname=evalname, 8572 ) 8573 ) 8574 8575 def _parse_xml_table(self) -> exp.XMLTable: 8576 namespaces = None 8577 passing = None 8578 columns = None 8579 8580 if self._match_text_seq("XMLNAMESPACES", "("): 8581 namespaces = self._parse_xml_namespace() 8582 self._match_text_seq(")", ",") 8583 8584 this = self._parse_string() 8585 8586 if self._match_text_seq("PASSING"): 8587 # The BY VALUE keywords are optional and are provided for semantic clarity 8588 self._match_text_seq("BY", "VALUE") 8589 passing = self._parse_csv(self._parse_column) 8590 8591 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8592 8593 if self._match_text_seq("COLUMNS"): 8594 columns = self._parse_csv(self._parse_field_def) 8595 8596 return self.expression( 8597 exp.XMLTable( 8598 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8599 ) 8600 ) 8601 8602 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8603 namespaces = [] 8604 8605 while True: 8606 if self._match(TokenType.DEFAULT): 8607 uri = self._parse_string() 8608 else: 8609 uri = self._parse_alias(self._parse_string()) 8610 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8611 if not self._match(TokenType.COMMA): 8612 break 8613 8614 return namespaces 8615 8616 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8617 args = self._parse_csv(self._parse_disjunction) 8618 8619 if len(args) < 3: 8620 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8621 8622 return self.expression(exp.DecodeCase(expressions=args)) 8623 8624 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8625 self._match_text_seq("KEY") 8626 key = self._parse_column() 8627 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8628 self._match_text_seq("VALUE") 8629 value = self._parse_bitwise() 8630 8631 if not key and not value: 8632 return None 8633 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8634 8635 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8636 if not this or not self._match_text_seq("FORMAT", "JSON"): 8637 return this 8638 8639 return self.expression(exp.FormatJson(this=this)) 8640 8641 def _parse_on_condition(self) -> exp.OnCondition | None: 8642 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8643 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8644 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8645 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8646 else: 8647 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8648 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8649 8650 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8651 8652 if not empty and not error and not null: 8653 return None 8654 8655 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8656 8657 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8658 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8659 for value in values: 8660 if self._match_text_seq(value, "ON", on): 8661 return f"{value} ON {on}" 8662 8663 index = self._index 8664 if self._match(TokenType.DEFAULT): 8665 default_value = self._parse_bitwise() 8666 if self._match_text_seq("ON", on): 8667 return default_value 8668 8669 self._retreat(index) 8670 8671 return None 8672 8673 @t.overload 8674 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8675 8676 @t.overload 8677 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8678 8679 def _parse_json_object(self, agg=False): 8680 star = self._parse_star() 8681 expressions = ( 8682 [star] 8683 if star 8684 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8685 ) 8686 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8687 8688 unique_keys = None 8689 if self._match_text_seq("WITH", "UNIQUE"): 8690 unique_keys = True 8691 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8692 unique_keys = False 8693 8694 self._match_text_seq("KEYS") 8695 8696 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8697 self._parse_type() 8698 ) 8699 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8700 8701 return self.expression( 8702 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8703 expressions=expressions, 8704 null_handling=null_handling, 8705 unique_keys=unique_keys, 8706 return_type=return_type, 8707 encoding=encoding, 8708 ) 8709 ) 8710 8711 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8712 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8713 if not self._match_text_seq("NESTED"): 8714 this = self._parse_id_var() 8715 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8716 kind = self._parse_types(allow_identifiers=False) 8717 nested = None 8718 else: 8719 this = None 8720 ordinality = None 8721 kind = None 8722 nested = True 8723 8724 format_json = self._match_text_seq("FORMAT", "JSON") 8725 path = self._match_text_seq("PATH") and self._parse_string() 8726 nested_schema = nested and self._parse_json_schema() 8727 8728 return self.expression( 8729 exp.JSONColumnDef( 8730 this=this, 8731 kind=kind, 8732 path=path, 8733 nested_schema=nested_schema, 8734 ordinality=ordinality, 8735 format_json=format_json, 8736 ) 8737 ) 8738 8739 def _parse_json_schema(self) -> exp.JSONSchema: 8740 self._match_text_seq("COLUMNS") 8741 return self.expression( 8742 exp.JSONSchema( 8743 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8744 ) 8745 ) 8746 8747 def _parse_json_table(self) -> exp.JSONTable: 8748 this = self._parse_format_json(self._parse_bitwise()) 8749 path = self._match(TokenType.COMMA) and self._parse_string() 8750 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8751 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8752 schema = self._parse_json_schema() 8753 8754 return exp.JSONTable( 8755 this=this, 8756 schema=schema, 8757 path=path, 8758 error_handling=error_handling, 8759 empty_handling=empty_handling, 8760 ) 8761 8762 def _parse_match_against(self) -> exp.MatchAgainst: 8763 if self._match_text_seq("TABLE"): 8764 # parse SingleStore MATCH(TABLE ...) syntax 8765 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8766 expressions = [] 8767 table = self._parse_table() 8768 if table: 8769 expressions = [table] 8770 else: 8771 expressions = self._parse_csv(self._parse_column) 8772 8773 self._match_text_seq(")", "AGAINST", "(") 8774 8775 this = self._parse_string() 8776 8777 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8778 modifier = "IN NATURAL LANGUAGE MODE" 8779 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8780 modifier = f"{modifier} WITH QUERY EXPANSION" 8781 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8782 modifier = "IN BOOLEAN MODE" 8783 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8784 modifier = "WITH QUERY EXPANSION" 8785 else: 8786 modifier = None 8787 8788 return self.expression( 8789 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8790 ) 8791 8792 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8793 def _parse_open_json(self) -> exp.OpenJSON: 8794 this = self._parse_bitwise() 8795 path = self._match(TokenType.COMMA) and self._parse_string() 8796 8797 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8798 this = self._parse_field(any_token=True) 8799 kind = self._parse_types() 8800 path = self._parse_string() 8801 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8802 8803 return self.expression( 8804 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8805 ) 8806 8807 expressions = None 8808 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8809 self._match_l_paren() 8810 expressions = self._parse_csv(_parse_open_json_column_def) 8811 8812 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8813 8814 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8815 args = self._parse_csv(self._parse_bitwise) 8816 8817 if self._match(TokenType.IN): 8818 return self.expression( 8819 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8820 ) 8821 8822 if haystack_first: 8823 haystack = seq_get(args, 0) 8824 needle = seq_get(args, 1) 8825 else: 8826 haystack = seq_get(args, 1) 8827 needle = seq_get(args, 0) 8828 8829 return self.expression( 8830 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8831 ) 8832 8833 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8834 args = self._parse_csv(self._parse_table) 8835 return exp.JoinHint(this=func_name.upper(), expressions=args) 8836 8837 def _parse_substring(self) -> exp.Substring: 8838 # Postgres supports the form: substring(string [from int] [for int]) 8839 # (despite being undocumented, the reverse order also works) 8840 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8841 8842 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8843 8844 start, length = None, None 8845 8846 while self._curr: 8847 if self._match(TokenType.FROM): 8848 start = self._parse_bitwise() 8849 elif self._match(TokenType.FOR): 8850 if not start: 8851 start = exp.Literal.number(1) 8852 length = self._parse_bitwise() 8853 else: 8854 break 8855 8856 if start: 8857 args.append(start) 8858 if length: 8859 args.append(length) 8860 8861 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8862 8863 def _parse_trim(self) -> exp.Trim: 8864 # https://www.w3resource.com/sql/character-functions/trim.php 8865 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8866 8867 position = None 8868 collation = None 8869 expression = None 8870 8871 if self._match_texts(self.TRIM_TYPES): 8872 position = self._prev.text.upper() 8873 8874 this = self._parse_bitwise() 8875 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8876 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8877 expression = self._parse_bitwise() 8878 8879 if invert_order: 8880 this, expression = expression, this 8881 8882 if self._match(TokenType.COLLATE): 8883 collation = self._parse_bitwise() 8884 8885 return self.expression( 8886 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8887 ) 8888 8889 def _parse_window_clause(self) -> list[exp.Expr] | None: 8890 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8891 8892 def _parse_named_window(self) -> exp.Expr | None: 8893 return self._parse_window(self._parse_id_var(), alias=True) 8894 8895 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8896 if self._curr.token_type == TokenType.VAR: 8897 if self._match_text_seq("IGNORE", "NULLS"): 8898 return self.expression(exp.IgnoreNulls(this=this)) 8899 if self._match_text_seq("RESPECT", "NULLS"): 8900 return self.expression(exp.RespectNulls(this=this)) 8901 return this 8902 8903 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8904 if self._match(TokenType.HAVING): 8905 self._match_texts(("MAX", "MIN")) 8906 max = self._prev.text.upper() != "MIN" 8907 return self.expression( 8908 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8909 ) 8910 8911 return this 8912 8913 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8914 func = this 8915 comments = func.comments if isinstance(func, exp.Expr) else None 8916 8917 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8918 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8919 if self._match_text_seq("FROM", "FIRST"): 8920 this.set("from_first", True) 8921 elif self._match_text_seq("FROM", "LAST"): 8922 this.set("from_first", False) 8923 8924 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8925 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8926 if self._match_text_seq("WITHIN", "GROUP"): 8927 order = self._parse_wrapped(self._parse_order) 8928 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8929 8930 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8931 self._match(TokenType.WHERE) 8932 this = self.expression( 8933 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8934 ) 8935 self._match_r_paren() 8936 8937 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8938 # Some dialects choose to implement and some do not. 8939 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8940 8941 # There is some code above in _parse_lambda that handles 8942 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8943 8944 # The below changes handle 8945 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8946 8947 # Oracle allows both formats 8948 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8949 # and Snowflake chose to do the same for familiarity 8950 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8951 if isinstance(this, exp.AggFunc): 8952 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8953 8954 if ignore_respect and ignore_respect is not this: 8955 ignore_respect.replace(ignore_respect.this) 8956 this = self.expression(ignore_respect.__class__(this=this)) 8957 8958 this = self._parse_respect_or_ignore_nulls(this) 8959 8960 # bigquery select from window x AS (partition by ...) 8961 if alias: 8962 over = None 8963 self._match(TokenType.ALIAS) 8964 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8965 return this 8966 else: 8967 over = self._prev.text.upper() 8968 8969 if comments and isinstance(func, exp.Expr): 8970 func.pop_comments() 8971 8972 if not self._match(TokenType.L_PAREN): 8973 return self.expression( 8974 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8975 ) 8976 8977 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8978 8979 first: bool | None = True if self._match(TokenType.FIRST) else None 8980 if self._match_text_seq("LAST"): 8981 first = False 8982 8983 partition, order = self._parse_partition_and_order() 8984 kind = ( 8985 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8986 ) and self._prev.text 8987 8988 if kind: 8989 self._match(TokenType.BETWEEN) 8990 start = self._parse_window_spec() 8991 8992 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8993 exclude = ( 8994 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8995 if self._match_text_seq("EXCLUDE") 8996 else None 8997 ) 8998 8999 spec = self.expression( 9000 exp.WindowSpec( 9001 kind=kind, 9002 start=start["value"], 9003 start_side=start["side"], 9004 end=end.get("value"), 9005 end_side=end.get("side"), 9006 exclude=exclude, 9007 ) 9008 ) 9009 else: 9010 spec = None 9011 9012 self._match_r_paren() 9013 9014 window = self.expression( 9015 exp.Window( 9016 this=this, 9017 partition_by=partition, 9018 order=order, 9019 spec=spec, 9020 alias=window_alias, 9021 over=over, 9022 first=first, 9023 ), 9024 comments=comments, 9025 ) 9026 9027 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 9028 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 9029 return self._parse_window(window, alias=alias) 9030 9031 return window 9032 9033 def _parse_partition_and_order( 9034 self, 9035 ) -> tuple[list[exp.Expr], exp.Expr | None]: 9036 return self._parse_partition_by(), self._parse_order() 9037 9038 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 9039 self._match(TokenType.BETWEEN) 9040 9041 return { 9042 "value": ( 9043 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 9044 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 9045 or self._parse_bitwise() 9046 ), 9047 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 9048 } 9049 9050 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 9051 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 9052 # so this section tries to parse the clause version and if it fails, it treats the token 9053 # as an identifier (alias) 9054 if self._can_parse_limit_or_offset(): 9055 return this 9056 9057 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 9058 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 9059 if self._can_parse_named_window(): 9060 return this 9061 9062 any_token = self._match(TokenType.ALIAS) 9063 comments = self._prev_comments 9064 9065 if explicit and not any_token: 9066 return this 9067 9068 if self._match(TokenType.L_PAREN): 9069 aliases = self.expression( 9070 exp.Aliases( 9071 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 9072 ), 9073 comments=comments, 9074 ) 9075 self._match_r_paren(aliases) 9076 return aliases 9077 9078 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 9079 self.STRING_ALIASES and self._parse_string_as_identifier() 9080 ) 9081 9082 if alias: 9083 comments.extend(alias.pop_comments()) 9084 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 9085 column = this.this 9086 9087 # Moves the comment next to the alias in `expr /* comment */ AS alias` 9088 if not this.comments and column and column.comments: 9089 this.comments = column.pop_comments() 9090 9091 return this 9092 9093 def _parse_id_var( 9094 self, 9095 any_token: bool = True, 9096 tokens: t.Collection[TokenType] | None = None, 9097 ) -> exp.Expr | None: 9098 expression = self._parse_identifier() 9099 if not expression and ( 9100 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 9101 ): 9102 quoted = self._prev.token_type == TokenType.STRING 9103 expression = self._identifier_expression(quoted=quoted) 9104 9105 return expression 9106 9107 def _parse_string(self) -> exp.Expr | None: 9108 if self._match_set(self.STRING_PARSERS): 9109 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 9110 return self._parse_placeholder() 9111 9112 def _parse_string_as_identifier(self) -> exp.Identifier | None: 9113 if not self._match(TokenType.STRING): 9114 return None 9115 output = exp.to_identifier(self._prev.text, quoted=True) 9116 output.update_positions(self._prev) 9117 return output 9118 9119 def _parse_number(self) -> exp.Expr | None: 9120 if self._match_set(self.NUMERIC_PARSERS): 9121 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 9122 return self._parse_placeholder() 9123 9124 def _parse_identifier(self) -> exp.Expr | None: 9125 if self._match(TokenType.IDENTIFIER): 9126 return self._identifier_expression(quoted=True) 9127 return self._parse_placeholder() 9128 9129 def _parse_var( 9130 self, 9131 any_token: bool = False, 9132 tokens: t.Collection[TokenType] | None = None, 9133 upper: bool = False, 9134 ) -> exp.Expr | None: 9135 if ( 9136 (any_token and self._advance_any()) 9137 or self._match(TokenType.VAR) 9138 or (self._match_set(tokens) if tokens else False) 9139 ): 9140 return self.expression( 9141 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 9142 ) 9143 return self._parse_placeholder() 9144 9145 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 9146 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 9147 self._advance() 9148 return self._prev 9149 return None 9150 9151 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 9152 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 9153 9154 def _parse_primary_or_var(self) -> exp.Expr | None: 9155 return self._parse_primary() or self._parse_var(any_token=True) 9156 9157 def _parse_null(self) -> exp.Expr | None: 9158 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 9159 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 9160 return self._parse_placeholder() 9161 9162 def _parse_boolean(self) -> exp.Expr | None: 9163 if self._match(TokenType.TRUE): 9164 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 9165 if self._match(TokenType.FALSE): 9166 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 9167 return self._parse_placeholder() 9168 9169 def _parse_star(self) -> exp.Expr | None: 9170 if self._match(TokenType.STAR): 9171 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 9172 return self._parse_placeholder() 9173 9174 def _parse_parameter(self) -> exp.Parameter: 9175 this = self._parse_identifier() or self._parse_primary_or_var() 9176 return self.expression(exp.Parameter(this=this)) 9177 9178 def _parse_placeholder(self) -> exp.Expr | None: 9179 if self._match_set(self.PLACEHOLDER_PARSERS): 9180 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 9181 if placeholder: 9182 return placeholder 9183 self._advance(-1) 9184 return None 9185 9186 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 9187 if not self._match_texts(keywords): 9188 return None 9189 if self._match(TokenType.L_PAREN, advance=False): 9190 return self._parse_wrapped_csv(self._parse_expression) 9191 9192 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 9193 return [expression] if expression else None 9194 9195 def _parse_csv( 9196 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 9197 ) -> list[T]: 9198 parse_result = parse_method() 9199 items = [parse_result] if parse_result is not None else [] 9200 9201 while self._match(sep): 9202 if isinstance(parse_result, exp.Expr): 9203 self._add_comments(parse_result) 9204 parse_result = parse_method() 9205 if parse_result is not None: 9206 items.append(parse_result) 9207 9208 return items 9209 9210 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 9211 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 9212 9213 def _parse_wrapped_csv( 9214 self, 9215 parse_method: t.Callable[[], T | None], 9216 sep: TokenType = TokenType.COMMA, 9217 optional: bool = False, 9218 ) -> list[T]: 9219 return self._parse_wrapped( 9220 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 9221 ) 9222 9223 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 9224 wrapped = self._match(TokenType.L_PAREN) 9225 if not wrapped and not optional: 9226 self.raise_error("Expecting (") 9227 parse_result = parse_method() 9228 if wrapped: 9229 self._match_r_paren() 9230 return parse_result 9231 9232 def _parse_expressions(self) -> list[exp.Expr]: 9233 return self._parse_csv(self._parse_expression) 9234 9235 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 9236 return ( 9237 self._parse_set_operations( 9238 self._parse_alias(self._parse_assignment(), explicit=True) 9239 if alias 9240 else self._parse_assignment() 9241 ) 9242 or self._parse_select() 9243 ) 9244 9245 def _parse_ddl_select(self) -> exp.Expr | None: 9246 return self._parse_query_modifiers( 9247 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 9248 ) 9249 9250 def _parse_transaction(self) -> exp.Transaction | exp.Command: 9251 this = None 9252 if self._match_texts(self.TRANSACTION_KIND): 9253 this = self._prev.text 9254 9255 self._match_texts(("TRANSACTION", "WORK")) 9256 9257 modes = [] 9258 while True: 9259 mode = [] 9260 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 9261 mode.append(self._prev.text) 9262 9263 if mode: 9264 modes.append(" ".join(mode)) 9265 if not self._match(TokenType.COMMA): 9266 break 9267 9268 return self.expression(exp.Transaction(this=this, modes=modes)) 9269 9270 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 9271 chain = None 9272 savepoint = None 9273 is_rollback = self._prev.token_type == TokenType.ROLLBACK 9274 9275 self._match_texts(("TRANSACTION", "WORK")) 9276 9277 if self._match_text_seq("TO"): 9278 self._match_text_seq("SAVEPOINT") 9279 savepoint = self._parse_id_var() 9280 9281 if self._match(TokenType.AND): 9282 chain = not self._match_text_seq("NO") 9283 self._match_text_seq("CHAIN") 9284 9285 if is_rollback: 9286 return self.expression(exp.Rollback(savepoint=savepoint)) 9287 9288 return self.expression(exp.Commit(chain=chain)) 9289 9290 def _parse_refresh(self) -> exp.Refresh | exp.Command: 9291 start = self._prev 9292 9293 if self._match_text_seq("EXTERNAL", "TABLE"): 9294 kind = "EXTERNAL TABLE" 9295 elif self._match(TokenType.TABLE): 9296 kind = "TABLE" 9297 elif self._match_text_seq("MATERIALIZED", "VIEW"): 9298 kind = "MATERIALIZED VIEW" 9299 else: 9300 kind = "" 9301 9302 this = self._parse_string() 9303 if not kind and not isinstance(this, exp.Literal): 9304 return self._parse_as_command(start) 9305 9306 return self.expression(exp.Refresh(this=this or self._parse_table(), kind=kind)) 9307 9308 def _parse_column_def_with_exists(self): 9309 start = self._index 9310 self._match(TokenType.COLUMN) 9311 9312 exists_column = self._parse_exists(not_=True) 9313 expression = self._parse_field_def() 9314 9315 if not isinstance(expression, exp.ColumnDef): 9316 self._retreat(start) 9317 return None 9318 9319 expression.set("exists", exists_column) 9320 9321 return expression 9322 9323 def _parse_add_column(self) -> exp.ColumnDef | None: 9324 if not self._prev.text.upper() == "ADD": 9325 return None 9326 9327 return self._parse_column_def_with_exists() 9328 9329 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9330 if not self._match(TokenType.DROP): 9331 return None 9332 9333 kind = None 9334 if not self.ALTER_DROP_REQUIRES_COLUMN: 9335 # A bare `DROP [IF EXISTS] c [CASCADE | RESTRICT]` drops a column 9336 index = self._index 9337 self._parse_exists() 9338 if ( 9339 self._curr.token_type not in (TokenType.COLUMN, TokenType.CONSTRAINT) 9340 and self._match_set(self.ID_VAR_TOKENS) 9341 and ( 9342 not self._curr 9343 or self._match(TokenType.COMMA, advance=False) 9344 or self._match_texts(("CASCADE", "RESTRICT"), advance=False) 9345 ) 9346 ): 9347 kind = "COLUMN" 9348 self._retreat(index) 9349 9350 return self._parse_drop(kind=kind) 9351 9352 def _parse_alter_drop_action(self) -> exp.Expr | None: 9353 drop = self._parse_drop_column() 9354 9355 if ( 9356 isinstance(drop, exp.Drop) 9357 and drop.kind == "COLUMN" 9358 and not self.dialect.ALTER_TABLE_DROP_REQUIRED_FOR_EACH_COLUMN 9359 and self._match(TokenType.COMMA) 9360 ): 9361 drop.set("tables", [*drop.args["tables"], *self._parse_csv(self._parse_column)]) 9362 9363 return drop 9364 9365 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9366 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9367 return self.expression( 9368 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9369 ) 9370 9371 def _parse_alter_table_add(self) -> list[exp.Expr]: 9372 def _parse_add_alteration() -> exp.Expr | None: 9373 self._match_text_seq("ADD") 9374 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) or self._match_texts( 9375 self.ADD_CONSTRAINT_KEYWORDS, advance=False 9376 ): 9377 return self.expression( 9378 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9379 ) 9380 9381 column_def = self._parse_add_column() 9382 if isinstance(column_def, exp.ColumnDef): 9383 return column_def 9384 9385 exists = self._parse_exists(not_=True) 9386 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9387 return self.expression( 9388 exp.AddPartition( 9389 exists=exists, 9390 this=self._parse_field(any_token=True), 9391 location=self._match_text_seq("LOCATION", advance=False) 9392 and self._parse_property(), 9393 ) 9394 ) 9395 9396 return None 9397 9398 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9399 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9400 or self._match_text_seq("COLUMNS") 9401 ): 9402 schema = self._parse_schema() 9403 9404 return ( 9405 ensure_list(schema) 9406 if schema 9407 else self._parse_csv(self._parse_column_def_with_exists) 9408 ) 9409 9410 return self._parse_csv(_parse_add_alteration) 9411 9412 def _parse_alter_table_alter(self) -> exp.Expr | None: 9413 if self._match_texts(self.ALTER_ALTER_PARSERS): 9414 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9415 9416 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9417 # keyword after ALTER we default to parsing this statement 9418 self._match(TokenType.COLUMN) 9419 exists = self._parse_exists() 9420 column = self._parse_field(any_token=True) 9421 9422 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9423 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9424 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9425 return self.expression( 9426 exp.AlterColumn( 9427 this=column, default=self._parse_disjunction(), exists=exists or None 9428 ) 9429 ) 9430 if self._match(TokenType.COMMENT): 9431 return self.expression( 9432 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9433 ) 9434 if self._match_text_seq("DROP", "NOT", "NULL"): 9435 return self.expression( 9436 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9437 ) 9438 if self._match_text_seq("SET", "NOT", "NULL"): 9439 return self.expression( 9440 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9441 ) 9442 9443 if self._match_text_seq("SET", "VISIBLE"): 9444 return self.expression( 9445 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9446 ) 9447 if self._match_text_seq("SET", "INVISIBLE"): 9448 return self.expression( 9449 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9450 ) 9451 9452 self._match_text_seq("SET", "DATA") 9453 dtype = ( 9454 self._parse_types() 9455 if self._match_text_seq("TYPE") or not self.ALTER_COLUMN_TYPE_REQUIRES_KEYWORD 9456 else None 9457 ) 9458 if not dtype: 9459 return None 9460 9461 return self.expression( 9462 exp.AlterColumn( 9463 this=column, 9464 dtype=dtype, 9465 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9466 using=self._match(TokenType.USING) and self._parse_disjunction(), 9467 exists=exists or None, 9468 ) 9469 ) 9470 9471 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9472 if self._match_texts(("ALL", "EVEN", "AUTO")): 9473 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9474 9475 self._match_text_seq("KEY", "DISTKEY") 9476 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9477 9478 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9479 if compound: 9480 self._match_text_seq("SORTKEY") 9481 9482 if self._match(TokenType.L_PAREN, advance=False): 9483 return self.expression( 9484 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9485 ) 9486 9487 self._match_texts(("AUTO", "NONE")) 9488 return self.expression( 9489 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9490 ) 9491 9492 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9493 index = self._index - 1 9494 9495 partition_exists = self._parse_exists() 9496 if self._match(TokenType.PARTITION, advance=False): 9497 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9498 9499 self._retreat(index) 9500 return self._parse_csv(self._parse_alter_drop_action) 9501 9502 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9503 if self._match(TokenType.COLUMN) or ( 9504 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9505 ): 9506 exists = self._parse_exists() 9507 old_column = self._parse_column() 9508 to = self._match_text_seq("TO") 9509 new_column = self._parse_column() 9510 9511 if old_column is None or not to or new_column is None: 9512 return None 9513 9514 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9515 9516 self._match_text_seq("TO") 9517 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9518 9519 def _parse_alter_table_set(self) -> exp.AlterSet: 9520 alter_set = self.expression(exp.AlterSet()) 9521 9522 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9523 "TABLE", "PROPERTIES" 9524 ): 9525 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9526 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9527 alter_set.set("expressions", [self._parse_assignment()]) 9528 elif self._match_texts(("LOGGED", "UNLOGGED")): 9529 alter_set.set("option", exp.var(self._prev.text.upper())) 9530 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9531 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9532 elif self._match_text_seq("LOCATION"): 9533 alter_set.set("location", self._parse_field()) 9534 elif self._match_text_seq("ACCESS", "METHOD"): 9535 alter_set.set("access_method", self._parse_field()) 9536 elif self._match_text_seq("TABLESPACE"): 9537 alter_set.set("tablespace", self._parse_field()) 9538 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9539 alter_set.set("file_format", [self._parse_field()]) 9540 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9541 alter_set.set("file_format", self._parse_wrapped_options()) 9542 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9543 alter_set.set("copy_options", self._parse_wrapped_options()) 9544 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9545 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9546 else: 9547 if self._match_text_seq("SERDE"): 9548 alter_set.set("serde", self._parse_field()) 9549 9550 properties = self._parse_wrapped(self._parse_properties, optional=True) 9551 alter_set.set("expressions", [properties]) 9552 9553 return alter_set 9554 9555 def _parse_alter_session(self) -> exp.AlterSession: 9556 """Parse ALTER SESSION SET/UNSET statements.""" 9557 if self._match(TokenType.SET): 9558 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9559 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9560 9561 self._match_text_seq("UNSET") 9562 expressions = self._parse_csv( 9563 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9564 ) 9565 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9566 9567 def _parse_alter(self) -> exp.Alter | exp.Command: 9568 start = self._prev 9569 9570 iceberg = self._match_text_seq("ICEBERG") 9571 9572 alter_token = self._match_set(self.ALTERABLES) and self._prev 9573 if not alter_token: 9574 return self._parse_as_command(start) 9575 if iceberg and alter_token.token_type != TokenType.TABLE: 9576 return self._parse_as_command(start) 9577 9578 exists = self._parse_exists() 9579 only = self._match_text_seq("ONLY") 9580 9581 if alter_token.token_type == TokenType.SESSION: 9582 this = None 9583 check = None 9584 cluster = None 9585 parser = self.ALTER_PARSERS.get(alter_token.text.upper()) 9586 else: 9587 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9588 check = self._match_text_seq("WITH", "CHECK") 9589 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9590 parser = ( 9591 self.ALTER_PARSERS[self._prev.text.upper()] 9592 if self._match_texts(self.ALTER_PARSERS) 9593 else None 9594 ) 9595 9596 options: list[exp.Expr] = [] 9597 if ( 9598 not parser 9599 and alter_token.token_type == TokenType.TABLE 9600 and not self.ALTER_TABLE_REQUIRES_ACTION 9601 ): 9602 # Table options may precede the actions, e.g. MySQL's ENGINE=InnoDB, ADD COLUMN a INT. 9603 # A clause the property parser rejects, like PARTITION BY RANGE ... MAXVALUE, stays a Command 9604 properties = self._try_parse(self._parse_properties) 9605 options = properties.expressions if properties else [] 9606 9607 actions = ensure_list(parser(self) if parser else None) 9608 if actions or options: 9609 # Actions of different kinds and table options may alternate, e.g. MySQL's 9610 # ADD c INT, AUTO_INCREMENT=5, ADD d INT. The comma before an item may have already 9611 # been consumed by the previous parser 9612 while self.ALTER_TABLE_MIXED_ACTIONS and ( 9613 self._match(TokenType.COMMA) or self._prev.token_type == TokenType.COMMA 9614 ): 9615 if not self._match_texts( 9616 self.PROPERTY_PARSERS, advance=False 9617 ) and self._match_texts(self.ALTER_PARSERS): 9618 parser = self.ALTER_PARSERS[self._prev.text.upper()] 9619 parsed = ensure_list(self._try_parse(lambda: parser(self))) 9620 if not parsed or any(isinstance(action, exp.Command) for action in parsed): 9621 return self._parse_as_command(start) 9622 9623 actions.extend(parsed) 9624 elif properties := self._try_parse(self._parse_properties): 9625 options.extend(properties.expressions) 9626 else: 9627 break 9628 9629 not_valid = self._match_text_seq("NOT", "VALID") 9630 if properties := self._try_parse(self._parse_properties): 9631 options.extend(properties.expressions) 9632 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9633 9634 if not self._curr: 9635 return self.expression( 9636 exp.Alter( 9637 this=this, 9638 kind=alter_token.text.upper(), 9639 exists=exists, 9640 actions=actions, 9641 only=only, 9642 options=options, 9643 cluster=cluster, 9644 not_valid=not_valid, 9645 check=check, 9646 cascade=cascade, 9647 iceberg=iceberg, 9648 ) 9649 ) 9650 9651 return self._parse_as_command(start) 9652 9653 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9654 start = self._prev 9655 # https://duckdb.org/docs/sql/statements/analyze 9656 if not self._curr: 9657 return self.expression(exp.Analyze()) 9658 9659 options = [] 9660 while self._match_texts(self.ANALYZE_STYLES): 9661 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9662 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9663 else: 9664 options.append(self._prev.text.upper()) 9665 9666 tables: exp.Expr | list[exp.Expr] | None = None 9667 inner_expression: exp.Expr | None = None 9668 9669 kind = self._curr.text.upper() if self._curr else None 9670 9671 if self._match(TokenType.TABLE): 9672 tables = self._parse_csv(self._parse_table_parts) 9673 elif self._match(TokenType.INDEX): 9674 tables = self._parse_table_parts() 9675 elif self._match_text_seq("TABLES"): 9676 if self._match_set((TokenType.FROM, TokenType.IN)): 9677 kind = f"{kind} {self._prev.text.upper()}" 9678 tables = self._parse_table(schema=True, is_db_reference=True) 9679 elif self._match_text_seq("DATABASE"): 9680 tables = self._parse_table(schema=True, is_db_reference=True) 9681 elif self._match_text_seq("CLUSTER"): 9682 tables = self._parse_table() 9683 # Try matching inner expr keywords before fallback to parse table. 9684 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9685 kind = None 9686 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9687 else: 9688 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9689 kind = None 9690 tables = self._parse_csv(self._parse_table_parts) 9691 9692 partition = self._try_parse(self._parse_partition) 9693 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9694 return self._parse_as_command(start) 9695 9696 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9697 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9698 "WITH", "ASYNC", "MODE" 9699 ): 9700 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9701 else: 9702 mode = None 9703 9704 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9705 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9706 9707 properties = self._parse_properties() 9708 return self.expression( 9709 exp.Analyze( 9710 kind=kind, 9711 tables=ensure_list(tables), 9712 mode=mode, 9713 partition=partition, 9714 properties=properties, 9715 expression=inner_expression, 9716 options=options, 9717 ) 9718 ) 9719 9720 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9721 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9722 this = None 9723 kind = self._prev.text.upper() 9724 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9725 expressions = [] 9726 9727 if not self._match_text_seq("STATISTICS"): 9728 self.raise_error("Expecting token STATISTICS") 9729 9730 if self._match_text_seq("NOSCAN"): 9731 this = "NOSCAN" 9732 elif self._match(TokenType.FOR): 9733 if self._match_text_seq("ALL", "COLUMNS"): 9734 this = "FOR ALL COLUMNS" 9735 if self._match_text_seq("COLUMNS"): 9736 this = "FOR COLUMNS" 9737 expressions = self._parse_csv(self._parse_column_reference) 9738 elif self._match_text_seq("SAMPLE"): 9739 sample = self._parse_number() 9740 expressions = [ 9741 self.expression( 9742 exp.AnalyzeSample( 9743 sample=sample, 9744 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9745 ) 9746 ) 9747 ] 9748 9749 return self.expression( 9750 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9751 ) 9752 9753 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9754 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9755 kind = None 9756 this = None 9757 expression: exp.Expr | None = None 9758 if self._match_text_seq("REF", "UPDATE"): 9759 kind = "REF" 9760 this = "UPDATE" 9761 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9762 this = "UPDATE SET DANGLING TO NULL" 9763 elif self._match_text_seq("STRUCTURE"): 9764 kind = "STRUCTURE" 9765 if self._match_text_seq("CASCADE", "FAST"): 9766 this = "CASCADE FAST" 9767 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9768 ("ONLINE", "OFFLINE") 9769 ): 9770 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9771 expression = self._parse_into() 9772 9773 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9774 9775 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9776 this = self._prev.text.upper() 9777 if self._match_text_seq("COLUMNS"): 9778 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9779 return None 9780 9781 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9782 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9783 if self._match_text_seq("STATISTICS"): 9784 return self.expression(exp.AnalyzeDelete(kind=kind)) 9785 return None 9786 9787 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9788 if self._match_text_seq("CHAINED", "ROWS"): 9789 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9790 return None 9791 9792 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9793 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9794 this = self._prev.text.upper() 9795 expression: exp.Expr | None = None 9796 expressions = [] 9797 update_options = None 9798 9799 if self._match_text_seq("HISTOGRAM", "ON"): 9800 expressions = self._parse_csv(self._parse_column_reference) 9801 with_expressions = [] 9802 while self._match(TokenType.WITH): 9803 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9804 if self._match_texts(("SYNC", "ASYNC")): 9805 if self._match_text_seq("MODE", advance=False): 9806 with_expressions.append(f"{self._prev.text.upper()} MODE") 9807 self._advance() 9808 else: 9809 buckets = self._parse_number() 9810 if self._match_text_seq("BUCKETS"): 9811 with_expressions.append(f"{buckets} BUCKETS") 9812 if with_expressions: 9813 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9814 9815 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9816 TokenType.UPDATE, advance=False 9817 ): 9818 update_options = self._prev.text.upper() 9819 self._advance() 9820 elif self._match_text_seq("USING", "DATA"): 9821 expression = self.expression(exp.UsingData(this=self._parse_string())) 9822 9823 return self.expression( 9824 exp.AnalyzeHistogram( 9825 this=this, 9826 expressions=expressions, 9827 expression=expression, 9828 update_options=update_options, 9829 ) 9830 ) 9831 9832 def _parse_merge(self) -> exp.Merge: 9833 self._match(TokenType.INTO) 9834 target = self._parse_table() 9835 9836 if target and self._match(TokenType.ALIAS, advance=False): 9837 target.set("alias", self._parse_table_alias()) 9838 9839 self._match(TokenType.USING) 9840 using = self._parse_table() 9841 9842 return self.expression( 9843 exp.Merge( 9844 this=target, 9845 using=using, 9846 on=self._match(TokenType.ON) and self._parse_disjunction(), 9847 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9848 whens=self._parse_when_matched(), 9849 returning=self._parse_returning(), 9850 ) 9851 ) 9852 9853 def _parse_when_matched(self) -> exp.Whens: 9854 whens = [] 9855 9856 while self._match(TokenType.WHEN): 9857 matched = not self._match(TokenType.NOT) 9858 self._match_text_seq("MATCHED") 9859 source = ( 9860 False 9861 if self._match_text_seq("BY", "TARGET") 9862 else self._match_text_seq("BY", "SOURCE") 9863 ) 9864 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9865 9866 self._match(TokenType.THEN) 9867 9868 if self._match(TokenType.INSERT): 9869 this = self._parse_star() 9870 if this: 9871 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9872 else: 9873 then = self.expression( 9874 exp.Insert( 9875 this=exp.var("ROW") 9876 if self._match_text_seq("ROW") 9877 else self._parse_value(values=False), 9878 expression=self._match_text_seq("VALUES") and self._parse_value(), 9879 where=self._parse_where(), 9880 ) 9881 ) 9882 elif self._match(TokenType.UPDATE): 9883 expressions = self._parse_star() 9884 if expressions: 9885 then = self.expression(exp.Update(expressions=expressions)) 9886 else: 9887 then = self.expression( 9888 exp.Update( 9889 expressions=self._match(TokenType.SET) 9890 and self._parse_csv(self._parse_update_assignment), 9891 where=self._parse_where(), 9892 ) 9893 ) 9894 elif self._match(TokenType.DELETE): 9895 then = self.expression(exp.Var(this=self._prev.text)) 9896 else: 9897 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9898 9899 whens.append( 9900 self.expression( 9901 exp.When(matched=matched, source=source, condition=condition, then=then) 9902 ) 9903 ) 9904 return self.expression(exp.Whens(expressions=whens)) 9905 9906 def _parse_show(self) -> exp.Expr | None: 9907 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9908 if parser: 9909 return parser(self) 9910 return self._parse_as_command(self._prev) 9911 9912 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9913 index = self._index 9914 9915 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9916 return self._parse_set_transaction(global_=kind == "GLOBAL") 9917 9918 left = self._parse_primary() or self._parse_column() 9919 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 9920 9921 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 9922 self._retreat(index) 9923 return None 9924 9925 right = self._parse_statement() or self._parse_id_var() 9926 if isinstance(right, (exp.Column, exp.Identifier)): 9927 right = exp.var(right.name) 9928 9929 this = self.expression(exp.EQ(this=left, expression=right)) 9930 return self.expression(exp.SetItem(this=this, kind=kind)) 9931 9932 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 9933 self._match_text_seq("TRANSACTION") 9934 characteristics = self._parse_csv( 9935 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 9936 ) 9937 return self.expression( 9938 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 9939 ) 9940 9941 def _parse_set_item(self) -> exp.Expr | None: 9942 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 9943 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 9944 9945 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 9946 index = self._index 9947 set_ = self.expression( 9948 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 9949 ) 9950 9951 if self._curr: 9952 self._retreat(index) 9953 return self._parse_as_command(self._prev) 9954 9955 return set_ 9956 9957 def _parse_var_from_options( 9958 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 9959 ) -> exp.Var | None: 9960 start = self._curr 9961 if not start: 9962 return None 9963 9964 option = start.text.upper() 9965 continuations = ( 9966 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 9967 ) 9968 9969 index = self._index 9970 self._advance() 9971 for keywords in continuations or []: 9972 if isinstance(keywords, str): 9973 keywords = (keywords,) 9974 9975 if self._match_text_seq(*keywords): 9976 option = f"{option} {' '.join(keywords)}" 9977 break 9978 else: 9979 if continuations or continuations is None: 9980 if raise_unmatched: 9981 self.raise_error(f"Unknown option {option}") 9982 9983 self._retreat(index) 9984 return None 9985 9986 return exp.var(option) 9987 9988 def _parse_as_command(self, start: Token) -> exp.Command: 9989 while self._curr: 9990 self._advance() 9991 text = self._find_sql(start, self._prev) 9992 size = len(start.text) 9993 self._warn_unsupported() 9994 return exp.Command(this=text[:size], expression=text[size:]) 9995 9996 def _parse_dict_property(self, this: str) -> exp.DictProperty: 9997 settings = [] 9998 9999 self._match_l_paren() 10000 kind = self._parse_id_var() 10001 10002 if self._match(TokenType.L_PAREN): 10003 while True: 10004 key = self._parse_id_var() 10005 value = self._parse_function() or self._parse_primary_or_var() 10006 if not key and value is None: 10007 break 10008 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 10009 self._match(TokenType.R_PAREN) 10010 10011 self._match_r_paren() 10012 10013 return self.expression( 10014 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 10015 ) 10016 10017 def _parse_dict_range(self, this: str) -> exp.DictRange: 10018 self._match_l_paren() 10019 has_min = self._match_text_seq("MIN") 10020 if has_min: 10021 min = self._parse_var() or self._parse_primary() 10022 self._match_text_seq("MAX") 10023 max = self._parse_var() or self._parse_primary() 10024 else: 10025 max = self._parse_var() or self._parse_primary() 10026 min = exp.Literal.number(0) 10027 self._match_r_paren() 10028 return self.expression(exp.DictRange(this=this, min=min, max=max)) 10029 10030 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 10031 index = self._index 10032 expression = self._parse_id_var() 10033 position = self._parse_id_var() if self._match(TokenType.COMMA) else None 10034 10035 if not self._match(TokenType.IN): 10036 self._retreat(index - 1) 10037 return None 10038 iterator = self._parse_column() 10039 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 10040 return self.expression( 10041 exp.Comprehension( 10042 this=this, 10043 expression=expression, 10044 position=position, 10045 iterator=iterator, 10046 condition=condition, 10047 ) 10048 ) 10049 10050 def _parse_heredoc(self) -> exp.Heredoc | None: 10051 if self._match(TokenType.HEREDOC_STRING): 10052 return self.expression(exp.Heredoc(this=self._prev.text)) 10053 10054 if not self._match_text_seq("$"): 10055 return None 10056 10057 tags = ["$"] 10058 tag_text = None 10059 10060 if self._is_connected(): 10061 self._advance() 10062 tags.append(self._prev.text.upper()) 10063 else: 10064 self.raise_error("No closing $ found") 10065 10066 if tags[-1] != "$": 10067 if self._is_connected() and self._match_text_seq("$"): 10068 tag_text = tags[-1] 10069 tags.append("$") 10070 else: 10071 self.raise_error("No closing $ found") 10072 10073 heredoc_start = self._curr 10074 10075 while self._curr: 10076 if self._match_text_seq(*tags, advance=False): 10077 this = self._find_sql(heredoc_start, self._prev) 10078 self._advance(len(tags)) 10079 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 10080 10081 self._advance() 10082 10083 self.raise_error(f"No closing {''.join(tags)} found") 10084 return None 10085 10086 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 10087 if not self._curr: 10088 return None 10089 10090 index = self._index 10091 this = [] 10092 while True: 10093 # The current token might be multiple words 10094 curr = self._curr.text.upper() 10095 key = curr.split(" ") 10096 this.append(curr) 10097 10098 self._advance() 10099 result, trie = in_trie(trie, key) 10100 if result == TrieResult.FAILED: 10101 break 10102 10103 if result == TrieResult.EXISTS: 10104 subparser = parsers[" ".join(this)] 10105 return subparser 10106 10107 self._retreat(index) 10108 return None 10109 10110 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 10111 if not self._match(TokenType.L_PAREN, expression=expression): 10112 self.raise_error("Expecting (") 10113 10114 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 10115 if not self._match(TokenType.R_PAREN, expression=expression): 10116 self.raise_error("Expecting )") 10117 10118 def _replace_lambda( 10119 self, node: exp.Expr | None, expressions: list[exp.Expr] 10120 ) -> exp.Expr | None: 10121 if not node: 10122 return node 10123 10124 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 10125 10126 for column in node.find_all(exp.Column): 10127 typ = lambda_types.get(column.parts[0].name) 10128 if typ is not None: 10129 dot_or_id = column.to_dot() if column.table else column.this 10130 10131 if typ: 10132 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ)) 10133 10134 parent = column.parent 10135 10136 while isinstance(parent, exp.Dot): 10137 if not isinstance(parent.parent, exp.Dot): 10138 parent.replace(dot_or_id) 10139 break 10140 parent = parent.parent 10141 else: 10142 if column is node: 10143 node = dot_or_id 10144 else: 10145 column.replace(dot_or_id) 10146 return node 10147 10148 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 10149 start = self._prev 10150 10151 # Not to be confused with TRUNCATE(number, decimals) function call 10152 if self._match(TokenType.L_PAREN): 10153 self._retreat(self._index - 2) 10154 return self._parse_function() 10155 10156 # Clickhouse supports TRUNCATE DATABASE as well 10157 is_database = self._match(TokenType.DATABASE) 10158 10159 self._match(TokenType.TABLE) 10160 10161 exists = self._parse_exists(not_=False) 10162 10163 expressions = self._parse_csv( 10164 lambda: self._parse_table(schema=True, is_db_reference=is_database) 10165 ) 10166 10167 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 10168 10169 if self._match_text_seq("RESTART", "IDENTITY"): 10170 identity = "RESTART" 10171 elif self._match_text_seq("CONTINUE", "IDENTITY"): 10172 identity = "CONTINUE" 10173 else: 10174 identity = None 10175 10176 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 10177 option = self._prev.text 10178 else: 10179 option = None 10180 10181 partition = self._parse_partition() 10182 10183 # Fallback case 10184 if self._curr: 10185 return self._parse_as_command(start) 10186 10187 return self.expression( 10188 exp.TruncateTable( 10189 expressions=expressions, 10190 is_database=is_database, 10191 exists=exists, 10192 cluster=cluster, 10193 identity=identity, 10194 option=option, 10195 partition=partition, 10196 ) 10197 ) 10198 10199 def _parse_indexed_column(self) -> exp.Expr | None: 10200 return self._parse_ordered(self._parse_opclass) 10201 10202 def _parse_with_operator(self) -> exp.Expr | None: 10203 this = self._parse_indexed_column() 10204 10205 if not self._match(TokenType.WITH): 10206 return this 10207 10208 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 10209 10210 return self.expression(exp.WithOperator(this=this, op=op)) 10211 10212 def _parse_wrapped_options(self) -> list[exp.Expr]: 10213 self._match(TokenType.EQ) 10214 self._match(TokenType.L_PAREN) 10215 10216 opts: list[exp.Expr] = [] 10217 option: exp.Expr | list[exp.Expr] | None 10218 while self._curr and not self._match(TokenType.R_PAREN): 10219 if self._match_text_seq("FORMAT_NAME", "="): 10220 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 10221 option = self._parse_format_name() 10222 else: 10223 option = self._parse_property() 10224 10225 if option is None: 10226 self.raise_error("Unable to parse option") 10227 break 10228 10229 opts.extend(ensure_list(option)) 10230 10231 return opts 10232 10233 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 10234 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 10235 10236 options = [] 10237 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 10238 option = self._parse_var(any_token=True) 10239 prev = self._prev.text.upper() 10240 10241 # Different dialects might separate options and values by white space, "=" and "AS" 10242 self._match(TokenType.EQ) 10243 self._match(TokenType.ALIAS) 10244 10245 param = self.expression(exp.CopyParameter(this=option)) 10246 10247 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 10248 TokenType.L_PAREN, advance=False 10249 ): 10250 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 10251 param.set("expressions", self._parse_wrapped_options()) 10252 elif prev == "FILE_FORMAT": 10253 # T-SQL's external file format case 10254 param.set("expression", self._parse_field()) 10255 elif ( 10256 prev == "FORMAT" 10257 and self._prev.token_type == TokenType.ALIAS 10258 and self._match_texts(("AVRO", "JSON")) 10259 ): 10260 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 10261 param.set("expression", self._parse_field()) 10262 else: 10263 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 10264 10265 options.append(param) 10266 10267 if sep: 10268 self._match(sep) 10269 10270 return options 10271 10272 def _parse_credentials(self) -> exp.Credentials | None: 10273 expr = self.expression(exp.Credentials()) 10274 10275 if self._match_text_seq("STORAGE_INTEGRATION", "="): 10276 expr.set("storage", self._parse_field()) 10277 if self._match_text_seq("CREDENTIALS"): 10278 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 10279 creds = ( 10280 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 10281 ) 10282 expr.set("credentials", creds) 10283 if self._match_text_seq("ENCRYPTION"): 10284 expr.set("encryption", self._parse_wrapped_options()) 10285 if self._match_text_seq("IAM_ROLE"): 10286 expr.set( 10287 "iam_role", 10288 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 10289 ) 10290 if self._match_text_seq("REGION"): 10291 expr.set("region", self._parse_field()) 10292 10293 return expr 10294 10295 def _parse_file_location(self) -> exp.Expr | None: 10296 return self._parse_field() 10297 10298 def _parse_copy(self) -> exp.Copy | exp.Command: 10299 start = self._prev 10300 10301 self._match(TokenType.INTO) 10302 10303 this = ( 10304 self._parse_select(nested=True, parse_subquery_alias=False) 10305 if self._match(TokenType.L_PAREN, advance=False) 10306 else self._parse_table(schema=True) 10307 ) 10308 10309 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 10310 10311 files = self._parse_csv(self._parse_file_location) 10312 if self._match(TokenType.EQ, advance=False): 10313 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 10314 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 10315 # list via `_parse_wrapped(..)` below. 10316 self._advance(-1) 10317 files = [] 10318 10319 credentials = self._parse_credentials() 10320 10321 self._match_text_seq("WITH") 10322 10323 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 10324 10325 # Fallback case 10326 if self._curr: 10327 return self._parse_as_command(start) 10328 10329 return self.expression( 10330 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 10331 ) 10332 10333 def _parse_normalize(self) -> exp.Normalize: 10334 return self.expression( 10335 exp.Normalize( 10336 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 10337 ) 10338 ) 10339 10340 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 10341 args = self._parse_csv(lambda: self._parse_lambda()) 10342 10343 this = seq_get(args, 0) 10344 decimals = seq_get(args, 1) 10345 10346 return expr_type( 10347 this=this, 10348 decimals=decimals, 10349 to=self._parse_var() if self._match_text_seq("TO") else None, 10350 ) 10351 10352 def _parse_star_ops(self) -> exp.Expr | None: 10353 star_token = self._prev 10354 10355 if self._match_text_seq("COLUMNS", "(", advance=False): 10356 this = self._parse_function() 10357 if isinstance(this, exp.Columns): 10358 this.set("unpack", True) 10359 return this 10360 10361 index = self._index 10362 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 10363 if not ilike: 10364 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 10365 self._retreat(index) 10366 10367 return self.expression( 10368 exp.Star( 10369 ilike=ilike, 10370 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 10371 replace=self._parse_star_op("REPLACE"), 10372 rename=self._parse_star_op("RENAME"), 10373 ) 10374 ).update_positions(star_token) 10375 10376 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 10377 privilege_parts = [] 10378 10379 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 10380 # (end of privilege list) or L_PAREN (start of column list) are met 10381 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 10382 privilege_parts.append(self._curr.text.upper()) 10383 self._advance() 10384 10385 if not privilege_parts: 10386 self.raise_error("Expected privilege") 10387 return None 10388 10389 this = exp.var(" ".join(privilege_parts)) 10390 expressions = ( 10391 self._parse_wrapped_csv(self._parse_column) 10392 if self._match(TokenType.L_PAREN, advance=False) 10393 else None 10394 ) 10395 10396 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10397 10398 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10399 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10400 principal = self._parse_id_var() 10401 10402 if not principal: 10403 return None 10404 10405 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10406 10407 def _parse_grant_revoke_common( 10408 self, 10409 ) -> tuple[list | None, str | None, exp.Expr | None]: 10410 privileges = self._parse_csv(self._parse_grant_privilege) 10411 10412 self._match(TokenType.ON) 10413 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10414 10415 # Attempt to parse the securable e.g. MySQL allows names 10416 # such as "foo.*", "*.*" which are not easily parseable yet 10417 securable = self._try_parse(self._parse_table_parts) 10418 10419 return privileges, kind, securable 10420 10421 def _parse_grant(self) -> exp.Grant | exp.Command: 10422 start = self._prev 10423 10424 privileges, kind, securable = self._parse_grant_revoke_common() 10425 10426 if not securable or not self._match_text_seq("TO"): 10427 return self._parse_as_command(start) 10428 10429 principals = self._parse_csv(self._parse_grant_principal) 10430 10431 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10432 10433 if self._curr: 10434 return self._parse_as_command(start) 10435 10436 return self.expression( 10437 exp.Grant( 10438 privileges=privileges, 10439 kind=kind, 10440 securable=securable, 10441 principals=principals, 10442 grant_option=grant_option, 10443 ) 10444 ) 10445 10446 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10447 start = self._prev 10448 10449 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10450 10451 privileges, kind, securable = self._parse_grant_revoke_common() 10452 10453 if not securable or not self._match_text_seq("FROM"): 10454 return self._parse_as_command(start) 10455 10456 principals = self._parse_csv(self._parse_grant_principal) 10457 10458 cascade = None 10459 if self._match_texts(("CASCADE", "RESTRICT")): 10460 cascade = self._prev.text.upper() 10461 10462 if self._curr: 10463 return self._parse_as_command(start) 10464 10465 return self.expression( 10466 exp.Revoke( 10467 privileges=privileges, 10468 kind=kind, 10469 securable=securable, 10470 principals=principals, 10471 grant_option=grant_option, 10472 cascade=cascade, 10473 ) 10474 ) 10475 10476 def _parse_overlay(self) -> exp.Overlay: 10477 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10478 return ( 10479 self._parse_bitwise() 10480 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10481 else None 10482 ) 10483 10484 return self.expression( 10485 exp.Overlay( 10486 this=self._parse_bitwise(), 10487 expression=_parse_overlay_arg("PLACING"), 10488 from_=_parse_overlay_arg("FROM"), 10489 for_=_parse_overlay_arg("FOR"), 10490 ) 10491 ) 10492 10493 def _parse_format_name(self) -> exp.Property: 10494 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10495 # for FILE_FORMAT = <format_name> 10496 return self.expression( 10497 exp.Property( 10498 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10499 ) 10500 ) 10501 10502 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10503 is_distinct = self._match(TokenType.DISTINCT) 10504 if not is_distinct: 10505 self._match(TokenType.ALL) 10506 10507 args = [self._parse_lambda()] 10508 if self._match(TokenType.COMMA): 10509 args.extend(self._parse_function_args()) 10510 10511 target = seq_get(args, distinct_index) 10512 if is_distinct and target: 10513 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10514 10515 return func.from_arg_list(args) 10516 10517 def _identifier_expression( 10518 self, token: Token | None = None, quoted: bool | None = None 10519 ) -> exp.Identifier: 10520 token = token or self._prev 10521 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10522 10523 def _build_pipe_cte( 10524 self, 10525 query: exp.Query, 10526 expressions: list[exp.Expr], 10527 alias_cte: exp.TableAlias | None = None, 10528 ) -> exp.Select: 10529 new_cte: str | exp.TableAlias | None 10530 if alias_cte: 10531 new_cte = alias_cte 10532 else: 10533 self._pipe_cte_counter += 1 10534 new_cte = f"__tmp{self._pipe_cte_counter}" 10535 10536 with_ = query.args.get("with_") 10537 ctes = with_.pop() if with_ else None 10538 10539 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10540 if ctes: 10541 new_select.set("with_", ctes) 10542 10543 return new_select.with_(new_cte, as_=query, copy=False) 10544 10545 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10546 select = self._parse_select(consume_pipe=False) 10547 if not select: 10548 return query 10549 10550 return self._build_pipe_cte( 10551 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10552 ) 10553 10554 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10555 limit = self._parse_limit() 10556 offset = self._parse_offset() 10557 if isinstance(limit, exp.Limit): 10558 curr_limit = query.args.get("limit", limit) 10559 curr_is_limit_all = isinstance(curr_limit, exp.Limit) and curr_limit.is_limit_all 10560 10561 # LIMIT ALL means "no cap", so it's never tighter than a numeric limit. 10562 if limit.is_limit_all: 10563 should_replace = curr_is_limit_all 10564 else: 10565 should_replace = ( 10566 curr_is_limit_all or curr_limit.expression.to_py() >= limit.expression.to_py() 10567 ) 10568 10569 if should_replace: 10570 query.limit(limit, copy=False) 10571 if offset: 10572 curr_offset = query.args.get("offset") 10573 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10574 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10575 10576 return query 10577 10578 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10579 this = self._parse_disjunction() 10580 if self._match_text_seq("GROUP", "AND", advance=False): 10581 return this 10582 10583 this = self._parse_alias(this) 10584 10585 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10586 return self._parse_ordered(lambda: this) 10587 10588 return this 10589 10590 def _parse_pipe_syntax_aggregate_group_order_by( 10591 self, query: exp.Select, group_by_exists: bool = True 10592 ) -> exp.Select: 10593 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10594 aggregates_or_groups, orders = [], [] 10595 for element in expr: 10596 if isinstance(element, exp.Ordered): 10597 this = element.this 10598 if isinstance(this, exp.Alias): 10599 element.set("this", this.args["alias"]) 10600 orders.append(element) 10601 else: 10602 this = element 10603 aggregates_or_groups.append(this) 10604 10605 if group_by_exists: 10606 query.select( 10607 *aggregates_or_groups, *query.expressions, append=False, copy=False 10608 ).group_by( 10609 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10610 copy=False, 10611 ) 10612 else: 10613 query.select(*aggregates_or_groups, append=False, copy=False) 10614 10615 if orders: 10616 return query.order_by(*orders, append=False, copy=False) 10617 10618 return query 10619 10620 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10621 self._match_text_seq("AGGREGATE") 10622 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10623 10624 if self._match(TokenType.GROUP_BY) or ( 10625 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10626 ): 10627 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10628 10629 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10630 10631 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10632 first_setop = self.parse_set_operation(this=query) 10633 if not first_setop: 10634 return None 10635 10636 def _parse_and_unwrap_query() -> exp.Expr | None: 10637 expr = self._parse_paren() 10638 return expr.assert_is(exp.Subquery).unnest() if expr else None 10639 10640 first_setop.this.pop() 10641 10642 setops = [ 10643 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10644 *self._parse_csv(_parse_and_unwrap_query), 10645 ] 10646 10647 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10648 with_ = query.args.get("with_") 10649 ctes = with_.pop() if with_ else None 10650 10651 if isinstance(first_setop, exp.Union): 10652 query = query.union(*setops, copy=False, **first_setop.args) 10653 elif isinstance(first_setop, exp.Except): 10654 query = query.except_(*setops, copy=False, **first_setop.args) 10655 else: 10656 query = query.intersect(*setops, copy=False, **first_setop.args) 10657 10658 query.set("with_", ctes) 10659 10660 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10661 10662 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10663 join = self._parse_join() 10664 if not join: 10665 return None 10666 10667 if isinstance(query, exp.Select): 10668 return query.join(join, copy=False) 10669 10670 return query 10671 10672 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10673 pivots = self._parse_pivots() 10674 if not pivots: 10675 return query 10676 10677 from_ = query.args.get("from_") 10678 if from_: 10679 from_.this.set("pivots", pivots) 10680 10681 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10682 10683 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10684 self._match_text_seq("EXTEND") 10685 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10686 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10687 10688 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10689 sample = self._parse_table_sample() 10690 10691 with_ = query.args.get("with_") 10692 if with_: 10693 with_.expressions[-1].this.set("sample", sample) 10694 else: 10695 query.set("sample", sample) 10696 10697 return query 10698 10699 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10700 if isinstance(query, exp.Subquery): 10701 query = exp.select("*").from_(query, copy=False) 10702 10703 if not query.args.get("from_"): 10704 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10705 10706 while self._match(TokenType.PIPE_GT): 10707 start_index = self._index 10708 start_text = self._curr.text.upper() 10709 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10710 if not parser: 10711 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10712 # keywords, making it tricky to disambiguate them without lookahead. The approach 10713 # here is to try and parse a set operation and if that fails, then try to parse a 10714 # join operator. If that fails as well, then the operator is not supported. 10715 parsed_query = self._parse_pipe_syntax_set_operator(query) 10716 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10717 if not parsed_query: 10718 self._retreat(start_index) 10719 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10720 break 10721 query = parsed_query 10722 else: 10723 query = parser(self, query) 10724 10725 return query 10726 10727 def _parse_declareitem(self) -> exp.DeclareItem | None: 10728 self._match_texts(("VAR", "VARIABLE")) 10729 10730 vars = self._parse_csv(self._parse_id_var) 10731 if not vars: 10732 return None 10733 10734 self._match(TokenType.ALIAS) 10735 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10736 default = ( 10737 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10738 ) and self._parse_bitwise() 10739 10740 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10741 10742 def _parse_declare(self) -> exp.Declare | exp.Command: 10743 start = self._prev 10744 replace = self._match_text_seq("OR", "REPLACE") 10745 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10746 10747 if not expressions or self._curr: 10748 return self._parse_as_command(start) 10749 10750 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10751 10752 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10753 exp_class = exp.Cast if strict else exp.TryCast 10754 10755 if exp_class == exp.TryCast: 10756 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10757 10758 return self.expression(exp_class(**kwargs)) 10759 10760 def _parse_json_value(self, json_path: bool = True) -> exp.JSONValue: 10761 this = self._parse_bitwise() 10762 self._match(TokenType.COMMA) 10763 path = self._parse_bitwise() 10764 10765 returning = self._match(TokenType.RETURNING) and self._parse_types() 10766 10767 return self.expression( 10768 exp.JSONValue( 10769 this=this, 10770 path=self.dialect.to_json_path(path) if json_path else path, 10771 returning=returning, 10772 on_condition=self._parse_on_condition(), 10773 ) 10774 ) 10775 10776 def _parse_group_concat(self) -> exp.Expr | None: 10777 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10778 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10779 concat_exprs = [ 10780 self.expression( 10781 exp.Concat( 10782 expressions=node.expressions, 10783 safe=True, 10784 coalesce=self.dialect.CONCAT_COALESCE, 10785 ) 10786 ) 10787 ] 10788 node.set("expressions", concat_exprs) 10789 return node 10790 if len(exprs) == 1: 10791 return exprs[0] 10792 return self.expression( 10793 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10794 ) 10795 10796 args = self._parse_csv(self._parse_lambda) 10797 10798 if args: 10799 order = args[-1] if isinstance(args[-1], exp.Order) else None 10800 10801 if order: 10802 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10803 # remove 'expr' from exp.Order and add it back to args 10804 args[-1] = order.this 10805 order.set("this", concat_exprs(order.this, args)) 10806 10807 this = order or concat_exprs(args[0], args) 10808 else: 10809 this = None 10810 10811 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10812 10813 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10814 10815 def _parse_initcap(self) -> exp.Initcap: 10816 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10817 10818 # attach dialect's default delimiters 10819 if expr.args.get("expression") is None: 10820 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10821 10822 return expr 10823 10824 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10825 if not self._match(TokenType.L_PAREN): 10826 self._retreat(self._index - 1) 10827 return None 10828 10829 op = "" 10830 while self._curr and not self._match(TokenType.R_PAREN): 10831 op += self._curr.text 10832 self._advance() 10833 10834 comments = self._prev_comments 10835 return self.expression( 10836 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10837 comments=comments, 10838 )
52def build_var_map(args: BuilderArgs) -> exp.StarMap | exp.VarMap: 53 if len(args) == 1 and args[0].is_star: 54 return exp.StarMap(this=args[0]) 55 56 keys: list[ExpOrStr] = [] 57 values: list[ExpOrStr] = [] 58 for i in range(0, len(args), 2): 59 keys.append(args[i]) 60 values.append(args[i + 1]) 61 62 return exp.VarMap(keys=exp.array(*keys, copy=False), values=exp.array(*values, copy=False))
86def binary_range_parser( 87 expr_type: Type[exp.Expr], reverse_args: bool = False 88) -> t.Callable[[Parser, exp.Expr | None], exp.Expr | None]: 89 def _parse_binary_range(self: Parser, this: exp.Expr | None) -> exp.Expr | None: 90 expression = self._parse_bitwise() 91 if reverse_args: 92 this, expression = expression, this 93 return self._parse_escape(self.expression(expr_type(this=this, expression=expression))) 94 95 return _parse_binary_range
98def build_logarithm(args: BuilderArgs, dialect: Dialect) -> exp.Func: 99 # Default argument order is base, expression 100 this = seq_get(args, 0) 101 expression = seq_get(args, 1) 102 103 if expression: 104 if not dialect.LOG_BASE_FIRST: 105 this, expression = expression, this 106 return exp.Log(this=this, expression=expression) 107 108 return (exp.Ln if dialect.parser_class.LOG_DEFAULTS_TO_LN else exp.Log)(this=this)
128def build_extract_json_with_path( 129 expr_type: Type[E], 130) -> t.Callable[[BuilderArgs, Dialect], E]: 131 def _builder(args: BuilderArgs, dialect: Dialect) -> E: 132 expression = expr_type( 133 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 134 ) 135 if len(args) > 2 and expr_type is exp.JSONExtract: 136 expression.set("expressions", args[2:]) 137 if expr_type is exp.JSONExtractScalar: 138 expression.set("scalar_only", dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY) 139 140 return expression 141 142 return _builder
145def build_mod(args: BuilderArgs) -> exp.Mod: 146 this = seq_get(args, 0) 147 expression = seq_get(args, 1) 148 149 # Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7 150 this = exp.Paren(this=this) if isinstance(this, exp.Binary) else this 151 expression = exp.Paren(this=expression) if isinstance(expression, exp.Binary) else expression 152 153 return exp.Mod(this=this, expression=expression)
165def build_array_constructor( 166 exp_class: Type[E], args: list[t.Any], bracket_kind: TokenType, dialect: Dialect 167) -> exp.Expr: 168 array_exp = exp_class(expressions=args) 169 170 if exp_class == exp.Array: 171 array_exp.set("value_constructor", len(args) == 1 and isinstance(args[0], exp.Query)) 172 if dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS: 173 array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET) 174 175 return array_exp
178def build_convert_timezone( 179 args: BuilderArgs, default_source_tz: str | None = None 180) -> exp.ConvertTimezone | exp.Anonymous: 181 if len(args) == 2: 182 source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None 183 return exp.ConvertTimezone( 184 source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1) 185 ) 186 187 return exp.ConvertTimezone.from_arg_list(args)
190def build_trim(args: BuilderArgs, is_left: bool = True, reverse_args: bool = False) -> exp.Trim: 191 this, expression = seq_get(args, 0), seq_get(args, 1) 192 193 if expression and reverse_args: 194 this, expression = expression, this 195 196 return exp.Trim(this=this, expression=expression, position="LEADING" if is_left else "TRAILING")
213def build_array_append(args: BuilderArgs, dialect: Dialect) -> exp.ArrayAppend: 214 """ 215 Builds ArrayAppend with NULL propagation semantics based on the dialect configuration. 216 217 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 218 Others (DuckDB, PostgreSQL) create a new single-element array instead. 219 220 Args: 221 args: Function arguments [array, element] 222 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 223 224 Returns: 225 ArrayAppend expression with appropriate null_propagation flag 226 """ 227 return exp.ArrayAppend( 228 this=seq_get(args, 0), 229 expression=seq_get(args, 1), 230 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 231 )
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
234def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend: 235 """ 236 Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration. 237 238 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 239 Others (DuckDB, PostgreSQL) create a new single-element array instead. 240 241 Args: 242 args: Function arguments [array, element] 243 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 244 245 Returns: 246 ArrayPrepend expression with appropriate null_propagation flag 247 """ 248 return exp.ArrayPrepend( 249 this=seq_get(args, 0), 250 expression=seq_get(args, 1), 251 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 252 )
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
255def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat: 256 """ 257 Builds ArrayConcat with NULL propagation semantics based on the dialect configuration. 258 259 Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. 260 Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation. 261 262 Args: 263 args: Function arguments [array1, array2, ...] (variadic) 264 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 265 266 Returns: 267 ArrayConcat expression with appropriate null_propagation flag 268 """ 269 return exp.ArrayConcat( 270 this=seq_get(args, 0), 271 expressions=args[1:], 272 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 273 )
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
276def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove: 277 """ 278 Builds ArrayRemove with NULL propagation semantics based on the dialect configuration. 279 280 Some dialects (Snowflake) return NULL when the removal value is NULL. 281 Others (DuckDB) may return empty array due to NULL comparison semantics. 282 283 Args: 284 args: Function arguments [array, value_to_remove] 285 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 286 287 Returns: 288 ArrayRemove expression with appropriate null_propagation flag 289 """ 290 return exp.ArrayRemove( 291 this=seq_get(args, 0), 292 expression=seq_get(args, 1), 293 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 294 )
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
325def build_json_extract_scalar( 326 self: Parser, this: exp.Expr, path: exp.Expr 327) -> exp.JSONExtractScalar: 328 return self.expression( 329 exp.JSONExtractScalar( 330 this=this, 331 expression=self.dialect.to_json_path(path), 332 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 333 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 334 ) 335 )
357class IsPredicatePrecedence(Enum): 358 HIGH = auto() 359 COMPARISON = auto() 360 EQUALITY = auto() 361 LOW = auto()
An enumeration.
364class Parser: 365 """ 366 Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree. 367 368 Args: 369 error_level: The desired error level. 370 Default: ErrorLevel.IMMEDIATE 371 error_message_context: The amount of context to capture from a query string when displaying 372 the error message (in number of characters). 373 Default: 100 374 max_errors: Maximum number of error messages to include in a raised ParseError. 375 This is only relevant if error_level is ErrorLevel.RAISE. 376 Default: 3 377 max_nodes: Maximum number of AST nodes to prevent memory exhaustion. 378 Set to -1 (default) to disable the check. 379 """ 380 381 __slots__ = ( 382 "error_level", 383 "error_message_context", 384 "max_errors", 385 "max_nodes", 386 "dialect", 387 "sql", 388 "errors", 389 "_tokens", 390 "_index", 391 "_curr", 392 "_next", 393 "_prev", 394 "_prev_comments", 395 "_pipe_cte_counter", 396 "_chunks", 397 "_chunk_index", 398 "_tokens_size", 399 "_node_count", 400 ) 401 402 FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = { 403 **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()}, 404 **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce), 405 "ARRAY": lambda args, dialect: exp.Array(expressions=args), 406 "SORT_ARRAY": build_sort_array, 407 "ARRAYAGG": lambda args, dialect: exp.ArrayAgg( 408 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 409 ), 410 "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg( 411 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 412 ), 413 "ARRAY_APPEND": build_array_append, 414 "ARRAY_CAT": build_array_concat, 415 "ARRAY_CONCAT": build_array_concat, 416 "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args), 417 "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args), 418 "ARRAY_PREPEND": build_array_prepend, 419 "ARRAY_REMOVE": build_array_remove, 420 "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True), 421 "CONCAT": lambda args, dialect: exp.Concat( 422 expressions=args, 423 safe=not dialect.STRICT_STRING_CONCAT, 424 coalesce=dialect.CONCAT_COALESCE, 425 ), 426 "CONCAT_WS": lambda args, dialect: exp.ConcatWs( 427 expressions=args, 428 safe=not dialect.STRICT_STRING_CONCAT, 429 coalesce=dialect.CONCAT_WS_COALESCE, 430 ), 431 "CONVERT_TIMEZONE": build_convert_timezone, 432 "DATE_TO_DATE_STR": lambda args: exp.Cast( 433 this=seq_get(args, 0), 434 to=exp.DataType(this=exp.DType.TEXT), 435 ), 436 "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray( 437 start=seq_get(args, 0), 438 end=seq_get(args, 1), 439 step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")), 440 ), 441 "GENERATE_UUID": lambda args, dialect: exp.Uuid( 442 is_string=dialect.UUID_IS_STRING_TYPE or None 443 ), 444 "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)), 445 "GREATEST": lambda args, dialect: exp.Greatest( 446 this=seq_get(args, 0), 447 expressions=args[1:], 448 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 449 ), 450 "LEAST": lambda args, dialect: exp.Least( 451 this=seq_get(args, 0), 452 expressions=args[1:], 453 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 454 ), 455 "HEX": build_hex, 456 "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract), 457 "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar), 458 "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar), 459 "JSON_KEYS": lambda args, dialect: exp.JSONKeys( 460 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 461 ), 462 "LIKE": build_like, 463 "LOG": build_logarithm, 464 "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)), 465 "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)), 466 "LOWER": build_lower, 467 "LPAD": lambda args: build_pad(args), 468 "LEFTPAD": lambda args: build_pad(args), 469 "LTRIM": lambda args: build_trim(args), 470 "MOD": build_mod, 471 "RIGHTPAD": lambda args: build_pad(args, is_left=False), 472 "RPAD": lambda args: build_pad(args, is_left=False), 473 "RTRIM": lambda args: build_trim(args, is_left=False), 474 "SCOPE_RESOLUTION": lambda args: ( 475 exp.ScopeResolution(expression=seq_get(args, 0)) 476 if len(args) != 2 477 else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1)) 478 ), 479 "STRPOS": exp.StrPosition.from_arg_list, 480 "CHARINDEX": lambda args: build_locate_strposition(args), 481 "INSTR": exp.StrPosition.from_arg_list, 482 "LOCATE": lambda args: build_locate_strposition(args), 483 "TIME_TO_TIME_STR": lambda args: exp.Cast( 484 this=seq_get(args, 0), 485 to=exp.DataType(this=exp.DType.TEXT), 486 ), 487 "TO_HEX": build_hex, 488 "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring( 489 this=exp.Cast( 490 this=seq_get(args, 0), 491 to=exp.DataType(this=exp.DType.TEXT), 492 ), 493 start=exp.Literal.number(1), 494 length=exp.Literal.number(10), 495 ), 496 "UNNEST": lambda args: exp.Unnest(expressions=args), 497 "UPPER": build_upper, 498 "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None), 499 "UUID_STRING": lambda args, dialect: exp.Uuid( 500 this=seq_get(args, 0), 501 name=seq_get(args, 1), 502 is_string=dialect.UUID_IS_STRING_TYPE or None, 503 ), 504 "VAR_MAP": build_var_map, 505 } 506 507 NO_PAREN_FUNCTIONS: t.ClassVar[dict] = { 508 TokenType.CURRENT_DATE: exp.CurrentDate, 509 TokenType.CURRENT_DATETIME: exp.CurrentDate, 510 TokenType.CURRENT_TIME: exp.CurrentTime, 511 TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp, 512 TokenType.CURRENT_USER: exp.CurrentUser, 513 TokenType.CURRENT_ROLE: exp.CurrentRole, 514 } 515 516 STRUCT_TYPE_TOKENS: t.ClassVar = { 517 TokenType.NESTED, 518 TokenType.OBJECT, 519 TokenType.STRUCT, 520 TokenType.UNION, 521 } 522 523 NESTED_TYPE_TOKENS: t.ClassVar = { 524 TokenType.ARRAY, 525 TokenType.LIST, 526 TokenType.LOWCARDINALITY, 527 TokenType.MAP, 528 TokenType.NULLABLE, 529 TokenType.RANGE, 530 *STRUCT_TYPE_TOKENS, 531 } 532 533 ENUM_TYPE_TOKENS: t.ClassVar = { 534 TokenType.DYNAMIC, 535 TokenType.ENUM, 536 TokenType.ENUM8, 537 TokenType.ENUM16, 538 } 539 540 AGGREGATE_TYPE_TOKENS: t.ClassVar = { 541 TokenType.AGGREGATEFUNCTION, 542 TokenType.SIMPLEAGGREGATEFUNCTION, 543 } 544 545 TYPE_TOKENS: t.ClassVar = { 546 TokenType.BIT, 547 TokenType.BOOLEAN, 548 TokenType.TINYINT, 549 TokenType.UTINYINT, 550 TokenType.SMALLINT, 551 TokenType.USMALLINT, 552 TokenType.INT, 553 TokenType.UINT, 554 TokenType.BIGINT, 555 TokenType.UBIGINT, 556 TokenType.BIGNUM, 557 TokenType.INT128, 558 TokenType.UINT128, 559 TokenType.INT256, 560 TokenType.UINT256, 561 TokenType.MEDIUMINT, 562 TokenType.UMEDIUMINT, 563 TokenType.FIXEDSTRING, 564 TokenType.FLOAT, 565 TokenType.DOUBLE, 566 TokenType.UDOUBLE, 567 TokenType.CHAR, 568 TokenType.NCHAR, 569 TokenType.VARCHAR, 570 TokenType.NVARCHAR, 571 TokenType.BPCHAR, 572 TokenType.TEXT, 573 TokenType.MEDIUMTEXT, 574 TokenType.LONGTEXT, 575 TokenType.BLOB, 576 TokenType.MEDIUMBLOB, 577 TokenType.LONGBLOB, 578 TokenType.BINARY, 579 TokenType.VARBINARY, 580 TokenType.JSON, 581 TokenType.JSONB, 582 TokenType.INTERVAL, 583 TokenType.TINYBLOB, 584 TokenType.TINYTEXT, 585 TokenType.TIME, 586 TokenType.TIMETZ, 587 TokenType.TIME_NS, 588 TokenType.TIMESTAMP, 589 TokenType.TIMESTAMP_S, 590 TokenType.TIMESTAMP_MS, 591 TokenType.TIMESTAMP_NS, 592 TokenType.TIMESTAMPTZ, 593 TokenType.TIMESTAMPLTZ, 594 TokenType.TIMESTAMPNTZ, 595 TokenType.DATETIME, 596 TokenType.DATETIME2, 597 TokenType.DATETIME64, 598 TokenType.SMALLDATETIME, 599 TokenType.DATE, 600 TokenType.DATE32, 601 TokenType.INT4RANGE, 602 TokenType.INT4MULTIRANGE, 603 TokenType.INT8RANGE, 604 TokenType.INT8MULTIRANGE, 605 TokenType.NUMRANGE, 606 TokenType.NUMMULTIRANGE, 607 TokenType.TSRANGE, 608 TokenType.TSMULTIRANGE, 609 TokenType.TSTZRANGE, 610 TokenType.TSTZMULTIRANGE, 611 TokenType.DATERANGE, 612 TokenType.DATEMULTIRANGE, 613 TokenType.DECIMAL, 614 TokenType.DECIMAL32, 615 TokenType.DECIMAL64, 616 TokenType.DECIMAL128, 617 TokenType.DECIMAL256, 618 TokenType.DECFLOAT, 619 TokenType.UDECIMAL, 620 TokenType.BIGDECIMAL, 621 TokenType.UUID, 622 TokenType.GEOGRAPHY, 623 TokenType.GEOGRAPHYPOINT, 624 TokenType.GEOMETRY, 625 TokenType.POINT, 626 TokenType.RING, 627 TokenType.LINESTRING, 628 TokenType.MULTILINESTRING, 629 TokenType.POLYGON, 630 TokenType.MULTIPOLYGON, 631 TokenType.HLLSKETCH, 632 TokenType.HSTORE, 633 TokenType.PSEUDO_TYPE, 634 TokenType.SUPER, 635 TokenType.SERIAL, 636 TokenType.SMALLSERIAL, 637 TokenType.BIGSERIAL, 638 TokenType.XML, 639 TokenType.YEAR, 640 TokenType.USERDEFINED, 641 TokenType.MONEY, 642 TokenType.SMALLMONEY, 643 TokenType.ROWVERSION, 644 TokenType.IMAGE, 645 TokenType.VARIANT, 646 TokenType.VECTOR, 647 TokenType.VOID, 648 TokenType.OBJECT, 649 TokenType.OBJECT_IDENTIFIER, 650 TokenType.INET, 651 TokenType.IPADDRESS, 652 TokenType.IPPREFIX, 653 TokenType.IPV4, 654 TokenType.IPV6, 655 TokenType.UNKNOWN, 656 TokenType.NOTHING, 657 TokenType.NULL, 658 TokenType.NAME, 659 TokenType.TDIGEST, 660 TokenType.DYNAMIC, 661 *ENUM_TYPE_TOKENS, 662 *NESTED_TYPE_TOKENS, 663 *AGGREGATE_TYPE_TOKENS, 664 } 665 666 SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = { 667 TokenType.BIGINT: TokenType.UBIGINT, 668 TokenType.INT: TokenType.UINT, 669 TokenType.MEDIUMINT: TokenType.UMEDIUMINT, 670 TokenType.SMALLINT: TokenType.USMALLINT, 671 TokenType.TINYINT: TokenType.UTINYINT, 672 TokenType.DECIMAL: TokenType.UDECIMAL, 673 TokenType.DOUBLE: TokenType.UDOUBLE, 674 } 675 676 SUBQUERY_PREDICATES: t.ClassVar = { 677 TokenType.ANY: exp.Any, 678 TokenType.ALL: exp.All, 679 TokenType.EXISTS: exp.Exists, 680 TokenType.SOME: exp.Any, 681 } 682 683 SUBQUERY_TOKENS: t.ClassVar = { 684 TokenType.SELECT, 685 TokenType.WITH, 686 TokenType.FROM, 687 } 688 689 RESERVED_TOKENS: t.ClassVar = { 690 *Tokenizer.SINGLE_TOKENS.values(), 691 TokenType.SELECT, 692 } - {TokenType.IDENTIFIER} 693 694 # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers, 695 # string literals), so they must never be treated as keywords when matching by text 696 TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset( 697 { 698 TokenType.BIT_STRING, 699 TokenType.BYTE_STRING, 700 TokenType.HEREDOC_STRING, 701 TokenType.HEX_STRING, 702 TokenType.IDENTIFIER, 703 TokenType.NATIONAL_RAW_STRING, 704 TokenType.NATIONAL_STRING, 705 TokenType.RAW_STRING, 706 TokenType.STRING, 707 TokenType.UNICODE_STRING, 708 } 709 ) 710 711 DB_CREATABLES: t.ClassVar = { 712 TokenType.DATABASE, 713 TokenType.DICTIONARY, 714 TokenType.FILE_FORMAT, 715 TokenType.MODEL, 716 TokenType.NAMESPACE, 717 TokenType.SCHEMA, 718 TokenType.SEMANTIC_VIEW, 719 TokenType.SEQUENCE, 720 TokenType.SINK, 721 TokenType.SOURCE, 722 TokenType.STAGE, 723 TokenType.STORAGE_INTEGRATION, 724 TokenType.STREAMLIT, 725 TokenType.TABLE, 726 TokenType.TAG, 727 TokenType.VIEW, 728 TokenType.WAREHOUSE, 729 } 730 731 CREATABLES: t.ClassVar = { 732 TokenType.COLUMN, 733 TokenType.CONSTRAINT, 734 TokenType.FOREIGN_KEY, 735 TokenType.FUNCTION, 736 TokenType.INDEX, 737 TokenType.PROCEDURE, 738 TokenType.TRIGGER, 739 TokenType.TYPE, 740 *DB_CREATABLES, 741 } 742 743 TRIGGER_EVENTS: t.ClassVar = { 744 TokenType.INSERT, 745 TokenType.UPDATE, 746 TokenType.DELETE, 747 TokenType.TRUNCATE, 748 } 749 750 ALTERABLES: t.ClassVar = { 751 TokenType.INDEX, 752 TokenType.TABLE, 753 TokenType.VIEW, 754 TokenType.SESSION, 755 } 756 757 # Tokens that can represent identifiers 758 ID_VAR_TOKENS: t.ClassVar[set] = { 759 TokenType.ALL, 760 TokenType.ANALYZE, 761 TokenType.ATTACH, 762 TokenType.VAR, 763 TokenType.ANTI, 764 TokenType.APPLY, 765 TokenType.ASC, 766 TokenType.ASOF, 767 TokenType.AUTO_INCREMENT, 768 TokenType.BEGIN, 769 TokenType.BPCHAR, 770 TokenType.CACHE, 771 TokenType.CASE, 772 TokenType.COLLATE, 773 TokenType.COMMAND, 774 TokenType.COMMENT, 775 TokenType.COMMIT, 776 TokenType.CONSTRAINT, 777 TokenType.COPY, 778 TokenType.CUBE, 779 TokenType.CURRENT_SCHEMA, 780 TokenType.DECLARE, 781 TokenType.DEFAULT, 782 TokenType.DELETE, 783 TokenType.DESC, 784 TokenType.DESCRIBE, 785 TokenType.DETACH, 786 TokenType.DICTIONARY, 787 TokenType.DIV, 788 TokenType.END, 789 TokenType.EXECUTE, 790 TokenType.EXPORT, 791 TokenType.ESCAPE, 792 TokenType.FALSE, 793 TokenType.FIRST, 794 TokenType.FILE, 795 TokenType.FILTER, 796 TokenType.FINAL, 797 TokenType.FORMAT, 798 TokenType.FULL, 799 TokenType.GET, 800 TokenType.IDENTIFIER, 801 TokenType.INOUT, 802 TokenType.IS, 803 TokenType.ISNULL, 804 TokenType.INTERVAL, 805 TokenType.KEEP, 806 TokenType.KILL, 807 TokenType.LEFT, 808 TokenType.LIMIT, 809 TokenType.LOAD, 810 TokenType.LOCK, 811 TokenType.MATCH, 812 TokenType.MERGE, 813 TokenType.NATURAL, 814 TokenType.NEXT, 815 TokenType.OFFSET, 816 TokenType.OPERATOR, 817 TokenType.ORDINALITY, 818 TokenType.OUT, 819 TokenType.OVER, 820 TokenType.OVERLAPS, 821 TokenType.OVERWRITE, 822 TokenType.PARTITION, 823 TokenType.PASTE, 824 TokenType.PERCENT, 825 TokenType.PIVOT, 826 TokenType.PROJECTION, 827 TokenType.PRAGMA, 828 TokenType.PUT, 829 TokenType.RANGE, 830 TokenType.RECURSIVE, 831 TokenType.REFERENCES, 832 TokenType.REFRESH, 833 TokenType.RENAME, 834 TokenType.REPLACE, 835 TokenType.RIGHT, 836 TokenType.ROLLUP, 837 TokenType.ROW, 838 TokenType.ROWS, 839 TokenType.SEMI, 840 TokenType.SET, 841 TokenType.SETTINGS, 842 TokenType.SHOW, 843 TokenType.STREAM, 844 TokenType.STREAMLIT, 845 TokenType.TEMPORARY, 846 TokenType.TOP, 847 TokenType.TRUE, 848 TokenType.TRUNCATE, 849 TokenType.UNIQUE, 850 TokenType.UNNEST, 851 TokenType.UNPIVOT, 852 TokenType.UPDATE, 853 TokenType.USE, 854 TokenType.VOLATILE, 855 TokenType.WINDOW, 856 TokenType.CURRENT_CATALOG, 857 TokenType.LOCALTIME, 858 TokenType.LOCALTIMESTAMP, 859 TokenType.SESSION_USER, 860 TokenType.STRAIGHT_JOIN, 861 *ALTERABLES, 862 *CREATABLES, 863 *SUBQUERY_PREDICATES, 864 *TYPE_TOKENS, 865 *NO_PAREN_FUNCTIONS, 866 } - {TokenType.UNION} 867 868 TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - { 869 TokenType.ANTI, 870 TokenType.ASOF, 871 TokenType.FULL, 872 TokenType.LEFT, 873 TokenType.LOCK, 874 TokenType.NATURAL, 875 TokenType.RIGHT, 876 TokenType.SEMI, 877 TokenType.WINDOW, 878 } 879 880 ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS 881 882 COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS 883 884 ARRAY_CONSTRUCTORS: t.ClassVar = { 885 "ARRAY": exp.Array, 886 "LIST": exp.List, 887 } 888 889 COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS} 890 891 UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET} 892 893 TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"} 894 895 # Tokens that indicate a simple column reference 896 IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER}) 897 898 BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE}) 899 900 # Postfix tokens that prevent the bare column fast path 901 COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 902 { 903 TokenType.L_PAREN, 904 TokenType.L_BRACKET, 905 TokenType.L_BRACE, 906 TokenType.COLON, 907 TokenType.JOIN_MARKER, 908 } 909 ) 910 911 TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 912 { 913 TokenType.L_PAREN, 914 TokenType.L_BRACKET, 915 TokenType.L_BRACE, 916 TokenType.PIVOT, 917 TokenType.UNPIVOT, 918 TokenType.TABLE_SAMPLE, 919 } 920 ) 921 922 FUNC_TOKENS: t.ClassVar = { 923 TokenType.COLLATE, 924 TokenType.COMMAND, 925 TokenType.CURRENT_DATE, 926 TokenType.CURRENT_DATETIME, 927 TokenType.CURRENT_SCHEMA, 928 TokenType.CURRENT_TIMESTAMP, 929 TokenType.CURRENT_TIME, 930 TokenType.CURRENT_USER, 931 TokenType.CURRENT_CATALOG, 932 TokenType.DECLARE, 933 TokenType.FILTER, 934 TokenType.FIRST, 935 TokenType.FORMAT, 936 TokenType.GET, 937 TokenType.GLOB, 938 TokenType.IDENTIFIER, 939 TokenType.INDEX, 940 TokenType.ISNULL, 941 TokenType.ILIKE, 942 TokenType.INSERT, 943 TokenType.LIKE, 944 TokenType.LOCALTIME, 945 TokenType.LOCALTIMESTAMP, 946 TokenType.MERGE, 947 TokenType.NEXT, 948 TokenType.OFFSET, 949 TokenType.PRIMARY_KEY, 950 TokenType.RANGE, 951 TokenType.REPLACE, 952 TokenType.RLIKE, 953 TokenType.ROW, 954 TokenType.SESSION_USER, 955 TokenType.UNNEST, 956 TokenType.VAR, 957 TokenType.LEFT, 958 TokenType.RIGHT, 959 TokenType.SEQUENCE, 960 TokenType.DATE, 961 TokenType.DATETIME, 962 TokenType.TABLE, 963 TokenType.TIMESTAMP, 964 TokenType.TIMESTAMPTZ, 965 TokenType.TRUNCATE, 966 TokenType.UTC_DATE, 967 TokenType.UTC_TIME, 968 TokenType.UTC_TIMESTAMP, 969 TokenType.WINDOW, 970 TokenType.XOR, 971 *TYPE_TOKENS, 972 *SUBQUERY_PREDICATES, 973 } 974 975 CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 976 TokenType.AND: exp.And, 977 } 978 979 ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 980 TokenType.COLON_EQ: exp.PropertyEQ, 981 } 982 983 DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 984 TokenType.OR: exp.Or, 985 } 986 987 EQUALITY: t.ClassVar = { 988 TokenType.EQ: exp.EQ, 989 TokenType.NEQ: exp.NEQ, 990 TokenType.NULLSAFE_EQ: exp.NullSafeEQ, 991 } 992 993 COMPARISON: t.ClassVar = { 994 TokenType.GT: exp.GT, 995 TokenType.GTE: exp.GTE, 996 TokenType.LT: exp.LT, 997 TokenType.LTE: exp.LTE, 998 } 999 1000 BITWISE: t.ClassVar = { 1001 TokenType.AMP: exp.BitwiseAnd, 1002 TokenType.CARET: exp.BitwiseXor, 1003 TokenType.PIPE: exp.BitwiseOr, 1004 } 1005 1006 TERM: t.ClassVar = { 1007 TokenType.DASH: exp.Sub, 1008 TokenType.PLUS: exp.Add, 1009 TokenType.COLLATE: exp.Collate, 1010 } 1011 1012 FACTOR: t.ClassVar = { 1013 TokenType.DIV: exp.IntDiv, 1014 TokenType.LR_ARROW: exp.Distance, 1015 TokenType.LLRR_ARROW: exp.DistanceNd, 1016 TokenType.MOD: exp.Mod, 1017 TokenType.SLASH: exp.Div, 1018 TokenType.STAR: exp.Mul, 1019 } 1020 1021 EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {} 1022 1023 TIMES: t.ClassVar = { 1024 TokenType.TIME, 1025 TokenType.TIMETZ, 1026 } 1027 1028 TIMESTAMPS: t.ClassVar = { 1029 TokenType.TIMESTAMP, 1030 TokenType.TIMESTAMPNTZ, 1031 TokenType.TIMESTAMPTZ, 1032 TokenType.TIMESTAMPLTZ, 1033 *TIMES, 1034 } 1035 1036 SET_OPERATIONS: t.ClassVar = { 1037 TokenType.UNION, 1038 TokenType.INTERSECT, 1039 TokenType.EXCEPT, 1040 } 1041 1042 JOIN_METHODS: t.ClassVar = { 1043 TokenType.ASOF, 1044 TokenType.NATURAL, 1045 TokenType.POSITIONAL, 1046 } 1047 1048 JOIN_SIDES: t.ClassVar = { 1049 TokenType.LEFT, 1050 TokenType.RIGHT, 1051 TokenType.FULL, 1052 } 1053 1054 JOIN_KINDS: t.ClassVar = { 1055 TokenType.ANTI, 1056 TokenType.CROSS, 1057 TokenType.INNER, 1058 TokenType.OUTER, 1059 TokenType.SEMI, 1060 TokenType.STRAIGHT_JOIN, 1061 } 1062 1063 JOIN_HINTS: t.ClassVar[set[str]] = set() 1064 1065 # Tokens that unambiguously end a table reference on the fast path 1066 TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset( 1067 { 1068 TokenType.COMMA, 1069 TokenType.GROUP_BY, 1070 TokenType.HAVING, 1071 TokenType.JOIN, 1072 TokenType.LIMIT, 1073 TokenType.ON, 1074 TokenType.ORDER_BY, 1075 TokenType.R_PAREN, 1076 TokenType.SEMICOLON, 1077 TokenType.SENTINEL, 1078 TokenType.WHERE, 1079 *SET_OPERATIONS, 1080 *JOIN_KINDS, 1081 *JOIN_METHODS, 1082 *JOIN_SIDES, 1083 } 1084 ) 1085 1086 LAMBDAS: t.ClassVar = { 1087 TokenType.ARROW: lambda self, expressions: self.expression( 1088 exp.Lambda( 1089 this=self._replace_lambda( 1090 self._parse_disjunction(), 1091 expressions, 1092 ), 1093 expressions=expressions, 1094 ) 1095 ), 1096 TokenType.FARROW: lambda self, expressions: self.expression( 1097 exp.Kwarg( 1098 this=exp.var(expressions[0].name), 1099 expression=self._parse_disjunction() or self._parse_select(), 1100 ) 1101 ), 1102 } 1103 1104 # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake 1105 TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False 1106 1107 LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN}) 1108 1109 COLUMN_OPERATORS: t.ClassVar = { 1110 TokenType.DOT: None, 1111 TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)), 1112 TokenType.DCOLON: lambda self, this, to: self.build_cast( 1113 strict=self.STRICT_CAST, this=this, to=to 1114 ), 1115 TokenType.ARROW: lambda self, this, path: self.expression( 1116 exp.JSONExtract( 1117 this=this, 1118 expression=self.dialect.to_json_path(path), 1119 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1120 ) 1121 ), 1122 TokenType.DARROW: lambda self, this, path: self.expression( 1123 exp.JSONExtractScalar( 1124 this=this, 1125 expression=self.dialect.to_json_path(path), 1126 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1127 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 1128 ) 1129 ), 1130 TokenType.HASH_ARROW: lambda self, this, path: self.expression( 1131 exp.JSONBExtract(this=this, expression=path) 1132 ), 1133 TokenType.DHASH_ARROW: lambda self, this, path: self.expression( 1134 exp.JSONBExtractScalar(this=this, expression=path) 1135 ), 1136 TokenType.PLACEHOLDER: lambda self, this, key: self.expression( 1137 exp.JSONBContainsTopKey(this=this, expression=key) 1138 ), 1139 } 1140 1141 # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator" 1142 # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs). 1143 JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {} 1144 1145 CAST_COLUMN_OPERATORS: t.ClassVar = { 1146 TokenType.DOTCOLON, 1147 TokenType.DCOLON, 1148 } 1149 1150 EXPRESSION_PARSERS: t.ClassVar = { 1151 exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), 1152 exp.Column: lambda self: self._parse_column(), 1153 exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()), 1154 exp.Condition: lambda self: self._parse_disjunction(), 1155 exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True), 1156 exp.Expr: lambda self: self._parse_expression(), 1157 exp.From: lambda self: self._parse_from(joins=True), 1158 exp.GrantPrincipal: lambda self: self._parse_grant_principal(), 1159 exp.GrantPrivilege: lambda self: self._parse_grant_privilege(), 1160 exp.Group: lambda self: self._parse_group(), 1161 exp.Having: lambda self: self._parse_having(), 1162 exp.Hint: lambda self: self._parse_hint_body(), 1163 exp.Identifier: lambda self: self._parse_id_var(), 1164 exp.Join: lambda self: self._parse_join(), 1165 exp.Lambda: lambda self: self._parse_lambda(), 1166 exp.Lateral: lambda self: self._parse_lateral(), 1167 exp.Limit: lambda self: self._parse_limit(), 1168 exp.Offset: lambda self: self._parse_offset(), 1169 exp.Order: lambda self: self._parse_order(), 1170 exp.Ordered: lambda self: self._parse_ordered(), 1171 exp.Properties: lambda self: self._parse_properties(), 1172 exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(), 1173 exp.Qualify: lambda self: self._parse_qualify(), 1174 exp.Returning: lambda self: self._parse_returning(), 1175 exp.Select: lambda self: self._parse_select(), 1176 exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), 1177 exp.Table: lambda self: self._parse_table_parts(), 1178 exp.TableAlias: lambda self: self._parse_table_alias(), 1179 exp.Tuple: lambda self: self._parse_value(values=False), 1180 exp.Whens: lambda self: self._parse_when_matched(), 1181 exp.Where: lambda self: self._parse_where(), 1182 exp.Window: lambda self: self._parse_named_window(), 1183 exp.With: lambda self: self._parse_with(), 1184 } 1185 1186 STATEMENT_PARSERS: t.ClassVar = { 1187 TokenType.ALTER: lambda self: self._parse_alter(), 1188 TokenType.ANALYZE: lambda self: self._parse_analyze(), 1189 TokenType.BEGIN: lambda self: self._parse_transaction(), 1190 TokenType.CACHE: lambda self: self._parse_cache(), 1191 TokenType.COMMENT: lambda self: self._parse_comment(), 1192 TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(), 1193 TokenType.COPY: lambda self: self._parse_copy(), 1194 TokenType.CREATE: lambda self: self._parse_create(), 1195 TokenType.DECLARE: lambda self: self._parse_declare(), 1196 TokenType.DELETE: lambda self: self._parse_delete(), 1197 TokenType.DESC: lambda self: self._parse_describe(), 1198 TokenType.DESCRIBE: lambda self: self._parse_describe(), 1199 TokenType.DROP: lambda self: self._parse_drop(), 1200 TokenType.GRANT: lambda self: self._parse_grant(), 1201 TokenType.REVOKE: lambda self: self._parse_revoke(), 1202 TokenType.INSERT: lambda self: self._parse_insert(), 1203 TokenType.KILL: lambda self: self._parse_kill(), 1204 TokenType.LOAD: lambda self: self._parse_load(), 1205 TokenType.MERGE: lambda self: self._parse_merge(), 1206 TokenType.PIVOT: lambda self: self._parse_simplified_pivot(), 1207 TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())), 1208 TokenType.REFRESH: lambda self: self._parse_refresh(), 1209 TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(), 1210 TokenType.SET: lambda self: self._parse_set(), 1211 TokenType.TRUNCATE: lambda self: self._parse_truncate_table(), 1212 TokenType.UNCACHE: lambda self: self._parse_uncache(), 1213 TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True), 1214 TokenType.UPDATE: lambda self: self._parse_update(), 1215 TokenType.USE: lambda self: self._parse_use(), 1216 TokenType.SEMICOLON: lambda self: exp.Semicolon(), 1217 } 1218 1219 UNARY_PARSERS: t.ClassVar = { 1220 TokenType.PLUS: lambda self: self._parse_unary(), # Unary + is handled as a no-op 1221 TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())), 1222 TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())), 1223 TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())), 1224 TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())), 1225 TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())), 1226 } 1227 1228 STRING_PARSERS: t.ClassVar = { 1229 TokenType.HEREDOC_STRING: lambda self, token: self.expression( 1230 exp.RawString(this=token.text), token 1231 ), 1232 **{ 1233 token_type: lambda self, token: self.expression(exp.National(this=token.text), token) 1234 for token_type in (TokenType.NATIONAL_STRING, TokenType.NATIONAL_RAW_STRING) 1235 }, 1236 TokenType.RAW_STRING: lambda self, token: self.expression( 1237 exp.RawString(this=token.text), token 1238 ), 1239 TokenType.STRING: lambda self, token: self.expression( 1240 exp.Literal(this=token.text, is_string=True), token 1241 ), 1242 TokenType.UNICODE_STRING: lambda self, token: self.expression( 1243 exp.UnicodeString( 1244 this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string() 1245 ), 1246 token, 1247 ), 1248 } 1249 1250 NUMERIC_PARSERS: t.ClassVar = { 1251 TokenType.BIT_STRING: lambda self, token: self.expression( 1252 exp.BitString(this=token.text), token 1253 ), 1254 TokenType.BYTE_STRING: lambda self, token: self.expression( 1255 exp.ByteString( 1256 this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None 1257 ), 1258 token, 1259 ), 1260 TokenType.HEX_STRING: lambda self, token: self.expression( 1261 exp.HexString( 1262 this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None 1263 ), 1264 token, 1265 ), 1266 TokenType.NUMBER: lambda self, token: self.expression( 1267 exp.Literal(this=token.text, is_string=False), token 1268 ), 1269 } 1270 1271 PRIMARY_PARSERS: t.ClassVar = { 1272 **STRING_PARSERS, 1273 **NUMERIC_PARSERS, 1274 TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token), 1275 TokenType.NULL: lambda self, _: self.expression(exp.Null()), 1276 TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)), 1277 TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)), 1278 TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(), 1279 TokenType.STAR: lambda self, _: self._parse_star_ops(), 1280 } 1281 1282 PLACEHOLDER_PARSERS: t.ClassVar = { 1283 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()), 1284 TokenType.PARAMETER: lambda self: self._parse_parameter(), 1285 TokenType.COLON: lambda self: ( 1286 self.expression(exp.Placeholder(this=self._prev.text)) 1287 if self._match_set(self.COLON_PLACEHOLDER_TOKENS) 1288 else None 1289 ), 1290 } 1291 1292 RANGE_PARSERS: t.ClassVar = { 1293 TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll), 1294 TokenType.BETWEEN: lambda self, this: self._parse_between(this), 1295 TokenType.GLOB: binary_range_parser(exp.Glob), 1296 TokenType.ILIKE: binary_range_parser(exp.ILike), 1297 TokenType.IN: lambda self, this: self._parse_in(this), 1298 TokenType.IRLIKE: binary_range_parser(exp.RegexpILike), 1299 TokenType.IS: lambda self, this: self._parse_is(this), 1300 TokenType.LIKE: binary_range_parser(exp.Like), 1301 TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy), 1302 TokenType.OVERLAPS: binary_range_parser(exp.Overlaps), 1303 TokenType.RLIKE: binary_range_parser(exp.RegexpLike), 1304 TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo), 1305 TokenType.FOR: lambda self, this: self._parse_comprehension(this), 1306 TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys), 1307 TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys), 1308 TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath), 1309 TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists), 1310 TokenType.ADJACENT: binary_range_parser(exp.Adjacent), 1311 TokenType.OPERATOR: lambda self, this: self._parse_operator(this), 1312 TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft), 1313 TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight), 1314 TokenType.CARET_AT: binary_range_parser(exp.StartsWith), 1315 } 1316 1317 PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = { 1318 "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query), 1319 "AS": lambda self, query: self._build_pipe_cte( 1320 query, [exp.Star()], self._parse_table_alias() 1321 ), 1322 "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False), 1323 "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query), 1324 "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query), 1325 "ORDER BY": lambda self, query: query.order_by( 1326 self._parse_order(), append=False, copy=False 1327 ), 1328 "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1329 "SELECT": lambda self, query: self._parse_pipe_syntax_select(query), 1330 "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query), 1331 "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1332 "WHERE": lambda self, query: query.where(self._parse_where(), copy=False), 1333 } 1334 1335 PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1336 "ALLOWED_VALUES": lambda self: self.expression( 1337 exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary)) 1338 ), 1339 "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty), 1340 "AUTO": lambda self: self._parse_auto_property(), 1341 "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty), 1342 "BACKUP": lambda self: self.expression( 1343 exp.BackupProperty(this=self._parse_var(any_token=True)) 1344 ), 1345 "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(), 1346 "CALLED": lambda self: self._parse_called_on_null_input_property(), 1347 "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs), 1348 "CHECKSUM": lambda self: self._parse_checksum(), 1349 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1350 "CLUSTERED": lambda self: self._parse_clustered_by(), 1351 "COLLATE": lambda self, **kwargs: self._parse_property_assignment( 1352 exp.CollateProperty, **kwargs 1353 ), 1354 "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty), 1355 "CONTAINS": lambda self: self._parse_contains_property(), 1356 "COPY": lambda self: self._parse_copy_property(), 1357 "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs), 1358 "DATA_DELETION": lambda self: self._parse_data_deletion_property(), 1359 "DEFINER": lambda self: self._parse_definer(), 1360 "DETERMINISTIC": lambda self: self.expression( 1361 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1362 ), 1363 "DISTRIBUTED": lambda self: self._parse_distributed_property(), 1364 "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty), 1365 "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()), 1366 "DISTKEY": lambda self: self._parse_distkey(), 1367 "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty), 1368 "EMPTY": lambda self: self.expression(exp.EmptyProperty()), 1369 "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty), 1370 "ENVIRONMENT": lambda self: self.expression( 1371 exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment)) 1372 ), 1373 "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty), 1374 "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty), 1375 "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()), 1376 "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs), 1377 "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1378 "FREESPACE": lambda self: self._parse_freespace(), 1379 "GLOBAL": lambda self: self.expression(exp.GlobalProperty()), 1380 "HEAP": lambda self: self.expression(exp.HeapProperty()), 1381 "ICEBERG": lambda self: self.expression(exp.IcebergProperty()), 1382 "IMMUTABLE": lambda self: self.expression( 1383 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1384 ), 1385 "INHERITS": lambda self: self.expression( 1386 exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table)) 1387 ), 1388 "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())), 1389 "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs), 1390 "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty), 1391 "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"), 1392 "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"), 1393 "LIKE": lambda self: self._parse_create_like(), 1394 "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty), 1395 "LOCK": lambda self: self._parse_locking(), 1396 "LOCKING": lambda self: self._parse_locking(), 1397 "LOG": lambda self, **kwargs: self._parse_log(**kwargs), 1398 "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()), 1399 "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs), 1400 "MODIFIES": lambda self: self._parse_modifies_property(), 1401 "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)), 1402 "NO": lambda self: self._parse_no_property(), 1403 "ON": lambda self: self._parse_on_property(), 1404 "ORDER BY": lambda self: self._parse_order(skip_order_token=True), 1405 "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())), 1406 "PARTITION": lambda self: self._parse_partitioned_of(), 1407 "PARTITION BY": lambda self: self._parse_partitioned_by(), 1408 "PARTITIONED BY": lambda self: self._parse_partitioned_by(), 1409 "PARTITIONED_BY": lambda self: self._parse_partitioned_by(), 1410 "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True), 1411 "RANGE": lambda self: self._parse_dict_range(this="RANGE"), 1412 "READS": lambda self: self._parse_reads_property(), 1413 "REMOTE": lambda self: self._parse_remote_with_connection(), 1414 "RETURNS": lambda self: self._parse_returns(), 1415 "STRICT": lambda self: self.expression(exp.StrictProperty()), 1416 "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()), 1417 "ROW": lambda self: self._parse_row(), 1418 "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty), 1419 "SAMPLE": lambda self: self.expression( 1420 exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise()) 1421 ), 1422 "SECURE": lambda self: self.expression(exp.SecureProperty()), 1423 "SECURITY": lambda self: self._parse_sql_security(), 1424 "SQL SECURITY": lambda self: self._parse_sql_security(), 1425 "SET": lambda self: self.expression(exp.SetProperty(multi=False)), 1426 "SETTINGS": lambda self: self._parse_settings_property(), 1427 "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty), 1428 "SORTKEY": lambda self: self._parse_sortkey(), 1429 "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"), 1430 "STABLE": lambda self: self.expression( 1431 exp.StabilityProperty(this=exp.Literal.string("STABLE")) 1432 ), 1433 "STORED": lambda self: self._parse_stored(), 1434 "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(), 1435 "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(), 1436 "TEMP": lambda self: self.expression(exp.TemporaryProperty()), 1437 "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()), 1438 "TO": lambda self: self._parse_to_table(), 1439 "TRANSIENT": lambda self: self.expression(exp.TransientProperty()), 1440 "TRANSFORM": lambda self: self.expression( 1441 exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression)) 1442 ), 1443 "TTL": lambda self: self._parse_ttl(), 1444 "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1445 "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()), 1446 "VOLATILE": lambda self: self._parse_volatile_property(), 1447 "WITH": lambda self: self._parse_with_property(), 1448 } 1449 1450 CONSTRAINT_PARSERS: t.ClassVar = { 1451 "AUTOINCREMENT": lambda self: self._parse_auto_increment(), 1452 "AUTO_INCREMENT": lambda self: self._parse_auto_increment(), 1453 "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)), 1454 "CHECK": lambda self: self._parse_check_constraint(), 1455 "COLLATE": lambda self: self.expression( 1456 exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column()) 1457 ), 1458 "COMMENT": lambda self: self.expression( 1459 exp.CommentColumnConstraint(this=self._parse_string()) 1460 ), 1461 "COMPRESS": lambda self: self._parse_compress(), 1462 "CLUSTERED": lambda self: self.expression( 1463 exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1464 ), 1465 "NONCLUSTERED": lambda self: self.expression( 1466 exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1467 ), 1468 "DEFAULT": lambda self: self.expression( 1469 exp.DefaultColumnConstraint(this=self._parse_bitwise()) 1470 ), 1471 "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())), 1472 "EPHEMERAL": lambda self: self.expression( 1473 exp.EphemeralColumnConstraint(this=self._parse_bitwise()) 1474 ), 1475 "EXCLUDE": lambda self: self.expression( 1476 exp.ExcludeColumnConstraint(this=self._parse_index_params()) 1477 ), 1478 "FOREIGN KEY": lambda self: self._parse_foreign_key(), 1479 "FORMAT": lambda self: self.expression( 1480 exp.DateFormatColumnConstraint(this=self._parse_var_or_string()) 1481 ), 1482 "GENERATED": lambda self: self._parse_generated_as_identity(), 1483 "IDENTITY": lambda self: self._parse_auto_increment(), 1484 "INLINE": lambda self: self._parse_inline(), 1485 "LIKE": lambda self: self._parse_create_like(), 1486 "NOT": lambda self: self._parse_not_constraint(), 1487 "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)), 1488 "ON": lambda self: ( 1489 ( 1490 self._match(TokenType.UPDATE) 1491 and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function())) 1492 ) 1493 or self.expression(exp.OnProperty(this=self._parse_id_var())) 1494 ), 1495 "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())), 1496 "PERIOD": lambda self: self._parse_period_for_system_time(), 1497 "PRIMARY KEY": lambda self: self._parse_primary_key(), 1498 "REFERENCES": lambda self: self._parse_references(match=False), 1499 "TITLE": lambda self: self.expression( 1500 exp.TitleColumnConstraint(this=self._parse_var_or_string()) 1501 ), 1502 "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])), 1503 "UNIQUE": lambda self: self._parse_unique(), 1504 "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()), 1505 "WITH": lambda self: self.expression( 1506 exp.Properties(expressions=self._parse_wrapped_properties()) 1507 ), 1508 "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1509 "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1510 } 1511 1512 def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None: 1513 if not self._match(TokenType.L_PAREN, advance=False): 1514 # Partitioning by bucket or truncate follows the syntax: 1515 # PARTITION BY (BUCKET(..) | TRUNCATE(..)) 1516 # If we don't have parenthesis after each keyword, we should instead parse this as an identifier 1517 self._retreat(self._index - 1) 1518 return None 1519 1520 klass = ( 1521 exp.PartitionedByBucket 1522 if self._prev.text.upper() == "BUCKET" 1523 else exp.PartitionByTruncate 1524 ) 1525 1526 args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column()) 1527 this, expression = seq_get(args, 0), seq_get(args, 1) 1528 1529 if isinstance(this, exp.Literal): 1530 # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order 1531 # - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)` 1532 # - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)` 1533 # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)` 1534 # 1535 # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning 1536 # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties 1537 this, expression = expression, this 1538 1539 return self.expression(klass(this=this, expression=expression)) 1540 1541 ALTER_PARSERS: t.ClassVar = { 1542 "ADD": lambda self: self._parse_alter_table_add(), 1543 "AS": lambda self: self._parse_select(), 1544 "ALTER": lambda self: self._parse_alter_table_alter(), 1545 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1546 "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())), 1547 "DROP": lambda self: self._parse_alter_table_drop(), 1548 "RENAME": lambda self: self._parse_alter_table_rename(), 1549 "SET": lambda self: self._parse_alter_table_set(), 1550 "SWAP": lambda self: self.expression( 1551 exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True)) 1552 ), 1553 } 1554 1555 ALTER_ALTER_PARSERS: t.ClassVar = { 1556 "DISTKEY": lambda self: self._parse_alter_diststyle(), 1557 "DISTSTYLE": lambda self: self._parse_alter_diststyle(), 1558 "SORTKEY": lambda self: self._parse_alter_sortkey(), 1559 "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True), 1560 } 1561 1562 SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = { 1563 "CHECK", 1564 "EXCLUDE", 1565 "FOREIGN KEY", 1566 "LIKE", 1567 "PERIOD", 1568 "PRIMARY KEY", 1569 "UNIQUE", 1570 "BUCKET", 1571 "TRUNCATE", 1572 } 1573 1574 # Kinds that can't be a constraint name, so `CONSTRAINT` directly followed by one is unnamed 1575 RESERVED_CONSTRAINT_KINDS: t.ClassVar[set[str]] = set() 1576 1577 NO_PAREN_FUNCTION_PARSERS: t.ClassVar = { 1578 "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())), 1579 "CASE": lambda self: self._parse_case(), 1580 "CONNECT_BY_ROOT": lambda self: self.expression( 1581 exp.ConnectByRoot(this=self._parse_column()) 1582 ), 1583 "IF": lambda self: self._parse_if(), 1584 } 1585 1586 INVALID_FUNC_NAME_TOKENS: t.ClassVar = { 1587 TokenType.IDENTIFIER, 1588 TokenType.STRING, 1589 } 1590 1591 FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"} 1592 1593 KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice) 1594 1595 FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1596 **{ 1597 name: lambda self: self._parse_distinct_arg_function(exp.ArgMax) 1598 for name in exp.ArgMax.sql_names() 1599 }, 1600 **{ 1601 name: lambda self: self._parse_distinct_arg_function(exp.ArgMin) 1602 for name in exp.ArgMin.sql_names() 1603 }, 1604 "CAST": lambda self: self._parse_cast(self.STRICT_CAST), 1605 "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), 1606 "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), 1607 "CHAR": lambda self: self._parse_char(), 1608 "CHR": lambda self: self._parse_char(), 1609 "DECODE": lambda self: self._parse_decode(), 1610 "EXTRACT": lambda self: self._parse_extract(), 1611 "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), 1612 "GAP_FILL": lambda self: self._parse_gap_fill(), 1613 "INITCAP": lambda self: self._parse_initcap(), 1614 "JSON_OBJECT": lambda self: self._parse_json_object(), 1615 "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True), 1616 "JSON_TABLE": lambda self: self._parse_json_table(), 1617 "MATCH": lambda self: self._parse_match_against(), 1618 "NORMALIZE": lambda self: self._parse_normalize(), 1619 "OPENJSON": lambda self: self._parse_open_json(), 1620 "OVERLAY": lambda self: self._parse_overlay(), 1621 "POSITION": lambda self: self._parse_position(), 1622 "SAFE_CAST": lambda self: self._parse_cast(False, safe=True), 1623 "STRING_AGG": lambda self: self._parse_string_agg(), 1624 "SUBSTRING": lambda self: self._parse_substring(), 1625 "TRIM": lambda self: self._parse_trim(), 1626 "TRY_CAST": lambda self: self._parse_cast(False, safe=True), 1627 "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True), 1628 "WEIGHT_STRING": lambda self: self._parse_weight_string(), 1629 "XMLELEMENT": lambda self: self._parse_xml_element(), 1630 "XMLTABLE": lambda self: self._parse_xml_table(), 1631 } 1632 1633 QUERY_MODIFIER_PARSERS: t.ClassVar = { 1634 TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()), 1635 TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()), 1636 TokenType.WHERE: lambda self: ("where", self._parse_where()), 1637 TokenType.GROUP_BY: lambda self: ("group", self._parse_group()), 1638 TokenType.HAVING: lambda self: ("having", self._parse_having()), 1639 TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()), 1640 TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()), 1641 TokenType.ORDER_BY: lambda self: ("order", self._parse_order()), 1642 TokenType.LIMIT: lambda self: ("limit", self._parse_limit()), 1643 TokenType.FETCH: lambda self: ("limit", self._parse_limit()), 1644 TokenType.OFFSET: lambda self: ("offset", self._parse_offset()), 1645 TokenType.FOR: lambda self: ("locks", self._parse_locks()), 1646 TokenType.LOCK: lambda self: ("locks", self._parse_locks()), 1647 TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1648 TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1649 TokenType.CLUSTER_BY: lambda self: ( 1650 "cluster", 1651 self._parse_cluster(), 1652 ), 1653 TokenType.DISTRIBUTE_BY: lambda self: ( 1654 "distribute", 1655 self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), 1656 ), 1657 TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)), 1658 TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)), 1659 } 1660 QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS) 1661 1662 SET_PARSERS: t.ClassVar = { 1663 "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"), 1664 "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"), 1665 "SESSION": lambda self: self._parse_set_item_assignment("SESSION"), 1666 "TRANSACTION": lambda self: self._parse_set_transaction(), 1667 } 1668 1669 SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {} 1670 1671 TYPE_LITERAL_PARSERS: t.ClassVar = { 1672 exp.DType.JSON: lambda self, this, _: self.expression( 1673 exp.ParseJSON(this=this, is_literal=True) 1674 ), 1675 } 1676 1677 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1678 1679 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1680 1681 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1682 1683 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1684 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1685 "ISOLATION": ( 1686 ("LEVEL", "REPEATABLE", "READ"), 1687 ("LEVEL", "READ", "COMMITTED"), 1688 ("LEVEL", "READ", "UNCOMITTED"), 1689 ("LEVEL", "SERIALIZABLE"), 1690 ), 1691 "READ": ("WRITE", "ONLY"), 1692 } 1693 1694 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1695 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1696 "DO": ("NOTHING", "UPDATE"), 1697 } 1698 1699 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1700 "INSTEAD": (("OF",),), 1701 "BEFORE": tuple(), 1702 "AFTER": tuple(), 1703 } 1704 1705 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1706 "NOT": (("DEFERRABLE",),), 1707 "DEFERRABLE": tuple(), 1708 } 1709 1710 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1711 "SCALE": ("EXTEND", "NOEXTEND"), 1712 "SHARD": ("EXTEND", "NOEXTEND"), 1713 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1714 **dict.fromkeys( 1715 ( 1716 "SESSION", 1717 "GLOBAL", 1718 "KEEP", 1719 "NOKEEP", 1720 "ORDER", 1721 "NOORDER", 1722 "NOCACHE", 1723 "CYCLE", 1724 "NOCYCLE", 1725 "NOMINVALUE", 1726 "NOMAXVALUE", 1727 "NOSCALE", 1728 "NOSHARD", 1729 ), 1730 tuple(), 1731 ), 1732 } 1733 1734 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1735 1736 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1737 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1738 ) 1739 1740 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1741 1742 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1743 "TYPE": ("EVOLUTION",), 1744 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1745 } 1746 1747 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1748 1749 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1750 ("CALLER", "SELF", "OWNER"), tuple() 1751 ) 1752 1753 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1754 "NOT": ("ENFORCED",), 1755 "MATCH": ( 1756 "FULL", 1757 "PARTIAL", 1758 "SIMPLE", 1759 ), 1760 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1761 "USING": ( 1762 "BTREE", 1763 "HASH", 1764 ), 1765 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1766 } 1767 1768 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1769 "NO": ("OTHERS",), 1770 "CURRENT": ("ROW",), 1771 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1772 } 1773 1774 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1775 1776 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1777 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1778 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1779 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1780 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1781 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1782 ("FOR", "VERSION"): "VERSION", 1783 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1784 ("VERSION", "AS", "OF"): "VERSION", 1785 } 1786 1787 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1788 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1789 1790 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1791 1792 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1793 1794 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1795 1796 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1797 1798 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1799 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1800 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1801 1802 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1803 1804 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1805 1806 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1807 TokenType.CONSTRAINT, 1808 TokenType.FOREIGN_KEY, 1809 TokenType.INDEX, 1810 TokenType.KEY, 1811 TokenType.PRIMARY_KEY, 1812 TokenType.UNIQUE, 1813 } 1814 1815 # Constraint kinds that start an ALTER ... ADD action but aren't tokens of their own 1816 ADD_CONSTRAINT_KEYWORDS: t.ClassVar[set[str]] = set() 1817 1818 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1819 1820 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1821 1822 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1823 1824 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1825 "FILE_FORMAT", 1826 "COPY_OPTIONS", 1827 "FORMAT_OPTIONS", 1828 "CREDENTIAL", 1829 } 1830 1831 # IS binds above comparisons (high), alongside ordering comparisons (comparison), 1832 # alongside equality (equality), or below both (low). Preserve the historical 1833 # high precedence unless the dialect specifies otherwise. 1834 IS_PREDICATE_PRECEDENCE: t.ClassVar[IsPredicatePrecedence] = IsPredicatePrecedence.HIGH 1835 IS_PREDICATE_TOKENS: t.ClassVar = { 1836 TokenType.IS, 1837 TokenType.ISNULL, 1838 TokenType.NOTNULL, 1839 TokenType.NOT, 1840 } 1841 1842 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1843 1844 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1845 1846 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1847 1848 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1849 1850 # The style options for the DESCRIBE statement 1851 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1852 1853 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1854 1855 # The style options for the ANALYZE statement 1856 ANALYZE_STYLES: t.ClassVar = { 1857 "BUFFER_USAGE_LIMIT", 1858 "FULL", 1859 "LOCAL", 1860 "NO_WRITE_TO_BINLOG", 1861 "SAMPLE", 1862 "SKIP_LOCKED", 1863 "VERBOSE", 1864 } 1865 1866 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1867 "ALL": lambda self: self._parse_analyze_columns(), 1868 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1869 "DELETE": lambda self: self._parse_analyze_delete(), 1870 "DROP": lambda self: self._parse_analyze_histogram(), 1871 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1872 "LIST": lambda self: self._parse_analyze_list(), 1873 "PREDICATE": lambda self: self._parse_analyze_columns(), 1874 "UPDATE": lambda self: self._parse_analyze_histogram(), 1875 "VALIDATE": lambda self: self._parse_analyze_validate(), 1876 } 1877 1878 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1879 1880 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1881 1882 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1883 1884 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1885 1886 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1887 1888 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1889 1890 STRICT_CAST: t.ClassVar = True 1891 1892 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1893 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1894 # Whether an UNPIVOT outputs its value column(s) before the name column 1895 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1896 # Controls when an aggregation's name is included in a pivoted column's name: 1897 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1898 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1899 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1900 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1901 1902 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1903 1904 # Whether the table sample clause expects CSV syntax 1905 TABLESAMPLE_CSV: t.ClassVar = False 1906 1907 # The default method used for table sampling 1908 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1909 1910 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1911 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1912 1913 # Whether the TRIM function expects the characters to trim as its first argument 1914 TRIM_PATTERN_FIRST: t.ClassVar = False 1915 1916 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1917 STRING_ALIASES: t.ClassVar = False 1918 1919 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1920 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1921 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"} 1922 1923 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1924 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1925 1926 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1927 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1928 1929 # Whether the `:` operator is used to extract a value from a VARIANT column 1930 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1931 1932 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1933 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1934 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1935 1936 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1937 # If this is True and '(' is not found, the keyword will be treated as an identifier 1938 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1939 1940 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1941 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1942 1943 # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery) 1944 SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False 1945 1946 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1947 INTERVAL_SPANS: t.ClassVar = True 1948 1949 # Whether a PARTITION clause can follow a table reference 1950 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1951 1952 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1953 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1954 1955 # Whether the 'AS' keyword is optional in the CTE definition syntax 1956 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1957 1958 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1959 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1960 1961 # Whether dropping a column with an ALTER statement requires the presence of the COLUMN keyword 1962 ALTER_DROP_REQUIRES_COLUMN: t.ClassVar = True 1963 1964 # Whether Alter statements are allowed to contain Partition specifications 1965 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1966 1967 # Whether an ALTER can combine different actions, e.g. ADD COLUMN a INT, DROP COLUMN b 1968 ALTER_TABLE_MIXED_ACTIONS: t.ClassVar = False 1969 1970 # Whether an ALTER TABLE needs an action, e.g. MySQL's ALTER TABLE t ENGINE=InnoDB has none 1971 ALTER_TABLE_REQUIRES_ACTION: t.ClassVar = True 1972 1973 # Whether changing a column's type with ALTER COLUMN requires the TYPE keyword 1974 ALTER_COLUMN_TYPE_REQUIRES_KEYWORD: t.ClassVar = False 1975 1976 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1977 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1978 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1979 # as BigQuery, where all joins have the same precedence. 1980 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1981 1982 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1983 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1984 1985 # Whether map literals support arbitrary expressions as keys. 1986 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1987 # When False, keys are typically restricted to identifiers. 1988 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1989 1990 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1991 # is true for Snowflake but not for BigQuery which can also process strings 1992 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1993 1994 # Dialects like Databricks support JOINS without join criteria 1995 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 1996 ADD_JOIN_ON_TRUE: t.ClassVar = False 1997 1998 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 1999 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 2000 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 2001 2002 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 2003 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 2004 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 2005 2006 # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause, 2007 # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake) 2008 SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False 2009 2010 # Type names that denote a different type when they're quoted, so quoting has to be 2011 # preserved instead of resolving them into the built-in type of the same name. These 2012 # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR 2013 QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set() 2014 2015 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 2016 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 2017 2018 def __init__( 2019 self, 2020 error_level: ErrorLevel | None = None, 2021 error_message_context: int = 100, 2022 max_errors: int = 3, 2023 max_nodes: int = -1, 2024 dialect: DialectType = None, 2025 ): 2026 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 2027 self.error_message_context: int = error_message_context 2028 self.max_errors: int = max_errors 2029 self.max_nodes: int = max_nodes 2030 self.dialect: t.Any = _resolve_dialect(dialect) 2031 self.sql: str = "" 2032 self.errors: list[ParseError] = [] 2033 self._tokens: list[Token] = [] 2034 self._tokens_size: i64 = 0 2035 self._index: i64 = 0 2036 self._curr: Token = SENTINEL_NONE 2037 self._next: Token = SENTINEL_NONE 2038 self._prev: Token = SENTINEL_NONE 2039 self._prev_comments: list[str] = [] 2040 self._pipe_cte_counter: int = 0 2041 self._chunks: list[list[Token]] = [] 2042 self._chunk_index: i64 = 0 2043 self._node_count: int = 0 2044 2045 def reset(self) -> None: 2046 self.sql = "" 2047 self.errors = [] 2048 self._tokens = [] 2049 self._tokens_size = 0 2050 self._index = 0 2051 self._curr = SENTINEL_NONE 2052 self._next = SENTINEL_NONE 2053 self._prev = SENTINEL_NONE 2054 self._prev_comments = [] 2055 self._pipe_cte_counter = 0 2056 self._chunks = [] 2057 self._chunk_index = 0 2058 self._node_count = 0 2059 2060 def _advance(self, times: i64 = 1) -> None: 2061 index = self._index + times 2062 self._index = index 2063 tokens = self._tokens 2064 size = self._tokens_size 2065 self._curr = tokens[index] if index < size else SENTINEL_NONE 2066 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 2067 2068 if index > 0: 2069 prev = tokens[index - 1] 2070 self._prev = prev 2071 self._prev_comments = prev.comments 2072 else: 2073 self._prev = SENTINEL_NONE 2074 self._prev_comments = [] 2075 2076 def _advance_chunk(self) -> None: 2077 self._index = -1 2078 self._tokens = self._chunks[self._chunk_index] 2079 self._tokens_size = i64(len(self._tokens)) 2080 self._chunk_index += 1 2081 self._advance() 2082 2083 def _retreat(self, index: i64) -> None: 2084 if index != self._index: 2085 self._advance(index - self._index) 2086 2087 def _add_comments(self, expression: exp.Expr | None) -> None: 2088 if expression and self._prev_comments: 2089 expression.add_comments(self._prev_comments) 2090 self._prev_comments = [] 2091 2092 def _match( 2093 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2094 ) -> bool: 2095 if self._curr.token_type == token_type: 2096 if advance: 2097 self._advance() 2098 self._add_comments(expression) 2099 return True 2100 return False 2101 2102 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2103 if self._curr.token_type in types: 2104 if advance: 2105 self._advance() 2106 return True 2107 return False 2108 2109 def _match_pair( 2110 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2111 ) -> bool: 2112 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2113 if advance: 2114 self._advance(2) 2115 return True 2116 return False 2117 2118 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2119 if ( 2120 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2121 and self._curr.text.upper() in texts 2122 ): 2123 if advance: 2124 self._advance() 2125 return True 2126 return False 2127 2128 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2129 index = self._index 2130 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2131 for text in texts: 2132 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2133 self._advance() 2134 else: 2135 self._retreat(index) 2136 return False 2137 2138 if not advance: 2139 self._retreat(index) 2140 2141 return True 2142 2143 def _is_connected(self) -> bool: 2144 prev = self._prev 2145 curr = self._curr 2146 return bool(prev and curr and prev.end + 1 == curr.start) 2147 2148 def _find_sql(self, start: Token, end: Token) -> str: 2149 return self.sql[start.start : end.end + 1] 2150 2151 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2152 token = token or self._curr or self._prev or Token.string("") 2153 formatted_sql, start_context, highlight, end_context = highlight_sql( 2154 sql=self.sql, 2155 positions=[(token.start, token.end)], 2156 context_length=self.error_message_context, 2157 ) 2158 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2159 2160 error = ParseError.new( 2161 formatted_message, 2162 description=message, 2163 line=token.line, 2164 col=token.col, 2165 start_context=start_context, 2166 highlight=highlight, 2167 end_context=end_context, 2168 ) 2169 2170 if self.error_level == ErrorLevel.IMMEDIATE: 2171 raise error 2172 2173 self.errors.append(error) 2174 2175 def validate_expression(self, expression: E, args: list | None = None) -> E: 2176 if self.max_nodes > -1: 2177 self._node_count += 1 2178 if self._node_count > self.max_nodes: 2179 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2180 if self.error_level != ErrorLevel.IGNORE: 2181 for error_message in expression.error_messages(args): 2182 self.raise_error(error_message) 2183 return expression 2184 2185 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2186 index = self._index 2187 error_level = self.error_level 2188 this: T | None = None 2189 2190 self.error_level = ErrorLevel.IMMEDIATE 2191 try: 2192 this = parse_method() 2193 except ParseError: 2194 this = None 2195 finally: 2196 if not this or retreat: 2197 self._retreat(index) 2198 self.error_level = error_level 2199 2200 return this 2201 2202 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2203 """ 2204 Parses a list of tokens and returns a list of syntax trees, one tree 2205 per parsed SQL statement. 2206 2207 Args: 2208 raw_tokens: The list of tokens. 2209 sql: The original SQL string. 2210 2211 Returns: 2212 The list of the produced syntax trees. 2213 """ 2214 return self._parse( 2215 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2216 ) 2217 2218 def parse_into( 2219 self, 2220 expression_types: exp.IntoType, 2221 raw_tokens: list[Token], 2222 sql: str | None = None, 2223 ) -> list[exp.Expr | None]: 2224 """ 2225 Parses a list of tokens into a given Expr type. If a collection of Expr 2226 types is given instead, this method will try to parse the token list into each one 2227 of them, stopping at the first for which the parsing succeeds. 2228 2229 Args: 2230 expression_types: The expression type(s) to try and parse the token list into. 2231 raw_tokens: The list of tokens. 2232 sql: The original SQL string, used to produce helpful debug messages. 2233 2234 Returns: 2235 The target Expr. 2236 """ 2237 errors = [] 2238 for expression_type in ensure_list(expression_types): 2239 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2240 if not parser: 2241 raise TypeError(f"No parser registered for {expression_type}") 2242 2243 try: 2244 return self._parse(parser, raw_tokens, sql) 2245 except ParseError as e: 2246 e.errors[0]["into_expression"] = expression_type 2247 errors.append(e) 2248 2249 raise ParseError( 2250 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2251 errors=merge_errors(errors), 2252 ) from errors[-1] 2253 2254 def check_errors(self) -> None: 2255 """Logs or raises any found errors, depending on the chosen error level setting.""" 2256 if self.error_level == ErrorLevel.WARN: 2257 for error in self.errors: 2258 logger.error(str(error)) 2259 elif self.error_level == ErrorLevel.RAISE and self.errors: 2260 raise ParseError( 2261 concat_messages(self.errors, self.max_errors), 2262 errors=merge_errors(self.errors), 2263 ) 2264 2265 def expression( 2266 self, 2267 instance: E, 2268 token: Token | None = None, 2269 comments: list[str] | None = None, 2270 ) -> E: 2271 if token: 2272 instance.update_positions(token) 2273 instance.add_comments(comments) if comments else self._add_comments(instance) 2274 if not instance.is_primitive: 2275 instance = self.validate_expression(instance) 2276 return instance 2277 2278 def _parse_batch_statements( 2279 self, 2280 parse_method: t.Callable[[Parser], exp.Expr | None], 2281 sep_first_statement: bool = True, 2282 ) -> list[exp.Expr | None]: 2283 expressions = [] 2284 2285 # Chunkification binds if/while statements with the first statement of the body 2286 if sep_first_statement: 2287 self._match(TokenType.BEGIN) 2288 expressions.append(parse_method(self)) 2289 2290 chunks_length = len(self._chunks) 2291 while self._chunk_index < chunks_length: 2292 self._advance_chunk() 2293 2294 if self._match(TokenType.ELSE, advance=False): 2295 return expressions 2296 2297 if expressions and not self._next and self._match(TokenType.END): 2298 expressions.append(exp.EndStatement()) 2299 continue 2300 2301 expressions.append(parse_method(self)) 2302 2303 if self._index < self._tokens_size: 2304 self.raise_error("Invalid expression / Unexpected token") 2305 2306 self.check_errors() 2307 2308 return expressions 2309 2310 def _parse( 2311 self, 2312 parse_method: t.Callable[[Parser], exp.Expr | None], 2313 raw_tokens: list[Token], 2314 sql: str | None = None, 2315 ) -> list[exp.Expr | None]: 2316 self.reset() 2317 self.sql = sql or "" 2318 2319 total = len(raw_tokens) 2320 chunks: list[list[Token]] = [[]] 2321 2322 for i, token in enumerate(raw_tokens): 2323 if token.token_type == TokenType.SEMICOLON: 2324 if token.comments: 2325 chunks.append([token]) 2326 2327 if i < total - 1: 2328 chunks.append([]) 2329 else: 2330 chunks[-1].append(token) 2331 2332 self._chunks = chunks 2333 2334 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2335 2336 def _warn_unsupported(self) -> None: 2337 if self._tokens_size <= 1: 2338 return 2339 2340 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2341 # interested in emitting a warning for the one being currently processed. 2342 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2343 2344 logger.warning( 2345 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2346 ) 2347 2348 def _parse_command(self) -> exp.Command: 2349 self._warn_unsupported() 2350 comments = self._prev_comments 2351 return self.expression( 2352 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2353 comments=comments, 2354 ) 2355 2356 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2357 start = self._prev 2358 exists = self._parse_exists() if allow_exists else None 2359 2360 self._match(TokenType.ON) 2361 2362 materialized = self._match_text_seq("MATERIALIZED") 2363 kind = self._match_set(self.CREATABLES) and self._prev 2364 if not kind: 2365 return self._parse_as_command(start) 2366 2367 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2368 this = self._parse_user_defined_function(kind=kind.token_type) 2369 elif kind.token_type == TokenType.TABLE: 2370 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2371 elif kind.token_type == TokenType.COLUMN: 2372 this = self._parse_column() 2373 else: 2374 this = self._parse_table_parts(schema=True) 2375 2376 self._match(TokenType.IS) 2377 2378 return self.expression( 2379 exp.Comment( 2380 this=this, 2381 kind=kind.text, 2382 expression=self._parse_string(), 2383 exists=exists, 2384 materialized=materialized, 2385 ) 2386 ) 2387 2388 def _parse_to_table( 2389 self, 2390 ) -> exp.ToTableProperty: 2391 table = self._parse_table_parts(schema=True) 2392 return self.expression(exp.ToTableProperty(this=table)) 2393 2394 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2395 def _parse_ttl(self) -> exp.Expr: 2396 def _parse_ttl_action() -> exp.Expr | None: 2397 this = self._parse_bitwise() 2398 2399 if self._match_text_seq("DELETE"): 2400 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2401 if self._match_text_seq("RECOMPRESS"): 2402 return self.expression( 2403 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2404 ) 2405 if self._match_text_seq("TO", "DISK"): 2406 return self.expression( 2407 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2408 ) 2409 if self._match_text_seq("TO", "VOLUME"): 2410 return self.expression( 2411 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2412 ) 2413 2414 return this 2415 2416 expressions = self._parse_csv(_parse_ttl_action) 2417 where = self._parse_where() 2418 group = self._parse_group() 2419 2420 aggregates = None 2421 if group and self._match(TokenType.SET): 2422 aggregates = self._parse_csv(self._parse_set_item) 2423 2424 return self.expression( 2425 exp.MergeTreeTTL( 2426 expressions=expressions, where=where, group=group, aggregates=aggregates 2427 ) 2428 ) 2429 2430 def _parse_condition(self) -> exp.Expr | None: 2431 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2432 2433 def _parse_block(self) -> exp.Block: 2434 return self.expression( 2435 exp.Block( 2436 expressions=self._parse_batch_statements( 2437 parse_method=lambda self: self._parse_statement() 2438 ) 2439 ) 2440 ) 2441 2442 def _parse_whileblock(self) -> exp.WhileBlock: 2443 return self.expression( 2444 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2445 ) 2446 2447 def _parse_statement(self) -> exp.Expr | None: 2448 if not self._curr: 2449 return None 2450 2451 if self._match_set(self.STATEMENT_PARSERS): 2452 comments = self._prev_comments 2453 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2454 stmt.add_comments(comments, prepend=True) 2455 return stmt 2456 2457 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2458 return self._parse_command() 2459 2460 if self._match_text_seq("WHILE"): 2461 return self._parse_whileblock() 2462 2463 expression = self._parse_expression() 2464 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2465 2466 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2467 expression = self._parse_pipe_syntax_query(expression) 2468 2469 return self._parse_query_modifiers(expression) 2470 2471 def _parse_drop(self, exists: bool = False, kind: str | None = None) -> exp.Drop | exp.Command: 2472 start = self._prev 2473 2474 if not kind: 2475 temporary = self._match(TokenType.TEMPORARY) 2476 materialized = self._match_text_seq("MATERIALIZED") 2477 iceberg = self._match_text_seq("ICEBERG") 2478 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 2479 else: 2480 temporary = False 2481 materialized = False 2482 iceberg = False 2483 2484 if not kind or (iceberg and kind != "TABLE"): 2485 return self._parse_as_command(start) 2486 2487 concurrently = ( 2488 kind == "INDEX" 2489 and self._next.token_type in self.ID_VAR_TOKENS 2490 and self._match_text_seq("CONCURRENTLY") 2491 ) 2492 2493 if_exists = exists or self._parse_exists() 2494 2495 tables: exp.Expr | list[exp.Expr] | None 2496 if kind == "COLUMN": 2497 # A dropped column named after a keyword, like `any` or `charset`, is only a name 2498 name = ( 2499 None 2500 if self._curr.text.upper() in self.NO_PAREN_FUNCTION_PARSERS 2501 else self._parse_column() 2502 ) or self._parse_id_var() 2503 tables = ( 2504 self.expression(exp.Column(this=name)) if isinstance(name, exp.Identifier) else name 2505 ) 2506 elif kind in ("TABLE", "VIEW"): 2507 tables = self._parse_csv(lambda: self._parse_table_parts(schema=True)) 2508 else: 2509 tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2510 2511 cluster: exp.Expr | None = None 2512 if self._match(TokenType.ON): 2513 if kind == "INDEX": 2514 cluster = self.expression(exp.OnProperty(this=self._parse_table_parts())) 2515 else: 2516 cluster = self._parse_on_property() 2517 2518 if self._match(TokenType.L_PAREN, advance=False): 2519 expressions = self._parse_wrapped_csv(self._parse_types) 2520 else: 2521 expressions = None 2522 2523 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2524 2525 return self.expression( 2526 exp.Drop( 2527 exists=if_exists, 2528 tables=ensure_list(tables), 2529 expressions=expressions, 2530 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2531 temporary=temporary, 2532 materialized=materialized, 2533 cascade=cascade_or_restrict == "CASCADE", 2534 restrict=cascade_or_restrict == "RESTRICT", 2535 constraints=self._match_text_seq("CONSTRAINTS"), 2536 purge=self._match_text_seq("PURGE"), 2537 cluster=cluster, 2538 concurrently=concurrently, 2539 sync=self._match_text_seq("SYNC"), 2540 iceberg=iceberg, 2541 force=self._match_text_seq("FORCE"), 2542 ) 2543 ) 2544 2545 def _parse_exists(self, not_: bool = False) -> bool | None: 2546 if not_: 2547 return self._match_text_seq("IF", "NOT", "EXISTS") 2548 return self._match_text_seq("IF", "EXISTS") 2549 2550 def _parse_create(self) -> exp.Create | exp.Command: 2551 # Note: this can't be None because we've matched a statement parser 2552 start = self._prev 2553 2554 replace = ( 2555 start.token_type == TokenType.REPLACE 2556 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2557 or self._match_pair(TokenType.OR, TokenType.ALTER) 2558 ) 2559 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2560 2561 unique = self._match(TokenType.UNIQUE) 2562 2563 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2564 clustered = True 2565 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2566 "COLUMNSTORE" 2567 ): 2568 clustered = False 2569 else: 2570 clustered = None 2571 2572 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2573 self._advance() 2574 2575 properties = None 2576 create_token = self._match_set(self.CREATABLES) and self._prev 2577 2578 if not create_token: 2579 # exp.Properties.Location.POST_CREATE 2580 properties = self._parse_properties() 2581 create_token = self._match_set(self.CREATABLES) and self._prev 2582 2583 if not properties or not create_token: 2584 return self._parse_as_command(start) 2585 2586 create_token_type = t.cast(Token, create_token).token_type 2587 2588 concurrently = self._match_text_seq("CONCURRENTLY") 2589 exists = self._parse_exists(not_=True) 2590 this = None 2591 expression: exp.Expr | None = None 2592 indexes = None 2593 no_schema_binding = None 2594 begin = None 2595 clone = None 2596 2597 def extend_props(temp_props: exp.Properties | None) -> None: 2598 nonlocal properties 2599 if properties and temp_props: 2600 properties.expressions.extend(temp_props.expressions) 2601 elif temp_props: 2602 properties = temp_props 2603 2604 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2605 this = self._parse_user_defined_function(kind=create_token_type) 2606 2607 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2608 extend_props(self._parse_properties()) 2609 2610 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2611 2612 if ( 2613 not expression 2614 and create_token_type == TokenType.FUNCTION 2615 and isinstance(this, exp.UserDefinedFunction) 2616 and this.args.get("wrapped") 2617 ): 2618 pre_table_index = self._index 2619 is_table = self._match(TokenType.TABLE) 2620 2621 expression = self._parse_expression() 2622 overload_mode = bool( 2623 expression 2624 and self._curr.token_type == TokenType.COMMA 2625 and self._next.token_type == TokenType.L_PAREN 2626 ) 2627 if not overload_mode: 2628 self._retreat(pre_table_index) 2629 is_table = False 2630 expression = None 2631 else: 2632 is_table = False 2633 overload_mode = False 2634 2635 extend_props(self._parse_function_properties()) 2636 2637 if not expression: 2638 if self._match(TokenType.COMMAND): 2639 expression = self._parse_as_command(self._prev) 2640 else: 2641 begin = self._match(TokenType.BEGIN) 2642 return_ = self._match_text_seq("RETURN") 2643 2644 if self._match(TokenType.STRING, advance=False): 2645 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2646 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2647 expression = self._parse_string() 2648 extend_props(self._parse_properties()) 2649 else: 2650 expression = ( 2651 self._parse_user_defined_function_expression() 2652 if create_token_type == TokenType.FUNCTION 2653 else self._parse_block() 2654 ) 2655 2656 if return_: 2657 expression = self.expression(exp.Return(this=expression)) 2658 2659 if overload_mode and expression: 2660 expression = self._parse_macro_overloads( 2661 t.cast(exp.UserDefinedFunction, this), expression, is_table 2662 ) 2663 elif create_token_type == TokenType.INDEX: 2664 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2665 if not self._match(TokenType.ON): 2666 index = self._parse_id_var() 2667 anonymous = False 2668 else: 2669 index = None 2670 anonymous = True 2671 2672 this = self._parse_index(index=index, anonymous=anonymous) 2673 elif ( 2674 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2675 ) or create_token_type == TokenType.TRIGGER: 2676 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2677 create_token = self._prev 2678 2679 trigger_name = self._parse_id_var() 2680 if not trigger_name: 2681 return self._parse_as_command(start) 2682 2683 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2684 timing = timing_var.this if timing_var else None 2685 if not timing: 2686 return self._parse_as_command(start) 2687 2688 events = self._parse_trigger_events() 2689 if not self._match(TokenType.ON): 2690 self.raise_error("Expected ON in trigger definition") 2691 2692 table = self._parse_table_parts() 2693 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2694 deferrable, initially = self._parse_trigger_deferrable() 2695 referencing = self._parse_trigger_referencing() 2696 for_each = self._parse_trigger_for_each() 2697 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2698 self._parse_disjunction, optional=True 2699 ) 2700 execute = self._parse_trigger_execute() 2701 2702 if execute is None: 2703 return self._parse_as_command(start) 2704 2705 trigger_props = self.expression( 2706 exp.TriggerProperties( 2707 table=table, 2708 timing=timing, 2709 events=events, 2710 execute=execute, 2711 constraint=is_constraint, 2712 referenced_table=referenced_table, 2713 deferrable=deferrable, 2714 initially=initially, 2715 referencing=referencing, 2716 for_each=for_each, 2717 when=when, 2718 ) 2719 ) 2720 2721 this = trigger_name 2722 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2723 elif create_token_type == TokenType.TYPE: 2724 this = self._parse_table_parts(schema=True) 2725 if not this or not self._match(TokenType.ALIAS): 2726 return self._parse_as_command(start) 2727 2728 if self._match(TokenType.ENUM): 2729 expression = exp.DataType( 2730 this=exp.DType.ENUM, 2731 expressions=self._parse_wrapped_csv(self._parse_string), 2732 ) 2733 elif self._match(TokenType.L_PAREN, advance=False): 2734 expression = self._parse_schema() 2735 else: 2736 return self._parse_as_command(start) 2737 elif create_token_type in self.DB_CREATABLES: 2738 table_parts = self._parse_table_parts( 2739 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2740 ) 2741 2742 # exp.Properties.Location.POST_NAME 2743 self._match(TokenType.COMMA) 2744 extend_props(self._parse_properties(before=True)) 2745 2746 this = self._parse_schema(this=table_parts) 2747 2748 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2749 extend_props(self._parse_properties()) 2750 2751 has_alias = self._match(TokenType.ALIAS) 2752 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2753 # exp.Properties.Location.POST_ALIAS 2754 extend_props(self._parse_properties()) 2755 2756 if create_token_type == TokenType.SEQUENCE: 2757 expression = self._parse_types() 2758 props = self._parse_properties() 2759 if props: 2760 sequence_props = exp.SequenceProperties() 2761 options = [] 2762 for prop in props: 2763 if isinstance(prop, exp.SequenceProperties): 2764 for arg, value in prop.args.items(): 2765 if arg == "options": 2766 options.extend(value) 2767 else: 2768 sequence_props.set(arg, value) 2769 prop.pop() 2770 2771 if options: 2772 sequence_props.set("options", options) 2773 2774 props.append("expressions", sequence_props) 2775 extend_props(props) 2776 else: 2777 expression = self._parse_ddl_select() 2778 2779 # Some dialects also support using a table as an alias instead of a SELECT. 2780 # Here we fallback to this as an alternative. 2781 if not expression and has_alias: 2782 expression = self._try_parse(self._parse_table_parts) 2783 2784 if create_token_type == TokenType.TABLE: 2785 # exp.Properties.Location.POST_EXPRESSION 2786 extend_props(self._parse_properties()) 2787 2788 indexes = [] 2789 while True: 2790 index = self._parse_index() 2791 2792 # exp.Properties.Location.POST_INDEX 2793 extend_props(self._parse_properties()) 2794 if not index: 2795 break 2796 else: 2797 self._match(TokenType.COMMA) 2798 indexes.append(index) 2799 elif create_token_type == TokenType.VIEW: 2800 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2801 no_schema_binding = True 2802 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2803 extend_props(self._parse_properties()) 2804 2805 shallow = self._match_text_seq("SHALLOW") 2806 2807 if self._match_texts(self.CLONE_KEYWORDS): 2808 copy = self._prev.text.lower() == "copy" 2809 clone = self.expression( 2810 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2811 ) 2812 2813 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2814 return self._parse_as_command(start) 2815 2816 create_kind_text = create_token.text.upper() 2817 return self.expression( 2818 exp.Create( 2819 this=this, 2820 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2821 replace=replace, 2822 refresh=refresh, 2823 unique=unique, 2824 expression=expression, 2825 exists=exists, 2826 properties=properties, 2827 indexes=indexes, 2828 no_schema_binding=no_schema_binding, 2829 begin=begin, 2830 clone=clone, 2831 concurrently=concurrently, 2832 clustered=clustered, 2833 ) 2834 ) 2835 2836 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2837 seq = exp.SequenceProperties() 2838 2839 options = [] 2840 index = self._index 2841 2842 while self._curr: 2843 self._match(TokenType.COMMA) 2844 if self._match_text_seq("INCREMENT"): 2845 self._match_text_seq("BY") 2846 self._match_text_seq("=") 2847 seq.set("increment", self._parse_term()) 2848 elif self._match_text_seq("MINVALUE"): 2849 seq.set("minvalue", self._parse_term()) 2850 elif self._match_text_seq("MAXVALUE"): 2851 seq.set("maxvalue", self._parse_term()) 2852 elif self._match_text_seq("START"): 2853 self._match_text_seq("WITH") 2854 self._match_text_seq("=") 2855 seq.set("start", self._parse_term()) 2856 elif self._match_text_seq("CACHE"): 2857 # T-SQL allows empty CACHE which is initialized dynamically 2858 seq.set("cache", self._parse_number() or True) 2859 elif self._match_text_seq("OWNED", "BY"): 2860 # "OWNED BY NONE" is the default 2861 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2862 else: 2863 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2864 if opt: 2865 options.append(opt) 2866 else: 2867 break 2868 2869 seq.set("options", options if options else None) 2870 return None if self._index == index else seq 2871 2872 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2873 events = [] 2874 2875 while True: 2876 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2877 2878 if not event_type: 2879 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2880 2881 columns = ( 2882 self._parse_csv(self._parse_column) 2883 if event_type == "UPDATE" and self._match_text_seq("OF") 2884 else None 2885 ) 2886 2887 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2888 2889 if not self._match(TokenType.OR): 2890 break 2891 2892 return events 2893 2894 def _parse_trigger_deferrable( 2895 self, 2896 ) -> tuple[str | None, str | None]: 2897 deferrable_var = self._parse_var_from_options( 2898 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2899 ) 2900 deferrable = deferrable_var.this if deferrable_var else None 2901 2902 initially = None 2903 if deferrable and self._match_text_seq("INITIALLY"): 2904 initially = ( 2905 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2906 ) 2907 2908 return deferrable, initially 2909 2910 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2911 if not self._match_text_seq(keyword): 2912 return None 2913 if not self._match_text_seq("TABLE"): 2914 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2915 self._match_text_seq("AS") 2916 return self._parse_id_var() 2917 2918 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2919 if not self._match_text_seq("REFERENCING"): 2920 return None 2921 2922 old_alias = None 2923 new_alias = None 2924 2925 while True: 2926 if alias := self._parse_trigger_referencing_clause("OLD"): 2927 if old_alias is not None: 2928 self.raise_error("Duplicate OLD clause in REFERENCING") 2929 old_alias = alias 2930 elif alias := self._parse_trigger_referencing_clause("NEW"): 2931 if new_alias is not None: 2932 self.raise_error("Duplicate NEW clause in REFERENCING") 2933 new_alias = alias 2934 else: 2935 break 2936 2937 if old_alias is None and new_alias is None: 2938 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2939 2940 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2941 2942 def _parse_trigger_for_each(self) -> str | None: 2943 if not self._match_text_seq("FOR", "EACH"): 2944 return None 2945 2946 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2947 2948 def _parse_trigger_execute(self) -> exp.TriggerExecute | None: 2949 if not self._match(TokenType.EXECUTE): 2950 return None 2951 2952 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2953 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2954 2955 func_call = self._parse_column() 2956 return self.expression(exp.TriggerExecute(this=func_call)) 2957 2958 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2959 # only used for teradata currently 2960 self._match(TokenType.COMMA) 2961 2962 kwargs = { 2963 "no": self._match_text_seq("NO"), 2964 "dual": self._match_text_seq("DUAL"), 2965 "before": self._match_text_seq("BEFORE"), 2966 "default": self._match_text_seq("DEFAULT"), 2967 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2968 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2969 "after": self._match_text_seq("AFTER"), 2970 "minimum": self._match_texts(("MIN", "MINIMUM")), 2971 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2972 } 2973 2974 if self._match_texts(self.PROPERTY_PARSERS): 2975 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2976 try: 2977 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2978 except TypeError: 2979 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2980 2981 if self._match_pair(TokenType.CHAR, TokenType.SET): 2982 return self._parse_character_set(default=bool(kwargs["default"])) 2983 2984 return None 2985 2986 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2987 return self._parse_wrapped_csv(self._parse_property) 2988 2989 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2990 if self._match_texts(self.PROPERTY_PARSERS): 2991 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2992 2993 if self._match_pair(TokenType.CHAR, TokenType.SET): 2994 return self._parse_character_set() 2995 2996 if self._match(TokenType.DEFAULT): 2997 if self._match_texts(self.PROPERTY_PARSERS): 2998 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 2999 3000 if self._match_pair(TokenType.CHAR, TokenType.SET): 3001 return self._parse_character_set(default=True) 3002 3003 if self._match_text_seq("COMPOUND", "SORTKEY"): 3004 return self._parse_sortkey(compound=True) 3005 3006 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 3007 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 3008 3009 if self._match_text_seq("NOT", "DETERMINISTIC"): 3010 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3011 3012 index = self._index 3013 3014 seq_props = self._parse_sequence_properties() 3015 if seq_props: 3016 return seq_props 3017 3018 self._retreat(index) 3019 return self._parse_key_value_property() 3020 3021 def _parse_key_value_property( 3022 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 3023 ) -> exp.Property | None: 3024 index = self._index 3025 key = self._parse_column() 3026 3027 if not self._match(TokenType.EQ): 3028 self._retreat(index) 3029 return None 3030 3031 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 3032 if isinstance(key, exp.Column): 3033 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 3034 3035 value = ( 3036 parse_value() 3037 if parse_value 3038 else self._parse_bitwise() or self._parse_var(any_token=True) 3039 ) 3040 3041 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 3042 if isinstance(value, exp.Column): 3043 value = exp.var(value.name) 3044 3045 return self.expression(exp.Property(this=key, value=value)) 3046 3047 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 3048 if self._match_text_seq("BY"): 3049 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 3050 3051 self._match(TokenType.ALIAS) 3052 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 3053 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 3054 3055 return self.expression( 3056 exp.FileFormatProperty( 3057 this=( 3058 self.expression( 3059 exp.InputOutputFormat( 3060 input_format=input_format, output_format=output_format 3061 ) 3062 ) 3063 if input_format or output_format 3064 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 3065 ), 3066 hive_format=True, 3067 ) 3068 ) 3069 3070 def _parse_unquoted_field(self) -> exp.Expr | None: 3071 field = self._parse_field() 3072 if isinstance(field, exp.Identifier) and not field.quoted: 3073 field = exp.var(field) 3074 3075 return field 3076 3077 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 3078 self._match(TokenType.EQ) 3079 self._match(TokenType.ALIAS) 3080 3081 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 3082 3083 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 3084 properties = [] 3085 while True: 3086 if before: 3087 prop = self._parse_property_before() 3088 else: 3089 prop = self._parse_property() 3090 if not prop: 3091 break 3092 for p in ensure_list(prop): 3093 properties.append(p) 3094 3095 self._match(TokenType.COMMA, expression=properties[-1]) 3096 3097 if properties: 3098 return self.expression(exp.Properties(expressions=properties)) 3099 3100 return None 3101 3102 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3103 return self.expression( 3104 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3105 ) 3106 3107 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3108 return self.expression( 3109 exp.SqlSecurityProperty( 3110 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3111 ) 3112 ) 3113 3114 def _parse_settings_property(self) -> exp.SettingsProperty: 3115 return self.expression( 3116 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3117 ) 3118 3119 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3120 if not self._match_text_seq("ON", "NULL", "INPUT"): 3121 self._retreat(self._index - 1) 3122 return None 3123 3124 return self.expression(exp.CalledOnNullInputProperty()) 3125 3126 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3127 if self._index >= 2: 3128 pre_volatile_token = self._tokens[self._index - 2] 3129 else: 3130 pre_volatile_token = None 3131 3132 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3133 return exp.VolatileProperty() 3134 3135 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3136 3137 def _parse_retention_period(self) -> exp.Var: 3138 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3139 number = self._parse_number() 3140 number_str = f"{number} " if number else "" 3141 unit = self._parse_var(any_token=True) 3142 return exp.var(f"{number_str}{unit}") 3143 3144 def _parse_system_versioning_property( 3145 self, with_: bool = False 3146 ) -> exp.WithSystemVersioningProperty: 3147 self._match(TokenType.EQ) 3148 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3149 3150 if self._match_text_seq("OFF"): 3151 prop.set("on", False) 3152 return prop 3153 3154 self._match(TokenType.ON) 3155 if self._match(TokenType.L_PAREN): 3156 while self._curr and not self._match(TokenType.R_PAREN): 3157 if self._match_text_seq("HISTORY_TABLE", "="): 3158 prop.set("this", self._parse_table_parts()) 3159 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3160 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3161 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3162 prop.set("retention_period", self._parse_retention_period()) 3163 3164 self._match(TokenType.COMMA) 3165 3166 return prop 3167 3168 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3169 self._match(TokenType.EQ) 3170 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3171 prop = self.expression(exp.DataDeletionProperty(on=on)) 3172 3173 if self._match(TokenType.L_PAREN): 3174 while self._curr and not self._match(TokenType.R_PAREN): 3175 if self._match_text_seq("FILTER_COLUMN", "="): 3176 prop.set("filter_column", self._parse_column()) 3177 elif self._match_text_seq("RETENTION_PERIOD", "="): 3178 prop.set("retention_period", self._parse_retention_period()) 3179 3180 self._match(TokenType.COMMA) 3181 3182 return prop 3183 3184 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3185 kind = "HASH" 3186 expressions: list[exp.Expr] | None = None 3187 if self._match_text_seq("BY", "HASH"): 3188 expressions = self._parse_wrapped_csv(self._parse_id_var) 3189 elif self._match_text_seq("BY", "RANDOM"): 3190 kind = "RANDOM" 3191 3192 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3193 buckets: exp.Expr | None = None 3194 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3195 buckets = self._parse_number() 3196 3197 return self.expression( 3198 exp.DistributedByProperty( 3199 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3200 ) 3201 ) 3202 3203 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3204 self._match_text_seq("KEY") 3205 expressions = self._parse_wrapped_id_vars() 3206 return self.expression(expr_type(expressions=expressions)) 3207 3208 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3209 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3210 prop = self._parse_system_versioning_property(with_=True) 3211 self._match_r_paren() 3212 return prop 3213 3214 if self._match(TokenType.L_PAREN, advance=False): 3215 result: list[exp.Expr] = [] 3216 for i in self._parse_wrapped_properties(): 3217 result.extend(i) if isinstance(i, list) else result.append(i) 3218 return result 3219 3220 if self._match_text_seq("JOURNAL"): 3221 return self._parse_withjournaltable() 3222 3223 if self._match_texts(self.VIEW_ATTRIBUTES): 3224 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3225 3226 if self._match_text_seq("DATA"): 3227 return self._parse_withdata(no=False) 3228 elif self._match_text_seq("NO", "DATA"): 3229 return self._parse_withdata(no=True) 3230 3231 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3232 return self._parse_serde_properties(with_=True) 3233 3234 if self._match(TokenType.SCHEMA): 3235 return self.expression( 3236 exp.WithSchemaBindingProperty( 3237 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3238 ) 3239 ) 3240 3241 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3242 return self.expression( 3243 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3244 ) 3245 3246 if not self._next: 3247 return None 3248 3249 return self._parse_withisolatedloading() 3250 3251 def _parse_procedure_option(self) -> exp.Expr | None: 3252 if self._match_text_seq("EXECUTE", "AS"): 3253 return self.expression( 3254 exp.ExecuteAsProperty( 3255 this=self._parse_var_from_options( 3256 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3257 ) 3258 or self._parse_string() 3259 ) 3260 ) 3261 3262 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3263 3264 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3265 def _parse_definer(self) -> exp.DefinerProperty | None: 3266 self._match(TokenType.EQ) 3267 3268 user = self._parse_id_var() 3269 self._match(TokenType.PARAMETER) 3270 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3271 3272 if not user or not host: 3273 return None 3274 3275 return exp.DefinerProperty(this=f"{user}@{host}") 3276 3277 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3278 self._match(TokenType.TABLE) 3279 self._match(TokenType.EQ) 3280 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3281 3282 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3283 return self.expression(exp.LogProperty(no=no)) 3284 3285 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3286 return self.expression(exp.JournalProperty(**kwargs)) 3287 3288 def _parse_checksum(self) -> exp.ChecksumProperty: 3289 self._match(TokenType.EQ) 3290 3291 on = None 3292 if self._match(TokenType.ON): 3293 on = True 3294 elif self._match_text_seq("OFF"): 3295 on = False 3296 3297 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3298 3299 def _parse_cluster(self) -> exp.Cluster: 3300 self._match(TokenType.CLUSTER_BY) 3301 return self.expression( 3302 exp.Cluster( 3303 expressions=self._parse_csv(self._parse_column), 3304 ) 3305 ) 3306 3307 def _parse_cluster_property(self) -> exp.ClusterProperty: 3308 return self.expression( 3309 exp.ClusterProperty( 3310 expressions=self._parse_wrapped_csv(self._parse_column), 3311 ) 3312 ) 3313 3314 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3315 self._match_text_seq("BY") 3316 3317 self._match_l_paren() 3318 expressions = self._parse_csv(self._parse_column) 3319 self._match_r_paren() 3320 3321 if self._match_text_seq("SORTED", "BY"): 3322 self._match_l_paren() 3323 sorted_by = self._parse_csv(self._parse_ordered) 3324 self._match_r_paren() 3325 else: 3326 sorted_by = None 3327 3328 self._match(TokenType.INTO) 3329 buckets = self._parse_number() 3330 self._match_text_seq("BUCKETS") 3331 3332 return self.expression( 3333 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3334 ) 3335 3336 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3337 if not self._match_text_seq("GRANTS"): 3338 self._retreat(self._index - 1) 3339 return None 3340 3341 return self.expression(exp.CopyGrantsProperty()) 3342 3343 def _parse_freespace(self) -> exp.FreespaceProperty: 3344 self._match(TokenType.EQ) 3345 return self.expression( 3346 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3347 ) 3348 3349 def _parse_mergeblockratio( 3350 self, no: bool = False, default: bool = False 3351 ) -> exp.MergeBlockRatioProperty: 3352 if self._match(TokenType.EQ): 3353 return self.expression( 3354 exp.MergeBlockRatioProperty( 3355 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3356 ) 3357 ) 3358 3359 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3360 3361 def _parse_datablocksize( 3362 self, 3363 default: bool | None = None, 3364 minimum: bool | None = None, 3365 maximum: bool | None = None, 3366 ) -> exp.DataBlocksizeProperty: 3367 self._match(TokenType.EQ) 3368 size = self._parse_number() 3369 3370 units = None 3371 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3372 units = self._prev.text 3373 3374 return self.expression( 3375 exp.DataBlocksizeProperty( 3376 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3377 ) 3378 ) 3379 3380 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3381 self._match(TokenType.EQ) 3382 always = self._match_text_seq("ALWAYS") 3383 manual = self._match_text_seq("MANUAL") 3384 never = self._match_text_seq("NEVER") 3385 default = self._match_text_seq("DEFAULT") 3386 3387 autotemp = None 3388 if self._match_text_seq("AUTOTEMP"): 3389 autotemp = self._parse_schema() 3390 3391 return self.expression( 3392 exp.BlockCompressionProperty( 3393 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3394 ) 3395 ) 3396 3397 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3398 index = self._index 3399 no = self._match_text_seq("NO") 3400 concurrent = self._match_text_seq("CONCURRENT") 3401 3402 if not self._match_text_seq("ISOLATED", "LOADING"): 3403 self._retreat(index) 3404 return None 3405 3406 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3407 return self.expression( 3408 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3409 ) 3410 3411 def _parse_locking(self) -> exp.LockingProperty: 3412 if self._match(TokenType.TABLE): 3413 kind = "TABLE" 3414 elif self._match(TokenType.VIEW): 3415 kind = "VIEW" 3416 elif self._match(TokenType.ROW): 3417 kind = "ROW" 3418 elif self._match_text_seq("DATABASE"): 3419 kind = "DATABASE" 3420 else: 3421 kind = None 3422 3423 if kind in ("DATABASE", "TABLE", "VIEW"): 3424 this = self._parse_table_parts() 3425 else: 3426 this = None 3427 3428 if self._match(TokenType.FOR): 3429 for_or_in = "FOR" 3430 elif self._match(TokenType.IN): 3431 for_or_in = "IN" 3432 else: 3433 for_or_in = None 3434 3435 if self._match_text_seq("ACCESS"): 3436 lock_type = "ACCESS" 3437 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3438 lock_type = "EXCLUSIVE" 3439 elif self._match_text_seq("SHARE"): 3440 lock_type = "SHARE" 3441 elif self._match_text_seq("READ"): 3442 lock_type = "READ" 3443 elif self._match_text_seq("WRITE"): 3444 lock_type = "WRITE" 3445 elif self._match_text_seq("CHECKSUM"): 3446 lock_type = "CHECKSUM" 3447 else: 3448 lock_type = None 3449 3450 override = self._match_text_seq("OVERRIDE") 3451 3452 return self.expression( 3453 exp.LockingProperty( 3454 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3455 ) 3456 ) 3457 3458 def _parse_partition_by(self) -> list[exp.Expr]: 3459 if not self._match(TokenType.PARTITION_BY): 3460 return [] 3461 3462 if self._next.token_type in self.ID_VAR_TOKENS and self._match_text_seq("EXCLUDING"): 3463 return [ 3464 self.expression( 3465 exp.PartitionByExcluding(expressions=self._parse_csv(self._parse_disjunction)) 3466 ) 3467 ] 3468 3469 return self._parse_csv(self._parse_disjunction) 3470 3471 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3472 def _parse_partition_bound_expr() -> exp.Expr | None: 3473 if self._match_text_seq("MINVALUE"): 3474 return exp.var("MINVALUE") 3475 if self._match_text_seq("MAXVALUE"): 3476 return exp.var("MAXVALUE") 3477 return self._parse_bitwise() 3478 3479 this: exp.Expr | list[exp.Expr] | None = None 3480 expression = None 3481 from_expressions = None 3482 to_expressions = None 3483 3484 if self._match(TokenType.IN): 3485 this = self._parse_wrapped_csv(self._parse_bitwise) 3486 elif self._match(TokenType.FROM): 3487 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3488 self._match_text_seq("TO") 3489 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3490 elif self._match_text_seq("WITH", "(", "MODULUS"): 3491 this = self._parse_number() 3492 self._match_text_seq(",", "REMAINDER") 3493 expression = self._parse_number() 3494 self._match_r_paren() 3495 else: 3496 self.raise_error("Failed to parse partition bound spec.") 3497 3498 return self.expression( 3499 exp.PartitionBoundSpec( 3500 this=this, 3501 expression=expression, 3502 from_expressions=from_expressions, 3503 to_expressions=to_expressions, 3504 ) 3505 ) 3506 3507 # https://www.postgresql.org/docs/current/sql-createtable.html 3508 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3509 if not self._match_text_seq("OF"): 3510 self._retreat(self._index - 1) 3511 return None 3512 3513 this = self._parse_table(schema=True) 3514 3515 if self._match(TokenType.DEFAULT): 3516 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3517 elif self._match_text_seq("FOR", "VALUES"): 3518 expression = self._parse_partition_bound_spec() 3519 else: 3520 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3521 3522 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3523 3524 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3525 self._match(TokenType.EQ) 3526 return self.expression( 3527 exp.PartitionedByProperty( 3528 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3529 ) 3530 ) 3531 3532 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3533 if self._match_text_seq("AND", "STATISTICS"): 3534 statistics = True 3535 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3536 statistics = False 3537 else: 3538 statistics = None 3539 3540 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3541 3542 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3543 if self._match_text_seq("SQL"): 3544 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3545 return None 3546 3547 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3548 if self._match_text_seq("SQL", "DATA"): 3549 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3550 return None 3551 3552 def _parse_no_property(self) -> exp.Expr | None: 3553 if self._match_text_seq("PRIMARY", "INDEX"): 3554 return exp.NoPrimaryIndexProperty() 3555 if self._match_text_seq("SQL"): 3556 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3557 return None 3558 3559 def _parse_on_property(self) -> exp.Expr | None: 3560 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3561 return exp.OnCommitProperty() 3562 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3563 return exp.OnCommitProperty(delete=True) 3564 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3565 3566 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3567 if self._match_text_seq("SQL", "DATA"): 3568 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3569 return None 3570 3571 def _parse_distkey(self) -> exp.DistKeyProperty: 3572 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3573 3574 def _parse_create_like(self) -> exp.LikeProperty | None: 3575 table = self._parse_table(schema=True) 3576 3577 options = [] 3578 while self._match_texts(("INCLUDING", "EXCLUDING")): 3579 this = self._prev.text.upper() 3580 3581 id_var = self._parse_id_var() 3582 if not id_var: 3583 return None 3584 3585 options.append( 3586 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3587 ) 3588 3589 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3590 3591 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3592 return self.expression( 3593 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3594 ) 3595 3596 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3597 self._match(TokenType.EQ) 3598 return self.expression( 3599 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3600 ) 3601 3602 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3603 self._match_text_seq("WITH", "CONNECTION") 3604 return self.expression( 3605 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3606 ) 3607 3608 def _parse_returns(self) -> exp.ReturnsProperty: 3609 value: exp.Expr | None 3610 null = None 3611 is_table = self._match(TokenType.TABLE) 3612 3613 if is_table: 3614 if self._match(TokenType.LT): 3615 value = self.expression( 3616 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3617 ) 3618 if not self._match(TokenType.GT): 3619 self.raise_error("Expecting >") 3620 else: 3621 value = self._parse_schema(exp.var("TABLE")) 3622 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3623 null = True 3624 value = None 3625 else: 3626 value = self._parse_types() 3627 3628 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3629 3630 def _parse_describe(self) -> exp.Describe: 3631 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3632 style: str | None = ( 3633 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3634 ) 3635 if self._match(TokenType.DOT): 3636 style = None 3637 self._retreat(self._index - 2) 3638 3639 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3640 3641 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3642 this = self._parse_statement() 3643 else: 3644 this = self._parse_table(schema=True) 3645 3646 properties = self._parse_properties() 3647 expressions = properties.expressions if properties else None 3648 partition = self._parse_partition() 3649 return self.expression( 3650 exp.Describe( 3651 this=this, 3652 style=style, 3653 kind=kind, 3654 expressions=expressions, 3655 partition=partition, 3656 format=format, 3657 as_json=self._match_text_seq("AS", "JSON"), 3658 ) 3659 ) 3660 3661 def _parse_multitable_inserts( 3662 self, comments: list[str] | None, kind: str | None = None 3663 ) -> exp.MultitableInserts: 3664 kind = kind or self._prev.text.upper() 3665 expressions = [] 3666 3667 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3668 if self._match(TokenType.WHEN): 3669 expression = self._parse_disjunction() 3670 self._match(TokenType.THEN) 3671 else: 3672 expression = None 3673 3674 else_ = self._match(TokenType.ELSE) 3675 3676 if not self._match(TokenType.INTO): 3677 return None 3678 3679 return self.expression( 3680 exp.ConditionalInsert( 3681 this=self.expression( 3682 exp.Insert( 3683 this=self._parse_table(schema=True), 3684 expression=self._parse_derived_table_values(), 3685 ) 3686 ), 3687 expression=expression, 3688 else_=else_, 3689 ) 3690 ) 3691 3692 expression = parse_conditional_insert() 3693 while expression is not None: 3694 expressions.append(expression) 3695 expression = parse_conditional_insert() 3696 3697 return self.expression( 3698 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3699 comments=comments, 3700 ) 3701 3702 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3703 comments: list[str] = [] 3704 hint = self._parse_hint() 3705 overwrite = self._match(TokenType.OVERWRITE) 3706 ignore = self._match(TokenType.IGNORE) 3707 local = self._match_text_seq("LOCAL") 3708 alternative = None 3709 is_function = None 3710 3711 if self._match_text_seq("DIRECTORY"): 3712 this: exp.Expr | None = self.expression( 3713 exp.Directory( 3714 this=self._parse_var_or_string(), 3715 local=local, 3716 row_format=self._parse_row_format(match_row=True), 3717 ) 3718 ) 3719 else: 3720 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3721 comments += ensure_list(self._prev_comments) 3722 return self._parse_multitable_inserts(comments) 3723 3724 # Oracle's conditional form defaults to ALL when the keyword is omitted 3725 if self._match(TokenType.WHEN, advance=False): 3726 return self._parse_multitable_inserts(comments, kind="ALL") 3727 3728 if self._match(TokenType.OR): 3729 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3730 3731 self._match(TokenType.INTO) 3732 comments += ensure_list(self._prev_comments) 3733 self._match(TokenType.TABLE) 3734 is_function = self._match(TokenType.FUNCTION) 3735 3736 this = self._parse_function() if is_function else self._parse_insert_table() 3737 3738 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3739 set_values = None 3740 if self._match(TokenType.SET): 3741 columns = [] 3742 values = [] 3743 3744 def _parse_set_assignment() -> exp.Expr | None: 3745 target = self._parse_column() 3746 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3747 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3748 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3749 else: 3750 value = self._parse_disjunction() 3751 3752 if value: 3753 columns.append(target.this) 3754 values.append(value) 3755 return value 3756 3757 self.raise_error("Expected column assignment in INSERT ... SET") 3758 return None 3759 3760 self._parse_csv(_parse_set_assignment) 3761 3762 this = self.expression(exp.Schema(this=this, expressions=columns)) 3763 set_values = self.expression( 3764 exp.Values( 3765 expressions=[exp.Tuple(expressions=values)], 3766 alias=self._parse_table_alias(), 3767 ) 3768 ) 3769 3770 returning = self._parse_returning() # TSQL allows RETURNING before source 3771 3772 stored = self._match_text_seq("STORED") and self._parse_stored() 3773 by_name = self._match_text_seq("BY", "NAME") 3774 exists = self._parse_exists() 3775 replace_where = None 3776 replace_using = None 3777 3778 if self._match(TokenType.REPLACE): 3779 if self._match(TokenType.WHERE): 3780 replace_where = self._parse_disjunction() 3781 elif self._match(TokenType.USING): 3782 replace_using = self._parse_using_identifiers() 3783 3784 return self.expression( 3785 exp.Insert( 3786 hint=hint, 3787 is_function=is_function, 3788 this=this, 3789 stored=stored, 3790 by_name=by_name, 3791 exists=exists, 3792 where=replace_where, 3793 using=replace_using, 3794 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3795 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3796 default=self._match_text_seq("DEFAULT", "VALUES"), 3797 expression=set_values 3798 or self._parse_derived_table_values(allow_value_synonym=True) 3799 or self._parse_ddl_select(), 3800 conflict=self._parse_on_conflict(), 3801 returning=returning or self._parse_returning(), 3802 overwrite=overwrite, 3803 alternative=alternative, 3804 ignore=ignore, 3805 source=self._match(TokenType.TABLE) and self._parse_table(), 3806 ), 3807 comments=comments, 3808 ) 3809 3810 def _parse_insert_table(self) -> exp.Expr | None: 3811 this = self._parse_table(schema=True, parse_partition=True) 3812 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3813 this.set("alias", self._parse_table_alias()) 3814 return this 3815 3816 def _parse_kill(self) -> exp.Kill: 3817 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3818 3819 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3820 3821 def _parse_on_conflict(self) -> exp.OnConflict | None: 3822 conflict = self._match_text_seq("ON", "CONFLICT") 3823 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3824 3825 if not conflict and not duplicate: 3826 return None 3827 3828 conflict_keys = None 3829 constraint = None 3830 3831 if conflict: 3832 if self._match_text_seq("ON", "CONSTRAINT"): 3833 constraint = self._parse_id_var() 3834 elif self._match(TokenType.L_PAREN): 3835 conflict_keys = self._parse_csv(self._parse_indexed_column) 3836 self._match_r_paren() 3837 3838 index_predicate = self._parse_where() 3839 3840 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3841 if self._prev.token_type == TokenType.UPDATE: 3842 self._match(TokenType.SET) 3843 expressions = self._parse_csv(self._parse_update_assignment) 3844 else: 3845 expressions = None 3846 3847 return self.expression( 3848 exp.OnConflict( 3849 duplicate=duplicate, 3850 expressions=expressions, 3851 action=action, 3852 conflict_keys=conflict_keys, 3853 index_predicate=index_predicate, 3854 constraint=constraint, 3855 where=self._parse_where(), 3856 ) 3857 ) 3858 3859 def _parse_returning(self) -> exp.Returning | None: 3860 if not self._match(TokenType.RETURNING): 3861 return None 3862 return self.expression( 3863 exp.Returning( 3864 expressions=self._parse_csv(self._parse_expression), 3865 into=self._match(TokenType.INTO) and self._parse_table_part(), 3866 ) 3867 ) 3868 3869 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3870 if not self._match(TokenType.FORMAT): 3871 return None 3872 return self._parse_row_format() 3873 3874 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3875 index = self._index 3876 with_ = with_ or self._match_text_seq("WITH") 3877 3878 if not self._match(TokenType.SERDE_PROPERTIES): 3879 self._retreat(index) 3880 return None 3881 return self.expression( 3882 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3883 ) 3884 3885 def _parse_row_format( 3886 self, match_row: bool = False 3887 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3888 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3889 return None 3890 3891 if self._match_text_seq("SERDE"): 3892 this = self._parse_string() 3893 3894 serde_properties = self._parse_serde_properties() 3895 3896 return self.expression( 3897 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3898 ) 3899 3900 self._match_text_seq("DELIMITED") 3901 3902 kwargs = {} 3903 3904 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3905 kwargs["fields"] = self._parse_string() 3906 if self._match_text_seq("ESCAPED", "BY"): 3907 kwargs["escaped"] = self._parse_string() 3908 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3909 kwargs["collection_items"] = self._parse_string() 3910 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3911 kwargs["map_keys"] = self._parse_string() 3912 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3913 kwargs["lines"] = self._parse_string() 3914 if self._match_text_seq("NULL", "DEFINED", "AS"): 3915 kwargs["null"] = self._parse_string() 3916 3917 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3918 3919 def _parse_load(self) -> exp.LoadData | exp.Command: 3920 if self._match_text_seq("DATA"): 3921 local = self._match_text_seq("LOCAL") 3922 self._match_text_seq("INPATH") 3923 inpath = self._parse_string() 3924 overwrite = self._match(TokenType.OVERWRITE) 3925 temp: bool | None = None 3926 if self._match(TokenType.INTO): 3927 temp = self._match(TokenType.TEMPORARY) 3928 self._match(TokenType.TABLE) 3929 3930 return self.expression( 3931 exp.LoadData( 3932 this=self._parse_table(schema=True), 3933 local=local, 3934 overwrite=overwrite, 3935 temp=temp, 3936 inpath=inpath, 3937 files=self._match_text_seq("FROM", "FILES") 3938 and exp.Properties(expressions=self._parse_wrapped_properties()), 3939 partition=self._parse_partition(), 3940 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3941 serde=self._match_text_seq("SERDE") and self._parse_string(), 3942 ) 3943 ) 3944 return self._parse_as_command(self._prev) 3945 3946 def _parse_delete(self) -> exp.Delete: 3947 hint = self._parse_hint() 3948 3949 # This handles MySQL's "Multiple-Table Syntax" 3950 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3951 tables = None 3952 if not self._match(TokenType.FROM, advance=False): 3953 tables = self._parse_csv(self._parse_table) or None 3954 3955 returning = self._parse_returning() 3956 3957 return self.expression( 3958 exp.Delete( 3959 hint=hint, 3960 tables=tables, 3961 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3962 using=self._match(TokenType.USING) 3963 and self._parse_csv(lambda: self._parse_table(joins=True)), 3964 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3965 where=self._parse_where(), 3966 returning=returning or self._parse_returning(), 3967 order=self._parse_order(), 3968 limit=self._parse_limit(), 3969 ) 3970 ) 3971 3972 def _parse_update_assignment(self) -> exp.Expr | None: 3973 this = self._parse_comparison() 3974 if self._match(TokenType.EQ): 3975 comments = self._prev_comments 3976 this = self.expression( 3977 exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments 3978 ) 3979 return this 3980 3981 def _parse_update(self) -> exp.Update: 3982 hint = self._parse_hint() 3983 kwargs: dict[str, object] = { 3984 "hint": hint, 3985 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3986 } 3987 while self._curr: 3988 if self._match(TokenType.SET): 3989 kwargs["expressions"] = self._parse_csv(self._parse_update_assignment) 3990 elif self._match(TokenType.RETURNING, advance=False): 3991 kwargs["returning"] = self._parse_returning() 3992 elif self._match(TokenType.FROM, advance=False): 3993 from_ = self._parse_from(joins=True) 3994 table = from_.this if from_ else None 3995 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3996 table.set("joins", list(self._parse_joins()) or None) 3997 3998 kwargs["from_"] = from_ 3999 elif self._match(TokenType.WHERE, advance=False): 4000 kwargs["where"] = self._parse_where() 4001 elif self._match(TokenType.ORDER_BY, advance=False): 4002 kwargs["order"] = self._parse_order() 4003 elif self._match(TokenType.LIMIT, advance=False): 4004 kwargs["limit"] = self._parse_limit() 4005 else: 4006 break 4007 4008 return self.expression(exp.Update(**kwargs)) 4009 4010 def _parse_use(self) -> exp.Use: 4011 return self.expression( 4012 exp.Use( 4013 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 4014 this=self._parse_table(schema=False), 4015 ) 4016 ) 4017 4018 def _parse_uncache(self) -> exp.Uncache: 4019 if not self._match(TokenType.TABLE): 4020 self.raise_error("Expecting TABLE after UNCACHE") 4021 4022 return self.expression( 4023 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 4024 ) 4025 4026 def _parse_cache(self) -> exp.Cache: 4027 lazy = self._match_text_seq("LAZY") 4028 self._match(TokenType.TABLE) 4029 table = self._parse_table(schema=True) 4030 4031 options = [] 4032 if self._match_text_seq("OPTIONS"): 4033 self._match_l_paren() 4034 k = self._parse_string() 4035 self._match(TokenType.EQ) 4036 v = self._parse_string() 4037 options = [k, v] 4038 self._match_r_paren() 4039 4040 self._match(TokenType.ALIAS) 4041 return self.expression( 4042 exp.Cache( 4043 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 4044 ) 4045 ) 4046 4047 def _parse_partition(self) -> exp.Partition | None: 4048 if not self._match_texts(self.PARTITION_KEYWORDS): 4049 return None 4050 4051 return self.expression( 4052 exp.Partition( 4053 subpartition=self._prev.text.upper() == "SUBPARTITION", 4054 expressions=self._parse_wrapped_csv(self._parse_disjunction), 4055 ) 4056 ) 4057 4058 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 4059 def _parse_value_expression() -> exp.Expr | None: 4060 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 4061 return exp.var(self._prev.text.upper()) 4062 return self._parse_expression() 4063 4064 if self._match(TokenType.L_PAREN): 4065 expressions = self._parse_csv(_parse_value_expression) 4066 self._match_r_paren() 4067 return self.expression(exp.Tuple(expressions=expressions)) 4068 4069 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 4070 expression = self._parse_expression() 4071 if expression: 4072 return self.expression(exp.Tuple(expressions=[expression])) 4073 return None 4074 4075 def _parse_projections( 4076 self, 4077 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 4078 return self._parse_expressions(), None 4079 4080 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 4081 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 4082 this: exp.Expr | None = self._parse_simplified_pivot( 4083 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 4084 ) 4085 else: 4086 # Support parentheses for duckdb FROM-first syntax 4087 this = ( 4088 self._parse_table(consume_pipe=True) 4089 if table and not self._match(TokenType.FROM, advance=False) 4090 else self._parse_select(nested=True, parse_set_operation=False) 4091 ) 4092 4093 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 4094 # in case a modifier (e.g. join) is following 4095 if table and isinstance(this, exp.Values) and this.alias: 4096 alias = this.args["alias"].pop() 4097 this = exp.Table(this=this, alias=alias) 4098 4099 this = self._parse_query_modifiers(self._parse_set_operations(this)) 4100 4101 return this 4102 4103 def _parse_select( 4104 self, 4105 nested: bool = False, 4106 table: bool = False, 4107 parse_subquery_alias: bool = True, 4108 parse_set_operation: bool = True, 4109 consume_pipe: bool = True, 4110 ) -> exp.Expr | None: 4111 query = self._parse_select_query( 4112 nested=nested, 4113 table=table, 4114 parse_subquery_alias=parse_subquery_alias, 4115 parse_set_operation=parse_set_operation, 4116 ) 4117 4118 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4119 if isinstance(query, exp.Query): 4120 query = self._parse_pipe_syntax_query(query) 4121 query = query.subquery(copy=False) if query and table else query 4122 4123 return query 4124 4125 def _parse_select_query( 4126 self, 4127 nested: bool = False, 4128 table: bool = False, 4129 parse_subquery_alias: bool = True, 4130 parse_set_operation: bool = True, 4131 ) -> exp.Expr | None: 4132 cte = self._parse_with() 4133 4134 if cte: 4135 this = self._parse_statement() 4136 4137 if not this: 4138 self.raise_error("Failed to parse any statement following CTE") 4139 return cte 4140 4141 while isinstance(this, exp.Subquery) and this.is_wrapper: 4142 this = this.this 4143 4144 assert this is not None 4145 if "with_" in this.arg_types: 4146 if inner_cte := this.args.get("with_"): 4147 cte.set("expressions", cte.expressions + inner_cte.expressions) 4148 if inner_cte.args.get("recursive"): 4149 cte.set("recursive", True) 4150 this.set("with_", cte) 4151 else: 4152 self.raise_error(f"{this.key} does not support CTE") 4153 this = cte 4154 4155 return this 4156 4157 # duckdb supports leading with FROM x 4158 from_ = ( 4159 self._parse_from(joins=True, consume_pipe=True) 4160 if self._match(TokenType.FROM, advance=False) 4161 else None 4162 ) 4163 laterals = list(iter(self._parse_lateral, None)) if from_ else None 4164 4165 # Hive supports multi-table inserts: FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 4166 if from_ and self._match(TokenType.INSERT, advance=False): 4167 source = from_.this 4168 source.set("laterals", laterals or None) 4169 4170 inserts = [] 4171 while self._match(TokenType.INSERT, advance=False): 4172 inserts.append(self._parse_statement()) 4173 4174 return self.expression( 4175 exp.MultitableInserts(expressions=inserts, source=source), 4176 comments=from_.comments, 4177 ) 4178 4179 if self._match(TokenType.SELECT): 4180 comments = self._prev_comments 4181 4182 hint = self._parse_hint() 4183 4184 if self._next and not self._next.token_type == TokenType.DOT: 4185 all_ = self._match(TokenType.ALL) 4186 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4187 else: 4188 all_, matched_distinct = None, False 4189 4190 kind = ( 4191 self._prev.text.upper() 4192 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4193 else None 4194 ) 4195 4196 distinct: exp.Expr | None = ( 4197 self.expression( 4198 exp.Distinct( 4199 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4200 ) 4201 ) 4202 if matched_distinct 4203 else None 4204 ) 4205 4206 operation_modifiers = [] 4207 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4208 operation_modifiers.append(exp.var(self._prev.text.upper())) 4209 4210 limit = self._parse_limit(top=True) 4211 4212 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4213 if limit and not matched_distinct and not all_: 4214 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4215 if matched_distinct: 4216 distinct = self.expression( 4217 exp.Distinct( 4218 on=self._parse_value(values=False) 4219 if self._match(TokenType.ON) 4220 else None 4221 ) 4222 ) 4223 else: 4224 all_ = self._match(TokenType.ALL) 4225 4226 if all_ and distinct: 4227 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4228 4229 projections, exclude = self._parse_projections() 4230 4231 this = self.expression( 4232 exp.Select( 4233 kind=kind, 4234 hint=hint, 4235 distinct=distinct, 4236 expressions=projections, 4237 limit=limit, 4238 exclude=exclude, 4239 operation_modifiers=operation_modifiers or None, 4240 ) 4241 ) 4242 this.comments = comments 4243 4244 into = self._parse_into() 4245 if into: 4246 this.set("into", into) 4247 4248 if not from_: 4249 from_ = self._parse_from() 4250 4251 if from_: 4252 this.set("from_", from_) 4253 4254 this = self._parse_query_modifiers(this) 4255 elif (table or nested) and self._match(TokenType.L_PAREN): 4256 comments = self._prev_comments 4257 this = self._parse_wrapped_select(table=table) 4258 4259 if this: 4260 this.add_comments(comments, prepend=True) 4261 4262 # We return early here so that the UNION isn't attached to the subquery by the 4263 # following call to _parse_set_operations, but instead becomes the parent node 4264 self._match_r_paren() 4265 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4266 elif self._match(TokenType.VALUES, advance=False): 4267 this = self._parse_derived_table_values() 4268 elif from_: 4269 this = exp.select("*").from_(from_.this, copy=False) 4270 this = self._parse_query_modifiers(this) 4271 elif self._match(TokenType.SUMMARIZE): 4272 table = self._match(TokenType.TABLE) 4273 this = self._parse_select() or self._parse_string() or self._parse_table() 4274 return self.expression(exp.Summarize(this=this, table=table)) 4275 elif self._match(TokenType.DESCRIBE): 4276 this = self._parse_describe() 4277 else: 4278 this = None 4279 4280 if laterals and isinstance(this, exp.Select): 4281 this.set("laterals", [*laterals, *(this.args.get("laterals") or [])]) 4282 4283 return self._parse_set_operations(this) if parse_set_operation else this 4284 4285 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4286 self._match_text_seq("SEARCH") 4287 4288 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4289 4290 if not kind: 4291 return None 4292 4293 self._match_text_seq("FIRST", "BY") 4294 4295 return self.expression( 4296 exp.RecursiveWithSearch( 4297 kind=kind, 4298 this=self._parse_id_var(), 4299 expression=self._match_text_seq("SET") and self._parse_id_var(), 4300 to=self._match_text_seq("TO") and self._parse_bitwise(), 4301 default=self._match(TokenType.DEFAULT) and self._parse_bitwise(), 4302 using=self._match_text_seq("USING") and self._parse_id_var(), 4303 ) 4304 ) 4305 4306 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4307 if not skip_with_token and not self._match(TokenType.WITH): 4308 return None 4309 4310 comments = self._prev_comments 4311 recursive = self._match(TokenType.RECURSIVE) 4312 4313 last_comments = None 4314 expressions = [] 4315 udfs = [] 4316 while True: 4317 cte = self._parse_cte() 4318 if cte: 4319 if isinstance(cte, exp.FunctionSpecification): 4320 udfs.append(cte) 4321 else: 4322 expressions.append(cte) 4323 4324 if last_comments: 4325 cte.add_comments(last_comments) 4326 4327 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4328 break 4329 else: 4330 self._match(TokenType.WITH) 4331 recursive = self._match(TokenType.RECURSIVE) or recursive 4332 4333 last_comments = self._prev_comments 4334 4335 search = self._parse_recursive_with_search() 4336 4337 # A SEARCH clause can be followed by a CYCLE clause 4338 cycle = ( 4339 self._parse_recursive_with_search() 4340 if search 4341 and search.args.get("kind") != "CYCLE" 4342 and self._match_text_seq("CYCLE", advance=False) 4343 else None 4344 ) 4345 4346 return self.expression( 4347 exp.With( 4348 expressions=expressions, 4349 recursive=recursive or None, 4350 search=search, 4351 cycle=cycle, 4352 udfs=udfs or None, 4353 ), 4354 comments=comments, 4355 ) 4356 4357 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4358 index = self._index 4359 4360 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4361 if not alias or not alias.this: 4362 self.raise_error("Expected CTE to have alias") 4363 4364 key_expressions = ( 4365 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4366 ) 4367 4368 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4369 self._retreat(index) 4370 return None 4371 4372 comments = self._prev_comments 4373 4374 if self._match_text_seq("NOT", "MATERIALIZED"): 4375 materialized = False 4376 elif self._match_text_seq("MATERIALIZED"): 4377 materialized = True 4378 else: 4379 materialized = None 4380 4381 cte = self.expression( 4382 exp.CTE( 4383 this=self._parse_wrapped(self._parse_statement), 4384 alias=alias, 4385 materialized=materialized, 4386 key_expressions=key_expressions, 4387 ), 4388 comments=comments, 4389 ) 4390 4391 values = cte.this 4392 if isinstance(values, exp.Values): 4393 cte.set("this", self._values_to_select(values)) 4394 4395 return cte 4396 4397 def _values_to_select(self, values: exp.Values) -> exp.Select: 4398 if values.alias: 4399 return exp.select("*").from_(values) 4400 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4401 4402 def _parse_table_alias( 4403 self, alias_tokens: t.Collection[TokenType] | None = None 4404 ) -> exp.TableAlias | None: 4405 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4406 # so this section tries to parse the clause version and if it fails, it treats the token 4407 # as an identifier (alias) 4408 if self._can_parse_limit_or_offset(): 4409 return None 4410 4411 # START is never treated as an implicit alias when followed by WITH, since that 4412 # would swallow the beginning of a START WITH ... CONNECT BY clause 4413 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4414 return None 4415 4416 any_token = self._match(TokenType.ALIAS) 4417 alias = ( 4418 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4419 or self._parse_string_as_identifier() 4420 ) 4421 4422 index = self._index 4423 if self._match(TokenType.L_PAREN): 4424 columns = self._parse_csv(self._parse_function_parameter) 4425 self._match_r_paren() if columns else self._retreat(index) 4426 else: 4427 columns = None 4428 4429 if not alias and not columns: 4430 return None 4431 4432 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4433 4434 # We bubble up comments from the Identifier to the TableAlias 4435 if isinstance(alias, exp.Identifier): 4436 table_alias.add_comments(alias.pop_comments()) 4437 4438 return table_alias 4439 4440 def _parse_subquery( 4441 self, this: exp.Expr | None, parse_alias: bool = True 4442 ) -> exp.Subquery | None: 4443 if not this: 4444 return None 4445 4446 return self.expression( 4447 exp.Subquery( 4448 this=this, 4449 pivots=self._parse_pivots(), 4450 alias=self._parse_table_alias() if parse_alias else None, 4451 sample=self._parse_table_sample(), 4452 ) 4453 ) 4454 4455 def _implicit_unnests_to_explicit(self, this: E) -> E: 4456 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4457 4458 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4459 for i, join in enumerate(this.args.get("joins") or []): 4460 table = join.this 4461 normalized_table = table.copy() 4462 normalized_table.meta["maybe_column"] = True 4463 normalized_table = _norm(normalized_table, dialect=self.dialect) 4464 4465 if isinstance(table, exp.Table) and not join.args.get("on"): 4466 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4467 table_as_column = table.to_column() 4468 unnest = exp.Unnest(expressions=[table_as_column]) 4469 4470 # Table.to_column creates a parent Alias node that we want to convert to 4471 # a TableAlias and attach to the Unnest, so it matches the parser's output 4472 if isinstance(table.args.get("alias"), exp.TableAlias): 4473 table_as_column.replace(table_as_column.this) 4474 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4475 4476 table.replace(unnest) 4477 4478 refs.add(normalized_table.alias_or_name) 4479 4480 return this 4481 4482 @t.overload 4483 def _parse_query_modifiers(self, this: E) -> E: ... 4484 4485 @t.overload 4486 def _parse_query_modifiers(self, this: None) -> None: ... 4487 4488 def _parse_query_modifiers(self, this): 4489 if isinstance(this, self.MODIFIABLES): 4490 for join in self._parse_joins(): 4491 this.append("joins", join) 4492 for lateral in iter(self._parse_lateral, None): 4493 this.append("laterals", lateral) 4494 4495 while True: 4496 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4497 modifier_token = self._curr 4498 4499 # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result 4500 # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union 4501 if ( 4502 modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH) 4503 and (limit := this.args.get("limit")) 4504 and limit.meta.get("top") 4505 ): 4506 break 4507 4508 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4509 key, expression = parser(self) 4510 4511 if expression: 4512 if this.args.get(key): 4513 self.raise_error( 4514 f"Found multiple '{modifier_token.text.upper()}' clauses", 4515 token=modifier_token, 4516 ) 4517 4518 this.set(key, expression) 4519 if key == "limit": 4520 offset = expression.args.get("offset") 4521 expression.set("offset", None) 4522 4523 if offset: 4524 if this.args.get("offset"): 4525 self.raise_error( 4526 "Found multiple 'OFFSET' clauses", token=modifier_token 4527 ) 4528 4529 offset = exp.Offset(expression=offset) 4530 this.set("offset", offset) 4531 4532 limit_by_expressions = expression.expressions 4533 expression.set("expressions", None) 4534 offset.set("expressions", limit_by_expressions) 4535 continue 4536 4537 if self._curr.text.upper() == "START": 4538 modifier_token = self._curr 4539 connect = self._parse_connect() 4540 if connect: 4541 if this.args.get("connect"): 4542 self.raise_error( 4543 "Found multiple 'START WITH' clauses", token=modifier_token 4544 ) 4545 4546 this.set("connect", connect) 4547 continue 4548 break 4549 4550 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4551 this = self._implicit_unnests_to_explicit(this) 4552 4553 return this 4554 4555 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4556 start = self._curr 4557 while self._curr: 4558 self._advance() 4559 4560 end = self._tokens[self._index - 1] 4561 return exp.Hint(expressions=[self._find_sql(start, end)]) 4562 4563 def _parse_hint_function_call(self) -> exp.Expr | None: 4564 return self._parse_function_call() 4565 4566 def _parse_hint_body(self) -> exp.Hint | None: 4567 start_index = self._index 4568 should_fallback_to_string = False 4569 4570 hints = [] 4571 try: 4572 for hint in iter( 4573 lambda: self._parse_csv( 4574 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4575 ), 4576 [], 4577 ): 4578 hints.extend(hint) 4579 except ParseError: 4580 should_fallback_to_string = True 4581 4582 if should_fallback_to_string or self._curr: 4583 self._retreat(start_index) 4584 return self._parse_hint_fallback_to_string() 4585 4586 return self.expression(exp.Hint(expressions=hints)) 4587 4588 def _parse_hint(self) -> exp.Hint | None: 4589 if self._match(TokenType.HINT) and self._prev_comments: 4590 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4591 4592 return None 4593 4594 def _parse_into(self) -> exp.Into | None: 4595 if not self._match(TokenType.INTO): 4596 return None 4597 4598 temp = self._match(TokenType.TEMPORARY) 4599 unlogged = self._match_text_seq("UNLOGGED") 4600 self._match(TokenType.TABLE) 4601 4602 return self.expression( 4603 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4604 ) 4605 4606 def _parse_from( 4607 self, 4608 joins: bool = False, 4609 skip_from_token: bool = False, 4610 consume_pipe: bool = False, 4611 ) -> exp.From | None: 4612 if not skip_from_token and not self._match(TokenType.FROM): 4613 return None 4614 4615 comments = self._prev_comments 4616 return self.expression( 4617 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4618 comments=comments, 4619 ) 4620 4621 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4622 return self.expression( 4623 exp.MatchRecognizeMeasure( 4624 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4625 this=self._parse_expression(), 4626 ) 4627 ) 4628 4629 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4630 if not self._match(TokenType.MATCH_RECOGNIZE): 4631 return None 4632 4633 self._match_l_paren() 4634 4635 partition = self._parse_partition_by() 4636 order = self._parse_order() 4637 4638 measures = ( 4639 self._parse_csv(self._parse_match_recognize_measure) 4640 if self._match_text_seq("MEASURES") 4641 else None 4642 ) 4643 4644 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4645 rows = exp.var("ONE ROW PER MATCH") 4646 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4647 text = "ALL ROWS PER MATCH" 4648 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4649 text += " SHOW EMPTY MATCHES" 4650 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4651 text += " OMIT EMPTY MATCHES" 4652 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4653 text += " WITH UNMATCHED ROWS" 4654 rows = exp.var(text) 4655 else: 4656 rows = None 4657 4658 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4659 text = "AFTER MATCH SKIP" 4660 if self._match_text_seq("PAST", "LAST", "ROW"): 4661 text += " PAST LAST ROW" 4662 elif self._match_text_seq("TO", "NEXT", "ROW"): 4663 text += " TO NEXT ROW" 4664 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4665 direction = self._prev.text.upper() 4666 pattern_var = self._advance_any() 4667 if not pattern_var: 4668 self.raise_error( 4669 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4670 ) 4671 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4672 after = exp.var(text) 4673 else: 4674 after = None 4675 4676 if self._match_text_seq("PATTERN"): 4677 self._match_l_paren() 4678 4679 if not self._curr: 4680 self.raise_error("Expecting )", self._curr) 4681 4682 paren = 1 4683 start = self._curr 4684 4685 while self._curr and paren > 0: 4686 if self._curr.token_type == TokenType.L_PAREN: 4687 paren += 1 4688 if self._curr.token_type == TokenType.R_PAREN: 4689 paren -= 1 4690 4691 end = self._prev 4692 self._advance() 4693 4694 if paren > 0: 4695 self.raise_error("Expecting )", self._curr) 4696 4697 pattern = exp.var(self._find_sql(start, end)) 4698 else: 4699 pattern = None 4700 4701 define = ( 4702 self._parse_csv(self._parse_name_as_expression) 4703 if self._match_text_seq("DEFINE") 4704 else None 4705 ) 4706 4707 self._match_r_paren() 4708 4709 return self.expression( 4710 exp.MatchRecognize( 4711 partition_by=partition, 4712 order=order, 4713 measures=measures, 4714 rows=rows, 4715 after=after, 4716 pattern=pattern, 4717 define=define, 4718 alias=self._parse_table_alias(), 4719 ) 4720 ) 4721 4722 def _parse_lateral(self) -> exp.Lateral | None: 4723 cross_apply: bool | None = None 4724 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4725 cross_apply = True 4726 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4727 cross_apply = False 4728 4729 if cross_apply is not None: 4730 this = self._parse_select(table=True) 4731 view = None 4732 outer = None 4733 elif self._match(TokenType.LATERAL): 4734 this = self._parse_select(table=True) 4735 view = self._match(TokenType.VIEW) 4736 outer = self._match(TokenType.OUTER) 4737 else: 4738 return None 4739 4740 if not this: 4741 this = ( 4742 self._parse_unnest() 4743 or self._parse_function() 4744 or self._parse_id_var(any_token=False) 4745 ) 4746 4747 while self._match(TokenType.DOT): 4748 this = exp.Dot( 4749 this=this, 4750 expression=self._parse_function() or self._parse_id_var(any_token=False), 4751 ) 4752 4753 ordinality: bool | None = None 4754 4755 if view: 4756 table = self._parse_id_var(any_token=False) 4757 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4758 table_alias: exp.TableAlias | None = self.expression( 4759 exp.TableAlias(this=table, columns=columns) 4760 ) 4761 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4762 # We move the alias from the lateral's child node to the lateral itself 4763 table_alias = this.args["alias"].pop() 4764 else: 4765 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4766 table_alias = self._parse_table_alias() 4767 4768 return self.expression( 4769 exp.Lateral( 4770 this=this, 4771 view=view, 4772 outer=outer, 4773 alias=table_alias, 4774 cross_apply=cross_apply, 4775 ordinality=ordinality, 4776 ) 4777 ) 4778 4779 def _parse_stream(self) -> exp.Stream | None: 4780 index = self._index 4781 if self._match(TokenType.STREAM): 4782 if this := self._try_parse(self._parse_table): 4783 return self.expression(exp.Stream(this=this)) 4784 self._retreat(index) 4785 return None 4786 4787 def _parse_join_parts( 4788 self, 4789 ) -> tuple[Token | None, Token | None, Token | None]: 4790 return ( 4791 self._prev if self._match_set(self.JOIN_METHODS) else None, 4792 self._prev if self._match_set(self.JOIN_SIDES) else None, 4793 self._prev if self._match_set(self.JOIN_KINDS) else None, 4794 ) 4795 4796 def _parse_using_identifiers(self) -> list[exp.Expr]: 4797 def _parse_column_as_identifier() -> exp.Expr | None: 4798 this = self._parse_column() 4799 if isinstance(this, exp.Column): 4800 return this.this 4801 return this 4802 4803 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4804 4805 def _parse_join( 4806 self, 4807 skip_join_token: bool = False, 4808 parse_bracket: bool = False, 4809 alias_tokens: t.Collection[TokenType] | None = None, 4810 ) -> exp.Join | None: 4811 if self._match(TokenType.COMMA): 4812 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4813 cross_join = self.expression(exp.Join(this=table)) if table else None 4814 4815 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4816 cross_join.set("kind", "CROSS") 4817 4818 return cross_join 4819 4820 index = self._index 4821 method, side, kind = self._parse_join_parts() 4822 directed = self._match_text_seq("DIRECTED") 4823 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4824 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4825 join_comments = self._prev_comments 4826 4827 if not skip_join_token and not join: 4828 self._retreat(index) 4829 kind = None 4830 method = None 4831 side = None 4832 4833 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4834 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4835 4836 if not skip_join_token and not join and not outer_apply and not cross_apply: 4837 return None 4838 4839 kwargs: dict[str, t.Any] = { 4840 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4841 } 4842 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4843 kwargs["expressions"] = self._parse_csv( 4844 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4845 ) 4846 4847 if method: 4848 kwargs["method"] = method.text.upper() 4849 if side: 4850 kwargs["side"] = side.text.upper() 4851 if kind: 4852 kwargs["kind"] = kind.text.upper() 4853 if hint: 4854 kwargs["hint"] = hint 4855 4856 if self._match(TokenType.MATCH_CONDITION): 4857 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4858 4859 if self._match(TokenType.ON): 4860 kwargs["on"] = self._parse_disjunction() 4861 elif self._match(TokenType.USING): 4862 kwargs["using"] = self._parse_using_identifiers() 4863 elif ( 4864 not method 4865 and not (outer_apply or cross_apply) 4866 and not isinstance(kwargs["this"], exp.Unnest) 4867 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4868 ): 4869 index = self._index 4870 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4871 4872 if joins and self._match(TokenType.ON): 4873 kwargs["on"] = self._parse_disjunction() 4874 elif joins and self._match(TokenType.USING): 4875 kwargs["using"] = self._parse_using_identifiers() 4876 else: 4877 joins = None 4878 self._retreat(index) 4879 4880 kwargs["this"].set("joins", joins if joins else None) 4881 4882 kwargs["pivots"] = self._parse_pivots() 4883 4884 comments = [c for token in (method, side, kind) if token for c in token.comments] 4885 comments = (join_comments or []) + comments 4886 4887 if ( 4888 self.ADD_JOIN_ON_TRUE 4889 and not kwargs.get("on") 4890 and not kwargs.get("using") 4891 and not kwargs.get("method") 4892 and kwargs.get("kind") in (None, "INNER", "OUTER") 4893 ): 4894 kwargs["on"] = exp.true() 4895 4896 if directed: 4897 kwargs["directed"] = directed 4898 4899 return self.expression(exp.Join(**kwargs), comments=comments) 4900 4901 def _parse_opclass(self) -> exp.Expr | None: 4902 this = self._parse_disjunction() 4903 4904 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4905 return this 4906 4907 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4908 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4909 4910 return this 4911 4912 def _parse_index_params(self) -> exp.IndexParameters: 4913 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4914 4915 if self._match(TokenType.L_PAREN, advance=False): 4916 columns = self._parse_wrapped_csv(self._parse_with_operator) 4917 else: 4918 columns = None 4919 4920 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4921 partition_by = self._parse_partition_by() 4922 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4923 tablespace = ( 4924 self._parse_var(any_token=True) 4925 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4926 else None 4927 ) 4928 where = self._parse_where() 4929 4930 on = self._parse_field() if self._match(TokenType.ON) else None 4931 4932 return self.expression( 4933 exp.IndexParameters( 4934 using=using, 4935 columns=columns, 4936 include=include, 4937 partition_by=partition_by, 4938 where=where, 4939 with_storage=with_storage, 4940 tablespace=tablespace, 4941 on=on, 4942 ) 4943 ) 4944 4945 def _parse_index( 4946 self, index: exp.Expr | None = None, anonymous: bool = False 4947 ) -> exp.Index | None: 4948 if index or anonymous: 4949 unique = None 4950 primary = None 4951 amp = None 4952 4953 self._match(TokenType.ON) 4954 self._match(TokenType.TABLE) # hive 4955 table = self._parse_table_parts(schema=True) 4956 else: 4957 unique = self._match(TokenType.UNIQUE) 4958 primary = self._match_text_seq("PRIMARY") 4959 amp = self._match_text_seq("AMP") 4960 4961 if not self._match(TokenType.INDEX): 4962 return None 4963 4964 index = self._parse_id_var() 4965 table = None 4966 4967 params = self._parse_index_params() 4968 4969 return self.expression( 4970 exp.Index( 4971 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4972 ) 4973 ) 4974 4975 def _parse_table_hints(self) -> list[exp.Expr] | None: 4976 hints: list[exp.Expr] = [] 4977 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4978 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4979 hints.append( 4980 self.expression( 4981 exp.WithTableHint( 4982 expressions=self._parse_csv( 4983 lambda: self._parse_function() or self._parse_var(any_token=True) 4984 ) 4985 ) 4986 ) 4987 ) 4988 self._match_r_paren() 4989 else: 4990 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4991 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4992 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4993 4994 self._match_set((TokenType.INDEX, TokenType.KEY)) 4995 if self._match(TokenType.FOR): 4996 hint.set("target", self._advance_any() and self._prev.text.upper()) 4997 4998 hint.set("expressions", self._parse_wrapped_id_vars()) 4999 hints.append(hint) 5000 5001 return hints or None 5002 5003 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 5004 return ( 5005 (not schema and self._parse_function(optional_parens=False)) 5006 or self._parse_id_var(any_token=False) 5007 or self._parse_string_as_identifier() 5008 or self._parse_placeholder() 5009 ) 5010 5011 def _parse_table_parts_fast(self) -> exp.Table | None: 5012 index = self._index 5013 parts: list[exp.Identifier] | None = None 5014 all_comments: list[str] | None = None 5015 5016 while self._match_set(self.IDENTIFIER_TOKENS): 5017 token = self._prev 5018 comments = self._prev_comments 5019 5020 has_dot = self._match(TokenType.DOT) 5021 curr_tt = self._curr.token_type 5022 5023 if not has_dot: 5024 if curr_tt in self.TABLE_POSTFIX_TOKENS: 5025 self._retreat(index) 5026 return None 5027 elif curr_tt not in self.IDENTIFIER_TOKENS: 5028 self._retreat(index) 5029 return None 5030 5031 if parts is None: 5032 parts = [] 5033 5034 if comments: 5035 if all_comments is None: 5036 all_comments = [] 5037 all_comments.extend(comments) 5038 self._prev_comments = [] 5039 5040 parts.append( 5041 self.expression( 5042 exp.Identifier( 5043 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 5044 ), 5045 token, 5046 ) 5047 ) 5048 5049 if not has_dot: 5050 break 5051 5052 if parts is None: 5053 return None 5054 5055 n = len(parts) 5056 5057 if n == 1: 5058 table: exp.Table = exp.Table(this=parts[0]) 5059 elif n == 2: 5060 table = exp.Table(this=parts[1], db=parts[0]) 5061 elif n >= 3: 5062 this: exp.Identifier | exp.Dot = parts[2] 5063 for i in range(3, n): 5064 this = exp.Dot(this=this, expression=parts[i]) 5065 5066 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 5067 5068 if table is None: 5069 self._retreat(index) 5070 elif all_comments: 5071 table.add_comments(all_comments) 5072 return table 5073 5074 def _parse_table_parts( 5075 self, 5076 schema: bool = False, 5077 is_db_reference: bool = False, 5078 wildcard: bool = False, 5079 fast: bool = False, 5080 ) -> exp.Table | exp.Dot | None: 5081 if fast: 5082 return self._parse_table_parts_fast() 5083 5084 catalog: exp.Expr | str | None = None 5085 db: exp.Expr | str | None = None 5086 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 5087 5088 while self._match(TokenType.DOT): 5089 if catalog: 5090 # This allows nesting the table in arbitrarily many dot expressions if needed 5091 table = self.expression( 5092 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 5093 ) 5094 else: 5095 catalog = db 5096 db = table 5097 # "" used for tsql FROM a..b case 5098 table = self._parse_table_part(schema=schema) or "" 5099 5100 if ( 5101 wildcard 5102 and self._is_connected() 5103 and (isinstance(table, exp.Identifier) or not table) 5104 and self._match(TokenType.STAR) 5105 ): 5106 if isinstance(table, exp.Identifier): 5107 table.args["this"] += "*" 5108 else: 5109 table = exp.Identifier(this="*") 5110 5111 if is_db_reference: 5112 catalog = db 5113 db = table 5114 table = None 5115 5116 if not table and not is_db_reference: 5117 self.raise_error(f"Expected table name but got {self._curr}") 5118 if not db and is_db_reference: 5119 self.raise_error(f"Expected database name but got {self._curr}") 5120 5121 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 5122 5123 # Bubble up comments from identifier parts to the Table 5124 comments = [] 5125 for part in table.parts: 5126 if part_comments := part.pop_comments(): 5127 comments.extend(part_comments) 5128 if comments: 5129 table.add_comments(comments) 5130 5131 changes = self._parse_changes() 5132 if changes: 5133 table.set("changes", changes) 5134 5135 at_before = self._parse_historical_data() 5136 if at_before: 5137 table.set("when", at_before) 5138 5139 pivots = self._parse_pivots() 5140 if pivots: 5141 table.set("pivots", pivots) 5142 5143 return table 5144 5145 def _parse_table( 5146 self, 5147 schema: bool = False, 5148 joins: bool = False, 5149 alias_tokens: t.Collection[TokenType] | None = None, 5150 parse_bracket: bool = False, 5151 is_db_reference: bool = False, 5152 parse_partition: bool = False, 5153 consume_pipe: bool = False, 5154 ) -> exp.Expr | None: 5155 if not schema and not is_db_reference and not consume_pipe and not joins: 5156 index = self._index 5157 table = self._parse_table_parts(fast=True) 5158 5159 if table is not None: 5160 curr_tt = self._curr.token_type 5161 next_tt = self._next.token_type 5162 5163 fast_terminators = self.TABLE_TERMINATORS 5164 5165 # only return the table if we're sure there are no other operators 5166 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5167 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5168 return table 5169 5170 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5171 5172 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5173 if alias := self._parse_table_alias( 5174 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5175 ): 5176 table.set("alias", alias) 5177 5178 if self._curr.token_type in fast_terminators: 5179 return table 5180 5181 self._retreat(index) 5182 5183 if stream := self._parse_stream(): 5184 return stream 5185 5186 if lateral := self._parse_lateral(): 5187 return lateral 5188 5189 if unnest := self._parse_unnest(): 5190 return unnest 5191 5192 if values := self._parse_derived_table_values(): 5193 return values 5194 5195 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5196 if not subquery.args.get("pivots"): 5197 subquery.set("pivots", self._parse_pivots()) 5198 if joins: 5199 for join in self._parse_joins(): 5200 subquery.append("joins", join) 5201 return subquery 5202 5203 bracket = parse_bracket and self._parse_bracket(None) 5204 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5205 5206 rows_from_tables = ( 5207 self._parse_wrapped_csv(self._parse_table) 5208 if self._match_text_seq("ROWS", "FROM") 5209 else None 5210 ) 5211 rows_from = ( 5212 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5213 ) 5214 5215 only = self._match(TokenType.ONLY) 5216 5217 this = t.cast( 5218 exp.Expr, 5219 bracket 5220 or rows_from 5221 or self._parse_bracket( 5222 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5223 ), 5224 ) 5225 5226 if only: 5227 this.set("only", only) 5228 5229 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5230 self._match(TokenType.STAR) 5231 5232 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5233 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5234 this.set("partition", self._parse_partition()) 5235 5236 if schema: 5237 return self._parse_schema(this=this) 5238 5239 if self.dialect.ALIAS_POST_VERSION: 5240 this.set("version", self._parse_version()) 5241 5242 if self.dialect.ALIAS_POST_TABLESAMPLE: 5243 this.set("sample", self._parse_table_sample()) 5244 if ( 5245 this.args.get("sample") 5246 and self.dialect.ALIAS_POST_VERSION 5247 and not this.args.get("version") 5248 ): 5249 this.set("version", self._parse_version()) 5250 5251 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5252 if alias: 5253 this.set("alias", alias) 5254 5255 # DuckDB requires the time-travel clause to come after the alias, e.g. 5256 # SELECT * FROM t AS a AT (VERSION => 1) 5257 if isinstance(this, exp.Table) and not this.args.get("when"): 5258 this.set("when", self._parse_historical_data()) 5259 5260 if self._match(TokenType.INDEXED_BY): 5261 this.set("indexed", self._parse_table_parts()) 5262 elif self._match_text_seq("NOT", "INDEXED"): 5263 this.set("indexed", False) 5264 5265 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5266 return self.expression( 5267 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5268 ) 5269 5270 this.set("hints", self._parse_table_hints()) 5271 5272 if not this.args.get("pivots"): 5273 this.set("pivots", self._parse_pivots()) 5274 5275 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5276 this.set("sample", self._parse_table_sample()) 5277 5278 if not self.dialect.ALIAS_POST_VERSION: 5279 this.set("version", self._parse_version()) 5280 5281 if joins: 5282 for join in self._parse_joins(alias_tokens=alias_tokens): 5283 this.append("joins", join) 5284 5285 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5286 this.set("ordinality", True) 5287 this.set("alias", self._parse_table_alias()) 5288 5289 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5290 # hoist the table args onto the latter and return it instead 5291 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5292 table_from_rows = this.this 5293 for arg in exp.TableFromRows.arg_types: 5294 if arg != "this": 5295 table_from_rows.set(arg, this.args.get(arg)) 5296 5297 this = table_from_rows 5298 5299 return this 5300 5301 def _parse_version(self) -> exp.Version | None: 5302 for phrase, this in self.VERSION_PHRASES.items(): 5303 if self._match_text_seq(*phrase): 5304 break 5305 else: 5306 return None 5307 5308 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5309 kind = self._prev.text.upper() 5310 start = self._parse_bitwise() 5311 self._match_texts(("TO", "AND")) 5312 end = self._parse_bitwise() 5313 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5314 elif self._match_text_seq("CONTAINED", "IN"): 5315 kind = "CONTAINED IN" 5316 expression = self.expression( 5317 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5318 ) 5319 elif self._match(TokenType.ALL): 5320 kind = "ALL" 5321 expression = None 5322 else: 5323 self._match_text_seq("AS", "OF") 5324 kind = "AS OF" 5325 expression = self._parse_bitwise() 5326 5327 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5328 5329 def _parse_historical_data(self) -> exp.HistoricalData | None: 5330 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5331 index = self._index 5332 historical_data = None 5333 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5334 this = self._prev.text.upper() 5335 kind = ( 5336 self._match(TokenType.L_PAREN) 5337 and self._match_texts(self.HISTORICAL_DATA_KIND) 5338 and self._prev.text.upper() 5339 ) 5340 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5341 5342 if expression: 5343 self._match_r_paren() 5344 historical_data = self.expression( 5345 exp.HistoricalData(this=this, kind=kind, expression=expression) 5346 ) 5347 else: 5348 self._retreat(index) 5349 5350 return historical_data 5351 5352 def _parse_changes(self) -> exp.Changes | None: 5353 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5354 return None 5355 5356 information = self._parse_var(any_token=True) 5357 self._match_r_paren() 5358 5359 return self.expression( 5360 exp.Changes( 5361 information=information, 5362 at_before=self._parse_historical_data(), 5363 end=self._parse_historical_data(), 5364 ) 5365 ) 5366 5367 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5368 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5369 return None 5370 5371 self._advance() 5372 5373 expressions = self._parse_wrapped_csv(self._parse_equality) 5374 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5375 5376 alias = self._parse_table_alias() if with_alias else None 5377 5378 if alias: 5379 if self.dialect.UNNEST_COLUMN_ONLY: 5380 if alias.args.get("columns"): 5381 self.raise_error("Unexpected extra column alias in unnest.") 5382 5383 alias.set("columns", [alias.this]) 5384 alias.set("this", None) 5385 5386 columns = alias.args.get("columns") or [] 5387 if offset and len(expressions) < len(columns): 5388 offset = columns.pop() 5389 5390 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5391 self._match(TokenType.ALIAS) 5392 offset = self._parse_id_var( 5393 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5394 ) or exp.to_identifier("offset") 5395 5396 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5397 5398 def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None: 5399 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5400 if not is_derived and not ( 5401 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5402 self._match_text_seq("VALUES") 5403 or self._match_text_seq("FORMAT", "VALUES") 5404 # MySQL accepts VALUE as a synonym for VALUES in INSERT statements. 5405 or (allow_value_synonym and self._match_text_seq("VALUE")) 5406 ): 5407 return None 5408 5409 expressions = self._parse_csv(self._parse_value) 5410 alias = self._parse_table_alias() 5411 5412 if is_derived: 5413 self._match_r_paren() 5414 5415 return self.expression( 5416 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5417 ) 5418 5419 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5420 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5421 as_modifier and self._match_text_seq("USING", "SAMPLE") 5422 ): 5423 return None 5424 5425 bucket_numerator = None 5426 bucket_denominator = None 5427 bucket_field = None 5428 percent = None 5429 size = None 5430 seed = None 5431 5432 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5433 matched_l_paren = self._match(TokenType.L_PAREN) 5434 5435 if self.TABLESAMPLE_CSV: 5436 num = None 5437 expressions = self._parse_csv(self._parse_primary) 5438 else: 5439 expressions = None 5440 num = ( 5441 self._parse_factor(parse_mod=False) 5442 if self._match(TokenType.NUMBER, advance=False) 5443 else self._parse_primary() or self._parse_placeholder() 5444 ) 5445 5446 if self._match_text_seq("BUCKET"): 5447 bucket_numerator = self._parse_number() 5448 self._match_text_seq("OUT", "OF") 5449 bucket_denominator = bucket_denominator = self._parse_number() 5450 self._match(TokenType.ON) 5451 bucket_field = self._parse_field() 5452 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5453 percent = num 5454 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5455 size = num 5456 else: 5457 percent = num 5458 5459 if matched_l_paren: 5460 self._match_r_paren() 5461 5462 if self._match(TokenType.L_PAREN): 5463 method = self._parse_var(upper=True) 5464 seed = self._match(TokenType.COMMA) and self._parse_number() 5465 self._match_r_paren() 5466 elif self._match_texts(("SEED", "REPEATABLE")): 5467 seed = self._parse_wrapped(self._parse_number) 5468 5469 if not method and self.DEFAULT_SAMPLING_METHOD: 5470 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5471 5472 return self.expression( 5473 exp.TableSample( 5474 expressions=expressions, 5475 method=method, 5476 bucket_numerator=bucket_numerator, 5477 bucket_denominator=bucket_denominator, 5478 bucket_field=bucket_field, 5479 percent=percent, 5480 size=size, 5481 seed=seed, 5482 ) 5483 ) 5484 5485 def _parse_pivots(self) -> list[exp.Pivot] | None: 5486 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5487 return None 5488 return list(iter(self._parse_pivot, None)) or None 5489 5490 def _parse_joins( 5491 self, alias_tokens: t.Collection[TokenType] | None = None 5492 ) -> t.Iterator[exp.Join]: 5493 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5494 5495 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5496 if not self._match(TokenType.INTO): 5497 return None 5498 5499 return self.expression( 5500 exp.UnpivotColumns( 5501 this=self._match_text_seq("NAME") and self._parse_column(), 5502 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5503 ) 5504 ) 5505 5506 # https://duckdb.org/docs/sql/statements/pivot 5507 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5508 def _parse_on() -> exp.Expr | None: 5509 this = self._parse_bitwise() 5510 5511 if self._match(TokenType.IN): 5512 # PIVOT ... ON col IN (row_val1, row_val2) 5513 return self._parse_in(this) 5514 if self._match(TokenType.ALIAS, advance=False): 5515 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5516 return self._parse_alias(this) 5517 5518 return this 5519 5520 this = self._parse_table() 5521 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5522 into = self._parse_unpivot_columns() 5523 using = self._match(TokenType.USING) and self._parse_csv( 5524 lambda: self._parse_alias(self._parse_column()) 5525 ) 5526 group = self._parse_group() 5527 5528 return self.expression( 5529 exp.Pivot( 5530 this=this, 5531 expressions=expressions, 5532 using=using, 5533 group=group, 5534 unpivot=is_unpivot, 5535 into=into, 5536 ) 5537 ) 5538 5539 def _parse_pivot_in(self) -> exp.In: 5540 def _parse_aliased_expression() -> exp.Expr | None: 5541 this = self._parse_select_or_expression() 5542 5543 self._match(TokenType.ALIAS) 5544 alias = self._parse_bitwise() 5545 if alias: 5546 if isinstance(alias, exp.Column) and not alias.db: 5547 alias = alias.this 5548 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5549 5550 return this 5551 5552 value = self._parse_column() 5553 5554 if not self._match(TokenType.IN): 5555 self.raise_error("Expecting IN") 5556 5557 if self._match(TokenType.L_PAREN): 5558 if self._match(TokenType.ANY): 5559 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5560 else: 5561 exprs = self._parse_csv(_parse_aliased_expression) 5562 self._match_r_paren() 5563 return self.expression(exp.In(this=value, expressions=exprs)) 5564 5565 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5566 5567 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5568 func = self._parse_function() 5569 if not func: 5570 if self._prev.token_type == TokenType.COMMA: 5571 return None 5572 self.raise_error("Expecting an aggregation function in PIVOT") 5573 5574 return self._parse_alias(func) 5575 5576 def _parse_pivot(self) -> exp.Pivot | None: 5577 index = self._index 5578 include_nulls = None 5579 5580 if self._match(TokenType.PIVOT): 5581 unpivot = False 5582 elif self._match(TokenType.UNPIVOT): 5583 unpivot = True 5584 5585 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5586 if self._match_text_seq("INCLUDE", "NULLS"): 5587 include_nulls = True 5588 elif self._match_text_seq("EXCLUDE", "NULLS"): 5589 include_nulls = False 5590 else: 5591 return None 5592 5593 expressions = [] 5594 5595 if not self._match(TokenType.L_PAREN): 5596 self._retreat(index) 5597 return None 5598 5599 if unpivot: 5600 expressions = self._parse_csv(self._parse_column) 5601 else: 5602 expressions = self._parse_csv(self._parse_pivot_aggregation) 5603 5604 if not expressions: 5605 self.raise_error("Failed to parse PIVOT's aggregation list") 5606 5607 if not self._match(TokenType.FOR): 5608 self.raise_error("Expecting FOR") 5609 5610 fields = [] 5611 while True: 5612 field = self._try_parse(self._parse_pivot_in) 5613 if not field: 5614 break 5615 fields.append(field) 5616 5617 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5618 self._parse_bitwise 5619 ) 5620 5621 group = self._parse_group() 5622 5623 self._match_r_paren() 5624 5625 pivot = self.expression( 5626 exp.Pivot( 5627 expressions=expressions, 5628 fields=fields, 5629 unpivot=unpivot, 5630 include_nulls=include_nulls, 5631 default_on_null=default_on_null, 5632 group=group, 5633 ) 5634 ) 5635 5636 if unpivot: 5637 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5638 for pivot_field in pivot.fields: 5639 if isinstance(pivot_field, exp.In): 5640 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5641 5642 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5643 5644 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5645 pivot.set("alias", self._parse_table_alias()) 5646 5647 if not unpivot: 5648 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5649 5650 columns: list[exp.Expr] = [] 5651 all_fields = [] 5652 for pivot_field in pivot.fields: 5653 pivot_field_expressions = pivot_field.expressions 5654 5655 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5656 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5657 continue 5658 5659 all_fields.append( 5660 [ 5661 # An explicit `<field> AS <alias>` names the output column directly, 5662 # so it wins over the dialect's string-identifying convention 5663 fld.sql() 5664 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5665 else fld.alias_or_name 5666 for fld in pivot_field_expressions 5667 ] 5668 ) 5669 5670 if all_fields: 5671 if names: 5672 all_fields.append(names) 5673 5674 # Generate all possible combinations of the pivot columns 5675 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5676 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5677 for fld_parts_tuple in itertools.product(*all_fields): 5678 fld_parts = list(fld_parts_tuple) 5679 5680 if names and self.PREFIXED_PIVOT_COLUMNS: 5681 # Move the "name" to the front of the list 5682 fld_parts.insert(0, fld_parts.pop(-1)) 5683 5684 columns.append(exp.to_identifier("_".join(fld_parts))) 5685 5686 pivot.set("columns", columns) 5687 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5688 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5689 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5690 5691 return pivot 5692 5693 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5694 return [agg.alias for agg in aggregations if agg.alias] 5695 5696 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5697 if not skip_where_token and not self._match(TokenType.PREWHERE): 5698 return None 5699 5700 comments = self._prev_comments 5701 return self.expression( 5702 exp.PreWhere(this=self._parse_disjunction()), 5703 comments=comments, 5704 ) 5705 5706 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5707 if not skip_where_token and not self._match(TokenType.WHERE): 5708 return None 5709 5710 comments = self._prev_comments 5711 return self.expression( 5712 exp.Where(this=self._parse_disjunction()), 5713 comments=comments, 5714 ) 5715 5716 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5717 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5718 return None 5719 comments = self._prev_comments 5720 5721 elements: dict[str, t.Any] = defaultdict(list) 5722 5723 if self._match(TokenType.ALL): 5724 elements["all"] = True 5725 elif self._match(TokenType.DISTINCT): 5726 elements["all"] = False 5727 5728 while True: 5729 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5730 # which are also valid identifiers 5731 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5732 break 5733 5734 elements["expressions"].extend( 5735 self._parse_csv( 5736 lambda: ( 5737 self._parse_grouping_sets() 5738 or self._parse_cube_or_rollup() 5739 or self._parse_disjunction() 5740 ) 5741 ) 5742 ) 5743 5744 before_with_index = self._index 5745 5746 if self._match(TokenType.WITH) and ( 5747 cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True) 5748 ): 5749 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5750 elements[key].append(cube_or_rollup) 5751 elif grouping_sets := self._parse_grouping_sets(): 5752 # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...) 5753 elements["grouping_sets"].append(grouping_sets) 5754 break 5755 elif self._match_text_seq("TOTALS"): 5756 elements["totals"] = True # type: ignore 5757 5758 if before_with_index <= self._index <= before_with_index + 1: 5759 self._retreat(before_with_index) 5760 break 5761 5762 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5763 5764 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5765 if self._match(TokenType.CUBE): 5766 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5767 elif self._match(TokenType.ROLLUP): 5768 kind = exp.Rollup 5769 else: 5770 return None 5771 5772 return self.expression( 5773 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5774 ) 5775 5776 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5777 if self._match(TokenType.GROUPING_SETS): 5778 return self.expression( 5779 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5780 ) 5781 return None 5782 5783 def _parse_grouping_set(self) -> exp.Expr | None: 5784 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5785 5786 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5787 if not skip_having_token and not self._match(TokenType.HAVING): 5788 return None 5789 comments = self._prev_comments 5790 return self.expression( 5791 exp.Having(this=self._parse_disjunction()), 5792 comments=comments, 5793 ) 5794 5795 def _parse_qualify(self) -> exp.Qualify | None: 5796 if not self._match(TokenType.QUALIFY): 5797 return None 5798 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5799 5800 def _parse_connect_with_prior(self) -> exp.Expr | None: 5801 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5802 exp.Prior(this=self._parse_bitwise()) 5803 ) 5804 connect = self._parse_disjunction() 5805 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5806 return connect 5807 5808 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5809 if skip_start_token: 5810 start = None 5811 elif self._match_text_seq("START", "WITH"): 5812 start = self._parse_disjunction() 5813 else: 5814 return None 5815 5816 self._match(TokenType.CONNECT_BY) 5817 nocycle = self._match_text_seq("NOCYCLE") 5818 connect = self._parse_connect_with_prior() 5819 5820 if not start and self._match_text_seq("START", "WITH"): 5821 start = self._parse_disjunction() 5822 5823 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5824 5825 def _parse_name_as_expression(self) -> exp.Expr | None: 5826 this = self._parse_id_var(any_token=True) 5827 if self._match(TokenType.ALIAS): 5828 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5829 return this 5830 5831 def _parse_interpolate(self) -> list[exp.Expr] | None: 5832 if self._match_text_seq("INTERPOLATE"): 5833 return self._parse_wrapped_csv(self._parse_name_as_expression) 5834 return None 5835 5836 def _parse_order( 5837 self, this: exp.Expr | None = None, skip_order_token: bool = False 5838 ) -> exp.Expr | None: 5839 siblings = None 5840 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5841 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5842 return this 5843 5844 siblings = True 5845 5846 comments = self._prev_comments 5847 return self.expression( 5848 exp.Order( 5849 this=this, 5850 expressions=self._parse_csv(self._parse_ordered), 5851 siblings=siblings, 5852 ), 5853 comments=comments, 5854 ) 5855 5856 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5857 if not self._match(token): 5858 return None 5859 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5860 5861 def _parse_ordered( 5862 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5863 ) -> exp.Ordered | None: 5864 this = parse_method() if parse_method else self._parse_disjunction() 5865 if not this: 5866 return None 5867 5868 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5869 this = exp.var("ALL") 5870 5871 asc = self._match(TokenType.ASC) 5872 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5873 5874 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5875 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5876 5877 nulls_first = is_nulls_first or False 5878 explicitly_null_ordered = is_nulls_first or is_nulls_last 5879 5880 if ( 5881 not explicitly_null_ordered 5882 and ( 5883 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5884 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5885 ) 5886 and self.dialect.NULL_ORDERING != "nulls_are_last" 5887 ): 5888 nulls_first = True 5889 5890 if self._match_text_seq("WITH", "FILL"): 5891 with_fill = self.expression( 5892 exp.WithFill( 5893 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5894 to=self._match_text_seq("TO") and self._parse_bitwise(), 5895 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5896 interpolate=self._parse_interpolate(), 5897 ) 5898 ) 5899 else: 5900 with_fill = None 5901 5902 return self.expression( 5903 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5904 ) 5905 5906 def _parse_limit_options(self) -> exp.LimitOptions | None: 5907 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5908 rows = self._match_texts(("ROW", "ROWS")) 5909 self._match_text_seq("ONLY") 5910 with_ties = self._match_text_seq("WITH", "TIES") 5911 5912 if not (percent or rows or with_ties): 5913 return None 5914 5915 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5916 5917 def _parse_limit( 5918 self, 5919 this: exp.Expr | None = None, 5920 top: bool = False, 5921 skip_limit_token: bool = False, 5922 ) -> exp.Expr | None: 5923 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5924 comments = self._prev_comments 5925 if top: 5926 limit_paren = self._match(TokenType.L_PAREN) 5927 expression = ( 5928 self._parse_term() or self._parse_select() 5929 if limit_paren 5930 else self._parse_number() 5931 ) 5932 5933 if limit_paren: 5934 self._match_r_paren() 5935 5936 else: 5937 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5938 expression = exp.var("ALL") 5939 else: 5940 expression = self._parse_term(parse_mod=False) 5941 limit_options = self._parse_limit_options() 5942 5943 if self._match(TokenType.COMMA): 5944 offset = expression 5945 expression = self._parse_term() 5946 else: 5947 offset = None 5948 5949 limit_exp = self.expression( 5950 exp.Limit( 5951 this=this, 5952 expression=expression, 5953 offset=offset, 5954 limit_options=limit_options, 5955 expressions=self._parse_limit_by(), 5956 ), 5957 comments=comments, 5958 ) 5959 5960 if top: 5961 limit_exp.meta["top"] = True 5962 5963 return limit_exp 5964 5965 if self._match(TokenType.FETCH): 5966 direction = ( 5967 self._prev.text.upper() 5968 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5969 else "FIRST" 5970 ) 5971 5972 count = ( 5973 None 5974 if self._match_texts(("ROW", "ROWS"), advance=False) 5975 else self._parse_field(tokens=self.FETCH_TOKENS) 5976 ) 5977 5978 return self.expression( 5979 exp.Fetch( 5980 direction=direction, count=count, limit_options=self._parse_limit_options() 5981 ) 5982 ) 5983 5984 return this 5985 5986 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5987 if not self._match(TokenType.OFFSET): 5988 return this 5989 5990 count = self._parse_term() 5991 self._match_texts(("ROW", "ROWS")) 5992 5993 return self.expression( 5994 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5995 ) 5996 5997 def _can_parse_limit_or_offset(self) -> bool: 5998 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5999 return False 6000 6001 index = self._index 6002 result = bool( 6003 self._try_parse(self._parse_limit, retreat=True) 6004 or self._try_parse(self._parse_offset, retreat=True) 6005 ) 6006 self._retreat(index) 6007 6008 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 6009 if self._next.token_type == TokenType.MATCH_CONDITION: 6010 result = False 6011 6012 return result 6013 6014 def _can_parse_named_window(self) -> bool: 6015 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 6016 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 6017 if not self._match(TokenType.WINDOW, advance=False): 6018 return False 6019 6020 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 6021 if name is None or name.token_type not in self.ID_VAR_TOKENS: 6022 return False 6023 6024 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 6025 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 6026 return False 6027 6028 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 6029 return body is not None and body.token_type == TokenType.L_PAREN 6030 6031 def _parse_limit_by(self) -> list[exp.Expr] | None: 6032 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 6033 6034 def _parse_locks(self) -> list[exp.Lock]: 6035 locks = [] 6036 while True: 6037 update, key = None, None 6038 if self._match_text_seq("FOR", "UPDATE"): 6039 update = True 6040 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 6041 "LOCK", "IN", "SHARE", "MODE" 6042 ): 6043 update = False 6044 elif self._match_text_seq("FOR", "KEY", "SHARE"): 6045 update, key = False, True 6046 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 6047 update, key = True, True 6048 else: 6049 break 6050 6051 expressions = None 6052 if self._match_text_seq("OF"): 6053 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 6054 6055 wait: bool | exp.Expr | None = None 6056 if self._match_text_seq("NOWAIT"): 6057 wait = True 6058 elif self._match_text_seq("WAIT"): 6059 wait = self._parse_primary() 6060 elif self._match_text_seq("SKIP", "LOCKED"): 6061 wait = False 6062 6063 locks.append( 6064 self.expression( 6065 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 6066 ) 6067 ) 6068 6069 return locks 6070 6071 def parse_set_operation( 6072 self, this: exp.Expr | None, consume_pipe: bool = False 6073 ) -> exp.Expr | None: 6074 start = self._index 6075 _, side_token, kind_token = self._parse_join_parts() 6076 6077 side = side_token.text if side_token else None 6078 kind = kind_token.text if kind_token else None 6079 6080 if not self._match_set(self.SET_OPERATIONS): 6081 self._retreat(start) 6082 return None 6083 6084 token_type = self._prev.token_type 6085 6086 if token_type == TokenType.UNION: 6087 operation: type[exp.SetOperation] = exp.Union 6088 elif token_type == TokenType.EXCEPT: 6089 operation = exp.Except 6090 else: 6091 operation = exp.Intersect 6092 6093 comments = self._prev.comments 6094 6095 if self._match(TokenType.DISTINCT): 6096 distinct: bool | None = True 6097 elif self._match(TokenType.ALL): 6098 distinct = False 6099 else: 6100 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 6101 if distinct is None: 6102 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 6103 6104 by_name = ( 6105 self._match_text_seq("BY", "NAME") 6106 or self._match_text_seq("STRICT", "CORRESPONDING") 6107 or None 6108 ) 6109 if self._match_text_seq("CORRESPONDING"): 6110 by_name = True 6111 if not side and not kind: 6112 kind = "INNER" 6113 6114 on_column_list = None 6115 if by_name and self._match_texts(("ON", "BY")): 6116 on_column_list = self._parse_wrapped_csv(self._parse_column) 6117 6118 expression = self._parse_select( 6119 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 6120 ) 6121 6122 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 6123 # in _parse_cte and so that alias pushdown can reach into set operation branches 6124 if isinstance(this, exp.Values): 6125 this = self._values_to_select(this) 6126 if isinstance(expression, exp.Values): 6127 expression = self._values_to_select(expression) 6128 6129 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 6130 subquery = this.this 6131 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 6132 subquery.add_comments(this.pop_comments()) 6133 this = subquery 6134 6135 return self.expression( 6136 operation( 6137 this=this, 6138 distinct=distinct, 6139 by_name=by_name, 6140 expression=expression, 6141 side=side, 6142 kind=kind, 6143 on=on_column_list, 6144 ), 6145 comments=comments, 6146 ) 6147 6148 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 6149 while this: 6150 setop = self.parse_set_operation(this) 6151 if not setop: 6152 break 6153 this = setop 6154 6155 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6156 expression = this.expression 6157 6158 if expression: 6159 for arg in self.SET_OP_MODIFIERS: 6160 expr = expression.args.get(arg) 6161 if expr and not (arg == "limit" and expr.meta.get("top")): 6162 expression.set(arg, None) 6163 this.set(arg, expr) 6164 6165 # A trailing LIMIT/FETCH can coexist with TOP on the final operand. 6166 if self._curr.token_type in (TokenType.LIMIT, TokenType.FETCH): 6167 this = self._parse_query_modifiers(this) 6168 6169 return this 6170 6171 def _parse_expression(self) -> exp.Expr | None: 6172 return self._parse_alias(self._parse_assignment()) 6173 6174 def _parse_assignment(self) -> exp.Expr | None: 6175 this = self._parse_disjunction() 6176 if not this and self._next.token_type in self.ASSIGNMENT: 6177 # This allows us to parse <non-identifier token> := <expr> 6178 this = exp.column( 6179 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6180 ) 6181 6182 while self._match_set(self.ASSIGNMENT): 6183 if isinstance(this, exp.Column) and len(this.parts) == 1: 6184 this = this.this 6185 6186 comments = self._prev_comments 6187 this = self.expression( 6188 self.ASSIGNMENT[self._prev.token_type]( 6189 this=this, expression=self._parse_assignment() 6190 ), 6191 comments=comments, 6192 ) 6193 6194 return this 6195 6196 def _parse_disjunction(self) -> exp.Expr | None: 6197 this = self._parse_conjunction() 6198 while self._match_set(self.DISJUNCTION): 6199 comments = self._prev_comments 6200 this = self.expression( 6201 self.DISJUNCTION[self._prev.token_type]( 6202 this=this, expression=self._parse_conjunction() 6203 ), 6204 comments=comments, 6205 ) 6206 return this 6207 6208 def _parse_conjunction(self) -> exp.Expr | None: 6209 this = self._parse_equality() 6210 while self._match_set(self.CONJUNCTION): 6211 comments = self._prev_comments 6212 this = self.expression( 6213 self.CONJUNCTION[self._prev.token_type]( 6214 this=this, expression=self._parse_equality() 6215 ), 6216 comments=comments, 6217 ) 6218 return this 6219 6220 def _build_predicate( 6221 self, 6222 klass: Type[exp.Binary], 6223 this: exp.Expr | None, 6224 expression: exp.Expr | None, 6225 comments: list[str] | None = None, 6226 negate: bool = False, 6227 ) -> exp.Expr: 6228 is_predicates = (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ) 6229 if klass in is_predicates: 6230 if ( 6231 isinstance(this, (exp.Connector, exp.Not, exp.NullSafeNEQ)) 6232 or type(this) in self.EQUALITY.values() 6233 or type(this) in self.COMPARISON.values() 6234 or (klass is not exp.Is and isinstance(this, exp.Is)) 6235 ): 6236 this = exp.Paren(this=this) 6237 if isinstance(expression, (exp.Predicate, exp.Connector, exp.Not, exp.Escape)): 6238 expression = exp.Paren(this=expression) 6239 else: 6240 if isinstance(this.this if isinstance(this, exp.Not) else this, is_predicates): 6241 this = exp.Paren(this=this) 6242 if isinstance( 6243 expression.this if isinstance(expression, exp.Not) else expression, is_predicates 6244 ): 6245 expression = exp.Paren(this=expression) 6246 6247 predicate = klass(this=this, expression=expression) 6248 if negate: 6249 predicate.set("negate", True) 6250 return self.expression(predicate, comments=comments) 6251 6252 def _parse_equality(self, parse_is: bool = True) -> exp.Expr | None: 6253 this = self._parse_comparison() 6254 while True: 6255 if self._match_set(self.EQUALITY): 6256 comments = self._prev_comments 6257 this = self._build_predicate( 6258 self.EQUALITY[self._prev.token_type], 6259 this, 6260 self._parse_comparison(), 6261 comments=comments, 6262 ) 6263 elif parse_is and self._is_predicate_ahead(): 6264 index = self._index 6265 # A postfix predicate can itself be an operand, e.g. x IS NULL = FALSE. 6266 this = self._parse_comparison(self._parse_range(this, parse_is=True)) 6267 if self._index == index: 6268 break 6269 else: 6270 break 6271 return this 6272 6273 def _parse_comparison(self, this: exp.Expr | None = None) -> exp.Expr | None: 6274 this = this or self._parse_range() 6275 while self._match_set(self.COMPARISON): 6276 comments = self._prev_comments 6277 this = self._build_predicate( 6278 self.COMPARISON[self._prev.token_type], 6279 this, 6280 self._parse_range(), 6281 comments=comments, 6282 ) 6283 return this 6284 6285 def _is_predicate_ahead(self) -> bool: 6286 if ( 6287 self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.HIGH 6288 or self._curr.token_type not in self.IS_PREDICATE_TOKENS 6289 ): 6290 return False 6291 return self._curr.token_type != TokenType.NOT or self._next.token_type == TokenType.NULL 6292 6293 def _parse_range(self, this: exp.Expr | None = None, parse_is: bool = False) -> exp.Expr | None: 6294 this = this or self._parse_bitwise() 6295 6296 while True: 6297 if not parse_is and self._is_predicate_ahead(): 6298 break 6299 negate = self._match(TokenType.NOT) 6300 if self._match_set(self.RANGE_PARSERS): 6301 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6302 if not expression: 6303 return this 6304 6305 this = expression 6306 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6307 this = self._build_predicate(exp.Is, this, exp.Null()) 6308 elif self._match(TokenType.NOTNULL): 6309 # Postgres supports ISNULL and NOTNULL for conditions. 6310 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6311 if self.dialect.NORMALIZE_NOT_NULL: 6312 this = self._build_predicate(exp.Is, this, exp.Null()) 6313 this = self.expression(exp.Not(this=this)) 6314 else: 6315 this = self._build_predicate(exp.Is, this, exp.Null(), negate=True) 6316 else: 6317 if negate: 6318 self._retreat(self._index - 1) 6319 break 6320 6321 if negate: 6322 this = self._negate_range(this) 6323 if (negate or (isinstance(this, exp.Not) and isinstance(this.this, exp.Is))) and ( 6324 self._curr.token_type == TokenType.NOT 6325 or self._curr.token_type in self.RANGE_PARSERS 6326 ): 6327 this = self.expression(exp.Paren(this=this)) 6328 6329 return this 6330 6331 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6332 if not this: 6333 return this 6334 6335 expression = this.this if isinstance(this, exp.Escape) else this 6336 if isinstance(expression, (exp.Like, exp.ILike)): 6337 expression.set("negate", True) 6338 return this 6339 6340 return self.expression(exp.Not(this=this)) 6341 6342 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6343 index = self._index - 1 6344 negate = self._match(TokenType.NOT) 6345 6346 if self._match_text_seq("DISTINCT", "FROM"): 6347 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6348 return self._build_predicate(klass, this, self._parse_is_rhs(distinct=True)) 6349 6350 if self._match(TokenType.JSON): 6351 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6352 6353 if self._match_text_seq("WITH"): 6354 _with = True 6355 elif self._match_text_seq("WITHOUT"): 6356 _with = False 6357 else: 6358 _with = None 6359 6360 unique = self._match(TokenType.UNIQUE) 6361 self._match_text_seq("KEYS") 6362 expression: exp.Expr | None = self.expression( 6363 exp.JSON(this=kind, with_=_with, unique=unique) 6364 ) 6365 else: 6366 expression = self._parse_is_rhs() 6367 if not expression: 6368 self._retreat(index) 6369 return None 6370 6371 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6372 this = self._build_predicate(exp.Is, this, expression, negate=True) 6373 else: 6374 this = self._build_predicate(exp.Is, this, expression) 6375 this = self.expression(exp.Not(this=this)) if negate else this 6376 6377 return self._parse_column_ops(this) 6378 6379 def _parse_is_rhs(self, distinct: bool = False) -> exp.Expr | None: 6380 if distinct: 6381 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.LOW: 6382 return self._parse_equality(parse_is=False) 6383 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.EQUALITY: 6384 return self._parse_comparison() 6385 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.COMPARISON: 6386 return self._parse_range() 6387 return self._parse_bitwise() 6388 return self._parse_null() or self._parse_bitwise() 6389 6390 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6391 unnest = self._parse_unnest(with_alias=False) 6392 if unnest: 6393 this = self.expression(exp.In(this=this, unnest=unnest)) 6394 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6395 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6396 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6397 6398 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6399 this = self.expression( 6400 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6401 ) 6402 else: 6403 this = self.expression(exp.In(this=this, expressions=expressions)) 6404 6405 if matched_l_paren: 6406 self._match_r_paren(this) 6407 elif not self._match(TokenType.R_BRACKET, expression=this): 6408 self.raise_error("Expecting ]") 6409 else: 6410 this = self.expression(exp.In(this=this, field=self._parse_column())) 6411 6412 return this 6413 6414 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6415 # Preserve grouping when BETWEEN is rewritten into comparisons. 6416 if isinstance(this, (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ)): 6417 this = exp.Paren(this=this) 6418 6419 symmetric = None 6420 if self._match_text_seq("SYMMETRIC"): 6421 symmetric = True 6422 elif self._match_text_seq("ASYMMETRIC"): 6423 symmetric = False 6424 6425 low = self._parse_bitwise() 6426 self._match(TokenType.AND) 6427 high = self._parse_bitwise() 6428 6429 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6430 6431 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6432 if not self._match(TokenType.ESCAPE): 6433 return this 6434 return self.expression( 6435 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6436 ) 6437 6438 def _parse_interval_span( 6439 self, this: exp.Expr, parse_function_unit: bool = True 6440 ) -> exp.Interval: 6441 # handle day-time format interval span with omitted units: 6442 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6443 interval_span_units_omitted = None 6444 if ( 6445 this 6446 and this.is_string 6447 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6448 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6449 ): 6450 index = self._index 6451 6452 # Var "TO" Var 6453 first_unit = self._parse_var(any_token=True, upper=True) 6454 second_unit = None 6455 if first_unit and self._match_text_seq("TO"): 6456 second_unit = self._parse_var(any_token=True, upper=True) 6457 6458 interval_span_units_omitted = not (first_unit and second_unit) 6459 6460 self._retreat(index) 6461 6462 unit_index = self._index 6463 if interval_span_units_omitted: 6464 unit = None 6465 else: 6466 # Only attempt to parse a unit if the current token can actually be one, so that a 6467 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6468 is_unit = self._curr is not None and ( 6469 self._curr.token_type == TokenType.VAR 6470 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6471 ) 6472 unit = self._parse_function() if parse_function_unit and is_unit else None 6473 if not unit and is_unit: 6474 unit = self._parse_var(any_token=True, upper=True) 6475 6476 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6477 # each INTERVAL expression into this canonical form so it's easy to transpile 6478 if this and this.is_number: 6479 try: 6480 this = exp.Literal.string(this.to_py()) 6481 except ValueError: 6482 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6483 elif this and this.is_string: 6484 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6485 if parts and unit: 6486 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6487 unit = None 6488 self._retreat(unit_index) 6489 6490 if len(parts) == 1: 6491 this = exp.Literal.string(parts[0][0]) 6492 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6493 6494 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6495 unit = self.expression( 6496 exp.IntervalSpan( 6497 this=unit, 6498 expression=self._parse_function() 6499 or self._parse_var(any_token=True, upper=True), 6500 ) 6501 ) 6502 6503 return self.expression(exp.Interval(this=this, unit=unit)) 6504 6505 def _parse_interval( 6506 self, require_interval: bool = True, parse_function_unit: bool = True 6507 ) -> exp.Add | exp.Interval | None: 6508 index = self._index 6509 6510 if not self._match(TokenType.INTERVAL) and require_interval: 6511 return None 6512 6513 if self._match(TokenType.STRING, advance=False): 6514 this = self._parse_primary() 6515 else: 6516 this = self._parse_term() 6517 6518 if not this or ( 6519 isinstance(this, exp.Column) 6520 and not this.table 6521 and not this.this.quoted 6522 and self._curr 6523 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6524 ): 6525 self._retreat(index) 6526 return None 6527 6528 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6529 6530 index = self._index 6531 self._match(TokenType.PLUS) 6532 6533 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6534 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6535 return self.expression( 6536 exp.Add( 6537 this=interval, 6538 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6539 ) 6540 ) 6541 6542 self._retreat(index) 6543 return interval 6544 6545 def _parse_bitwise(self) -> exp.Expr | None: 6546 this = self._parse_term() 6547 6548 while True: 6549 if self._match_set(self.BITWISE): 6550 this = self.expression( 6551 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6552 ) 6553 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6554 this = self.expression( 6555 exp.DPipe( 6556 this=this, 6557 expression=self._parse_term(), 6558 safe=not self.dialect.STRICT_STRING_CONCAT, 6559 ) 6560 ) 6561 elif self._match(TokenType.DQMARK): 6562 this = self.expression( 6563 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6564 ) 6565 elif self._match_pair(TokenType.LT, TokenType.LT): 6566 this = self.expression( 6567 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6568 ) 6569 elif self._match_pair(TokenType.GT, TokenType.GT): 6570 this = self.expression( 6571 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6572 ) 6573 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6574 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6575 else: 6576 break 6577 6578 return this 6579 6580 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6581 this = self._parse_factor(parse_mod=parse_mod) 6582 6583 while self._match_set(self.TERM): 6584 klass = self.TERM[self._prev.token_type] 6585 comments = self._prev_comments 6586 expression = self._parse_factor(parse_mod=parse_mod) 6587 6588 this = self.expression(klass(this=this, expression=expression), comments=comments) 6589 6590 if isinstance(this, exp.Collate): 6591 self._normalize_collate(this) 6592 6593 return this 6594 6595 def _normalize_collate(self, collate: exp.Collate) -> None: 6596 expr = collate.expression 6597 6598 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6599 # fallback to Identifier / Var 6600 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6601 ident = expr.this 6602 if isinstance(ident, exp.Identifier): 6603 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6604 6605 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6606 parse_method = self._parse_factor_operand 6607 this = self._parse_at_time_zone(parse_method()) 6608 6609 while self._match_set(self.FACTOR, advance=False): 6610 if not parse_mod and self._curr.token_type == TokenType.MOD: 6611 break 6612 6613 self._advance() 6614 klass = self.FACTOR[self._prev.token_type] 6615 comments = self._prev_comments 6616 expression = parse_method() 6617 6618 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6619 self._retreat(self._index - 1) 6620 return this 6621 6622 this = self.expression(klass(this=this, expression=expression), comments=comments) 6623 6624 if isinstance(this, exp.Div): 6625 this.set("typed", self.dialect.TYPED_DIVISION) 6626 this.set("safe", self.dialect.SAFE_DIVISION) 6627 6628 return this 6629 6630 def _parse_factor_operand(self) -> exp.Expr | None: 6631 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6632 6633 def _parse_exponent(self) -> exp.Expr | None: 6634 this = self._parse_unary() 6635 while self._match_set(self.EXPONENT): 6636 comments = self._prev_comments 6637 this = self.expression( 6638 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6639 comments=comments, 6640 ) 6641 return this 6642 6643 def _parse_unary(self) -> exp.Expr | None: 6644 if self._match_set(self.UNARY_PARSERS): 6645 return self.UNARY_PARSERS[self._prev.token_type](self) 6646 return self._parse_type() 6647 6648 def _parse_type( 6649 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6650 ) -> exp.Expr | None: 6651 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6652 return atom 6653 6654 if interval := parse_interval and self._parse_interval(): 6655 return self._parse_column_ops(interval) 6656 6657 index = self._index 6658 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6659 6660 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6661 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6662 if isinstance(data_type, exp.Cast): 6663 # This constructor can contain ops directly after it, for instance struct unnesting: 6664 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6665 return self._parse_column_ops(data_type) 6666 6667 if data_type: 6668 index2 = self._index 6669 this = self._parse_primary() 6670 6671 if isinstance(this, exp.Literal): 6672 literal = this.name 6673 this = self._parse_column_ops(this) 6674 6675 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6676 if data_type.is_type(exp.DType.TIMESTAMP): 6677 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6678 elif data_type.is_type(exp.DType.TIME): 6679 data_type = exp.DType.TIMETZ.into_expr() 6680 6681 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6682 if parser: 6683 return parser(self, this, data_type) 6684 6685 return self.expression(exp.Cast(this=this, to=data_type)) 6686 6687 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6688 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6689 # 6690 # If the index difference here is greater than 1, that means the parser itself must have 6691 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6692 # 6693 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6694 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6695 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6696 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6697 # 6698 # In these cases, we don't really want to return the converted type, but instead retreat 6699 # and try to parse a Column or Identifier in the section below. 6700 if data_type.expressions and index2 - index > 1: 6701 self._retreat(index2) 6702 return self._parse_column_ops(data_type) 6703 6704 self._retreat(index) 6705 6706 if fallback_to_identifier: 6707 return self._parse_id_var() 6708 6709 return self._parse_column() 6710 6711 def _parse_type_size(self) -> exp.DataTypeParam | None: 6712 this = self._parse_type() 6713 if not this: 6714 return None 6715 6716 if isinstance(this, exp.Column) and not this.table: 6717 this = exp.var(this.name.upper()) 6718 6719 return self.expression( 6720 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6721 ) 6722 6723 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6724 type_name = identifier.name 6725 6726 while self._match(TokenType.DOT): 6727 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6728 6729 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6730 6731 def _parse_types( 6732 self, 6733 check_func: bool = False, 6734 schema: bool = False, 6735 allow_identifiers: bool = True, 6736 with_collation: bool = False, 6737 ) -> exp.Expr | None: 6738 index = self._index 6739 this: exp.Expr | None = None 6740 6741 if self._match_set(self.TYPE_TOKENS): 6742 type_token = self._prev.token_type 6743 else: 6744 type_token = None 6745 identifier = allow_identifiers and self._parse_id_var( 6746 any_token=False, tokens=(TokenType.VAR,) 6747 ) 6748 if isinstance(identifier, exp.Identifier): 6749 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6750 this = exp.DataType.build(identifier, udt=True) 6751 else: 6752 try: 6753 tokens = self.dialect.tokenize(identifier.name) 6754 except TokenError: 6755 tokens = None 6756 6757 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6758 if len(tokens) > 1: 6759 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6760 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6761 this = self._parse_user_defined_type(identifier) 6762 else: 6763 self._retreat(self._index - 1) 6764 return None 6765 else: 6766 return None 6767 6768 if type_token == TokenType.PSEUDO_TYPE: 6769 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6770 6771 if type_token == TokenType.OBJECT_IDENTIFIER: 6772 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6773 6774 # https://materialize.com/docs/sql/types/map/ 6775 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6776 key_type = self._parse_types( 6777 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6778 ) 6779 if not self._match(TokenType.FARROW): 6780 self._retreat(index) 6781 return None 6782 6783 value_type = self._parse_types( 6784 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6785 ) 6786 if not self._match(TokenType.R_BRACKET): 6787 self._retreat(index) 6788 return None 6789 6790 return exp.DataType( 6791 this=exp.DType.MAP, 6792 expressions=[key_type, value_type], 6793 nested=True, 6794 ) 6795 6796 nested = type_token in self.NESTED_TYPE_TOKENS 6797 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6798 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6799 expressions = None 6800 maybe_func = False 6801 6802 if self._match(TokenType.L_PAREN): 6803 if is_struct: 6804 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6805 elif nested: 6806 expressions = self._parse_csv( 6807 lambda: self._parse_types( 6808 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6809 ) 6810 ) 6811 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6812 this = expressions[0] 6813 this.set("nullable", True) 6814 self._match_r_paren() 6815 return this 6816 elif type_token in self.ENUM_TYPE_TOKENS: 6817 expressions = self._parse_csv(self._parse_equality) 6818 elif type_token == TokenType.JSON: 6819 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6820 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6821 expressions = self._parse_csv(self._parse_json_type_arg) 6822 elif is_aggregate: 6823 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6824 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6825 ) 6826 if not func_or_ident: 6827 return None 6828 expressions = [func_or_ident] 6829 if self._match(TokenType.COMMA): 6830 expressions.extend( 6831 self._parse_csv( 6832 lambda: self._parse_types( 6833 check_func=check_func, 6834 schema=schema, 6835 allow_identifiers=allow_identifiers, 6836 ) 6837 ) 6838 ) 6839 else: 6840 expressions = self._parse_csv(self._parse_type_size) 6841 6842 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6843 if type_token == TokenType.VECTOR and len(expressions) == 2: 6844 expressions = self._parse_vector_expressions(expressions) 6845 6846 if not self._match(TokenType.R_PAREN): 6847 self._retreat(index) 6848 return None 6849 6850 maybe_func = True 6851 6852 values: list[exp.Expr] | None = None 6853 6854 if nested and self._match(TokenType.LT): 6855 if is_struct: 6856 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6857 else: 6858 expressions = self._parse_csv( 6859 lambda: self._parse_types( 6860 check_func=check_func, 6861 schema=schema, 6862 allow_identifiers=allow_identifiers, 6863 with_collation=True, 6864 ) 6865 ) 6866 6867 if not self._match(TokenType.GT): 6868 self.raise_error("Expecting >") 6869 6870 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6871 values = self._parse_csv(self._parse_disjunction) 6872 if not values and is_struct: 6873 values = None 6874 self._retreat(self._index - 1) 6875 else: 6876 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6877 6878 if type_token in self.TIMESTAMPS: 6879 if self._match_text_seq("WITH", "TIME", "ZONE"): 6880 maybe_func = False 6881 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6882 this = exp.DataType(this=tz_type, expressions=expressions) 6883 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6884 maybe_func = False 6885 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6886 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6887 maybe_func = False 6888 elif type_token == TokenType.INTERVAL: 6889 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6890 unit = self._parse_var(upper=True) 6891 if self._match_text_seq("TO"): 6892 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6893 6894 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6895 else: 6896 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6897 elif type_token == TokenType.VOID: 6898 this = exp.DataType(this=exp.DType.NULL) 6899 6900 if maybe_func and check_func: 6901 index2 = self._index 6902 peek = self._parse_string() 6903 6904 if not peek: 6905 self._retreat(index) 6906 return None 6907 6908 self._retreat(index2) 6909 6910 if not this: 6911 assert type_token is not None 6912 if self._match_text_seq("UNSIGNED"): 6913 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6914 if not unsigned_type_token: 6915 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6916 6917 type_token = unsigned_type_token or type_token 6918 6919 # NULLABLE without parentheses can be a column (Presto/Trino) 6920 if type_token == TokenType.NULLABLE and not expressions: 6921 self._retreat(index) 6922 return None 6923 6924 this = exp.DataType( 6925 this=exp.DType[type_token.name], 6926 expressions=expressions, 6927 nested=nested, 6928 ) 6929 6930 # Empty arrays/structs are allowed 6931 if values is not None: 6932 cls = exp.Struct if is_struct else exp.Array 6933 this = exp.cast(cls(expressions=values), this, copy=False) 6934 6935 elif expressions: 6936 this.set("expressions", expressions) 6937 6938 # https://materialize.com/docs/sql/types/list/#type-name 6939 while self._match(TokenType.LIST): 6940 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6941 6942 index = self._index 6943 6944 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6945 matched_array = self._match(TokenType.ARRAY) 6946 6947 while self._curr: 6948 datatype_token = self._prev.token_type 6949 matched_l_bracket = self._match(TokenType.L_BRACKET) 6950 6951 if (not matched_l_bracket and not matched_array) or ( 6952 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6953 ): 6954 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6955 # not to be confused with the fixed size array parsing 6956 break 6957 6958 matched_array = False 6959 values = self._parse_csv(self._parse_disjunction) or None 6960 if ( 6961 values 6962 and not schema 6963 and ( 6964 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6965 or datatype_token == TokenType.ARRAY 6966 or not self._match(TokenType.R_BRACKET, advance=False) 6967 ) 6968 ): 6969 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6970 # 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 6971 self._retreat(index) 6972 break 6973 6974 this = exp.DataType( 6975 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6976 ) 6977 self._match(TokenType.R_BRACKET) 6978 6979 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6980 converter = self.TYPE_CONVERTERS.get(this.this) 6981 if converter: 6982 this = converter(t.cast(exp.DataType, this)) 6983 6984 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6985 this.set("collate", self._parse_identifier() or self._parse_column()) 6986 6987 return this 6988 6989 def _parse_json_type_arg(self) -> exp.Expr | None: 6990 """Parse a single argument to ClickHouse's JSON type.""" 6991 6992 # SKIP col or SKIP REGEXP 'pattern' 6993 if self._match_text_seq("SKIP"): 6994 regexp = self._match(TokenType.RLIKE) 6995 arg = self._parse_column() 6996 if isinstance(arg, exp.Column): 6997 arg = arg.to_dot() 6998 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6999 7000 param_or_col = self._parse_column() 7001 if not isinstance(param_or_col, exp.Column): 7002 return None 7003 7004 # Parameter: name=value (e.g., max_dynamic_paths=2) 7005 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 7006 param = param_or_col.name 7007 value = self._parse_primary() 7008 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 7009 7010 # Column type hint: col_name Type 7011 col = param_or_col.to_dot() 7012 kind = self._parse_types(check_func=False, allow_identifiers=False) 7013 return self.expression(exp.ColumnDef(this=col, kind=kind)) 7014 7015 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 7016 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 7017 7018 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 7019 index = self._index 7020 7021 if ( 7022 self._curr 7023 and self._next 7024 and self._curr.token_type in self.TYPE_TOKENS 7025 and self._next.token_type in self.TYPE_TOKENS 7026 ): 7027 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 7028 # type token. Without this, the list will be parsed as a type and we'll eventually crash 7029 this = self._parse_id_var() 7030 else: 7031 this = ( 7032 self._parse_type(parse_interval=False, fallback_to_identifier=True) 7033 or self._parse_id_var() 7034 ) 7035 7036 self._match(TokenType.COLON) 7037 7038 if ( 7039 type_required 7040 and not isinstance(this, exp.DataType) 7041 and not self._match_set(self.TYPE_TOKENS, advance=False) 7042 ): 7043 self._retreat(index) 7044 return self._parse_types() 7045 7046 return self._parse_column_def(this) 7047 7048 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 7049 if not self._match_text_seq("AT", "TIME", "ZONE"): 7050 return this 7051 return self._parse_at_time_zone( 7052 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 7053 ) 7054 7055 def _parse_atom(self) -> exp.Expr | None: 7056 if ( 7057 self._curr.token_type in self.IDENTIFIER_TOKENS 7058 and (column := self._parse_column()) is not None 7059 ): 7060 return column 7061 7062 token = self._curr 7063 token_type = token.token_type 7064 7065 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 7066 return None 7067 7068 next_type = self._next.token_type 7069 7070 if ( 7071 next_type in self.COLUMN_OPERATORS 7072 or next_type in self.COLUMN_POSTFIX_TOKENS 7073 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 7074 ): 7075 return None 7076 7077 self._advance() 7078 return primary_parser(self, token) 7079 7080 def _parse_column(self) -> exp.Expr | None: 7081 column: exp.Expr | None = self._parse_column_parts_fast() 7082 if column is None: 7083 this = self._parse_column_reference() 7084 if not this: 7085 this = self._parse_bracket(this) 7086 column = self._parse_column_ops(this) if this else this 7087 7088 if column: 7089 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS and self._match(TokenType.JOIN_MARKER): 7090 column.set("join_mark", True) 7091 if self.COLON_IS_VARIANT_EXTRACT: 7092 column = self._parse_colon_as_variant_extract(column) 7093 7094 return column 7095 7096 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 7097 """Fast path for simple column and dot references (a, a.b, ...). 7098 7099 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 7100 that nothing complex follows. If it does, retreats and returns None so 7101 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 7102 """ 7103 index = self._index 7104 parts: list[exp.Identifier] | None = None 7105 all_comments: list[str] | None = None 7106 7107 while self._match_set(self.IDENTIFIER_TOKENS): 7108 token = self._prev 7109 comments = self._prev_comments 7110 7111 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 7112 self._retreat(index) 7113 return None 7114 7115 has_dot = self._match(TokenType.DOT) 7116 curr_tt = self._curr.token_type 7117 7118 if not has_dot: 7119 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 7120 self._retreat(index) 7121 return None 7122 elif curr_tt not in self.IDENTIFIER_TOKENS: 7123 self._retreat(index) 7124 return None 7125 7126 if parts is None: 7127 parts = [] 7128 7129 if comments: 7130 if all_comments is None: 7131 all_comments = [] 7132 all_comments.extend(comments) 7133 self._prev_comments = [] 7134 7135 parts.append( 7136 self.expression( 7137 exp.Identifier( 7138 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 7139 ), 7140 token, 7141 ) 7142 ) 7143 7144 if not has_dot: 7145 break 7146 7147 if parts is None: 7148 return None 7149 7150 n = len(parts) 7151 7152 if n == 1: 7153 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 7154 elif n == 2: 7155 column = exp.Column(this=parts[1], table=parts[0]) 7156 elif n == 3: 7157 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 7158 else: 7159 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 7160 7161 for i in range(4, n): 7162 column = exp.Dot(this=column, expression=parts[i]) 7163 7164 if all_comments: 7165 column.add_comments(all_comments) 7166 7167 return column 7168 7169 def _parse_column_reference(self) -> exp.Expr | None: 7170 this = self._parse_field() 7171 if ( 7172 not this 7173 and self._match(TokenType.VALUES, advance=False) 7174 and self.VALUES_FOLLOWED_BY_PAREN 7175 and (not self._next or self._next.token_type != TokenType.L_PAREN) 7176 ): 7177 this = self._parse_id_var() 7178 7179 if isinstance(this, exp.Identifier): 7180 # We bubble up comments from the Identifier to the Column 7181 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 7182 7183 return this 7184 7185 def _build_json_extract( 7186 self, 7187 this: exp.Expr | None, 7188 path_parts: list[exp.JSONPathPart], 7189 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 7190 if len(path_parts) > 1: 7191 this = self.expression( 7192 exp.JSONExtract( 7193 this=this, 7194 expression=exp.JSONPath(expressions=path_parts), 7195 variant_extract=True, 7196 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 7197 ) 7198 ) 7199 path_parts = [exp.JSONPathRoot()] 7200 7201 return this, path_parts 7202 7203 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 7204 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 7205 7206 while self._match(TokenType.COLON): 7207 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 7208 this, path_parts = self._build_json_extract(this, path_parts) 7209 7210 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7211 7212 if key: 7213 quoted = isinstance(key, exp.Identifier) and key.quoted 7214 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 7215 7216 while True: 7217 if self._match(TokenType.DOT): 7218 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7219 7220 if next_key: 7221 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 7222 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 7223 elif self._match(TokenType.L_BRACKET): 7224 bracket_expr = self._parse_bracket_key_value() 7225 7226 if not self._match(TokenType.R_BRACKET): 7227 self.raise_error("Expected ]") 7228 7229 if bracket_expr: 7230 if bracket_expr.is_string: 7231 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7232 elif bracket_expr.is_star: 7233 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7234 elif bracket_expr.is_number: 7235 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7236 else: 7237 this, path_parts = self._build_json_extract(this, path_parts) 7238 7239 this = self.expression( 7240 exp.Bracket( 7241 this=this, expressions=[bracket_expr], json_access=True 7242 ), 7243 ) 7244 7245 elif self._match(TokenType.DCOLON): 7246 this, path_parts = self._build_json_extract(this, path_parts) 7247 7248 cast_type = self._parse_types() 7249 if cast_type: 7250 this = self.expression(exp.Cast(this=this, to=cast_type)) 7251 else: 7252 self.raise_error("Expected type after '::'") 7253 else: 7254 break 7255 7256 this, _ = self._build_json_extract(this, path_parts) 7257 7258 return this 7259 7260 def _parse_dcolon(self) -> exp.Expr | None: 7261 return self._parse_types() 7262 7263 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7264 while self._curr.token_type in self.BRACKETS: 7265 this = self._parse_bracket(this) 7266 7267 column_operators = self.COLUMN_OPERATORS 7268 cast_column_operators = self.CAST_COLUMN_OPERATORS 7269 while self._curr: 7270 op_token = self._curr.token_type 7271 7272 if op_token not in column_operators: 7273 break 7274 op = column_operators[op_token] 7275 self._advance() 7276 7277 if op_token in cast_column_operators: 7278 field = self._parse_dcolon() 7279 if not field: 7280 self.raise_error("Expected type") 7281 elif op and self._curr: 7282 field = self._parse_column_reference() or self._parse_bitwise() 7283 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7284 field = self._parse_column_ops(field) 7285 else: 7286 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7287 field = self._parse_field(any_token=True, anonymous_func=True) 7288 7289 # In t.true, t.null we should produce an Identifier node 7290 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7291 field = self.expression( 7292 exp.Identifier(this=self._prev.text), 7293 comments=field.comments, 7294 ) 7295 7296 # Function calls can be qualified, e.g., x.y.FOO() 7297 # This converts the final AST to a series of Dots leading to the function call 7298 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7299 if isinstance(field, (exp.Func, exp.Window)) and this: 7300 this = this.transform( 7301 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7302 ) 7303 7304 if op: 7305 this = op(self, this, field) 7306 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7307 this = self.expression( 7308 exp.Column( 7309 this=field, 7310 table=this.this, 7311 db=this.args.get("table"), 7312 catalog=this.args.get("db"), 7313 ), 7314 comments=this.comments, 7315 ) 7316 elif isinstance(field, exp.Window): 7317 # Move the exp.Dot's to the window's function 7318 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7319 field.set("this", window_func) 7320 this = field 7321 else: 7322 this = self.expression(exp.Dot(this=this, expression=field)) 7323 7324 if field and field.comments: 7325 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7326 7327 this = self._parse_bracket(this) 7328 7329 return this 7330 7331 def _parse_paren(self) -> exp.Expr | None: 7332 if not self._match(TokenType.L_PAREN): 7333 return None 7334 7335 comments = self._prev_comments 7336 query = self._parse_select() 7337 7338 if query: 7339 expressions = [query] 7340 else: 7341 expressions = self._parse_expressions() 7342 7343 this = seq_get(expressions, 0) 7344 7345 if not this and self._match(TokenType.R_PAREN, advance=False): 7346 this = self.expression(exp.Tuple()) 7347 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7348 this = self.expression(exp.Tuple(expressions=expressions)) 7349 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7350 this = self._parse_subquery(this=this, parse_alias=False) 7351 elif isinstance(this, (exp.Subquery, exp.Values)): 7352 this = self._parse_subquery( 7353 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7354 parse_alias=False, 7355 ) 7356 else: 7357 this = self.expression(exp.Paren(this=this)) 7358 7359 if this: 7360 this.add_comments(comments) 7361 7362 self._match_r_paren(expression=this) 7363 7364 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7365 return self._parse_window(this) 7366 7367 return this 7368 7369 def _parse_primary(self) -> exp.Expr | None: 7370 if self._match_set(self.PRIMARY_PARSERS): 7371 token_type = self._prev.token_type 7372 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7373 7374 if token_type == TokenType.STRING: 7375 expressions = [primary] 7376 while self._match(TokenType.STRING, advance=False): 7377 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7378 self.raise_error( 7379 "Adjacent string literals need to be separated by whitespace or comments" 7380 ) 7381 7382 self._advance() 7383 expressions.append(exp.Literal.string(self._prev.text)) 7384 7385 if len(expressions) > 1: 7386 return self.expression( 7387 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7388 ) 7389 7390 return primary 7391 7392 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7393 return exp.Literal.number(f"0.{self._prev.text}") 7394 7395 return self._parse_paren() 7396 7397 def _parse_field( 7398 self, 7399 any_token: bool = False, 7400 tokens: t.Collection[TokenType] | None = None, 7401 anonymous_func: bool = False, 7402 ) -> exp.Expr | None: 7403 after_dot = ( 7404 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7405 ) 7406 7407 if anonymous_func: 7408 field = ( 7409 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7410 or self._parse_primary() 7411 ) 7412 else: 7413 field = self._parse_primary() or self._parse_function( 7414 anonymous=anonymous_func, any_token=any_token 7415 ) 7416 7417 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7418 7419 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7420 name = field.name 7421 if self._is_connected() and self._parse_var(any_token=True): 7422 name += self._prev.text 7423 7424 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7425 7426 return field 7427 7428 def _parse_function( 7429 self, 7430 functions: dict[str, t.Callable] | None = None, 7431 anonymous: bool = False, 7432 optional_parens: bool = True, 7433 any_token: bool = False, 7434 ) -> exp.Expr | None: 7435 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7436 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7437 fn_syntax = False 7438 if ( 7439 self._match(TokenType.L_BRACE, advance=False) 7440 and self._next 7441 and self._next.text.upper() == "FN" 7442 ): 7443 self._advance(2) 7444 fn_syntax = True 7445 7446 func = self._parse_function_call( 7447 functions=functions, 7448 anonymous=anonymous, 7449 optional_parens=optional_parens, 7450 any_token=any_token, 7451 ) 7452 7453 if fn_syntax: 7454 self._match(TokenType.R_BRACE) 7455 7456 return func 7457 7458 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7459 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7460 7461 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7462 args = self._parse_function_args(alias=False) 7463 if not args: 7464 self.raise_error("Expected at least one argument") 7465 7466 # Wrapped so the connector keeps its precedence in the parent context 7467 return exp.Paren(this=connector(*args, copy=False)) 7468 7469 def _parse_function_call( 7470 self, 7471 functions: dict[str, t.Callable] | None = None, 7472 anonymous: bool = False, 7473 optional_parens: bool = True, 7474 any_token: bool = False, 7475 ) -> exp.Expr | None: 7476 if not self._curr: 7477 return None 7478 7479 comments = self._curr.comments 7480 prev = self._prev 7481 token = self._curr 7482 token_type = self._curr.token_type 7483 this: str | exp.Expr = self._curr.text 7484 upper = self._curr.text.upper() 7485 7486 after_dot = prev.token_type == TokenType.DOT 7487 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7488 if ( 7489 optional_parens 7490 and parser 7491 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7492 and not after_dot 7493 ): 7494 self._advance() 7495 return self._parse_window(parser(self)) 7496 7497 if self._next.token_type != TokenType.L_PAREN: 7498 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7499 self._advance() 7500 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7501 7502 return None 7503 7504 if any_token: 7505 if token_type in self.RESERVED_TOKENS: 7506 return None 7507 elif token_type not in self.FUNC_TOKENS: 7508 return None 7509 7510 self._advance(2) 7511 7512 parser = self.FUNCTION_PARSERS.get(upper) 7513 if parser and not anonymous: 7514 result = parser(self) 7515 else: 7516 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7517 7518 if subquery_predicate: 7519 expr = None 7520 if self._curr.token_type in self.SUBQUERY_TOKENS: 7521 expr = self._parse_select() 7522 self._match_r_paren() 7523 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7524 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7525 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7526 self._advance(-1) 7527 expr = self._parse_bitwise() 7528 7529 if expr: 7530 return self.expression(subquery_predicate(this=expr), comments=comments) 7531 7532 if functions is None: 7533 functions = self.FUNCTIONS 7534 7535 function = functions.get(upper) 7536 known_function = function and not anonymous 7537 7538 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7539 args = self._parse_function_args(alias) 7540 7541 post_func_comments = self._curr.comments if self._curr else None 7542 if known_function and post_func_comments: 7543 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7544 # call we'll construct it as exp.Anonymous, even if it's "known" 7545 if any( 7546 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7547 for comment in post_func_comments 7548 ): 7549 known_function = False 7550 7551 if alias and known_function: 7552 args = self._kv_to_prop_eq(args) 7553 7554 if known_function: 7555 func_builder = t.cast(t.Callable, function) 7556 7557 # mypyc compiled functions don't have __code__, so we use 7558 # try/except to check if func_builder accepts 'dialect'. 7559 try: 7560 func = func_builder(args) 7561 except TypeError: 7562 func = func_builder(args, dialect=self.dialect) 7563 7564 func = self.validate_expression(func, args) 7565 if name_key := self.dialect.ORIGINAL_NAME_META_KEY: 7566 func.meta[name_key] = this 7567 7568 result = func 7569 else: 7570 if token_type == TokenType.IDENTIFIER: 7571 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7572 7573 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7574 7575 result = result.update_positions(token) 7576 7577 if isinstance(result, exp.Expr): 7578 result.add_comments(comments) 7579 7580 if parser: 7581 self._match(TokenType.R_PAREN, expression=result) 7582 else: 7583 self._match_r_paren(result) 7584 return self._parse_window(result) 7585 7586 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7587 return expression 7588 7589 def _kv_to_prop_eq( 7590 self, expressions: list[exp.Expr], parse_map: bool = False 7591 ) -> list[exp.Expr]: 7592 transformed = [] 7593 7594 for index, e in enumerate(expressions): 7595 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7596 if isinstance(e, exp.Alias): 7597 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7598 7599 if not isinstance(e, exp.PropertyEQ): 7600 e = self.expression( 7601 exp.PropertyEQ( 7602 this=e.this if parse_map else exp.to_identifier(e.this.name), 7603 expression=e.expression, 7604 ) 7605 ) 7606 7607 if not parse_map and isinstance(e.this, exp.Column): 7608 e.this.replace(e.this.this) 7609 else: 7610 e = self._to_prop_eq(e, index) 7611 7612 transformed.append(e) 7613 7614 return transformed 7615 7616 def _parse_function_properties(self) -> exp.Properties | None: 7617 # Skip the generic `key = value` fallback in _parse_property since this 7618 # runs post-AS where a function body like `name = expr` can be misread 7619 # as a property. 7620 properties = [] 7621 while True: 7622 if self._match_texts(self.PROPERTY_PARSERS): 7623 keyword = self._prev.text.upper() 7624 prop = self.PROPERTY_PARSERS[keyword](self) 7625 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7626 keyword = self._prev.text.upper() 7627 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7628 else: 7629 break 7630 if not prop: 7631 self.raise_error(f"Failed to parse property '{keyword}'") 7632 break 7633 for p in ensure_list(prop): 7634 properties.append(p) 7635 7636 return self.expression(exp.Properties(expressions=properties)) if properties else None 7637 7638 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7639 return self._parse_statement() 7640 7641 def _parse_function_parameter(self) -> exp.Expr | None: 7642 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7643 7644 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7645 this = self._parse_table_parts(schema=True) 7646 7647 if not self._match(TokenType.L_PAREN): 7648 return this 7649 7650 expressions = self._parse_csv(self._parse_function_parameter) 7651 self._match_r_paren() 7652 return self.expression( 7653 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7654 ) 7655 7656 def _parse_macro_overloads( 7657 self, 7658 this: exp.UserDefinedFunction, 7659 first_body: exp.Expr, 7660 first_is_table: bool = False, 7661 ) -> exp.MacroOverloads: 7662 overloads = [ 7663 self.expression( 7664 exp.MacroOverload( 7665 this=first_body, 7666 expressions=this.expressions or None, 7667 is_table=first_is_table, 7668 ) 7669 ) 7670 ] 7671 this.set("expressions", None) 7672 this.set("wrapped", False) 7673 7674 while self._match(TokenType.COMMA): 7675 if not self._match(TokenType.L_PAREN): 7676 break 7677 7678 params = self._parse_csv(self._parse_function_parameter) 7679 self._match_r_paren() 7680 7681 if not self._match(TokenType.ALIAS): 7682 break 7683 7684 is_table = self._match(TokenType.TABLE) 7685 body = self._parse_expression() 7686 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7687 overloads.append(self.expression(macro)) 7688 7689 return self.expression(exp.MacroOverloads(expressions=overloads)) 7690 7691 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7692 literal = self._parse_primary() 7693 if literal: 7694 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7695 7696 return self._identifier_expression(token) 7697 7698 def _parse_session_parameter(self) -> exp.SessionParameter: 7699 kind = None 7700 this = self._parse_id_var() or self._parse_primary() 7701 7702 if this and self._match(TokenType.DOT): 7703 kind = this.name 7704 this = self._parse_var() or self._parse_primary() 7705 7706 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7707 7708 def _parse_lambda_arg(self) -> exp.Expr | None: 7709 return self._parse_id_var() 7710 7711 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7712 next_token_type = self._next.token_type 7713 7714 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7715 if ( 7716 next_token_type in self.LAMBDA_ARG_TERMINATORS 7717 and (atom := self._parse_atom()) is not None 7718 ): 7719 return atom 7720 7721 index = self._index 7722 7723 if self._match(TokenType.L_PAREN): 7724 expressions = t.cast( 7725 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7726 ) 7727 7728 if not self._match(TokenType.R_PAREN): 7729 self._retreat(index) 7730 elif self._match_set(self.LAMBDAS): 7731 return self.LAMBDAS[self._prev.token_type](self, expressions) 7732 else: 7733 self._retreat(index) 7734 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7735 expressions = [self._parse_lambda_arg()] 7736 7737 if self._match_set(self.LAMBDAS): 7738 return self.LAMBDAS[self._prev.token_type](self, expressions) 7739 7740 self._retreat(index) 7741 7742 this: exp.Expr | None 7743 7744 if self._match(TokenType.DISTINCT): 7745 this = self.expression( 7746 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7747 ) 7748 else: 7749 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7750 this = self._parse_select_or_expression(alias=alias) 7751 7752 return self._parse_limit( 7753 self._parse_respect_or_ignore_nulls( 7754 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7755 ) 7756 ) 7757 7758 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7759 index = self._index 7760 if not self._match(TokenType.L_PAREN): 7761 return this 7762 7763 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7764 # expr can be of both types 7765 if self._match_set(self.SELECT_START_TOKENS): 7766 self._retreat(index) 7767 return this 7768 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7769 self._match_r_paren() 7770 return self.expression(exp.Schema(this=this, expressions=args)) 7771 7772 def _parse_field_def(self) -> exp.Expr | None: 7773 return self._parse_column_def(self._parse_field(any_token=True)) 7774 7775 def _parse_column_def( 7776 self, this: exp.Expr | None, computed_column: bool = True 7777 ) -> exp.Expr | None: 7778 # column defs are not really columns, they're identifiers 7779 if isinstance(this, exp.Column): 7780 this = this.this 7781 7782 if not computed_column: 7783 self._match(TokenType.ALIAS) 7784 7785 kind = self._parse_types(schema=True) 7786 7787 if self._match_text_seq("FOR", "ORDINALITY"): 7788 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7789 7790 constraints: list[exp.Expr] = [] 7791 7792 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7793 ("ALIAS", "MATERIALIZED") 7794 ): 7795 # Match storage before _parse_types so STORED is not treated as a data type 7796 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7797 persisted = self._prev.text.upper() == "MATERIALIZED" 7798 expression = self._parse_disjunction() 7799 if not persisted: 7800 if self._match_text_seq("PERSISTED"): 7801 persisted = True 7802 elif self._match_texts(("STORED", "VIRTUAL")): 7803 persisted = self._prev.text.upper() == "STORED" 7804 constraint_kind = exp.ComputedColumnConstraint( 7805 this=expression, 7806 persisted=persisted, 7807 data_type=exp.Var(this="AUTO") 7808 if self._match_text_seq("AUTO") 7809 else self._parse_types(), 7810 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7811 ) 7812 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7813 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7814 in_out_constraint = self.expression( 7815 exp.InOutColumnConstraint( 7816 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7817 ) 7818 ) 7819 constraints.append(in_out_constraint) 7820 kind = self._parse_types() 7821 elif ( 7822 kind 7823 and self._match(TokenType.ALIAS, advance=False) 7824 and ( 7825 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7826 or self._next.token_type == TokenType.L_PAREN 7827 ) 7828 ): 7829 self._advance() 7830 constraints.append( 7831 self.expression( 7832 exp.ColumnConstraint( 7833 kind=exp.ComputedColumnConstraint( 7834 this=self._parse_disjunction(), 7835 persisted=self._match_texts(("STORED", "VIRTUAL")) 7836 and self._prev.text.upper() == "STORED", 7837 ) 7838 ) 7839 ) 7840 ) 7841 7842 while True: 7843 constraint = self._parse_column_constraint() 7844 if not constraint: 7845 break 7846 constraints.append(constraint) 7847 7848 if not kind and not constraints: 7849 return this 7850 7851 position = None 7852 if self._match_texts(("FIRST", "AFTER")): 7853 pos = self._prev.text 7854 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7855 7856 return self.expression( 7857 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7858 ) 7859 7860 def _parse_auto_increment( 7861 self, 7862 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7863 start = None 7864 increment = None 7865 order = None 7866 7867 if self._match(TokenType.L_PAREN, advance=False): 7868 args = self._parse_wrapped_csv(self._parse_bitwise) 7869 start = seq_get(args, 0) 7870 increment = seq_get(args, 1) 7871 7872 # The remaining parts form an unordered bag and any of them can be omitted, in which 7873 # case the engine falls back to its own default, so they're parsed independently. 7874 while True: 7875 if self._match_text_seq("START"): 7876 start = self._parse_bitwise() 7877 elif self._match_text_seq("INCREMENT"): 7878 increment = self._parse_bitwise() 7879 elif self._match_text_seq("ORDER"): 7880 order = True 7881 elif self._match_text_seq("NOORDER"): 7882 order = False 7883 else: 7884 break 7885 7886 if start or increment or order is not None: 7887 return exp.GeneratedAsIdentityColumnConstraint( 7888 start=start, increment=increment, this=False, order=order 7889 ) 7890 7891 return exp.AutoIncrementColumnConstraint() 7892 7893 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7894 if not self._match(TokenType.L_PAREN, advance=False): 7895 return None 7896 7897 return self.expression( 7898 exp.CheckColumnConstraint( 7899 this=self._parse_wrapped(self._parse_assignment), 7900 enforced=self._match_text_seq("ENFORCED"), 7901 ) 7902 ) 7903 7904 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7905 if not self._match_text_seq("REFRESH"): 7906 self._retreat(self._index - 1) 7907 return None 7908 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7909 7910 def _parse_compress(self) -> exp.CompressColumnConstraint: 7911 if self._match(TokenType.L_PAREN, advance=False): 7912 return self.expression( 7913 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7914 ) 7915 7916 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7917 7918 def _parse_generated_as_identity( 7919 self, 7920 ) -> ( 7921 exp.GeneratedAsIdentityColumnConstraint 7922 | exp.ComputedColumnConstraint 7923 | exp.GeneratedAsRowColumnConstraint 7924 ): 7925 if self._match_text_seq("BY", "DEFAULT"): 7926 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7927 this = self.expression( 7928 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7929 ) 7930 else: 7931 self._match_text_seq("ALWAYS") 7932 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7933 7934 self._match(TokenType.ALIAS) 7935 7936 if self._match_text_seq("ROW"): 7937 start = self._match_text_seq("START") 7938 if not start: 7939 self._match(TokenType.END) 7940 hidden = self._match_text_seq("HIDDEN") 7941 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7942 7943 identity = self._match_text_seq("IDENTITY") 7944 7945 if self._match(TokenType.L_PAREN): 7946 if self._match_text_seq("START", "WITH"): 7947 this.set("start", self._parse_bitwise()) 7948 if self._match_text_seq("INCREMENT", "BY"): 7949 this.set("increment", self._parse_bitwise()) 7950 if self._match_text_seq("MINVALUE"): 7951 this.set("minvalue", self._parse_bitwise()) 7952 if self._match_text_seq("MAXVALUE"): 7953 this.set("maxvalue", self._parse_bitwise()) 7954 7955 if self._match_text_seq("CYCLE"): 7956 this.set("cycle", True) 7957 elif self._match_text_seq("NO", "CYCLE"): 7958 this.set("cycle", False) 7959 7960 if not identity: 7961 this.set("expression", self._parse_disjunction()) 7962 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7963 args = self._parse_csv(self._parse_bitwise) 7964 this.set("start", seq_get(args, 0)) 7965 this.set("increment", seq_get(args, 1)) 7966 7967 self._match_r_paren() 7968 7969 return this 7970 7971 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7972 self._match_text_seq("LENGTH") 7973 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7974 7975 def _parse_not_constraint(self) -> exp.Expr | None: 7976 if self._match_text_seq("NULL"): 7977 return self.expression(exp.NotNullColumnConstraint()) 7978 if self._match_text_seq("CASESPECIFIC"): 7979 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7980 if self._match_text_seq("FOR", "REPLICATION"): 7981 return self.expression(exp.NotForReplicationColumnConstraint()) 7982 7983 # Unconsume the `NOT` token 7984 self._retreat(self._index - 1) 7985 return None 7986 7987 def _parse_column_constraint(self) -> exp.Expr | None: 7988 this = ( 7989 self._parse_id_var() 7990 if self._match(TokenType.CONSTRAINT) 7991 and not self._match_texts(self.RESERVED_CONSTRAINT_KINDS, advance=False) 7992 else None 7993 ) 7994 7995 procedure_option_follows = ( 7996 self._match(TokenType.WITH, advance=False) 7997 and self._next 7998 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7999 ) 8000 8001 index = self._index 8002 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 8003 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 8004 if not constraint: 8005 self._retreat(index) 8006 return None 8007 8008 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 8009 8010 if self._match(TokenType.CHARACTER_SET) or self._match_pair(TokenType.CHAR, TokenType.SET): 8011 return self.expression( 8012 exp.ColumnConstraint( 8013 this=this, 8014 kind=self.expression( 8015 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 8016 ), 8017 ) 8018 ) 8019 8020 return this 8021 8022 def _parse_constraint(self) -> exp.Expr | None: 8023 if not self._match(TokenType.CONSTRAINT) or self._match_texts( 8024 self.RESERVED_CONSTRAINT_KINDS, advance=False 8025 ): 8026 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 8027 8028 return self.expression( 8029 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 8030 ) 8031 8032 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 8033 constraints = [] 8034 while True: 8035 constraint = self._parse_unnamed_constraint() or self._parse_function() 8036 if not constraint: 8037 break 8038 constraints.append(constraint) 8039 8040 return constraints 8041 8042 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 8043 index = self._index 8044 8045 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 8046 constraints or self.CONSTRAINT_PARSERS 8047 ): 8048 return None 8049 8050 constraint_key = self._prev.text.upper() 8051 if constraint_key not in self.CONSTRAINT_PARSERS: 8052 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 8053 8054 result = self.CONSTRAINT_PARSERS[constraint_key](self) 8055 if not result: 8056 self._retreat(index) 8057 8058 return result 8059 8060 def _parse_unique_key(self) -> exp.Expr | None: 8061 if ( 8062 self._curr 8063 and self._curr.token_type != TokenType.IDENTIFIER 8064 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 8065 ): 8066 return None 8067 return self._parse_id_var(any_token=False) 8068 8069 def _parse_unique(self) -> exp.UniqueColumnConstraint: 8070 self._match_texts(("KEY", "INDEX")) 8071 return self.expression( 8072 exp.UniqueColumnConstraint( 8073 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 8074 this=self._parse_schema(self._parse_unique_key()), 8075 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 8076 on_conflict=self._parse_on_conflict(), 8077 options=self._parse_key_constraint_options(), 8078 ) 8079 ) 8080 8081 def _parse_key_constraint_options(self) -> list[str]: 8082 options = [] 8083 while True: 8084 if not self._curr: 8085 break 8086 8087 if self._match(TokenType.ON): 8088 action = None 8089 on = self._advance_any() and self._prev.text 8090 8091 if self._match_text_seq("NO", "ACTION"): 8092 action = "NO ACTION" 8093 elif self._match_text_seq("CASCADE"): 8094 action = "CASCADE" 8095 elif self._match_text_seq("RESTRICT"): 8096 action = "RESTRICT" 8097 elif self._match_pair(TokenType.SET, TokenType.NULL): 8098 action = "SET NULL" 8099 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 8100 action = "SET DEFAULT" 8101 else: 8102 self.raise_error("Invalid key constraint") 8103 8104 options.append(f"ON {on} {action}") 8105 else: 8106 var = self._parse_var_from_options( 8107 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 8108 ) 8109 if not var: 8110 break 8111 options.append(var.name) 8112 8113 return options 8114 8115 def _parse_references(self, match: bool = True) -> exp.Reference | None: 8116 if match and not self._match(TokenType.REFERENCES): 8117 return None 8118 8119 expressions: list | None = None 8120 this = self._parse_table(schema=True) 8121 options = self._parse_key_constraint_options() 8122 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 8123 8124 def _parse_foreign_key(self) -> exp.ForeignKey: 8125 expressions = ( 8126 self._parse_wrapped_id_vars() 8127 if not self._match(TokenType.REFERENCES, advance=False) 8128 else None 8129 ) 8130 reference = self._parse_references() 8131 on_options = {} 8132 8133 while self._match(TokenType.ON): 8134 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 8135 self.raise_error("Expected DELETE or UPDATE") 8136 8137 kind = self._prev.text.lower() 8138 8139 if self._match_text_seq("NO", "ACTION"): 8140 action = "NO ACTION" 8141 elif self._match(TokenType.SET): 8142 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 8143 action = "SET " + self._prev.text.upper() 8144 else: 8145 self._advance() 8146 action = self._prev.text.upper() 8147 8148 on_options[kind] = action 8149 8150 return self.expression( 8151 exp.ForeignKey( 8152 expressions=expressions, 8153 reference=reference, 8154 options=self._parse_key_constraint_options(), 8155 **on_options, 8156 ) 8157 ) 8158 8159 def _parse_primary_key_part(self) -> exp.Expr | None: 8160 return self._parse_field() 8161 8162 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 8163 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 8164 self._retreat(self._index - 1) 8165 return None 8166 8167 id_vars = self._parse_wrapped_id_vars() 8168 return self.expression( 8169 exp.PeriodForSystemTimeConstraint( 8170 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 8171 ) 8172 ) 8173 8174 def _parse_primary_key( 8175 self, 8176 wrapped_optional: bool = False, 8177 in_props: bool = False, 8178 named_primary_key: bool = False, 8179 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 8180 desc = ( 8181 self._prev.token_type == TokenType.DESC 8182 if self._match_set((TokenType.ASC, TokenType.DESC)) 8183 else None 8184 ) 8185 8186 this = None 8187 if ( 8188 named_primary_key 8189 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 8190 and self._next 8191 and self._next.token_type == TokenType.L_PAREN 8192 ): 8193 this = self._parse_id_var() 8194 8195 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 8196 return self.expression( 8197 exp.PrimaryKeyColumnConstraint( 8198 desc=desc, options=self._parse_key_constraint_options() 8199 ) 8200 ) 8201 8202 expressions = self._parse_wrapped_csv( 8203 self._parse_primary_key_part, optional=wrapped_optional 8204 ) 8205 8206 return self.expression( 8207 exp.PrimaryKey( 8208 this=this, 8209 expressions=expressions, 8210 include=self._parse_index_params(), 8211 options=self._parse_key_constraint_options(), 8212 ) 8213 ) 8214 8215 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 8216 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 8217 8218 def _parse_odbc_datetime_literal(self) -> exp.Expr: 8219 """ 8220 Parses a datetime column in ODBC format. We parse the column into the corresponding 8221 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 8222 same as we did for `DATE('yyyy-mm-dd')`. 8223 8224 Reference: 8225 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 8226 """ 8227 self._match(TokenType.VAR) 8228 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 8229 expression = self.expression(exp_class(this=self._parse_string())) 8230 if not self._match(TokenType.R_BRACE): 8231 self.raise_error("Expected }") 8232 return expression 8233 8234 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 8235 if not self._match_set(self.BRACKETS): 8236 return this 8237 8238 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8239 map_token = seq_get(self._tokens, self._index - 2) 8240 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8241 else: 8242 parse_map = False 8243 8244 bracket_kind = self._prev.token_type 8245 if ( 8246 bracket_kind == TokenType.L_BRACE 8247 and self._curr 8248 and self._curr.token_type == TokenType.VAR 8249 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8250 ): 8251 return self._parse_odbc_datetime_literal() 8252 8253 expressions = self._parse_csv( 8254 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8255 ) 8256 8257 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8258 self.raise_error("Expected ]") 8259 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8260 self.raise_error("Expected }") 8261 8262 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8263 if bracket_kind == TokenType.L_BRACE: 8264 this = self.expression( 8265 exp.Struct( 8266 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8267 ) 8268 ) 8269 elif not this: 8270 this = build_array_constructor( 8271 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8272 ) 8273 else: 8274 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8275 if constructor_type: 8276 return build_array_constructor( 8277 constructor_type, 8278 args=expressions, 8279 bracket_kind=bracket_kind, 8280 dialect=self.dialect, 8281 ) 8282 8283 expressions = apply_index_offset( 8284 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8285 ) 8286 this = self.expression( 8287 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8288 ) 8289 8290 self._add_comments(this) 8291 return self._parse_bracket(this) 8292 8293 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8294 if not self._match(TokenType.COLON): 8295 return this 8296 8297 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8298 self._advance() 8299 end: exp.Expr | None = -exp.Literal.number("1") 8300 else: 8301 end = self._parse_assignment() 8302 step = self._parse_unary() if self._match(TokenType.COLON) else None 8303 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8304 8305 def _parse_case(self) -> exp.Expr | None: 8306 if self._match(TokenType.DOT, advance=False): 8307 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8308 self._retreat(self._index - 1) 8309 return None 8310 8311 ifs = [] 8312 default = None 8313 8314 comments = self._prev_comments 8315 expression = self._parse_disjunction() 8316 8317 while self._match(TokenType.WHEN): 8318 this = self._parse_disjunction() 8319 self._match(TokenType.THEN) 8320 then = self._parse_disjunction() 8321 ifs.append(self.expression(exp.If(this=this, true=then))) 8322 8323 if self._match(TokenType.ELSE): 8324 default = self._parse_disjunction() 8325 8326 if not self._match(TokenType.END): 8327 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8328 default = exp.column("interval") 8329 else: 8330 self.raise_error("Expected END after CASE", self._prev) 8331 8332 return self.expression( 8333 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8334 ) 8335 8336 def _parse_if(self) -> exp.Expr | None: 8337 if self._match(TokenType.L_PAREN): 8338 args = self._parse_csv( 8339 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8340 ) 8341 this = self.validate_expression(exp.If.from_arg_list(args), args) 8342 self._match_r_paren() 8343 else: 8344 index = self._index - 1 8345 8346 if self.NO_PAREN_IF_COMMANDS and index == 0: 8347 return self._parse_as_command(self._prev) 8348 8349 condition = self._parse_disjunction() 8350 8351 if not condition or not self._match(TokenType.THEN): 8352 self._retreat(index) 8353 return None 8354 8355 true = self._parse_disjunction() 8356 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8357 self._match(TokenType.END) 8358 this = self.expression(exp.If(this=condition, true=true, false=false)) 8359 8360 return this 8361 8362 def _parse_next_value_for(self) -> exp.Expr | None: 8363 if not self._match_text_seq("VALUE", "FOR"): 8364 self._retreat(self._index - 1) 8365 return None 8366 8367 return self.expression( 8368 exp.NextValueFor( 8369 this=self._parse_column(), 8370 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8371 ) 8372 ) 8373 8374 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8375 this = self._parse_function() or self._parse_var_or_string(upper=True) 8376 8377 if self._match(TokenType.FROM): 8378 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8379 8380 if not self._match(TokenType.COMMA): 8381 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8382 8383 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8384 8385 def _parse_gap_fill(self) -> exp.GapFill: 8386 self._match(TokenType.TABLE) 8387 this = self._parse_table() 8388 8389 self._match(TokenType.COMMA) 8390 args = [this, *self._parse_csv(self._parse_lambda)] 8391 8392 gap_fill = exp.GapFill.from_arg_list(args) 8393 return self.validate_expression(gap_fill, args) 8394 8395 def _parse_char(self) -> exp.Chr: 8396 return self.expression( 8397 exp.Chr( 8398 expressions=self._parse_csv(self._parse_assignment), 8399 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8400 ) 8401 ) 8402 8403 def _parse_charset_name(self) -> exp.Expr | None: 8404 """ 8405 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8406 for specific name shapes override this. 8407 """ 8408 return self._parse_var( 8409 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8410 ) 8411 8412 def _parse_weight_string(self) -> exp.WeightString | exp.Anonymous: 8413 name = self._tokens[self._index - 2].text 8414 args = self._parse_csv(self._parse_assignment) 8415 8416 # MySQL also accepts an undocumented WEIGHT_STRING(str, n, n, n) form 8417 if len(args) != 1: 8418 return self.expression(exp.Anonymous(this=name, expressions=args)) 8419 8420 return self.expression( 8421 exp.WeightString(this=args[0], to=self._match(TokenType.ALIAS) and self._parse_types()) 8422 ) 8423 8424 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8425 this = self._parse_assignment() 8426 8427 if not self._match(TokenType.ALIAS): 8428 if self._match(TokenType.COMMA): 8429 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8430 8431 self.raise_error("Expected AS after CAST") 8432 8433 fmt = None 8434 to = self._parse_types(with_collation=True) 8435 8436 default = None 8437 if self._match(TokenType.DEFAULT): 8438 default = self._parse_bitwise() 8439 self._match_text_seq("ON", "CONVERSION", "ERROR") 8440 8441 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8442 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8443 fmt = self._parse_at_time_zone(fmt_string) 8444 8445 if not to: 8446 to = exp.DType.UNKNOWN.into_expr() 8447 if to.this in exp.DataType.TEMPORAL_TYPES: 8448 this = self.expression( 8449 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8450 this=this, 8451 format=exp.Literal.string( 8452 format_time( 8453 fmt_string.this if fmt_string else "", 8454 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8455 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8456 ) 8457 ), 8458 safe=safe, 8459 ) 8460 ) 8461 8462 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8463 this.set("zone", fmt.args["zone"]) 8464 return this 8465 elif not to: 8466 self.raise_error("Expected TYPE after CAST") 8467 elif isinstance(to, exp.Identifier): 8468 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8469 else: 8470 to = self._parse_char_charset(to) 8471 8472 return self.build_cast( 8473 strict=strict, 8474 this=this, 8475 to=to, 8476 format=fmt, 8477 safe=safe, 8478 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8479 default=default, 8480 ) 8481 8482 def _parse_string_agg(self) -> exp.GroupConcat: 8483 if self._match(TokenType.DISTINCT): 8484 args: list[exp.Expr | None] = [ 8485 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8486 ] 8487 if self._match(TokenType.COMMA): 8488 args.extend(self._parse_csv(self._parse_disjunction)) 8489 else: 8490 args = self._parse_csv(self._parse_disjunction) # type: ignore 8491 8492 if self._match_text_seq("ON", "OVERFLOW"): 8493 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8494 if self._match_text_seq("ERROR"): 8495 on_overflow: exp.Expr | None = exp.var("ERROR") 8496 else: 8497 self._match_text_seq("TRUNCATE") 8498 on_overflow = self.expression( 8499 exp.OverflowTruncateBehavior( 8500 this=self._parse_string(), 8501 with_count=( 8502 self._match_text_seq("WITH", "COUNT") 8503 or not self._match_text_seq("WITHOUT", "COUNT") 8504 ), 8505 ) 8506 ) 8507 else: 8508 on_overflow = None 8509 8510 index = self._index 8511 if not self._match(TokenType.R_PAREN) and args: 8512 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8513 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8514 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8515 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8516 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8517 8518 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8519 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8520 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8521 if not self._match_text_seq("WITHIN", "GROUP"): 8522 self._retreat(index) 8523 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8524 8525 # The corresponding match_r_paren will be called in parse_function (caller) 8526 self._match_l_paren() 8527 8528 return self.expression( 8529 exp.GroupConcat( 8530 this=self._parse_order(this=seq_get(args, 0)), 8531 separator=seq_get(args, 1), 8532 on_overflow=on_overflow, 8533 ) 8534 ) 8535 8536 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8537 this = self._parse_bitwise() 8538 8539 if self._match(TokenType.USING): 8540 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8541 elif self._match(TokenType.COMMA): 8542 to = self._parse_char_charset(self._parse_types()) 8543 else: 8544 to = None 8545 8546 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8547 8548 def _parse_char_charset(self, to: exp.Expr | None) -> exp.Expr | None: 8549 # MySQL: CHAR[(N)] CHARACTER SET charset_name, the length is kept 8550 if ( 8551 isinstance(to, exp.DataType) 8552 and to.this == exp.DType.CHAR 8553 and (self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")) 8554 ): 8555 to.set("this", exp.DType.CHARACTER_SET) 8556 to.set("kind", self._parse_var_or_string()) 8557 return to 8558 8559 def _parse_xml_element(self) -> exp.XMLElement: 8560 if self._match_text_seq("EVALNAME"): 8561 evalname = True 8562 this = self._parse_bitwise() 8563 else: 8564 evalname = None 8565 self._match_text_seq("NAME") 8566 this = self._parse_id_var() 8567 8568 return self.expression( 8569 exp.XMLElement( 8570 this=this, 8571 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8572 evalname=evalname, 8573 ) 8574 ) 8575 8576 def _parse_xml_table(self) -> exp.XMLTable: 8577 namespaces = None 8578 passing = None 8579 columns = None 8580 8581 if self._match_text_seq("XMLNAMESPACES", "("): 8582 namespaces = self._parse_xml_namespace() 8583 self._match_text_seq(")", ",") 8584 8585 this = self._parse_string() 8586 8587 if self._match_text_seq("PASSING"): 8588 # The BY VALUE keywords are optional and are provided for semantic clarity 8589 self._match_text_seq("BY", "VALUE") 8590 passing = self._parse_csv(self._parse_column) 8591 8592 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8593 8594 if self._match_text_seq("COLUMNS"): 8595 columns = self._parse_csv(self._parse_field_def) 8596 8597 return self.expression( 8598 exp.XMLTable( 8599 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8600 ) 8601 ) 8602 8603 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8604 namespaces = [] 8605 8606 while True: 8607 if self._match(TokenType.DEFAULT): 8608 uri = self._parse_string() 8609 else: 8610 uri = self._parse_alias(self._parse_string()) 8611 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8612 if not self._match(TokenType.COMMA): 8613 break 8614 8615 return namespaces 8616 8617 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8618 args = self._parse_csv(self._parse_disjunction) 8619 8620 if len(args) < 3: 8621 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8622 8623 return self.expression(exp.DecodeCase(expressions=args)) 8624 8625 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8626 self._match_text_seq("KEY") 8627 key = self._parse_column() 8628 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8629 self._match_text_seq("VALUE") 8630 value = self._parse_bitwise() 8631 8632 if not key and not value: 8633 return None 8634 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8635 8636 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8637 if not this or not self._match_text_seq("FORMAT", "JSON"): 8638 return this 8639 8640 return self.expression(exp.FormatJson(this=this)) 8641 8642 def _parse_on_condition(self) -> exp.OnCondition | None: 8643 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8644 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8645 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8646 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8647 else: 8648 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8649 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8650 8651 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8652 8653 if not empty and not error and not null: 8654 return None 8655 8656 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8657 8658 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8659 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8660 for value in values: 8661 if self._match_text_seq(value, "ON", on): 8662 return f"{value} ON {on}" 8663 8664 index = self._index 8665 if self._match(TokenType.DEFAULT): 8666 default_value = self._parse_bitwise() 8667 if self._match_text_seq("ON", on): 8668 return default_value 8669 8670 self._retreat(index) 8671 8672 return None 8673 8674 @t.overload 8675 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8676 8677 @t.overload 8678 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8679 8680 def _parse_json_object(self, agg=False): 8681 star = self._parse_star() 8682 expressions = ( 8683 [star] 8684 if star 8685 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8686 ) 8687 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8688 8689 unique_keys = None 8690 if self._match_text_seq("WITH", "UNIQUE"): 8691 unique_keys = True 8692 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8693 unique_keys = False 8694 8695 self._match_text_seq("KEYS") 8696 8697 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8698 self._parse_type() 8699 ) 8700 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8701 8702 return self.expression( 8703 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8704 expressions=expressions, 8705 null_handling=null_handling, 8706 unique_keys=unique_keys, 8707 return_type=return_type, 8708 encoding=encoding, 8709 ) 8710 ) 8711 8712 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8713 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8714 if not self._match_text_seq("NESTED"): 8715 this = self._parse_id_var() 8716 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8717 kind = self._parse_types(allow_identifiers=False) 8718 nested = None 8719 else: 8720 this = None 8721 ordinality = None 8722 kind = None 8723 nested = True 8724 8725 format_json = self._match_text_seq("FORMAT", "JSON") 8726 path = self._match_text_seq("PATH") and self._parse_string() 8727 nested_schema = nested and self._parse_json_schema() 8728 8729 return self.expression( 8730 exp.JSONColumnDef( 8731 this=this, 8732 kind=kind, 8733 path=path, 8734 nested_schema=nested_schema, 8735 ordinality=ordinality, 8736 format_json=format_json, 8737 ) 8738 ) 8739 8740 def _parse_json_schema(self) -> exp.JSONSchema: 8741 self._match_text_seq("COLUMNS") 8742 return self.expression( 8743 exp.JSONSchema( 8744 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8745 ) 8746 ) 8747 8748 def _parse_json_table(self) -> exp.JSONTable: 8749 this = self._parse_format_json(self._parse_bitwise()) 8750 path = self._match(TokenType.COMMA) and self._parse_string() 8751 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8752 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8753 schema = self._parse_json_schema() 8754 8755 return exp.JSONTable( 8756 this=this, 8757 schema=schema, 8758 path=path, 8759 error_handling=error_handling, 8760 empty_handling=empty_handling, 8761 ) 8762 8763 def _parse_match_against(self) -> exp.MatchAgainst: 8764 if self._match_text_seq("TABLE"): 8765 # parse SingleStore MATCH(TABLE ...) syntax 8766 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8767 expressions = [] 8768 table = self._parse_table() 8769 if table: 8770 expressions = [table] 8771 else: 8772 expressions = self._parse_csv(self._parse_column) 8773 8774 self._match_text_seq(")", "AGAINST", "(") 8775 8776 this = self._parse_string() 8777 8778 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8779 modifier = "IN NATURAL LANGUAGE MODE" 8780 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8781 modifier = f"{modifier} WITH QUERY EXPANSION" 8782 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8783 modifier = "IN BOOLEAN MODE" 8784 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8785 modifier = "WITH QUERY EXPANSION" 8786 else: 8787 modifier = None 8788 8789 return self.expression( 8790 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8791 ) 8792 8793 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8794 def _parse_open_json(self) -> exp.OpenJSON: 8795 this = self._parse_bitwise() 8796 path = self._match(TokenType.COMMA) and self._parse_string() 8797 8798 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8799 this = self._parse_field(any_token=True) 8800 kind = self._parse_types() 8801 path = self._parse_string() 8802 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8803 8804 return self.expression( 8805 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8806 ) 8807 8808 expressions = None 8809 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8810 self._match_l_paren() 8811 expressions = self._parse_csv(_parse_open_json_column_def) 8812 8813 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8814 8815 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8816 args = self._parse_csv(self._parse_bitwise) 8817 8818 if self._match(TokenType.IN): 8819 return self.expression( 8820 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8821 ) 8822 8823 if haystack_first: 8824 haystack = seq_get(args, 0) 8825 needle = seq_get(args, 1) 8826 else: 8827 haystack = seq_get(args, 1) 8828 needle = seq_get(args, 0) 8829 8830 return self.expression( 8831 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8832 ) 8833 8834 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8835 args = self._parse_csv(self._parse_table) 8836 return exp.JoinHint(this=func_name.upper(), expressions=args) 8837 8838 def _parse_substring(self) -> exp.Substring: 8839 # Postgres supports the form: substring(string [from int] [for int]) 8840 # (despite being undocumented, the reverse order also works) 8841 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8842 8843 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8844 8845 start, length = None, None 8846 8847 while self._curr: 8848 if self._match(TokenType.FROM): 8849 start = self._parse_bitwise() 8850 elif self._match(TokenType.FOR): 8851 if not start: 8852 start = exp.Literal.number(1) 8853 length = self._parse_bitwise() 8854 else: 8855 break 8856 8857 if start: 8858 args.append(start) 8859 if length: 8860 args.append(length) 8861 8862 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8863 8864 def _parse_trim(self) -> exp.Trim: 8865 # https://www.w3resource.com/sql/character-functions/trim.php 8866 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8867 8868 position = None 8869 collation = None 8870 expression = None 8871 8872 if self._match_texts(self.TRIM_TYPES): 8873 position = self._prev.text.upper() 8874 8875 this = self._parse_bitwise() 8876 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8877 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8878 expression = self._parse_bitwise() 8879 8880 if invert_order: 8881 this, expression = expression, this 8882 8883 if self._match(TokenType.COLLATE): 8884 collation = self._parse_bitwise() 8885 8886 return self.expression( 8887 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8888 ) 8889 8890 def _parse_window_clause(self) -> list[exp.Expr] | None: 8891 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8892 8893 def _parse_named_window(self) -> exp.Expr | None: 8894 return self._parse_window(self._parse_id_var(), alias=True) 8895 8896 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8897 if self._curr.token_type == TokenType.VAR: 8898 if self._match_text_seq("IGNORE", "NULLS"): 8899 return self.expression(exp.IgnoreNulls(this=this)) 8900 if self._match_text_seq("RESPECT", "NULLS"): 8901 return self.expression(exp.RespectNulls(this=this)) 8902 return this 8903 8904 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8905 if self._match(TokenType.HAVING): 8906 self._match_texts(("MAX", "MIN")) 8907 max = self._prev.text.upper() != "MIN" 8908 return self.expression( 8909 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8910 ) 8911 8912 return this 8913 8914 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8915 func = this 8916 comments = func.comments if isinstance(func, exp.Expr) else None 8917 8918 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8919 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8920 if self._match_text_seq("FROM", "FIRST"): 8921 this.set("from_first", True) 8922 elif self._match_text_seq("FROM", "LAST"): 8923 this.set("from_first", False) 8924 8925 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8926 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8927 if self._match_text_seq("WITHIN", "GROUP"): 8928 order = self._parse_wrapped(self._parse_order) 8929 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8930 8931 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8932 self._match(TokenType.WHERE) 8933 this = self.expression( 8934 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8935 ) 8936 self._match_r_paren() 8937 8938 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8939 # Some dialects choose to implement and some do not. 8940 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8941 8942 # There is some code above in _parse_lambda that handles 8943 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8944 8945 # The below changes handle 8946 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8947 8948 # Oracle allows both formats 8949 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8950 # and Snowflake chose to do the same for familiarity 8951 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8952 if isinstance(this, exp.AggFunc): 8953 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8954 8955 if ignore_respect and ignore_respect is not this: 8956 ignore_respect.replace(ignore_respect.this) 8957 this = self.expression(ignore_respect.__class__(this=this)) 8958 8959 this = self._parse_respect_or_ignore_nulls(this) 8960 8961 # bigquery select from window x AS (partition by ...) 8962 if alias: 8963 over = None 8964 self._match(TokenType.ALIAS) 8965 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8966 return this 8967 else: 8968 over = self._prev.text.upper() 8969 8970 if comments and isinstance(func, exp.Expr): 8971 func.pop_comments() 8972 8973 if not self._match(TokenType.L_PAREN): 8974 return self.expression( 8975 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8976 ) 8977 8978 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8979 8980 first: bool | None = True if self._match(TokenType.FIRST) else None 8981 if self._match_text_seq("LAST"): 8982 first = False 8983 8984 partition, order = self._parse_partition_and_order() 8985 kind = ( 8986 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8987 ) and self._prev.text 8988 8989 if kind: 8990 self._match(TokenType.BETWEEN) 8991 start = self._parse_window_spec() 8992 8993 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8994 exclude = ( 8995 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8996 if self._match_text_seq("EXCLUDE") 8997 else None 8998 ) 8999 9000 spec = self.expression( 9001 exp.WindowSpec( 9002 kind=kind, 9003 start=start["value"], 9004 start_side=start["side"], 9005 end=end.get("value"), 9006 end_side=end.get("side"), 9007 exclude=exclude, 9008 ) 9009 ) 9010 else: 9011 spec = None 9012 9013 self._match_r_paren() 9014 9015 window = self.expression( 9016 exp.Window( 9017 this=this, 9018 partition_by=partition, 9019 order=order, 9020 spec=spec, 9021 alias=window_alias, 9022 over=over, 9023 first=first, 9024 ), 9025 comments=comments, 9026 ) 9027 9028 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 9029 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 9030 return self._parse_window(window, alias=alias) 9031 9032 return window 9033 9034 def _parse_partition_and_order( 9035 self, 9036 ) -> tuple[list[exp.Expr], exp.Expr | None]: 9037 return self._parse_partition_by(), self._parse_order() 9038 9039 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 9040 self._match(TokenType.BETWEEN) 9041 9042 return { 9043 "value": ( 9044 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 9045 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 9046 or self._parse_bitwise() 9047 ), 9048 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 9049 } 9050 9051 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 9052 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 9053 # so this section tries to parse the clause version and if it fails, it treats the token 9054 # as an identifier (alias) 9055 if self._can_parse_limit_or_offset(): 9056 return this 9057 9058 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 9059 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 9060 if self._can_parse_named_window(): 9061 return this 9062 9063 any_token = self._match(TokenType.ALIAS) 9064 comments = self._prev_comments 9065 9066 if explicit and not any_token: 9067 return this 9068 9069 if self._match(TokenType.L_PAREN): 9070 aliases = self.expression( 9071 exp.Aliases( 9072 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 9073 ), 9074 comments=comments, 9075 ) 9076 self._match_r_paren(aliases) 9077 return aliases 9078 9079 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 9080 self.STRING_ALIASES and self._parse_string_as_identifier() 9081 ) 9082 9083 if alias: 9084 comments.extend(alias.pop_comments()) 9085 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 9086 column = this.this 9087 9088 # Moves the comment next to the alias in `expr /* comment */ AS alias` 9089 if not this.comments and column and column.comments: 9090 this.comments = column.pop_comments() 9091 9092 return this 9093 9094 def _parse_id_var( 9095 self, 9096 any_token: bool = True, 9097 tokens: t.Collection[TokenType] | None = None, 9098 ) -> exp.Expr | None: 9099 expression = self._parse_identifier() 9100 if not expression and ( 9101 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 9102 ): 9103 quoted = self._prev.token_type == TokenType.STRING 9104 expression = self._identifier_expression(quoted=quoted) 9105 9106 return expression 9107 9108 def _parse_string(self) -> exp.Expr | None: 9109 if self._match_set(self.STRING_PARSERS): 9110 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 9111 return self._parse_placeholder() 9112 9113 def _parse_string_as_identifier(self) -> exp.Identifier | None: 9114 if not self._match(TokenType.STRING): 9115 return None 9116 output = exp.to_identifier(self._prev.text, quoted=True) 9117 output.update_positions(self._prev) 9118 return output 9119 9120 def _parse_number(self) -> exp.Expr | None: 9121 if self._match_set(self.NUMERIC_PARSERS): 9122 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 9123 return self._parse_placeholder() 9124 9125 def _parse_identifier(self) -> exp.Expr | None: 9126 if self._match(TokenType.IDENTIFIER): 9127 return self._identifier_expression(quoted=True) 9128 return self._parse_placeholder() 9129 9130 def _parse_var( 9131 self, 9132 any_token: bool = False, 9133 tokens: t.Collection[TokenType] | None = None, 9134 upper: bool = False, 9135 ) -> exp.Expr | None: 9136 if ( 9137 (any_token and self._advance_any()) 9138 or self._match(TokenType.VAR) 9139 or (self._match_set(tokens) if tokens else False) 9140 ): 9141 return self.expression( 9142 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 9143 ) 9144 return self._parse_placeholder() 9145 9146 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 9147 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 9148 self._advance() 9149 return self._prev 9150 return None 9151 9152 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 9153 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 9154 9155 def _parse_primary_or_var(self) -> exp.Expr | None: 9156 return self._parse_primary() or self._parse_var(any_token=True) 9157 9158 def _parse_null(self) -> exp.Expr | None: 9159 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 9160 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 9161 return self._parse_placeholder() 9162 9163 def _parse_boolean(self) -> exp.Expr | None: 9164 if self._match(TokenType.TRUE): 9165 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 9166 if self._match(TokenType.FALSE): 9167 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 9168 return self._parse_placeholder() 9169 9170 def _parse_star(self) -> exp.Expr | None: 9171 if self._match(TokenType.STAR): 9172 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 9173 return self._parse_placeholder() 9174 9175 def _parse_parameter(self) -> exp.Parameter: 9176 this = self._parse_identifier() or self._parse_primary_or_var() 9177 return self.expression(exp.Parameter(this=this)) 9178 9179 def _parse_placeholder(self) -> exp.Expr | None: 9180 if self._match_set(self.PLACEHOLDER_PARSERS): 9181 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 9182 if placeholder: 9183 return placeholder 9184 self._advance(-1) 9185 return None 9186 9187 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 9188 if not self._match_texts(keywords): 9189 return None 9190 if self._match(TokenType.L_PAREN, advance=False): 9191 return self._parse_wrapped_csv(self._parse_expression) 9192 9193 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 9194 return [expression] if expression else None 9195 9196 def _parse_csv( 9197 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 9198 ) -> list[T]: 9199 parse_result = parse_method() 9200 items = [parse_result] if parse_result is not None else [] 9201 9202 while self._match(sep): 9203 if isinstance(parse_result, exp.Expr): 9204 self._add_comments(parse_result) 9205 parse_result = parse_method() 9206 if parse_result is not None: 9207 items.append(parse_result) 9208 9209 return items 9210 9211 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 9212 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 9213 9214 def _parse_wrapped_csv( 9215 self, 9216 parse_method: t.Callable[[], T | None], 9217 sep: TokenType = TokenType.COMMA, 9218 optional: bool = False, 9219 ) -> list[T]: 9220 return self._parse_wrapped( 9221 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 9222 ) 9223 9224 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 9225 wrapped = self._match(TokenType.L_PAREN) 9226 if not wrapped and not optional: 9227 self.raise_error("Expecting (") 9228 parse_result = parse_method() 9229 if wrapped: 9230 self._match_r_paren() 9231 return parse_result 9232 9233 def _parse_expressions(self) -> list[exp.Expr]: 9234 return self._parse_csv(self._parse_expression) 9235 9236 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 9237 return ( 9238 self._parse_set_operations( 9239 self._parse_alias(self._parse_assignment(), explicit=True) 9240 if alias 9241 else self._parse_assignment() 9242 ) 9243 or self._parse_select() 9244 ) 9245 9246 def _parse_ddl_select(self) -> exp.Expr | None: 9247 return self._parse_query_modifiers( 9248 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 9249 ) 9250 9251 def _parse_transaction(self) -> exp.Transaction | exp.Command: 9252 this = None 9253 if self._match_texts(self.TRANSACTION_KIND): 9254 this = self._prev.text 9255 9256 self._match_texts(("TRANSACTION", "WORK")) 9257 9258 modes = [] 9259 while True: 9260 mode = [] 9261 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 9262 mode.append(self._prev.text) 9263 9264 if mode: 9265 modes.append(" ".join(mode)) 9266 if not self._match(TokenType.COMMA): 9267 break 9268 9269 return self.expression(exp.Transaction(this=this, modes=modes)) 9270 9271 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 9272 chain = None 9273 savepoint = None 9274 is_rollback = self._prev.token_type == TokenType.ROLLBACK 9275 9276 self._match_texts(("TRANSACTION", "WORK")) 9277 9278 if self._match_text_seq("TO"): 9279 self._match_text_seq("SAVEPOINT") 9280 savepoint = self._parse_id_var() 9281 9282 if self._match(TokenType.AND): 9283 chain = not self._match_text_seq("NO") 9284 self._match_text_seq("CHAIN") 9285 9286 if is_rollback: 9287 return self.expression(exp.Rollback(savepoint=savepoint)) 9288 9289 return self.expression(exp.Commit(chain=chain)) 9290 9291 def _parse_refresh(self) -> exp.Refresh | exp.Command: 9292 start = self._prev 9293 9294 if self._match_text_seq("EXTERNAL", "TABLE"): 9295 kind = "EXTERNAL TABLE" 9296 elif self._match(TokenType.TABLE): 9297 kind = "TABLE" 9298 elif self._match_text_seq("MATERIALIZED", "VIEW"): 9299 kind = "MATERIALIZED VIEW" 9300 else: 9301 kind = "" 9302 9303 this = self._parse_string() 9304 if not kind and not isinstance(this, exp.Literal): 9305 return self._parse_as_command(start) 9306 9307 return self.expression(exp.Refresh(this=this or self._parse_table(), kind=kind)) 9308 9309 def _parse_column_def_with_exists(self): 9310 start = self._index 9311 self._match(TokenType.COLUMN) 9312 9313 exists_column = self._parse_exists(not_=True) 9314 expression = self._parse_field_def() 9315 9316 if not isinstance(expression, exp.ColumnDef): 9317 self._retreat(start) 9318 return None 9319 9320 expression.set("exists", exists_column) 9321 9322 return expression 9323 9324 def _parse_add_column(self) -> exp.ColumnDef | None: 9325 if not self._prev.text.upper() == "ADD": 9326 return None 9327 9328 return self._parse_column_def_with_exists() 9329 9330 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9331 if not self._match(TokenType.DROP): 9332 return None 9333 9334 kind = None 9335 if not self.ALTER_DROP_REQUIRES_COLUMN: 9336 # A bare `DROP [IF EXISTS] c [CASCADE | RESTRICT]` drops a column 9337 index = self._index 9338 self._parse_exists() 9339 if ( 9340 self._curr.token_type not in (TokenType.COLUMN, TokenType.CONSTRAINT) 9341 and self._match_set(self.ID_VAR_TOKENS) 9342 and ( 9343 not self._curr 9344 or self._match(TokenType.COMMA, advance=False) 9345 or self._match_texts(("CASCADE", "RESTRICT"), advance=False) 9346 ) 9347 ): 9348 kind = "COLUMN" 9349 self._retreat(index) 9350 9351 return self._parse_drop(kind=kind) 9352 9353 def _parse_alter_drop_action(self) -> exp.Expr | None: 9354 drop = self._parse_drop_column() 9355 9356 if ( 9357 isinstance(drop, exp.Drop) 9358 and drop.kind == "COLUMN" 9359 and not self.dialect.ALTER_TABLE_DROP_REQUIRED_FOR_EACH_COLUMN 9360 and self._match(TokenType.COMMA) 9361 ): 9362 drop.set("tables", [*drop.args["tables"], *self._parse_csv(self._parse_column)]) 9363 9364 return drop 9365 9366 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9367 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9368 return self.expression( 9369 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9370 ) 9371 9372 def _parse_alter_table_add(self) -> list[exp.Expr]: 9373 def _parse_add_alteration() -> exp.Expr | None: 9374 self._match_text_seq("ADD") 9375 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) or self._match_texts( 9376 self.ADD_CONSTRAINT_KEYWORDS, advance=False 9377 ): 9378 return self.expression( 9379 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9380 ) 9381 9382 column_def = self._parse_add_column() 9383 if isinstance(column_def, exp.ColumnDef): 9384 return column_def 9385 9386 exists = self._parse_exists(not_=True) 9387 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9388 return self.expression( 9389 exp.AddPartition( 9390 exists=exists, 9391 this=self._parse_field(any_token=True), 9392 location=self._match_text_seq("LOCATION", advance=False) 9393 and self._parse_property(), 9394 ) 9395 ) 9396 9397 return None 9398 9399 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9400 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9401 or self._match_text_seq("COLUMNS") 9402 ): 9403 schema = self._parse_schema() 9404 9405 return ( 9406 ensure_list(schema) 9407 if schema 9408 else self._parse_csv(self._parse_column_def_with_exists) 9409 ) 9410 9411 return self._parse_csv(_parse_add_alteration) 9412 9413 def _parse_alter_table_alter(self) -> exp.Expr | None: 9414 if self._match_texts(self.ALTER_ALTER_PARSERS): 9415 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9416 9417 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9418 # keyword after ALTER we default to parsing this statement 9419 self._match(TokenType.COLUMN) 9420 exists = self._parse_exists() 9421 column = self._parse_field(any_token=True) 9422 9423 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9424 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9425 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9426 return self.expression( 9427 exp.AlterColumn( 9428 this=column, default=self._parse_disjunction(), exists=exists or None 9429 ) 9430 ) 9431 if self._match(TokenType.COMMENT): 9432 return self.expression( 9433 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9434 ) 9435 if self._match_text_seq("DROP", "NOT", "NULL"): 9436 return self.expression( 9437 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9438 ) 9439 if self._match_text_seq("SET", "NOT", "NULL"): 9440 return self.expression( 9441 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9442 ) 9443 9444 if self._match_text_seq("SET", "VISIBLE"): 9445 return self.expression( 9446 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9447 ) 9448 if self._match_text_seq("SET", "INVISIBLE"): 9449 return self.expression( 9450 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9451 ) 9452 9453 self._match_text_seq("SET", "DATA") 9454 dtype = ( 9455 self._parse_types() 9456 if self._match_text_seq("TYPE") or not self.ALTER_COLUMN_TYPE_REQUIRES_KEYWORD 9457 else None 9458 ) 9459 if not dtype: 9460 return None 9461 9462 return self.expression( 9463 exp.AlterColumn( 9464 this=column, 9465 dtype=dtype, 9466 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9467 using=self._match(TokenType.USING) and self._parse_disjunction(), 9468 exists=exists or None, 9469 ) 9470 ) 9471 9472 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9473 if self._match_texts(("ALL", "EVEN", "AUTO")): 9474 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9475 9476 self._match_text_seq("KEY", "DISTKEY") 9477 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9478 9479 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9480 if compound: 9481 self._match_text_seq("SORTKEY") 9482 9483 if self._match(TokenType.L_PAREN, advance=False): 9484 return self.expression( 9485 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9486 ) 9487 9488 self._match_texts(("AUTO", "NONE")) 9489 return self.expression( 9490 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9491 ) 9492 9493 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9494 index = self._index - 1 9495 9496 partition_exists = self._parse_exists() 9497 if self._match(TokenType.PARTITION, advance=False): 9498 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9499 9500 self._retreat(index) 9501 return self._parse_csv(self._parse_alter_drop_action) 9502 9503 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9504 if self._match(TokenType.COLUMN) or ( 9505 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9506 ): 9507 exists = self._parse_exists() 9508 old_column = self._parse_column() 9509 to = self._match_text_seq("TO") 9510 new_column = self._parse_column() 9511 9512 if old_column is None or not to or new_column is None: 9513 return None 9514 9515 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9516 9517 self._match_text_seq("TO") 9518 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9519 9520 def _parse_alter_table_set(self) -> exp.AlterSet: 9521 alter_set = self.expression(exp.AlterSet()) 9522 9523 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9524 "TABLE", "PROPERTIES" 9525 ): 9526 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9527 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9528 alter_set.set("expressions", [self._parse_assignment()]) 9529 elif self._match_texts(("LOGGED", "UNLOGGED")): 9530 alter_set.set("option", exp.var(self._prev.text.upper())) 9531 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9532 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9533 elif self._match_text_seq("LOCATION"): 9534 alter_set.set("location", self._parse_field()) 9535 elif self._match_text_seq("ACCESS", "METHOD"): 9536 alter_set.set("access_method", self._parse_field()) 9537 elif self._match_text_seq("TABLESPACE"): 9538 alter_set.set("tablespace", self._parse_field()) 9539 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9540 alter_set.set("file_format", [self._parse_field()]) 9541 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9542 alter_set.set("file_format", self._parse_wrapped_options()) 9543 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9544 alter_set.set("copy_options", self._parse_wrapped_options()) 9545 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9546 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9547 else: 9548 if self._match_text_seq("SERDE"): 9549 alter_set.set("serde", self._parse_field()) 9550 9551 properties = self._parse_wrapped(self._parse_properties, optional=True) 9552 alter_set.set("expressions", [properties]) 9553 9554 return alter_set 9555 9556 def _parse_alter_session(self) -> exp.AlterSession: 9557 """Parse ALTER SESSION SET/UNSET statements.""" 9558 if self._match(TokenType.SET): 9559 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9560 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9561 9562 self._match_text_seq("UNSET") 9563 expressions = self._parse_csv( 9564 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9565 ) 9566 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9567 9568 def _parse_alter(self) -> exp.Alter | exp.Command: 9569 start = self._prev 9570 9571 iceberg = self._match_text_seq("ICEBERG") 9572 9573 alter_token = self._match_set(self.ALTERABLES) and self._prev 9574 if not alter_token: 9575 return self._parse_as_command(start) 9576 if iceberg and alter_token.token_type != TokenType.TABLE: 9577 return self._parse_as_command(start) 9578 9579 exists = self._parse_exists() 9580 only = self._match_text_seq("ONLY") 9581 9582 if alter_token.token_type == TokenType.SESSION: 9583 this = None 9584 check = None 9585 cluster = None 9586 parser = self.ALTER_PARSERS.get(alter_token.text.upper()) 9587 else: 9588 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9589 check = self._match_text_seq("WITH", "CHECK") 9590 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9591 parser = ( 9592 self.ALTER_PARSERS[self._prev.text.upper()] 9593 if self._match_texts(self.ALTER_PARSERS) 9594 else None 9595 ) 9596 9597 options: list[exp.Expr] = [] 9598 if ( 9599 not parser 9600 and alter_token.token_type == TokenType.TABLE 9601 and not self.ALTER_TABLE_REQUIRES_ACTION 9602 ): 9603 # Table options may precede the actions, e.g. MySQL's ENGINE=InnoDB, ADD COLUMN a INT. 9604 # A clause the property parser rejects, like PARTITION BY RANGE ... MAXVALUE, stays a Command 9605 properties = self._try_parse(self._parse_properties) 9606 options = properties.expressions if properties else [] 9607 9608 actions = ensure_list(parser(self) if parser else None) 9609 if actions or options: 9610 # Actions of different kinds and table options may alternate, e.g. MySQL's 9611 # ADD c INT, AUTO_INCREMENT=5, ADD d INT. The comma before an item may have already 9612 # been consumed by the previous parser 9613 while self.ALTER_TABLE_MIXED_ACTIONS and ( 9614 self._match(TokenType.COMMA) or self._prev.token_type == TokenType.COMMA 9615 ): 9616 if not self._match_texts( 9617 self.PROPERTY_PARSERS, advance=False 9618 ) and self._match_texts(self.ALTER_PARSERS): 9619 parser = self.ALTER_PARSERS[self._prev.text.upper()] 9620 parsed = ensure_list(self._try_parse(lambda: parser(self))) 9621 if not parsed or any(isinstance(action, exp.Command) for action in parsed): 9622 return self._parse_as_command(start) 9623 9624 actions.extend(parsed) 9625 elif properties := self._try_parse(self._parse_properties): 9626 options.extend(properties.expressions) 9627 else: 9628 break 9629 9630 not_valid = self._match_text_seq("NOT", "VALID") 9631 if properties := self._try_parse(self._parse_properties): 9632 options.extend(properties.expressions) 9633 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9634 9635 if not self._curr: 9636 return self.expression( 9637 exp.Alter( 9638 this=this, 9639 kind=alter_token.text.upper(), 9640 exists=exists, 9641 actions=actions, 9642 only=only, 9643 options=options, 9644 cluster=cluster, 9645 not_valid=not_valid, 9646 check=check, 9647 cascade=cascade, 9648 iceberg=iceberg, 9649 ) 9650 ) 9651 9652 return self._parse_as_command(start) 9653 9654 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9655 start = self._prev 9656 # https://duckdb.org/docs/sql/statements/analyze 9657 if not self._curr: 9658 return self.expression(exp.Analyze()) 9659 9660 options = [] 9661 while self._match_texts(self.ANALYZE_STYLES): 9662 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9663 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9664 else: 9665 options.append(self._prev.text.upper()) 9666 9667 tables: exp.Expr | list[exp.Expr] | None = None 9668 inner_expression: exp.Expr | None = None 9669 9670 kind = self._curr.text.upper() if self._curr else None 9671 9672 if self._match(TokenType.TABLE): 9673 tables = self._parse_csv(self._parse_table_parts) 9674 elif self._match(TokenType.INDEX): 9675 tables = self._parse_table_parts() 9676 elif self._match_text_seq("TABLES"): 9677 if self._match_set((TokenType.FROM, TokenType.IN)): 9678 kind = f"{kind} {self._prev.text.upper()}" 9679 tables = self._parse_table(schema=True, is_db_reference=True) 9680 elif self._match_text_seq("DATABASE"): 9681 tables = self._parse_table(schema=True, is_db_reference=True) 9682 elif self._match_text_seq("CLUSTER"): 9683 tables = self._parse_table() 9684 # Try matching inner expr keywords before fallback to parse table. 9685 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9686 kind = None 9687 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9688 else: 9689 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9690 kind = None 9691 tables = self._parse_csv(self._parse_table_parts) 9692 9693 partition = self._try_parse(self._parse_partition) 9694 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9695 return self._parse_as_command(start) 9696 9697 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9698 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9699 "WITH", "ASYNC", "MODE" 9700 ): 9701 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9702 else: 9703 mode = None 9704 9705 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9706 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9707 9708 properties = self._parse_properties() 9709 return self.expression( 9710 exp.Analyze( 9711 kind=kind, 9712 tables=ensure_list(tables), 9713 mode=mode, 9714 partition=partition, 9715 properties=properties, 9716 expression=inner_expression, 9717 options=options, 9718 ) 9719 ) 9720 9721 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9722 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9723 this = None 9724 kind = self._prev.text.upper() 9725 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9726 expressions = [] 9727 9728 if not self._match_text_seq("STATISTICS"): 9729 self.raise_error("Expecting token STATISTICS") 9730 9731 if self._match_text_seq("NOSCAN"): 9732 this = "NOSCAN" 9733 elif self._match(TokenType.FOR): 9734 if self._match_text_seq("ALL", "COLUMNS"): 9735 this = "FOR ALL COLUMNS" 9736 if self._match_text_seq("COLUMNS"): 9737 this = "FOR COLUMNS" 9738 expressions = self._parse_csv(self._parse_column_reference) 9739 elif self._match_text_seq("SAMPLE"): 9740 sample = self._parse_number() 9741 expressions = [ 9742 self.expression( 9743 exp.AnalyzeSample( 9744 sample=sample, 9745 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9746 ) 9747 ) 9748 ] 9749 9750 return self.expression( 9751 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9752 ) 9753 9754 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9755 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9756 kind = None 9757 this = None 9758 expression: exp.Expr | None = None 9759 if self._match_text_seq("REF", "UPDATE"): 9760 kind = "REF" 9761 this = "UPDATE" 9762 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9763 this = "UPDATE SET DANGLING TO NULL" 9764 elif self._match_text_seq("STRUCTURE"): 9765 kind = "STRUCTURE" 9766 if self._match_text_seq("CASCADE", "FAST"): 9767 this = "CASCADE FAST" 9768 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9769 ("ONLINE", "OFFLINE") 9770 ): 9771 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9772 expression = self._parse_into() 9773 9774 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9775 9776 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9777 this = self._prev.text.upper() 9778 if self._match_text_seq("COLUMNS"): 9779 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9780 return None 9781 9782 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9783 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9784 if self._match_text_seq("STATISTICS"): 9785 return self.expression(exp.AnalyzeDelete(kind=kind)) 9786 return None 9787 9788 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9789 if self._match_text_seq("CHAINED", "ROWS"): 9790 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9791 return None 9792 9793 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9794 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9795 this = self._prev.text.upper() 9796 expression: exp.Expr | None = None 9797 expressions = [] 9798 update_options = None 9799 9800 if self._match_text_seq("HISTOGRAM", "ON"): 9801 expressions = self._parse_csv(self._parse_column_reference) 9802 with_expressions = [] 9803 while self._match(TokenType.WITH): 9804 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9805 if self._match_texts(("SYNC", "ASYNC")): 9806 if self._match_text_seq("MODE", advance=False): 9807 with_expressions.append(f"{self._prev.text.upper()} MODE") 9808 self._advance() 9809 else: 9810 buckets = self._parse_number() 9811 if self._match_text_seq("BUCKETS"): 9812 with_expressions.append(f"{buckets} BUCKETS") 9813 if with_expressions: 9814 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9815 9816 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9817 TokenType.UPDATE, advance=False 9818 ): 9819 update_options = self._prev.text.upper() 9820 self._advance() 9821 elif self._match_text_seq("USING", "DATA"): 9822 expression = self.expression(exp.UsingData(this=self._parse_string())) 9823 9824 return self.expression( 9825 exp.AnalyzeHistogram( 9826 this=this, 9827 expressions=expressions, 9828 expression=expression, 9829 update_options=update_options, 9830 ) 9831 ) 9832 9833 def _parse_merge(self) -> exp.Merge: 9834 self._match(TokenType.INTO) 9835 target = self._parse_table() 9836 9837 if target and self._match(TokenType.ALIAS, advance=False): 9838 target.set("alias", self._parse_table_alias()) 9839 9840 self._match(TokenType.USING) 9841 using = self._parse_table() 9842 9843 return self.expression( 9844 exp.Merge( 9845 this=target, 9846 using=using, 9847 on=self._match(TokenType.ON) and self._parse_disjunction(), 9848 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9849 whens=self._parse_when_matched(), 9850 returning=self._parse_returning(), 9851 ) 9852 ) 9853 9854 def _parse_when_matched(self) -> exp.Whens: 9855 whens = [] 9856 9857 while self._match(TokenType.WHEN): 9858 matched = not self._match(TokenType.NOT) 9859 self._match_text_seq("MATCHED") 9860 source = ( 9861 False 9862 if self._match_text_seq("BY", "TARGET") 9863 else self._match_text_seq("BY", "SOURCE") 9864 ) 9865 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9866 9867 self._match(TokenType.THEN) 9868 9869 if self._match(TokenType.INSERT): 9870 this = self._parse_star() 9871 if this: 9872 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9873 else: 9874 then = self.expression( 9875 exp.Insert( 9876 this=exp.var("ROW") 9877 if self._match_text_seq("ROW") 9878 else self._parse_value(values=False), 9879 expression=self._match_text_seq("VALUES") and self._parse_value(), 9880 where=self._parse_where(), 9881 ) 9882 ) 9883 elif self._match(TokenType.UPDATE): 9884 expressions = self._parse_star() 9885 if expressions: 9886 then = self.expression(exp.Update(expressions=expressions)) 9887 else: 9888 then = self.expression( 9889 exp.Update( 9890 expressions=self._match(TokenType.SET) 9891 and self._parse_csv(self._parse_update_assignment), 9892 where=self._parse_where(), 9893 ) 9894 ) 9895 elif self._match(TokenType.DELETE): 9896 then = self.expression(exp.Var(this=self._prev.text)) 9897 else: 9898 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9899 9900 whens.append( 9901 self.expression( 9902 exp.When(matched=matched, source=source, condition=condition, then=then) 9903 ) 9904 ) 9905 return self.expression(exp.Whens(expressions=whens)) 9906 9907 def _parse_show(self) -> exp.Expr | None: 9908 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9909 if parser: 9910 return parser(self) 9911 return self._parse_as_command(self._prev) 9912 9913 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9914 index = self._index 9915 9916 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9917 return self._parse_set_transaction(global_=kind == "GLOBAL") 9918 9919 left = self._parse_primary() or self._parse_column() 9920 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 9921 9922 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 9923 self._retreat(index) 9924 return None 9925 9926 right = self._parse_statement() or self._parse_id_var() 9927 if isinstance(right, (exp.Column, exp.Identifier)): 9928 right = exp.var(right.name) 9929 9930 this = self.expression(exp.EQ(this=left, expression=right)) 9931 return self.expression(exp.SetItem(this=this, kind=kind)) 9932 9933 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 9934 self._match_text_seq("TRANSACTION") 9935 characteristics = self._parse_csv( 9936 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 9937 ) 9938 return self.expression( 9939 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 9940 ) 9941 9942 def _parse_set_item(self) -> exp.Expr | None: 9943 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 9944 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 9945 9946 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 9947 index = self._index 9948 set_ = self.expression( 9949 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 9950 ) 9951 9952 if self._curr: 9953 self._retreat(index) 9954 return self._parse_as_command(self._prev) 9955 9956 return set_ 9957 9958 def _parse_var_from_options( 9959 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 9960 ) -> exp.Var | None: 9961 start = self._curr 9962 if not start: 9963 return None 9964 9965 option = start.text.upper() 9966 continuations = ( 9967 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 9968 ) 9969 9970 index = self._index 9971 self._advance() 9972 for keywords in continuations or []: 9973 if isinstance(keywords, str): 9974 keywords = (keywords,) 9975 9976 if self._match_text_seq(*keywords): 9977 option = f"{option} {' '.join(keywords)}" 9978 break 9979 else: 9980 if continuations or continuations is None: 9981 if raise_unmatched: 9982 self.raise_error(f"Unknown option {option}") 9983 9984 self._retreat(index) 9985 return None 9986 9987 return exp.var(option) 9988 9989 def _parse_as_command(self, start: Token) -> exp.Command: 9990 while self._curr: 9991 self._advance() 9992 text = self._find_sql(start, self._prev) 9993 size = len(start.text) 9994 self._warn_unsupported() 9995 return exp.Command(this=text[:size], expression=text[size:]) 9996 9997 def _parse_dict_property(self, this: str) -> exp.DictProperty: 9998 settings = [] 9999 10000 self._match_l_paren() 10001 kind = self._parse_id_var() 10002 10003 if self._match(TokenType.L_PAREN): 10004 while True: 10005 key = self._parse_id_var() 10006 value = self._parse_function() or self._parse_primary_or_var() 10007 if not key and value is None: 10008 break 10009 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 10010 self._match(TokenType.R_PAREN) 10011 10012 self._match_r_paren() 10013 10014 return self.expression( 10015 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 10016 ) 10017 10018 def _parse_dict_range(self, this: str) -> exp.DictRange: 10019 self._match_l_paren() 10020 has_min = self._match_text_seq("MIN") 10021 if has_min: 10022 min = self._parse_var() or self._parse_primary() 10023 self._match_text_seq("MAX") 10024 max = self._parse_var() or self._parse_primary() 10025 else: 10026 max = self._parse_var() or self._parse_primary() 10027 min = exp.Literal.number(0) 10028 self._match_r_paren() 10029 return self.expression(exp.DictRange(this=this, min=min, max=max)) 10030 10031 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 10032 index = self._index 10033 expression = self._parse_id_var() 10034 position = self._parse_id_var() if self._match(TokenType.COMMA) else None 10035 10036 if not self._match(TokenType.IN): 10037 self._retreat(index - 1) 10038 return None 10039 iterator = self._parse_column() 10040 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 10041 return self.expression( 10042 exp.Comprehension( 10043 this=this, 10044 expression=expression, 10045 position=position, 10046 iterator=iterator, 10047 condition=condition, 10048 ) 10049 ) 10050 10051 def _parse_heredoc(self) -> exp.Heredoc | None: 10052 if self._match(TokenType.HEREDOC_STRING): 10053 return self.expression(exp.Heredoc(this=self._prev.text)) 10054 10055 if not self._match_text_seq("$"): 10056 return None 10057 10058 tags = ["$"] 10059 tag_text = None 10060 10061 if self._is_connected(): 10062 self._advance() 10063 tags.append(self._prev.text.upper()) 10064 else: 10065 self.raise_error("No closing $ found") 10066 10067 if tags[-1] != "$": 10068 if self._is_connected() and self._match_text_seq("$"): 10069 tag_text = tags[-1] 10070 tags.append("$") 10071 else: 10072 self.raise_error("No closing $ found") 10073 10074 heredoc_start = self._curr 10075 10076 while self._curr: 10077 if self._match_text_seq(*tags, advance=False): 10078 this = self._find_sql(heredoc_start, self._prev) 10079 self._advance(len(tags)) 10080 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 10081 10082 self._advance() 10083 10084 self.raise_error(f"No closing {''.join(tags)} found") 10085 return None 10086 10087 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 10088 if not self._curr: 10089 return None 10090 10091 index = self._index 10092 this = [] 10093 while True: 10094 # The current token might be multiple words 10095 curr = self._curr.text.upper() 10096 key = curr.split(" ") 10097 this.append(curr) 10098 10099 self._advance() 10100 result, trie = in_trie(trie, key) 10101 if result == TrieResult.FAILED: 10102 break 10103 10104 if result == TrieResult.EXISTS: 10105 subparser = parsers[" ".join(this)] 10106 return subparser 10107 10108 self._retreat(index) 10109 return None 10110 10111 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 10112 if not self._match(TokenType.L_PAREN, expression=expression): 10113 self.raise_error("Expecting (") 10114 10115 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 10116 if not self._match(TokenType.R_PAREN, expression=expression): 10117 self.raise_error("Expecting )") 10118 10119 def _replace_lambda( 10120 self, node: exp.Expr | None, expressions: list[exp.Expr] 10121 ) -> exp.Expr | None: 10122 if not node: 10123 return node 10124 10125 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 10126 10127 for column in node.find_all(exp.Column): 10128 typ = lambda_types.get(column.parts[0].name) 10129 if typ is not None: 10130 dot_or_id = column.to_dot() if column.table else column.this 10131 10132 if typ: 10133 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ)) 10134 10135 parent = column.parent 10136 10137 while isinstance(parent, exp.Dot): 10138 if not isinstance(parent.parent, exp.Dot): 10139 parent.replace(dot_or_id) 10140 break 10141 parent = parent.parent 10142 else: 10143 if column is node: 10144 node = dot_or_id 10145 else: 10146 column.replace(dot_or_id) 10147 return node 10148 10149 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 10150 start = self._prev 10151 10152 # Not to be confused with TRUNCATE(number, decimals) function call 10153 if self._match(TokenType.L_PAREN): 10154 self._retreat(self._index - 2) 10155 return self._parse_function() 10156 10157 # Clickhouse supports TRUNCATE DATABASE as well 10158 is_database = self._match(TokenType.DATABASE) 10159 10160 self._match(TokenType.TABLE) 10161 10162 exists = self._parse_exists(not_=False) 10163 10164 expressions = self._parse_csv( 10165 lambda: self._parse_table(schema=True, is_db_reference=is_database) 10166 ) 10167 10168 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 10169 10170 if self._match_text_seq("RESTART", "IDENTITY"): 10171 identity = "RESTART" 10172 elif self._match_text_seq("CONTINUE", "IDENTITY"): 10173 identity = "CONTINUE" 10174 else: 10175 identity = None 10176 10177 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 10178 option = self._prev.text 10179 else: 10180 option = None 10181 10182 partition = self._parse_partition() 10183 10184 # Fallback case 10185 if self._curr: 10186 return self._parse_as_command(start) 10187 10188 return self.expression( 10189 exp.TruncateTable( 10190 expressions=expressions, 10191 is_database=is_database, 10192 exists=exists, 10193 cluster=cluster, 10194 identity=identity, 10195 option=option, 10196 partition=partition, 10197 ) 10198 ) 10199 10200 def _parse_indexed_column(self) -> exp.Expr | None: 10201 return self._parse_ordered(self._parse_opclass) 10202 10203 def _parse_with_operator(self) -> exp.Expr | None: 10204 this = self._parse_indexed_column() 10205 10206 if not self._match(TokenType.WITH): 10207 return this 10208 10209 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 10210 10211 return self.expression(exp.WithOperator(this=this, op=op)) 10212 10213 def _parse_wrapped_options(self) -> list[exp.Expr]: 10214 self._match(TokenType.EQ) 10215 self._match(TokenType.L_PAREN) 10216 10217 opts: list[exp.Expr] = [] 10218 option: exp.Expr | list[exp.Expr] | None 10219 while self._curr and not self._match(TokenType.R_PAREN): 10220 if self._match_text_seq("FORMAT_NAME", "="): 10221 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 10222 option = self._parse_format_name() 10223 else: 10224 option = self._parse_property() 10225 10226 if option is None: 10227 self.raise_error("Unable to parse option") 10228 break 10229 10230 opts.extend(ensure_list(option)) 10231 10232 return opts 10233 10234 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 10235 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 10236 10237 options = [] 10238 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 10239 option = self._parse_var(any_token=True) 10240 prev = self._prev.text.upper() 10241 10242 # Different dialects might separate options and values by white space, "=" and "AS" 10243 self._match(TokenType.EQ) 10244 self._match(TokenType.ALIAS) 10245 10246 param = self.expression(exp.CopyParameter(this=option)) 10247 10248 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 10249 TokenType.L_PAREN, advance=False 10250 ): 10251 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 10252 param.set("expressions", self._parse_wrapped_options()) 10253 elif prev == "FILE_FORMAT": 10254 # T-SQL's external file format case 10255 param.set("expression", self._parse_field()) 10256 elif ( 10257 prev == "FORMAT" 10258 and self._prev.token_type == TokenType.ALIAS 10259 and self._match_texts(("AVRO", "JSON")) 10260 ): 10261 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 10262 param.set("expression", self._parse_field()) 10263 else: 10264 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 10265 10266 options.append(param) 10267 10268 if sep: 10269 self._match(sep) 10270 10271 return options 10272 10273 def _parse_credentials(self) -> exp.Credentials | None: 10274 expr = self.expression(exp.Credentials()) 10275 10276 if self._match_text_seq("STORAGE_INTEGRATION", "="): 10277 expr.set("storage", self._parse_field()) 10278 if self._match_text_seq("CREDENTIALS"): 10279 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 10280 creds = ( 10281 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 10282 ) 10283 expr.set("credentials", creds) 10284 if self._match_text_seq("ENCRYPTION"): 10285 expr.set("encryption", self._parse_wrapped_options()) 10286 if self._match_text_seq("IAM_ROLE"): 10287 expr.set( 10288 "iam_role", 10289 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 10290 ) 10291 if self._match_text_seq("REGION"): 10292 expr.set("region", self._parse_field()) 10293 10294 return expr 10295 10296 def _parse_file_location(self) -> exp.Expr | None: 10297 return self._parse_field() 10298 10299 def _parse_copy(self) -> exp.Copy | exp.Command: 10300 start = self._prev 10301 10302 self._match(TokenType.INTO) 10303 10304 this = ( 10305 self._parse_select(nested=True, parse_subquery_alias=False) 10306 if self._match(TokenType.L_PAREN, advance=False) 10307 else self._parse_table(schema=True) 10308 ) 10309 10310 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 10311 10312 files = self._parse_csv(self._parse_file_location) 10313 if self._match(TokenType.EQ, advance=False): 10314 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 10315 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 10316 # list via `_parse_wrapped(..)` below. 10317 self._advance(-1) 10318 files = [] 10319 10320 credentials = self._parse_credentials() 10321 10322 self._match_text_seq("WITH") 10323 10324 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 10325 10326 # Fallback case 10327 if self._curr: 10328 return self._parse_as_command(start) 10329 10330 return self.expression( 10331 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 10332 ) 10333 10334 def _parse_normalize(self) -> exp.Normalize: 10335 return self.expression( 10336 exp.Normalize( 10337 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 10338 ) 10339 ) 10340 10341 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 10342 args = self._parse_csv(lambda: self._parse_lambda()) 10343 10344 this = seq_get(args, 0) 10345 decimals = seq_get(args, 1) 10346 10347 return expr_type( 10348 this=this, 10349 decimals=decimals, 10350 to=self._parse_var() if self._match_text_seq("TO") else None, 10351 ) 10352 10353 def _parse_star_ops(self) -> exp.Expr | None: 10354 star_token = self._prev 10355 10356 if self._match_text_seq("COLUMNS", "(", advance=False): 10357 this = self._parse_function() 10358 if isinstance(this, exp.Columns): 10359 this.set("unpack", True) 10360 return this 10361 10362 index = self._index 10363 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 10364 if not ilike: 10365 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 10366 self._retreat(index) 10367 10368 return self.expression( 10369 exp.Star( 10370 ilike=ilike, 10371 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 10372 replace=self._parse_star_op("REPLACE"), 10373 rename=self._parse_star_op("RENAME"), 10374 ) 10375 ).update_positions(star_token) 10376 10377 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 10378 privilege_parts = [] 10379 10380 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 10381 # (end of privilege list) or L_PAREN (start of column list) are met 10382 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 10383 privilege_parts.append(self._curr.text.upper()) 10384 self._advance() 10385 10386 if not privilege_parts: 10387 self.raise_error("Expected privilege") 10388 return None 10389 10390 this = exp.var(" ".join(privilege_parts)) 10391 expressions = ( 10392 self._parse_wrapped_csv(self._parse_column) 10393 if self._match(TokenType.L_PAREN, advance=False) 10394 else None 10395 ) 10396 10397 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10398 10399 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10400 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10401 principal = self._parse_id_var() 10402 10403 if not principal: 10404 return None 10405 10406 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10407 10408 def _parse_grant_revoke_common( 10409 self, 10410 ) -> tuple[list | None, str | None, exp.Expr | None]: 10411 privileges = self._parse_csv(self._parse_grant_privilege) 10412 10413 self._match(TokenType.ON) 10414 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10415 10416 # Attempt to parse the securable e.g. MySQL allows names 10417 # such as "foo.*", "*.*" which are not easily parseable yet 10418 securable = self._try_parse(self._parse_table_parts) 10419 10420 return privileges, kind, securable 10421 10422 def _parse_grant(self) -> exp.Grant | exp.Command: 10423 start = self._prev 10424 10425 privileges, kind, securable = self._parse_grant_revoke_common() 10426 10427 if not securable or not self._match_text_seq("TO"): 10428 return self._parse_as_command(start) 10429 10430 principals = self._parse_csv(self._parse_grant_principal) 10431 10432 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10433 10434 if self._curr: 10435 return self._parse_as_command(start) 10436 10437 return self.expression( 10438 exp.Grant( 10439 privileges=privileges, 10440 kind=kind, 10441 securable=securable, 10442 principals=principals, 10443 grant_option=grant_option, 10444 ) 10445 ) 10446 10447 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10448 start = self._prev 10449 10450 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10451 10452 privileges, kind, securable = self._parse_grant_revoke_common() 10453 10454 if not securable or not self._match_text_seq("FROM"): 10455 return self._parse_as_command(start) 10456 10457 principals = self._parse_csv(self._parse_grant_principal) 10458 10459 cascade = None 10460 if self._match_texts(("CASCADE", "RESTRICT")): 10461 cascade = self._prev.text.upper() 10462 10463 if self._curr: 10464 return self._parse_as_command(start) 10465 10466 return self.expression( 10467 exp.Revoke( 10468 privileges=privileges, 10469 kind=kind, 10470 securable=securable, 10471 principals=principals, 10472 grant_option=grant_option, 10473 cascade=cascade, 10474 ) 10475 ) 10476 10477 def _parse_overlay(self) -> exp.Overlay: 10478 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10479 return ( 10480 self._parse_bitwise() 10481 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10482 else None 10483 ) 10484 10485 return self.expression( 10486 exp.Overlay( 10487 this=self._parse_bitwise(), 10488 expression=_parse_overlay_arg("PLACING"), 10489 from_=_parse_overlay_arg("FROM"), 10490 for_=_parse_overlay_arg("FOR"), 10491 ) 10492 ) 10493 10494 def _parse_format_name(self) -> exp.Property: 10495 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10496 # for FILE_FORMAT = <format_name> 10497 return self.expression( 10498 exp.Property( 10499 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10500 ) 10501 ) 10502 10503 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10504 is_distinct = self._match(TokenType.DISTINCT) 10505 if not is_distinct: 10506 self._match(TokenType.ALL) 10507 10508 args = [self._parse_lambda()] 10509 if self._match(TokenType.COMMA): 10510 args.extend(self._parse_function_args()) 10511 10512 target = seq_get(args, distinct_index) 10513 if is_distinct and target: 10514 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10515 10516 return func.from_arg_list(args) 10517 10518 def _identifier_expression( 10519 self, token: Token | None = None, quoted: bool | None = None 10520 ) -> exp.Identifier: 10521 token = token or self._prev 10522 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10523 10524 def _build_pipe_cte( 10525 self, 10526 query: exp.Query, 10527 expressions: list[exp.Expr], 10528 alias_cte: exp.TableAlias | None = None, 10529 ) -> exp.Select: 10530 new_cte: str | exp.TableAlias | None 10531 if alias_cte: 10532 new_cte = alias_cte 10533 else: 10534 self._pipe_cte_counter += 1 10535 new_cte = f"__tmp{self._pipe_cte_counter}" 10536 10537 with_ = query.args.get("with_") 10538 ctes = with_.pop() if with_ else None 10539 10540 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10541 if ctes: 10542 new_select.set("with_", ctes) 10543 10544 return new_select.with_(new_cte, as_=query, copy=False) 10545 10546 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10547 select = self._parse_select(consume_pipe=False) 10548 if not select: 10549 return query 10550 10551 return self._build_pipe_cte( 10552 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10553 ) 10554 10555 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10556 limit = self._parse_limit() 10557 offset = self._parse_offset() 10558 if isinstance(limit, exp.Limit): 10559 curr_limit = query.args.get("limit", limit) 10560 curr_is_limit_all = isinstance(curr_limit, exp.Limit) and curr_limit.is_limit_all 10561 10562 # LIMIT ALL means "no cap", so it's never tighter than a numeric limit. 10563 if limit.is_limit_all: 10564 should_replace = curr_is_limit_all 10565 else: 10566 should_replace = ( 10567 curr_is_limit_all or curr_limit.expression.to_py() >= limit.expression.to_py() 10568 ) 10569 10570 if should_replace: 10571 query.limit(limit, copy=False) 10572 if offset: 10573 curr_offset = query.args.get("offset") 10574 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10575 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10576 10577 return query 10578 10579 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10580 this = self._parse_disjunction() 10581 if self._match_text_seq("GROUP", "AND", advance=False): 10582 return this 10583 10584 this = self._parse_alias(this) 10585 10586 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10587 return self._parse_ordered(lambda: this) 10588 10589 return this 10590 10591 def _parse_pipe_syntax_aggregate_group_order_by( 10592 self, query: exp.Select, group_by_exists: bool = True 10593 ) -> exp.Select: 10594 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10595 aggregates_or_groups, orders = [], [] 10596 for element in expr: 10597 if isinstance(element, exp.Ordered): 10598 this = element.this 10599 if isinstance(this, exp.Alias): 10600 element.set("this", this.args["alias"]) 10601 orders.append(element) 10602 else: 10603 this = element 10604 aggregates_or_groups.append(this) 10605 10606 if group_by_exists: 10607 query.select( 10608 *aggregates_or_groups, *query.expressions, append=False, copy=False 10609 ).group_by( 10610 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10611 copy=False, 10612 ) 10613 else: 10614 query.select(*aggregates_or_groups, append=False, copy=False) 10615 10616 if orders: 10617 return query.order_by(*orders, append=False, copy=False) 10618 10619 return query 10620 10621 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10622 self._match_text_seq("AGGREGATE") 10623 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10624 10625 if self._match(TokenType.GROUP_BY) or ( 10626 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10627 ): 10628 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10629 10630 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10631 10632 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10633 first_setop = self.parse_set_operation(this=query) 10634 if not first_setop: 10635 return None 10636 10637 def _parse_and_unwrap_query() -> exp.Expr | None: 10638 expr = self._parse_paren() 10639 return expr.assert_is(exp.Subquery).unnest() if expr else None 10640 10641 first_setop.this.pop() 10642 10643 setops = [ 10644 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10645 *self._parse_csv(_parse_and_unwrap_query), 10646 ] 10647 10648 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10649 with_ = query.args.get("with_") 10650 ctes = with_.pop() if with_ else None 10651 10652 if isinstance(first_setop, exp.Union): 10653 query = query.union(*setops, copy=False, **first_setop.args) 10654 elif isinstance(first_setop, exp.Except): 10655 query = query.except_(*setops, copy=False, **first_setop.args) 10656 else: 10657 query = query.intersect(*setops, copy=False, **first_setop.args) 10658 10659 query.set("with_", ctes) 10660 10661 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10662 10663 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10664 join = self._parse_join() 10665 if not join: 10666 return None 10667 10668 if isinstance(query, exp.Select): 10669 return query.join(join, copy=False) 10670 10671 return query 10672 10673 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10674 pivots = self._parse_pivots() 10675 if not pivots: 10676 return query 10677 10678 from_ = query.args.get("from_") 10679 if from_: 10680 from_.this.set("pivots", pivots) 10681 10682 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10683 10684 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10685 self._match_text_seq("EXTEND") 10686 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10687 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10688 10689 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10690 sample = self._parse_table_sample() 10691 10692 with_ = query.args.get("with_") 10693 if with_: 10694 with_.expressions[-1].this.set("sample", sample) 10695 else: 10696 query.set("sample", sample) 10697 10698 return query 10699 10700 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10701 if isinstance(query, exp.Subquery): 10702 query = exp.select("*").from_(query, copy=False) 10703 10704 if not query.args.get("from_"): 10705 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10706 10707 while self._match(TokenType.PIPE_GT): 10708 start_index = self._index 10709 start_text = self._curr.text.upper() 10710 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10711 if not parser: 10712 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10713 # keywords, making it tricky to disambiguate them without lookahead. The approach 10714 # here is to try and parse a set operation and if that fails, then try to parse a 10715 # join operator. If that fails as well, then the operator is not supported. 10716 parsed_query = self._parse_pipe_syntax_set_operator(query) 10717 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10718 if not parsed_query: 10719 self._retreat(start_index) 10720 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10721 break 10722 query = parsed_query 10723 else: 10724 query = parser(self, query) 10725 10726 return query 10727 10728 def _parse_declareitem(self) -> exp.DeclareItem | None: 10729 self._match_texts(("VAR", "VARIABLE")) 10730 10731 vars = self._parse_csv(self._parse_id_var) 10732 if not vars: 10733 return None 10734 10735 self._match(TokenType.ALIAS) 10736 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10737 default = ( 10738 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10739 ) and self._parse_bitwise() 10740 10741 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10742 10743 def _parse_declare(self) -> exp.Declare | exp.Command: 10744 start = self._prev 10745 replace = self._match_text_seq("OR", "REPLACE") 10746 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10747 10748 if not expressions or self._curr: 10749 return self._parse_as_command(start) 10750 10751 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10752 10753 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10754 exp_class = exp.Cast if strict else exp.TryCast 10755 10756 if exp_class == exp.TryCast: 10757 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10758 10759 return self.expression(exp_class(**kwargs)) 10760 10761 def _parse_json_value(self, json_path: bool = True) -> exp.JSONValue: 10762 this = self._parse_bitwise() 10763 self._match(TokenType.COMMA) 10764 path = self._parse_bitwise() 10765 10766 returning = self._match(TokenType.RETURNING) and self._parse_types() 10767 10768 return self.expression( 10769 exp.JSONValue( 10770 this=this, 10771 path=self.dialect.to_json_path(path) if json_path else path, 10772 returning=returning, 10773 on_condition=self._parse_on_condition(), 10774 ) 10775 ) 10776 10777 def _parse_group_concat(self) -> exp.Expr | None: 10778 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10779 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10780 concat_exprs = [ 10781 self.expression( 10782 exp.Concat( 10783 expressions=node.expressions, 10784 safe=True, 10785 coalesce=self.dialect.CONCAT_COALESCE, 10786 ) 10787 ) 10788 ] 10789 node.set("expressions", concat_exprs) 10790 return node 10791 if len(exprs) == 1: 10792 return exprs[0] 10793 return self.expression( 10794 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10795 ) 10796 10797 args = self._parse_csv(self._parse_lambda) 10798 10799 if args: 10800 order = args[-1] if isinstance(args[-1], exp.Order) else None 10801 10802 if order: 10803 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10804 # remove 'expr' from exp.Order and add it back to args 10805 args[-1] = order.this 10806 order.set("this", concat_exprs(order.this, args)) 10807 10808 this = order or concat_exprs(args[0], args) 10809 else: 10810 this = None 10811 10812 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10813 10814 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10815 10816 def _parse_initcap(self) -> exp.Initcap: 10817 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10818 10819 # attach dialect's default delimiters 10820 if expr.args.get("expression") is None: 10821 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10822 10823 return expr 10824 10825 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10826 if not self._match(TokenType.L_PAREN): 10827 self._retreat(self._index - 1) 10828 return None 10829 10830 op = "" 10831 while self._curr and not self._match(TokenType.R_PAREN): 10832 op += self._curr.text 10833 self._advance() 10834 10835 comments = self._prev_comments 10836 return self.expression( 10837 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10838 comments=comments, 10839 )
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.
2018 def __init__( 2019 self, 2020 error_level: ErrorLevel | None = None, 2021 error_message_context: int = 100, 2022 max_errors: int = 3, 2023 max_nodes: int = -1, 2024 dialect: DialectType = None, 2025 ): 2026 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 2027 self.error_message_context: int = error_message_context 2028 self.max_errors: int = max_errors 2029 self.max_nodes: int = max_nodes 2030 self.dialect: t.Any = _resolve_dialect(dialect) 2031 self.sql: str = "" 2032 self.errors: list[ParseError] = [] 2033 self._tokens: list[Token] = [] 2034 self._tokens_size: i64 = 0 2035 self._index: i64 = 0 2036 self._curr: Token = SENTINEL_NONE 2037 self._next: Token = SENTINEL_NONE 2038 self._prev: Token = SENTINEL_NONE 2039 self._prev_comments: list[str] = [] 2040 self._pipe_cte_counter: int = 0 2041 self._chunks: list[list[Token]] = [] 2042 self._chunk_index: i64 = 0 2043 self._node_count: int = 0
2045 def reset(self) -> None: 2046 self.sql = "" 2047 self.errors = [] 2048 self._tokens = [] 2049 self._tokens_size = 0 2050 self._index = 0 2051 self._curr = SENTINEL_NONE 2052 self._next = SENTINEL_NONE 2053 self._prev = SENTINEL_NONE 2054 self._prev_comments = [] 2055 self._pipe_cte_counter = 0 2056 self._chunks = [] 2057 self._chunk_index = 0 2058 self._node_count = 0
2151 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2152 token = token or self._curr or self._prev or Token.string("") 2153 formatted_sql, start_context, highlight, end_context = highlight_sql( 2154 sql=self.sql, 2155 positions=[(token.start, token.end)], 2156 context_length=self.error_message_context, 2157 ) 2158 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2159 2160 error = ParseError.new( 2161 formatted_message, 2162 description=message, 2163 line=token.line, 2164 col=token.col, 2165 start_context=start_context, 2166 highlight=highlight, 2167 end_context=end_context, 2168 ) 2169 2170 if self.error_level == ErrorLevel.IMMEDIATE: 2171 raise error 2172 2173 self.errors.append(error)
2175 def validate_expression(self, expression: E, args: list | None = None) -> E: 2176 if self.max_nodes > -1: 2177 self._node_count += 1 2178 if self._node_count > self.max_nodes: 2179 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2180 if self.error_level != ErrorLevel.IGNORE: 2181 for error_message in expression.error_messages(args): 2182 self.raise_error(error_message) 2183 return expression
2202 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2203 """ 2204 Parses a list of tokens and returns a list of syntax trees, one tree 2205 per parsed SQL statement. 2206 2207 Args: 2208 raw_tokens: The list of tokens. 2209 sql: The original SQL string. 2210 2211 Returns: 2212 The list of the produced syntax trees. 2213 """ 2214 return self._parse( 2215 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2216 )
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.
2218 def parse_into( 2219 self, 2220 expression_types: exp.IntoType, 2221 raw_tokens: list[Token], 2222 sql: str | None = None, 2223 ) -> list[exp.Expr | None]: 2224 """ 2225 Parses a list of tokens into a given Expr type. If a collection of Expr 2226 types is given instead, this method will try to parse the token list into each one 2227 of them, stopping at the first for which the parsing succeeds. 2228 2229 Args: 2230 expression_types: The expression type(s) to try and parse the token list into. 2231 raw_tokens: The list of tokens. 2232 sql: The original SQL string, used to produce helpful debug messages. 2233 2234 Returns: 2235 The target Expr. 2236 """ 2237 errors = [] 2238 for expression_type in ensure_list(expression_types): 2239 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2240 if not parser: 2241 raise TypeError(f"No parser registered for {expression_type}") 2242 2243 try: 2244 return self._parse(parser, raw_tokens, sql) 2245 except ParseError as e: 2246 e.errors[0]["into_expression"] = expression_type 2247 errors.append(e) 2248 2249 raise ParseError( 2250 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2251 errors=merge_errors(errors), 2252 ) 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.
2254 def check_errors(self) -> None: 2255 """Logs or raises any found errors, depending on the chosen error level setting.""" 2256 if self.error_level == ErrorLevel.WARN: 2257 for error in self.errors: 2258 logger.error(str(error)) 2259 elif self.error_level == ErrorLevel.RAISE and self.errors: 2260 raise ParseError( 2261 concat_messages(self.errors, self.max_errors), 2262 errors=merge_errors(self.errors), 2263 )
Logs or raises any found errors, depending on the chosen error level setting.
2265 def expression( 2266 self, 2267 instance: E, 2268 token: Token | None = None, 2269 comments: list[str] | None = None, 2270 ) -> E: 2271 if token: 2272 instance.update_positions(token) 2273 instance.add_comments(comments) if comments else self._add_comments(instance) 2274 if not instance.is_primitive: 2275 instance = self.validate_expression(instance) 2276 return instance
6071 def parse_set_operation( 6072 self, this: exp.Expr | None, consume_pipe: bool = False 6073 ) -> exp.Expr | None: 6074 start = self._index 6075 _, side_token, kind_token = self._parse_join_parts() 6076 6077 side = side_token.text if side_token else None 6078 kind = kind_token.text if kind_token else None 6079 6080 if not self._match_set(self.SET_OPERATIONS): 6081 self._retreat(start) 6082 return None 6083 6084 token_type = self._prev.token_type 6085 6086 if token_type == TokenType.UNION: 6087 operation: type[exp.SetOperation] = exp.Union 6088 elif token_type == TokenType.EXCEPT: 6089 operation = exp.Except 6090 else: 6091 operation = exp.Intersect 6092 6093 comments = self._prev.comments 6094 6095 if self._match(TokenType.DISTINCT): 6096 distinct: bool | None = True 6097 elif self._match(TokenType.ALL): 6098 distinct = False 6099 else: 6100 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 6101 if distinct is None: 6102 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 6103 6104 by_name = ( 6105 self._match_text_seq("BY", "NAME") 6106 or self._match_text_seq("STRICT", "CORRESPONDING") 6107 or None 6108 ) 6109 if self._match_text_seq("CORRESPONDING"): 6110 by_name = True 6111 if not side and not kind: 6112 kind = "INNER" 6113 6114 on_column_list = None 6115 if by_name and self._match_texts(("ON", "BY")): 6116 on_column_list = self._parse_wrapped_csv(self._parse_column) 6117 6118 expression = self._parse_select( 6119 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 6120 ) 6121 6122 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 6123 # in _parse_cte and so that alias pushdown can reach into set operation branches 6124 if isinstance(this, exp.Values): 6125 this = self._values_to_select(this) 6126 if isinstance(expression, exp.Values): 6127 expression = self._values_to_select(expression) 6128 6129 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 6130 subquery = this.this 6131 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 6132 subquery.add_comments(this.pop_comments()) 6133 this = subquery 6134 6135 return self.expression( 6136 operation( 6137 this=this, 6138 distinct=distinct, 6139 by_name=by_name, 6140 expression=expression, 6141 side=side, 6142 kind=kind, 6143 on=on_column_list, 6144 ), 6145 comments=comments, 6146 )