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.REF, 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.CONNECT, 777 TokenType.CONSTRAINT, 778 TokenType.COPY, 779 TokenType.CUBE, 780 TokenType.CURRENT_SCHEMA, 781 TokenType.DECLARE, 782 TokenType.DEFAULT, 783 TokenType.DELETE, 784 TokenType.DESC, 785 TokenType.DESCRIBE, 786 TokenType.DETACH, 787 TokenType.DICTIONARY, 788 TokenType.DISCONNECT, 789 TokenType.DIV, 790 TokenType.END, 791 TokenType.EXECUTE, 792 TokenType.EXPORT, 793 TokenType.ESCAPE, 794 TokenType.FALSE, 795 TokenType.FIRST, 796 TokenType.FILE, 797 TokenType.FILTER, 798 TokenType.FINAL, 799 TokenType.FORMAT, 800 TokenType.FULL, 801 TokenType.GET, 802 TokenType.IDENTIFIER, 803 TokenType.IN, 804 TokenType.INOUT, 805 TokenType.IS, 806 TokenType.ISNULL, 807 TokenType.INTERVAL, 808 TokenType.KEEP, 809 TokenType.KILL, 810 TokenType.LEFT, 811 TokenType.LIMIT, 812 TokenType.LOAD, 813 TokenType.LOCK, 814 TokenType.MATCH, 815 TokenType.MERGE, 816 TokenType.NATURAL, 817 TokenType.NEXT, 818 TokenType.OFFSET, 819 TokenType.OPERATOR, 820 TokenType.ORDINALITY, 821 TokenType.OUT, 822 TokenType.OVER, 823 TokenType.OVERLAPS, 824 TokenType.OVERWRITE, 825 TokenType.PARTITION, 826 TokenType.PASTE, 827 TokenType.PERCENT, 828 TokenType.PIVOT, 829 TokenType.PROJECTION, 830 TokenType.PRAGMA, 831 TokenType.PUT, 832 TokenType.RANGE, 833 TokenType.RECURSIVE, 834 TokenType.REFERENCES, 835 TokenType.REFRESH, 836 TokenType.RENAME, 837 TokenType.REPLACE, 838 TokenType.RIGHT, 839 TokenType.ROLLUP, 840 TokenType.ROW, 841 TokenType.ROWS, 842 TokenType.SEMI, 843 TokenType.SET, 844 TokenType.SETTINGS, 845 TokenType.SHOW, 846 TokenType.STREAM, 847 TokenType.STREAMLIT, 848 TokenType.TEMPORARY, 849 TokenType.TOP, 850 TokenType.TRUE, 851 TokenType.TRUNCATE, 852 TokenType.UNIQUE, 853 TokenType.UNNEST, 854 TokenType.UNPIVOT, 855 TokenType.UPDATE, 856 TokenType.USE, 857 TokenType.VOLATILE, 858 TokenType.WINDOW, 859 TokenType.CURRENT_CATALOG, 860 TokenType.LOCALTIME, 861 TokenType.LOCALTIMESTAMP, 862 TokenType.SESSION_USER, 863 TokenType.STRAIGHT_JOIN, 864 *ALTERABLES, 865 *CREATABLES, 866 *SUBQUERY_PREDICATES, 867 *TYPE_TOKENS, 868 *NO_PAREN_FUNCTIONS, 869 } - {TokenType.UNION} 870 871 TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - { 872 TokenType.ANTI, 873 TokenType.ASOF, 874 TokenType.FULL, 875 TokenType.LEFT, 876 TokenType.LOCK, 877 TokenType.NATURAL, 878 TokenType.RIGHT, 879 TokenType.SEMI, 880 TokenType.WINDOW, 881 } 882 883 ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS 884 885 COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS 886 887 ARRAY_CONSTRUCTORS: t.ClassVar = { 888 "ARRAY": exp.Array, 889 "LIST": exp.List, 890 } 891 892 COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS} 893 894 UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET} 895 896 TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"} 897 898 # Tokens that indicate a simple column reference 899 IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER}) 900 901 BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE}) 902 903 # Postfix tokens that prevent the bare column fast path 904 COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 905 { 906 TokenType.L_PAREN, 907 TokenType.L_BRACKET, 908 TokenType.L_BRACE, 909 TokenType.COLON, 910 TokenType.JOIN_MARKER, 911 } 912 ) 913 914 TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 915 { 916 TokenType.L_PAREN, 917 TokenType.L_BRACKET, 918 TokenType.L_BRACE, 919 TokenType.PIVOT, 920 TokenType.UNPIVOT, 921 TokenType.TABLE_SAMPLE, 922 } 923 ) 924 925 FUNC_TOKENS: t.ClassVar = { 926 TokenType.COLLATE, 927 TokenType.COMMAND, 928 TokenType.CURRENT_DATE, 929 TokenType.CURRENT_DATETIME, 930 TokenType.CURRENT_SCHEMA, 931 TokenType.CURRENT_TIMESTAMP, 932 TokenType.CURRENT_TIME, 933 TokenType.CURRENT_USER, 934 TokenType.CURRENT_CATALOG, 935 TokenType.DECLARE, 936 TokenType.FILTER, 937 TokenType.FIRST, 938 TokenType.FORMAT, 939 TokenType.GET, 940 TokenType.GLOB, 941 TokenType.IDENTIFIER, 942 TokenType.INDEX, 943 TokenType.ISNULL, 944 TokenType.ILIKE, 945 TokenType.INSERT, 946 TokenType.LIKE, 947 TokenType.LOCALTIME, 948 TokenType.LOCALTIMESTAMP, 949 TokenType.MERGE, 950 TokenType.NEXT, 951 TokenType.OFFSET, 952 TokenType.PRIMARY_KEY, 953 TokenType.RANGE, 954 TokenType.REPLACE, 955 TokenType.RLIKE, 956 TokenType.ROW, 957 TokenType.SESSION_USER, 958 TokenType.UNNEST, 959 TokenType.VAR, 960 TokenType.LEFT, 961 TokenType.RIGHT, 962 TokenType.SEQUENCE, 963 TokenType.DATE, 964 TokenType.DATETIME, 965 TokenType.TABLE, 966 TokenType.TIMESTAMP, 967 TokenType.TIMESTAMPTZ, 968 TokenType.TRUNCATE, 969 TokenType.UTC_DATE, 970 TokenType.UTC_TIME, 971 TokenType.UTC_TIMESTAMP, 972 TokenType.WINDOW, 973 TokenType.XOR, 974 *TYPE_TOKENS, 975 *SUBQUERY_PREDICATES, 976 } 977 978 CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 979 TokenType.AND: exp.And, 980 } 981 982 ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 983 TokenType.COLON_EQ: exp.PropertyEQ, 984 } 985 986 DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 987 TokenType.OR: exp.Or, 988 } 989 990 EQUALITY: t.ClassVar = { 991 TokenType.EQ: exp.EQ, 992 TokenType.NEQ: exp.NEQ, 993 TokenType.NULLSAFE_EQ: exp.NullSafeEQ, 994 } 995 996 COMPARISON: t.ClassVar = { 997 TokenType.GT: exp.GT, 998 TokenType.GTE: exp.GTE, 999 TokenType.LT: exp.LT, 1000 TokenType.LTE: exp.LTE, 1001 } 1002 1003 BITWISE: t.ClassVar = { 1004 TokenType.AMP: exp.BitwiseAnd, 1005 TokenType.CARET: exp.BitwiseXor, 1006 TokenType.PIPE: exp.BitwiseOr, 1007 } 1008 1009 TERM: t.ClassVar = { 1010 TokenType.DASH: exp.Sub, 1011 TokenType.PLUS: exp.Add, 1012 TokenType.COLLATE: exp.Collate, 1013 } 1014 1015 FACTOR: t.ClassVar = { 1016 TokenType.DIV: exp.IntDiv, 1017 TokenType.LR_ARROW: exp.Distance, 1018 TokenType.LLRR_ARROW: exp.DistanceNd, 1019 TokenType.MOD: exp.Mod, 1020 TokenType.SLASH: exp.Div, 1021 TokenType.STAR: exp.Mul, 1022 } 1023 1024 EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {} 1025 1026 TIMES: t.ClassVar = { 1027 TokenType.TIME, 1028 TokenType.TIMETZ, 1029 } 1030 1031 TIMESTAMPS: t.ClassVar = { 1032 TokenType.TIMESTAMP, 1033 TokenType.TIMESTAMPNTZ, 1034 TokenType.TIMESTAMPTZ, 1035 TokenType.TIMESTAMPLTZ, 1036 *TIMES, 1037 } 1038 1039 SET_OPERATIONS: t.ClassVar = { 1040 TokenType.UNION, 1041 TokenType.INTERSECT, 1042 TokenType.EXCEPT, 1043 } 1044 1045 JOIN_METHODS: t.ClassVar = { 1046 TokenType.ASOF, 1047 TokenType.NATURAL, 1048 TokenType.POSITIONAL, 1049 } 1050 1051 JOIN_SIDES: t.ClassVar = { 1052 TokenType.LEFT, 1053 TokenType.RIGHT, 1054 TokenType.FULL, 1055 } 1056 1057 JOIN_KINDS: t.ClassVar = { 1058 TokenType.ANTI, 1059 TokenType.CROSS, 1060 TokenType.INNER, 1061 TokenType.OUTER, 1062 TokenType.SEMI, 1063 TokenType.STRAIGHT_JOIN, 1064 } 1065 1066 JOIN_HINTS: t.ClassVar[set[str]] = set() 1067 1068 SUPPORTS_DIRECTED_JOIN = True 1069 1070 # Tokens that unambiguously end a table reference on the fast path 1071 TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset( 1072 { 1073 TokenType.COMMA, 1074 TokenType.GROUP_BY, 1075 TokenType.HAVING, 1076 TokenType.JOIN, 1077 TokenType.LIMIT, 1078 TokenType.ON, 1079 TokenType.ORDER_BY, 1080 TokenType.R_PAREN, 1081 TokenType.SEMICOLON, 1082 TokenType.SENTINEL, 1083 TokenType.WHERE, 1084 *SET_OPERATIONS, 1085 *JOIN_KINDS, 1086 *JOIN_METHODS, 1087 *JOIN_SIDES, 1088 } 1089 ) 1090 1091 LAMBDAS: t.ClassVar = { 1092 TokenType.ARROW: lambda self, expressions: self.expression( 1093 exp.Lambda( 1094 this=self._replace_lambda( 1095 self._parse_disjunction(), 1096 expressions, 1097 ), 1098 expressions=expressions, 1099 ) 1100 ), 1101 TokenType.FARROW: lambda self, expressions: self.expression( 1102 exp.Kwarg( 1103 this=exp.var(expressions[0].name), 1104 expression=self._parse_disjunction() or self._parse_select(), 1105 ) 1106 ), 1107 } 1108 1109 # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake 1110 TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False 1111 1112 LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN}) 1113 1114 COLUMN_OPERATORS: t.ClassVar = { 1115 TokenType.DOT: None, 1116 TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)), 1117 TokenType.DCOLON: lambda self, this, to: self.build_cast( 1118 strict=self.STRICT_CAST, this=this, to=to 1119 ), 1120 TokenType.ARROW: lambda self, this, path: self.expression( 1121 exp.JSONExtract( 1122 this=this, 1123 expression=self.dialect.to_json_path(path), 1124 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1125 ) 1126 ), 1127 TokenType.DARROW: lambda self, this, path: self.expression( 1128 exp.JSONExtractScalar( 1129 this=this, 1130 expression=self.dialect.to_json_path(path), 1131 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1132 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 1133 ) 1134 ), 1135 TokenType.HASH_ARROW: lambda self, this, path: self.expression( 1136 exp.JSONBExtract(this=this, expression=path) 1137 ), 1138 TokenType.DHASH_ARROW: lambda self, this, path: self.expression( 1139 exp.JSONBExtractScalar(this=this, expression=path) 1140 ), 1141 TokenType.PLACEHOLDER: lambda self, this, key: self.expression( 1142 exp.JSONBContainsTopKey(this=this, expression=key) 1143 ), 1144 } 1145 1146 # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator" 1147 # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs). 1148 JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {} 1149 1150 CAST_COLUMN_OPERATORS: t.ClassVar = { 1151 TokenType.DOTCOLON, 1152 TokenType.DCOLON, 1153 } 1154 1155 EXPRESSION_PARSERS: t.ClassVar = { 1156 exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), 1157 exp.Column: lambda self: self._parse_column(), 1158 exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()), 1159 exp.Condition: lambda self: self._parse_disjunction(), 1160 exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True), 1161 exp.Expr: lambda self: self._parse_expression(), 1162 exp.From: lambda self: self._parse_from(joins=True), 1163 exp.GrantPrincipal: lambda self: self._parse_grant_principal(), 1164 exp.GrantPrivilege: lambda self: self._parse_grant_privilege(), 1165 exp.Group: lambda self: self._parse_group(), 1166 exp.Having: lambda self: self._parse_having(), 1167 exp.Hint: lambda self: self._parse_hint_body(), 1168 exp.Identifier: lambda self: self._parse_id_var(), 1169 exp.Join: lambda self: self._parse_join(), 1170 exp.Lambda: lambda self: self._parse_lambda(), 1171 exp.Lateral: lambda self: self._parse_lateral(), 1172 exp.Limit: lambda self: self._parse_limit(), 1173 exp.Offset: lambda self: self._parse_offset(), 1174 exp.Order: lambda self: self._parse_order(), 1175 exp.Ordered: lambda self: self._parse_ordered(), 1176 exp.Properties: lambda self: self._parse_properties(), 1177 exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(), 1178 exp.Qualify: lambda self: self._parse_qualify(), 1179 exp.Returning: lambda self: self._parse_returning(), 1180 exp.Select: lambda self: self._parse_select(), 1181 exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), 1182 exp.Table: lambda self: self._parse_table_parts(), 1183 exp.TableAlias: lambda self: self._parse_table_alias(), 1184 exp.Tuple: lambda self: self._parse_value(values=False), 1185 exp.Whens: lambda self: self._parse_when_matched(), 1186 exp.Where: lambda self: self._parse_where(), 1187 exp.Window: lambda self: self._parse_named_window(), 1188 exp.With: lambda self: self._parse_with(), 1189 } 1190 1191 STATEMENT_PARSERS: t.ClassVar = { 1192 TokenType.ALTER: lambda self: self._parse_alter(), 1193 TokenType.ANALYZE: lambda self: self._parse_analyze(), 1194 TokenType.BEGIN: lambda self: self._parse_transaction(), 1195 TokenType.CACHE: lambda self: self._parse_cache(), 1196 TokenType.COMMENT: lambda self: self._parse_comment(), 1197 TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(), 1198 TokenType.COPY: lambda self: self._parse_copy(), 1199 TokenType.CREATE: lambda self: self._parse_create(), 1200 TokenType.DECLARE: lambda self: self._parse_declare(), 1201 TokenType.DELETE: lambda self: self._parse_delete(), 1202 TokenType.DESC: lambda self: self._parse_describe(), 1203 TokenType.DESCRIBE: lambda self: self._parse_describe(), 1204 TokenType.DROP: lambda self: self._parse_drop(), 1205 TokenType.GRANT: lambda self: self._parse_grant(), 1206 TokenType.REVOKE: lambda self: self._parse_revoke(), 1207 TokenType.INSERT: lambda self: self._parse_insert(), 1208 TokenType.KILL: lambda self: self._parse_kill(), 1209 TokenType.LOAD: lambda self: self._parse_load(), 1210 TokenType.MERGE: lambda self: self._parse_merge(), 1211 TokenType.PIVOT: lambda self: self._parse_simplified_pivot(), 1212 TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())), 1213 TokenType.REFRESH: lambda self: self._parse_refresh(), 1214 TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(), 1215 TokenType.SET: lambda self: self._parse_set(), 1216 TokenType.TRUNCATE: lambda self: self._parse_truncate_table(), 1217 TokenType.UNCACHE: lambda self: self._parse_uncache(), 1218 TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True), 1219 TokenType.UPDATE: lambda self: self._parse_update(), 1220 TokenType.USE: lambda self: self._parse_use(), 1221 TokenType.SEMICOLON: lambda self: exp.Semicolon(), 1222 } 1223 1224 UNARY_PARSERS: t.ClassVar = { 1225 TokenType.PLUS: lambda self: self._parse_unary(), # Unary + is handled as a no-op 1226 TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())), 1227 TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())), 1228 TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())), 1229 TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())), 1230 TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())), 1231 } 1232 1233 STRING_PARSERS: t.ClassVar = { 1234 TokenType.HEREDOC_STRING: lambda self, token: self.expression( 1235 exp.RawString(this=token.text), token 1236 ), 1237 **{ 1238 token_type: lambda self, token: self.expression(exp.National(this=token.text), token) 1239 for token_type in (TokenType.NATIONAL_STRING, TokenType.NATIONAL_RAW_STRING) 1240 }, 1241 TokenType.RAW_STRING: lambda self, token: self.expression( 1242 exp.RawString(this=token.text), token 1243 ), 1244 TokenType.STRING: lambda self, token: self.expression( 1245 exp.Literal(this=token.text, is_string=True), token 1246 ), 1247 TokenType.UNICODE_STRING: lambda self, token: self.expression( 1248 exp.UnicodeString( 1249 this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string() 1250 ), 1251 token, 1252 ), 1253 } 1254 1255 NUMERIC_PARSERS: t.ClassVar = { 1256 TokenType.BIT_STRING: lambda self, token: self.expression( 1257 exp.BitString(this=token.text), token 1258 ), 1259 TokenType.BYTE_STRING: lambda self, token: self.expression( 1260 exp.ByteString( 1261 this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None 1262 ), 1263 token, 1264 ), 1265 TokenType.HEX_STRING: lambda self, token: self.expression( 1266 exp.HexString( 1267 this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None 1268 ), 1269 token, 1270 ), 1271 TokenType.NUMBER: lambda self, token: self.expression( 1272 exp.Literal(this=token.text, is_string=False), token 1273 ), 1274 } 1275 1276 PRIMARY_PARSERS: t.ClassVar = { 1277 **STRING_PARSERS, 1278 **NUMERIC_PARSERS, 1279 TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token), 1280 TokenType.NULL: lambda self, _: self.expression(exp.Null()), 1281 TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)), 1282 TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)), 1283 TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(), 1284 TokenType.STAR: lambda self, _: self._parse_star_ops(), 1285 } 1286 1287 PLACEHOLDER_PARSERS: t.ClassVar = { 1288 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()), 1289 TokenType.PARAMETER: lambda self: self._parse_parameter(), 1290 TokenType.COLON: lambda self: ( 1291 self.expression(exp.Placeholder(this=self._prev.text)) 1292 if self._match_set(self.COLON_PLACEHOLDER_TOKENS) 1293 else None 1294 ), 1295 } 1296 1297 RANGE_PARSERS: t.ClassVar = { 1298 TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll), 1299 TokenType.BETWEEN: lambda self, this: self._parse_between(this), 1300 TokenType.GLOB: binary_range_parser(exp.Glob), 1301 TokenType.ILIKE: binary_range_parser(exp.ILike), 1302 TokenType.IN: lambda self, this: self._parse_in(this), 1303 TokenType.IRLIKE: binary_range_parser(exp.RegexpILike), 1304 TokenType.IS: lambda self, this: self._parse_is(this), 1305 TokenType.LIKE: binary_range_parser(exp.Like), 1306 TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy), 1307 TokenType.OVERLAPS: binary_range_parser(exp.Overlaps), 1308 TokenType.RLIKE: binary_range_parser(exp.RegexpLike), 1309 TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo), 1310 TokenType.FOR: lambda self, this: self._parse_comprehension(this), 1311 TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys), 1312 TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys), 1313 TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath), 1314 TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists), 1315 TokenType.ADJACENT: binary_range_parser(exp.Adjacent), 1316 TokenType.OPERATOR: lambda self, this: self._parse_operator(this), 1317 TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft), 1318 TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight), 1319 TokenType.CARET_AT: binary_range_parser(exp.StartsWith), 1320 } 1321 1322 PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = { 1323 "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query), 1324 "AS": lambda self, query: self._build_pipe_cte( 1325 query, [exp.Star()], self._parse_table_alias() 1326 ), 1327 "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False), 1328 "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query), 1329 "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query), 1330 "ORDER BY": lambda self, query: query.order_by( 1331 self._parse_order(), append=False, copy=False 1332 ), 1333 "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1334 "SELECT": lambda self, query: self._parse_pipe_syntax_select(query), 1335 "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query), 1336 "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1337 "WHERE": lambda self, query: query.where(self._parse_where(), copy=False), 1338 } 1339 1340 PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1341 "ALLOWED_VALUES": lambda self: self.expression( 1342 exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary)) 1343 ), 1344 "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty), 1345 "AUTO": lambda self: self._parse_auto_property(), 1346 "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty), 1347 "BACKUP": lambda self: self.expression( 1348 exp.BackupProperty(this=self._parse_var(any_token=True)) 1349 ), 1350 "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(), 1351 "CALLED": lambda self: self._parse_called_on_null_input_property(), 1352 "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs), 1353 "CHECKSUM": lambda self: self._parse_checksum(), 1354 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1355 "CLUSTERED": lambda self: self._parse_clustered_by(), 1356 "COLLATE": lambda self, **kwargs: self._parse_property_assignment( 1357 exp.CollateProperty, **kwargs 1358 ), 1359 "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty), 1360 "CONTAINS": lambda self: self._parse_contains_property(), 1361 "COPY": lambda self: self._parse_copy_property(), 1362 "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs), 1363 "DATA_DELETION": lambda self: self._parse_data_deletion_property(), 1364 "DEFINER": lambda self: self._parse_definer(), 1365 "DETERMINISTIC": lambda self: self.expression( 1366 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1367 ), 1368 "DISTRIBUTED": lambda self: self._parse_distributed_property(), 1369 "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty), 1370 "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()), 1371 "DISTKEY": lambda self: self._parse_distkey(), 1372 "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty), 1373 "EMPTY": lambda self: self.expression(exp.EmptyProperty()), 1374 "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty), 1375 "ENVIRONMENT": lambda self: self.expression( 1376 exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment)) 1377 ), 1378 "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty), 1379 "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty), 1380 "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()), 1381 "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs), 1382 "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1383 "FREESPACE": lambda self: self._parse_freespace(), 1384 "GLOBAL": lambda self: self.expression(exp.GlobalProperty()), 1385 "HEAP": lambda self: self.expression(exp.HeapProperty()), 1386 "ICEBERG": lambda self: self.expression(exp.IcebergProperty()), 1387 "IMMUTABLE": lambda self: self.expression( 1388 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1389 ), 1390 "INHERITS": lambda self: self.expression( 1391 exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table)) 1392 ), 1393 "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())), 1394 "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs), 1395 "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty), 1396 "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"), 1397 "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"), 1398 "LIKE": lambda self: self._parse_create_like(), 1399 "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty), 1400 "LOCK": lambda self: self._parse_locking(), 1401 "LOCKING": lambda self: self._parse_locking(), 1402 "LOG": lambda self, **kwargs: self._parse_log(**kwargs), 1403 "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()), 1404 "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs), 1405 "MODIFIES": lambda self: self._parse_modifies_property(), 1406 "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)), 1407 "NO": lambda self: self._parse_no_property(), 1408 "ON": lambda self: self._parse_on_property(), 1409 "ORDER BY": lambda self: self._parse_order(skip_order_token=True), 1410 "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())), 1411 "PARTITION": lambda self: self._parse_partitioned_of(), 1412 "PARTITION BY": lambda self: self._parse_partitioned_by(), 1413 "PARTITIONED BY": lambda self: self._parse_partitioned_by(), 1414 "PARTITIONED_BY": lambda self: self._parse_partitioned_by(), 1415 "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True), 1416 "RANGE": lambda self: self._parse_dict_range(this="RANGE"), 1417 "READS": lambda self: self._parse_reads_property(), 1418 "REMOTE": lambda self: self._parse_remote_with_connection(), 1419 "RETURNS": lambda self: self._parse_returns(), 1420 "STRICT": lambda self: self.expression(exp.StrictProperty()), 1421 "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()), 1422 "ROW": lambda self: self._parse_row(), 1423 "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty), 1424 "SAMPLE": lambda self: self.expression( 1425 exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise()) 1426 ), 1427 "SECURE": lambda self: self.expression(exp.SecureProperty()), 1428 "SECURITY": lambda self: self._parse_sql_security(), 1429 "SQL SECURITY": lambda self: self._parse_sql_security(), 1430 "SET": lambda self: self.expression(exp.SetProperty(multi=False)), 1431 "SETTINGS": lambda self: self._parse_settings_property(), 1432 "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty), 1433 "SORTKEY": lambda self: self._parse_sortkey(), 1434 "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"), 1435 "STABLE": lambda self: self.expression( 1436 exp.StabilityProperty(this=exp.Literal.string("STABLE")) 1437 ), 1438 "STORED": lambda self: self._parse_stored(), 1439 "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(), 1440 "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(), 1441 "TEMP": lambda self: self.expression(exp.TemporaryProperty()), 1442 "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()), 1443 "TO": lambda self: self._parse_to_table(), 1444 "TRANSIENT": lambda self: self.expression(exp.TransientProperty()), 1445 "TRANSFORM": lambda self: self.expression( 1446 exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression)) 1447 ), 1448 "TTL": lambda self: self._parse_ttl(), 1449 "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1450 "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()), 1451 "VOLATILE": lambda self: self._parse_volatile_property(), 1452 "WITH": lambda self: self._parse_with_property(), 1453 } 1454 1455 CONSTRAINT_PARSERS: t.ClassVar = { 1456 "AUTOINCREMENT": lambda self: self._parse_auto_increment(), 1457 "AUTO_INCREMENT": lambda self: self._parse_auto_increment(), 1458 "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)), 1459 "CHECK": lambda self: self._parse_check_constraint(), 1460 "COLLATE": lambda self: self.expression( 1461 exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column()) 1462 ), 1463 "COMMENT": lambda self: self.expression( 1464 exp.CommentColumnConstraint(this=self._parse_string()) 1465 ), 1466 "COMPRESS": lambda self: self._parse_compress(), 1467 "CLUSTERED": lambda self: self.expression( 1468 exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1469 ), 1470 "NONCLUSTERED": lambda self: self.expression( 1471 exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1472 ), 1473 "DEFAULT": lambda self: self.expression( 1474 exp.DefaultColumnConstraint(this=self._parse_bitwise()) 1475 ), 1476 "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())), 1477 "EPHEMERAL": lambda self: self.expression( 1478 exp.EphemeralColumnConstraint(this=self._parse_bitwise()) 1479 ), 1480 "EXCLUDE": lambda self: self.expression( 1481 exp.ExcludeColumnConstraint(this=self._parse_index_params()) 1482 ), 1483 "FOREIGN KEY": lambda self: self._parse_foreign_key(), 1484 "FORMAT": lambda self: self.expression( 1485 exp.DateFormatColumnConstraint(this=self._parse_var_or_string()) 1486 ), 1487 "GENERATED": lambda self: self._parse_generated_as_identity(), 1488 "IDENTITY": lambda self: self._parse_auto_increment(), 1489 "INLINE": lambda self: self._parse_inline(), 1490 "LIKE": lambda self: self._parse_create_like(), 1491 "NOT": lambda self: self._parse_not_constraint(), 1492 "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)), 1493 "ON": lambda self: ( 1494 ( 1495 self._match(TokenType.UPDATE) 1496 and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function())) 1497 ) 1498 or self.expression(exp.OnProperty(this=self._parse_id_var())) 1499 ), 1500 "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())), 1501 "PERIOD": lambda self: self._parse_period_for_system_time(), 1502 "PRIMARY KEY": lambda self: self._parse_primary_key(), 1503 "REFERENCES": lambda self: self._parse_references(match=False), 1504 "TITLE": lambda self: self.expression( 1505 exp.TitleColumnConstraint(this=self._parse_var_or_string()) 1506 ), 1507 "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])), 1508 "UNIQUE": lambda self: self._parse_unique(), 1509 "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()), 1510 "WITH": lambda self: self.expression( 1511 exp.Properties(expressions=self._parse_wrapped_properties()) 1512 ), 1513 "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1514 "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1515 } 1516 1517 def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None: 1518 if not self._match(TokenType.L_PAREN, advance=False): 1519 # Partitioning by bucket or truncate follows the syntax: 1520 # PARTITION BY (BUCKET(..) | TRUNCATE(..)) 1521 # If we don't have parenthesis after each keyword, we should instead parse this as an identifier 1522 self._retreat(self._index - 1) 1523 return None 1524 1525 klass = ( 1526 exp.PartitionedByBucket 1527 if self._prev.text.upper() == "BUCKET" 1528 else exp.PartitionByTruncate 1529 ) 1530 1531 args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column()) 1532 this, expression = seq_get(args, 0), seq_get(args, 1) 1533 1534 if isinstance(this, exp.Literal): 1535 # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order 1536 # - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)` 1537 # - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)` 1538 # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)` 1539 # 1540 # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning 1541 # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties 1542 this, expression = expression, this 1543 1544 return self.expression(klass(this=this, expression=expression)) 1545 1546 ALTER_PARSERS: t.ClassVar = { 1547 "ADD": lambda self: self._parse_alter_table_add(), 1548 "AS": lambda self: self._parse_select(), 1549 "ALTER": lambda self: self._parse_alter_table_alter(), 1550 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1551 "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())), 1552 "DROP": lambda self: self._parse_alter_table_drop(), 1553 "RENAME": lambda self: self._parse_alter_table_rename(), 1554 "SET": lambda self: self._parse_alter_table_set(), 1555 "SWAP": lambda self: self.expression( 1556 exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True)) 1557 ), 1558 } 1559 1560 ALTER_ALTER_PARSERS: t.ClassVar = { 1561 "DISTKEY": lambda self: self._parse_alter_diststyle(), 1562 "DISTSTYLE": lambda self: self._parse_alter_diststyle(), 1563 "SORTKEY": lambda self: self._parse_alter_sortkey(), 1564 "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True), 1565 } 1566 1567 SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = { 1568 "CHECK", 1569 "EXCLUDE", 1570 "FOREIGN KEY", 1571 "LIKE", 1572 "PERIOD", 1573 "PRIMARY KEY", 1574 "UNIQUE", 1575 "BUCKET", 1576 "TRUNCATE", 1577 } 1578 1579 # Kinds that can't be a constraint name, so `CONSTRAINT` directly followed by one is unnamed 1580 RESERVED_CONSTRAINT_KINDS: t.ClassVar[set[str]] = set() 1581 1582 NO_PAREN_FUNCTION_PARSERS: t.ClassVar = { 1583 "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())), 1584 "CASE": lambda self: self._parse_case(), 1585 "CONNECT_BY_ROOT": lambda self: self.expression( 1586 exp.ConnectByRoot(this=self._parse_column()) 1587 ), 1588 "IF": lambda self: self._parse_if(), 1589 } 1590 1591 INVALID_FUNC_NAME_TOKENS: t.ClassVar = { 1592 TokenType.IDENTIFIER, 1593 TokenType.STRING, 1594 } 1595 1596 FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"} 1597 1598 KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice) 1599 1600 FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1601 **{ 1602 name: lambda self: self._parse_distinct_arg_function(exp.ArgMax) 1603 for name in exp.ArgMax.sql_names() 1604 }, 1605 **{ 1606 name: lambda self: self._parse_distinct_arg_function(exp.ArgMin) 1607 for name in exp.ArgMin.sql_names() 1608 }, 1609 "CAST": lambda self: self._parse_cast(self.STRICT_CAST), 1610 "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), 1611 "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), 1612 "CHAR": lambda self: self._parse_char(), 1613 "CHR": lambda self: self._parse_char(), 1614 "DECODE": lambda self: self._parse_decode(), 1615 "EXTRACT": lambda self: self._parse_extract(), 1616 "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), 1617 "GAP_FILL": lambda self: self._parse_gap_fill(), 1618 "INITCAP": lambda self: self._parse_initcap(), 1619 "JSON_OBJECT": lambda self: self._parse_json_object(), 1620 "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True), 1621 "JSON_TABLE": lambda self: self._parse_json_table(), 1622 "MATCH": lambda self: self._parse_match_against(), 1623 "NORMALIZE": lambda self: self._parse_normalize(), 1624 "OPENJSON": lambda self: self._parse_open_json(), 1625 "OVERLAY": lambda self: self._parse_overlay(), 1626 "POSITION": lambda self: self._parse_position(), 1627 "SAFE_CAST": lambda self: self._parse_cast(False, safe=True), 1628 "STRING_AGG": lambda self: self._parse_string_agg(), 1629 "SUBSTRING": lambda self: self._parse_substring(), 1630 "TRIM": lambda self: self._parse_trim(), 1631 "TRY_CAST": lambda self: self._parse_cast(False, safe=True), 1632 "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True), 1633 "WEIGHT_STRING": lambda self: self._parse_weight_string(), 1634 "XMLELEMENT": lambda self: self._parse_xml_element(), 1635 "XMLTABLE": lambda self: self._parse_xml_table(), 1636 } 1637 1638 QUERY_MODIFIER_PARSERS: t.ClassVar = { 1639 TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()), 1640 TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()), 1641 TokenType.WHERE: lambda self: ("where", self._parse_where()), 1642 TokenType.GROUP_BY: lambda self: ("group", self._parse_group()), 1643 TokenType.HAVING: lambda self: ("having", self._parse_having()), 1644 TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()), 1645 TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()), 1646 TokenType.ORDER_BY: lambda self: ("order", self._parse_order()), 1647 TokenType.LIMIT: lambda self: ("limit", self._parse_limit()), 1648 TokenType.FETCH: lambda self: ("limit", self._parse_limit()), 1649 TokenType.OFFSET: lambda self: ("offset", self._parse_offset()), 1650 TokenType.FOR: lambda self: ("locks", self._parse_locks()), 1651 TokenType.LOCK: lambda self: ("locks", self._parse_locks()), 1652 TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1653 TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1654 TokenType.CLUSTER_BY: lambda self: ( 1655 "cluster", 1656 self._parse_cluster(), 1657 ), 1658 TokenType.DISTRIBUTE_BY: lambda self: ( 1659 "distribute", 1660 self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), 1661 ), 1662 TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)), 1663 TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)), 1664 } 1665 QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS) 1666 1667 SET_PARSERS: t.ClassVar = { 1668 "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"), 1669 "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"), 1670 "SESSION": lambda self: self._parse_set_item_assignment("SESSION"), 1671 "TRANSACTION": lambda self: self._parse_set_transaction(), 1672 } 1673 1674 SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {} 1675 1676 TYPE_LITERAL_PARSERS: t.ClassVar = { 1677 exp.DType.JSON: lambda self, this, _: self.expression( 1678 exp.ParseJSON(this=this, is_literal=True) 1679 ), 1680 } 1681 1682 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1683 1684 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1685 1686 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1687 1688 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1689 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1690 "ISOLATION": ( 1691 ("LEVEL", "REPEATABLE", "READ"), 1692 ("LEVEL", "READ", "COMMITTED"), 1693 ("LEVEL", "READ", "UNCOMITTED"), 1694 ("LEVEL", "SERIALIZABLE"), 1695 ), 1696 "READ": ("WRITE", "ONLY"), 1697 } 1698 1699 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1700 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1701 "DO": ("NOTHING", "UPDATE"), 1702 } 1703 1704 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1705 "INSTEAD": (("OF",),), 1706 "BEFORE": tuple(), 1707 "AFTER": tuple(), 1708 } 1709 1710 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1711 "NOT": (("DEFERRABLE",),), 1712 "DEFERRABLE": tuple(), 1713 } 1714 1715 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1716 "SCALE": ("EXTEND", "NOEXTEND"), 1717 "SHARD": ("EXTEND", "NOEXTEND"), 1718 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1719 **dict.fromkeys( 1720 ( 1721 "SESSION", 1722 "GLOBAL", 1723 "KEEP", 1724 "NOKEEP", 1725 "ORDER", 1726 "NOORDER", 1727 "NOCACHE", 1728 "CYCLE", 1729 "NOCYCLE", 1730 "NOMINVALUE", 1731 "NOMAXVALUE", 1732 "NOSCALE", 1733 "NOSHARD", 1734 ), 1735 tuple(), 1736 ), 1737 } 1738 1739 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1740 1741 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1742 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1743 ) 1744 1745 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1746 1747 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1748 "TYPE": ("EVOLUTION",), 1749 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1750 } 1751 1752 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1753 1754 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1755 ("CALLER", "SELF", "OWNER"), tuple() 1756 ) 1757 1758 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1759 "NOT": ("ENFORCED",), 1760 "MATCH": ( 1761 "FULL", 1762 "PARTIAL", 1763 "SIMPLE", 1764 ), 1765 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1766 "USING": ( 1767 "BTREE", 1768 "HASH", 1769 ), 1770 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1771 } 1772 1773 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1774 "NO": ("OTHERS",), 1775 "CURRENT": ("ROW",), 1776 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1777 } 1778 1779 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1780 1781 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1782 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1783 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1784 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1785 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1786 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1787 ("FOR", "VERSION"): "VERSION", 1788 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1789 ("VERSION", "AS", "OF"): "VERSION", 1790 } 1791 1792 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1793 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1794 1795 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1796 1797 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1798 1799 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1800 1801 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1802 1803 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1804 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1805 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1806 1807 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1808 1809 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1810 1811 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1812 TokenType.CONSTRAINT, 1813 TokenType.FOREIGN_KEY, 1814 TokenType.INDEX, 1815 TokenType.KEY, 1816 TokenType.PRIMARY_KEY, 1817 TokenType.UNIQUE, 1818 } 1819 1820 # Constraint kinds that start an ALTER ... ADD action but aren't tokens of their own 1821 ADD_CONSTRAINT_KEYWORDS: t.ClassVar[set[str]] = set() 1822 1823 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1824 1825 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1826 1827 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1828 1829 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1830 "FILE_FORMAT", 1831 "COPY_OPTIONS", 1832 "FORMAT_OPTIONS", 1833 "CREDENTIAL", 1834 } 1835 1836 # IS binds above comparisons (high), alongside ordering comparisons (comparison), 1837 # alongside equality (equality), or below both (low). Preserve the historical 1838 # high precedence unless the dialect specifies otherwise. 1839 IS_PREDICATE_PRECEDENCE: t.ClassVar[IsPredicatePrecedence] = IsPredicatePrecedence.HIGH 1840 IS_PREDICATE_TOKENS: t.ClassVar = { 1841 TokenType.IS, 1842 TokenType.ISNULL, 1843 TokenType.NOTNULL, 1844 TokenType.NOT, 1845 } 1846 1847 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1848 1849 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1850 1851 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1852 1853 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1854 1855 # The style options for the DESCRIBE statement 1856 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1857 1858 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1859 1860 # The style options for the ANALYZE statement 1861 ANALYZE_STYLES: t.ClassVar = { 1862 "BUFFER_USAGE_LIMIT", 1863 "FULL", 1864 "LOCAL", 1865 "NO_WRITE_TO_BINLOG", 1866 "SAMPLE", 1867 "SKIP_LOCKED", 1868 "VERBOSE", 1869 } 1870 1871 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1872 "ALL": lambda self: self._parse_analyze_columns(), 1873 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1874 "DELETE": lambda self: self._parse_analyze_delete(), 1875 "DROP": lambda self: self._parse_analyze_histogram(), 1876 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1877 "LIST": lambda self: self._parse_analyze_list(), 1878 "PREDICATE": lambda self: self._parse_analyze_columns(), 1879 "UPDATE": lambda self: self._parse_analyze_histogram(), 1880 "VALIDATE": lambda self: self._parse_analyze_validate(), 1881 } 1882 1883 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1884 1885 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1886 1887 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1888 1889 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1890 1891 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1892 1893 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1894 1895 STRICT_CAST: t.ClassVar = True 1896 1897 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1898 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1899 # Whether an UNPIVOT outputs its value column(s) before the name column 1900 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1901 # Controls when an aggregation's name is included in a pivoted column's name: 1902 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1903 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1904 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1905 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1906 1907 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1908 1909 # Whether the table sample clause expects CSV syntax 1910 TABLESAMPLE_CSV: t.ClassVar = False 1911 1912 # The default method used for table sampling 1913 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1914 1915 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1916 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1917 1918 # Whether the TRIM function expects the characters to trim as its first argument 1919 TRIM_PATTERN_FIRST: t.ClassVar = False 1920 1921 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1922 STRING_ALIASES: t.ClassVar = False 1923 1924 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1925 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1926 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"} 1927 1928 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1929 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1930 1931 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1932 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1933 1934 # Whether the `:` operator is used to extract a value from a VARIANT column 1935 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1936 1937 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1938 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1939 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1940 1941 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1942 # If this is True and '(' is not found, the keyword will be treated as an identifier 1943 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1944 1945 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1946 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1947 1948 # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery) 1949 SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False 1950 1951 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1952 INTERVAL_SPANS: t.ClassVar = True 1953 1954 # Whether a PARTITION clause can follow a table reference 1955 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1956 1957 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1958 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1959 1960 # Whether the 'AS' keyword is optional in the CTE definition syntax 1961 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1962 1963 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1964 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1965 1966 # Whether dropping a column with an ALTER statement requires the presence of the COLUMN keyword 1967 ALTER_DROP_REQUIRES_COLUMN: t.ClassVar = True 1968 1969 # Whether Alter statements are allowed to contain Partition specifications 1970 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1971 1972 # Whether an ALTER can combine different actions, e.g. ADD COLUMN a INT, DROP COLUMN b 1973 ALTER_TABLE_MIXED_ACTIONS: t.ClassVar = False 1974 1975 # Whether an ALTER TABLE needs an action, e.g. MySQL's ALTER TABLE t ENGINE=InnoDB has none 1976 ALTER_TABLE_REQUIRES_ACTION: t.ClassVar = True 1977 1978 # Whether changing a column's type with ALTER COLUMN requires the TYPE keyword 1979 ALTER_COLUMN_TYPE_REQUIRES_KEYWORD: t.ClassVar = False 1980 1981 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1982 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1983 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1984 # as BigQuery, where all joins have the same precedence. 1985 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1986 1987 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1988 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1989 1990 # Whether map literals support arbitrary expressions as keys. 1991 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1992 # When False, keys are typically restricted to identifiers. 1993 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1994 1995 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1996 # is true for Snowflake but not for BigQuery which can also process strings 1997 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1998 1999 # Dialects like Databricks support JOINS without join criteria 2000 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 2001 ADD_JOIN_ON_TRUE: t.ClassVar = False 2002 2003 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 2004 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 2005 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 2006 2007 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 2008 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 2009 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 2010 2011 # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause, 2012 # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake) 2013 SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False 2014 2015 # Type names that denote a different type when they're quoted, so quoting has to be 2016 # preserved instead of resolving them into the built-in type of the same name. These 2017 # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR 2018 QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set() 2019 2020 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 2021 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 2022 2023 def __init__( 2024 self, 2025 error_level: ErrorLevel | None = None, 2026 error_message_context: int = 100, 2027 max_errors: int = 3, 2028 max_nodes: int = -1, 2029 dialect: DialectType = None, 2030 ): 2031 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 2032 self.error_message_context: int = error_message_context 2033 self.max_errors: int = max_errors 2034 self.max_nodes: int = max_nodes 2035 self.dialect: t.Any = _resolve_dialect(dialect) 2036 self.sql: str = "" 2037 self.errors: list[ParseError] = [] 2038 self._tokens: list[Token] = [] 2039 self._tokens_size: i64 = 0 2040 self._index: i64 = 0 2041 self._curr: Token = SENTINEL_NONE 2042 self._next: Token = SENTINEL_NONE 2043 self._prev: Token = SENTINEL_NONE 2044 self._prev_comments: list[str] = [] 2045 self._pipe_cte_counter: int = 0 2046 self._chunks: list[list[Token]] = [] 2047 self._chunk_index: i64 = 0 2048 self._node_count: int = 0 2049 2050 def reset(self) -> None: 2051 self.sql = "" 2052 self.errors = [] 2053 self._tokens = [] 2054 self._tokens_size = 0 2055 self._index = 0 2056 self._curr = SENTINEL_NONE 2057 self._next = SENTINEL_NONE 2058 self._prev = SENTINEL_NONE 2059 self._prev_comments = [] 2060 self._pipe_cte_counter = 0 2061 self._chunks = [] 2062 self._chunk_index = 0 2063 self._node_count = 0 2064 2065 def _advance(self, times: i64 = 1) -> None: 2066 index = self._index + times 2067 self._index = index 2068 tokens = self._tokens 2069 size = self._tokens_size 2070 self._curr = tokens[index] if index < size else SENTINEL_NONE 2071 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 2072 2073 if index > 0: 2074 prev = tokens[index - 1] 2075 self._prev = prev 2076 self._prev_comments = prev.comments 2077 else: 2078 self._prev = SENTINEL_NONE 2079 self._prev_comments = [] 2080 2081 def _advance_chunk(self) -> None: 2082 self._index = -1 2083 self._tokens = self._chunks[self._chunk_index] 2084 self._tokens_size = i64(len(self._tokens)) 2085 self._chunk_index += 1 2086 self._advance() 2087 2088 def _retreat(self, index: i64) -> None: 2089 if index != self._index: 2090 self._advance(index - self._index) 2091 2092 def _add_comments(self, expression: exp.Expr | None) -> None: 2093 if expression and self._prev_comments: 2094 expression.add_comments(self._prev_comments) 2095 self._prev_comments = [] 2096 2097 def _match( 2098 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2099 ) -> bool: 2100 if self._curr.token_type == token_type: 2101 if advance: 2102 self._advance() 2103 self._add_comments(expression) 2104 return True 2105 return False 2106 2107 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2108 if self._curr.token_type in types: 2109 if advance: 2110 self._advance() 2111 return True 2112 return False 2113 2114 def _match_pair( 2115 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2116 ) -> bool: 2117 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2118 if advance: 2119 self._advance(2) 2120 return True 2121 return False 2122 2123 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2124 if ( 2125 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2126 and self._curr.text.upper() in texts 2127 ): 2128 if advance: 2129 self._advance() 2130 return True 2131 return False 2132 2133 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2134 index = self._index 2135 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2136 for text in texts: 2137 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2138 self._advance() 2139 else: 2140 self._retreat(index) 2141 return False 2142 2143 if not advance: 2144 self._retreat(index) 2145 2146 return True 2147 2148 def _is_connected(self) -> bool: 2149 prev = self._prev 2150 curr = self._curr 2151 return bool(prev and curr and prev.end + 1 == curr.start) 2152 2153 def _find_sql(self, start: Token, end: Token) -> str: 2154 return self.sql[start.start : end.end + 1] 2155 2156 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2157 token = token or self._curr or self._prev or Token.string("") 2158 formatted_sql, start_context, highlight, end_context = highlight_sql( 2159 sql=self.sql, 2160 positions=[(token.start, token.end)], 2161 context_length=self.error_message_context, 2162 ) 2163 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2164 2165 error = ParseError.new( 2166 formatted_message, 2167 description=message, 2168 line=token.line, 2169 col=token.col, 2170 start_context=start_context, 2171 highlight=highlight, 2172 end_context=end_context, 2173 ) 2174 2175 if self.error_level == ErrorLevel.IMMEDIATE: 2176 raise error 2177 2178 self.errors.append(error) 2179 2180 def validate_expression(self, expression: E, args: list | None = None) -> E: 2181 if self.max_nodes > -1: 2182 self._node_count += 1 2183 if self._node_count > self.max_nodes: 2184 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2185 if self.error_level != ErrorLevel.IGNORE: 2186 for error_message in expression.error_messages(args): 2187 self.raise_error(error_message) 2188 return expression 2189 2190 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2191 index = self._index 2192 error_level = self.error_level 2193 this: T | None = None 2194 2195 self.error_level = ErrorLevel.IMMEDIATE 2196 try: 2197 this = parse_method() 2198 except ParseError: 2199 this = None 2200 finally: 2201 if not this or retreat: 2202 self._retreat(index) 2203 self.error_level = error_level 2204 2205 return this 2206 2207 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2208 """ 2209 Parses a list of tokens and returns a list of syntax trees, one tree 2210 per parsed SQL statement. 2211 2212 Args: 2213 raw_tokens: The list of tokens. 2214 sql: The original SQL string. 2215 2216 Returns: 2217 The list of the produced syntax trees. 2218 """ 2219 return self._parse( 2220 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2221 ) 2222 2223 def parse_into( 2224 self, 2225 expression_types: exp.IntoType, 2226 raw_tokens: list[Token], 2227 sql: str | None = None, 2228 ) -> list[exp.Expr | None]: 2229 """ 2230 Parses a list of tokens into a given Expr type. If a collection of Expr 2231 types is given instead, this method will try to parse the token list into each one 2232 of them, stopping at the first for which the parsing succeeds. 2233 2234 Args: 2235 expression_types: The expression type(s) to try and parse the token list into. 2236 raw_tokens: The list of tokens. 2237 sql: The original SQL string, used to produce helpful debug messages. 2238 2239 Returns: 2240 The target Expr. 2241 """ 2242 errors = [] 2243 for expression_type in ensure_list(expression_types): 2244 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2245 if not parser: 2246 raise TypeError(f"No parser registered for {expression_type}") 2247 2248 try: 2249 return self._parse(parser, raw_tokens, sql) 2250 except ParseError as e: 2251 e.errors[0]["into_expression"] = expression_type 2252 errors.append(e) 2253 2254 raise ParseError( 2255 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2256 errors=merge_errors(errors), 2257 ) from errors[-1] 2258 2259 def check_errors(self) -> None: 2260 """Logs or raises any found errors, depending on the chosen error level setting.""" 2261 if self.error_level == ErrorLevel.WARN: 2262 for error in self.errors: 2263 logger.error(str(error)) 2264 elif self.error_level == ErrorLevel.RAISE and self.errors: 2265 raise ParseError( 2266 concat_messages(self.errors, self.max_errors), 2267 errors=merge_errors(self.errors), 2268 ) 2269 2270 def expression( 2271 self, 2272 instance: E, 2273 token: Token | None = None, 2274 comments: list[str] | None = None, 2275 ) -> E: 2276 if token: 2277 instance.update_positions(token) 2278 instance.add_comments(comments) if comments else self._add_comments(instance) 2279 if not instance.is_primitive: 2280 instance = self.validate_expression(instance) 2281 return instance 2282 2283 def _parse_batch_statements( 2284 self, 2285 parse_method: t.Callable[[Parser], exp.Expr | None], 2286 sep_first_statement: bool = True, 2287 ) -> list[exp.Expr | None]: 2288 expressions = [] 2289 2290 # Chunkification binds if/while statements with the first statement of the body 2291 if sep_first_statement: 2292 self._match(TokenType.BEGIN) 2293 expressions.append(parse_method(self)) 2294 2295 chunks_length = len(self._chunks) 2296 while self._chunk_index < chunks_length: 2297 self._advance_chunk() 2298 2299 if self._match(TokenType.ELSE, advance=False): 2300 return expressions 2301 2302 if expressions and not self._next and self._match(TokenType.END): 2303 expressions.append(exp.EndStatement()) 2304 continue 2305 2306 expressions.append(parse_method(self)) 2307 2308 if self._index < self._tokens_size: 2309 self.raise_error("Invalid expression / Unexpected token") 2310 2311 self.check_errors() 2312 2313 return expressions 2314 2315 def _parse( 2316 self, 2317 parse_method: t.Callable[[Parser], exp.Expr | None], 2318 raw_tokens: list[Token], 2319 sql: str | None = None, 2320 ) -> list[exp.Expr | None]: 2321 self.reset() 2322 self.sql = sql or "" 2323 2324 total = len(raw_tokens) 2325 chunks: list[list[Token]] = [[]] 2326 2327 for i, token in enumerate(raw_tokens): 2328 if token.token_type == TokenType.SEMICOLON: 2329 if token.comments: 2330 chunks.append([token]) 2331 2332 if i < total - 1: 2333 chunks.append([]) 2334 else: 2335 chunks[-1].append(token) 2336 2337 self._chunks = chunks 2338 2339 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2340 2341 def _warn_unsupported(self) -> None: 2342 if self._tokens_size <= 1: 2343 return 2344 2345 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2346 # interested in emitting a warning for the one being currently processed. 2347 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2348 2349 logger.warning( 2350 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2351 ) 2352 2353 def _parse_command(self) -> exp.Command: 2354 self._warn_unsupported() 2355 comments = self._prev_comments 2356 return self.expression( 2357 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2358 comments=comments, 2359 ) 2360 2361 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2362 start = self._prev 2363 exists = self._parse_exists() if allow_exists else None 2364 2365 self._match(TokenType.ON) 2366 2367 materialized = self._match_text_seq("MATERIALIZED") 2368 kind = self._match_set(self.CREATABLES) and self._prev 2369 if not kind: 2370 return self._parse_as_command(start) 2371 2372 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2373 this = self._parse_user_defined_function(kind=kind.token_type) 2374 elif kind.token_type == TokenType.TABLE: 2375 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2376 elif kind.token_type == TokenType.COLUMN: 2377 this = self._parse_column() 2378 else: 2379 this = self._parse_table_parts(schema=True) 2380 2381 self._match(TokenType.IS) 2382 2383 return self.expression( 2384 exp.Comment( 2385 this=this, 2386 kind=kind.text, 2387 expression=self._parse_string(), 2388 exists=exists, 2389 materialized=materialized, 2390 ) 2391 ) 2392 2393 def _parse_to_table( 2394 self, 2395 ) -> exp.ToTableProperty: 2396 table = self._parse_table_parts(schema=True) 2397 return self.expression(exp.ToTableProperty(this=table)) 2398 2399 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2400 def _parse_ttl(self) -> exp.Expr: 2401 def _parse_ttl_action() -> exp.Expr | None: 2402 this = self._parse_bitwise() 2403 2404 if self._match_text_seq("DELETE"): 2405 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2406 if self._match_text_seq("RECOMPRESS"): 2407 return self.expression( 2408 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2409 ) 2410 if self._match_text_seq("TO", "DISK"): 2411 return self.expression( 2412 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2413 ) 2414 if self._match_text_seq("TO", "VOLUME"): 2415 return self.expression( 2416 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2417 ) 2418 2419 return this 2420 2421 expressions = self._parse_csv(_parse_ttl_action) 2422 where = self._parse_where() 2423 group = self._parse_group() 2424 2425 aggregates = None 2426 if group and self._match(TokenType.SET): 2427 aggregates = self._parse_csv(self._parse_set_item) 2428 2429 return self.expression( 2430 exp.MergeTreeTTL( 2431 expressions=expressions, where=where, group=group, aggregates=aggregates 2432 ) 2433 ) 2434 2435 def _parse_condition(self) -> exp.Expr | None: 2436 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2437 2438 def _parse_block(self) -> exp.Block: 2439 return self.expression( 2440 exp.Block( 2441 expressions=self._parse_batch_statements( 2442 parse_method=lambda self: self._parse_statement() 2443 ) 2444 ) 2445 ) 2446 2447 def _parse_whileblock(self) -> exp.WhileBlock: 2448 return self.expression( 2449 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2450 ) 2451 2452 def _parse_statement(self) -> exp.Expr | None: 2453 if not self._curr: 2454 return None 2455 2456 if self._match_set(self.STATEMENT_PARSERS): 2457 comments = self._prev_comments 2458 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2459 stmt.add_comments(comments, prepend=True) 2460 return stmt 2461 2462 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2463 return self._parse_command() 2464 2465 if self._match_text_seq("WHILE"): 2466 return self._parse_whileblock() 2467 2468 expression = self._parse_expression() 2469 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2470 2471 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2472 expression = self._parse_pipe_syntax_query(expression) 2473 2474 return self._parse_query_modifiers(expression) 2475 2476 def _parse_drop(self, exists: bool = False, kind: str | None = None) -> exp.Drop | exp.Command: 2477 start = self._prev 2478 2479 if not kind: 2480 temporary = self._match(TokenType.TEMPORARY) 2481 materialized = self._match_text_seq("MATERIALIZED") 2482 iceberg = self._match_text_seq("ICEBERG") 2483 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 2484 else: 2485 temporary = False 2486 materialized = False 2487 iceberg = False 2488 2489 if not kind or (iceberg and kind != "TABLE"): 2490 return self._parse_as_command(start) 2491 2492 concurrently = ( 2493 kind == "INDEX" 2494 and self._next.token_type in self.ID_VAR_TOKENS 2495 and self._match_text_seq("CONCURRENTLY") 2496 ) 2497 2498 if_exists = exists or self._parse_exists() 2499 2500 tables: exp.Expr | list[exp.Expr] | None 2501 if kind == "COLUMN": 2502 # A dropped column named after a keyword, like `any` or `charset`, is only a name 2503 name = ( 2504 None 2505 if self._curr.text.upper() in self.NO_PAREN_FUNCTION_PARSERS 2506 else self._parse_column() 2507 ) or self._parse_id_var() 2508 tables = ( 2509 self.expression(exp.Column(this=name)) if isinstance(name, exp.Identifier) else name 2510 ) 2511 elif kind in ("TABLE", "VIEW"): 2512 tables = self._parse_csv(lambda: self._parse_table_parts(schema=True)) 2513 else: 2514 tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2515 2516 cluster: exp.Expr | None = None 2517 if self._match(TokenType.ON): 2518 if kind in ("INDEX", "TRIGGER"): 2519 cluster = self.expression(exp.OnProperty(this=self._parse_table_parts())) 2520 else: 2521 cluster = self._parse_on_property() 2522 2523 if self._match(TokenType.L_PAREN, advance=False): 2524 expressions = self._parse_wrapped_csv(self._parse_types) 2525 else: 2526 expressions = None 2527 2528 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2529 2530 return self.expression( 2531 exp.Drop( 2532 exists=if_exists, 2533 tables=ensure_list(tables), 2534 expressions=expressions, 2535 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2536 temporary=temporary, 2537 materialized=materialized, 2538 cascade=cascade_or_restrict == "CASCADE", 2539 restrict=cascade_or_restrict == "RESTRICT", 2540 constraints=self._match_text_seq("CONSTRAINTS"), 2541 purge=self._match_text_seq("PURGE"), 2542 cluster=cluster, 2543 concurrently=concurrently, 2544 sync=self._match_text_seq("SYNC"), 2545 iceberg=iceberg, 2546 force=self._match_text_seq("FORCE"), 2547 ) 2548 ) 2549 2550 def _parse_exists(self, not_: bool = False) -> bool | None: 2551 if not_: 2552 return self._match_text_seq("IF", "NOT", "EXISTS") 2553 return self._match_text_seq("IF", "EXISTS") 2554 2555 def _parse_create(self) -> exp.Create | exp.Command: 2556 # Note: this can't be None because we've matched a statement parser 2557 start = self._prev 2558 2559 replace = ( 2560 start.token_type == TokenType.REPLACE 2561 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2562 or self._match_pair(TokenType.OR, TokenType.ALTER) 2563 ) 2564 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2565 2566 unique = self._match(TokenType.UNIQUE) 2567 2568 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2569 clustered = True 2570 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2571 "COLUMNSTORE" 2572 ): 2573 clustered = False 2574 else: 2575 clustered = None 2576 2577 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2578 self._advance() 2579 2580 properties = None 2581 create_token = self._match_set(self.CREATABLES) and self._prev 2582 2583 if not create_token: 2584 # exp.Properties.Location.POST_CREATE 2585 properties = self._parse_properties() 2586 create_token = self._match_set(self.CREATABLES) and self._prev 2587 2588 if not properties or not create_token: 2589 return self._parse_as_command(start) 2590 2591 create_token_type = t.cast(Token, create_token).token_type 2592 2593 concurrently = create_token_type == TokenType.INDEX and self._match_text_seq("CONCURRENTLY") 2594 exists = self._parse_exists(not_=True) 2595 this = None 2596 expression: exp.Expr | None = None 2597 indexes = None 2598 no_schema_binding = None 2599 begin = None 2600 clone = None 2601 2602 def extend_props(temp_props: exp.Properties | None) -> None: 2603 nonlocal properties 2604 if properties and temp_props: 2605 properties.expressions.extend(temp_props.expressions) 2606 elif temp_props: 2607 properties = temp_props 2608 2609 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2610 this = self._parse_user_defined_function(kind=create_token_type) 2611 2612 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2613 extend_props(self._parse_properties()) 2614 2615 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2616 2617 if ( 2618 not expression 2619 and create_token_type == TokenType.FUNCTION 2620 and isinstance(this, exp.UserDefinedFunction) 2621 and this.args.get("wrapped") 2622 ): 2623 pre_table_index = self._index 2624 is_table = self._match(TokenType.TABLE) 2625 2626 expression = self._parse_expression() 2627 overload_mode = bool( 2628 expression 2629 and self._curr.token_type == TokenType.COMMA 2630 and self._next.token_type == TokenType.L_PAREN 2631 ) 2632 if not overload_mode: 2633 self._retreat(pre_table_index) 2634 is_table = False 2635 expression = None 2636 else: 2637 is_table = False 2638 overload_mode = False 2639 2640 extend_props(self._parse_function_properties()) 2641 2642 if not expression: 2643 if self._match(TokenType.COMMAND): 2644 expression = self._parse_as_command(self._prev) 2645 else: 2646 begin = self._match(TokenType.BEGIN) 2647 return_ = self._match_text_seq("RETURN") 2648 2649 if self._match(TokenType.STRING, advance=False): 2650 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2651 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2652 expression = self._parse_string() 2653 extend_props(self._parse_properties()) 2654 else: 2655 expression = ( 2656 self._parse_user_defined_function_expression() 2657 if create_token_type == TokenType.FUNCTION 2658 else self._parse_block() 2659 ) 2660 2661 if return_: 2662 expression = self.expression(exp.Return(this=expression)) 2663 2664 if overload_mode and expression: 2665 expression = self._parse_macro_overloads( 2666 t.cast(exp.UserDefinedFunction, this), expression, is_table 2667 ) 2668 elif create_token_type == TokenType.INDEX: 2669 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2670 if not self._match(TokenType.ON): 2671 index = self._parse_id_var() 2672 anonymous = False 2673 else: 2674 index = None 2675 anonymous = True 2676 2677 this = self._parse_index(index=index, anonymous=anonymous) 2678 elif ( 2679 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2680 ) or create_token_type == TokenType.TRIGGER: 2681 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2682 create_token = self._prev 2683 2684 trigger_name = self._parse_id_var() 2685 if not trigger_name: 2686 return self._parse_as_command(start) 2687 2688 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2689 timing = timing_var.this if timing_var else None 2690 if not timing: 2691 return self._parse_as_command(start) 2692 2693 events = self._parse_trigger_events() 2694 if not self._match(TokenType.ON): 2695 self.raise_error("Expected ON in trigger definition") 2696 2697 table = self._parse_table_parts() 2698 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2699 deferrable, initially = self._parse_trigger_deferrable() 2700 referencing = self._parse_trigger_referencing() 2701 for_each = self._parse_trigger_for_each() 2702 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2703 self._parse_disjunction, optional=True 2704 ) 2705 execute = self._parse_trigger_execute() 2706 2707 if execute is None: 2708 return self._parse_as_command(start) 2709 2710 trigger_props = self.expression( 2711 exp.TriggerProperties( 2712 table=table, 2713 timing=timing, 2714 events=events, 2715 execute=execute, 2716 constraint=is_constraint, 2717 referenced_table=referenced_table, 2718 deferrable=deferrable, 2719 initially=initially, 2720 referencing=referencing, 2721 for_each=for_each, 2722 when=when, 2723 ) 2724 ) 2725 2726 this = trigger_name 2727 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2728 elif create_token_type == TokenType.TYPE: 2729 this = self._parse_table_parts(schema=True) 2730 if not this or not self._match(TokenType.ALIAS): 2731 return self._parse_as_command(start) 2732 2733 if self._match(TokenType.ENUM): 2734 expression = exp.DataType( 2735 this=exp.DType.ENUM, 2736 expressions=self._parse_wrapped_csv(self._parse_string), 2737 ) 2738 elif self._match(TokenType.L_PAREN, advance=False): 2739 expression = self._parse_schema() 2740 else: 2741 return self._parse_as_command(start) 2742 elif create_token_type in self.DB_CREATABLES: 2743 table_parts = self._parse_table_parts( 2744 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2745 ) 2746 2747 # exp.Properties.Location.POST_NAME 2748 self._match(TokenType.COMMA) 2749 extend_props(self._parse_properties(before=True)) 2750 2751 this = self._parse_schema(this=table_parts) 2752 2753 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2754 extend_props(self._parse_properties()) 2755 2756 has_alias = self._match(TokenType.ALIAS) 2757 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2758 # exp.Properties.Location.POST_ALIAS 2759 extend_props(self._parse_properties()) 2760 2761 if create_token_type == TokenType.SEQUENCE: 2762 expression = self._parse_types() 2763 props = self._parse_properties() 2764 if props: 2765 sequence_props = exp.SequenceProperties() 2766 options = [] 2767 for prop in props: 2768 if isinstance(prop, exp.SequenceProperties): 2769 for arg, value in prop.args.items(): 2770 if arg == "options": 2771 options.extend(value) 2772 else: 2773 sequence_props.set(arg, value) 2774 prop.pop() 2775 2776 if options: 2777 sequence_props.set("options", options) 2778 2779 props.append("expressions", sequence_props) 2780 extend_props(props) 2781 else: 2782 expression = self._parse_ddl_select() 2783 2784 # Some dialects also support using a table as an alias instead of a SELECT. 2785 # Here we fallback to this as an alternative. 2786 if not expression and has_alias: 2787 expression = self._try_parse(self._parse_table_parts) 2788 2789 if create_token_type == TokenType.TABLE: 2790 # exp.Properties.Location.POST_EXPRESSION 2791 extend_props(self._parse_properties()) 2792 2793 indexes = [] 2794 while True: 2795 index = self._parse_index() 2796 2797 # exp.Properties.Location.POST_INDEX 2798 extend_props(self._parse_properties()) 2799 if not index: 2800 break 2801 else: 2802 self._match(TokenType.COMMA) 2803 indexes.append(index) 2804 elif create_token_type == TokenType.VIEW: 2805 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2806 no_schema_binding = True 2807 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2808 extend_props(self._parse_properties()) 2809 2810 shallow = self._match_text_seq("SHALLOW") 2811 2812 if self._match_texts(self.CLONE_KEYWORDS): 2813 copy = self._prev.text.lower() == "copy" 2814 clone = self.expression( 2815 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2816 ) 2817 2818 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2819 return self._parse_as_command(start) 2820 2821 create_kind_text = create_token.text.upper() 2822 return self.expression( 2823 exp.Create( 2824 this=this, 2825 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2826 replace=replace, 2827 refresh=refresh, 2828 unique=unique, 2829 expression=expression, 2830 exists=exists, 2831 properties=properties, 2832 indexes=indexes, 2833 no_schema_binding=no_schema_binding, 2834 begin=begin, 2835 clone=clone, 2836 concurrently=concurrently, 2837 clustered=clustered, 2838 ) 2839 ) 2840 2841 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2842 seq = exp.SequenceProperties() 2843 2844 options = [] 2845 index = self._index 2846 2847 while self._curr: 2848 self._match(TokenType.COMMA) 2849 if self._match_text_seq("INCREMENT"): 2850 self._match_text_seq("BY") 2851 self._match_text_seq("=") 2852 seq.set("increment", self._parse_term()) 2853 elif self._match_text_seq("MINVALUE"): 2854 seq.set("minvalue", self._parse_term()) 2855 elif self._match_text_seq("MAXVALUE"): 2856 seq.set("maxvalue", self._parse_term()) 2857 elif self._match_text_seq("START"): 2858 self._match_text_seq("WITH") 2859 self._match_text_seq("=") 2860 seq.set("start", self._parse_term()) 2861 elif self._match_text_seq("CACHE"): 2862 # T-SQL allows empty CACHE which is initialized dynamically 2863 seq.set("cache", self._parse_number() or True) 2864 elif self._match_text_seq("OWNED", "BY"): 2865 # "OWNED BY NONE" is the default 2866 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2867 else: 2868 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2869 if opt: 2870 options.append(opt) 2871 else: 2872 break 2873 2874 seq.set("options", options if options else None) 2875 return None if self._index == index else seq 2876 2877 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2878 events = [] 2879 2880 while True: 2881 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2882 2883 if not event_type: 2884 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2885 2886 columns = ( 2887 self._parse_csv(self._parse_column) 2888 if event_type == "UPDATE" and self._match_text_seq("OF") 2889 else None 2890 ) 2891 2892 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2893 2894 if not self._match(TokenType.OR): 2895 break 2896 2897 return events 2898 2899 def _parse_trigger_deferrable( 2900 self, 2901 ) -> tuple[str | None, str | None]: 2902 deferrable_var = self._parse_var_from_options( 2903 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2904 ) 2905 deferrable = deferrable_var.this if deferrable_var else None 2906 2907 initially = None 2908 if deferrable and self._match_text_seq("INITIALLY"): 2909 initially = ( 2910 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2911 ) 2912 2913 return deferrable, initially 2914 2915 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2916 if not self._match_text_seq(keyword): 2917 return None 2918 if not self._match_text_seq("TABLE"): 2919 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2920 self._match_text_seq("AS") 2921 return self._parse_id_var() 2922 2923 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2924 if not self._match_text_seq("REFERENCING"): 2925 return None 2926 2927 old_alias = None 2928 new_alias = None 2929 2930 while True: 2931 if alias := self._parse_trigger_referencing_clause("OLD"): 2932 if old_alias is not None: 2933 self.raise_error("Duplicate OLD clause in REFERENCING") 2934 old_alias = alias 2935 elif alias := self._parse_trigger_referencing_clause("NEW"): 2936 if new_alias is not None: 2937 self.raise_error("Duplicate NEW clause in REFERENCING") 2938 new_alias = alias 2939 else: 2940 break 2941 2942 if old_alias is None and new_alias is None: 2943 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2944 2945 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2946 2947 def _parse_trigger_for_each(self) -> str | None: 2948 if not self._match_text_seq("FOR", "EACH"): 2949 return None 2950 2951 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2952 2953 def _parse_trigger_execute(self) -> exp.Expr | None: 2954 if not self._match(TokenType.EXECUTE): 2955 return None 2956 2957 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2958 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2959 2960 func_call = self._parse_column() 2961 return self.expression(exp.TriggerExecute(this=func_call)) 2962 2963 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2964 # only used for teradata currently 2965 self._match(TokenType.COMMA) 2966 2967 kwargs = { 2968 "no": self._match_text_seq("NO"), 2969 "dual": self._match_text_seq("DUAL"), 2970 "before": self._match_text_seq("BEFORE"), 2971 "default": self._match_text_seq("DEFAULT"), 2972 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2973 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2974 "after": self._match_text_seq("AFTER"), 2975 "minimum": self._match_texts(("MIN", "MINIMUM")), 2976 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2977 } 2978 2979 if self._match_texts(self.PROPERTY_PARSERS): 2980 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2981 try: 2982 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2983 except TypeError: 2984 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2985 2986 if self._match_pair(TokenType.CHAR, TokenType.SET): 2987 return self._parse_character_set(default=bool(kwargs["default"])) 2988 2989 return None 2990 2991 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2992 return self._parse_wrapped_csv(self._parse_property) 2993 2994 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2995 if self._match_texts(self.PROPERTY_PARSERS): 2996 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2997 2998 if self._match_pair(TokenType.CHAR, TokenType.SET): 2999 return self._parse_character_set() 3000 3001 if self._match(TokenType.DEFAULT): 3002 if self._match_texts(self.PROPERTY_PARSERS): 3003 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 3004 3005 if self._match_pair(TokenType.CHAR, TokenType.SET): 3006 return self._parse_character_set(default=True) 3007 3008 if self._match_text_seq("COMPOUND", "SORTKEY"): 3009 return self._parse_sortkey(compound=True) 3010 3011 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 3012 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 3013 3014 if self._match_text_seq("NOT", "DETERMINISTIC"): 3015 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3016 3017 index = self._index 3018 3019 seq_props = self._parse_sequence_properties() 3020 if seq_props: 3021 return seq_props 3022 3023 self._retreat(index) 3024 return self._parse_key_value_property() 3025 3026 def _parse_key_value_property( 3027 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 3028 ) -> exp.Property | None: 3029 index = self._index 3030 key = self._parse_column() 3031 3032 if not self._match(TokenType.EQ): 3033 self._retreat(index) 3034 return None 3035 3036 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 3037 if isinstance(key, exp.Column): 3038 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 3039 3040 value = ( 3041 parse_value() 3042 if parse_value 3043 else self._parse_bitwise() or self._parse_var(any_token=True) 3044 ) 3045 3046 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 3047 if isinstance(value, exp.Column): 3048 value = exp.var(value.name) 3049 3050 return self.expression(exp.Property(this=key, value=value)) 3051 3052 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 3053 if self._match_text_seq("BY"): 3054 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 3055 3056 self._match(TokenType.ALIAS) 3057 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 3058 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 3059 3060 return self.expression( 3061 exp.FileFormatProperty( 3062 this=( 3063 self.expression( 3064 exp.InputOutputFormat( 3065 input_format=input_format, output_format=output_format 3066 ) 3067 ) 3068 if input_format or output_format 3069 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 3070 ), 3071 hive_format=True, 3072 ) 3073 ) 3074 3075 def _parse_unquoted_field(self) -> exp.Expr | None: 3076 field = self._parse_field() 3077 if isinstance(field, exp.Identifier) and not field.quoted: 3078 field = exp.var(field) 3079 3080 return field 3081 3082 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 3083 self._match(TokenType.EQ) 3084 self._match(TokenType.ALIAS) 3085 3086 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 3087 3088 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 3089 properties = [] 3090 while True: 3091 if before: 3092 prop = self._parse_property_before() 3093 else: 3094 prop = self._parse_property() 3095 if not prop: 3096 break 3097 for p in ensure_list(prop): 3098 properties.append(p) 3099 3100 self._match(TokenType.COMMA, expression=properties[-1]) 3101 3102 if properties: 3103 return self.expression(exp.Properties(expressions=properties)) 3104 3105 return None 3106 3107 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3108 return self.expression( 3109 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3110 ) 3111 3112 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3113 return self.expression( 3114 exp.SqlSecurityProperty( 3115 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3116 ) 3117 ) 3118 3119 def _parse_settings_property(self) -> exp.SettingsProperty: 3120 return self.expression( 3121 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3122 ) 3123 3124 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3125 if not self._match_text_seq("ON", "NULL", "INPUT"): 3126 self._retreat(self._index - 1) 3127 return None 3128 3129 return self.expression(exp.CalledOnNullInputProperty()) 3130 3131 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3132 if self._index >= 2: 3133 pre_volatile_token = self._tokens[self._index - 2] 3134 else: 3135 pre_volatile_token = None 3136 3137 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3138 return exp.VolatileProperty() 3139 3140 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3141 3142 def _parse_retention_period(self) -> exp.Var: 3143 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3144 number = self._parse_number() 3145 number_str = f"{number} " if number else "" 3146 unit = self._parse_var(any_token=True) 3147 return exp.var(f"{number_str}{unit}") 3148 3149 def _parse_system_versioning_property( 3150 self, with_: bool = False 3151 ) -> exp.WithSystemVersioningProperty: 3152 self._match(TokenType.EQ) 3153 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3154 3155 if self._match_text_seq("OFF"): 3156 prop.set("on", False) 3157 return prop 3158 3159 self._match(TokenType.ON) 3160 if self._match(TokenType.L_PAREN): 3161 while self._curr and not self._match(TokenType.R_PAREN): 3162 if self._match_text_seq("HISTORY_TABLE", "="): 3163 prop.set("this", self._parse_table_parts()) 3164 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3165 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3166 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3167 prop.set("retention_period", self._parse_retention_period()) 3168 3169 self._match(TokenType.COMMA) 3170 3171 return prop 3172 3173 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3174 self._match(TokenType.EQ) 3175 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3176 prop = self.expression(exp.DataDeletionProperty(on=on)) 3177 3178 if self._match(TokenType.L_PAREN): 3179 while self._curr and not self._match(TokenType.R_PAREN): 3180 if self._match_text_seq("FILTER_COLUMN", "="): 3181 prop.set("filter_column", self._parse_column()) 3182 elif self._match_text_seq("RETENTION_PERIOD", "="): 3183 prop.set("retention_period", self._parse_retention_period()) 3184 3185 self._match(TokenType.COMMA) 3186 3187 return prop 3188 3189 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3190 kind = "HASH" 3191 expressions: list[exp.Expr] | None = None 3192 if self._match_text_seq("BY", "HASH"): 3193 expressions = self._parse_wrapped_csv(self._parse_id_var) 3194 elif self._match_text_seq("BY", "RANDOM"): 3195 kind = "RANDOM" 3196 3197 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3198 buckets: exp.Expr | None = None 3199 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3200 buckets = self._parse_number() 3201 3202 return self.expression( 3203 exp.DistributedByProperty( 3204 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3205 ) 3206 ) 3207 3208 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3209 self._match_text_seq("KEY") 3210 expressions = self._parse_wrapped_id_vars() 3211 return self.expression(expr_type(expressions=expressions)) 3212 3213 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3214 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3215 prop = self._parse_system_versioning_property(with_=True) 3216 self._match_r_paren() 3217 return prop 3218 3219 if self._match(TokenType.L_PAREN, advance=False): 3220 result: list[exp.Expr] = [] 3221 for i in self._parse_wrapped_properties(): 3222 result.extend(i) if isinstance(i, list) else result.append(i) 3223 return result 3224 3225 if self._match_text_seq("JOURNAL"): 3226 return self._parse_withjournaltable() 3227 3228 if self._match_texts(self.VIEW_ATTRIBUTES): 3229 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3230 3231 if self._match_text_seq("DATA"): 3232 return self._parse_withdata(no=False) 3233 elif self._match_text_seq("NO", "DATA"): 3234 return self._parse_withdata(no=True) 3235 3236 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3237 return self._parse_serde_properties(with_=True) 3238 3239 if self._match(TokenType.SCHEMA): 3240 return self.expression( 3241 exp.WithSchemaBindingProperty( 3242 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3243 ) 3244 ) 3245 3246 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3247 return self.expression( 3248 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3249 ) 3250 3251 if not self._next: 3252 return None 3253 3254 return self._parse_withisolatedloading() 3255 3256 def _parse_procedure_option(self) -> exp.Expr | None: 3257 if self._match_text_seq("EXECUTE", "AS"): 3258 return self.expression( 3259 exp.ExecuteAsProperty( 3260 this=self._parse_var_from_options( 3261 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3262 ) 3263 or self._parse_string() 3264 ) 3265 ) 3266 3267 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3268 3269 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3270 def _parse_definer(self) -> exp.DefinerProperty | None: 3271 self._match(TokenType.EQ) 3272 3273 user = self._parse_id_var() 3274 self._match(TokenType.PARAMETER) 3275 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3276 3277 if not user or not host: 3278 return None 3279 3280 return exp.DefinerProperty(this=f"{user}@{host}") 3281 3282 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3283 self._match(TokenType.TABLE) 3284 self._match(TokenType.EQ) 3285 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3286 3287 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3288 return self.expression(exp.LogProperty(no=no)) 3289 3290 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3291 return self.expression(exp.JournalProperty(**kwargs)) 3292 3293 def _parse_checksum(self) -> exp.ChecksumProperty: 3294 self._match(TokenType.EQ) 3295 3296 on = None 3297 if self._match(TokenType.ON): 3298 on = True 3299 elif self._match_text_seq("OFF"): 3300 on = False 3301 3302 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3303 3304 def _parse_cluster(self) -> exp.Cluster: 3305 self._match(TokenType.CLUSTER_BY) 3306 return self.expression( 3307 exp.Cluster( 3308 expressions=self._parse_csv(self._parse_column), 3309 ) 3310 ) 3311 3312 def _parse_cluster_property(self) -> exp.ClusterProperty: 3313 return self.expression( 3314 exp.ClusterProperty( 3315 expressions=self._parse_wrapped_csv(self._parse_column), 3316 ) 3317 ) 3318 3319 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3320 self._match_text_seq("BY") 3321 3322 self._match_l_paren() 3323 expressions = self._parse_csv(self._parse_column) 3324 self._match_r_paren() 3325 3326 if self._match_text_seq("SORTED", "BY"): 3327 self._match_l_paren() 3328 sorted_by = self._parse_csv(self._parse_ordered) 3329 self._match_r_paren() 3330 else: 3331 sorted_by = None 3332 3333 self._match(TokenType.INTO) 3334 buckets = self._parse_number() 3335 self._match_text_seq("BUCKETS") 3336 3337 return self.expression( 3338 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3339 ) 3340 3341 def _parse_copy_property(self) -> exp.Property | None: 3342 if not self._match_text_seq("GRANTS"): 3343 self._retreat(self._index - 1) 3344 return None 3345 3346 return self.expression(exp.CopyGrantsProperty()) 3347 3348 def _parse_freespace(self) -> exp.FreespaceProperty: 3349 self._match(TokenType.EQ) 3350 return self.expression( 3351 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3352 ) 3353 3354 def _parse_mergeblockratio( 3355 self, no: bool = False, default: bool = False 3356 ) -> exp.MergeBlockRatioProperty: 3357 if self._match(TokenType.EQ): 3358 return self.expression( 3359 exp.MergeBlockRatioProperty( 3360 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3361 ) 3362 ) 3363 3364 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3365 3366 def _parse_datablocksize( 3367 self, 3368 default: bool | None = None, 3369 minimum: bool | None = None, 3370 maximum: bool | None = None, 3371 ) -> exp.DataBlocksizeProperty: 3372 self._match(TokenType.EQ) 3373 size = self._parse_number() 3374 3375 units = None 3376 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3377 units = self._prev.text 3378 3379 return self.expression( 3380 exp.DataBlocksizeProperty( 3381 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3382 ) 3383 ) 3384 3385 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3386 self._match(TokenType.EQ) 3387 always = self._match_text_seq("ALWAYS") 3388 manual = self._match_text_seq("MANUAL") 3389 never = self._match_text_seq("NEVER") 3390 default = self._match_text_seq("DEFAULT") 3391 3392 autotemp = None 3393 if self._match_text_seq("AUTOTEMP"): 3394 autotemp = self._parse_schema() 3395 3396 return self.expression( 3397 exp.BlockCompressionProperty( 3398 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3399 ) 3400 ) 3401 3402 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3403 index = self._index 3404 no = self._match_text_seq("NO") 3405 concurrent = self._match_text_seq("CONCURRENT") 3406 3407 if not self._match_text_seq("ISOLATED", "LOADING"): 3408 self._retreat(index) 3409 return None 3410 3411 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3412 return self.expression( 3413 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3414 ) 3415 3416 def _parse_locking(self) -> exp.LockingProperty: 3417 if self._match(TokenType.TABLE): 3418 kind = "TABLE" 3419 elif self._match(TokenType.VIEW): 3420 kind = "VIEW" 3421 elif self._match(TokenType.ROW): 3422 kind = "ROW" 3423 elif self._match_text_seq("DATABASE"): 3424 kind = "DATABASE" 3425 else: 3426 kind = None 3427 3428 if kind in ("DATABASE", "TABLE", "VIEW"): 3429 this = self._parse_table_parts() 3430 else: 3431 this = None 3432 3433 if self._match(TokenType.FOR): 3434 for_or_in = "FOR" 3435 elif self._match(TokenType.IN): 3436 for_or_in = "IN" 3437 else: 3438 for_or_in = None 3439 3440 if self._match_text_seq("ACCESS"): 3441 lock_type = "ACCESS" 3442 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3443 lock_type = "EXCLUSIVE" 3444 elif self._match_text_seq("SHARE"): 3445 lock_type = "SHARE" 3446 elif self._match_text_seq("READ"): 3447 lock_type = "READ" 3448 elif self._match_text_seq("WRITE"): 3449 lock_type = "WRITE" 3450 elif self._match_text_seq("CHECKSUM"): 3451 lock_type = "CHECKSUM" 3452 else: 3453 lock_type = None 3454 3455 override = self._match_text_seq("OVERRIDE") 3456 3457 return self.expression( 3458 exp.LockingProperty( 3459 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3460 ) 3461 ) 3462 3463 def _parse_partition_by(self) -> list[exp.Expr]: 3464 if not self._match(TokenType.PARTITION_BY): 3465 return [] 3466 3467 if self._next.token_type in self.ID_VAR_TOKENS and self._match_text_seq("EXCLUDING"): 3468 return [ 3469 self.expression( 3470 exp.PartitionByExcluding(expressions=self._parse_csv(self._parse_disjunction)) 3471 ) 3472 ] 3473 3474 return self._parse_csv(self._parse_disjunction) 3475 3476 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3477 def _parse_partition_bound_expr() -> exp.Expr | None: 3478 if self._match_text_seq("MINVALUE"): 3479 return exp.var("MINVALUE") 3480 if self._match_text_seq("MAXVALUE"): 3481 return exp.var("MAXVALUE") 3482 return self._parse_bitwise() 3483 3484 this: exp.Expr | list[exp.Expr] | None = None 3485 expression = None 3486 from_expressions = None 3487 to_expressions = None 3488 3489 if self._match(TokenType.IN): 3490 this = self._parse_wrapped_csv(self._parse_bitwise) 3491 elif self._match(TokenType.FROM): 3492 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3493 self._match_text_seq("TO") 3494 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3495 elif self._match_text_seq("WITH", "(", "MODULUS"): 3496 this = self._parse_number() 3497 self._match_text_seq(",", "REMAINDER") 3498 expression = self._parse_number() 3499 self._match_r_paren() 3500 else: 3501 self.raise_error("Failed to parse partition bound spec.") 3502 3503 return self.expression( 3504 exp.PartitionBoundSpec( 3505 this=this, 3506 expression=expression, 3507 from_expressions=from_expressions, 3508 to_expressions=to_expressions, 3509 ) 3510 ) 3511 3512 # https://www.postgresql.org/docs/current/sql-createtable.html 3513 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3514 if not self._match_text_seq("OF"): 3515 self._retreat(self._index - 1) 3516 return None 3517 3518 this = self._parse_table(schema=True) 3519 3520 if self._match(TokenType.DEFAULT): 3521 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3522 elif self._match_text_seq("FOR", "VALUES"): 3523 expression = self._parse_partition_bound_spec() 3524 else: 3525 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3526 3527 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3528 3529 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3530 self._match(TokenType.EQ) 3531 return self.expression( 3532 exp.PartitionedByProperty( 3533 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3534 ) 3535 ) 3536 3537 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3538 if self._match_text_seq("AND", "STATISTICS"): 3539 statistics = True 3540 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3541 statistics = False 3542 else: 3543 statistics = None 3544 3545 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3546 3547 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3548 if self._match_text_seq("SQL"): 3549 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3550 return None 3551 3552 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3553 if self._match_text_seq("SQL", "DATA"): 3554 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3555 return None 3556 3557 def _parse_no_property(self) -> exp.Expr | None: 3558 if self._match_text_seq("PRIMARY", "INDEX"): 3559 return exp.NoPrimaryIndexProperty() 3560 if self._match_text_seq("SQL"): 3561 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3562 return None 3563 3564 def _parse_on_property(self) -> exp.Expr | None: 3565 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3566 return exp.OnCommitProperty() 3567 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3568 return exp.OnCommitProperty(delete=True) 3569 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3570 3571 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3572 if self._match_text_seq("SQL", "DATA"): 3573 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3574 return None 3575 3576 def _parse_distkey(self) -> exp.DistKeyProperty: 3577 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3578 3579 def _parse_create_like(self) -> exp.LikeProperty | None: 3580 table = self._parse_table(schema=True) 3581 3582 options = [] 3583 while self._match_texts(("INCLUDING", "EXCLUDING")): 3584 this = self._prev.text.upper() 3585 3586 id_var = self._parse_id_var() 3587 if not id_var: 3588 return None 3589 3590 options.append( 3591 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3592 ) 3593 3594 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3595 3596 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3597 return self.expression( 3598 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3599 ) 3600 3601 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3602 self._match(TokenType.EQ) 3603 return self.expression( 3604 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3605 ) 3606 3607 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3608 self._match_text_seq("WITH", "CONNECTION") 3609 return self.expression( 3610 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3611 ) 3612 3613 def _parse_returns(self) -> exp.ReturnsProperty: 3614 value: exp.Expr | None 3615 null = None 3616 is_table = self._match(TokenType.TABLE) 3617 3618 if is_table: 3619 if self._match(TokenType.LT): 3620 value = self.expression( 3621 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3622 ) 3623 if not self._match(TokenType.GT): 3624 self.raise_error("Expecting >") 3625 else: 3626 value = self._parse_schema(exp.var("TABLE")) 3627 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3628 null = True 3629 value = None 3630 else: 3631 value = self._parse_types() 3632 3633 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3634 3635 def _parse_describe(self) -> exp.Describe: 3636 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3637 style: str | None = ( 3638 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3639 ) 3640 if self._match(TokenType.DOT): 3641 style = None 3642 self._retreat(self._index - 2) 3643 3644 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3645 3646 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3647 this = self._parse_statement() 3648 else: 3649 this = self._parse_table(schema=True) 3650 3651 properties = self._parse_properties() 3652 expressions = properties.expressions if properties else None 3653 partition = self._parse_partition() 3654 return self.expression( 3655 exp.Describe( 3656 this=this, 3657 style=style, 3658 kind=kind, 3659 expressions=expressions, 3660 partition=partition, 3661 format=format, 3662 as_json=self._match_text_seq("AS", "JSON"), 3663 ) 3664 ) 3665 3666 def _parse_multitable_inserts( 3667 self, comments: list[str] | None, kind: str | None = None 3668 ) -> exp.MultitableInserts: 3669 kind = kind or self._prev.text.upper() 3670 expressions = [] 3671 3672 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3673 if self._match(TokenType.WHEN): 3674 expression = self._parse_disjunction() 3675 self._match(TokenType.THEN) 3676 else: 3677 expression = None 3678 3679 else_ = self._match(TokenType.ELSE) 3680 3681 if not self._match(TokenType.INTO): 3682 return None 3683 3684 return self.expression( 3685 exp.ConditionalInsert( 3686 this=self.expression( 3687 exp.Insert( 3688 this=self._parse_table(schema=True), 3689 expression=self._parse_derived_table_values(), 3690 ) 3691 ), 3692 expression=expression, 3693 else_=else_, 3694 ) 3695 ) 3696 3697 expression = parse_conditional_insert() 3698 while expression is not None: 3699 expressions.append(expression) 3700 expression = parse_conditional_insert() 3701 3702 return self.expression( 3703 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3704 comments=comments, 3705 ) 3706 3707 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3708 comments: list[str] = [] 3709 hint = self._parse_hint() 3710 overwrite = self._match(TokenType.OVERWRITE) 3711 ignore = self._match(TokenType.IGNORE) 3712 local = self._match_text_seq("LOCAL") 3713 alternative = None 3714 is_function = None 3715 3716 if self._match_text_seq("DIRECTORY"): 3717 this: exp.Expr | None = self.expression( 3718 exp.Directory( 3719 this=self._parse_var_or_string(), 3720 local=local, 3721 row_format=self._parse_row_format(match_row=True), 3722 ) 3723 ) 3724 else: 3725 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3726 comments += ensure_list(self._prev_comments) 3727 return self._parse_multitable_inserts(comments) 3728 3729 # Oracle's conditional form defaults to ALL when the keyword is omitted 3730 if self._match(TokenType.WHEN, advance=False): 3731 return self._parse_multitable_inserts(comments, kind="ALL") 3732 3733 if self._match(TokenType.OR): 3734 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3735 3736 self._match(TokenType.INTO) 3737 comments += ensure_list(self._prev_comments) 3738 self._match(TokenType.TABLE) 3739 is_function = self._match(TokenType.FUNCTION) 3740 3741 this = self._parse_function() if is_function else self._parse_insert_table() 3742 3743 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3744 set_values = None 3745 if self._match(TokenType.SET): 3746 columns = [] 3747 values = [] 3748 3749 def _parse_set_assignment() -> exp.Expr | None: 3750 target = self._parse_column() 3751 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3752 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3753 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3754 else: 3755 value = self._parse_disjunction() 3756 3757 if value: 3758 columns.append(target.this) 3759 values.append(value) 3760 return value 3761 3762 self.raise_error("Expected column assignment in INSERT ... SET") 3763 return None 3764 3765 self._parse_csv(_parse_set_assignment) 3766 3767 this = self.expression(exp.Schema(this=this, expressions=columns)) 3768 set_values = self.expression( 3769 exp.Values( 3770 expressions=[exp.Tuple(expressions=values)], 3771 alias=self._parse_table_alias(), 3772 ) 3773 ) 3774 3775 returning = self._parse_returning() # TSQL allows RETURNING before source 3776 3777 stored = self._match_text_seq("STORED") and self._parse_stored() 3778 by_name = self._match_text_seq("BY", "NAME") 3779 exists = self._parse_exists() 3780 replace_where = None 3781 replace_using = None 3782 3783 if self._match(TokenType.REPLACE): 3784 if self._match(TokenType.WHERE): 3785 replace_where = self._parse_disjunction() 3786 elif self._match(TokenType.USING): 3787 replace_using = self._parse_using_identifiers() 3788 3789 return self.expression( 3790 exp.Insert( 3791 hint=hint, 3792 is_function=is_function, 3793 this=this, 3794 stored=stored, 3795 by_name=by_name, 3796 exists=exists, 3797 where=replace_where, 3798 using=replace_using, 3799 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3800 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3801 default=self._match_text_seq("DEFAULT", "VALUES"), 3802 expression=set_values 3803 or self._parse_derived_table_values(allow_value_synonym=True) 3804 or self._parse_ddl_select(), 3805 conflict=self._parse_on_conflict(), 3806 returning=returning or self._parse_returning(), 3807 overwrite=overwrite, 3808 alternative=alternative, 3809 ignore=ignore, 3810 source=self._match(TokenType.TABLE) and self._parse_table(), 3811 ), 3812 comments=comments, 3813 ) 3814 3815 def _parse_insert_table(self) -> exp.Expr | None: 3816 this = self._parse_table(schema=True, parse_partition=True) 3817 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS): 3818 # The column list after the alias belongs to the INSERT: INSERT INTO t AS s (a, b) 3819 alias = self._parse_id_var() 3820 if alias: 3821 this.set( 3822 "alias", 3823 self.expression(exp.TableAlias(this=alias), comments=alias.pop_comments()), 3824 ) 3825 return self._parse_schema(this=this) 3826 return this 3827 3828 def _parse_kill(self) -> exp.Kill: 3829 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3830 3831 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3832 3833 def _parse_on_conflict(self) -> exp.OnConflict | None: 3834 conflict = self._match_text_seq("ON", "CONFLICT") 3835 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3836 3837 if not conflict and not duplicate: 3838 return None 3839 3840 conflict_keys = None 3841 constraint = None 3842 3843 if conflict: 3844 if self._match_text_seq("ON", "CONSTRAINT"): 3845 constraint = self._parse_id_var() 3846 elif self._match(TokenType.L_PAREN): 3847 conflict_keys = self._parse_csv(self._parse_indexed_column) 3848 self._match_r_paren() 3849 3850 index_predicate = self._parse_where() 3851 3852 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3853 if self._prev.token_type == TokenType.UPDATE: 3854 self._match(TokenType.SET) 3855 expressions = self._parse_csv(self._parse_update_assignment) 3856 else: 3857 expressions = None 3858 3859 return self.expression( 3860 exp.OnConflict( 3861 duplicate=duplicate, 3862 expressions=expressions, 3863 action=action, 3864 conflict_keys=conflict_keys, 3865 index_predicate=index_predicate, 3866 constraint=constraint, 3867 where=self._parse_where(), 3868 ) 3869 ) 3870 3871 def _parse_returning(self) -> exp.Returning | None: 3872 if not self._match(TokenType.RETURNING): 3873 return None 3874 return self.expression( 3875 exp.Returning( 3876 expressions=self._parse_csv(self._parse_expression), 3877 into=self._match(TokenType.INTO) and self._parse_table_part(), 3878 ) 3879 ) 3880 3881 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3882 if not self._match(TokenType.FORMAT): 3883 return None 3884 return self._parse_row_format() 3885 3886 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3887 index = self._index 3888 with_ = with_ or self._match_text_seq("WITH") 3889 3890 if not self._match(TokenType.SERDE_PROPERTIES): 3891 self._retreat(index) 3892 return None 3893 return self.expression( 3894 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3895 ) 3896 3897 def _parse_row_format( 3898 self, match_row: bool = False 3899 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3900 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3901 return None 3902 3903 if self._match_text_seq("SERDE"): 3904 this = self._parse_string() 3905 3906 serde_properties = self._parse_serde_properties() 3907 3908 return self.expression( 3909 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3910 ) 3911 3912 self._match_text_seq("DELIMITED") 3913 3914 kwargs = {} 3915 3916 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3917 kwargs["fields"] = self._parse_string() 3918 if self._match_text_seq("ESCAPED", "BY"): 3919 kwargs["escaped"] = self._parse_string() 3920 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3921 kwargs["collection_items"] = self._parse_string() 3922 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3923 kwargs["map_keys"] = self._parse_string() 3924 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3925 kwargs["lines"] = self._parse_string() 3926 if self._match_text_seq("NULL", "DEFINED", "AS"): 3927 kwargs["null"] = self._parse_string() 3928 3929 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3930 3931 def _parse_load(self) -> exp.LoadData | exp.Command: 3932 if self._match_text_seq("DATA"): 3933 local = self._match_text_seq("LOCAL") 3934 self._match_text_seq("INPATH") 3935 inpath = self._parse_string() 3936 overwrite = self._match(TokenType.OVERWRITE) 3937 temp: bool | None = None 3938 if self._match(TokenType.INTO): 3939 temp = self._match(TokenType.TEMPORARY) 3940 self._match(TokenType.TABLE) 3941 3942 return self.expression( 3943 exp.LoadData( 3944 this=self._parse_table(schema=True), 3945 local=local, 3946 overwrite=overwrite, 3947 temp=temp, 3948 inpath=inpath, 3949 files=self._match_text_seq("FROM", "FILES") 3950 and exp.Properties(expressions=self._parse_wrapped_properties()), 3951 partition=self._parse_partition(), 3952 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3953 serde=self._match_text_seq("SERDE") and self._parse_string(), 3954 ) 3955 ) 3956 return self._parse_as_command(self._prev) 3957 3958 def _parse_delete(self) -> exp.Delete: 3959 hint = self._parse_hint() 3960 3961 # This handles MySQL's "Multiple-Table Syntax" 3962 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3963 tables = None 3964 if not self._match(TokenType.FROM, advance=False): 3965 tables = self._parse_csv(self._parse_table) or None 3966 3967 returning = self._parse_returning() 3968 3969 return self.expression( 3970 exp.Delete( 3971 hint=hint, 3972 tables=tables, 3973 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3974 using=self._match(TokenType.USING) 3975 and self._parse_csv(lambda: self._parse_table(joins=True)), 3976 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3977 where=self._parse_where(), 3978 returning=returning or self._parse_returning(), 3979 order=self._parse_order(), 3980 limit=self._parse_limit(), 3981 ) 3982 ) 3983 3984 def _parse_update_assignment(self) -> exp.Expr | None: 3985 this = self._parse_comparison() 3986 if self._match(TokenType.EQ): 3987 comments = self._prev_comments 3988 this = self.expression( 3989 exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments 3990 ) 3991 return this 3992 3993 def _parse_update(self) -> exp.Update: 3994 hint = self._parse_hint() 3995 kwargs: dict[str, object] = { 3996 "hint": hint, 3997 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3998 } 3999 while self._curr: 4000 if self._match(TokenType.SET): 4001 kwargs["expressions"] = self._parse_csv(self._parse_update_assignment) 4002 elif self._match(TokenType.RETURNING, advance=False): 4003 kwargs["returning"] = self._parse_returning() 4004 elif self._match(TokenType.FROM, advance=False): 4005 from_ = self._parse_from(joins=True) 4006 table = from_.this if from_ else None 4007 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 4008 table.set("joins", list(self._parse_joins()) or None) 4009 4010 kwargs["from_"] = from_ 4011 elif self._match(TokenType.WHERE, advance=False): 4012 kwargs["where"] = self._parse_where() 4013 elif self._match(TokenType.ORDER_BY, advance=False): 4014 kwargs["order"] = self._parse_order() 4015 elif self._match(TokenType.LIMIT, advance=False): 4016 kwargs["limit"] = self._parse_limit() 4017 else: 4018 break 4019 4020 return self.expression(exp.Update(**kwargs)) 4021 4022 def _parse_use(self) -> exp.Use: 4023 return self.expression( 4024 exp.Use( 4025 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 4026 this=self._parse_table(schema=False), 4027 ) 4028 ) 4029 4030 def _parse_uncache(self) -> exp.Uncache: 4031 if not self._match(TokenType.TABLE): 4032 self.raise_error("Expecting TABLE after UNCACHE") 4033 4034 return self.expression( 4035 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 4036 ) 4037 4038 def _parse_cache(self) -> exp.Cache: 4039 lazy = self._match_text_seq("LAZY") 4040 self._match(TokenType.TABLE) 4041 table = self._parse_table(schema=True) 4042 4043 options = [] 4044 if self._match_text_seq("OPTIONS"): 4045 self._match_l_paren() 4046 k = self._parse_string() 4047 self._match(TokenType.EQ) 4048 v = self._parse_string() 4049 options = [k, v] 4050 self._match_r_paren() 4051 4052 self._match(TokenType.ALIAS) 4053 return self.expression( 4054 exp.Cache( 4055 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 4056 ) 4057 ) 4058 4059 def _parse_partition(self) -> exp.Partition | None: 4060 if not self._match_texts(self.PARTITION_KEYWORDS): 4061 return None 4062 4063 return self.expression( 4064 exp.Partition( 4065 subpartition=self._prev.text.upper() == "SUBPARTITION", 4066 expressions=self._parse_wrapped_csv(self._parse_disjunction), 4067 ) 4068 ) 4069 4070 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 4071 def _parse_value_expression() -> exp.Expr | None: 4072 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 4073 return exp.var(self._prev.text.upper()) 4074 return self._parse_expression() 4075 4076 if self._match(TokenType.L_PAREN): 4077 expressions = self._parse_csv(_parse_value_expression) 4078 self._match_r_paren() 4079 return self.expression(exp.Tuple(expressions=expressions)) 4080 4081 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 4082 expression = self._parse_expression() 4083 if expression: 4084 return self.expression(exp.Tuple(expressions=[expression])) 4085 return None 4086 4087 def _parse_projections( 4088 self, 4089 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 4090 return self._parse_expressions(), None 4091 4092 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 4093 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 4094 this: exp.Expr | None = self._parse_simplified_pivot( 4095 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 4096 ) 4097 else: 4098 # Support parentheses for duckdb FROM-first syntax 4099 this = ( 4100 self._parse_table(consume_pipe=True) 4101 if table and not self._match(TokenType.FROM, advance=False) 4102 else self._parse_select(nested=True, parse_set_operation=False) 4103 ) 4104 4105 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 4106 # in case a modifier (e.g. join) is following 4107 if table and isinstance(this, exp.Values) and this.alias: 4108 alias = this.args["alias"].pop() 4109 this = exp.Table(this=this, alias=alias) 4110 4111 this = self._parse_query_modifiers(self._parse_set_operations(this)) 4112 4113 return this 4114 4115 def _parse_select( 4116 self, 4117 nested: bool = False, 4118 table: bool = False, 4119 parse_subquery_alias: bool = True, 4120 parse_set_operation: bool = True, 4121 consume_pipe: bool = True, 4122 ) -> exp.Expr | None: 4123 query = self._parse_select_query( 4124 nested=nested, 4125 table=table, 4126 parse_subquery_alias=parse_subquery_alias, 4127 parse_set_operation=parse_set_operation, 4128 ) 4129 4130 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4131 if isinstance(query, exp.Query): 4132 query = self._parse_pipe_syntax_query(query) 4133 query = query.subquery(copy=False) if query and table else query 4134 4135 return query 4136 4137 def _parse_select_query( 4138 self, 4139 nested: bool = False, 4140 table: bool = False, 4141 parse_subquery_alias: bool = True, 4142 parse_set_operation: bool = True, 4143 ) -> exp.Expr | None: 4144 cte = self._parse_with() 4145 4146 if cte: 4147 this = self._parse_statement() 4148 4149 if not this: 4150 self.raise_error("Failed to parse any statement following CTE") 4151 return cte 4152 4153 while isinstance(this, exp.Subquery) and this.is_wrapper: 4154 this = this.this 4155 4156 assert this is not None 4157 if "with_" in this.arg_types: 4158 if inner_cte := this.args.get("with_"): 4159 cte.set("expressions", cte.expressions + inner_cte.expressions) 4160 if inner_cte.args.get("recursive"): 4161 cte.set("recursive", True) 4162 this.set("with_", cte) 4163 else: 4164 self.raise_error(f"{this.key} does not support CTE") 4165 this = cte 4166 4167 return this 4168 4169 # duckdb supports leading with FROM x 4170 from_ = ( 4171 self._parse_from(joins=True, consume_pipe=True) 4172 if self._match(TokenType.FROM, advance=False) 4173 else None 4174 ) 4175 laterals = list(iter(self._parse_lateral, None)) if from_ else None 4176 4177 # Hive supports multi-table inserts: FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 4178 if from_ and self._match(TokenType.INSERT, advance=False): 4179 source = from_.this 4180 source.set("laterals", laterals or None) 4181 4182 inserts = [] 4183 while self._match(TokenType.INSERT, advance=False): 4184 inserts.append(self._parse_statement()) 4185 4186 return self.expression( 4187 exp.MultitableInserts(expressions=inserts, source=source), 4188 comments=from_.comments, 4189 ) 4190 4191 if self._match(TokenType.SELECT): 4192 comments = self._prev_comments 4193 4194 hint = self._parse_hint() 4195 4196 if self._next and not self._next.token_type == TokenType.DOT: 4197 all_ = self._match(TokenType.ALL) 4198 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4199 else: 4200 all_, matched_distinct = None, False 4201 4202 kind = ( 4203 self._prev.text.upper() 4204 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4205 else None 4206 ) 4207 4208 distinct: exp.Expr | None = ( 4209 self.expression( 4210 exp.Distinct( 4211 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4212 ) 4213 ) 4214 if matched_distinct 4215 else None 4216 ) 4217 4218 operation_modifiers = [] 4219 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4220 operation_modifiers.append(exp.var(self._prev.text.upper())) 4221 4222 limit = self._parse_limit(top=True) 4223 4224 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4225 if limit and not matched_distinct and not all_: 4226 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4227 if matched_distinct: 4228 distinct = self.expression( 4229 exp.Distinct( 4230 on=self._parse_value(values=False) 4231 if self._match(TokenType.ON) 4232 else None 4233 ) 4234 ) 4235 else: 4236 all_ = self._match(TokenType.ALL) 4237 4238 if all_ and distinct: 4239 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4240 4241 projections, exclude = self._parse_projections() 4242 4243 this = self.expression( 4244 exp.Select( 4245 kind=kind, 4246 hint=hint, 4247 distinct=distinct, 4248 expressions=projections, 4249 limit=limit, 4250 exclude=exclude, 4251 operation_modifiers=operation_modifiers or None, 4252 ) 4253 ) 4254 this.comments = comments 4255 4256 into = self._parse_into() 4257 if into: 4258 this.set("into", into) 4259 4260 if not from_: 4261 from_ = self._parse_from() 4262 4263 if from_: 4264 this.set("from_", from_) 4265 4266 this = self._parse_query_modifiers(this) 4267 elif (table or nested) and self._match(TokenType.L_PAREN): 4268 comments = self._prev_comments 4269 this = self._parse_wrapped_select(table=table) 4270 4271 if this: 4272 this.add_comments(comments, prepend=True) 4273 4274 # We return early here so that the UNION isn't attached to the subquery by the 4275 # following call to _parse_set_operations, but instead becomes the parent node 4276 self._match_r_paren() 4277 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4278 elif self._match(TokenType.VALUES, advance=False): 4279 this = self._parse_derived_table_values() 4280 elif from_: 4281 this = exp.select("*").from_(from_.this, copy=False) 4282 this = self._parse_query_modifiers(this) 4283 elif self._match(TokenType.SUMMARIZE): 4284 table = self._match(TokenType.TABLE) 4285 this = self._parse_select() or self._parse_string() or self._parse_table() 4286 return self.expression(exp.Summarize(this=this, table=table)) 4287 elif self._match(TokenType.DESCRIBE): 4288 this = self._parse_describe() 4289 else: 4290 this = None 4291 4292 if laterals and isinstance(this, exp.Select): 4293 this.set("laterals", [*laterals, *(this.args.get("laterals") or [])]) 4294 4295 return self._parse_set_operations(this) if parse_set_operation else this 4296 4297 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4298 self._match_text_seq("SEARCH") 4299 4300 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4301 4302 if not kind: 4303 return None 4304 4305 self._match_text_seq("FIRST", "BY") 4306 4307 return self.expression( 4308 exp.RecursiveWithSearch( 4309 kind=kind, 4310 this=self._parse_id_var(), 4311 expression=self._match_text_seq("SET") and self._parse_id_var(), 4312 to=self._match_text_seq("TO") and self._parse_bitwise(), 4313 default=self._match(TokenType.DEFAULT) and self._parse_bitwise(), 4314 using=self._match_text_seq("USING") and self._parse_id_var(), 4315 ) 4316 ) 4317 4318 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4319 if not skip_with_token and not self._match(TokenType.WITH): 4320 return None 4321 4322 comments = self._prev_comments 4323 recursive = self._match(TokenType.RECURSIVE) 4324 4325 last_comments = None 4326 expressions = [] 4327 udfs = [] 4328 while True: 4329 cte = self._parse_cte() 4330 if cte: 4331 if isinstance(cte, exp.FunctionSpecification): 4332 udfs.append(cte) 4333 else: 4334 expressions.append(cte) 4335 4336 if last_comments: 4337 cte.add_comments(last_comments) 4338 4339 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4340 break 4341 else: 4342 self._match(TokenType.WITH) 4343 recursive = self._match(TokenType.RECURSIVE) or recursive 4344 4345 last_comments = self._prev_comments 4346 4347 search = self._parse_recursive_with_search() 4348 4349 # A SEARCH clause can be followed by a CYCLE clause 4350 cycle = ( 4351 self._parse_recursive_with_search() 4352 if search 4353 and search.args.get("kind") != "CYCLE" 4354 and self._match_text_seq("CYCLE", advance=False) 4355 else None 4356 ) 4357 4358 return self.expression( 4359 exp.With( 4360 expressions=expressions, 4361 recursive=recursive or None, 4362 search=search, 4363 cycle=cycle, 4364 udfs=udfs or None, 4365 ), 4366 comments=comments, 4367 ) 4368 4369 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4370 index = self._index 4371 4372 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4373 if not alias or not alias.this: 4374 self.raise_error("Expected CTE to have alias") 4375 4376 key_expressions = ( 4377 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4378 ) 4379 4380 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4381 self._retreat(index) 4382 return None 4383 4384 comments = self._prev_comments 4385 4386 if self._match_text_seq("NOT", "MATERIALIZED"): 4387 materialized = False 4388 elif self._match_text_seq("MATERIALIZED"): 4389 materialized = True 4390 else: 4391 materialized = None 4392 4393 cte = self.expression( 4394 exp.CTE( 4395 this=self._parse_wrapped(self._parse_statement), 4396 alias=alias, 4397 materialized=materialized, 4398 key_expressions=key_expressions, 4399 ), 4400 comments=comments, 4401 ) 4402 4403 values = cte.this 4404 if isinstance(values, exp.Values): 4405 cte.set("this", self._values_to_select(values)) 4406 4407 return cte 4408 4409 def _values_to_select(self, values: exp.Values) -> exp.Select: 4410 select = exp.select("*") 4411 for key in exp.QUERY_MODIFIERS: 4412 if modifier := values.args.get(key): 4413 values.set(key, None) 4414 select.set(key, modifier) 4415 return select.from_(values if values.alias else exp.alias_(values, "_values", table=True)) 4416 4417 def _parse_table_alias( 4418 self, alias_tokens: t.Collection[TokenType] | None = None 4419 ) -> exp.TableAlias | None: 4420 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4421 # so this section tries to parse the clause version and if it fails, it treats the token 4422 # as an identifier (alias) 4423 if self._can_parse_limit_or_offset(): 4424 return None 4425 4426 # START is never treated as an implicit alias when followed by WITH, since that 4427 # would swallow the beginning of a START WITH ... CONNECT BY clause 4428 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4429 return None 4430 4431 any_token = self._match(TokenType.ALIAS) 4432 alias = ( 4433 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4434 or self._parse_string_as_identifier() 4435 ) 4436 4437 index = self._index 4438 if self._match(TokenType.L_PAREN): 4439 columns = self._parse_csv(self._parse_function_parameter) 4440 self._match_r_paren() if columns else self._retreat(index) 4441 else: 4442 columns = None 4443 4444 if not alias and not columns: 4445 return None 4446 4447 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4448 4449 # We bubble up comments from the Identifier to the TableAlias 4450 if isinstance(alias, exp.Identifier): 4451 table_alias.add_comments(alias.pop_comments()) 4452 4453 return table_alias 4454 4455 def _parse_subquery( 4456 self, this: exp.Expr | None, parse_alias: bool = True 4457 ) -> exp.Subquery | None: 4458 if not this: 4459 return None 4460 4461 return self.expression( 4462 exp.Subquery( 4463 this=this, 4464 pivots=self._parse_pivots(), 4465 alias=self._parse_table_alias() if parse_alias else None, 4466 sample=self._parse_table_sample(), 4467 ) 4468 ) 4469 4470 def _implicit_unnests_to_explicit(self, this: E) -> E: 4471 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4472 4473 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4474 for i, join in enumerate(this.args.get("joins") or []): 4475 table = join.this 4476 normalized_table = table.copy() 4477 normalized_table.meta["maybe_column"] = True 4478 normalized_table = _norm(normalized_table, dialect=self.dialect) 4479 4480 if isinstance(table, exp.Table) and not join.args.get("on"): 4481 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4482 table_as_column = table.to_column() 4483 unnest = exp.Unnest(expressions=[table_as_column]) 4484 4485 # Table.to_column creates a parent Alias node that we want to convert to 4486 # a TableAlias and attach to the Unnest, so it matches the parser's output 4487 if isinstance(table.args.get("alias"), exp.TableAlias): 4488 table_as_column.replace(table_as_column.this) 4489 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4490 4491 table.replace(unnest) 4492 4493 refs.add(normalized_table.alias_or_name) 4494 4495 return this 4496 4497 @t.overload 4498 def _parse_query_modifiers(self, this: E) -> E: ... 4499 4500 @t.overload 4501 def _parse_query_modifiers(self, this: None) -> None: ... 4502 4503 def _parse_query_modifiers(self, this): 4504 if isinstance(this, self.MODIFIABLES): 4505 for join in self._parse_joins(): 4506 this.append("joins", join) 4507 for lateral in iter(self._parse_lateral, None): 4508 this.append("laterals", lateral) 4509 4510 while True: 4511 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4512 modifier_token = self._curr 4513 4514 # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result 4515 # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union 4516 if ( 4517 modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH) 4518 and (limit := this.args.get("limit")) 4519 and limit.meta.get("top") 4520 ): 4521 break 4522 4523 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4524 key, expression = parser(self) 4525 4526 if expression: 4527 if this.args.get(key): 4528 self.raise_error( 4529 f"Found multiple '{modifier_token.text.upper()}' clauses", 4530 token=modifier_token, 4531 ) 4532 4533 this.set(key, expression) 4534 if key == "limit": 4535 offset = expression.args.get("offset") 4536 expression.set("offset", None) 4537 4538 if offset: 4539 if this.args.get("offset"): 4540 self.raise_error( 4541 "Found multiple 'OFFSET' clauses", token=modifier_token 4542 ) 4543 4544 offset = exp.Offset(expression=offset) 4545 this.set("offset", offset) 4546 4547 limit_by_expressions = expression.expressions 4548 expression.set("expressions", None) 4549 offset.set("expressions", limit_by_expressions) 4550 continue 4551 4552 if self._curr.text.upper() == "START": 4553 modifier_token = self._curr 4554 connect = self._parse_connect() 4555 if connect: 4556 if this.args.get("connect"): 4557 self.raise_error( 4558 "Found multiple 'START WITH' clauses", token=modifier_token 4559 ) 4560 4561 this.set("connect", connect) 4562 continue 4563 break 4564 4565 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4566 this = self._implicit_unnests_to_explicit(this) 4567 4568 return this 4569 4570 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4571 start = self._curr 4572 while self._curr: 4573 self._advance() 4574 4575 end = self._tokens[self._index - 1] 4576 return exp.Hint(expressions=[self._find_sql(start, end)]) 4577 4578 def _parse_hint_function_call(self) -> exp.Expr | None: 4579 return self._parse_function_call() 4580 4581 def _parse_hint_body(self) -> exp.Hint | None: 4582 start_index = self._index 4583 should_fallback_to_string = False 4584 4585 hints = [] 4586 try: 4587 for hint in iter( 4588 lambda: self._parse_csv( 4589 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4590 ), 4591 [], 4592 ): 4593 hints.extend(hint) 4594 except ParseError: 4595 should_fallback_to_string = True 4596 4597 if should_fallback_to_string or self._curr: 4598 self._retreat(start_index) 4599 return self._parse_hint_fallback_to_string() 4600 4601 return self.expression(exp.Hint(expressions=hints)) 4602 4603 def _parse_hint(self) -> exp.Hint | None: 4604 if self._match(TokenType.HINT) and self._prev_comments: 4605 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4606 4607 return None 4608 4609 def _parse_into(self) -> exp.Into | None: 4610 if not self._match(TokenType.INTO): 4611 return None 4612 4613 temp = self._match(TokenType.TEMPORARY) 4614 unlogged = self._match_text_seq("UNLOGGED") 4615 self._match(TokenType.TABLE) 4616 4617 return self.expression( 4618 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4619 ) 4620 4621 def _parse_from( 4622 self, 4623 joins: bool = False, 4624 skip_from_token: bool = False, 4625 consume_pipe: bool = False, 4626 ) -> exp.From | None: 4627 if not skip_from_token and not self._match(TokenType.FROM): 4628 return None 4629 4630 comments = self._prev_comments 4631 return self.expression( 4632 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4633 comments=comments, 4634 ) 4635 4636 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4637 return self.expression( 4638 exp.MatchRecognizeMeasure( 4639 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4640 this=self._parse_expression(), 4641 ) 4642 ) 4643 4644 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4645 if not self._match(TokenType.MATCH_RECOGNIZE): 4646 return None 4647 4648 self._match_l_paren() 4649 4650 partition = self._parse_partition_by() 4651 order = self._parse_order() 4652 4653 measures = ( 4654 self._parse_csv(self._parse_match_recognize_measure) 4655 if self._match_text_seq("MEASURES") 4656 else None 4657 ) 4658 4659 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4660 rows = exp.var("ONE ROW PER MATCH") 4661 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4662 text = "ALL ROWS PER MATCH" 4663 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4664 text += " SHOW EMPTY MATCHES" 4665 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4666 text += " OMIT EMPTY MATCHES" 4667 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4668 text += " WITH UNMATCHED ROWS" 4669 rows = exp.var(text) 4670 else: 4671 rows = None 4672 4673 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4674 text = "AFTER MATCH SKIP" 4675 if self._match_text_seq("PAST", "LAST", "ROW"): 4676 text += " PAST LAST ROW" 4677 elif self._match_text_seq("TO", "NEXT", "ROW"): 4678 text += " TO NEXT ROW" 4679 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4680 direction = self._prev.text.upper() 4681 pattern_var = self._advance_any() 4682 if not pattern_var: 4683 self.raise_error( 4684 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4685 ) 4686 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4687 after = exp.var(text) 4688 else: 4689 after = None 4690 4691 if self._match_text_seq("PATTERN"): 4692 self._match_l_paren() 4693 4694 if not self._curr: 4695 self.raise_error("Expecting )", self._curr) 4696 4697 paren = 1 4698 start = self._curr 4699 4700 while self._curr and paren > 0: 4701 if self._curr.token_type == TokenType.L_PAREN: 4702 paren += 1 4703 if self._curr.token_type == TokenType.R_PAREN: 4704 paren -= 1 4705 4706 end = self._prev 4707 self._advance() 4708 4709 if paren > 0: 4710 self.raise_error("Expecting )", self._curr) 4711 4712 pattern = exp.var(self._find_sql(start, end)) 4713 else: 4714 pattern = None 4715 4716 define = ( 4717 self._parse_csv(self._parse_name_as_expression) 4718 if self._match_text_seq("DEFINE") 4719 else None 4720 ) 4721 4722 self._match_r_paren() 4723 4724 return self.expression( 4725 exp.MatchRecognize( 4726 partition_by=partition, 4727 order=order, 4728 measures=measures, 4729 rows=rows, 4730 after=after, 4731 pattern=pattern, 4732 define=define, 4733 alias=self._parse_table_alias(), 4734 ) 4735 ) 4736 4737 def _parse_lateral(self) -> exp.Lateral | None: 4738 cross_apply: bool | None = None 4739 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4740 cross_apply = True 4741 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4742 cross_apply = False 4743 4744 if cross_apply is not None: 4745 this = self._parse_select(table=True) 4746 view = None 4747 outer = None 4748 elif self._match(TokenType.LATERAL): 4749 this = self._parse_select(table=True) 4750 view = self._match(TokenType.VIEW) 4751 outer = self._match(TokenType.OUTER) 4752 else: 4753 return None 4754 4755 if not this: 4756 this = ( 4757 self._parse_unnest() 4758 or self._parse_function() 4759 or self._parse_id_var(any_token=False) 4760 ) 4761 4762 while self._match(TokenType.DOT): 4763 this = exp.Dot( 4764 this=this, 4765 expression=self._parse_function() or self._parse_id_var(any_token=False), 4766 ) 4767 4768 ordinality: bool | None = None 4769 4770 if view: 4771 table = self._parse_id_var(any_token=False) 4772 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4773 table_alias: exp.TableAlias | None = self.expression( 4774 exp.TableAlias(this=table, columns=columns) 4775 ) 4776 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4777 # We move the alias from the lateral's child node to the lateral itself 4778 table_alias = this.args["alias"].pop() 4779 else: 4780 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4781 table_alias = self._parse_table_alias() 4782 4783 return self.expression( 4784 exp.Lateral( 4785 this=this, 4786 view=view, 4787 outer=outer, 4788 alias=table_alias, 4789 cross_apply=cross_apply, 4790 ordinality=ordinality, 4791 ) 4792 ) 4793 4794 def _parse_stream(self) -> exp.Stream | None: 4795 index = self._index 4796 if self._match(TokenType.STREAM): 4797 if this := self._try_parse(self._parse_table): 4798 return self.expression(exp.Stream(this=this)) 4799 self._retreat(index) 4800 return None 4801 4802 def _parse_join_parts( 4803 self, 4804 ) -> tuple[Token | None, Token | None, Token | None]: 4805 return ( 4806 self._prev if self._match_set(self.JOIN_METHODS) else None, 4807 self._prev if self._match_set(self.JOIN_SIDES) else None, 4808 self._prev if self._match_set(self.JOIN_KINDS) else None, 4809 ) 4810 4811 def _parse_using_identifiers(self) -> list[exp.Expr]: 4812 def _parse_column_as_identifier() -> exp.Expr | None: 4813 this = self._parse_column() 4814 if isinstance(this, exp.Column): 4815 return this.this 4816 return this 4817 4818 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4819 4820 def _parse_join( 4821 self, 4822 skip_join_token: bool = False, 4823 parse_bracket: bool = False, 4824 alias_tokens: t.Collection[TokenType] | None = None, 4825 ) -> exp.Join | None: 4826 if self._match(TokenType.COMMA): 4827 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4828 cross_join = self.expression(exp.Join(this=table)) if table else None 4829 4830 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4831 cross_join.set("kind", "CROSS") 4832 4833 return cross_join 4834 4835 index = self._index 4836 method, side, kind = self._parse_join_parts() 4837 directed = self.SUPPORTS_DIRECTED_JOIN and self._match_text_seq("DIRECTED") 4838 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4839 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4840 join_comments = self._prev_comments 4841 4842 if not skip_join_token and not join: 4843 self._retreat(index) 4844 kind = None 4845 method = None 4846 side = None 4847 4848 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4849 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4850 4851 if not skip_join_token and not join and not outer_apply and not cross_apply: 4852 return None 4853 4854 kwargs: dict[str, t.Any] = { 4855 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4856 } 4857 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4858 kwargs["expressions"] = self._parse_csv( 4859 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4860 ) 4861 4862 if method: 4863 kwargs["method"] = method.text.upper() 4864 if side: 4865 kwargs["side"] = side.text.upper() 4866 if kind: 4867 kwargs["kind"] = kind.text.upper() 4868 if hint: 4869 kwargs["hint"] = hint 4870 4871 if self._match(TokenType.MATCH_CONDITION): 4872 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4873 4874 if self._match(TokenType.ON): 4875 kwargs["on"] = self._parse_disjunction() 4876 elif self._match(TokenType.USING): 4877 kwargs["using"] = self._parse_using_identifiers() 4878 elif ( 4879 not method 4880 and not (outer_apply or cross_apply) 4881 and not isinstance(kwargs["this"], exp.Unnest) 4882 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4883 ): 4884 index = self._index 4885 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4886 4887 if joins and self._match(TokenType.ON): 4888 kwargs["on"] = self._parse_disjunction() 4889 elif joins and self._match(TokenType.USING): 4890 kwargs["using"] = self._parse_using_identifiers() 4891 else: 4892 joins = None 4893 self._retreat(index) 4894 4895 kwargs["this"].set("joins", joins if joins else None) 4896 4897 kwargs["pivots"] = self._parse_pivots() 4898 4899 comments = [c for token in (method, side, kind) if token for c in token.comments] 4900 comments = (join_comments or []) + comments 4901 4902 if ( 4903 self.ADD_JOIN_ON_TRUE 4904 and not kwargs.get("on") 4905 and not kwargs.get("using") 4906 and not kwargs.get("method") 4907 and kwargs.get("kind") in (None, "INNER", "OUTER") 4908 ): 4909 kwargs["on"] = exp.true() 4910 4911 if directed: 4912 kwargs["directed"] = directed 4913 4914 return self.expression(exp.Join(**kwargs), comments=comments) 4915 4916 def _parse_opclass(self) -> exp.Expr | None: 4917 this = self._parse_disjunction() 4918 4919 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4920 return this 4921 4922 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4923 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4924 4925 return this 4926 4927 def _parse_index_params(self) -> exp.IndexParameters: 4928 using = ( 4929 self._parse_var(any_token=True) 4930 if not self._match_text_seq("USING", "INDEX", "TABLESPACE", advance=False) 4931 and self._match(TokenType.USING) 4932 else None 4933 ) 4934 4935 if self._match(TokenType.L_PAREN, advance=False): 4936 columns = self._parse_wrapped_csv(self._parse_with_operator) 4937 else: 4938 columns = None 4939 4940 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4941 partition_by = self._parse_partition_by() 4942 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4943 tablespace = ( 4944 self._parse_id_var() 4945 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4946 or self._match_text_seq("TABLESPACE") 4947 else None 4948 ) 4949 where = self._parse_where() 4950 4951 on = self._parse_field() if self._match(TokenType.ON) else None 4952 4953 return self.expression( 4954 exp.IndexParameters( 4955 using=using, 4956 columns=columns, 4957 include=include, 4958 partition_by=partition_by, 4959 where=where, 4960 with_storage=with_storage, 4961 tablespace=tablespace, 4962 on=on, 4963 ) 4964 ) 4965 4966 def _parse_index( 4967 self, index: exp.Expr | None = None, anonymous: bool = False 4968 ) -> exp.Index | None: 4969 if index or anonymous: 4970 unique = None 4971 primary = None 4972 amp = None 4973 4974 self._match(TokenType.ON) 4975 self._match(TokenType.TABLE) # hive 4976 table = self._parse_table_parts(schema=True) 4977 else: 4978 unique = self._match(TokenType.UNIQUE) 4979 primary = self._match_text_seq("PRIMARY") 4980 amp = self._match_text_seq("AMP") 4981 4982 if not self._match(TokenType.INDEX): 4983 return None 4984 4985 index = self._parse_id_var() 4986 table = None 4987 4988 params = self._parse_index_params() 4989 4990 return self.expression( 4991 exp.Index( 4992 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4993 ) 4994 ) 4995 4996 def _parse_table_hints(self) -> list[exp.Expr] | None: 4997 hints: list[exp.Expr] = [] 4998 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4999 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 5000 hints.append( 5001 self.expression( 5002 exp.WithTableHint( 5003 expressions=self._parse_csv( 5004 lambda: self._parse_function() or self._parse_var(any_token=True) 5005 ) 5006 ) 5007 ) 5008 ) 5009 self._match_r_paren() 5010 else: 5011 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 5012 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 5013 hint = exp.IndexTableHint(this=self._prev.text.upper()) 5014 5015 self._match_set((TokenType.INDEX, TokenType.KEY)) 5016 if self._match(TokenType.FOR): 5017 hint.set("target", self._advance_any() and self._prev.text.upper()) 5018 5019 hint.set("expressions", self._parse_wrapped_id_vars()) 5020 hints.append(hint) 5021 5022 return hints or None 5023 5024 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 5025 return ( 5026 (not schema and self._parse_function(optional_parens=False)) 5027 or self._parse_id_var(any_token=False) 5028 or self._parse_string_as_identifier() 5029 or self._parse_placeholder() 5030 ) 5031 5032 def _parse_table_parts_fast(self) -> exp.Table | None: 5033 index = self._index 5034 parts: list[exp.Identifier] | None = None 5035 all_comments: list[str] | None = None 5036 5037 while self._match_set(self.IDENTIFIER_TOKENS): 5038 token = self._prev 5039 comments = self._prev_comments 5040 5041 has_dot = self._match(TokenType.DOT) 5042 curr_tt = self._curr.token_type 5043 5044 if not has_dot: 5045 if curr_tt in self.TABLE_POSTFIX_TOKENS: 5046 self._retreat(index) 5047 return None 5048 elif curr_tt not in self.IDENTIFIER_TOKENS: 5049 self._retreat(index) 5050 return None 5051 5052 if parts is None: 5053 parts = [] 5054 5055 if comments: 5056 if all_comments is None: 5057 all_comments = [] 5058 all_comments.extend(comments) 5059 self._prev_comments = [] 5060 5061 parts.append( 5062 self.expression( 5063 exp.Identifier( 5064 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 5065 ), 5066 token, 5067 ) 5068 ) 5069 5070 if not has_dot: 5071 break 5072 5073 if parts is None: 5074 return None 5075 5076 n = len(parts) 5077 5078 if n == 1: 5079 table: exp.Table = exp.Table(this=parts[0]) 5080 elif n == 2: 5081 table = exp.Table(this=parts[1], db=parts[0]) 5082 elif n >= 3: 5083 this: exp.Identifier | exp.Dot = parts[2] 5084 for i in range(3, n): 5085 this = exp.Dot(this=this, expression=parts[i]) 5086 5087 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 5088 5089 if table is None: 5090 self._retreat(index) 5091 elif all_comments: 5092 table.add_comments(all_comments) 5093 return table 5094 5095 def _parse_table_parts( 5096 self, 5097 schema: bool = False, 5098 is_db_reference: bool = False, 5099 wildcard: bool = False, 5100 fast: bool = False, 5101 ) -> exp.Table | exp.Dot | None: 5102 if fast: 5103 return self._parse_table_parts_fast() 5104 5105 catalog: exp.Expr | str | None = None 5106 db: exp.Expr | str | None = None 5107 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 5108 5109 while self._match(TokenType.DOT): 5110 if catalog: 5111 # This allows nesting the table in arbitrarily many dot expressions if needed 5112 table = self.expression( 5113 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 5114 ) 5115 else: 5116 catalog = db 5117 db = table 5118 # "" used for tsql FROM a..b case 5119 table = self._parse_table_part(schema=schema) or "" 5120 5121 if ( 5122 wildcard 5123 and self._is_connected() 5124 and (isinstance(table, exp.Identifier) or not table) 5125 and self._match(TokenType.STAR) 5126 ): 5127 if isinstance(table, exp.Identifier): 5128 table.args["this"] += "*" 5129 else: 5130 table = exp.Identifier(this="*") 5131 5132 if is_db_reference: 5133 catalog = db 5134 db = table 5135 table = None 5136 5137 if not table and not is_db_reference: 5138 self.raise_error(f"Expected table name but got {self._curr}") 5139 if not db and is_db_reference: 5140 self.raise_error(f"Expected database name but got {self._curr}") 5141 5142 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 5143 5144 # Bubble up comments from identifier parts to the Table 5145 comments = [] 5146 for part in table.parts: 5147 if part_comments := part.pop_comments(): 5148 comments.extend(part_comments) 5149 if comments: 5150 table.add_comments(comments) 5151 5152 changes = self._parse_changes() 5153 if changes: 5154 table.set("changes", changes) 5155 5156 at_before = self._parse_historical_data() 5157 if at_before: 5158 table.set("when", at_before) 5159 5160 pivots = self._parse_pivots() 5161 if pivots: 5162 table.set("pivots", pivots) 5163 5164 return table 5165 5166 def _parse_table( 5167 self, 5168 schema: bool = False, 5169 joins: bool = False, 5170 alias_tokens: t.Collection[TokenType] | None = None, 5171 parse_bracket: bool = False, 5172 is_db_reference: bool = False, 5173 parse_partition: bool = False, 5174 consume_pipe: bool = False, 5175 ) -> exp.Expr | None: 5176 if not schema and not is_db_reference and not consume_pipe and not joins: 5177 index = self._index 5178 table = self._parse_table_parts(fast=True) 5179 5180 if table is not None: 5181 curr_tt = self._curr.token_type 5182 next_tt = self._next.token_type 5183 5184 fast_terminators = self.TABLE_TERMINATORS 5185 5186 # only return the table if we're sure there are no other operators 5187 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5188 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5189 return table 5190 5191 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5192 5193 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5194 if alias := self._parse_table_alias( 5195 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5196 ): 5197 table.set("alias", alias) 5198 5199 if self._curr.token_type in fast_terminators: 5200 return table 5201 5202 self._retreat(index) 5203 5204 if stream := self._parse_stream(): 5205 return stream 5206 5207 if lateral := self._parse_lateral(): 5208 return lateral 5209 5210 if unnest := self._parse_unnest(): 5211 return unnest 5212 5213 if values := self._parse_derived_table_values(): 5214 return values 5215 5216 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5217 if not subquery.args.get("pivots"): 5218 subquery.set("pivots", self._parse_pivots()) 5219 if joins: 5220 for join in self._parse_joins(): 5221 subquery.append("joins", join) 5222 return subquery 5223 5224 bracket = parse_bracket and self._parse_bracket(None) 5225 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5226 5227 rows_from_tables = ( 5228 self._parse_wrapped_csv(self._parse_table) 5229 if self._match_text_seq("ROWS", "FROM") 5230 else None 5231 ) 5232 rows_from = ( 5233 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5234 ) 5235 5236 only = self._match(TokenType.ONLY) 5237 5238 this = t.cast( 5239 exp.Expr, 5240 bracket 5241 or rows_from 5242 or self._parse_bracket( 5243 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5244 ), 5245 ) 5246 5247 if only: 5248 this.set("only", only) 5249 5250 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5251 self._match(TokenType.STAR) 5252 5253 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5254 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5255 this.set("partition", self._parse_partition()) 5256 5257 if schema: 5258 return self._parse_schema(this=this) 5259 5260 if self.dialect.ALIAS_POST_VERSION: 5261 this.set("version", self._parse_version()) 5262 5263 if self.dialect.ALIAS_POST_TABLESAMPLE: 5264 this.set("sample", self._parse_table_sample()) 5265 if ( 5266 this.args.get("sample") 5267 and self.dialect.ALIAS_POST_VERSION 5268 and not this.args.get("version") 5269 ): 5270 this.set("version", self._parse_version()) 5271 5272 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5273 if alias: 5274 this.set("alias", alias) 5275 5276 # DuckDB requires the time-travel clause to come after the alias, e.g. 5277 # SELECT * FROM t AS a AT (VERSION => 1) 5278 if isinstance(this, exp.Table) and not this.args.get("when"): 5279 this.set("when", self._parse_historical_data()) 5280 5281 if self._match(TokenType.INDEXED_BY): 5282 this.set("indexed", self._parse_table_parts()) 5283 elif self._match_text_seq("NOT", "INDEXED"): 5284 this.set("indexed", False) 5285 5286 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5287 return self.expression( 5288 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5289 ) 5290 5291 this.set("hints", self._parse_table_hints()) 5292 5293 if not this.args.get("pivots"): 5294 this.set("pivots", self._parse_pivots()) 5295 5296 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5297 this.set("sample", self._parse_table_sample()) 5298 5299 if not self.dialect.ALIAS_POST_VERSION: 5300 this.set("version", self._parse_version()) 5301 5302 if joins: 5303 for join in self._parse_joins(alias_tokens=alias_tokens): 5304 this.append("joins", join) 5305 5306 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5307 this.set("ordinality", True) 5308 this.set("alias", self._parse_table_alias()) 5309 5310 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5311 # hoist the table args onto the latter and return it instead 5312 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5313 table_from_rows = this.this 5314 for arg in exp.TableFromRows.arg_types: 5315 if arg != "this": 5316 table_from_rows.set(arg, this.args.get(arg)) 5317 5318 this = table_from_rows 5319 5320 return this 5321 5322 def _parse_version(self) -> exp.Version | None: 5323 for phrase, this in self.VERSION_PHRASES.items(): 5324 if self._match_text_seq(*phrase): 5325 break 5326 else: 5327 return None 5328 5329 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5330 kind = self._prev.text.upper() 5331 start = self._parse_bitwise() 5332 self._match_texts(("TO", "AND")) 5333 end = self._parse_bitwise() 5334 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5335 elif self._match_text_seq("CONTAINED", "IN"): 5336 kind = "CONTAINED IN" 5337 expression = self.expression( 5338 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5339 ) 5340 elif self._match(TokenType.ALL): 5341 kind = "ALL" 5342 expression = None 5343 else: 5344 self._match_text_seq("AS", "OF") 5345 kind = "AS OF" 5346 expression = self._parse_bitwise() 5347 5348 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5349 5350 def _parse_historical_data(self) -> exp.HistoricalData | None: 5351 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5352 index = self._index 5353 historical_data = None 5354 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5355 this = self._prev.text.upper() 5356 kind = ( 5357 self._match(TokenType.L_PAREN) 5358 and self._match_texts(self.HISTORICAL_DATA_KIND) 5359 and self._prev.text.upper() 5360 ) 5361 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5362 5363 if expression: 5364 self._match_r_paren() 5365 historical_data = self.expression( 5366 exp.HistoricalData(this=this, kind=kind, expression=expression) 5367 ) 5368 else: 5369 self._retreat(index) 5370 5371 return historical_data 5372 5373 def _parse_changes(self) -> exp.Changes | None: 5374 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5375 return None 5376 5377 information = self._parse_var(any_token=True) 5378 self._match_r_paren() 5379 5380 return self.expression( 5381 exp.Changes( 5382 information=information, 5383 at_before=self._parse_historical_data(), 5384 end=self._parse_historical_data(), 5385 ) 5386 ) 5387 5388 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5389 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5390 return None 5391 5392 self._advance() 5393 5394 expressions = self._parse_wrapped_csv(self._parse_equality) 5395 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5396 5397 alias = self._parse_table_alias() if with_alias else None 5398 5399 if alias: 5400 if self.dialect.UNNEST_COLUMN_ONLY: 5401 if alias.args.get("columns"): 5402 self.raise_error("Unexpected extra column alias in unnest.") 5403 5404 alias.set("columns", [alias.this]) 5405 alias.set("this", None) 5406 5407 columns = alias.args.get("columns") or [] 5408 if offset and len(expressions) < len(columns): 5409 offset = columns.pop() 5410 5411 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5412 self._match(TokenType.ALIAS) 5413 offset = self._parse_id_var( 5414 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5415 ) or exp.to_identifier("offset") 5416 5417 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5418 5419 def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None: 5420 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5421 if not is_derived and not ( 5422 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5423 self._match_text_seq("VALUES") 5424 or self._match_text_seq("FORMAT", "VALUES") 5425 # MySQL accepts VALUE as a synonym for VALUES in INSERT statements. 5426 or (allow_value_synonym and self._match_text_seq("VALUE")) 5427 ): 5428 return None 5429 5430 expressions = self._parse_csv(self._parse_value) 5431 alias = self._parse_table_alias() 5432 5433 if is_derived: 5434 self._match_r_paren() 5435 5436 return self.expression( 5437 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5438 ) 5439 5440 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5441 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5442 as_modifier and self._match_text_seq("USING", "SAMPLE") 5443 ): 5444 return None 5445 5446 bucket_numerator = None 5447 bucket_denominator = None 5448 bucket_field = None 5449 percent = None 5450 size = None 5451 seed = None 5452 5453 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5454 matched_l_paren = self._match(TokenType.L_PAREN) 5455 5456 if self.TABLESAMPLE_CSV: 5457 num = None 5458 expressions = self._parse_csv(self._parse_primary) 5459 else: 5460 expressions = None 5461 num = ( 5462 self._parse_factor(parse_mod=False) 5463 if self._match(TokenType.NUMBER, advance=False) 5464 else self._parse_primary() or self._parse_placeholder() 5465 ) 5466 5467 if self._match_text_seq("BUCKET"): 5468 bucket_numerator = self._parse_number() 5469 self._match_text_seq("OUT", "OF") 5470 bucket_denominator = bucket_denominator = self._parse_number() 5471 self._match(TokenType.ON) 5472 bucket_field = self._parse_field() 5473 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5474 percent = num 5475 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5476 size = num 5477 else: 5478 percent = num 5479 5480 if matched_l_paren: 5481 self._match_r_paren() 5482 5483 if self._match(TokenType.L_PAREN): 5484 method = self._parse_var(upper=True) 5485 seed = self._match(TokenType.COMMA) and self._parse_number() 5486 self._match_r_paren() 5487 elif self._match_texts(("SEED", "REPEATABLE")): 5488 seed = self._parse_wrapped(self._parse_number) 5489 5490 if not method and self.DEFAULT_SAMPLING_METHOD: 5491 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5492 5493 return self.expression( 5494 exp.TableSample( 5495 expressions=expressions, 5496 method=method, 5497 bucket_numerator=bucket_numerator, 5498 bucket_denominator=bucket_denominator, 5499 bucket_field=bucket_field, 5500 percent=percent, 5501 size=size, 5502 seed=seed, 5503 ) 5504 ) 5505 5506 def _parse_pivots(self) -> list[exp.Pivot] | None: 5507 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5508 return None 5509 return list(iter(self._parse_pivot, None)) or None 5510 5511 def _parse_joins( 5512 self, alias_tokens: t.Collection[TokenType] | None = None 5513 ) -> t.Iterator[exp.Join]: 5514 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5515 5516 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5517 if not self._match(TokenType.INTO): 5518 return None 5519 5520 return self.expression( 5521 exp.UnpivotColumns( 5522 this=self._match_text_seq("NAME") and self._parse_column(), 5523 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5524 ) 5525 ) 5526 5527 # https://duckdb.org/docs/sql/statements/pivot 5528 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5529 def _parse_on() -> exp.Expr | None: 5530 this = self._parse_bitwise() 5531 5532 if self._match(TokenType.IN): 5533 # PIVOT ... ON col IN (row_val1, row_val2) 5534 return self._parse_in(this) 5535 if self._match(TokenType.ALIAS, advance=False): 5536 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5537 return self._parse_alias(this) 5538 5539 return this 5540 5541 this = self._parse_table() 5542 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5543 into = self._parse_unpivot_columns() 5544 using = self._match(TokenType.USING) and self._parse_csv( 5545 lambda: self._parse_alias(self._parse_column()) 5546 ) 5547 group = self._parse_group() 5548 5549 return self.expression( 5550 exp.Pivot( 5551 this=this, 5552 expressions=expressions, 5553 using=using, 5554 group=group, 5555 unpivot=is_unpivot, 5556 into=into, 5557 ) 5558 ) 5559 5560 def _parse_pivot_in(self) -> exp.In: 5561 def _parse_aliased_expression() -> exp.Expr | None: 5562 this = self._parse_select_or_expression() 5563 5564 self._match(TokenType.ALIAS) 5565 alias = self._parse_bitwise() 5566 if alias: 5567 if isinstance(alias, exp.Column) and not alias.db: 5568 alias = alias.this 5569 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5570 5571 return this 5572 5573 value = self._parse_column() 5574 5575 if not self._match(TokenType.IN): 5576 self.raise_error("Expecting IN") 5577 5578 if self._match(TokenType.L_PAREN): 5579 if self._match(TokenType.ANY): 5580 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5581 else: 5582 exprs = self._parse_csv(_parse_aliased_expression) 5583 self._match_r_paren() 5584 return self.expression(exp.In(this=value, expressions=exprs)) 5585 5586 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5587 5588 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5589 func = self._parse_function() 5590 if not func: 5591 if self._prev.token_type == TokenType.COMMA: 5592 return None 5593 self.raise_error("Expecting an aggregation function in PIVOT") 5594 5595 return self._parse_alias(func) 5596 5597 def _parse_pivot(self) -> exp.Pivot | None: 5598 index = self._index 5599 include_nulls = None 5600 5601 if self._match(TokenType.PIVOT): 5602 unpivot = False 5603 elif self._match(TokenType.UNPIVOT): 5604 unpivot = True 5605 5606 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5607 if self._match_text_seq("INCLUDE", "NULLS"): 5608 include_nulls = True 5609 elif self._match_text_seq("EXCLUDE", "NULLS"): 5610 include_nulls = False 5611 else: 5612 return None 5613 5614 expressions = [] 5615 5616 if not self._match(TokenType.L_PAREN): 5617 self._retreat(index) 5618 return None 5619 5620 if unpivot: 5621 expressions = self._parse_csv(self._parse_column) 5622 else: 5623 expressions = self._parse_csv(self._parse_pivot_aggregation) 5624 5625 if not expressions: 5626 self.raise_error("Failed to parse PIVOT's aggregation list") 5627 5628 if not self._match(TokenType.FOR): 5629 self.raise_error("Expecting FOR") 5630 5631 fields = [] 5632 while True: 5633 field = self._try_parse(self._parse_pivot_in) 5634 if not field: 5635 break 5636 fields.append(field) 5637 5638 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5639 self._parse_bitwise 5640 ) 5641 5642 group = self._parse_group() 5643 5644 self._match_r_paren() 5645 5646 pivot = self.expression( 5647 exp.Pivot( 5648 expressions=expressions, 5649 fields=fields, 5650 unpivot=unpivot, 5651 include_nulls=include_nulls, 5652 default_on_null=default_on_null, 5653 group=group, 5654 ) 5655 ) 5656 5657 if unpivot: 5658 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5659 for pivot_field in pivot.fields: 5660 if isinstance(pivot_field, exp.In): 5661 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5662 5663 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5664 5665 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5666 pivot.set("alias", self._parse_table_alias()) 5667 5668 if not unpivot: 5669 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5670 5671 columns: list[exp.Expr] = [] 5672 all_fields = [] 5673 for pivot_field in pivot.fields: 5674 pivot_field_expressions = pivot_field.expressions 5675 5676 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5677 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5678 continue 5679 5680 all_fields.append( 5681 [ 5682 # An explicit `<field> AS <alias>` names the output column directly, 5683 # so it wins over the dialect's string-identifying convention 5684 fld.sql() 5685 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5686 else fld.alias_or_name 5687 for fld in pivot_field_expressions 5688 ] 5689 ) 5690 5691 if all_fields: 5692 if names: 5693 all_fields.append(names) 5694 5695 # Generate all possible combinations of the pivot columns 5696 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5697 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5698 for fld_parts_tuple in itertools.product(*all_fields): 5699 fld_parts = list(fld_parts_tuple) 5700 5701 if names and self.PREFIXED_PIVOT_COLUMNS: 5702 # Move the "name" to the front of the list 5703 fld_parts.insert(0, fld_parts.pop(-1)) 5704 5705 columns.append(exp.to_identifier("_".join(fld_parts))) 5706 5707 pivot.set("columns", columns) 5708 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5709 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5710 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5711 5712 return pivot 5713 5714 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5715 return [agg.alias for agg in aggregations if agg.alias] 5716 5717 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5718 if not skip_where_token and not self._match(TokenType.PREWHERE): 5719 return None 5720 5721 comments = self._prev_comments 5722 return self.expression( 5723 exp.PreWhere(this=self._parse_disjunction()), 5724 comments=comments, 5725 ) 5726 5727 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5728 if not skip_where_token and not self._match(TokenType.WHERE): 5729 return None 5730 5731 comments = self._prev_comments 5732 return self.expression( 5733 exp.Where(this=self._parse_disjunction()), 5734 comments=comments, 5735 ) 5736 5737 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5738 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5739 return None 5740 comments = self._prev_comments 5741 5742 elements: dict[str, t.Any] = defaultdict(list) 5743 5744 if self._match(TokenType.ALL): 5745 elements["all"] = True 5746 elif self._match(TokenType.DISTINCT): 5747 elements["all"] = False 5748 5749 while True: 5750 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5751 # which are also valid identifiers 5752 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5753 break 5754 5755 elements["expressions"].extend( 5756 self._parse_csv( 5757 lambda: ( 5758 self._parse_grouping_sets() 5759 or self._parse_cube_or_rollup() 5760 or self._parse_disjunction() 5761 ) 5762 ) 5763 ) 5764 5765 before_with_index = self._index 5766 5767 if self._match(TokenType.WITH) and ( 5768 cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True) 5769 ): 5770 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5771 elements[key].append(cube_or_rollup) 5772 elif grouping_sets := self._parse_grouping_sets(): 5773 # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...) 5774 elements["grouping_sets"].append(grouping_sets) 5775 break 5776 elif self._match_text_seq("TOTALS"): 5777 elements["totals"] = True # type: ignore 5778 5779 if before_with_index <= self._index <= before_with_index + 1: 5780 self._retreat(before_with_index) 5781 break 5782 5783 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5784 5785 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5786 if self._match(TokenType.CUBE): 5787 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5788 elif self._match(TokenType.ROLLUP): 5789 kind = exp.Rollup 5790 else: 5791 return None 5792 5793 return self.expression( 5794 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5795 ) 5796 5797 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5798 if self._match(TokenType.GROUPING_SETS): 5799 return self.expression( 5800 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5801 ) 5802 return None 5803 5804 def _parse_grouping_set(self) -> exp.Expr | None: 5805 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5806 5807 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5808 if not skip_having_token and not self._match(TokenType.HAVING): 5809 return None 5810 comments = self._prev_comments 5811 return self.expression( 5812 exp.Having(this=self._parse_disjunction()), 5813 comments=comments, 5814 ) 5815 5816 def _parse_qualify(self) -> exp.Qualify | None: 5817 if not self._match(TokenType.QUALIFY): 5818 return None 5819 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5820 5821 def _parse_connect_with_prior(self) -> exp.Expr | None: 5822 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5823 exp.Prior(this=self._parse_bitwise()) 5824 ) 5825 connect = self._parse_disjunction() 5826 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5827 return connect 5828 5829 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5830 if skip_start_token: 5831 start = None 5832 elif self._match_text_seq("START", "WITH"): 5833 start = self._parse_disjunction() 5834 else: 5835 return None 5836 5837 self._match(TokenType.CONNECT_BY) 5838 nocycle = self._match_text_seq("NOCYCLE") 5839 connect = self._parse_connect_with_prior() 5840 5841 if not start and self._match_text_seq("START", "WITH"): 5842 start = self._parse_disjunction() 5843 5844 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5845 5846 def _parse_name_as_expression(self) -> exp.Expr | None: 5847 this = self._parse_id_var(any_token=True) 5848 if self._match(TokenType.ALIAS): 5849 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5850 return this 5851 5852 def _parse_interpolate(self) -> list[exp.Expr] | None: 5853 if self._match_text_seq("INTERPOLATE"): 5854 return self._parse_wrapped_csv(self._parse_name_as_expression) 5855 return None 5856 5857 def _parse_order( 5858 self, this: exp.Expr | None = None, skip_order_token: bool = False 5859 ) -> exp.Expr | None: 5860 siblings = None 5861 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5862 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5863 return this 5864 5865 siblings = True 5866 5867 comments = self._prev_comments 5868 return self.expression( 5869 exp.Order( 5870 this=this, 5871 expressions=self._parse_csv(self._parse_ordered), 5872 siblings=siblings, 5873 ), 5874 comments=comments, 5875 ) 5876 5877 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5878 if not self._match(token): 5879 return None 5880 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5881 5882 def _parse_ordered( 5883 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5884 ) -> exp.Ordered | None: 5885 this = parse_method() if parse_method else self._parse_disjunction() 5886 if not this: 5887 return None 5888 5889 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5890 this = exp.var("ALL") 5891 5892 asc = self._match(TokenType.ASC) 5893 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5894 5895 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5896 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5897 5898 nulls_first = is_nulls_first or False 5899 explicitly_null_ordered = is_nulls_first or is_nulls_last 5900 5901 if ( 5902 not explicitly_null_ordered 5903 and ( 5904 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5905 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5906 ) 5907 and self.dialect.NULL_ORDERING != "nulls_are_last" 5908 ): 5909 nulls_first = True 5910 5911 if self._match_text_seq("WITH", "FILL"): 5912 with_fill = self.expression( 5913 exp.WithFill( 5914 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5915 to=self._match_text_seq("TO") and self._parse_bitwise(), 5916 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5917 interpolate=self._parse_interpolate(), 5918 ) 5919 ) 5920 else: 5921 with_fill = None 5922 5923 return self.expression( 5924 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5925 ) 5926 5927 def _parse_limit_options(self) -> exp.LimitOptions | None: 5928 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5929 rows = self._match_texts(("ROW", "ROWS")) 5930 self._match_text_seq("ONLY") 5931 with_ties = self._match_text_seq("WITH", "TIES") 5932 5933 if not (percent or rows or with_ties): 5934 return None 5935 5936 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5937 5938 def _parse_limit( 5939 self, 5940 this: exp.Expr | None = None, 5941 top: bool = False, 5942 skip_limit_token: bool = False, 5943 ) -> exp.Expr | None: 5944 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5945 comments = self._prev_comments 5946 if top: 5947 limit_paren = self._match(TokenType.L_PAREN) 5948 expression = ( 5949 self._parse_term() or self._parse_select() 5950 if limit_paren 5951 else self._parse_number() 5952 ) 5953 5954 if limit_paren: 5955 self._match_r_paren() 5956 5957 else: 5958 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5959 expression = exp.var("ALL") 5960 else: 5961 expression = self._parse_term(parse_mod=False) 5962 limit_options = self._parse_limit_options() 5963 5964 if self._match(TokenType.COMMA): 5965 offset = expression 5966 expression = self._parse_term() 5967 else: 5968 offset = None 5969 5970 limit_exp = self.expression( 5971 exp.Limit( 5972 this=this, 5973 expression=expression, 5974 offset=offset, 5975 limit_options=limit_options, 5976 expressions=self._parse_limit_by(), 5977 ), 5978 comments=comments, 5979 ) 5980 5981 if top: 5982 limit_exp.meta["top"] = True 5983 5984 return limit_exp 5985 5986 if self._match(TokenType.FETCH): 5987 direction = ( 5988 self._prev.text.upper() 5989 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5990 else "FIRST" 5991 ) 5992 5993 count = ( 5994 None 5995 if self._match_texts(("ROW", "ROWS"), advance=False) 5996 else self._parse_field(tokens=self.FETCH_TOKENS) 5997 ) 5998 5999 return self.expression( 6000 exp.Fetch( 6001 direction=direction, count=count, limit_options=self._parse_limit_options() 6002 ) 6003 ) 6004 6005 return this 6006 6007 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 6008 if not self._match(TokenType.OFFSET): 6009 return this 6010 6011 count = self._parse_term() 6012 self._match_texts(("ROW", "ROWS")) 6013 6014 return self.expression( 6015 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 6016 ) 6017 6018 def _can_parse_limit_or_offset(self) -> bool: 6019 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 6020 return False 6021 6022 index = self._index 6023 result = bool( 6024 self._try_parse(self._parse_limit, retreat=True) 6025 or self._try_parse(self._parse_offset, retreat=True) 6026 ) 6027 self._retreat(index) 6028 6029 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 6030 if self._next.token_type == TokenType.MATCH_CONDITION: 6031 result = False 6032 6033 return result 6034 6035 def _can_parse_named_window(self) -> bool: 6036 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 6037 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 6038 if not self._match(TokenType.WINDOW, advance=False): 6039 return False 6040 6041 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 6042 if name is None or name.token_type not in self.ID_VAR_TOKENS: 6043 return False 6044 6045 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 6046 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 6047 return False 6048 6049 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 6050 return body is not None and body.token_type == TokenType.L_PAREN 6051 6052 def _parse_limit_by(self) -> list[exp.Expr] | None: 6053 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 6054 6055 def _parse_locks(self) -> list[exp.Lock]: 6056 locks = [] 6057 while True: 6058 update, key = None, None 6059 if self._match_text_seq("FOR", "UPDATE"): 6060 update = True 6061 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 6062 "LOCK", "IN", "SHARE", "MODE" 6063 ): 6064 update = False 6065 elif self._match_text_seq("FOR", "KEY", "SHARE"): 6066 update, key = False, True 6067 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 6068 update, key = True, True 6069 else: 6070 break 6071 6072 expressions = None 6073 if self._match_text_seq("OF"): 6074 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 6075 6076 wait: bool | exp.Expr | None = None 6077 if self._match_text_seq("NOWAIT"): 6078 wait = True 6079 elif self._match_text_seq("WAIT"): 6080 wait = self._parse_primary() 6081 elif self._match_text_seq("SKIP", "LOCKED"): 6082 wait = False 6083 6084 locks.append( 6085 self.expression( 6086 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 6087 ) 6088 ) 6089 6090 return locks 6091 6092 def parse_set_operation( 6093 self, this: exp.Expr | None, consume_pipe: bool = False, intersect_only: bool = False 6094 ) -> exp.SetOperation | None: 6095 start = self._index 6096 _, side_token, kind_token = self._parse_join_parts() 6097 6098 side = side_token.text if side_token else None 6099 kind = kind_token.text if kind_token else None 6100 6101 if not self._match_set(self.SET_OPERATIONS) or ( 6102 intersect_only and self._prev.token_type != TokenType.INTERSECT 6103 ): 6104 self._retreat(start) 6105 return None 6106 6107 token_type = self._prev.token_type 6108 6109 if token_type == TokenType.UNION: 6110 operation: type[exp.SetOperation] = exp.Union 6111 elif token_type == TokenType.EXCEPT: 6112 operation = exp.Except 6113 else: 6114 operation = exp.Intersect 6115 6116 comments = self._prev.comments 6117 6118 if self._match(TokenType.DISTINCT): 6119 distinct: bool | None = True 6120 elif self._match(TokenType.ALL): 6121 distinct = False 6122 else: 6123 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 6124 if distinct is None: 6125 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 6126 6127 by_name = ( 6128 self._match_text_seq("BY", "NAME") 6129 or self._match_text_seq("STRICT", "CORRESPONDING") 6130 or None 6131 ) 6132 if self._match_text_seq("CORRESPONDING"): 6133 by_name = True 6134 if not side and not kind: 6135 kind = "INNER" 6136 6137 on_column_list = None 6138 if by_name and self._match_texts(("ON", "BY")): 6139 on_column_list = self._parse_wrapped_csv(self._parse_column) 6140 6141 expression = self._parse_select( 6142 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 6143 ) 6144 6145 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 6146 # in _parse_cte and so that alias pushdown can reach into set operation branches 6147 if isinstance(this, exp.Values): 6148 this = self._values_to_select(this) 6149 if isinstance(expression, exp.Values): 6150 expression = self._values_to_select(expression) 6151 for operand in (this, expression): 6152 if isinstance(operand, exp.Subquery) and isinstance( 6153 values := operand.unnest(), exp.Values 6154 ): 6155 wrapper = t.cast(exp.Subquery, values.parent) 6156 wrapper.set("this", self._values_to_select(values)) 6157 6158 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 6159 subquery = this.this 6160 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 6161 subquery.add_comments(this.pop_comments()) 6162 this = subquery 6163 6164 return self.expression( 6165 operation( 6166 this=this, 6167 distinct=distinct, 6168 by_name=by_name, 6169 expression=expression, 6170 side=side, 6171 kind=kind, 6172 on=on_column_list, 6173 ), 6174 comments=comments, 6175 ) 6176 6177 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 6178 intersect_binds_tighter = self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT 6179 last_setop: exp.SetOperation | None = None 6180 6181 while this: 6182 setop = self.parse_set_operation(this) 6183 if not setop: 6184 break 6185 6186 last_setop = setop 6187 if ( 6188 isinstance(setop, exp.Intersect) 6189 and isinstance(setop.this, (exp.Union, exp.Except)) 6190 and not intersect_binds_tighter 6191 ): 6192 setop.set("this", setop.this.subquery(copy=False)) 6193 elif not isinstance(setop, exp.Intersect) and intersect_binds_tighter: 6194 # INTERSECT binds tighter than this UNION / EXCEPT, so any INTERSECTs that 6195 # follow it group with its right operand first: 6196 # a UNION b INTERSECT c INTERSECT d -> a UNION (b INTERSECT c INTERSECT d) 6197 operand = setop.expression 6198 while intersect := self.parse_set_operation(operand, intersect_only=True): 6199 last_setop = operand = intersect 6200 if operand is not setop.expression: 6201 setop.set("expression", operand.subquery(copy=False)) 6202 6203 this = setop 6204 6205 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6206 expression = last_setop.expression if last_setop else None 6207 6208 if expression: 6209 for arg in self.SET_OP_MODIFIERS: 6210 expr = expression.args.get(arg) 6211 if expr and not (arg == "limit" and expr.meta.get("top")): 6212 expression.set(arg, None) 6213 this.set(arg, expr) 6214 6215 # Consume modifiers after a parenthesized final operand (e.g., SELECT 1 UNION (SELECT 2) 6216 # ORDER BY 1), or a LIMIT/FETCH that a final operand with TOP leaves for the whole 6217 # result (e.g., SELECT 1 UNION SELECT TOP 2 2 LIMIT 1). 6218 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 6219 this = self._parse_query_modifiers(this) 6220 6221 return this 6222 6223 def _parse_expression(self) -> exp.Expr | None: 6224 return self._parse_alias(self._parse_assignment()) 6225 6226 def _parse_assignment(self) -> exp.Expr | None: 6227 this = self._parse_disjunction() 6228 if not this and self._next.token_type in self.ASSIGNMENT: 6229 # This allows us to parse <non-identifier token> := <expr> 6230 this = exp.column( 6231 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6232 ) 6233 6234 while self._match_set(self.ASSIGNMENT): 6235 if isinstance(this, exp.Column) and len(this.parts) == 1: 6236 this = this.this 6237 6238 comments = self._prev_comments 6239 this = self.expression( 6240 self.ASSIGNMENT[self._prev.token_type]( 6241 this=this, expression=self._parse_assignment() 6242 ), 6243 comments=comments, 6244 ) 6245 6246 return this 6247 6248 def _parse_disjunction(self) -> exp.Expr | None: 6249 this = self._parse_conjunction() 6250 while self._match_set(self.DISJUNCTION): 6251 comments = self._prev_comments 6252 this = self.expression( 6253 self.DISJUNCTION[self._prev.token_type]( 6254 this=this, expression=self._parse_conjunction() 6255 ), 6256 comments=comments, 6257 ) 6258 return this 6259 6260 def _parse_conjunction(self) -> exp.Expr | None: 6261 this = self._parse_equality() 6262 while self._match_set(self.CONJUNCTION): 6263 comments = self._prev_comments 6264 this = self.expression( 6265 self.CONJUNCTION[self._prev.token_type]( 6266 this=this, expression=self._parse_equality() 6267 ), 6268 comments=comments, 6269 ) 6270 return this 6271 6272 def _build_predicate( 6273 self, 6274 klass: Type[exp.Binary], 6275 this: exp.Expr | None, 6276 expression: exp.Expr | None, 6277 comments: list[str] | None = None, 6278 negate: bool = False, 6279 ) -> exp.Expr: 6280 is_predicates = (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ) 6281 if klass in is_predicates: 6282 if ( 6283 isinstance(this, (exp.Connector, exp.Not, exp.NullSafeNEQ)) 6284 or type(this) in self.EQUALITY.values() 6285 or type(this) in self.COMPARISON.values() 6286 or (klass is not exp.Is and isinstance(this, exp.Is)) 6287 ): 6288 this = exp.Paren(this=this) 6289 if isinstance(expression, (exp.Predicate, exp.Connector, exp.Not, exp.Escape)): 6290 expression = exp.Paren(this=expression) 6291 else: 6292 if isinstance(this.this if isinstance(this, exp.Not) else this, is_predicates): 6293 this = exp.Paren(this=this) 6294 if isinstance( 6295 expression.this if isinstance(expression, exp.Not) else expression, is_predicates 6296 ): 6297 expression = exp.Paren(this=expression) 6298 6299 predicate = klass(this=this, expression=expression) 6300 if negate: 6301 predicate.set("negate", True) 6302 return self.expression(predicate, comments=comments) 6303 6304 def _parse_equality(self, parse_is: bool = True) -> exp.Expr | None: 6305 this = self._parse_comparison() 6306 while True: 6307 if self._match_set(self.EQUALITY): 6308 comments = self._prev_comments 6309 this = self._build_predicate( 6310 self.EQUALITY[self._prev.token_type], 6311 this, 6312 self._parse_comparison(), 6313 comments=comments, 6314 ) 6315 elif parse_is and self._is_predicate_ahead(): 6316 index = self._index 6317 # A postfix predicate can itself be an operand, e.g. x IS NULL = FALSE. 6318 this = self._parse_comparison(self._parse_range(this, parse_is=True)) 6319 if self._index == index: 6320 break 6321 else: 6322 break 6323 return this 6324 6325 def _parse_comparison(self, this: exp.Expr | None = None) -> exp.Expr | None: 6326 this = this or self._parse_range() 6327 while self._match_set(self.COMPARISON): 6328 comments = self._prev_comments 6329 this = self._build_predicate( 6330 self.COMPARISON[self._prev.token_type], 6331 this, 6332 self._parse_range(), 6333 comments=comments, 6334 ) 6335 return this 6336 6337 def _is_predicate_ahead(self) -> bool: 6338 if ( 6339 self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.HIGH 6340 or self._curr.token_type not in self.IS_PREDICATE_TOKENS 6341 ): 6342 return False 6343 return self._curr.token_type != TokenType.NOT or self._next.token_type == TokenType.NULL 6344 6345 def _parse_range(self, this: exp.Expr | None = None, parse_is: bool = False) -> exp.Expr | None: 6346 this = this or self._parse_bitwise() 6347 6348 while True: 6349 if not parse_is and self._is_predicate_ahead(): 6350 break 6351 negate = self._match(TokenType.NOT) 6352 if self._match_set(self.RANGE_PARSERS): 6353 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6354 if not expression: 6355 return this 6356 6357 this = expression 6358 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6359 this = self._build_predicate(exp.Is, this, exp.Null()) 6360 elif self._match(TokenType.NOTNULL): 6361 # Postgres supports ISNULL and NOTNULL for conditions. 6362 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6363 if self.dialect.NORMALIZE_NOT_NULL: 6364 this = self._build_predicate(exp.Is, this, exp.Null()) 6365 this = self.expression(exp.Not(this=this)) 6366 else: 6367 this = self._build_predicate(exp.Is, this, exp.Null(), negate=True) 6368 else: 6369 if negate: 6370 self._retreat(self._index - 1) 6371 break 6372 6373 if negate: 6374 this = self._negate_range(this) 6375 if (negate or (isinstance(this, exp.Not) and isinstance(this.this, exp.Is))) and ( 6376 self._curr.token_type == TokenType.NOT 6377 or self._curr.token_type in self.RANGE_PARSERS 6378 ): 6379 this = self.expression(exp.Paren(this=this)) 6380 6381 return this 6382 6383 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6384 if not this: 6385 return this 6386 6387 expression = this.this if isinstance(this, exp.Escape) else this 6388 if isinstance(expression, (exp.Like, exp.ILike)): 6389 expression.set("negate", True) 6390 return this 6391 6392 return self.expression(exp.Not(this=this)) 6393 6394 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6395 index = self._index - 1 6396 negate = self._match(TokenType.NOT) 6397 6398 if self._match_text_seq("DISTINCT", "FROM"): 6399 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6400 return self._build_predicate(klass, this, self._parse_is_rhs(distinct=True)) 6401 6402 if self._match(TokenType.JSON): 6403 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6404 6405 if self._match_text_seq("WITH"): 6406 _with = True 6407 elif self._match_text_seq("WITHOUT"): 6408 _with = False 6409 else: 6410 _with = None 6411 6412 unique = self._match(TokenType.UNIQUE) 6413 self._match_text_seq("KEYS") 6414 expression: exp.Expr | None = self.expression( 6415 exp.JSON(this=kind, with_=_with, unique=unique) 6416 ) 6417 else: 6418 expression = self._parse_is_rhs() 6419 if not expression: 6420 self._retreat(index) 6421 return None 6422 6423 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6424 this = self._build_predicate(exp.Is, this, expression, negate=True) 6425 else: 6426 this = self._build_predicate(exp.Is, this, expression) 6427 this = self.expression(exp.Not(this=this)) if negate else this 6428 6429 return self._parse_column_ops(this) 6430 6431 def _parse_is_rhs(self, distinct: bool = False) -> exp.Expr | None: 6432 if distinct: 6433 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.LOW: 6434 return self._parse_equality(parse_is=False) 6435 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.EQUALITY: 6436 return self._parse_comparison() 6437 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.COMPARISON: 6438 return self._parse_range() 6439 return self._parse_bitwise() 6440 return self._parse_null() or self._parse_bitwise() 6441 6442 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6443 unnest = self._parse_unnest(with_alias=False) 6444 if unnest: 6445 this = self.expression(exp.In(this=this, unnest=unnest)) 6446 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6447 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6448 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6449 6450 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6451 this = self.expression( 6452 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6453 ) 6454 else: 6455 this = self.expression(exp.In(this=this, expressions=expressions)) 6456 6457 if matched_l_paren: 6458 self._match_r_paren(this) 6459 elif not self._match(TokenType.R_BRACKET, expression=this): 6460 self.raise_error("Expecting ]") 6461 else: 6462 this = self.expression(exp.In(this=this, field=self._parse_column())) 6463 6464 return this 6465 6466 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6467 # Preserve grouping when BETWEEN is rewritten into comparisons. 6468 if isinstance(this, (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ)): 6469 this = exp.Paren(this=this) 6470 6471 symmetric = None 6472 if self._match_text_seq("SYMMETRIC"): 6473 symmetric = True 6474 elif self._match_text_seq("ASYMMETRIC"): 6475 symmetric = False 6476 6477 low = self._parse_bitwise() 6478 self._match(TokenType.AND) 6479 high = self._parse_bitwise() 6480 6481 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6482 6483 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6484 if not self._match(TokenType.ESCAPE): 6485 return this 6486 return self.expression( 6487 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6488 ) 6489 6490 def _parse_interval_span( 6491 self, this: exp.Expr, parse_function_unit: bool = True 6492 ) -> exp.Interval: 6493 # handle day-time format interval span with omitted units: 6494 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6495 interval_span_units_omitted = None 6496 fraction_in_string = False 6497 if ( 6498 this 6499 and this.is_string 6500 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6501 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6502 ): 6503 index = self._index 6504 6505 # Var "TO" Var 6506 first_unit = self._parse_var(any_token=True, upper=True) 6507 second_unit = None 6508 if first_unit and self._match_text_seq("TO"): 6509 second_unit = self._parse_var(any_token=True, upper=True) 6510 6511 interval_span_units_omitted = not (first_unit and second_unit) 6512 6513 self._retreat(index) 6514 6515 unit_index = self._index 6516 if interval_span_units_omitted: 6517 unit = None 6518 else: 6519 # Only attempt to parse a unit if the current token can actually be one, so that a 6520 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6521 is_unit = self._curr is not None and ( 6522 self._curr.token_type == TokenType.VAR 6523 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6524 ) 6525 unit = self._parse_function() if parse_function_unit and is_unit else None 6526 if not unit and is_unit: 6527 unit = self._parse_var(any_token=True, upper=True) 6528 6529 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6530 # each INTERVAL expression into this canonical form so it's easy to transpile 6531 if this and this.is_number: 6532 try: 6533 this = exp.Literal.string(this.to_py()) 6534 except ValueError: 6535 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6536 elif this and this.is_string: 6537 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6538 if parts and unit: 6539 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6540 unit = None 6541 self._retreat(unit_index) 6542 6543 if len(parts) == 1: 6544 this = exp.Literal.string(parts[0][0]) 6545 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6546 fraction_in_string = "." in parts[0][0] 6547 6548 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6549 unit = self.expression( 6550 exp.IntervalSpan( 6551 this=unit, 6552 expression=self._parse_function() 6553 or self._parse_var(any_token=True, upper=True), 6554 ) 6555 ) 6556 6557 return self.expression( 6558 exp.Interval(this=this, unit=unit, fraction_in_string=fraction_in_string or None) 6559 ) 6560 6561 def _parse_interval( 6562 self, require_interval: bool = True, parse_function_unit: bool = True 6563 ) -> exp.Add | exp.Interval | None: 6564 index = self._index 6565 6566 if not self._match(TokenType.INTERVAL) and require_interval: 6567 return None 6568 6569 if self._match(TokenType.STRING, advance=False): 6570 this = self._parse_primary() 6571 else: 6572 this = self._parse_term() 6573 6574 if not this or ( 6575 isinstance(this, exp.Column) 6576 and not this.table 6577 and not this.this.quoted 6578 and self._curr 6579 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6580 ): 6581 self._retreat(index) 6582 return None 6583 6584 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6585 6586 index = self._index 6587 self._match(TokenType.PLUS) 6588 6589 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6590 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6591 return self.expression( 6592 exp.Add( 6593 this=interval, 6594 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6595 ) 6596 ) 6597 6598 self._retreat(index) 6599 return interval 6600 6601 def _parse_bitwise(self) -> exp.Expr | None: 6602 this = self._parse_term() 6603 6604 while True: 6605 if self._match_set(self.BITWISE): 6606 this = self.expression( 6607 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6608 ) 6609 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6610 this = self.expression( 6611 exp.DPipe( 6612 this=this, 6613 expression=self._parse_term(), 6614 safe=not self.dialect.STRICT_STRING_CONCAT, 6615 ) 6616 ) 6617 elif self._match(TokenType.DQMARK): 6618 this = self.expression( 6619 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6620 ) 6621 elif self._match_pair(TokenType.LT, TokenType.LT): 6622 this = self.expression( 6623 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6624 ) 6625 elif self._match_pair(TokenType.GT, TokenType.GT): 6626 this = self.expression( 6627 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6628 ) 6629 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6630 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6631 else: 6632 break 6633 6634 return this 6635 6636 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6637 this = self._parse_factor(parse_mod=parse_mod) 6638 6639 while self._match_set(self.TERM): 6640 klass = self.TERM[self._prev.token_type] 6641 comments = self._prev_comments 6642 expression = self._parse_factor(parse_mod=parse_mod) 6643 6644 this = self.expression(klass(this=this, expression=expression), comments=comments) 6645 6646 if isinstance(this, exp.Collate): 6647 self._normalize_collate(this) 6648 6649 return this 6650 6651 def _normalize_collate(self, collate: exp.Collate) -> None: 6652 expr = collate.expression 6653 6654 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6655 # fallback to Identifier / Var 6656 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6657 ident = expr.this 6658 if isinstance(ident, exp.Identifier): 6659 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6660 6661 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6662 parse_method = self._parse_factor_operand 6663 this = self._parse_at_time_zone(parse_method()) 6664 6665 while self._match_set(self.FACTOR, advance=False): 6666 if not parse_mod and self._curr.token_type == TokenType.MOD: 6667 break 6668 6669 self._advance() 6670 klass = self.FACTOR[self._prev.token_type] 6671 comments = self._prev_comments 6672 expression = parse_method() 6673 6674 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6675 self._retreat(self._index - 1) 6676 return this 6677 6678 this = self.expression(klass(this=this, expression=expression), comments=comments) 6679 6680 if isinstance(this, exp.Div): 6681 this.set("typed", self.dialect.TYPED_DIVISION) 6682 this.set("safe", self.dialect.SAFE_DIVISION) 6683 6684 return this 6685 6686 def _parse_factor_operand(self) -> exp.Expr | None: 6687 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6688 6689 def _parse_exponent(self) -> exp.Expr | None: 6690 this = self._parse_unary() 6691 while self._match_set(self.EXPONENT): 6692 comments = self._prev_comments 6693 this = self.expression( 6694 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6695 comments=comments, 6696 ) 6697 return this 6698 6699 def _parse_unary(self) -> exp.Expr | None: 6700 if self._match_set(self.UNARY_PARSERS): 6701 return self.UNARY_PARSERS[self._prev.token_type](self) 6702 return self._parse_type() 6703 6704 def _parse_type( 6705 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6706 ) -> exp.Expr | None: 6707 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6708 return atom 6709 6710 if interval := parse_interval and self._parse_interval(): 6711 return self._parse_column_ops(interval) 6712 6713 index = self._index 6714 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6715 6716 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6717 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6718 if isinstance(data_type, exp.Cast): 6719 # This constructor can contain ops directly after it, for instance struct unnesting: 6720 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6721 return self._parse_column_ops(data_type) 6722 6723 if data_type: 6724 index2 = self._index 6725 this = self._parse_primary() 6726 6727 if isinstance(this, exp.Literal): 6728 literal = this.name 6729 this = self._parse_column_ops(this) 6730 6731 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6732 if data_type.is_type(exp.DType.TIMESTAMP): 6733 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6734 elif data_type.is_type(exp.DType.TIME): 6735 data_type = exp.DType.TIMETZ.into_expr() 6736 6737 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6738 if parser: 6739 return parser(self, this, data_type) 6740 6741 return self.expression(exp.Cast(this=this, to=data_type)) 6742 6743 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6744 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6745 # 6746 # If the index difference here is greater than 1, that means the parser itself must have 6747 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6748 # 6749 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6750 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6751 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6752 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6753 # 6754 # In these cases, we don't really want to return the converted type, but instead retreat 6755 # and try to parse a Column or Identifier in the section below. 6756 if data_type.expressions and index2 - index > 1: 6757 self._retreat(index2) 6758 return self._parse_column_ops(data_type) 6759 6760 self._retreat(index) 6761 6762 if fallback_to_identifier: 6763 return self._parse_id_var() 6764 6765 return self._parse_column() 6766 6767 def _parse_type_size(self) -> exp.DataTypeParam | None: 6768 this = self._parse_type() 6769 if not this: 6770 return None 6771 6772 if isinstance(this, exp.Column) and not this.table: 6773 this = exp.var(this.name.upper()) 6774 6775 return self.expression( 6776 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6777 ) 6778 6779 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6780 type_name = identifier.name 6781 6782 while self._match(TokenType.DOT): 6783 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6784 6785 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6786 6787 def _parse_types( 6788 self, 6789 check_func: bool = False, 6790 schema: bool = False, 6791 allow_identifiers: bool = True, 6792 with_collation: bool = False, 6793 ) -> exp.Expr | None: 6794 index = self._index 6795 this: exp.Expr | None = None 6796 6797 if self._match_set(self.TYPE_TOKENS): 6798 type_token = self._prev.token_type 6799 else: 6800 type_token = None 6801 identifier = allow_identifiers and self._parse_id_var( 6802 any_token=False, tokens=(TokenType.VAR,) 6803 ) 6804 if isinstance(identifier, exp.Identifier): 6805 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6806 this = exp.DataType.build(identifier, udt=True) 6807 else: 6808 try: 6809 tokens = self.dialect.tokenize(identifier.name) 6810 except TokenError: 6811 tokens = None 6812 6813 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6814 if len(tokens) > 1: 6815 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6816 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6817 this = self._parse_user_defined_type(identifier) 6818 else: 6819 self._retreat(self._index - 1) 6820 return None 6821 else: 6822 return None 6823 6824 if type_token == TokenType.PSEUDO_TYPE: 6825 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6826 6827 if type_token == TokenType.OBJECT_IDENTIFIER: 6828 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6829 6830 # https://materialize.com/docs/sql/types/map/ 6831 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6832 key_type = self._parse_types( 6833 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6834 ) 6835 if not self._match(TokenType.FARROW): 6836 self._retreat(index) 6837 return None 6838 6839 value_type = self._parse_types( 6840 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6841 ) 6842 if not self._match(TokenType.R_BRACKET): 6843 self._retreat(index) 6844 return None 6845 6846 return exp.DataType( 6847 this=exp.DType.MAP, 6848 expressions=[key_type, value_type], 6849 nested=True, 6850 ) 6851 6852 nested = type_token in self.NESTED_TYPE_TOKENS 6853 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6854 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6855 expressions = None 6856 maybe_func = False 6857 6858 if self._match(TokenType.L_PAREN): 6859 if is_struct: 6860 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6861 elif nested: 6862 expressions = self._parse_csv( 6863 lambda: self._parse_types( 6864 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6865 ) 6866 ) 6867 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6868 this = expressions[0] 6869 this.set("nullable", True) 6870 self._match_r_paren() 6871 return this 6872 elif type_token in self.ENUM_TYPE_TOKENS: 6873 expressions = self._parse_csv(self._parse_equality) 6874 elif type_token == TokenType.REF: 6875 # REF(schema.customer) names a target; REF(1) can still be a function call. 6876 target = self._try_parse(lambda: self._parse_table_parts(schema=True)) 6877 if target is None: 6878 self._retreat(index) 6879 return None 6880 expressions = [target] 6881 elif type_token == TokenType.JSON: 6882 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6883 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6884 expressions = self._parse_csv(self._parse_json_type_arg) 6885 elif is_aggregate: 6886 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6887 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6888 ) 6889 if not func_or_ident: 6890 return None 6891 expressions = [func_or_ident] 6892 if self._match(TokenType.COMMA): 6893 expressions.extend( 6894 self._parse_csv( 6895 lambda: self._parse_types( 6896 check_func=check_func, 6897 schema=schema, 6898 allow_identifiers=allow_identifiers, 6899 ) 6900 ) 6901 ) 6902 else: 6903 expressions = self._parse_csv(self._parse_type_size) 6904 6905 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6906 if type_token == TokenType.VECTOR and len(expressions) == 2: 6907 expressions = self._parse_vector_expressions(expressions) 6908 6909 if not self._match(TokenType.R_PAREN): 6910 self._retreat(index) 6911 return None 6912 6913 maybe_func = True 6914 6915 values: list[exp.Expr] | None = None 6916 6917 if nested and self._match(TokenType.LT): 6918 if is_struct: 6919 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6920 else: 6921 expressions = self._parse_csv( 6922 lambda: self._parse_types( 6923 check_func=check_func, 6924 schema=schema, 6925 allow_identifiers=allow_identifiers, 6926 with_collation=True, 6927 ) 6928 ) 6929 6930 if not self._match(TokenType.GT): 6931 self.raise_error("Expecting >") 6932 6933 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6934 values = self._parse_csv(self._parse_disjunction) 6935 if not values and is_struct: 6936 values = None 6937 self._retreat(self._index - 1) 6938 else: 6939 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6940 6941 if type_token in self.TIMESTAMPS: 6942 if self._match_text_seq("WITH", "TIME", "ZONE"): 6943 maybe_func = False 6944 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6945 this = exp.DataType(this=tz_type, expressions=expressions) 6946 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6947 maybe_func = False 6948 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6949 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6950 maybe_func = False 6951 elif type_token == TokenType.INTERVAL: 6952 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6953 unit = self._parse_var(upper=True) 6954 if self._match_text_seq("TO"): 6955 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6956 6957 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6958 else: 6959 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6960 elif type_token == TokenType.VOID: 6961 this = exp.DataType(this=exp.DType.NULL) 6962 6963 if maybe_func and check_func: 6964 index2 = self._index 6965 peek = self._parse_string() 6966 6967 if not peek: 6968 self._retreat(index) 6969 return None 6970 6971 self._retreat(index2) 6972 6973 if not this: 6974 assert type_token is not None 6975 if self._match_text_seq("UNSIGNED"): 6976 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6977 if not unsigned_type_token: 6978 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6979 6980 type_token = unsigned_type_token or type_token 6981 6982 # NULLABLE without parentheses can be a column (Presto/Trino) 6983 if type_token == TokenType.NULLABLE and not expressions: 6984 self._retreat(index) 6985 return None 6986 6987 this = exp.DataType( 6988 this=exp.DType[type_token.name], 6989 expressions=expressions, 6990 nested=nested, 6991 ) 6992 6993 # Empty arrays/structs are allowed 6994 if values is not None: 6995 cls = exp.Struct if is_struct else exp.Array 6996 this = exp.cast(cls(expressions=values), this, copy=False) 6997 6998 elif expressions: 6999 this.set("expressions", expressions) 7000 7001 # https://materialize.com/docs/sql/types/list/#type-name 7002 while self._match(TokenType.LIST): 7003 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 7004 7005 index = self._index 7006 7007 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 7008 matched_array = self._match(TokenType.ARRAY) 7009 7010 while self._curr: 7011 datatype_token = self._prev.token_type 7012 matched_l_bracket = self._match(TokenType.L_BRACKET) 7013 7014 if (not matched_l_bracket and not matched_array) or ( 7015 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 7016 ): 7017 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 7018 # not to be confused with the fixed size array parsing 7019 break 7020 7021 matched_array = False 7022 values = self._parse_csv(self._parse_disjunction) or None 7023 if ( 7024 values 7025 and not schema 7026 and ( 7027 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 7028 or datatype_token == TokenType.ARRAY 7029 or not self._match(TokenType.R_BRACKET, advance=False) 7030 ) 7031 ): 7032 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 7033 # 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 7034 self._retreat(index) 7035 break 7036 7037 this = exp.DataType( 7038 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 7039 ) 7040 self._match(TokenType.R_BRACKET) 7041 7042 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 7043 converter = self.TYPE_CONVERTERS.get(this.this) 7044 if converter: 7045 this = converter(t.cast(exp.DataType, this)) 7046 7047 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 7048 this.set("collate", self._parse_identifier() or self._parse_column()) 7049 7050 return this 7051 7052 def _parse_json_type_arg(self) -> exp.Expr | None: 7053 """Parse a single argument to ClickHouse's JSON type.""" 7054 7055 # SKIP col or SKIP REGEXP 'pattern' 7056 if self._match_text_seq("SKIP"): 7057 regexp = self._match(TokenType.RLIKE) 7058 arg = self._parse_column() 7059 if isinstance(arg, exp.Column): 7060 arg = arg.to_dot() 7061 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 7062 7063 param_or_col = self._parse_column() 7064 if not isinstance(param_or_col, exp.Column): 7065 return None 7066 7067 # Parameter: name=value (e.g., max_dynamic_paths=2) 7068 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 7069 param = param_or_col.name 7070 value = self._parse_primary() 7071 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 7072 7073 # Column type hint: col_name Type 7074 col = param_or_col.to_dot() 7075 kind = self._parse_types(check_func=False, allow_identifiers=False) 7076 return self.expression(exp.ColumnDef(this=col, kind=kind)) 7077 7078 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 7079 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 7080 7081 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 7082 index = self._index 7083 7084 if ( 7085 self._curr 7086 and self._next 7087 and self._curr.token_type in self.TYPE_TOKENS 7088 and self._next.token_type in self.TYPE_TOKENS 7089 ): 7090 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 7091 # type token. Without this, the list will be parsed as a type and we'll eventually crash 7092 this = self._parse_id_var() 7093 else: 7094 this = ( 7095 self._parse_type(parse_interval=False, fallback_to_identifier=True) 7096 or self._parse_id_var() 7097 ) 7098 7099 self._match(TokenType.COLON) 7100 7101 if ( 7102 type_required 7103 and not isinstance(this, exp.DataType) 7104 and not self._match_set(self.TYPE_TOKENS, advance=False) 7105 ): 7106 self._retreat(index) 7107 return self._parse_types() 7108 7109 return self._parse_column_def(this) 7110 7111 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 7112 if not self._match_text_seq("AT", "TIME", "ZONE"): 7113 return this 7114 return self._parse_at_time_zone( 7115 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 7116 ) 7117 7118 def _parse_atom(self) -> exp.Expr | None: 7119 if ( 7120 self._curr.token_type in self.IDENTIFIER_TOKENS 7121 and (column := self._parse_column()) is not None 7122 ): 7123 return column 7124 7125 token = self._curr 7126 token_type = token.token_type 7127 7128 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 7129 return None 7130 7131 next_type = self._next.token_type 7132 7133 if ( 7134 next_type in self.COLUMN_OPERATORS 7135 or next_type in self.COLUMN_POSTFIX_TOKENS 7136 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 7137 ): 7138 return None 7139 7140 self._advance() 7141 return primary_parser(self, token) 7142 7143 def _parse_column(self) -> exp.Expr | None: 7144 column: exp.Expr | None = self._parse_column_parts_fast() 7145 if column is None: 7146 this = self._parse_column_reference() 7147 if not this: 7148 this = self._parse_bracket(this) 7149 column = self._parse_column_ops(this) if this else this 7150 7151 if column: 7152 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS and self._match(TokenType.JOIN_MARKER): 7153 column.set("join_mark", True) 7154 if self.COLON_IS_VARIANT_EXTRACT: 7155 column = self._parse_colon_as_variant_extract(column) 7156 7157 return column 7158 7159 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 7160 """Fast path for simple column and dot references (a, a.b, ...). 7161 7162 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 7163 that nothing complex follows. If it does, retreats and returns None so 7164 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 7165 """ 7166 index = self._index 7167 parts: list[exp.Identifier] | None = None 7168 all_comments: list[str] | None = None 7169 7170 while self._match_set(self.IDENTIFIER_TOKENS): 7171 token = self._prev 7172 comments = self._prev_comments 7173 7174 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 7175 self._retreat(index) 7176 return None 7177 7178 has_dot = self._match(TokenType.DOT) 7179 curr_tt = self._curr.token_type 7180 7181 if not has_dot: 7182 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 7183 self._retreat(index) 7184 return None 7185 elif curr_tt not in self.IDENTIFIER_TOKENS: 7186 self._retreat(index) 7187 return None 7188 7189 if parts is None: 7190 parts = [] 7191 7192 if comments: 7193 if all_comments is None: 7194 all_comments = [] 7195 all_comments.extend(comments) 7196 self._prev_comments = [] 7197 7198 parts.append( 7199 self.expression( 7200 exp.Identifier( 7201 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 7202 ), 7203 token, 7204 ) 7205 ) 7206 7207 if not has_dot: 7208 break 7209 7210 if parts is None: 7211 return None 7212 7213 n = len(parts) 7214 7215 if n == 1: 7216 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 7217 elif n == 2: 7218 column = exp.Column(this=parts[1], table=parts[0]) 7219 elif n == 3: 7220 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 7221 else: 7222 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 7223 7224 for i in range(4, n): 7225 column = exp.Dot(this=column, expression=parts[i]) 7226 7227 if all_comments: 7228 column.add_comments(all_comments) 7229 7230 return column 7231 7232 def _parse_column_reference(self) -> exp.Expr | None: 7233 this = self._parse_field() 7234 if ( 7235 not this 7236 and self._match(TokenType.VALUES, advance=False) 7237 and self.VALUES_FOLLOWED_BY_PAREN 7238 and (not self._next or self._next.token_type != TokenType.L_PAREN) 7239 ): 7240 this = self._parse_id_var() 7241 7242 if isinstance(this, exp.Identifier): 7243 # We bubble up comments from the Identifier to the Column 7244 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 7245 7246 return this 7247 7248 def _build_json_extract( 7249 self, 7250 this: exp.Expr | None, 7251 path_parts: list[exp.JSONPathPart], 7252 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 7253 if len(path_parts) > 1: 7254 this = self.expression( 7255 exp.JSONExtract( 7256 this=this, 7257 expression=exp.JSONPath(expressions=path_parts), 7258 variant_extract=True, 7259 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 7260 ) 7261 ) 7262 path_parts = [exp.JSONPathRoot()] 7263 7264 return this, path_parts 7265 7266 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 7267 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 7268 7269 while self._match(TokenType.COLON): 7270 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 7271 this, path_parts = self._build_json_extract(this, path_parts) 7272 7273 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7274 7275 if key: 7276 quoted = isinstance(key, exp.Identifier) and key.quoted 7277 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 7278 7279 while True: 7280 if self._match(TokenType.DOT): 7281 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7282 7283 if next_key: 7284 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 7285 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 7286 elif self._match(TokenType.L_BRACKET): 7287 bracket_expr = self._parse_bracket_key_value() 7288 7289 if not self._match(TokenType.R_BRACKET): 7290 self.raise_error("Expected ]") 7291 7292 if bracket_expr: 7293 if bracket_expr.is_string: 7294 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7295 elif bracket_expr.is_star: 7296 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7297 elif bracket_expr.is_number: 7298 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7299 else: 7300 this, path_parts = self._build_json_extract(this, path_parts) 7301 7302 this = self.expression( 7303 exp.Bracket( 7304 this=this, expressions=[bracket_expr], json_access=True 7305 ), 7306 ) 7307 7308 elif self._match(TokenType.DCOLON): 7309 this, path_parts = self._build_json_extract(this, path_parts) 7310 7311 cast_type = self._parse_types() 7312 if cast_type: 7313 this = self.expression(exp.Cast(this=this, to=cast_type)) 7314 else: 7315 self.raise_error("Expected type after '::'") 7316 else: 7317 break 7318 7319 this, _ = self._build_json_extract(this, path_parts) 7320 7321 return this 7322 7323 def _parse_dcolon(self) -> exp.Expr | None: 7324 return self._parse_types() 7325 7326 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7327 while self._curr.token_type in self.BRACKETS: 7328 this = self._parse_bracket(this) 7329 7330 column_operators = self.COLUMN_OPERATORS 7331 cast_column_operators = self.CAST_COLUMN_OPERATORS 7332 while self._curr: 7333 op_token = self._curr.token_type 7334 7335 if op_token not in column_operators: 7336 break 7337 op = column_operators[op_token] 7338 self._advance() 7339 7340 if op_token in cast_column_operators: 7341 field = self._parse_dcolon() 7342 if not field: 7343 self.raise_error("Expected type") 7344 elif op and self._curr: 7345 field = self._parse_column_reference() or self._parse_bitwise() 7346 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7347 field = self._parse_column_ops(field) 7348 else: 7349 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7350 field = self._parse_field(any_token=True, anonymous_func=True) 7351 7352 # In t.true, t.null we should produce an Identifier node 7353 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7354 field = self.expression( 7355 exp.Identifier(this=self._prev.text), 7356 comments=field.comments, 7357 ) 7358 7359 # Function calls can be qualified, e.g., x.y.FOO() 7360 # This converts the final AST to a series of Dots leading to the function call 7361 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7362 if isinstance(field, (exp.Func, exp.Window)) and this: 7363 this = this.transform( 7364 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7365 ) 7366 7367 if op: 7368 this = op(self, this, field) 7369 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7370 this = self.expression( 7371 exp.Column( 7372 this=field, 7373 table=this.this, 7374 db=this.args.get("table"), 7375 catalog=this.args.get("db"), 7376 ), 7377 comments=this.comments, 7378 ) 7379 elif isinstance(field, exp.Window): 7380 # Move the exp.Dot's to the window's function 7381 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7382 field.set("this", window_func) 7383 this = field 7384 else: 7385 this = self.expression(exp.Dot(this=this, expression=field)) 7386 7387 if field and field.comments: 7388 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7389 7390 this = self._parse_bracket(this) 7391 7392 return this 7393 7394 def _parse_paren(self) -> exp.Expr | None: 7395 if not self._match(TokenType.L_PAREN): 7396 return None 7397 7398 comments = self._prev_comments 7399 query = self._parse_select() 7400 7401 if query: 7402 expressions = [query] 7403 else: 7404 expressions = self._parse_expressions() 7405 7406 this = seq_get(expressions, 0) 7407 7408 if not this and self._match(TokenType.R_PAREN, advance=False): 7409 this = self.expression(exp.Tuple()) 7410 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7411 this = self.expression(exp.Tuple(expressions=expressions)) 7412 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7413 this = self._parse_subquery(this=this, parse_alias=False) 7414 elif isinstance(this, (exp.Subquery, exp.Values)): 7415 this = self._parse_subquery( 7416 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7417 parse_alias=False, 7418 ) 7419 else: 7420 this = self.expression(exp.Paren(this=this)) 7421 7422 if this: 7423 this.add_comments(comments) 7424 7425 self._match_r_paren(expression=this) 7426 7427 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7428 return self._parse_window(this) 7429 7430 return this 7431 7432 def _parse_primary(self) -> exp.Expr | None: 7433 if self._match_set(self.PRIMARY_PARSERS): 7434 token_type = self._prev.token_type 7435 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7436 7437 if token_type == TokenType.STRING: 7438 expressions = [primary] 7439 while self._match(TokenType.STRING, advance=False): 7440 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7441 self.raise_error( 7442 "Adjacent string literals need to be separated by whitespace or comments" 7443 ) 7444 7445 self._advance() 7446 expressions.append(exp.Literal.string(self._prev.text)) 7447 7448 if len(expressions) > 1: 7449 return self.expression( 7450 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7451 ) 7452 7453 return primary 7454 7455 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7456 return exp.Literal.number(f"0.{self._prev.text}") 7457 7458 return self._parse_paren() 7459 7460 def _parse_field( 7461 self, 7462 any_token: bool = False, 7463 tokens: t.Collection[TokenType] | None = None, 7464 anonymous_func: bool = False, 7465 ) -> exp.Expr | None: 7466 after_dot = ( 7467 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7468 ) 7469 7470 if anonymous_func: 7471 field = ( 7472 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7473 or self._parse_primary() 7474 ) 7475 else: 7476 field = self._parse_primary() or self._parse_function( 7477 anonymous=anonymous_func, any_token=any_token 7478 ) 7479 7480 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7481 7482 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7483 name = field.name 7484 if self._is_connected() and self._parse_var(any_token=True): 7485 name += self._prev.text 7486 7487 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7488 7489 return field 7490 7491 def _parse_function( 7492 self, 7493 functions: dict[str, t.Callable] | None = None, 7494 anonymous: bool = False, 7495 optional_parens: bool = True, 7496 any_token: bool = False, 7497 ) -> exp.Expr | None: 7498 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7499 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7500 fn_syntax = False 7501 if ( 7502 self._match(TokenType.L_BRACE, advance=False) 7503 and self._next 7504 and self._next.text.upper() == "FN" 7505 ): 7506 self._advance(2) 7507 fn_syntax = True 7508 7509 func = self._parse_function_call( 7510 functions=functions, 7511 anonymous=anonymous, 7512 optional_parens=optional_parens, 7513 any_token=any_token, 7514 ) 7515 7516 if fn_syntax: 7517 self._match(TokenType.R_BRACE) 7518 7519 return func 7520 7521 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7522 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7523 7524 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7525 args = self._parse_function_args(alias=False) 7526 if not args: 7527 self.raise_error("Expected at least one argument") 7528 7529 # Wrapped so the connector keeps its precedence in the parent context 7530 return exp.Paren(this=connector(*args, copy=False)) 7531 7532 def _parse_function_call( 7533 self, 7534 functions: dict[str, t.Callable] | None = None, 7535 anonymous: bool = False, 7536 optional_parens: bool = True, 7537 any_token: bool = False, 7538 ) -> exp.Expr | None: 7539 if not self._curr: 7540 return None 7541 7542 comments = self._curr.comments 7543 prev = self._prev 7544 token = self._curr 7545 token_type = self._curr.token_type 7546 this: str | exp.Expr = self._curr.text 7547 upper = self._curr.text.upper() 7548 7549 after_dot = prev.token_type == TokenType.DOT 7550 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7551 if ( 7552 optional_parens 7553 and parser 7554 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7555 and not after_dot 7556 ): 7557 self._advance() 7558 return self._parse_window(parser(self)) 7559 7560 if self._next.token_type != TokenType.L_PAREN: 7561 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7562 self._advance() 7563 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7564 7565 return None 7566 7567 if any_token: 7568 if token_type in self.RESERVED_TOKENS: 7569 return None 7570 elif token_type not in self.FUNC_TOKENS: 7571 return None 7572 7573 self._advance(2) 7574 7575 parser = self.FUNCTION_PARSERS.get(upper) 7576 if parser and not anonymous: 7577 result = parser(self) 7578 else: 7579 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7580 7581 if subquery_predicate: 7582 expr = None 7583 if self._curr.token_type in self.SUBQUERY_TOKENS: 7584 expr = self._parse_select() 7585 self._match_r_paren() 7586 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7587 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7588 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7589 self._advance(-1) 7590 expr = self._parse_bitwise() 7591 7592 if expr: 7593 return self.expression(subquery_predicate(this=expr), comments=comments) 7594 7595 if functions is None: 7596 functions = self.FUNCTIONS 7597 7598 function = functions.get(upper) 7599 known_function = function and not anonymous 7600 7601 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7602 args = self._parse_function_args(alias) 7603 7604 post_func_comments = self._curr.comments if self._curr else None 7605 if known_function and post_func_comments: 7606 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7607 # call we'll construct it as exp.Anonymous, even if it's "known" 7608 if any( 7609 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7610 for comment in post_func_comments 7611 ): 7612 known_function = False 7613 7614 if alias and known_function: 7615 args = self._kv_to_prop_eq(args) 7616 7617 if known_function: 7618 func_builder = t.cast(t.Callable, function) 7619 7620 # mypyc compiled functions don't have __code__, so we use 7621 # try/except to check if func_builder accepts 'dialect'. 7622 try: 7623 func = func_builder(args) 7624 except TypeError: 7625 func = func_builder(args, dialect=self.dialect) 7626 7627 func = self.validate_expression(func, args) 7628 if name_key := self.dialect.ORIGINAL_NAME_META_KEY: 7629 func.meta[name_key] = this 7630 7631 result = func 7632 else: 7633 if token_type == TokenType.IDENTIFIER: 7634 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7635 7636 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7637 7638 result = result.update_positions(token) 7639 7640 if isinstance(result, exp.Expr): 7641 result.add_comments(comments) 7642 7643 if parser: 7644 self._match(TokenType.R_PAREN, expression=result) 7645 else: 7646 self._match_r_paren(result) 7647 return self._parse_window(result) 7648 7649 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7650 return expression 7651 7652 def _kv_to_prop_eq( 7653 self, expressions: list[exp.Expr], parse_map: bool = False 7654 ) -> list[exp.Expr]: 7655 transformed = [] 7656 7657 for index, e in enumerate(expressions): 7658 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7659 if isinstance(e, exp.Alias): 7660 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7661 7662 if not isinstance(e, exp.PropertyEQ): 7663 e = self.expression( 7664 exp.PropertyEQ( 7665 this=e.this if parse_map else exp.to_identifier(e.this.name), 7666 expression=e.expression, 7667 ) 7668 ) 7669 7670 if not parse_map and isinstance(e.this, exp.Column): 7671 e.this.replace(e.this.this) 7672 else: 7673 e = self._to_prop_eq(e, index) 7674 7675 transformed.append(e) 7676 7677 return transformed 7678 7679 def _parse_function_properties(self) -> exp.Properties | None: 7680 # Skip the generic `key = value` fallback in _parse_property since this 7681 # runs post-AS where a function body like `name = expr` can be misread 7682 # as a property. 7683 properties = [] 7684 while True: 7685 if self._match_texts(self.PROPERTY_PARSERS): 7686 keyword = self._prev.text.upper() 7687 prop = self.PROPERTY_PARSERS[keyword](self) 7688 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7689 keyword = self._prev.text.upper() 7690 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7691 else: 7692 break 7693 if not prop: 7694 self.raise_error(f"Failed to parse property '{keyword}'") 7695 break 7696 for p in ensure_list(prop): 7697 properties.append(p) 7698 7699 return self.expression(exp.Properties(expressions=properties)) if properties else None 7700 7701 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7702 return self._parse_statement() 7703 7704 def _parse_function_parameter(self) -> exp.Expr | None: 7705 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7706 7707 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7708 this = self._parse_table_parts(schema=True) 7709 7710 if not self._match(TokenType.L_PAREN): 7711 return this 7712 7713 expressions = self._parse_csv(self._parse_function_parameter) 7714 self._match_r_paren() 7715 return self.expression( 7716 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7717 ) 7718 7719 def _parse_macro_overloads( 7720 self, 7721 this: exp.UserDefinedFunction, 7722 first_body: exp.Expr, 7723 first_is_table: bool = False, 7724 ) -> exp.MacroOverloads: 7725 overloads = [ 7726 self.expression( 7727 exp.MacroOverload( 7728 this=first_body, 7729 expressions=this.expressions or None, 7730 is_table=first_is_table, 7731 ) 7732 ) 7733 ] 7734 this.set("expressions", None) 7735 this.set("wrapped", False) 7736 7737 while self._match(TokenType.COMMA): 7738 if not self._match(TokenType.L_PAREN): 7739 break 7740 7741 params = self._parse_csv(self._parse_function_parameter) 7742 self._match_r_paren() 7743 7744 if not self._match(TokenType.ALIAS): 7745 break 7746 7747 is_table = self._match(TokenType.TABLE) 7748 body = self._parse_expression() 7749 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7750 overloads.append(self.expression(macro)) 7751 7752 return self.expression(exp.MacroOverloads(expressions=overloads)) 7753 7754 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7755 literal = self._parse_primary() 7756 if literal: 7757 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7758 7759 return self._identifier_expression(token) 7760 7761 def _parse_session_parameter(self) -> exp.SessionParameter: 7762 kind = None 7763 this = self._parse_id_var() or self._parse_primary() 7764 7765 if this and self._match(TokenType.DOT): 7766 kind = this.name 7767 this = self._parse_var() or self._parse_primary() 7768 7769 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7770 7771 def _parse_lambda_arg(self) -> exp.Expr | None: 7772 return self._parse_id_var() 7773 7774 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7775 next_token_type = self._next.token_type 7776 7777 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7778 if ( 7779 next_token_type in self.LAMBDA_ARG_TERMINATORS 7780 and (atom := self._parse_atom()) is not None 7781 ): 7782 return atom 7783 7784 index = self._index 7785 7786 if self._match(TokenType.L_PAREN): 7787 expressions = t.cast( 7788 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7789 ) 7790 7791 if not self._match(TokenType.R_PAREN): 7792 self._retreat(index) 7793 elif self._match_set(self.LAMBDAS): 7794 return self.LAMBDAS[self._prev.token_type](self, expressions) 7795 else: 7796 self._retreat(index) 7797 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7798 expressions = [self._parse_lambda_arg()] 7799 7800 if self._match_set(self.LAMBDAS): 7801 return self.LAMBDAS[self._prev.token_type](self, expressions) 7802 7803 self._retreat(index) 7804 7805 this: exp.Expr | None 7806 7807 if self._match(TokenType.DISTINCT): 7808 this = self.expression( 7809 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7810 ) 7811 else: 7812 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7813 this = self._parse_select_or_expression(alias=alias) 7814 7815 return self._parse_limit( 7816 self._parse_respect_or_ignore_nulls( 7817 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7818 ) 7819 ) 7820 7821 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7822 index = self._index 7823 if not self._match(TokenType.L_PAREN): 7824 return this 7825 7826 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7827 # expr can be of both types 7828 if self._match_set(self.SELECT_START_TOKENS): 7829 self._retreat(index) 7830 return this 7831 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7832 self._match_r_paren() 7833 return self.expression(exp.Schema(this=this, expressions=args)) 7834 7835 def _parse_field_def(self) -> exp.Expr | None: 7836 return self._parse_column_def(self._parse_field(any_token=True)) 7837 7838 def _parse_column_def( 7839 self, this: exp.Expr | None, computed_column: bool = True 7840 ) -> exp.Expr | None: 7841 # column defs are not really columns, they're identifiers 7842 if isinstance(this, exp.Column): 7843 this = this.this 7844 7845 if not computed_column: 7846 self._match(TokenType.ALIAS) 7847 7848 kind = self._parse_types(schema=True) 7849 7850 if self._match_text_seq("FOR", "ORDINALITY"): 7851 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7852 7853 constraints: list[exp.Expr] = [] 7854 7855 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7856 ("ALIAS", "MATERIALIZED") 7857 ): 7858 # Match storage before _parse_types so STORED is not treated as a data type 7859 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7860 persisted = self._prev.text.upper() == "MATERIALIZED" 7861 expression = self._parse_disjunction() 7862 if not persisted: 7863 if self._match_text_seq("PERSISTED"): 7864 persisted = True 7865 elif self._match_texts(("STORED", "VIRTUAL")): 7866 persisted = self._prev.text.upper() == "STORED" 7867 constraint_kind = exp.ComputedColumnConstraint( 7868 this=expression, 7869 persisted=persisted, 7870 data_type=exp.Var(this="AUTO") 7871 if self._match_text_seq("AUTO") 7872 else self._parse_types(), 7873 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7874 ) 7875 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7876 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7877 in_out_constraint = self.expression( 7878 exp.InOutColumnConstraint( 7879 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7880 ) 7881 ) 7882 constraints.append(in_out_constraint) 7883 kind = self._parse_types() 7884 elif ( 7885 kind 7886 and self._match(TokenType.ALIAS, advance=False) 7887 and ( 7888 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7889 or self._next.token_type == TokenType.L_PAREN 7890 ) 7891 ): 7892 self._advance() 7893 constraints.append( 7894 self.expression( 7895 exp.ColumnConstraint( 7896 kind=exp.ComputedColumnConstraint( 7897 this=self._parse_disjunction(), 7898 persisted=self._match_texts(("STORED", "VIRTUAL")) 7899 and self._prev.text.upper() == "STORED", 7900 ) 7901 ) 7902 ) 7903 ) 7904 7905 while True: 7906 constraint = self._parse_column_constraint() 7907 if not constraint: 7908 break 7909 constraints.append(constraint) 7910 7911 if not kind and not constraints: 7912 return this 7913 7914 position = None 7915 if self._match_texts(("FIRST", "AFTER")): 7916 pos = self._prev.text 7917 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7918 7919 return self.expression( 7920 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7921 ) 7922 7923 def _parse_auto_increment( 7924 self, 7925 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7926 start = None 7927 increment = None 7928 order = None 7929 7930 if self._match(TokenType.L_PAREN, advance=False): 7931 args = self._parse_wrapped_csv(self._parse_bitwise) 7932 start = seq_get(args, 0) 7933 increment = seq_get(args, 1) 7934 7935 # The remaining parts form an unordered bag and any of them can be omitted, in which 7936 # case the engine falls back to its own default, so they're parsed independently. 7937 while True: 7938 if self._match_text_seq("START"): 7939 start = self._parse_bitwise() 7940 elif self._match_text_seq("INCREMENT"): 7941 increment = self._parse_bitwise() 7942 elif self._match_text_seq("ORDER"): 7943 order = True 7944 elif self._match_text_seq("NOORDER"): 7945 order = False 7946 else: 7947 break 7948 7949 if start or increment or order is not None: 7950 return exp.GeneratedAsIdentityColumnConstraint( 7951 start=start, increment=increment, this=False, order=order 7952 ) 7953 7954 return exp.AutoIncrementColumnConstraint() 7955 7956 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7957 if not self._match(TokenType.L_PAREN, advance=False): 7958 return None 7959 7960 return self.expression( 7961 exp.CheckColumnConstraint( 7962 this=self._parse_wrapped(self._parse_assignment), 7963 enforced=self._match_text_seq("ENFORCED"), 7964 ) 7965 ) 7966 7967 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7968 if not self._match_text_seq("REFRESH"): 7969 self._retreat(self._index - 1) 7970 return None 7971 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7972 7973 def _parse_compress(self) -> exp.CompressColumnConstraint: 7974 if self._match(TokenType.L_PAREN, advance=False): 7975 return self.expression( 7976 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7977 ) 7978 7979 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7980 7981 def _parse_generated_as_identity( 7982 self, 7983 ) -> ( 7984 exp.GeneratedAsIdentityColumnConstraint 7985 | exp.ComputedColumnConstraint 7986 | exp.GeneratedAsRowColumnConstraint 7987 ): 7988 if self._match_text_seq("BY", "DEFAULT"): 7989 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7990 this = self.expression( 7991 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7992 ) 7993 else: 7994 self._match_text_seq("ALWAYS") 7995 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7996 7997 self._match(TokenType.ALIAS) 7998 7999 if self._match_text_seq("ROW"): 8000 start = self._match_text_seq("START") 8001 if not start: 8002 self._match(TokenType.END) 8003 hidden = self._match_text_seq("HIDDEN") 8004 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 8005 8006 identity = self._match_text_seq("IDENTITY") 8007 8008 if self._match(TokenType.L_PAREN): 8009 if self._match_text_seq("START", "WITH"): 8010 this.set("start", self._parse_bitwise()) 8011 if self._match_text_seq("INCREMENT", "BY"): 8012 this.set("increment", self._parse_bitwise()) 8013 if self._match_text_seq("MINVALUE"): 8014 this.set("minvalue", self._parse_bitwise()) 8015 if self._match_text_seq("MAXVALUE"): 8016 this.set("maxvalue", self._parse_bitwise()) 8017 8018 if self._match_text_seq("CYCLE"): 8019 this.set("cycle", True) 8020 elif self._match_text_seq("NO", "CYCLE"): 8021 this.set("cycle", False) 8022 8023 if not identity: 8024 this.set("expression", self._parse_disjunction()) 8025 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 8026 args = self._parse_csv(self._parse_bitwise) 8027 this.set("start", seq_get(args, 0)) 8028 this.set("increment", seq_get(args, 1)) 8029 8030 self._match_r_paren() 8031 8032 return this 8033 8034 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 8035 self._match_text_seq("LENGTH") 8036 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 8037 8038 def _parse_not_constraint(self) -> exp.Expr | None: 8039 if self._match_text_seq("NULL"): 8040 return self.expression(exp.NotNullColumnConstraint()) 8041 if self._match_text_seq("CASESPECIFIC"): 8042 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 8043 if self._match_text_seq("FOR", "REPLICATION"): 8044 return self.expression(exp.NotForReplicationColumnConstraint()) 8045 8046 # Unconsume the `NOT` token 8047 self._retreat(self._index - 1) 8048 return None 8049 8050 def _parse_column_constraint(self) -> exp.Expr | None: 8051 this = ( 8052 self._parse_id_var() 8053 if self._match(TokenType.CONSTRAINT) 8054 and not self._match_texts(self.RESERVED_CONSTRAINT_KINDS, advance=False) 8055 else None 8056 ) 8057 8058 procedure_option_follows = ( 8059 self._match(TokenType.WITH, advance=False) 8060 and self._next 8061 and self._next.text.upper() in self.PROCEDURE_OPTIONS 8062 ) 8063 8064 index = self._index 8065 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 8066 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 8067 if not constraint: 8068 self._retreat(index) 8069 return None 8070 8071 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 8072 8073 if self._match(TokenType.CHARACTER_SET) or self._match_pair(TokenType.CHAR, TokenType.SET): 8074 return self.expression( 8075 exp.ColumnConstraint( 8076 this=this, 8077 kind=self.expression( 8078 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 8079 ), 8080 ) 8081 ) 8082 8083 return this 8084 8085 def _parse_constraint(self) -> exp.Expr | None: 8086 if not self._match(TokenType.CONSTRAINT) or self._match_texts( 8087 self.RESERVED_CONSTRAINT_KINDS, advance=False 8088 ): 8089 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 8090 8091 return self.expression( 8092 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 8093 ) 8094 8095 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 8096 constraints = [] 8097 while True: 8098 constraint = self._parse_unnamed_constraint() or self._parse_function() 8099 if not constraint: 8100 break 8101 constraints.append(constraint) 8102 8103 return constraints 8104 8105 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 8106 index = self._index 8107 8108 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 8109 constraints or self.CONSTRAINT_PARSERS 8110 ): 8111 return None 8112 8113 constraint_key = self._prev.text.upper() 8114 if constraint_key not in self.CONSTRAINT_PARSERS: 8115 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 8116 8117 result = self.CONSTRAINT_PARSERS[constraint_key](self) 8118 if not result: 8119 self._retreat(index) 8120 8121 return result 8122 8123 def _parse_unique_key(self) -> exp.Expr | None: 8124 if ( 8125 self._curr 8126 and self._curr.token_type != TokenType.IDENTIFIER 8127 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 8128 ): 8129 return None 8130 return self._parse_id_var(any_token=False) 8131 8132 def _parse_unique(self) -> exp.UniqueColumnConstraint: 8133 self._match_texts(("KEY", "INDEX")) 8134 return self.expression( 8135 exp.UniqueColumnConstraint( 8136 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 8137 this=self._parse_schema(self._parse_unique_key()), 8138 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 8139 on_conflict=self._parse_on_conflict(), 8140 options=self._parse_key_constraint_options(), 8141 ) 8142 ) 8143 8144 def _parse_key_constraint_options(self) -> list[str]: 8145 options = [] 8146 while True: 8147 if not self._curr: 8148 break 8149 8150 if self._match(TokenType.ON): 8151 action = None 8152 on = self._advance_any() and self._prev.text 8153 8154 if self._match_text_seq("NO", "ACTION"): 8155 action = "NO ACTION" 8156 elif self._match_text_seq("CASCADE"): 8157 action = "CASCADE" 8158 elif self._match_text_seq("RESTRICT"): 8159 action = "RESTRICT" 8160 elif self._match_pair(TokenType.SET, TokenType.NULL): 8161 action = "SET NULL" 8162 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 8163 action = "SET DEFAULT" 8164 else: 8165 self.raise_error("Invalid key constraint") 8166 8167 options.append(f"ON {on} {action}") 8168 else: 8169 var = self._parse_var_from_options( 8170 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 8171 ) 8172 if not var: 8173 break 8174 options.append(var.name) 8175 8176 return options 8177 8178 def _parse_references(self, match: bool = True) -> exp.Reference | None: 8179 if match and not self._match(TokenType.REFERENCES): 8180 return None 8181 8182 expressions: list | None = None 8183 this = self._parse_table(schema=True) 8184 options = self._parse_key_constraint_options() 8185 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 8186 8187 def _parse_foreign_key(self) -> exp.ForeignKey: 8188 expressions = ( 8189 self._parse_wrapped_id_vars() 8190 if not self._match(TokenType.REFERENCES, advance=False) 8191 else None 8192 ) 8193 reference = self._parse_references() 8194 on_options = {} 8195 8196 while self._match(TokenType.ON): 8197 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 8198 self.raise_error("Expected DELETE or UPDATE") 8199 8200 kind = self._prev.text.lower() 8201 8202 if self._match_text_seq("NO", "ACTION"): 8203 action = "NO ACTION" 8204 elif self._match(TokenType.SET): 8205 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 8206 action = "SET " + self._prev.text.upper() 8207 else: 8208 self._advance() 8209 action = self._prev.text.upper() 8210 8211 on_options[kind] = action 8212 8213 return self.expression( 8214 exp.ForeignKey( 8215 expressions=expressions, 8216 reference=reference, 8217 options=self._parse_key_constraint_options(), 8218 **on_options, 8219 ) 8220 ) 8221 8222 def _parse_primary_key_part(self) -> exp.Expr | None: 8223 return self._parse_field() 8224 8225 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 8226 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 8227 self._retreat(self._index - 1) 8228 return None 8229 8230 id_vars = self._parse_wrapped_id_vars() 8231 return self.expression( 8232 exp.PeriodForSystemTimeConstraint( 8233 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 8234 ) 8235 ) 8236 8237 def _parse_primary_key( 8238 self, 8239 wrapped_optional: bool = False, 8240 in_props: bool = False, 8241 named_primary_key: bool = False, 8242 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 8243 desc = ( 8244 self._prev.token_type == TokenType.DESC 8245 if self._match_set((TokenType.ASC, TokenType.DESC)) 8246 else None 8247 ) 8248 8249 this = None 8250 if ( 8251 named_primary_key 8252 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 8253 and self._next 8254 and self._next.token_type == TokenType.L_PAREN 8255 ): 8256 this = self._parse_id_var() 8257 8258 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 8259 return self.expression( 8260 exp.PrimaryKeyColumnConstraint( 8261 desc=desc, options=self._parse_key_constraint_options() 8262 ) 8263 ) 8264 8265 expressions = self._parse_wrapped_csv( 8266 self._parse_primary_key_part, optional=wrapped_optional 8267 ) 8268 8269 return self.expression( 8270 exp.PrimaryKey( 8271 this=this, 8272 expressions=expressions, 8273 include=self._parse_index_params(), 8274 options=self._parse_key_constraint_options(), 8275 ) 8276 ) 8277 8278 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 8279 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 8280 8281 def _parse_odbc_datetime_literal(self) -> exp.Expr: 8282 """ 8283 Parses a datetime column in ODBC format. We parse the column into the corresponding 8284 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 8285 same as we did for `DATE('yyyy-mm-dd')`. 8286 8287 Reference: 8288 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 8289 """ 8290 self._match(TokenType.VAR) 8291 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 8292 expression = self.expression(exp_class(this=self._parse_string())) 8293 if not self._match(TokenType.R_BRACE): 8294 self.raise_error("Expected }") 8295 return expression 8296 8297 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 8298 if not self._match_set(self.BRACKETS): 8299 return this 8300 8301 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8302 map_token = seq_get(self._tokens, self._index - 2) 8303 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8304 else: 8305 parse_map = False 8306 8307 bracket_kind = self._prev.token_type 8308 if ( 8309 bracket_kind == TokenType.L_BRACE 8310 and self._curr 8311 and self._curr.token_type == TokenType.VAR 8312 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8313 ): 8314 return self._parse_odbc_datetime_literal() 8315 8316 expressions = self._parse_csv( 8317 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8318 ) 8319 8320 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8321 self.raise_error("Expected ]") 8322 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8323 self.raise_error("Expected }") 8324 8325 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8326 if bracket_kind == TokenType.L_BRACE: 8327 this = self.expression( 8328 exp.Struct( 8329 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8330 ) 8331 ) 8332 elif not this: 8333 this = build_array_constructor( 8334 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8335 ) 8336 else: 8337 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8338 if constructor_type: 8339 return build_array_constructor( 8340 constructor_type, 8341 args=expressions, 8342 bracket_kind=bracket_kind, 8343 dialect=self.dialect, 8344 ) 8345 8346 expressions = apply_index_offset( 8347 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8348 ) 8349 this = self.expression( 8350 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8351 ) 8352 8353 self._add_comments(this) 8354 return self._parse_bracket(this) 8355 8356 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8357 if not self._match(TokenType.COLON): 8358 return this 8359 8360 # DuckDB's `-` in `[a:-:step]` is the default end, which depends on the sign of step 8361 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8362 self._advance() 8363 end: exp.Expr | None = None 8364 else: 8365 end = self._parse_assignment() 8366 step = self._parse_unary() if self._match(TokenType.COLON) else None 8367 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8368 8369 def _parse_case(self) -> exp.Expr | None: 8370 if self._match(TokenType.DOT, advance=False): 8371 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8372 self._retreat(self._index - 1) 8373 return None 8374 8375 ifs = [] 8376 default = None 8377 8378 comments = self._prev_comments 8379 expression = self._parse_disjunction() 8380 8381 while self._match(TokenType.WHEN): 8382 this = self._parse_disjunction() 8383 self._match(TokenType.THEN) 8384 then = self._parse_disjunction() 8385 ifs.append(self.expression(exp.If(this=this, true=then))) 8386 8387 if self._match(TokenType.ELSE): 8388 default = self._parse_disjunction() 8389 8390 if not self._match(TokenType.END): 8391 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8392 default = exp.column("interval") 8393 else: 8394 self.raise_error("Expected END after CASE", self._prev) 8395 8396 return self.expression( 8397 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8398 ) 8399 8400 def _parse_if(self) -> exp.Expr | None: 8401 if self._match(TokenType.L_PAREN): 8402 args = self._parse_csv( 8403 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8404 ) 8405 this = self.validate_expression(exp.If.from_arg_list(args), args) 8406 self._match_r_paren() 8407 else: 8408 index = self._index - 1 8409 8410 if self.NO_PAREN_IF_COMMANDS and index == 0: 8411 return self._parse_as_command(self._prev) 8412 8413 condition = self._parse_disjunction() 8414 8415 if not condition or not self._match(TokenType.THEN): 8416 self._retreat(index) 8417 return None 8418 8419 true = self._parse_disjunction() 8420 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8421 self._match(TokenType.END) 8422 this = self.expression(exp.If(this=condition, true=true, false=false)) 8423 8424 return this 8425 8426 def _parse_next_value_for(self) -> exp.Expr | None: 8427 if not self._match_text_seq("VALUE", "FOR"): 8428 self._retreat(self._index - 1) 8429 return None 8430 8431 return self.expression( 8432 exp.NextValueFor( 8433 this=self._parse_column(), 8434 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8435 ) 8436 ) 8437 8438 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8439 this = self._parse_function() or self._parse_var_or_string(upper=True) 8440 8441 if self._match(TokenType.FROM): 8442 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8443 8444 if not self._match(TokenType.COMMA): 8445 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8446 8447 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8448 8449 def _parse_gap_fill(self) -> exp.GapFill: 8450 self._match(TokenType.TABLE) 8451 this = self._parse_table() 8452 8453 self._match(TokenType.COMMA) 8454 args = [this, *self._parse_csv(self._parse_lambda)] 8455 8456 gap_fill = exp.GapFill.from_arg_list(args) 8457 return self.validate_expression(gap_fill, args) 8458 8459 def _parse_char(self) -> exp.Chr: 8460 return self.expression( 8461 exp.Chr( 8462 expressions=self._parse_csv(self._parse_assignment), 8463 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8464 ) 8465 ) 8466 8467 def _parse_charset_name(self) -> exp.Expr | None: 8468 """ 8469 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8470 for specific name shapes override this. 8471 """ 8472 return self._parse_var( 8473 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8474 ) 8475 8476 def _parse_weight_string(self) -> exp.WeightString | exp.Anonymous: 8477 name = self._tokens[self._index - 2].text 8478 args = self._parse_csv(self._parse_assignment) 8479 8480 # MySQL also accepts an undocumented WEIGHT_STRING(str, n, n, n) form 8481 if len(args) != 1: 8482 return self.expression(exp.Anonymous(this=name, expressions=args)) 8483 8484 return self.expression( 8485 exp.WeightString(this=args[0], to=self._match(TokenType.ALIAS) and self._parse_types()) 8486 ) 8487 8488 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8489 this = self._parse_assignment() 8490 8491 if not self._match(TokenType.ALIAS): 8492 if self._match(TokenType.COMMA): 8493 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8494 8495 self.raise_error("Expected AS after CAST") 8496 8497 fmt = None 8498 to = self._parse_types(with_collation=True) 8499 8500 default = None 8501 if self._match(TokenType.DEFAULT): 8502 default = self._parse_bitwise() 8503 self._match_text_seq("ON", "CONVERSION", "ERROR") 8504 8505 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8506 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8507 fmt = self._parse_at_time_zone(fmt_string) 8508 8509 if not to: 8510 to = exp.DType.UNKNOWN.into_expr() 8511 if to.this in exp.DataType.TEMPORAL_TYPES: 8512 this = self.expression( 8513 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8514 this=this, 8515 format=exp.Literal.string( 8516 format_time( 8517 fmt_string.this if fmt_string else "", 8518 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8519 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8520 ) 8521 ), 8522 safe=safe, 8523 ) 8524 ) 8525 8526 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8527 this.set("zone", fmt.args["zone"]) 8528 return this 8529 elif not to: 8530 self.raise_error("Expected TYPE after CAST") 8531 elif isinstance(to, exp.Identifier): 8532 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8533 else: 8534 to = self._parse_char_charset(to) 8535 8536 return self.build_cast( 8537 strict=strict, 8538 this=this, 8539 to=to, 8540 format=fmt, 8541 safe=safe, 8542 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8543 default=default, 8544 ) 8545 8546 def _parse_string_agg(self) -> exp.GroupConcat: 8547 if self._match(TokenType.DISTINCT): 8548 args: list[exp.Expr | None] = [ 8549 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8550 ] 8551 if self._match(TokenType.COMMA): 8552 args.extend(self._parse_csv(self._parse_disjunction)) 8553 else: 8554 self._match(TokenType.ALL) 8555 args = self._parse_csv(self._parse_disjunction) # type: ignore 8556 8557 if self._match_text_seq("ON", "OVERFLOW"): 8558 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8559 if self._match_text_seq("ERROR"): 8560 on_overflow: exp.Expr | None = exp.var("ERROR") 8561 else: 8562 self._match_text_seq("TRUNCATE") 8563 on_overflow = self.expression( 8564 exp.OverflowTruncateBehavior( 8565 this=self._parse_string(), 8566 with_count=( 8567 self._match_text_seq("WITH", "COUNT") 8568 or not self._match_text_seq("WITHOUT", "COUNT") 8569 ), 8570 ) 8571 ) 8572 else: 8573 on_overflow = None 8574 8575 index = self._index 8576 if not self._match(TokenType.R_PAREN) and args: 8577 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8578 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8579 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8580 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8581 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8582 8583 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8584 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8585 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8586 if not self._match_text_seq("WITHIN", "GROUP"): 8587 self._retreat(index) 8588 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8589 8590 # The corresponding match_r_paren will be called in parse_function (caller) 8591 self._match_l_paren() 8592 8593 return self.expression( 8594 exp.GroupConcat( 8595 this=self._parse_order(this=seq_get(args, 0)), 8596 separator=seq_get(args, 1), 8597 on_overflow=on_overflow, 8598 ) 8599 ) 8600 8601 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8602 this = self._parse_bitwise() 8603 8604 if self._match(TokenType.USING): 8605 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8606 elif self._match(TokenType.COMMA): 8607 to = self._parse_char_charset(self._parse_types()) 8608 else: 8609 to = None 8610 8611 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8612 8613 def _parse_char_charset(self, to: exp.Expr | None) -> exp.Expr | None: 8614 # MySQL: CHAR[(N)] CHARACTER SET charset_name, the length is kept 8615 if ( 8616 isinstance(to, exp.DataType) 8617 and to.this == exp.DType.CHAR 8618 and (self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")) 8619 ): 8620 to.set("this", exp.DType.CHARACTER_SET) 8621 to.set("kind", self._parse_var_or_string()) 8622 return to 8623 8624 def _parse_xml_element(self) -> exp.XMLElement: 8625 if self._match_text_seq("EVALNAME"): 8626 evalname = True 8627 this = self._parse_bitwise() 8628 else: 8629 evalname = None 8630 self._match_text_seq("NAME") 8631 this = self._parse_id_var() 8632 8633 return self.expression( 8634 exp.XMLElement( 8635 this=this, 8636 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8637 evalname=evalname, 8638 ) 8639 ) 8640 8641 def _parse_xml_table(self) -> exp.XMLTable: 8642 namespaces = None 8643 passing = None 8644 columns = None 8645 8646 if self._match_text_seq("XMLNAMESPACES", "("): 8647 namespaces = self._parse_xml_namespace() 8648 self._match_text_seq(")", ",") 8649 8650 this = self._parse_string() 8651 8652 if self._match_text_seq("PASSING"): 8653 # The BY VALUE keywords are optional and are provided for semantic clarity 8654 self._match_text_seq("BY", "VALUE") 8655 passing = self._parse_csv(self._parse_column) 8656 8657 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8658 8659 if self._match_text_seq("COLUMNS"): 8660 columns = self._parse_csv(self._parse_field_def) 8661 8662 return self.expression( 8663 exp.XMLTable( 8664 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8665 ) 8666 ) 8667 8668 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8669 namespaces = [] 8670 8671 while True: 8672 if self._match(TokenType.DEFAULT): 8673 uri = self._parse_string() 8674 else: 8675 uri = self._parse_alias(self._parse_string()) 8676 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8677 if not self._match(TokenType.COMMA): 8678 break 8679 8680 return namespaces 8681 8682 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8683 args = self._parse_csv(self._parse_disjunction) 8684 8685 if len(args) < 3: 8686 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8687 8688 return self.expression(exp.DecodeCase(expressions=args)) 8689 8690 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8691 self._match_text_seq("KEY") 8692 key = self._parse_column() 8693 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8694 self._match_text_seq("VALUE") 8695 value = self._parse_bitwise() 8696 8697 if not key and not value: 8698 return None 8699 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8700 8701 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8702 if not this or not self._match_text_seq("FORMAT", "JSON"): 8703 return this 8704 8705 return self.expression(exp.FormatJson(this=this)) 8706 8707 def _parse_on_condition(self) -> exp.OnCondition | None: 8708 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8709 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8710 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8711 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8712 else: 8713 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8714 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8715 8716 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8717 8718 if not empty and not error and not null: 8719 return None 8720 8721 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8722 8723 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8724 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8725 for value in values: 8726 if self._match_text_seq(value, "ON", on): 8727 return f"{value} ON {on}" 8728 8729 index = self._index 8730 if self._match(TokenType.DEFAULT): 8731 default_value = self._parse_bitwise() 8732 if self._match_text_seq("ON", on): 8733 return default_value 8734 8735 self._retreat(index) 8736 8737 return None 8738 8739 @t.overload 8740 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8741 8742 @t.overload 8743 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8744 8745 def _parse_json_object(self, agg=False): 8746 star = self._parse_star() 8747 expressions = ( 8748 [star] 8749 if star 8750 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8751 ) 8752 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8753 8754 unique_keys = None 8755 if self._match_text_seq("WITH", "UNIQUE"): 8756 unique_keys = True 8757 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8758 unique_keys = False 8759 8760 self._match_text_seq("KEYS") 8761 8762 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8763 self._parse_type() 8764 ) 8765 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8766 8767 return self.expression( 8768 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8769 expressions=expressions, 8770 null_handling=null_handling, 8771 unique_keys=unique_keys, 8772 return_type=return_type, 8773 encoding=encoding, 8774 ) 8775 ) 8776 8777 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8778 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8779 if not self._match_text_seq("NESTED"): 8780 this = self._parse_id_var() 8781 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8782 kind = self._parse_types(allow_identifiers=False) 8783 nested = None 8784 else: 8785 this = None 8786 ordinality = None 8787 kind = None 8788 nested = True 8789 8790 format_json = self._match_text_seq("FORMAT", "JSON") 8791 path = self._match_text_seq("PATH") and self._parse_string() 8792 nested_schema = nested and self._parse_json_schema() 8793 8794 return self.expression( 8795 exp.JSONColumnDef( 8796 this=this, 8797 kind=kind, 8798 path=path, 8799 nested_schema=nested_schema, 8800 ordinality=ordinality, 8801 format_json=format_json, 8802 ) 8803 ) 8804 8805 def _parse_json_schema(self) -> exp.JSONSchema: 8806 self._match_text_seq("COLUMNS") 8807 return self.expression( 8808 exp.JSONSchema( 8809 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8810 ) 8811 ) 8812 8813 def _parse_json_table(self) -> exp.JSONTable: 8814 this = self._parse_format_json(self._parse_bitwise()) 8815 path = self._match(TokenType.COMMA) and self._parse_string() 8816 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8817 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8818 schema = self._parse_json_schema() 8819 8820 return exp.JSONTable( 8821 this=this, 8822 schema=schema, 8823 path=path, 8824 error_handling=error_handling, 8825 empty_handling=empty_handling, 8826 ) 8827 8828 def _parse_match_against(self) -> exp.MatchAgainst: 8829 if self._match_text_seq("TABLE"): 8830 # parse SingleStore MATCH(TABLE ...) syntax 8831 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8832 expressions = [] 8833 table = self._parse_table() 8834 if table: 8835 expressions = [table] 8836 else: 8837 expressions = self._parse_csv(self._parse_column) 8838 8839 self._match_text_seq(")", "AGAINST", "(") 8840 8841 this = self._parse_string() 8842 8843 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8844 modifier = "IN NATURAL LANGUAGE MODE" 8845 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8846 modifier = f"{modifier} WITH QUERY EXPANSION" 8847 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8848 modifier = "IN BOOLEAN MODE" 8849 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8850 modifier = "WITH QUERY EXPANSION" 8851 else: 8852 modifier = None 8853 8854 return self.expression( 8855 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8856 ) 8857 8858 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8859 def _parse_open_json(self) -> exp.OpenJSON: 8860 this = self._parse_bitwise() 8861 path = self._match(TokenType.COMMA) and self._parse_string() 8862 8863 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8864 this = self._parse_field(any_token=True) 8865 kind = self._parse_types() 8866 path = self._parse_string() 8867 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8868 8869 return self.expression( 8870 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8871 ) 8872 8873 expressions = None 8874 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8875 self._match_l_paren() 8876 expressions = self._parse_csv(_parse_open_json_column_def) 8877 8878 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8879 8880 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8881 args = self._parse_csv(self._parse_bitwise) 8882 8883 if self._match(TokenType.IN): 8884 return self.expression( 8885 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8886 ) 8887 8888 if haystack_first: 8889 haystack = seq_get(args, 0) 8890 needle = seq_get(args, 1) 8891 else: 8892 haystack = seq_get(args, 1) 8893 needle = seq_get(args, 0) 8894 8895 return self.expression( 8896 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8897 ) 8898 8899 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8900 args = self._parse_csv(self._parse_table) 8901 return exp.JoinHint(this=func_name.upper(), expressions=args) 8902 8903 def _parse_substring(self) -> exp.Substring: 8904 # Postgres supports the form: substring(string [from int] [for int]) 8905 # (despite being undocumented, the reverse order also works) 8906 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8907 8908 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8909 8910 start, length = None, None 8911 8912 while self._curr: 8913 if self._match(TokenType.FROM): 8914 start = self._parse_bitwise() 8915 elif self._match(TokenType.FOR): 8916 if not start: 8917 start = exp.Literal.number(1) 8918 length = self._parse_bitwise() 8919 else: 8920 break 8921 8922 if start: 8923 args.append(start) 8924 if length: 8925 args.append(length) 8926 8927 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8928 8929 def _parse_trim(self) -> exp.Trim: 8930 # https://www.w3resource.com/sql/character-functions/trim.php 8931 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8932 8933 position = None 8934 collation = None 8935 expression = None 8936 8937 if self._match_texts(self.TRIM_TYPES): 8938 position = self._prev.text.upper() 8939 8940 this = self._parse_bitwise() 8941 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8942 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8943 expression = self._parse_bitwise() 8944 8945 if invert_order: 8946 this, expression = expression, this 8947 8948 if self._match(TokenType.COLLATE): 8949 collation = self._parse_bitwise() 8950 8951 return self.expression( 8952 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8953 ) 8954 8955 def _parse_window_clause(self) -> list[exp.Expr] | None: 8956 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8957 8958 def _parse_named_window(self) -> exp.Expr | None: 8959 return self._parse_window(self._parse_id_var(), alias=True) 8960 8961 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8962 if self._curr.token_type == TokenType.VAR: 8963 if self._match_text_seq("IGNORE", "NULLS"): 8964 return self.expression(exp.IgnoreNulls(this=this)) 8965 if self._match_text_seq("RESPECT", "NULLS"): 8966 return self.expression(exp.RespectNulls(this=this)) 8967 return this 8968 8969 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8970 if self._match(TokenType.HAVING): 8971 self._match_texts(("MAX", "MIN")) 8972 max = self._prev.text.upper() != "MIN" 8973 return self.expression( 8974 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8975 ) 8976 8977 return this 8978 8979 def _parse_per(self, this: exp.Expr | None) -> exp.Expr | None: 8980 if self._next.token_type == TokenType.L_PAREN and self._match_text_seq("PER"): 8981 return self.expression( 8982 exp.Per(this=this, expressions=self._parse_wrapped_csv(self._parse_disjunction)) 8983 ) 8984 8985 return this 8986 8987 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8988 func = this 8989 comments = func.comments if isinstance(func, exp.Expr) else None 8990 8991 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8992 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8993 if self._match_text_seq("FROM", "FIRST"): 8994 this.set("from_first", True) 8995 elif self._match_text_seq("FROM", "LAST"): 8996 this.set("from_first", False) 8997 8998 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8999 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 9000 if self._match_text_seq("WITHIN", "GROUP"): 9001 order = self._parse_wrapped(self._parse_order) 9002 this = self.expression(exp.WithinGroup(this=this, expression=order)) 9003 9004 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 9005 self._match(TokenType.WHERE) 9006 this = self.expression( 9007 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 9008 ) 9009 self._match_r_paren() 9010 9011 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 9012 # Some dialects choose to implement and some do not. 9013 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 9014 9015 # There is some code above in _parse_lambda that handles 9016 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 9017 9018 # The below changes handle 9019 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 9020 9021 # Oracle allows both formats 9022 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 9023 # and Snowflake chose to do the same for familiarity 9024 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 9025 if isinstance(this, exp.AggFunc): 9026 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 9027 9028 if ignore_respect and ignore_respect is not this: 9029 ignore_respect.replace(ignore_respect.this) 9030 this = self.expression(ignore_respect.__class__(this=this)) 9031 9032 this = self._parse_respect_or_ignore_nulls(this) 9033 9034 # bigquery select from window x AS (partition by ...) 9035 if alias: 9036 over = None 9037 self._match(TokenType.ALIAS) 9038 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 9039 return self._parse_per(this) 9040 else: 9041 over = self._prev.text.upper() 9042 9043 if comments and isinstance(func, exp.Expr): 9044 func.pop_comments() 9045 9046 if not self._match(TokenType.L_PAREN): 9047 return self._parse_per( 9048 self.expression( 9049 exp.Window(this=this, alias=self._parse_id_var(False), over=over), 9050 comments=comments, 9051 ) 9052 ) 9053 9054 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 9055 9056 first: bool | None = True if self._match(TokenType.FIRST) else None 9057 if self._match_text_seq("LAST"): 9058 first = False 9059 9060 partition, order = self._parse_partition_and_order() 9061 kind = ( 9062 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 9063 ) and self._prev.text 9064 9065 if kind: 9066 self._match(TokenType.BETWEEN) 9067 start = self._parse_window_spec() 9068 9069 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 9070 exclude = ( 9071 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 9072 if self._match_text_seq("EXCLUDE") 9073 else None 9074 ) 9075 9076 spec = self.expression( 9077 exp.WindowSpec( 9078 kind=kind, 9079 start=start["value"], 9080 start_side=start["side"], 9081 end=end.get("value"), 9082 end_side=end.get("side"), 9083 exclude=exclude, 9084 ) 9085 ) 9086 else: 9087 spec = None 9088 9089 self._match_r_paren() 9090 9091 window = self.expression( 9092 exp.Window( 9093 this=this, 9094 partition_by=partition, 9095 order=order, 9096 spec=spec, 9097 alias=window_alias, 9098 over=over, 9099 first=first, 9100 ), 9101 comments=comments, 9102 ) 9103 9104 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 9105 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 9106 return self._parse_window(window, alias=alias) 9107 9108 return self._parse_per(window) 9109 9110 def _parse_partition_and_order( 9111 self, 9112 ) -> tuple[list[exp.Expr], exp.Expr | None]: 9113 return self._parse_partition_by(), self._parse_order() 9114 9115 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 9116 self._match(TokenType.BETWEEN) 9117 9118 return { 9119 "value": ( 9120 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 9121 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 9122 or self._parse_bitwise() 9123 ), 9124 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 9125 } 9126 9127 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 9128 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 9129 # so this section tries to parse the clause version and if it fails, it treats the token 9130 # as an identifier (alias) 9131 if self._can_parse_limit_or_offset(): 9132 return this 9133 9134 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 9135 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 9136 if self._can_parse_named_window(): 9137 return this 9138 9139 any_token = self._match(TokenType.ALIAS) 9140 comments = self._prev_comments 9141 9142 if explicit and not any_token: 9143 return this 9144 9145 if self._match(TokenType.L_PAREN): 9146 aliases = self.expression( 9147 exp.Aliases( 9148 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 9149 ), 9150 comments=comments, 9151 ) 9152 self._match_r_paren(aliases) 9153 return aliases 9154 9155 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 9156 self.STRING_ALIASES and self._parse_string_as_identifier() 9157 ) 9158 9159 if alias: 9160 comments.extend(alias.pop_comments()) 9161 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 9162 column = this.this 9163 9164 # Moves the comment next to the alias in `expr /* comment */ AS alias` 9165 if not this.comments and column and column.comments: 9166 this.comments = column.pop_comments() 9167 9168 return this 9169 9170 def _parse_id_var( 9171 self, 9172 any_token: bool = True, 9173 tokens: t.Collection[TokenType] | None = None, 9174 ) -> exp.Expr | None: 9175 expression = self._parse_identifier() 9176 if not expression and ( 9177 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 9178 ): 9179 quoted = self._prev.token_type == TokenType.STRING 9180 expression = self._identifier_expression(quoted=quoted) 9181 9182 return expression 9183 9184 def _parse_string(self) -> exp.Expr | None: 9185 if self._match_set(self.STRING_PARSERS): 9186 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 9187 return self._parse_placeholder() 9188 9189 def _parse_string_as_identifier(self) -> exp.Identifier | None: 9190 if not self._match(TokenType.STRING): 9191 return None 9192 output = exp.to_identifier(self._prev.text, quoted=True) 9193 output.update_positions(self._prev) 9194 return output 9195 9196 def _parse_number(self) -> exp.Expr | None: 9197 if self._match_set(self.NUMERIC_PARSERS): 9198 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 9199 return self._parse_placeholder() 9200 9201 def _parse_identifier(self) -> exp.Expr | None: 9202 if self._match(TokenType.IDENTIFIER): 9203 return self._identifier_expression(quoted=True) 9204 return self._parse_placeholder() 9205 9206 def _parse_var( 9207 self, 9208 any_token: bool = False, 9209 tokens: t.Collection[TokenType] | None = None, 9210 upper: bool = False, 9211 ) -> exp.Expr | None: 9212 if ( 9213 (any_token and self._advance_any()) 9214 or self._match(TokenType.VAR) 9215 or (self._match_set(tokens) if tokens else False) 9216 ): 9217 return self.expression( 9218 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 9219 ) 9220 return self._parse_placeholder() 9221 9222 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 9223 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 9224 self._advance() 9225 return self._prev 9226 return None 9227 9228 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 9229 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 9230 9231 def _parse_primary_or_var(self) -> exp.Expr | None: 9232 return self._parse_primary() or self._parse_var(any_token=True) 9233 9234 def _parse_null(self) -> exp.Expr | None: 9235 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 9236 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 9237 return self._parse_placeholder() 9238 9239 def _parse_boolean(self) -> exp.Expr | None: 9240 if self._match(TokenType.TRUE): 9241 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 9242 if self._match(TokenType.FALSE): 9243 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 9244 return self._parse_placeholder() 9245 9246 def _parse_star(self) -> exp.Expr | None: 9247 if self._match(TokenType.STAR): 9248 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 9249 return self._parse_placeholder() 9250 9251 def _parse_parameter(self) -> exp.Parameter: 9252 this = self._parse_identifier() or self._parse_primary_or_var() 9253 return self.expression(exp.Parameter(this=this)) 9254 9255 def _parse_placeholder(self) -> exp.Expr | None: 9256 if self._match_set(self.PLACEHOLDER_PARSERS): 9257 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 9258 if placeholder: 9259 return placeholder 9260 self._advance(-1) 9261 return None 9262 9263 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 9264 if not self._match_texts(keywords): 9265 return None 9266 if self._match(TokenType.L_PAREN, advance=False): 9267 return self._parse_wrapped_csv(self._parse_expression) 9268 9269 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 9270 return [expression] if expression else None 9271 9272 def _parse_csv( 9273 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 9274 ) -> list[T]: 9275 parse_result = parse_method() 9276 items = [parse_result] if parse_result is not None else [] 9277 9278 while self._match(sep): 9279 if isinstance(parse_result, exp.Expr): 9280 self._add_comments(parse_result) 9281 parse_result = parse_method() 9282 if parse_result is not None: 9283 items.append(parse_result) 9284 9285 return items 9286 9287 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 9288 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 9289 9290 def _parse_wrapped_csv( 9291 self, 9292 parse_method: t.Callable[[], T | None], 9293 sep: TokenType = TokenType.COMMA, 9294 optional: bool = False, 9295 ) -> list[T]: 9296 return self._parse_wrapped( 9297 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 9298 ) 9299 9300 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 9301 wrapped = self._match(TokenType.L_PAREN) 9302 if not wrapped and not optional: 9303 self.raise_error("Expecting (") 9304 parse_result = parse_method() 9305 if wrapped: 9306 self._match_r_paren() 9307 return parse_result 9308 9309 def _parse_expressions(self) -> list[exp.Expr]: 9310 return self._parse_csv(self._parse_expression) 9311 9312 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 9313 return ( 9314 self._parse_set_operations( 9315 self._parse_alias(self._parse_assignment(), explicit=True) 9316 if alias 9317 else self._parse_assignment() 9318 ) 9319 or self._parse_select() 9320 ) 9321 9322 def _parse_ddl_select(self) -> exp.Expr | None: 9323 return self._parse_query_modifiers( 9324 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 9325 ) 9326 9327 def _parse_transaction(self) -> exp.Transaction | exp.Command: 9328 this = None 9329 if self._match_texts(self.TRANSACTION_KIND): 9330 this = self._prev.text 9331 9332 self._match_texts(("TRANSACTION", "WORK")) 9333 9334 modes = [] 9335 while True: 9336 mode = [] 9337 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 9338 mode.append(self._prev.text) 9339 9340 if mode: 9341 modes.append(" ".join(mode)) 9342 if not self._match(TokenType.COMMA): 9343 break 9344 9345 return self.expression(exp.Transaction(this=this, modes=modes)) 9346 9347 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 9348 chain = None 9349 savepoint = None 9350 is_rollback = self._prev.token_type == TokenType.ROLLBACK 9351 9352 self._match_texts(("TRANSACTION", "WORK")) 9353 9354 if self._match_text_seq("TO"): 9355 self._match_text_seq("SAVEPOINT") 9356 savepoint = self._parse_id_var() 9357 9358 if self._match(TokenType.AND): 9359 chain = not self._match_text_seq("NO") 9360 self._match_text_seq("CHAIN") 9361 9362 if is_rollback: 9363 return self.expression(exp.Rollback(savepoint=savepoint)) 9364 9365 return self.expression(exp.Commit(chain=chain)) 9366 9367 def _parse_refresh(self) -> exp.Refresh | exp.Command: 9368 start = self._prev 9369 9370 if self._match_text_seq("EXTERNAL", "TABLE"): 9371 kind = "EXTERNAL TABLE" 9372 elif self._match(TokenType.TABLE): 9373 kind = "TABLE" 9374 elif self._match_text_seq("MATERIALIZED", "VIEW"): 9375 kind = "MATERIALIZED VIEW" 9376 else: 9377 kind = "" 9378 9379 this = self._parse_string() 9380 if not kind and not isinstance(this, exp.Literal): 9381 return self._parse_as_command(start) 9382 9383 return self.expression(exp.Refresh(this=this or self._parse_table(), kind=kind)) 9384 9385 def _parse_column_def_with_exists(self): 9386 start = self._index 9387 self._match(TokenType.COLUMN) 9388 9389 exists_column = self._parse_exists(not_=True) 9390 expression = self._parse_field_def() 9391 9392 if not isinstance(expression, exp.ColumnDef): 9393 self._retreat(start) 9394 return None 9395 9396 expression.set("exists", exists_column) 9397 9398 return expression 9399 9400 def _parse_add_column(self) -> exp.ColumnDef | None: 9401 if not self._prev.text.upper() == "ADD": 9402 return None 9403 9404 return self._parse_column_def_with_exists() 9405 9406 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9407 if not self._match(TokenType.DROP): 9408 return None 9409 9410 kind = None 9411 if not self.ALTER_DROP_REQUIRES_COLUMN: 9412 # A bare `DROP [IF EXISTS] c [CASCADE | RESTRICT]` drops a column 9413 index = self._index 9414 self._parse_exists() 9415 if ( 9416 self._curr.token_type not in (TokenType.COLUMN, TokenType.CONSTRAINT) 9417 and self._match_set(self.ID_VAR_TOKENS) 9418 and ( 9419 not self._curr 9420 or self._match(TokenType.COMMA, advance=False) 9421 or self._match_texts(("CASCADE", "RESTRICT"), advance=False) 9422 ) 9423 ): 9424 kind = "COLUMN" 9425 self._retreat(index) 9426 9427 return self._parse_drop(kind=kind) 9428 9429 def _parse_alter_drop_action(self) -> exp.Expr | None: 9430 drop = self._parse_drop_column() 9431 9432 if ( 9433 isinstance(drop, exp.Drop) 9434 and drop.kind == "COLUMN" 9435 and not self.dialect.ALTER_TABLE_DROP_REQUIRED_FOR_EACH_COLUMN 9436 and self._match(TokenType.COMMA) 9437 ): 9438 drop.set("tables", [*drop.args["tables"], *self._parse_csv(self._parse_column)]) 9439 9440 return drop 9441 9442 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9443 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9444 return self.expression( 9445 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9446 ) 9447 9448 def _parse_alter_table_add(self) -> list[exp.Expr]: 9449 def _parse_add_alteration() -> exp.Expr | None: 9450 self._match_text_seq("ADD") 9451 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) or self._match_texts( 9452 self.ADD_CONSTRAINT_KEYWORDS, advance=False 9453 ): 9454 return self.expression( 9455 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9456 ) 9457 9458 column_def = self._parse_add_column() 9459 if isinstance(column_def, exp.ColumnDef): 9460 return column_def 9461 9462 exists = self._parse_exists(not_=True) 9463 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9464 return self.expression( 9465 exp.AddPartition( 9466 exists=exists, 9467 this=self._parse_field(any_token=True), 9468 location=self._match_text_seq("LOCATION", advance=False) 9469 and self._parse_property(), 9470 ) 9471 ) 9472 9473 return None 9474 9475 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9476 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9477 or self._match_text_seq("COLUMNS") 9478 ): 9479 schema = self._parse_schema() 9480 9481 return ( 9482 ensure_list(schema) 9483 if schema 9484 else self._parse_csv(self._parse_column_def_with_exists) 9485 ) 9486 9487 return self._parse_csv(_parse_add_alteration) 9488 9489 def _parse_alter_table_alter(self) -> exp.Expr | None: 9490 if self._match_texts(self.ALTER_ALTER_PARSERS): 9491 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9492 9493 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9494 # keyword after ALTER we default to parsing this statement 9495 self._match(TokenType.COLUMN) 9496 exists = self._parse_exists() 9497 column = self._parse_field(any_token=True) 9498 9499 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9500 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9501 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9502 return self.expression( 9503 exp.AlterColumn( 9504 this=column, default=self._parse_disjunction(), exists=exists or None 9505 ) 9506 ) 9507 if self._match(TokenType.COMMENT): 9508 return self.expression( 9509 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9510 ) 9511 if self._match_text_seq("DROP", "NOT", "NULL"): 9512 return self.expression( 9513 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9514 ) 9515 if self._match_text_seq("SET", "NOT", "NULL"): 9516 return self.expression( 9517 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9518 ) 9519 9520 if self._match_text_seq("SET", "VISIBLE"): 9521 return self.expression( 9522 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9523 ) 9524 if self._match_text_seq("SET", "INVISIBLE"): 9525 return self.expression( 9526 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9527 ) 9528 9529 self._match_text_seq("SET", "DATA") 9530 dtype = ( 9531 self._parse_types() 9532 if self._match_text_seq("TYPE") or not self.ALTER_COLUMN_TYPE_REQUIRES_KEYWORD 9533 else None 9534 ) 9535 if not dtype: 9536 return None 9537 9538 return self.expression( 9539 exp.AlterColumn( 9540 this=column, 9541 dtype=dtype, 9542 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9543 using=self._match(TokenType.USING) and self._parse_disjunction(), 9544 exists=exists or None, 9545 ) 9546 ) 9547 9548 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9549 if self._match_texts(("ALL", "EVEN", "AUTO")): 9550 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9551 9552 self._match_text_seq("KEY", "DISTKEY") 9553 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9554 9555 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9556 if compound: 9557 self._match_text_seq("SORTKEY") 9558 9559 if self._match(TokenType.L_PAREN, advance=False): 9560 return self.expression( 9561 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9562 ) 9563 9564 self._match_texts(("AUTO", "NONE")) 9565 return self.expression( 9566 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9567 ) 9568 9569 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9570 index = self._index - 1 9571 9572 partition_exists = self._parse_exists() 9573 if self._match(TokenType.PARTITION, advance=False): 9574 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9575 9576 self._retreat(index) 9577 return self._parse_csv(self._parse_alter_drop_action) 9578 9579 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9580 if self._match(TokenType.COLUMN) or ( 9581 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9582 ): 9583 exists = self._parse_exists() 9584 old_column = self._parse_column() 9585 to = self._match_text_seq("TO") 9586 new_column = self._parse_column() 9587 9588 if old_column is None or not to or new_column is None: 9589 return None 9590 9591 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9592 9593 self._match_text_seq("TO") 9594 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9595 9596 def _parse_alter_table_set(self) -> exp.AlterSet: 9597 alter_set = self.expression(exp.AlterSet()) 9598 9599 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9600 "TABLE", "PROPERTIES" 9601 ): 9602 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9603 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9604 alter_set.set("expressions", [self._parse_assignment()]) 9605 elif self._match_texts(("LOGGED", "UNLOGGED")): 9606 alter_set.set("option", exp.var(self._prev.text.upper())) 9607 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9608 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9609 elif self._match_text_seq("LOCATION"): 9610 alter_set.set("location", self._parse_field()) 9611 elif self._match_text_seq("ACCESS", "METHOD"): 9612 alter_set.set("access_method", self._parse_field()) 9613 elif self._match_text_seq("TABLESPACE"): 9614 alter_set.set("tablespace", self._parse_field()) 9615 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9616 alter_set.set("file_format", [self._parse_field()]) 9617 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9618 alter_set.set("file_format", self._parse_wrapped_options()) 9619 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9620 alter_set.set("copy_options", self._parse_wrapped_options()) 9621 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9622 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9623 else: 9624 if self._match_text_seq("SERDE"): 9625 alter_set.set("serde", self._parse_field()) 9626 9627 properties = self._parse_wrapped(self._parse_properties, optional=True) 9628 alter_set.set("expressions", [properties]) 9629 9630 return alter_set 9631 9632 def _parse_alter_session(self) -> exp.AlterSession: 9633 """Parse ALTER SESSION SET/UNSET statements.""" 9634 if self._match(TokenType.SET): 9635 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9636 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9637 9638 self._match_text_seq("UNSET") 9639 expressions = self._parse_csv( 9640 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9641 ) 9642 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9643 9644 def _parse_alter(self) -> exp.Alter | exp.Command: 9645 start = self._prev 9646 9647 iceberg = self._match_text_seq("ICEBERG") 9648 9649 alter_token = self._match_set(self.ALTERABLES) and self._prev 9650 if not alter_token: 9651 return self._parse_as_command(start) 9652 if iceberg and alter_token.token_type != TokenType.TABLE: 9653 return self._parse_as_command(start) 9654 9655 exists = self._parse_exists() 9656 only = self._match_text_seq("ONLY") 9657 9658 if alter_token.token_type == TokenType.SESSION: 9659 this = None 9660 check = None 9661 cluster = None 9662 parser = self.ALTER_PARSERS.get(alter_token.text.upper()) 9663 else: 9664 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9665 check = self._match_text_seq("WITH", "CHECK") 9666 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9667 parser = ( 9668 self.ALTER_PARSERS[self._prev.text.upper()] 9669 if self._match_texts(self.ALTER_PARSERS) 9670 else None 9671 ) 9672 9673 options: list[exp.Expr] = [] 9674 if ( 9675 not parser 9676 and alter_token.token_type == TokenType.TABLE 9677 and not self.ALTER_TABLE_REQUIRES_ACTION 9678 ): 9679 # Table options may precede the actions, e.g. MySQL's ENGINE=InnoDB, ADD COLUMN a INT. 9680 # A clause the property parser rejects, like PARTITION BY RANGE ... MAXVALUE, stays a Command 9681 properties = self._try_parse(self._parse_properties) 9682 options = properties.expressions if properties else [] 9683 9684 actions = ensure_list(parser(self) if parser else None) 9685 if actions or options: 9686 # Actions of different kinds and table options may alternate, e.g. MySQL's 9687 # ADD c INT, AUTO_INCREMENT=5, ADD d INT. The comma before an item may have already 9688 # been consumed by the previous parser 9689 while self.ALTER_TABLE_MIXED_ACTIONS and ( 9690 self._match(TokenType.COMMA) or self._prev.token_type == TokenType.COMMA 9691 ): 9692 if not self._match_texts( 9693 self.PROPERTY_PARSERS, advance=False 9694 ) and self._match_texts(self.ALTER_PARSERS): 9695 parser = self.ALTER_PARSERS[self._prev.text.upper()] 9696 parsed = ensure_list(self._try_parse(lambda: parser(self))) 9697 if not parsed or any(isinstance(action, exp.Command) for action in parsed): 9698 return self._parse_as_command(start) 9699 9700 actions.extend(parsed) 9701 elif properties := self._try_parse(self._parse_properties): 9702 options.extend(properties.expressions) 9703 else: 9704 break 9705 9706 not_valid = self._match_text_seq("NOT", "VALID") 9707 if properties := self._try_parse(self._parse_properties): 9708 options.extend(properties.expressions) 9709 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9710 9711 if not self._curr: 9712 return self.expression( 9713 exp.Alter( 9714 this=this, 9715 kind=alter_token.text.upper(), 9716 exists=exists, 9717 actions=actions, 9718 only=only, 9719 options=options, 9720 cluster=cluster, 9721 not_valid=not_valid, 9722 check=check, 9723 cascade=cascade, 9724 iceberg=iceberg, 9725 ) 9726 ) 9727 9728 return self._parse_as_command(start) 9729 9730 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9731 start = self._prev 9732 # https://duckdb.org/docs/sql/statements/analyze 9733 if not self._curr: 9734 return self.expression(exp.Analyze()) 9735 9736 options = [] 9737 while self._match_texts(self.ANALYZE_STYLES): 9738 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9739 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9740 else: 9741 options.append(self._prev.text.upper()) 9742 9743 tables: exp.Expr | list[exp.Expr] | None = None 9744 inner_expression: exp.Expr | None = None 9745 9746 kind = self._curr.text.upper() if self._curr else None 9747 9748 if self._match(TokenType.TABLE): 9749 tables = self._parse_csv(self._parse_table_parts) 9750 elif self._match(TokenType.INDEX): 9751 tables = self._parse_table_parts() 9752 elif self._match_text_seq("TABLES"): 9753 if self._match_set((TokenType.FROM, TokenType.IN)): 9754 kind = f"{kind} {self._prev.text.upper()}" 9755 tables = self._parse_table(schema=True, is_db_reference=True) 9756 elif self._match_text_seq("DATABASE"): 9757 tables = self._parse_table(schema=True, is_db_reference=True) 9758 elif self._match_text_seq("CLUSTER"): 9759 tables = self._parse_table() 9760 # Try matching inner expr keywords before fallback to parse table. 9761 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9762 kind = None 9763 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9764 else: 9765 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9766 kind = None 9767 tables = self._parse_csv(self._parse_table_parts) 9768 9769 partition = self._try_parse(self._parse_partition) 9770 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9771 return self._parse_as_command(start) 9772 9773 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9774 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9775 "WITH", "ASYNC", "MODE" 9776 ): 9777 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9778 else: 9779 mode = None 9780 9781 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9782 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9783 9784 properties = self._parse_properties() 9785 return self.expression( 9786 exp.Analyze( 9787 kind=kind, 9788 tables=ensure_list(tables), 9789 mode=mode, 9790 partition=partition, 9791 properties=properties, 9792 expression=inner_expression, 9793 options=options, 9794 ) 9795 ) 9796 9797 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9798 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9799 this = None 9800 kind = self._prev.text.upper() 9801 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9802 expressions = [] 9803 9804 if not self._match_text_seq("STATISTICS"): 9805 self.raise_error("Expecting token STATISTICS") 9806 9807 if self._match_text_seq("NOSCAN"): 9808 this = "NOSCAN" 9809 elif self._match(TokenType.FOR): 9810 if self._match_text_seq("ALL", "COLUMNS"): 9811 this = "FOR ALL COLUMNS" 9812 if self._match_text_seq("COLUMNS"): 9813 this = "FOR COLUMNS" 9814 expressions = self._parse_csv(self._parse_column_reference) 9815 elif self._match_text_seq("SAMPLE"): 9816 sample = self._parse_number() 9817 expressions = [ 9818 self.expression( 9819 exp.AnalyzeSample( 9820 sample=sample, 9821 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9822 ) 9823 ) 9824 ] 9825 9826 return self.expression( 9827 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9828 ) 9829 9830 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9831 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9832 kind = None 9833 this = None 9834 expression: exp.Expr | None = None 9835 if self._match_text_seq("REF", "UPDATE"): 9836 kind = "REF" 9837 this = "UPDATE" 9838 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9839 this = "UPDATE SET DANGLING TO NULL" 9840 elif self._match_text_seq("STRUCTURE"): 9841 kind = "STRUCTURE" 9842 if self._match_text_seq("CASCADE", "FAST"): 9843 this = "CASCADE FAST" 9844 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9845 ("ONLINE", "OFFLINE") 9846 ): 9847 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9848 expression = self._parse_into() 9849 9850 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9851 9852 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9853 this = self._prev.text.upper() 9854 if self._match_text_seq("COLUMNS"): 9855 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9856 return None 9857 9858 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9859 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9860 if self._match_text_seq("STATISTICS"): 9861 return self.expression(exp.AnalyzeDelete(kind=kind)) 9862 return None 9863 9864 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9865 if self._match_text_seq("CHAINED", "ROWS"): 9866 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9867 return None 9868 9869 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9870 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9871 this = self._prev.text.upper() 9872 expression: exp.Expr | None = None 9873 expressions = [] 9874 update_options = None 9875 9876 if self._match_text_seq("HISTOGRAM", "ON"): 9877 expressions = self._parse_csv(self._parse_column_reference) 9878 with_expressions = [] 9879 while self._match(TokenType.WITH): 9880 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9881 if self._match_texts(("SYNC", "ASYNC")): 9882 if self._match_text_seq("MODE", advance=False): 9883 with_expressions.append(f"{self._prev.text.upper()} MODE") 9884 self._advance() 9885 else: 9886 buckets = self._parse_number() 9887 if self._match_text_seq("BUCKETS"): 9888 with_expressions.append(f"{buckets} BUCKETS") 9889 if with_expressions: 9890 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9891 9892 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9893 TokenType.UPDATE, advance=False 9894 ): 9895 update_options = self._prev.text.upper() 9896 self._advance() 9897 elif self._match_text_seq("USING", "DATA"): 9898 expression = self.expression(exp.UsingData(this=self._parse_string())) 9899 9900 return self.expression( 9901 exp.AnalyzeHistogram( 9902 this=this, 9903 expressions=expressions, 9904 expression=expression, 9905 update_options=update_options, 9906 ) 9907 ) 9908 9909 def _parse_merge(self) -> exp.Merge: 9910 self._match(TokenType.INTO) 9911 target = self._parse_table() 9912 9913 if target and self._match(TokenType.ALIAS, advance=False): 9914 target.set("alias", self._parse_table_alias()) 9915 9916 self._match(TokenType.USING) 9917 using = self._parse_table() 9918 9919 return self.expression( 9920 exp.Merge( 9921 this=target, 9922 using=using, 9923 on=self._match(TokenType.ON) and self._parse_disjunction(), 9924 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9925 whens=self._parse_when_matched(), 9926 returning=self._parse_returning(), 9927 ) 9928 ) 9929 9930 def _parse_when_matched(self) -> exp.Whens: 9931 whens = [] 9932 9933 while self._match(TokenType.WHEN): 9934 matched = not self._match(TokenType.NOT) 9935 self._match_text_seq("MATCHED") 9936 source = ( 9937 False 9938 if self._match_text_seq("BY", "TARGET") 9939 else self._match_text_seq("BY", "SOURCE") 9940 ) 9941 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9942 9943 self._match(TokenType.THEN) 9944 9945 if self._match(TokenType.INSERT): 9946 this = ( 9947 exp.Star() if self._match_text_seq("ALL", "BY", "NAME") else self._parse_star() 9948 ) 9949 if this: 9950 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9951 else: 9952 then = self.expression( 9953 exp.Insert( 9954 this=exp.var("ROW") 9955 if self._match_text_seq("ROW") 9956 else self._parse_value(values=False), 9957 expression=self._match_text_seq("VALUES") and self._parse_value(), 9958 where=self._parse_where(), 9959 ) 9960 ) 9961 elif self._match(TokenType.UPDATE): 9962 expressions = self._parse_star() 9963 if expressions: 9964 then = self.expression(exp.Update(expressions=expressions)) 9965 elif self._match_text_seq("ALL", "BY", "NAME"): 9966 then = self.expression(exp.Update(expressions=[exp.Star()])) 9967 else: 9968 then = self.expression( 9969 exp.Update( 9970 expressions=self._match(TokenType.SET) 9971 and self._parse_csv(self._parse_update_assignment), 9972 where=self._parse_where(), 9973 ) 9974 ) 9975 elif self._match(TokenType.DELETE): 9976 then = self.expression(exp.Var(this=self._prev.text)) 9977 else: 9978 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9979 9980 whens.append( 9981 self.expression( 9982 exp.When(matched=matched, source=source, condition=condition, then=then) 9983 ) 9984 ) 9985 return self.expression(exp.Whens(expressions=whens)) 9986 9987 def _parse_show(self) -> exp.Expr | None: 9988 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9989 if parser: 9990 return parser(self) 9991 return self._parse_as_command(self._prev) 9992 9993 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9994 index = self._index 9995 9996 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9997 return self._parse_set_transaction(global_=kind == "GLOBAL") 9998 9999 left = self._parse_primary() or self._parse_column() 10000 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 10001 10002 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 10003 self._retreat(index) 10004 return None 10005 10006 right = self._parse_statement() or self._parse_id_var() 10007 if isinstance(right, (exp.Column, exp.Identifier)): 10008 right = exp.var(right.name) 10009 10010 this = self.expression(exp.EQ(this=left, expression=right)) 10011 return self.expression(exp.SetItem(this=this, kind=kind)) 10012 10013 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 10014 self._match_text_seq("TRANSACTION") 10015 characteristics = self._parse_csv( 10016 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 10017 ) 10018 return self.expression( 10019 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 10020 ) 10021 10022 def _parse_set_item(self) -> exp.Expr | None: 10023 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 10024 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 10025 10026 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 10027 index = self._index 10028 set_ = self.expression( 10029 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 10030 ) 10031 10032 if self._curr: 10033 self._retreat(index) 10034 return self._parse_as_command(self._prev) 10035 10036 return set_ 10037 10038 def _parse_var_from_options( 10039 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 10040 ) -> exp.Var | None: 10041 start = self._curr 10042 if not start: 10043 return None 10044 10045 option = start.text.upper() 10046 continuations = ( 10047 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 10048 ) 10049 10050 index = self._index 10051 self._advance() 10052 for keywords in continuations or []: 10053 if isinstance(keywords, str): 10054 keywords = (keywords,) 10055 10056 if self._match_text_seq(*keywords): 10057 option = f"{option} {' '.join(keywords)}" 10058 break 10059 else: 10060 if continuations or continuations is None: 10061 if raise_unmatched: 10062 self.raise_error(f"Unknown option {option}") 10063 10064 self._retreat(index) 10065 return None 10066 10067 return exp.var(option) 10068 10069 def _parse_as_command(self, start: Token) -> exp.Command: 10070 while self._curr: 10071 self._advance() 10072 text = self._find_sql(start, self._prev) 10073 size = len(start.text) 10074 self._warn_unsupported() 10075 return exp.Command(this=text[:size], expression=text[size:]) 10076 10077 def _parse_dict_property(self, this: str) -> exp.DictProperty: 10078 settings = [] 10079 10080 self._match_l_paren() 10081 kind = self._parse_id_var() 10082 10083 if self._match(TokenType.L_PAREN): 10084 while True: 10085 key = self._parse_id_var() 10086 value = self._parse_function() or self._parse_primary_or_var() 10087 if not key and value is None: 10088 break 10089 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 10090 self._match(TokenType.R_PAREN) 10091 10092 self._match_r_paren() 10093 10094 return self.expression( 10095 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 10096 ) 10097 10098 def _parse_dict_range(self, this: str) -> exp.DictRange: 10099 self._match_l_paren() 10100 has_min = self._match_text_seq("MIN") 10101 if has_min: 10102 min = self._parse_var() or self._parse_primary() 10103 self._match_text_seq("MAX") 10104 max = self._parse_var() or self._parse_primary() 10105 else: 10106 max = self._parse_var() or self._parse_primary() 10107 min = exp.Literal.number(0) 10108 self._match_r_paren() 10109 return self.expression(exp.DictRange(this=this, min=min, max=max)) 10110 10111 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 10112 index = self._index 10113 expression = self._parse_id_var() 10114 position = self._parse_id_var() if self._match(TokenType.COMMA) else None 10115 10116 if not self._match(TokenType.IN): 10117 self._retreat(index - 1) 10118 return None 10119 iterator = self._parse_column() 10120 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 10121 return self.expression( 10122 exp.Comprehension( 10123 this=this, 10124 expression=expression, 10125 position=position, 10126 iterator=iterator, 10127 condition=condition, 10128 ) 10129 ) 10130 10131 def _parse_heredoc(self) -> exp.Heredoc | None: 10132 if self._match(TokenType.HEREDOC_STRING): 10133 return self.expression(exp.Heredoc(this=self._prev.text)) 10134 10135 if not self._match_text_seq("$"): 10136 return None 10137 10138 tags = ["$"] 10139 tag_text = None 10140 10141 if self._is_connected(): 10142 self._advance() 10143 tags.append(self._prev.text.upper()) 10144 else: 10145 self.raise_error("No closing $ found") 10146 10147 if tags[-1] != "$": 10148 if self._is_connected() and self._match_text_seq("$"): 10149 tag_text = tags[-1] 10150 tags.append("$") 10151 else: 10152 self.raise_error("No closing $ found") 10153 10154 heredoc_start = self._curr 10155 10156 while self._curr: 10157 if self._match_text_seq(*tags, advance=False): 10158 this = self._find_sql(heredoc_start, self._prev) 10159 self._advance(len(tags)) 10160 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 10161 10162 self._advance() 10163 10164 self.raise_error(f"No closing {''.join(tags)} found") 10165 return None 10166 10167 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 10168 if not self._curr: 10169 return None 10170 10171 index = self._index 10172 this = [] 10173 while True: 10174 # The current token might be multiple words 10175 curr = self._curr.text.upper() 10176 key = curr.split(" ") 10177 this.append(curr) 10178 10179 self._advance() 10180 result, trie = in_trie(trie, key) 10181 if result == TrieResult.FAILED: 10182 break 10183 10184 if result == TrieResult.EXISTS: 10185 subparser = parsers[" ".join(this)] 10186 return subparser 10187 10188 self._retreat(index) 10189 return None 10190 10191 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 10192 if not self._match(TokenType.L_PAREN, expression=expression): 10193 self.raise_error("Expecting (") 10194 10195 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 10196 if not self._match(TokenType.R_PAREN, expression=expression): 10197 self.raise_error("Expecting )") 10198 10199 def _replace_lambda( 10200 self, node: exp.Expr | None, expressions: list[exp.Expr] 10201 ) -> exp.Expr | None: 10202 if not node: 10203 return node 10204 10205 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 10206 10207 for column in node.find_all(exp.Column): 10208 typ = lambda_types.get(column.parts[0].name) 10209 if typ is not None: 10210 dot_or_id = column.to_dot() if column.table else column.this 10211 10212 if isinstance(typ, exp.DataType): 10213 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ.copy())) 10214 10215 parent = column.parent 10216 10217 while isinstance(parent, exp.Dot): 10218 if not isinstance(parent.parent, exp.Dot): 10219 parent.replace(dot_or_id) 10220 break 10221 parent = parent.parent 10222 else: 10223 if column is node: 10224 node = dot_or_id 10225 else: 10226 column.replace(dot_or_id) 10227 return node 10228 10229 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 10230 start = self._prev 10231 10232 # Not to be confused with TRUNCATE(number, decimals) function call 10233 if self._match(TokenType.L_PAREN): 10234 self._retreat(self._index - 2) 10235 return self._parse_function() 10236 10237 # Clickhouse supports TRUNCATE DATABASE as well 10238 is_database = self._match(TokenType.DATABASE) 10239 10240 self._match(TokenType.TABLE) 10241 10242 exists = self._parse_exists(not_=False) 10243 10244 expressions = self._parse_csv( 10245 lambda: self._parse_table(schema=True, is_db_reference=is_database) 10246 ) 10247 10248 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 10249 10250 if self._match_text_seq("RESTART", "IDENTITY"): 10251 identity = "RESTART" 10252 elif self._match_text_seq("CONTINUE", "IDENTITY"): 10253 identity = "CONTINUE" 10254 else: 10255 identity = None 10256 10257 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 10258 option = self._prev.text 10259 else: 10260 option = None 10261 10262 partition = self._parse_partition() 10263 10264 # Fallback case 10265 if self._curr: 10266 return self._parse_as_command(start) 10267 10268 return self.expression( 10269 exp.TruncateTable( 10270 expressions=expressions, 10271 is_database=is_database, 10272 exists=exists, 10273 cluster=cluster, 10274 identity=identity, 10275 option=option, 10276 partition=partition, 10277 ) 10278 ) 10279 10280 def _parse_indexed_column(self) -> exp.Expr | None: 10281 return self._parse_ordered(self._parse_opclass) 10282 10283 def _parse_with_operator(self) -> exp.Expr | None: 10284 this = self._parse_indexed_column() 10285 10286 if not self._match(TokenType.WITH): 10287 return this 10288 10289 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 10290 10291 return self.expression(exp.WithOperator(this=this, op=op)) 10292 10293 def _parse_wrapped_options(self) -> list[exp.Expr]: 10294 self._match(TokenType.EQ) 10295 self._match(TokenType.L_PAREN) 10296 10297 opts: list[exp.Expr] = [] 10298 option: exp.Expr | list[exp.Expr] | None 10299 while self._curr and not self._match(TokenType.R_PAREN): 10300 if self._match_text_seq("FORMAT_NAME", "="): 10301 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 10302 option = self._parse_format_name() 10303 else: 10304 option = self._parse_property() 10305 10306 if option is None: 10307 self.raise_error("Unable to parse option") 10308 break 10309 10310 opts.extend(ensure_list(option)) 10311 10312 return opts 10313 10314 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 10315 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 10316 10317 options = [] 10318 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 10319 option = self._parse_var(any_token=True) 10320 if option is None: 10321 break 10322 10323 prev = self._prev.text.upper() 10324 10325 # Different dialects might separate options and values by white space, "=" and "AS" 10326 self._match(TokenType.EQ) 10327 self._match(TokenType.ALIAS) 10328 10329 param = self.expression(exp.CopyParameter(this=option)) 10330 10331 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 10332 TokenType.L_PAREN, advance=False 10333 ): 10334 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 10335 param.set("expressions", self._parse_wrapped_options()) 10336 elif prev == "FILE_FORMAT": 10337 # T-SQL's external file format case 10338 param.set("expression", self._parse_field()) 10339 elif ( 10340 prev == "FORMAT" 10341 and self._prev.token_type == TokenType.ALIAS 10342 and self._match_texts(("AVRO", "JSON")) 10343 ): 10344 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 10345 param.set("expression", self._parse_field()) 10346 else: 10347 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 10348 10349 options.append(param) 10350 10351 if sep: 10352 self._match(sep) 10353 10354 return options 10355 10356 def _parse_credentials(self) -> exp.Credentials | None: 10357 expr = self.expression(exp.Credentials()) 10358 10359 if self._match_text_seq("STORAGE_INTEGRATION", "="): 10360 expr.set("storage", self._parse_field()) 10361 if self._match_text_seq("CREDENTIALS"): 10362 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 10363 creds = ( 10364 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 10365 ) 10366 expr.set("credentials", creds) 10367 if self._match_text_seq("ENCRYPTION"): 10368 expr.set("encryption", self._parse_wrapped_options()) 10369 if self._match_text_seq("IAM_ROLE"): 10370 expr.set( 10371 "iam_role", 10372 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 10373 ) 10374 if self._match_text_seq("REGION"): 10375 expr.set("region", self._parse_field()) 10376 10377 return expr 10378 10379 def _parse_file_location(self) -> exp.Expr | None: 10380 return self._parse_field() 10381 10382 def _parse_copy(self) -> exp.Copy | exp.Command: 10383 start = self._prev 10384 10385 self._match(TokenType.INTO) 10386 10387 this = ( 10388 self._parse_select(nested=True, parse_subquery_alias=False) 10389 if self._match(TokenType.L_PAREN, advance=False) 10390 else self._parse_table(schema=True) 10391 ) 10392 10393 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 10394 10395 files = self._parse_csv(self._parse_file_location) 10396 if self._match(TokenType.EQ, advance=False): 10397 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 10398 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 10399 # list via `_parse_wrapped(..)` below. 10400 self._advance(-1) 10401 files = [] 10402 10403 credentials = self._parse_credentials() 10404 10405 self._match_text_seq("WITH") 10406 10407 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 10408 10409 # Fallback case 10410 if self._curr: 10411 return self._parse_as_command(start) 10412 10413 return self.expression( 10414 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 10415 ) 10416 10417 def _parse_normalize(self) -> exp.Normalize: 10418 return self.expression( 10419 exp.Normalize( 10420 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 10421 ) 10422 ) 10423 10424 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 10425 args = self._parse_csv(lambda: self._parse_lambda()) 10426 10427 this = seq_get(args, 0) 10428 decimals = seq_get(args, 1) 10429 10430 return expr_type( 10431 this=this, 10432 decimals=decimals, 10433 to=self._parse_var() if self._match_text_seq("TO") else None, 10434 ) 10435 10436 def _parse_star_ops(self) -> exp.Expr | None: 10437 star_token = self._prev 10438 10439 if self._match_text_seq("COLUMNS", "(", advance=False): 10440 this = self._parse_function() 10441 if isinstance(this, exp.Columns): 10442 this.set("unpack", True) 10443 return this 10444 10445 index = self._index 10446 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 10447 if not ilike: 10448 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 10449 self._retreat(index) 10450 10451 return self.expression( 10452 exp.Star( 10453 ilike=ilike, 10454 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 10455 replace=self._parse_star_op("REPLACE"), 10456 rename=self._parse_star_op("RENAME"), 10457 ) 10458 ).update_positions(star_token) 10459 10460 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 10461 privilege_parts = [] 10462 10463 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 10464 # (end of privilege list) or L_PAREN (start of column list) are met 10465 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 10466 privilege_parts.append(self._curr.text.upper()) 10467 self._advance() 10468 10469 if not privilege_parts: 10470 self.raise_error("Expected privilege") 10471 return None 10472 10473 this = exp.var(" ".join(privilege_parts)) 10474 expressions = ( 10475 self._parse_wrapped_csv(self._parse_column) 10476 if self._match(TokenType.L_PAREN, advance=False) 10477 else None 10478 ) 10479 10480 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10481 10482 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10483 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10484 principal = self._parse_id_var() 10485 10486 if not principal: 10487 return None 10488 10489 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10490 10491 def _parse_grant_revoke_common( 10492 self, 10493 ) -> tuple[list | None, str | None, exp.Expr | None]: 10494 privileges = self._parse_csv(self._parse_grant_privilege) 10495 10496 self._match(TokenType.ON) 10497 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10498 10499 # Attempt to parse the securable e.g. MySQL allows names 10500 # such as "foo.*", "*.*" which are not easily parseable yet 10501 securable = self._try_parse(self._parse_table_parts) 10502 10503 return privileges, kind, securable 10504 10505 def _parse_grant(self) -> exp.Grant | exp.Command: 10506 start = self._prev 10507 10508 privileges, kind, securable = self._parse_grant_revoke_common() 10509 10510 if not securable or not self._match_text_seq("TO"): 10511 return self._parse_as_command(start) 10512 10513 principals = self._parse_csv(self._parse_grant_principal) 10514 10515 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10516 10517 if self._curr: 10518 return self._parse_as_command(start) 10519 10520 return self.expression( 10521 exp.Grant( 10522 privileges=privileges, 10523 kind=kind, 10524 securable=securable, 10525 principals=principals, 10526 grant_option=grant_option, 10527 ) 10528 ) 10529 10530 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10531 start = self._prev 10532 10533 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10534 10535 privileges, kind, securable = self._parse_grant_revoke_common() 10536 10537 if not securable or not self._match_text_seq("FROM"): 10538 return self._parse_as_command(start) 10539 10540 principals = self._parse_csv(self._parse_grant_principal) 10541 10542 cascade = None 10543 if self._match_texts(("CASCADE", "RESTRICT")): 10544 cascade = self._prev.text.upper() 10545 10546 if self._curr: 10547 return self._parse_as_command(start) 10548 10549 return self.expression( 10550 exp.Revoke( 10551 privileges=privileges, 10552 kind=kind, 10553 securable=securable, 10554 principals=principals, 10555 grant_option=grant_option, 10556 cascade=cascade, 10557 ) 10558 ) 10559 10560 def _parse_overlay(self) -> exp.Overlay: 10561 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10562 return ( 10563 self._parse_bitwise() 10564 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10565 else None 10566 ) 10567 10568 return self.expression( 10569 exp.Overlay( 10570 this=self._parse_bitwise(), 10571 expression=_parse_overlay_arg("PLACING"), 10572 from_=_parse_overlay_arg("FROM"), 10573 for_=_parse_overlay_arg("FOR"), 10574 ) 10575 ) 10576 10577 def _parse_format_name(self) -> exp.Property: 10578 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10579 # for FILE_FORMAT = <format_name> 10580 return self.expression( 10581 exp.Property( 10582 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10583 ) 10584 ) 10585 10586 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10587 is_distinct = self._match(TokenType.DISTINCT) 10588 if not is_distinct: 10589 self._match(TokenType.ALL) 10590 10591 args = [self._parse_lambda()] 10592 if self._match(TokenType.COMMA): 10593 args.extend(self._parse_function_args()) 10594 10595 target = seq_get(args, distinct_index) 10596 if is_distinct and target: 10597 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10598 10599 return func.from_arg_list(args) 10600 10601 def _identifier_expression( 10602 self, token: Token | None = None, quoted: bool | None = None 10603 ) -> exp.Identifier: 10604 token = token or self._prev 10605 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10606 10607 def _build_pipe_cte( 10608 self, 10609 query: exp.Query, 10610 expressions: list[exp.Expr], 10611 alias_cte: exp.TableAlias | None = None, 10612 ) -> exp.Select: 10613 new_cte: str | exp.TableAlias | None 10614 if alias_cte: 10615 new_cte = alias_cte 10616 else: 10617 self._pipe_cte_counter += 1 10618 new_cte = f"__tmp{self._pipe_cte_counter}" 10619 10620 with_ = query.args.get("with_") 10621 ctes = with_.pop() if with_ else None 10622 10623 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10624 if ctes: 10625 new_select.set("with_", ctes) 10626 10627 return new_select.with_(new_cte, as_=query, copy=False) 10628 10629 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10630 select = self._parse_select(consume_pipe=False) 10631 if not select: 10632 return query 10633 10634 return self._build_pipe_cte( 10635 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10636 ) 10637 10638 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10639 limit = self._parse_limit() 10640 offset = self._parse_offset() 10641 if isinstance(limit, exp.Limit): 10642 curr_limit = query.args.get("limit", limit) 10643 curr_is_limit_all = isinstance(curr_limit, exp.Limit) and curr_limit.is_limit_all 10644 10645 # LIMIT ALL means "no cap", so it's never tighter than a numeric limit. 10646 if limit.is_limit_all: 10647 should_replace = curr_is_limit_all 10648 else: 10649 should_replace = ( 10650 curr_is_limit_all or curr_limit.expression.to_py() >= limit.expression.to_py() 10651 ) 10652 10653 if should_replace: 10654 query.limit(limit, copy=False) 10655 if offset: 10656 curr_offset = query.args.get("offset") 10657 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10658 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10659 10660 return query 10661 10662 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10663 this = self._parse_disjunction() 10664 if self._match_text_seq("GROUP", "AND", advance=False): 10665 return this 10666 10667 this = self._parse_alias(this) 10668 10669 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10670 return self._parse_ordered(lambda: this) 10671 10672 return this 10673 10674 def _parse_pipe_syntax_aggregate_group_order_by( 10675 self, query: exp.Select, group_by_exists: bool = True 10676 ) -> exp.Select: 10677 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10678 aggregates_or_groups, orders = [], [] 10679 for element in expr: 10680 if isinstance(element, exp.Ordered): 10681 this = element.this 10682 if isinstance(this, exp.Alias): 10683 element.set("this", this.args["alias"]) 10684 orders.append(element) 10685 else: 10686 this = element 10687 aggregates_or_groups.append(this) 10688 10689 if group_by_exists: 10690 query.select( 10691 *aggregates_or_groups, *query.expressions, append=False, copy=False 10692 ).group_by( 10693 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10694 copy=False, 10695 ) 10696 else: 10697 query.select(*aggregates_or_groups, append=False, copy=False) 10698 10699 if orders: 10700 return query.order_by(*orders, append=False, copy=False) 10701 10702 return query 10703 10704 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10705 self._match_text_seq("AGGREGATE") 10706 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10707 10708 if self._match(TokenType.GROUP_BY) or ( 10709 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10710 ): 10711 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10712 10713 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10714 10715 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10716 first_setop = self.parse_set_operation(this=query) 10717 if not first_setop: 10718 return None 10719 10720 def _parse_and_unwrap_query() -> exp.Expr | None: 10721 expr = self._parse_paren() 10722 return expr.assert_is(exp.Subquery).unnest() if expr else None 10723 10724 first_setop.this.pop() 10725 10726 setops = [ 10727 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10728 *self._parse_csv(_parse_and_unwrap_query), 10729 ] 10730 10731 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10732 with_ = query.args.get("with_") 10733 ctes = with_.pop() if with_ else None 10734 10735 if isinstance(first_setop, exp.Union): 10736 query = query.union(*setops, copy=False, **first_setop.args) 10737 elif isinstance(first_setop, exp.Except): 10738 query = query.except_(*setops, copy=False, **first_setop.args) 10739 else: 10740 query = query.intersect(*setops, copy=False, **first_setop.args) 10741 10742 query.set("with_", ctes) 10743 10744 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10745 10746 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10747 join = self._parse_join() 10748 if not join: 10749 return None 10750 10751 if isinstance(query, exp.Select): 10752 return query.join(join, copy=False) 10753 10754 return query 10755 10756 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10757 pivots = self._parse_pivots() 10758 if not pivots: 10759 return query 10760 10761 from_ = query.args.get("from_") 10762 if from_: 10763 from_.this.set("pivots", pivots) 10764 10765 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10766 10767 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10768 self._match_text_seq("EXTEND") 10769 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10770 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10771 10772 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10773 sample = self._parse_table_sample() 10774 10775 with_ = query.args.get("with_") 10776 if with_: 10777 with_.expressions[-1].this.set("sample", sample) 10778 else: 10779 query.set("sample", sample) 10780 10781 return query 10782 10783 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10784 if isinstance(query, exp.Subquery): 10785 query = exp.select("*").from_(query, copy=False) 10786 10787 if not query.args.get("from_"): 10788 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10789 10790 while self._match(TokenType.PIPE_GT): 10791 start_index = self._index 10792 start_text = self._curr.text.upper() 10793 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10794 if not parser: 10795 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10796 # keywords, making it tricky to disambiguate them without lookahead. The approach 10797 # here is to try and parse a set operation and if that fails, then try to parse a 10798 # join operator. If that fails as well, then the operator is not supported. 10799 parsed_query = self._parse_pipe_syntax_set_operator(query) 10800 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10801 if not parsed_query: 10802 self._retreat(start_index) 10803 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10804 break 10805 query = parsed_query 10806 else: 10807 query = parser(self, query) 10808 10809 return query 10810 10811 def _parse_declareitem(self) -> exp.DeclareItem | None: 10812 self._match_texts(("VAR", "VARIABLE")) 10813 10814 vars = self._parse_csv(self._parse_id_var) 10815 if not vars: 10816 return None 10817 10818 self._match(TokenType.ALIAS) 10819 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10820 default = ( 10821 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10822 ) and self._parse_bitwise() 10823 10824 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10825 10826 def _parse_declare(self) -> exp.Declare | exp.Command: 10827 start = self._prev 10828 replace = self._match_text_seq("OR", "REPLACE") 10829 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10830 10831 if not expressions or self._curr: 10832 return self._parse_as_command(start) 10833 10834 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10835 10836 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10837 exp_class = exp.Cast if strict else exp.TryCast 10838 10839 if exp_class == exp.TryCast: 10840 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10841 10842 return self.expression(exp_class(**kwargs)) 10843 10844 def _parse_json_value(self, json_path: bool = True) -> exp.JSONValue: 10845 this = self._parse_bitwise() 10846 self._match(TokenType.COMMA) 10847 path = self._parse_bitwise() 10848 10849 returning = self._match(TokenType.RETURNING) and self._parse_types() 10850 10851 return self.expression( 10852 exp.JSONValue( 10853 this=this, 10854 path=self.dialect.to_json_path(path) if json_path else path, 10855 returning=returning, 10856 on_condition=self._parse_on_condition(), 10857 ) 10858 ) 10859 10860 def _parse_group_concat(self) -> exp.Expr | None: 10861 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10862 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10863 concat_exprs = [ 10864 self.expression( 10865 exp.Concat( 10866 expressions=node.expressions, 10867 safe=True, 10868 coalesce=self.dialect.CONCAT_COALESCE, 10869 ) 10870 ) 10871 ] 10872 node.set("expressions", concat_exprs) 10873 return node 10874 if len(exprs) == 1: 10875 return exprs[0] 10876 return self.expression( 10877 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10878 ) 10879 10880 args = self._parse_csv(self._parse_lambda) 10881 10882 if args: 10883 order = args[-1] if isinstance(args[-1], exp.Order) else None 10884 10885 if order: 10886 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10887 # remove 'expr' from exp.Order and add it back to args 10888 args[-1] = order.this 10889 order.set("this", concat_exprs(order.this, args)) 10890 10891 this = order or concat_exprs(args[0], args) 10892 else: 10893 this = None 10894 10895 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10896 10897 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10898 10899 def _parse_initcap(self) -> exp.Initcap: 10900 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10901 10902 # attach dialect's default delimiters 10903 if expr.args.get("expression") is None: 10904 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10905 10906 return expr 10907 10908 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10909 if not self._match(TokenType.L_PAREN): 10910 self._retreat(self._index - 1) 10911 return None 10912 10913 op = "" 10914 while self._curr and not self._match(TokenType.R_PAREN): 10915 op += self._curr.text 10916 self._advance() 10917 10918 comments = self._prev_comments 10919 return self.expression( 10920 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10921 comments=comments, 10922 )
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.REF, 651 TokenType.INET, 652 TokenType.IPADDRESS, 653 TokenType.IPPREFIX, 654 TokenType.IPV4, 655 TokenType.IPV6, 656 TokenType.UNKNOWN, 657 TokenType.NOTHING, 658 TokenType.NULL, 659 TokenType.NAME, 660 TokenType.TDIGEST, 661 TokenType.DYNAMIC, 662 *ENUM_TYPE_TOKENS, 663 *NESTED_TYPE_TOKENS, 664 *AGGREGATE_TYPE_TOKENS, 665 } 666 667 SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = { 668 TokenType.BIGINT: TokenType.UBIGINT, 669 TokenType.INT: TokenType.UINT, 670 TokenType.MEDIUMINT: TokenType.UMEDIUMINT, 671 TokenType.SMALLINT: TokenType.USMALLINT, 672 TokenType.TINYINT: TokenType.UTINYINT, 673 TokenType.DECIMAL: TokenType.UDECIMAL, 674 TokenType.DOUBLE: TokenType.UDOUBLE, 675 } 676 677 SUBQUERY_PREDICATES: t.ClassVar = { 678 TokenType.ANY: exp.Any, 679 TokenType.ALL: exp.All, 680 TokenType.EXISTS: exp.Exists, 681 TokenType.SOME: exp.Any, 682 } 683 684 SUBQUERY_TOKENS: t.ClassVar = { 685 TokenType.SELECT, 686 TokenType.WITH, 687 TokenType.FROM, 688 } 689 690 RESERVED_TOKENS: t.ClassVar = { 691 *Tokenizer.SINGLE_TOKENS.values(), 692 TokenType.SELECT, 693 } - {TokenType.IDENTIFIER} 694 695 # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers, 696 # string literals), so they must never be treated as keywords when matching by text 697 TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset( 698 { 699 TokenType.BIT_STRING, 700 TokenType.BYTE_STRING, 701 TokenType.HEREDOC_STRING, 702 TokenType.HEX_STRING, 703 TokenType.IDENTIFIER, 704 TokenType.NATIONAL_RAW_STRING, 705 TokenType.NATIONAL_STRING, 706 TokenType.RAW_STRING, 707 TokenType.STRING, 708 TokenType.UNICODE_STRING, 709 } 710 ) 711 712 DB_CREATABLES: t.ClassVar = { 713 TokenType.DATABASE, 714 TokenType.DICTIONARY, 715 TokenType.FILE_FORMAT, 716 TokenType.MODEL, 717 TokenType.NAMESPACE, 718 TokenType.SCHEMA, 719 TokenType.SEMANTIC_VIEW, 720 TokenType.SEQUENCE, 721 TokenType.SINK, 722 TokenType.SOURCE, 723 TokenType.STAGE, 724 TokenType.STORAGE_INTEGRATION, 725 TokenType.STREAMLIT, 726 TokenType.TABLE, 727 TokenType.TAG, 728 TokenType.VIEW, 729 TokenType.WAREHOUSE, 730 } 731 732 CREATABLES: t.ClassVar = { 733 TokenType.COLUMN, 734 TokenType.CONSTRAINT, 735 TokenType.FOREIGN_KEY, 736 TokenType.FUNCTION, 737 TokenType.INDEX, 738 TokenType.PROCEDURE, 739 TokenType.TRIGGER, 740 TokenType.TYPE, 741 *DB_CREATABLES, 742 } 743 744 TRIGGER_EVENTS: t.ClassVar = { 745 TokenType.INSERT, 746 TokenType.UPDATE, 747 TokenType.DELETE, 748 TokenType.TRUNCATE, 749 } 750 751 ALTERABLES: t.ClassVar = { 752 TokenType.INDEX, 753 TokenType.TABLE, 754 TokenType.VIEW, 755 TokenType.SESSION, 756 } 757 758 # Tokens that can represent identifiers 759 ID_VAR_TOKENS: t.ClassVar[set] = { 760 TokenType.ALL, 761 TokenType.ANALYZE, 762 TokenType.ATTACH, 763 TokenType.VAR, 764 TokenType.ANTI, 765 TokenType.APPLY, 766 TokenType.ASC, 767 TokenType.ASOF, 768 TokenType.AUTO_INCREMENT, 769 TokenType.BEGIN, 770 TokenType.BPCHAR, 771 TokenType.CACHE, 772 TokenType.CASE, 773 TokenType.COLLATE, 774 TokenType.COMMAND, 775 TokenType.COMMENT, 776 TokenType.COMMIT, 777 TokenType.CONNECT, 778 TokenType.CONSTRAINT, 779 TokenType.COPY, 780 TokenType.CUBE, 781 TokenType.CURRENT_SCHEMA, 782 TokenType.DECLARE, 783 TokenType.DEFAULT, 784 TokenType.DELETE, 785 TokenType.DESC, 786 TokenType.DESCRIBE, 787 TokenType.DETACH, 788 TokenType.DICTIONARY, 789 TokenType.DISCONNECT, 790 TokenType.DIV, 791 TokenType.END, 792 TokenType.EXECUTE, 793 TokenType.EXPORT, 794 TokenType.ESCAPE, 795 TokenType.FALSE, 796 TokenType.FIRST, 797 TokenType.FILE, 798 TokenType.FILTER, 799 TokenType.FINAL, 800 TokenType.FORMAT, 801 TokenType.FULL, 802 TokenType.GET, 803 TokenType.IDENTIFIER, 804 TokenType.IN, 805 TokenType.INOUT, 806 TokenType.IS, 807 TokenType.ISNULL, 808 TokenType.INTERVAL, 809 TokenType.KEEP, 810 TokenType.KILL, 811 TokenType.LEFT, 812 TokenType.LIMIT, 813 TokenType.LOAD, 814 TokenType.LOCK, 815 TokenType.MATCH, 816 TokenType.MERGE, 817 TokenType.NATURAL, 818 TokenType.NEXT, 819 TokenType.OFFSET, 820 TokenType.OPERATOR, 821 TokenType.ORDINALITY, 822 TokenType.OUT, 823 TokenType.OVER, 824 TokenType.OVERLAPS, 825 TokenType.OVERWRITE, 826 TokenType.PARTITION, 827 TokenType.PASTE, 828 TokenType.PERCENT, 829 TokenType.PIVOT, 830 TokenType.PROJECTION, 831 TokenType.PRAGMA, 832 TokenType.PUT, 833 TokenType.RANGE, 834 TokenType.RECURSIVE, 835 TokenType.REFERENCES, 836 TokenType.REFRESH, 837 TokenType.RENAME, 838 TokenType.REPLACE, 839 TokenType.RIGHT, 840 TokenType.ROLLUP, 841 TokenType.ROW, 842 TokenType.ROWS, 843 TokenType.SEMI, 844 TokenType.SET, 845 TokenType.SETTINGS, 846 TokenType.SHOW, 847 TokenType.STREAM, 848 TokenType.STREAMLIT, 849 TokenType.TEMPORARY, 850 TokenType.TOP, 851 TokenType.TRUE, 852 TokenType.TRUNCATE, 853 TokenType.UNIQUE, 854 TokenType.UNNEST, 855 TokenType.UNPIVOT, 856 TokenType.UPDATE, 857 TokenType.USE, 858 TokenType.VOLATILE, 859 TokenType.WINDOW, 860 TokenType.CURRENT_CATALOG, 861 TokenType.LOCALTIME, 862 TokenType.LOCALTIMESTAMP, 863 TokenType.SESSION_USER, 864 TokenType.STRAIGHT_JOIN, 865 *ALTERABLES, 866 *CREATABLES, 867 *SUBQUERY_PREDICATES, 868 *TYPE_TOKENS, 869 *NO_PAREN_FUNCTIONS, 870 } - {TokenType.UNION} 871 872 TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - { 873 TokenType.ANTI, 874 TokenType.ASOF, 875 TokenType.FULL, 876 TokenType.LEFT, 877 TokenType.LOCK, 878 TokenType.NATURAL, 879 TokenType.RIGHT, 880 TokenType.SEMI, 881 TokenType.WINDOW, 882 } 883 884 ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS 885 886 COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS 887 888 ARRAY_CONSTRUCTORS: t.ClassVar = { 889 "ARRAY": exp.Array, 890 "LIST": exp.List, 891 } 892 893 COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS} 894 895 UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET} 896 897 TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"} 898 899 # Tokens that indicate a simple column reference 900 IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER}) 901 902 BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE}) 903 904 # Postfix tokens that prevent the bare column fast path 905 COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 906 { 907 TokenType.L_PAREN, 908 TokenType.L_BRACKET, 909 TokenType.L_BRACE, 910 TokenType.COLON, 911 TokenType.JOIN_MARKER, 912 } 913 ) 914 915 TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 916 { 917 TokenType.L_PAREN, 918 TokenType.L_BRACKET, 919 TokenType.L_BRACE, 920 TokenType.PIVOT, 921 TokenType.UNPIVOT, 922 TokenType.TABLE_SAMPLE, 923 } 924 ) 925 926 FUNC_TOKENS: t.ClassVar = { 927 TokenType.COLLATE, 928 TokenType.COMMAND, 929 TokenType.CURRENT_DATE, 930 TokenType.CURRENT_DATETIME, 931 TokenType.CURRENT_SCHEMA, 932 TokenType.CURRENT_TIMESTAMP, 933 TokenType.CURRENT_TIME, 934 TokenType.CURRENT_USER, 935 TokenType.CURRENT_CATALOG, 936 TokenType.DECLARE, 937 TokenType.FILTER, 938 TokenType.FIRST, 939 TokenType.FORMAT, 940 TokenType.GET, 941 TokenType.GLOB, 942 TokenType.IDENTIFIER, 943 TokenType.INDEX, 944 TokenType.ISNULL, 945 TokenType.ILIKE, 946 TokenType.INSERT, 947 TokenType.LIKE, 948 TokenType.LOCALTIME, 949 TokenType.LOCALTIMESTAMP, 950 TokenType.MERGE, 951 TokenType.NEXT, 952 TokenType.OFFSET, 953 TokenType.PRIMARY_KEY, 954 TokenType.RANGE, 955 TokenType.REPLACE, 956 TokenType.RLIKE, 957 TokenType.ROW, 958 TokenType.SESSION_USER, 959 TokenType.UNNEST, 960 TokenType.VAR, 961 TokenType.LEFT, 962 TokenType.RIGHT, 963 TokenType.SEQUENCE, 964 TokenType.DATE, 965 TokenType.DATETIME, 966 TokenType.TABLE, 967 TokenType.TIMESTAMP, 968 TokenType.TIMESTAMPTZ, 969 TokenType.TRUNCATE, 970 TokenType.UTC_DATE, 971 TokenType.UTC_TIME, 972 TokenType.UTC_TIMESTAMP, 973 TokenType.WINDOW, 974 TokenType.XOR, 975 *TYPE_TOKENS, 976 *SUBQUERY_PREDICATES, 977 } 978 979 CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 980 TokenType.AND: exp.And, 981 } 982 983 ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 984 TokenType.COLON_EQ: exp.PropertyEQ, 985 } 986 987 DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 988 TokenType.OR: exp.Or, 989 } 990 991 EQUALITY: t.ClassVar = { 992 TokenType.EQ: exp.EQ, 993 TokenType.NEQ: exp.NEQ, 994 TokenType.NULLSAFE_EQ: exp.NullSafeEQ, 995 } 996 997 COMPARISON: t.ClassVar = { 998 TokenType.GT: exp.GT, 999 TokenType.GTE: exp.GTE, 1000 TokenType.LT: exp.LT, 1001 TokenType.LTE: exp.LTE, 1002 } 1003 1004 BITWISE: t.ClassVar = { 1005 TokenType.AMP: exp.BitwiseAnd, 1006 TokenType.CARET: exp.BitwiseXor, 1007 TokenType.PIPE: exp.BitwiseOr, 1008 } 1009 1010 TERM: t.ClassVar = { 1011 TokenType.DASH: exp.Sub, 1012 TokenType.PLUS: exp.Add, 1013 TokenType.COLLATE: exp.Collate, 1014 } 1015 1016 FACTOR: t.ClassVar = { 1017 TokenType.DIV: exp.IntDiv, 1018 TokenType.LR_ARROW: exp.Distance, 1019 TokenType.LLRR_ARROW: exp.DistanceNd, 1020 TokenType.MOD: exp.Mod, 1021 TokenType.SLASH: exp.Div, 1022 TokenType.STAR: exp.Mul, 1023 } 1024 1025 EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {} 1026 1027 TIMES: t.ClassVar = { 1028 TokenType.TIME, 1029 TokenType.TIMETZ, 1030 } 1031 1032 TIMESTAMPS: t.ClassVar = { 1033 TokenType.TIMESTAMP, 1034 TokenType.TIMESTAMPNTZ, 1035 TokenType.TIMESTAMPTZ, 1036 TokenType.TIMESTAMPLTZ, 1037 *TIMES, 1038 } 1039 1040 SET_OPERATIONS: t.ClassVar = { 1041 TokenType.UNION, 1042 TokenType.INTERSECT, 1043 TokenType.EXCEPT, 1044 } 1045 1046 JOIN_METHODS: t.ClassVar = { 1047 TokenType.ASOF, 1048 TokenType.NATURAL, 1049 TokenType.POSITIONAL, 1050 } 1051 1052 JOIN_SIDES: t.ClassVar = { 1053 TokenType.LEFT, 1054 TokenType.RIGHT, 1055 TokenType.FULL, 1056 } 1057 1058 JOIN_KINDS: t.ClassVar = { 1059 TokenType.ANTI, 1060 TokenType.CROSS, 1061 TokenType.INNER, 1062 TokenType.OUTER, 1063 TokenType.SEMI, 1064 TokenType.STRAIGHT_JOIN, 1065 } 1066 1067 JOIN_HINTS: t.ClassVar[set[str]] = set() 1068 1069 SUPPORTS_DIRECTED_JOIN = True 1070 1071 # Tokens that unambiguously end a table reference on the fast path 1072 TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset( 1073 { 1074 TokenType.COMMA, 1075 TokenType.GROUP_BY, 1076 TokenType.HAVING, 1077 TokenType.JOIN, 1078 TokenType.LIMIT, 1079 TokenType.ON, 1080 TokenType.ORDER_BY, 1081 TokenType.R_PAREN, 1082 TokenType.SEMICOLON, 1083 TokenType.SENTINEL, 1084 TokenType.WHERE, 1085 *SET_OPERATIONS, 1086 *JOIN_KINDS, 1087 *JOIN_METHODS, 1088 *JOIN_SIDES, 1089 } 1090 ) 1091 1092 LAMBDAS: t.ClassVar = { 1093 TokenType.ARROW: lambda self, expressions: self.expression( 1094 exp.Lambda( 1095 this=self._replace_lambda( 1096 self._parse_disjunction(), 1097 expressions, 1098 ), 1099 expressions=expressions, 1100 ) 1101 ), 1102 TokenType.FARROW: lambda self, expressions: self.expression( 1103 exp.Kwarg( 1104 this=exp.var(expressions[0].name), 1105 expression=self._parse_disjunction() or self._parse_select(), 1106 ) 1107 ), 1108 } 1109 1110 # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake 1111 TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False 1112 1113 LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN}) 1114 1115 COLUMN_OPERATORS: t.ClassVar = { 1116 TokenType.DOT: None, 1117 TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)), 1118 TokenType.DCOLON: lambda self, this, to: self.build_cast( 1119 strict=self.STRICT_CAST, this=this, to=to 1120 ), 1121 TokenType.ARROW: lambda self, this, path: self.expression( 1122 exp.JSONExtract( 1123 this=this, 1124 expression=self.dialect.to_json_path(path), 1125 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1126 ) 1127 ), 1128 TokenType.DARROW: lambda self, this, path: self.expression( 1129 exp.JSONExtractScalar( 1130 this=this, 1131 expression=self.dialect.to_json_path(path), 1132 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1133 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 1134 ) 1135 ), 1136 TokenType.HASH_ARROW: lambda self, this, path: self.expression( 1137 exp.JSONBExtract(this=this, expression=path) 1138 ), 1139 TokenType.DHASH_ARROW: lambda self, this, path: self.expression( 1140 exp.JSONBExtractScalar(this=this, expression=path) 1141 ), 1142 TokenType.PLACEHOLDER: lambda self, this, key: self.expression( 1143 exp.JSONBContainsTopKey(this=this, expression=key) 1144 ), 1145 } 1146 1147 # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator" 1148 # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs). 1149 JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {} 1150 1151 CAST_COLUMN_OPERATORS: t.ClassVar = { 1152 TokenType.DOTCOLON, 1153 TokenType.DCOLON, 1154 } 1155 1156 EXPRESSION_PARSERS: t.ClassVar = { 1157 exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), 1158 exp.Column: lambda self: self._parse_column(), 1159 exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()), 1160 exp.Condition: lambda self: self._parse_disjunction(), 1161 exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True), 1162 exp.Expr: lambda self: self._parse_expression(), 1163 exp.From: lambda self: self._parse_from(joins=True), 1164 exp.GrantPrincipal: lambda self: self._parse_grant_principal(), 1165 exp.GrantPrivilege: lambda self: self._parse_grant_privilege(), 1166 exp.Group: lambda self: self._parse_group(), 1167 exp.Having: lambda self: self._parse_having(), 1168 exp.Hint: lambda self: self._parse_hint_body(), 1169 exp.Identifier: lambda self: self._parse_id_var(), 1170 exp.Join: lambda self: self._parse_join(), 1171 exp.Lambda: lambda self: self._parse_lambda(), 1172 exp.Lateral: lambda self: self._parse_lateral(), 1173 exp.Limit: lambda self: self._parse_limit(), 1174 exp.Offset: lambda self: self._parse_offset(), 1175 exp.Order: lambda self: self._parse_order(), 1176 exp.Ordered: lambda self: self._parse_ordered(), 1177 exp.Properties: lambda self: self._parse_properties(), 1178 exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(), 1179 exp.Qualify: lambda self: self._parse_qualify(), 1180 exp.Returning: lambda self: self._parse_returning(), 1181 exp.Select: lambda self: self._parse_select(), 1182 exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), 1183 exp.Table: lambda self: self._parse_table_parts(), 1184 exp.TableAlias: lambda self: self._parse_table_alias(), 1185 exp.Tuple: lambda self: self._parse_value(values=False), 1186 exp.Whens: lambda self: self._parse_when_matched(), 1187 exp.Where: lambda self: self._parse_where(), 1188 exp.Window: lambda self: self._parse_named_window(), 1189 exp.With: lambda self: self._parse_with(), 1190 } 1191 1192 STATEMENT_PARSERS: t.ClassVar = { 1193 TokenType.ALTER: lambda self: self._parse_alter(), 1194 TokenType.ANALYZE: lambda self: self._parse_analyze(), 1195 TokenType.BEGIN: lambda self: self._parse_transaction(), 1196 TokenType.CACHE: lambda self: self._parse_cache(), 1197 TokenType.COMMENT: lambda self: self._parse_comment(), 1198 TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(), 1199 TokenType.COPY: lambda self: self._parse_copy(), 1200 TokenType.CREATE: lambda self: self._parse_create(), 1201 TokenType.DECLARE: lambda self: self._parse_declare(), 1202 TokenType.DELETE: lambda self: self._parse_delete(), 1203 TokenType.DESC: lambda self: self._parse_describe(), 1204 TokenType.DESCRIBE: lambda self: self._parse_describe(), 1205 TokenType.DROP: lambda self: self._parse_drop(), 1206 TokenType.GRANT: lambda self: self._parse_grant(), 1207 TokenType.REVOKE: lambda self: self._parse_revoke(), 1208 TokenType.INSERT: lambda self: self._parse_insert(), 1209 TokenType.KILL: lambda self: self._parse_kill(), 1210 TokenType.LOAD: lambda self: self._parse_load(), 1211 TokenType.MERGE: lambda self: self._parse_merge(), 1212 TokenType.PIVOT: lambda self: self._parse_simplified_pivot(), 1213 TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())), 1214 TokenType.REFRESH: lambda self: self._parse_refresh(), 1215 TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(), 1216 TokenType.SET: lambda self: self._parse_set(), 1217 TokenType.TRUNCATE: lambda self: self._parse_truncate_table(), 1218 TokenType.UNCACHE: lambda self: self._parse_uncache(), 1219 TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True), 1220 TokenType.UPDATE: lambda self: self._parse_update(), 1221 TokenType.USE: lambda self: self._parse_use(), 1222 TokenType.SEMICOLON: lambda self: exp.Semicolon(), 1223 } 1224 1225 UNARY_PARSERS: t.ClassVar = { 1226 TokenType.PLUS: lambda self: self._parse_unary(), # Unary + is handled as a no-op 1227 TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())), 1228 TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())), 1229 TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())), 1230 TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())), 1231 TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())), 1232 } 1233 1234 STRING_PARSERS: t.ClassVar = { 1235 TokenType.HEREDOC_STRING: lambda self, token: self.expression( 1236 exp.RawString(this=token.text), token 1237 ), 1238 **{ 1239 token_type: lambda self, token: self.expression(exp.National(this=token.text), token) 1240 for token_type in (TokenType.NATIONAL_STRING, TokenType.NATIONAL_RAW_STRING) 1241 }, 1242 TokenType.RAW_STRING: lambda self, token: self.expression( 1243 exp.RawString(this=token.text), token 1244 ), 1245 TokenType.STRING: lambda self, token: self.expression( 1246 exp.Literal(this=token.text, is_string=True), token 1247 ), 1248 TokenType.UNICODE_STRING: lambda self, token: self.expression( 1249 exp.UnicodeString( 1250 this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string() 1251 ), 1252 token, 1253 ), 1254 } 1255 1256 NUMERIC_PARSERS: t.ClassVar = { 1257 TokenType.BIT_STRING: lambda self, token: self.expression( 1258 exp.BitString(this=token.text), token 1259 ), 1260 TokenType.BYTE_STRING: lambda self, token: self.expression( 1261 exp.ByteString( 1262 this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None 1263 ), 1264 token, 1265 ), 1266 TokenType.HEX_STRING: lambda self, token: self.expression( 1267 exp.HexString( 1268 this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None 1269 ), 1270 token, 1271 ), 1272 TokenType.NUMBER: lambda self, token: self.expression( 1273 exp.Literal(this=token.text, is_string=False), token 1274 ), 1275 } 1276 1277 PRIMARY_PARSERS: t.ClassVar = { 1278 **STRING_PARSERS, 1279 **NUMERIC_PARSERS, 1280 TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token), 1281 TokenType.NULL: lambda self, _: self.expression(exp.Null()), 1282 TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)), 1283 TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)), 1284 TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(), 1285 TokenType.STAR: lambda self, _: self._parse_star_ops(), 1286 } 1287 1288 PLACEHOLDER_PARSERS: t.ClassVar = { 1289 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()), 1290 TokenType.PARAMETER: lambda self: self._parse_parameter(), 1291 TokenType.COLON: lambda self: ( 1292 self.expression(exp.Placeholder(this=self._prev.text)) 1293 if self._match_set(self.COLON_PLACEHOLDER_TOKENS) 1294 else None 1295 ), 1296 } 1297 1298 RANGE_PARSERS: t.ClassVar = { 1299 TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll), 1300 TokenType.BETWEEN: lambda self, this: self._parse_between(this), 1301 TokenType.GLOB: binary_range_parser(exp.Glob), 1302 TokenType.ILIKE: binary_range_parser(exp.ILike), 1303 TokenType.IN: lambda self, this: self._parse_in(this), 1304 TokenType.IRLIKE: binary_range_parser(exp.RegexpILike), 1305 TokenType.IS: lambda self, this: self._parse_is(this), 1306 TokenType.LIKE: binary_range_parser(exp.Like), 1307 TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy), 1308 TokenType.OVERLAPS: binary_range_parser(exp.Overlaps), 1309 TokenType.RLIKE: binary_range_parser(exp.RegexpLike), 1310 TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo), 1311 TokenType.FOR: lambda self, this: self._parse_comprehension(this), 1312 TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys), 1313 TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys), 1314 TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath), 1315 TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists), 1316 TokenType.ADJACENT: binary_range_parser(exp.Adjacent), 1317 TokenType.OPERATOR: lambda self, this: self._parse_operator(this), 1318 TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft), 1319 TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight), 1320 TokenType.CARET_AT: binary_range_parser(exp.StartsWith), 1321 } 1322 1323 PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = { 1324 "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query), 1325 "AS": lambda self, query: self._build_pipe_cte( 1326 query, [exp.Star()], self._parse_table_alias() 1327 ), 1328 "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False), 1329 "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query), 1330 "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query), 1331 "ORDER BY": lambda self, query: query.order_by( 1332 self._parse_order(), append=False, copy=False 1333 ), 1334 "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1335 "SELECT": lambda self, query: self._parse_pipe_syntax_select(query), 1336 "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query), 1337 "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1338 "WHERE": lambda self, query: query.where(self._parse_where(), copy=False), 1339 } 1340 1341 PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1342 "ALLOWED_VALUES": lambda self: self.expression( 1343 exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary)) 1344 ), 1345 "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty), 1346 "AUTO": lambda self: self._parse_auto_property(), 1347 "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty), 1348 "BACKUP": lambda self: self.expression( 1349 exp.BackupProperty(this=self._parse_var(any_token=True)) 1350 ), 1351 "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(), 1352 "CALLED": lambda self: self._parse_called_on_null_input_property(), 1353 "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs), 1354 "CHECKSUM": lambda self: self._parse_checksum(), 1355 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1356 "CLUSTERED": lambda self: self._parse_clustered_by(), 1357 "COLLATE": lambda self, **kwargs: self._parse_property_assignment( 1358 exp.CollateProperty, **kwargs 1359 ), 1360 "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty), 1361 "CONTAINS": lambda self: self._parse_contains_property(), 1362 "COPY": lambda self: self._parse_copy_property(), 1363 "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs), 1364 "DATA_DELETION": lambda self: self._parse_data_deletion_property(), 1365 "DEFINER": lambda self: self._parse_definer(), 1366 "DETERMINISTIC": lambda self: self.expression( 1367 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1368 ), 1369 "DISTRIBUTED": lambda self: self._parse_distributed_property(), 1370 "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty), 1371 "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()), 1372 "DISTKEY": lambda self: self._parse_distkey(), 1373 "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty), 1374 "EMPTY": lambda self: self.expression(exp.EmptyProperty()), 1375 "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty), 1376 "ENVIRONMENT": lambda self: self.expression( 1377 exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment)) 1378 ), 1379 "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty), 1380 "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty), 1381 "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()), 1382 "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs), 1383 "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1384 "FREESPACE": lambda self: self._parse_freespace(), 1385 "GLOBAL": lambda self: self.expression(exp.GlobalProperty()), 1386 "HEAP": lambda self: self.expression(exp.HeapProperty()), 1387 "ICEBERG": lambda self: self.expression(exp.IcebergProperty()), 1388 "IMMUTABLE": lambda self: self.expression( 1389 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1390 ), 1391 "INHERITS": lambda self: self.expression( 1392 exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table)) 1393 ), 1394 "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())), 1395 "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs), 1396 "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty), 1397 "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"), 1398 "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"), 1399 "LIKE": lambda self: self._parse_create_like(), 1400 "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty), 1401 "LOCK": lambda self: self._parse_locking(), 1402 "LOCKING": lambda self: self._parse_locking(), 1403 "LOG": lambda self, **kwargs: self._parse_log(**kwargs), 1404 "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()), 1405 "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs), 1406 "MODIFIES": lambda self: self._parse_modifies_property(), 1407 "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)), 1408 "NO": lambda self: self._parse_no_property(), 1409 "ON": lambda self: self._parse_on_property(), 1410 "ORDER BY": lambda self: self._parse_order(skip_order_token=True), 1411 "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())), 1412 "PARTITION": lambda self: self._parse_partitioned_of(), 1413 "PARTITION BY": lambda self: self._parse_partitioned_by(), 1414 "PARTITIONED BY": lambda self: self._parse_partitioned_by(), 1415 "PARTITIONED_BY": lambda self: self._parse_partitioned_by(), 1416 "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True), 1417 "RANGE": lambda self: self._parse_dict_range(this="RANGE"), 1418 "READS": lambda self: self._parse_reads_property(), 1419 "REMOTE": lambda self: self._parse_remote_with_connection(), 1420 "RETURNS": lambda self: self._parse_returns(), 1421 "STRICT": lambda self: self.expression(exp.StrictProperty()), 1422 "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()), 1423 "ROW": lambda self: self._parse_row(), 1424 "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty), 1425 "SAMPLE": lambda self: self.expression( 1426 exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise()) 1427 ), 1428 "SECURE": lambda self: self.expression(exp.SecureProperty()), 1429 "SECURITY": lambda self: self._parse_sql_security(), 1430 "SQL SECURITY": lambda self: self._parse_sql_security(), 1431 "SET": lambda self: self.expression(exp.SetProperty(multi=False)), 1432 "SETTINGS": lambda self: self._parse_settings_property(), 1433 "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty), 1434 "SORTKEY": lambda self: self._parse_sortkey(), 1435 "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"), 1436 "STABLE": lambda self: self.expression( 1437 exp.StabilityProperty(this=exp.Literal.string("STABLE")) 1438 ), 1439 "STORED": lambda self: self._parse_stored(), 1440 "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(), 1441 "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(), 1442 "TEMP": lambda self: self.expression(exp.TemporaryProperty()), 1443 "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()), 1444 "TO": lambda self: self._parse_to_table(), 1445 "TRANSIENT": lambda self: self.expression(exp.TransientProperty()), 1446 "TRANSFORM": lambda self: self.expression( 1447 exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression)) 1448 ), 1449 "TTL": lambda self: self._parse_ttl(), 1450 "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1451 "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()), 1452 "VOLATILE": lambda self: self._parse_volatile_property(), 1453 "WITH": lambda self: self._parse_with_property(), 1454 } 1455 1456 CONSTRAINT_PARSERS: t.ClassVar = { 1457 "AUTOINCREMENT": lambda self: self._parse_auto_increment(), 1458 "AUTO_INCREMENT": lambda self: self._parse_auto_increment(), 1459 "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)), 1460 "CHECK": lambda self: self._parse_check_constraint(), 1461 "COLLATE": lambda self: self.expression( 1462 exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column()) 1463 ), 1464 "COMMENT": lambda self: self.expression( 1465 exp.CommentColumnConstraint(this=self._parse_string()) 1466 ), 1467 "COMPRESS": lambda self: self._parse_compress(), 1468 "CLUSTERED": lambda self: self.expression( 1469 exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1470 ), 1471 "NONCLUSTERED": lambda self: self.expression( 1472 exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1473 ), 1474 "DEFAULT": lambda self: self.expression( 1475 exp.DefaultColumnConstraint(this=self._parse_bitwise()) 1476 ), 1477 "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())), 1478 "EPHEMERAL": lambda self: self.expression( 1479 exp.EphemeralColumnConstraint(this=self._parse_bitwise()) 1480 ), 1481 "EXCLUDE": lambda self: self.expression( 1482 exp.ExcludeColumnConstraint(this=self._parse_index_params()) 1483 ), 1484 "FOREIGN KEY": lambda self: self._parse_foreign_key(), 1485 "FORMAT": lambda self: self.expression( 1486 exp.DateFormatColumnConstraint(this=self._parse_var_or_string()) 1487 ), 1488 "GENERATED": lambda self: self._parse_generated_as_identity(), 1489 "IDENTITY": lambda self: self._parse_auto_increment(), 1490 "INLINE": lambda self: self._parse_inline(), 1491 "LIKE": lambda self: self._parse_create_like(), 1492 "NOT": lambda self: self._parse_not_constraint(), 1493 "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)), 1494 "ON": lambda self: ( 1495 ( 1496 self._match(TokenType.UPDATE) 1497 and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function())) 1498 ) 1499 or self.expression(exp.OnProperty(this=self._parse_id_var())) 1500 ), 1501 "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())), 1502 "PERIOD": lambda self: self._parse_period_for_system_time(), 1503 "PRIMARY KEY": lambda self: self._parse_primary_key(), 1504 "REFERENCES": lambda self: self._parse_references(match=False), 1505 "TITLE": lambda self: self.expression( 1506 exp.TitleColumnConstraint(this=self._parse_var_or_string()) 1507 ), 1508 "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])), 1509 "UNIQUE": lambda self: self._parse_unique(), 1510 "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()), 1511 "WITH": lambda self: self.expression( 1512 exp.Properties(expressions=self._parse_wrapped_properties()) 1513 ), 1514 "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1515 "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1516 } 1517 1518 def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None: 1519 if not self._match(TokenType.L_PAREN, advance=False): 1520 # Partitioning by bucket or truncate follows the syntax: 1521 # PARTITION BY (BUCKET(..) | TRUNCATE(..)) 1522 # If we don't have parenthesis after each keyword, we should instead parse this as an identifier 1523 self._retreat(self._index - 1) 1524 return None 1525 1526 klass = ( 1527 exp.PartitionedByBucket 1528 if self._prev.text.upper() == "BUCKET" 1529 else exp.PartitionByTruncate 1530 ) 1531 1532 args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column()) 1533 this, expression = seq_get(args, 0), seq_get(args, 1) 1534 1535 if isinstance(this, exp.Literal): 1536 # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order 1537 # - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)` 1538 # - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)` 1539 # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)` 1540 # 1541 # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning 1542 # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties 1543 this, expression = expression, this 1544 1545 return self.expression(klass(this=this, expression=expression)) 1546 1547 ALTER_PARSERS: t.ClassVar = { 1548 "ADD": lambda self: self._parse_alter_table_add(), 1549 "AS": lambda self: self._parse_select(), 1550 "ALTER": lambda self: self._parse_alter_table_alter(), 1551 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1552 "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())), 1553 "DROP": lambda self: self._parse_alter_table_drop(), 1554 "RENAME": lambda self: self._parse_alter_table_rename(), 1555 "SET": lambda self: self._parse_alter_table_set(), 1556 "SWAP": lambda self: self.expression( 1557 exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True)) 1558 ), 1559 } 1560 1561 ALTER_ALTER_PARSERS: t.ClassVar = { 1562 "DISTKEY": lambda self: self._parse_alter_diststyle(), 1563 "DISTSTYLE": lambda self: self._parse_alter_diststyle(), 1564 "SORTKEY": lambda self: self._parse_alter_sortkey(), 1565 "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True), 1566 } 1567 1568 SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = { 1569 "CHECK", 1570 "EXCLUDE", 1571 "FOREIGN KEY", 1572 "LIKE", 1573 "PERIOD", 1574 "PRIMARY KEY", 1575 "UNIQUE", 1576 "BUCKET", 1577 "TRUNCATE", 1578 } 1579 1580 # Kinds that can't be a constraint name, so `CONSTRAINT` directly followed by one is unnamed 1581 RESERVED_CONSTRAINT_KINDS: t.ClassVar[set[str]] = set() 1582 1583 NO_PAREN_FUNCTION_PARSERS: t.ClassVar = { 1584 "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())), 1585 "CASE": lambda self: self._parse_case(), 1586 "CONNECT_BY_ROOT": lambda self: self.expression( 1587 exp.ConnectByRoot(this=self._parse_column()) 1588 ), 1589 "IF": lambda self: self._parse_if(), 1590 } 1591 1592 INVALID_FUNC_NAME_TOKENS: t.ClassVar = { 1593 TokenType.IDENTIFIER, 1594 TokenType.STRING, 1595 } 1596 1597 FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"} 1598 1599 KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice) 1600 1601 FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1602 **{ 1603 name: lambda self: self._parse_distinct_arg_function(exp.ArgMax) 1604 for name in exp.ArgMax.sql_names() 1605 }, 1606 **{ 1607 name: lambda self: self._parse_distinct_arg_function(exp.ArgMin) 1608 for name in exp.ArgMin.sql_names() 1609 }, 1610 "CAST": lambda self: self._parse_cast(self.STRICT_CAST), 1611 "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), 1612 "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), 1613 "CHAR": lambda self: self._parse_char(), 1614 "CHR": lambda self: self._parse_char(), 1615 "DECODE": lambda self: self._parse_decode(), 1616 "EXTRACT": lambda self: self._parse_extract(), 1617 "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), 1618 "GAP_FILL": lambda self: self._parse_gap_fill(), 1619 "INITCAP": lambda self: self._parse_initcap(), 1620 "JSON_OBJECT": lambda self: self._parse_json_object(), 1621 "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True), 1622 "JSON_TABLE": lambda self: self._parse_json_table(), 1623 "MATCH": lambda self: self._parse_match_against(), 1624 "NORMALIZE": lambda self: self._parse_normalize(), 1625 "OPENJSON": lambda self: self._parse_open_json(), 1626 "OVERLAY": lambda self: self._parse_overlay(), 1627 "POSITION": lambda self: self._parse_position(), 1628 "SAFE_CAST": lambda self: self._parse_cast(False, safe=True), 1629 "STRING_AGG": lambda self: self._parse_string_agg(), 1630 "SUBSTRING": lambda self: self._parse_substring(), 1631 "TRIM": lambda self: self._parse_trim(), 1632 "TRY_CAST": lambda self: self._parse_cast(False, safe=True), 1633 "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True), 1634 "WEIGHT_STRING": lambda self: self._parse_weight_string(), 1635 "XMLELEMENT": lambda self: self._parse_xml_element(), 1636 "XMLTABLE": lambda self: self._parse_xml_table(), 1637 } 1638 1639 QUERY_MODIFIER_PARSERS: t.ClassVar = { 1640 TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()), 1641 TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()), 1642 TokenType.WHERE: lambda self: ("where", self._parse_where()), 1643 TokenType.GROUP_BY: lambda self: ("group", self._parse_group()), 1644 TokenType.HAVING: lambda self: ("having", self._parse_having()), 1645 TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()), 1646 TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()), 1647 TokenType.ORDER_BY: lambda self: ("order", self._parse_order()), 1648 TokenType.LIMIT: lambda self: ("limit", self._parse_limit()), 1649 TokenType.FETCH: lambda self: ("limit", self._parse_limit()), 1650 TokenType.OFFSET: lambda self: ("offset", self._parse_offset()), 1651 TokenType.FOR: lambda self: ("locks", self._parse_locks()), 1652 TokenType.LOCK: lambda self: ("locks", self._parse_locks()), 1653 TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1654 TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1655 TokenType.CLUSTER_BY: lambda self: ( 1656 "cluster", 1657 self._parse_cluster(), 1658 ), 1659 TokenType.DISTRIBUTE_BY: lambda self: ( 1660 "distribute", 1661 self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), 1662 ), 1663 TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)), 1664 TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)), 1665 } 1666 QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS) 1667 1668 SET_PARSERS: t.ClassVar = { 1669 "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"), 1670 "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"), 1671 "SESSION": lambda self: self._parse_set_item_assignment("SESSION"), 1672 "TRANSACTION": lambda self: self._parse_set_transaction(), 1673 } 1674 1675 SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {} 1676 1677 TYPE_LITERAL_PARSERS: t.ClassVar = { 1678 exp.DType.JSON: lambda self, this, _: self.expression( 1679 exp.ParseJSON(this=this, is_literal=True) 1680 ), 1681 } 1682 1683 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1684 1685 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1686 1687 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1688 1689 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1690 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1691 "ISOLATION": ( 1692 ("LEVEL", "REPEATABLE", "READ"), 1693 ("LEVEL", "READ", "COMMITTED"), 1694 ("LEVEL", "READ", "UNCOMITTED"), 1695 ("LEVEL", "SERIALIZABLE"), 1696 ), 1697 "READ": ("WRITE", "ONLY"), 1698 } 1699 1700 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1701 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1702 "DO": ("NOTHING", "UPDATE"), 1703 } 1704 1705 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1706 "INSTEAD": (("OF",),), 1707 "BEFORE": tuple(), 1708 "AFTER": tuple(), 1709 } 1710 1711 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1712 "NOT": (("DEFERRABLE",),), 1713 "DEFERRABLE": tuple(), 1714 } 1715 1716 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1717 "SCALE": ("EXTEND", "NOEXTEND"), 1718 "SHARD": ("EXTEND", "NOEXTEND"), 1719 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1720 **dict.fromkeys( 1721 ( 1722 "SESSION", 1723 "GLOBAL", 1724 "KEEP", 1725 "NOKEEP", 1726 "ORDER", 1727 "NOORDER", 1728 "NOCACHE", 1729 "CYCLE", 1730 "NOCYCLE", 1731 "NOMINVALUE", 1732 "NOMAXVALUE", 1733 "NOSCALE", 1734 "NOSHARD", 1735 ), 1736 tuple(), 1737 ), 1738 } 1739 1740 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1741 1742 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1743 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1744 ) 1745 1746 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1747 1748 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1749 "TYPE": ("EVOLUTION",), 1750 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1751 } 1752 1753 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1754 1755 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1756 ("CALLER", "SELF", "OWNER"), tuple() 1757 ) 1758 1759 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1760 "NOT": ("ENFORCED",), 1761 "MATCH": ( 1762 "FULL", 1763 "PARTIAL", 1764 "SIMPLE", 1765 ), 1766 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1767 "USING": ( 1768 "BTREE", 1769 "HASH", 1770 ), 1771 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1772 } 1773 1774 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1775 "NO": ("OTHERS",), 1776 "CURRENT": ("ROW",), 1777 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1778 } 1779 1780 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1781 1782 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1783 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1784 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1785 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1786 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1787 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1788 ("FOR", "VERSION"): "VERSION", 1789 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1790 ("VERSION", "AS", "OF"): "VERSION", 1791 } 1792 1793 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1794 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1795 1796 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1797 1798 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1799 1800 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1801 1802 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1803 1804 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1805 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1806 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1807 1808 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1809 1810 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1811 1812 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1813 TokenType.CONSTRAINT, 1814 TokenType.FOREIGN_KEY, 1815 TokenType.INDEX, 1816 TokenType.KEY, 1817 TokenType.PRIMARY_KEY, 1818 TokenType.UNIQUE, 1819 } 1820 1821 # Constraint kinds that start an ALTER ... ADD action but aren't tokens of their own 1822 ADD_CONSTRAINT_KEYWORDS: t.ClassVar[set[str]] = set() 1823 1824 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1825 1826 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1827 1828 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1829 1830 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1831 "FILE_FORMAT", 1832 "COPY_OPTIONS", 1833 "FORMAT_OPTIONS", 1834 "CREDENTIAL", 1835 } 1836 1837 # IS binds above comparisons (high), alongside ordering comparisons (comparison), 1838 # alongside equality (equality), or below both (low). Preserve the historical 1839 # high precedence unless the dialect specifies otherwise. 1840 IS_PREDICATE_PRECEDENCE: t.ClassVar[IsPredicatePrecedence] = IsPredicatePrecedence.HIGH 1841 IS_PREDICATE_TOKENS: t.ClassVar = { 1842 TokenType.IS, 1843 TokenType.ISNULL, 1844 TokenType.NOTNULL, 1845 TokenType.NOT, 1846 } 1847 1848 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1849 1850 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1851 1852 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1853 1854 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1855 1856 # The style options for the DESCRIBE statement 1857 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1858 1859 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1860 1861 # The style options for the ANALYZE statement 1862 ANALYZE_STYLES: t.ClassVar = { 1863 "BUFFER_USAGE_LIMIT", 1864 "FULL", 1865 "LOCAL", 1866 "NO_WRITE_TO_BINLOG", 1867 "SAMPLE", 1868 "SKIP_LOCKED", 1869 "VERBOSE", 1870 } 1871 1872 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1873 "ALL": lambda self: self._parse_analyze_columns(), 1874 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1875 "DELETE": lambda self: self._parse_analyze_delete(), 1876 "DROP": lambda self: self._parse_analyze_histogram(), 1877 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1878 "LIST": lambda self: self._parse_analyze_list(), 1879 "PREDICATE": lambda self: self._parse_analyze_columns(), 1880 "UPDATE": lambda self: self._parse_analyze_histogram(), 1881 "VALIDATE": lambda self: self._parse_analyze_validate(), 1882 } 1883 1884 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1885 1886 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1887 1888 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1889 1890 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1891 1892 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1893 1894 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1895 1896 STRICT_CAST: t.ClassVar = True 1897 1898 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1899 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1900 # Whether an UNPIVOT outputs its value column(s) before the name column 1901 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1902 # Controls when an aggregation's name is included in a pivoted column's name: 1903 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1904 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1905 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1906 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1907 1908 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1909 1910 # Whether the table sample clause expects CSV syntax 1911 TABLESAMPLE_CSV: t.ClassVar = False 1912 1913 # The default method used for table sampling 1914 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1915 1916 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1917 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1918 1919 # Whether the TRIM function expects the characters to trim as its first argument 1920 TRIM_PATTERN_FIRST: t.ClassVar = False 1921 1922 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1923 STRING_ALIASES: t.ClassVar = False 1924 1925 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1926 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1927 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"} 1928 1929 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1930 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1931 1932 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1933 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1934 1935 # Whether the `:` operator is used to extract a value from a VARIANT column 1936 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1937 1938 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1939 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1940 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1941 1942 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1943 # If this is True and '(' is not found, the keyword will be treated as an identifier 1944 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1945 1946 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1947 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1948 1949 # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery) 1950 SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False 1951 1952 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1953 INTERVAL_SPANS: t.ClassVar = True 1954 1955 # Whether a PARTITION clause can follow a table reference 1956 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1957 1958 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1959 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1960 1961 # Whether the 'AS' keyword is optional in the CTE definition syntax 1962 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1963 1964 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1965 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1966 1967 # Whether dropping a column with an ALTER statement requires the presence of the COLUMN keyword 1968 ALTER_DROP_REQUIRES_COLUMN: t.ClassVar = True 1969 1970 # Whether Alter statements are allowed to contain Partition specifications 1971 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1972 1973 # Whether an ALTER can combine different actions, e.g. ADD COLUMN a INT, DROP COLUMN b 1974 ALTER_TABLE_MIXED_ACTIONS: t.ClassVar = False 1975 1976 # Whether an ALTER TABLE needs an action, e.g. MySQL's ALTER TABLE t ENGINE=InnoDB has none 1977 ALTER_TABLE_REQUIRES_ACTION: t.ClassVar = True 1978 1979 # Whether changing a column's type with ALTER COLUMN requires the TYPE keyword 1980 ALTER_COLUMN_TYPE_REQUIRES_KEYWORD: t.ClassVar = False 1981 1982 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1983 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1984 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1985 # as BigQuery, where all joins have the same precedence. 1986 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1987 1988 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1989 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1990 1991 # Whether map literals support arbitrary expressions as keys. 1992 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1993 # When False, keys are typically restricted to identifiers. 1994 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1995 1996 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1997 # is true for Snowflake but not for BigQuery which can also process strings 1998 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1999 2000 # Dialects like Databricks support JOINS without join criteria 2001 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 2002 ADD_JOIN_ON_TRUE: t.ClassVar = False 2003 2004 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 2005 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 2006 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 2007 2008 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 2009 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 2010 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 2011 2012 # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause, 2013 # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake) 2014 SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False 2015 2016 # Type names that denote a different type when they're quoted, so quoting has to be 2017 # preserved instead of resolving them into the built-in type of the same name. These 2018 # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR 2019 QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set() 2020 2021 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 2022 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 2023 2024 def __init__( 2025 self, 2026 error_level: ErrorLevel | None = None, 2027 error_message_context: int = 100, 2028 max_errors: int = 3, 2029 max_nodes: int = -1, 2030 dialect: DialectType = None, 2031 ): 2032 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 2033 self.error_message_context: int = error_message_context 2034 self.max_errors: int = max_errors 2035 self.max_nodes: int = max_nodes 2036 self.dialect: t.Any = _resolve_dialect(dialect) 2037 self.sql: str = "" 2038 self.errors: list[ParseError] = [] 2039 self._tokens: list[Token] = [] 2040 self._tokens_size: i64 = 0 2041 self._index: i64 = 0 2042 self._curr: Token = SENTINEL_NONE 2043 self._next: Token = SENTINEL_NONE 2044 self._prev: Token = SENTINEL_NONE 2045 self._prev_comments: list[str] = [] 2046 self._pipe_cte_counter: int = 0 2047 self._chunks: list[list[Token]] = [] 2048 self._chunk_index: i64 = 0 2049 self._node_count: int = 0 2050 2051 def reset(self) -> None: 2052 self.sql = "" 2053 self.errors = [] 2054 self._tokens = [] 2055 self._tokens_size = 0 2056 self._index = 0 2057 self._curr = SENTINEL_NONE 2058 self._next = SENTINEL_NONE 2059 self._prev = SENTINEL_NONE 2060 self._prev_comments = [] 2061 self._pipe_cte_counter = 0 2062 self._chunks = [] 2063 self._chunk_index = 0 2064 self._node_count = 0 2065 2066 def _advance(self, times: i64 = 1) -> None: 2067 index = self._index + times 2068 self._index = index 2069 tokens = self._tokens 2070 size = self._tokens_size 2071 self._curr = tokens[index] if index < size else SENTINEL_NONE 2072 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 2073 2074 if index > 0: 2075 prev = tokens[index - 1] 2076 self._prev = prev 2077 self._prev_comments = prev.comments 2078 else: 2079 self._prev = SENTINEL_NONE 2080 self._prev_comments = [] 2081 2082 def _advance_chunk(self) -> None: 2083 self._index = -1 2084 self._tokens = self._chunks[self._chunk_index] 2085 self._tokens_size = i64(len(self._tokens)) 2086 self._chunk_index += 1 2087 self._advance() 2088 2089 def _retreat(self, index: i64) -> None: 2090 if index != self._index: 2091 self._advance(index - self._index) 2092 2093 def _add_comments(self, expression: exp.Expr | None) -> None: 2094 if expression and self._prev_comments: 2095 expression.add_comments(self._prev_comments) 2096 self._prev_comments = [] 2097 2098 def _match( 2099 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2100 ) -> bool: 2101 if self._curr.token_type == token_type: 2102 if advance: 2103 self._advance() 2104 self._add_comments(expression) 2105 return True 2106 return False 2107 2108 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2109 if self._curr.token_type in types: 2110 if advance: 2111 self._advance() 2112 return True 2113 return False 2114 2115 def _match_pair( 2116 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2117 ) -> bool: 2118 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2119 if advance: 2120 self._advance(2) 2121 return True 2122 return False 2123 2124 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2125 if ( 2126 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2127 and self._curr.text.upper() in texts 2128 ): 2129 if advance: 2130 self._advance() 2131 return True 2132 return False 2133 2134 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2135 index = self._index 2136 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2137 for text in texts: 2138 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2139 self._advance() 2140 else: 2141 self._retreat(index) 2142 return False 2143 2144 if not advance: 2145 self._retreat(index) 2146 2147 return True 2148 2149 def _is_connected(self) -> bool: 2150 prev = self._prev 2151 curr = self._curr 2152 return bool(prev and curr and prev.end + 1 == curr.start) 2153 2154 def _find_sql(self, start: Token, end: Token) -> str: 2155 return self.sql[start.start : end.end + 1] 2156 2157 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2158 token = token or self._curr or self._prev or Token.string("") 2159 formatted_sql, start_context, highlight, end_context = highlight_sql( 2160 sql=self.sql, 2161 positions=[(token.start, token.end)], 2162 context_length=self.error_message_context, 2163 ) 2164 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2165 2166 error = ParseError.new( 2167 formatted_message, 2168 description=message, 2169 line=token.line, 2170 col=token.col, 2171 start_context=start_context, 2172 highlight=highlight, 2173 end_context=end_context, 2174 ) 2175 2176 if self.error_level == ErrorLevel.IMMEDIATE: 2177 raise error 2178 2179 self.errors.append(error) 2180 2181 def validate_expression(self, expression: E, args: list | None = None) -> E: 2182 if self.max_nodes > -1: 2183 self._node_count += 1 2184 if self._node_count > self.max_nodes: 2185 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2186 if self.error_level != ErrorLevel.IGNORE: 2187 for error_message in expression.error_messages(args): 2188 self.raise_error(error_message) 2189 return expression 2190 2191 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2192 index = self._index 2193 error_level = self.error_level 2194 this: T | None = None 2195 2196 self.error_level = ErrorLevel.IMMEDIATE 2197 try: 2198 this = parse_method() 2199 except ParseError: 2200 this = None 2201 finally: 2202 if not this or retreat: 2203 self._retreat(index) 2204 self.error_level = error_level 2205 2206 return this 2207 2208 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2209 """ 2210 Parses a list of tokens and returns a list of syntax trees, one tree 2211 per parsed SQL statement. 2212 2213 Args: 2214 raw_tokens: The list of tokens. 2215 sql: The original SQL string. 2216 2217 Returns: 2218 The list of the produced syntax trees. 2219 """ 2220 return self._parse( 2221 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2222 ) 2223 2224 def parse_into( 2225 self, 2226 expression_types: exp.IntoType, 2227 raw_tokens: list[Token], 2228 sql: str | None = None, 2229 ) -> list[exp.Expr | None]: 2230 """ 2231 Parses a list of tokens into a given Expr type. If a collection of Expr 2232 types is given instead, this method will try to parse the token list into each one 2233 of them, stopping at the first for which the parsing succeeds. 2234 2235 Args: 2236 expression_types: The expression type(s) to try and parse the token list into. 2237 raw_tokens: The list of tokens. 2238 sql: The original SQL string, used to produce helpful debug messages. 2239 2240 Returns: 2241 The target Expr. 2242 """ 2243 errors = [] 2244 for expression_type in ensure_list(expression_types): 2245 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2246 if not parser: 2247 raise TypeError(f"No parser registered for {expression_type}") 2248 2249 try: 2250 return self._parse(parser, raw_tokens, sql) 2251 except ParseError as e: 2252 e.errors[0]["into_expression"] = expression_type 2253 errors.append(e) 2254 2255 raise ParseError( 2256 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2257 errors=merge_errors(errors), 2258 ) from errors[-1] 2259 2260 def check_errors(self) -> None: 2261 """Logs or raises any found errors, depending on the chosen error level setting.""" 2262 if self.error_level == ErrorLevel.WARN: 2263 for error in self.errors: 2264 logger.error(str(error)) 2265 elif self.error_level == ErrorLevel.RAISE and self.errors: 2266 raise ParseError( 2267 concat_messages(self.errors, self.max_errors), 2268 errors=merge_errors(self.errors), 2269 ) 2270 2271 def expression( 2272 self, 2273 instance: E, 2274 token: Token | None = None, 2275 comments: list[str] | None = None, 2276 ) -> E: 2277 if token: 2278 instance.update_positions(token) 2279 instance.add_comments(comments) if comments else self._add_comments(instance) 2280 if not instance.is_primitive: 2281 instance = self.validate_expression(instance) 2282 return instance 2283 2284 def _parse_batch_statements( 2285 self, 2286 parse_method: t.Callable[[Parser], exp.Expr | None], 2287 sep_first_statement: bool = True, 2288 ) -> list[exp.Expr | None]: 2289 expressions = [] 2290 2291 # Chunkification binds if/while statements with the first statement of the body 2292 if sep_first_statement: 2293 self._match(TokenType.BEGIN) 2294 expressions.append(parse_method(self)) 2295 2296 chunks_length = len(self._chunks) 2297 while self._chunk_index < chunks_length: 2298 self._advance_chunk() 2299 2300 if self._match(TokenType.ELSE, advance=False): 2301 return expressions 2302 2303 if expressions and not self._next and self._match(TokenType.END): 2304 expressions.append(exp.EndStatement()) 2305 continue 2306 2307 expressions.append(parse_method(self)) 2308 2309 if self._index < self._tokens_size: 2310 self.raise_error("Invalid expression / Unexpected token") 2311 2312 self.check_errors() 2313 2314 return expressions 2315 2316 def _parse( 2317 self, 2318 parse_method: t.Callable[[Parser], exp.Expr | None], 2319 raw_tokens: list[Token], 2320 sql: str | None = None, 2321 ) -> list[exp.Expr | None]: 2322 self.reset() 2323 self.sql = sql or "" 2324 2325 total = len(raw_tokens) 2326 chunks: list[list[Token]] = [[]] 2327 2328 for i, token in enumerate(raw_tokens): 2329 if token.token_type == TokenType.SEMICOLON: 2330 if token.comments: 2331 chunks.append([token]) 2332 2333 if i < total - 1: 2334 chunks.append([]) 2335 else: 2336 chunks[-1].append(token) 2337 2338 self._chunks = chunks 2339 2340 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2341 2342 def _warn_unsupported(self) -> None: 2343 if self._tokens_size <= 1: 2344 return 2345 2346 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2347 # interested in emitting a warning for the one being currently processed. 2348 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2349 2350 logger.warning( 2351 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2352 ) 2353 2354 def _parse_command(self) -> exp.Command: 2355 self._warn_unsupported() 2356 comments = self._prev_comments 2357 return self.expression( 2358 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2359 comments=comments, 2360 ) 2361 2362 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2363 start = self._prev 2364 exists = self._parse_exists() if allow_exists else None 2365 2366 self._match(TokenType.ON) 2367 2368 materialized = self._match_text_seq("MATERIALIZED") 2369 kind = self._match_set(self.CREATABLES) and self._prev 2370 if not kind: 2371 return self._parse_as_command(start) 2372 2373 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2374 this = self._parse_user_defined_function(kind=kind.token_type) 2375 elif kind.token_type == TokenType.TABLE: 2376 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2377 elif kind.token_type == TokenType.COLUMN: 2378 this = self._parse_column() 2379 else: 2380 this = self._parse_table_parts(schema=True) 2381 2382 self._match(TokenType.IS) 2383 2384 return self.expression( 2385 exp.Comment( 2386 this=this, 2387 kind=kind.text, 2388 expression=self._parse_string(), 2389 exists=exists, 2390 materialized=materialized, 2391 ) 2392 ) 2393 2394 def _parse_to_table( 2395 self, 2396 ) -> exp.ToTableProperty: 2397 table = self._parse_table_parts(schema=True) 2398 return self.expression(exp.ToTableProperty(this=table)) 2399 2400 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2401 def _parse_ttl(self) -> exp.Expr: 2402 def _parse_ttl_action() -> exp.Expr | None: 2403 this = self._parse_bitwise() 2404 2405 if self._match_text_seq("DELETE"): 2406 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2407 if self._match_text_seq("RECOMPRESS"): 2408 return self.expression( 2409 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2410 ) 2411 if self._match_text_seq("TO", "DISK"): 2412 return self.expression( 2413 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2414 ) 2415 if self._match_text_seq("TO", "VOLUME"): 2416 return self.expression( 2417 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2418 ) 2419 2420 return this 2421 2422 expressions = self._parse_csv(_parse_ttl_action) 2423 where = self._parse_where() 2424 group = self._parse_group() 2425 2426 aggregates = None 2427 if group and self._match(TokenType.SET): 2428 aggregates = self._parse_csv(self._parse_set_item) 2429 2430 return self.expression( 2431 exp.MergeTreeTTL( 2432 expressions=expressions, where=where, group=group, aggregates=aggregates 2433 ) 2434 ) 2435 2436 def _parse_condition(self) -> exp.Expr | None: 2437 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2438 2439 def _parse_block(self) -> exp.Block: 2440 return self.expression( 2441 exp.Block( 2442 expressions=self._parse_batch_statements( 2443 parse_method=lambda self: self._parse_statement() 2444 ) 2445 ) 2446 ) 2447 2448 def _parse_whileblock(self) -> exp.WhileBlock: 2449 return self.expression( 2450 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2451 ) 2452 2453 def _parse_statement(self) -> exp.Expr | None: 2454 if not self._curr: 2455 return None 2456 2457 if self._match_set(self.STATEMENT_PARSERS): 2458 comments = self._prev_comments 2459 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2460 stmt.add_comments(comments, prepend=True) 2461 return stmt 2462 2463 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2464 return self._parse_command() 2465 2466 if self._match_text_seq("WHILE"): 2467 return self._parse_whileblock() 2468 2469 expression = self._parse_expression() 2470 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2471 2472 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2473 expression = self._parse_pipe_syntax_query(expression) 2474 2475 return self._parse_query_modifiers(expression) 2476 2477 def _parse_drop(self, exists: bool = False, kind: str | None = None) -> exp.Drop | exp.Command: 2478 start = self._prev 2479 2480 if not kind: 2481 temporary = self._match(TokenType.TEMPORARY) 2482 materialized = self._match_text_seq("MATERIALIZED") 2483 iceberg = self._match_text_seq("ICEBERG") 2484 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 2485 else: 2486 temporary = False 2487 materialized = False 2488 iceberg = False 2489 2490 if not kind or (iceberg and kind != "TABLE"): 2491 return self._parse_as_command(start) 2492 2493 concurrently = ( 2494 kind == "INDEX" 2495 and self._next.token_type in self.ID_VAR_TOKENS 2496 and self._match_text_seq("CONCURRENTLY") 2497 ) 2498 2499 if_exists = exists or self._parse_exists() 2500 2501 tables: exp.Expr | list[exp.Expr] | None 2502 if kind == "COLUMN": 2503 # A dropped column named after a keyword, like `any` or `charset`, is only a name 2504 name = ( 2505 None 2506 if self._curr.text.upper() in self.NO_PAREN_FUNCTION_PARSERS 2507 else self._parse_column() 2508 ) or self._parse_id_var() 2509 tables = ( 2510 self.expression(exp.Column(this=name)) if isinstance(name, exp.Identifier) else name 2511 ) 2512 elif kind in ("TABLE", "VIEW"): 2513 tables = self._parse_csv(lambda: self._parse_table_parts(schema=True)) 2514 else: 2515 tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2516 2517 cluster: exp.Expr | None = None 2518 if self._match(TokenType.ON): 2519 if kind in ("INDEX", "TRIGGER"): 2520 cluster = self.expression(exp.OnProperty(this=self._parse_table_parts())) 2521 else: 2522 cluster = self._parse_on_property() 2523 2524 if self._match(TokenType.L_PAREN, advance=False): 2525 expressions = self._parse_wrapped_csv(self._parse_types) 2526 else: 2527 expressions = None 2528 2529 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2530 2531 return self.expression( 2532 exp.Drop( 2533 exists=if_exists, 2534 tables=ensure_list(tables), 2535 expressions=expressions, 2536 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2537 temporary=temporary, 2538 materialized=materialized, 2539 cascade=cascade_or_restrict == "CASCADE", 2540 restrict=cascade_or_restrict == "RESTRICT", 2541 constraints=self._match_text_seq("CONSTRAINTS"), 2542 purge=self._match_text_seq("PURGE"), 2543 cluster=cluster, 2544 concurrently=concurrently, 2545 sync=self._match_text_seq("SYNC"), 2546 iceberg=iceberg, 2547 force=self._match_text_seq("FORCE"), 2548 ) 2549 ) 2550 2551 def _parse_exists(self, not_: bool = False) -> bool | None: 2552 if not_: 2553 return self._match_text_seq("IF", "NOT", "EXISTS") 2554 return self._match_text_seq("IF", "EXISTS") 2555 2556 def _parse_create(self) -> exp.Create | exp.Command: 2557 # Note: this can't be None because we've matched a statement parser 2558 start = self._prev 2559 2560 replace = ( 2561 start.token_type == TokenType.REPLACE 2562 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2563 or self._match_pair(TokenType.OR, TokenType.ALTER) 2564 ) 2565 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2566 2567 unique = self._match(TokenType.UNIQUE) 2568 2569 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2570 clustered = True 2571 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2572 "COLUMNSTORE" 2573 ): 2574 clustered = False 2575 else: 2576 clustered = None 2577 2578 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2579 self._advance() 2580 2581 properties = None 2582 create_token = self._match_set(self.CREATABLES) and self._prev 2583 2584 if not create_token: 2585 # exp.Properties.Location.POST_CREATE 2586 properties = self._parse_properties() 2587 create_token = self._match_set(self.CREATABLES) and self._prev 2588 2589 if not properties or not create_token: 2590 return self._parse_as_command(start) 2591 2592 create_token_type = t.cast(Token, create_token).token_type 2593 2594 concurrently = create_token_type == TokenType.INDEX and self._match_text_seq("CONCURRENTLY") 2595 exists = self._parse_exists(not_=True) 2596 this = None 2597 expression: exp.Expr | None = None 2598 indexes = None 2599 no_schema_binding = None 2600 begin = None 2601 clone = None 2602 2603 def extend_props(temp_props: exp.Properties | None) -> None: 2604 nonlocal properties 2605 if properties and temp_props: 2606 properties.expressions.extend(temp_props.expressions) 2607 elif temp_props: 2608 properties = temp_props 2609 2610 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2611 this = self._parse_user_defined_function(kind=create_token_type) 2612 2613 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2614 extend_props(self._parse_properties()) 2615 2616 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2617 2618 if ( 2619 not expression 2620 and create_token_type == TokenType.FUNCTION 2621 and isinstance(this, exp.UserDefinedFunction) 2622 and this.args.get("wrapped") 2623 ): 2624 pre_table_index = self._index 2625 is_table = self._match(TokenType.TABLE) 2626 2627 expression = self._parse_expression() 2628 overload_mode = bool( 2629 expression 2630 and self._curr.token_type == TokenType.COMMA 2631 and self._next.token_type == TokenType.L_PAREN 2632 ) 2633 if not overload_mode: 2634 self._retreat(pre_table_index) 2635 is_table = False 2636 expression = None 2637 else: 2638 is_table = False 2639 overload_mode = False 2640 2641 extend_props(self._parse_function_properties()) 2642 2643 if not expression: 2644 if self._match(TokenType.COMMAND): 2645 expression = self._parse_as_command(self._prev) 2646 else: 2647 begin = self._match(TokenType.BEGIN) 2648 return_ = self._match_text_seq("RETURN") 2649 2650 if self._match(TokenType.STRING, advance=False): 2651 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2652 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2653 expression = self._parse_string() 2654 extend_props(self._parse_properties()) 2655 else: 2656 expression = ( 2657 self._parse_user_defined_function_expression() 2658 if create_token_type == TokenType.FUNCTION 2659 else self._parse_block() 2660 ) 2661 2662 if return_: 2663 expression = self.expression(exp.Return(this=expression)) 2664 2665 if overload_mode and expression: 2666 expression = self._parse_macro_overloads( 2667 t.cast(exp.UserDefinedFunction, this), expression, is_table 2668 ) 2669 elif create_token_type == TokenType.INDEX: 2670 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2671 if not self._match(TokenType.ON): 2672 index = self._parse_id_var() 2673 anonymous = False 2674 else: 2675 index = None 2676 anonymous = True 2677 2678 this = self._parse_index(index=index, anonymous=anonymous) 2679 elif ( 2680 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2681 ) or create_token_type == TokenType.TRIGGER: 2682 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2683 create_token = self._prev 2684 2685 trigger_name = self._parse_id_var() 2686 if not trigger_name: 2687 return self._parse_as_command(start) 2688 2689 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2690 timing = timing_var.this if timing_var else None 2691 if not timing: 2692 return self._parse_as_command(start) 2693 2694 events = self._parse_trigger_events() 2695 if not self._match(TokenType.ON): 2696 self.raise_error("Expected ON in trigger definition") 2697 2698 table = self._parse_table_parts() 2699 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2700 deferrable, initially = self._parse_trigger_deferrable() 2701 referencing = self._parse_trigger_referencing() 2702 for_each = self._parse_trigger_for_each() 2703 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2704 self._parse_disjunction, optional=True 2705 ) 2706 execute = self._parse_trigger_execute() 2707 2708 if execute is None: 2709 return self._parse_as_command(start) 2710 2711 trigger_props = self.expression( 2712 exp.TriggerProperties( 2713 table=table, 2714 timing=timing, 2715 events=events, 2716 execute=execute, 2717 constraint=is_constraint, 2718 referenced_table=referenced_table, 2719 deferrable=deferrable, 2720 initially=initially, 2721 referencing=referencing, 2722 for_each=for_each, 2723 when=when, 2724 ) 2725 ) 2726 2727 this = trigger_name 2728 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2729 elif create_token_type == TokenType.TYPE: 2730 this = self._parse_table_parts(schema=True) 2731 if not this or not self._match(TokenType.ALIAS): 2732 return self._parse_as_command(start) 2733 2734 if self._match(TokenType.ENUM): 2735 expression = exp.DataType( 2736 this=exp.DType.ENUM, 2737 expressions=self._parse_wrapped_csv(self._parse_string), 2738 ) 2739 elif self._match(TokenType.L_PAREN, advance=False): 2740 expression = self._parse_schema() 2741 else: 2742 return self._parse_as_command(start) 2743 elif create_token_type in self.DB_CREATABLES: 2744 table_parts = self._parse_table_parts( 2745 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2746 ) 2747 2748 # exp.Properties.Location.POST_NAME 2749 self._match(TokenType.COMMA) 2750 extend_props(self._parse_properties(before=True)) 2751 2752 this = self._parse_schema(this=table_parts) 2753 2754 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2755 extend_props(self._parse_properties()) 2756 2757 has_alias = self._match(TokenType.ALIAS) 2758 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2759 # exp.Properties.Location.POST_ALIAS 2760 extend_props(self._parse_properties()) 2761 2762 if create_token_type == TokenType.SEQUENCE: 2763 expression = self._parse_types() 2764 props = self._parse_properties() 2765 if props: 2766 sequence_props = exp.SequenceProperties() 2767 options = [] 2768 for prop in props: 2769 if isinstance(prop, exp.SequenceProperties): 2770 for arg, value in prop.args.items(): 2771 if arg == "options": 2772 options.extend(value) 2773 else: 2774 sequence_props.set(arg, value) 2775 prop.pop() 2776 2777 if options: 2778 sequence_props.set("options", options) 2779 2780 props.append("expressions", sequence_props) 2781 extend_props(props) 2782 else: 2783 expression = self._parse_ddl_select() 2784 2785 # Some dialects also support using a table as an alias instead of a SELECT. 2786 # Here we fallback to this as an alternative. 2787 if not expression and has_alias: 2788 expression = self._try_parse(self._parse_table_parts) 2789 2790 if create_token_type == TokenType.TABLE: 2791 # exp.Properties.Location.POST_EXPRESSION 2792 extend_props(self._parse_properties()) 2793 2794 indexes = [] 2795 while True: 2796 index = self._parse_index() 2797 2798 # exp.Properties.Location.POST_INDEX 2799 extend_props(self._parse_properties()) 2800 if not index: 2801 break 2802 else: 2803 self._match(TokenType.COMMA) 2804 indexes.append(index) 2805 elif create_token_type == TokenType.VIEW: 2806 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2807 no_schema_binding = True 2808 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2809 extend_props(self._parse_properties()) 2810 2811 shallow = self._match_text_seq("SHALLOW") 2812 2813 if self._match_texts(self.CLONE_KEYWORDS): 2814 copy = self._prev.text.lower() == "copy" 2815 clone = self.expression( 2816 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2817 ) 2818 2819 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2820 return self._parse_as_command(start) 2821 2822 create_kind_text = create_token.text.upper() 2823 return self.expression( 2824 exp.Create( 2825 this=this, 2826 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2827 replace=replace, 2828 refresh=refresh, 2829 unique=unique, 2830 expression=expression, 2831 exists=exists, 2832 properties=properties, 2833 indexes=indexes, 2834 no_schema_binding=no_schema_binding, 2835 begin=begin, 2836 clone=clone, 2837 concurrently=concurrently, 2838 clustered=clustered, 2839 ) 2840 ) 2841 2842 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2843 seq = exp.SequenceProperties() 2844 2845 options = [] 2846 index = self._index 2847 2848 while self._curr: 2849 self._match(TokenType.COMMA) 2850 if self._match_text_seq("INCREMENT"): 2851 self._match_text_seq("BY") 2852 self._match_text_seq("=") 2853 seq.set("increment", self._parse_term()) 2854 elif self._match_text_seq("MINVALUE"): 2855 seq.set("minvalue", self._parse_term()) 2856 elif self._match_text_seq("MAXVALUE"): 2857 seq.set("maxvalue", self._parse_term()) 2858 elif self._match_text_seq("START"): 2859 self._match_text_seq("WITH") 2860 self._match_text_seq("=") 2861 seq.set("start", self._parse_term()) 2862 elif self._match_text_seq("CACHE"): 2863 # T-SQL allows empty CACHE which is initialized dynamically 2864 seq.set("cache", self._parse_number() or True) 2865 elif self._match_text_seq("OWNED", "BY"): 2866 # "OWNED BY NONE" is the default 2867 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2868 else: 2869 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2870 if opt: 2871 options.append(opt) 2872 else: 2873 break 2874 2875 seq.set("options", options if options else None) 2876 return None if self._index == index else seq 2877 2878 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2879 events = [] 2880 2881 while True: 2882 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2883 2884 if not event_type: 2885 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2886 2887 columns = ( 2888 self._parse_csv(self._parse_column) 2889 if event_type == "UPDATE" and self._match_text_seq("OF") 2890 else None 2891 ) 2892 2893 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2894 2895 if not self._match(TokenType.OR): 2896 break 2897 2898 return events 2899 2900 def _parse_trigger_deferrable( 2901 self, 2902 ) -> tuple[str | None, str | None]: 2903 deferrable_var = self._parse_var_from_options( 2904 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2905 ) 2906 deferrable = deferrable_var.this if deferrable_var else None 2907 2908 initially = None 2909 if deferrable and self._match_text_seq("INITIALLY"): 2910 initially = ( 2911 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2912 ) 2913 2914 return deferrable, initially 2915 2916 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2917 if not self._match_text_seq(keyword): 2918 return None 2919 if not self._match_text_seq("TABLE"): 2920 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2921 self._match_text_seq("AS") 2922 return self._parse_id_var() 2923 2924 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2925 if not self._match_text_seq("REFERENCING"): 2926 return None 2927 2928 old_alias = None 2929 new_alias = None 2930 2931 while True: 2932 if alias := self._parse_trigger_referencing_clause("OLD"): 2933 if old_alias is not None: 2934 self.raise_error("Duplicate OLD clause in REFERENCING") 2935 old_alias = alias 2936 elif alias := self._parse_trigger_referencing_clause("NEW"): 2937 if new_alias is not None: 2938 self.raise_error("Duplicate NEW clause in REFERENCING") 2939 new_alias = alias 2940 else: 2941 break 2942 2943 if old_alias is None and new_alias is None: 2944 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2945 2946 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2947 2948 def _parse_trigger_for_each(self) -> str | None: 2949 if not self._match_text_seq("FOR", "EACH"): 2950 return None 2951 2952 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2953 2954 def _parse_trigger_execute(self) -> exp.Expr | None: 2955 if not self._match(TokenType.EXECUTE): 2956 return None 2957 2958 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2959 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2960 2961 func_call = self._parse_column() 2962 return self.expression(exp.TriggerExecute(this=func_call)) 2963 2964 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2965 # only used for teradata currently 2966 self._match(TokenType.COMMA) 2967 2968 kwargs = { 2969 "no": self._match_text_seq("NO"), 2970 "dual": self._match_text_seq("DUAL"), 2971 "before": self._match_text_seq("BEFORE"), 2972 "default": self._match_text_seq("DEFAULT"), 2973 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2974 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2975 "after": self._match_text_seq("AFTER"), 2976 "minimum": self._match_texts(("MIN", "MINIMUM")), 2977 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2978 } 2979 2980 if self._match_texts(self.PROPERTY_PARSERS): 2981 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2982 try: 2983 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2984 except TypeError: 2985 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2986 2987 if self._match_pair(TokenType.CHAR, TokenType.SET): 2988 return self._parse_character_set(default=bool(kwargs["default"])) 2989 2990 return None 2991 2992 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2993 return self._parse_wrapped_csv(self._parse_property) 2994 2995 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2996 if self._match_texts(self.PROPERTY_PARSERS): 2997 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2998 2999 if self._match_pair(TokenType.CHAR, TokenType.SET): 3000 return self._parse_character_set() 3001 3002 if self._match(TokenType.DEFAULT): 3003 if self._match_texts(self.PROPERTY_PARSERS): 3004 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 3005 3006 if self._match_pair(TokenType.CHAR, TokenType.SET): 3007 return self._parse_character_set(default=True) 3008 3009 if self._match_text_seq("COMPOUND", "SORTKEY"): 3010 return self._parse_sortkey(compound=True) 3011 3012 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 3013 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 3014 3015 if self._match_text_seq("NOT", "DETERMINISTIC"): 3016 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3017 3018 index = self._index 3019 3020 seq_props = self._parse_sequence_properties() 3021 if seq_props: 3022 return seq_props 3023 3024 self._retreat(index) 3025 return self._parse_key_value_property() 3026 3027 def _parse_key_value_property( 3028 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 3029 ) -> exp.Property | None: 3030 index = self._index 3031 key = self._parse_column() 3032 3033 if not self._match(TokenType.EQ): 3034 self._retreat(index) 3035 return None 3036 3037 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 3038 if isinstance(key, exp.Column): 3039 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 3040 3041 value = ( 3042 parse_value() 3043 if parse_value 3044 else self._parse_bitwise() or self._parse_var(any_token=True) 3045 ) 3046 3047 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 3048 if isinstance(value, exp.Column): 3049 value = exp.var(value.name) 3050 3051 return self.expression(exp.Property(this=key, value=value)) 3052 3053 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 3054 if self._match_text_seq("BY"): 3055 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 3056 3057 self._match(TokenType.ALIAS) 3058 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 3059 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 3060 3061 return self.expression( 3062 exp.FileFormatProperty( 3063 this=( 3064 self.expression( 3065 exp.InputOutputFormat( 3066 input_format=input_format, output_format=output_format 3067 ) 3068 ) 3069 if input_format or output_format 3070 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 3071 ), 3072 hive_format=True, 3073 ) 3074 ) 3075 3076 def _parse_unquoted_field(self) -> exp.Expr | None: 3077 field = self._parse_field() 3078 if isinstance(field, exp.Identifier) and not field.quoted: 3079 field = exp.var(field) 3080 3081 return field 3082 3083 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 3084 self._match(TokenType.EQ) 3085 self._match(TokenType.ALIAS) 3086 3087 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 3088 3089 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 3090 properties = [] 3091 while True: 3092 if before: 3093 prop = self._parse_property_before() 3094 else: 3095 prop = self._parse_property() 3096 if not prop: 3097 break 3098 for p in ensure_list(prop): 3099 properties.append(p) 3100 3101 self._match(TokenType.COMMA, expression=properties[-1]) 3102 3103 if properties: 3104 return self.expression(exp.Properties(expressions=properties)) 3105 3106 return None 3107 3108 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3109 return self.expression( 3110 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3111 ) 3112 3113 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3114 return self.expression( 3115 exp.SqlSecurityProperty( 3116 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3117 ) 3118 ) 3119 3120 def _parse_settings_property(self) -> exp.SettingsProperty: 3121 return self.expression( 3122 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3123 ) 3124 3125 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3126 if not self._match_text_seq("ON", "NULL", "INPUT"): 3127 self._retreat(self._index - 1) 3128 return None 3129 3130 return self.expression(exp.CalledOnNullInputProperty()) 3131 3132 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3133 if self._index >= 2: 3134 pre_volatile_token = self._tokens[self._index - 2] 3135 else: 3136 pre_volatile_token = None 3137 3138 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3139 return exp.VolatileProperty() 3140 3141 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3142 3143 def _parse_retention_period(self) -> exp.Var: 3144 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3145 number = self._parse_number() 3146 number_str = f"{number} " if number else "" 3147 unit = self._parse_var(any_token=True) 3148 return exp.var(f"{number_str}{unit}") 3149 3150 def _parse_system_versioning_property( 3151 self, with_: bool = False 3152 ) -> exp.WithSystemVersioningProperty: 3153 self._match(TokenType.EQ) 3154 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3155 3156 if self._match_text_seq("OFF"): 3157 prop.set("on", False) 3158 return prop 3159 3160 self._match(TokenType.ON) 3161 if self._match(TokenType.L_PAREN): 3162 while self._curr and not self._match(TokenType.R_PAREN): 3163 if self._match_text_seq("HISTORY_TABLE", "="): 3164 prop.set("this", self._parse_table_parts()) 3165 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3166 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3167 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3168 prop.set("retention_period", self._parse_retention_period()) 3169 3170 self._match(TokenType.COMMA) 3171 3172 return prop 3173 3174 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3175 self._match(TokenType.EQ) 3176 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3177 prop = self.expression(exp.DataDeletionProperty(on=on)) 3178 3179 if self._match(TokenType.L_PAREN): 3180 while self._curr and not self._match(TokenType.R_PAREN): 3181 if self._match_text_seq("FILTER_COLUMN", "="): 3182 prop.set("filter_column", self._parse_column()) 3183 elif self._match_text_seq("RETENTION_PERIOD", "="): 3184 prop.set("retention_period", self._parse_retention_period()) 3185 3186 self._match(TokenType.COMMA) 3187 3188 return prop 3189 3190 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3191 kind = "HASH" 3192 expressions: list[exp.Expr] | None = None 3193 if self._match_text_seq("BY", "HASH"): 3194 expressions = self._parse_wrapped_csv(self._parse_id_var) 3195 elif self._match_text_seq("BY", "RANDOM"): 3196 kind = "RANDOM" 3197 3198 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3199 buckets: exp.Expr | None = None 3200 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3201 buckets = self._parse_number() 3202 3203 return self.expression( 3204 exp.DistributedByProperty( 3205 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3206 ) 3207 ) 3208 3209 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3210 self._match_text_seq("KEY") 3211 expressions = self._parse_wrapped_id_vars() 3212 return self.expression(expr_type(expressions=expressions)) 3213 3214 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3215 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3216 prop = self._parse_system_versioning_property(with_=True) 3217 self._match_r_paren() 3218 return prop 3219 3220 if self._match(TokenType.L_PAREN, advance=False): 3221 result: list[exp.Expr] = [] 3222 for i in self._parse_wrapped_properties(): 3223 result.extend(i) if isinstance(i, list) else result.append(i) 3224 return result 3225 3226 if self._match_text_seq("JOURNAL"): 3227 return self._parse_withjournaltable() 3228 3229 if self._match_texts(self.VIEW_ATTRIBUTES): 3230 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3231 3232 if self._match_text_seq("DATA"): 3233 return self._parse_withdata(no=False) 3234 elif self._match_text_seq("NO", "DATA"): 3235 return self._parse_withdata(no=True) 3236 3237 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3238 return self._parse_serde_properties(with_=True) 3239 3240 if self._match(TokenType.SCHEMA): 3241 return self.expression( 3242 exp.WithSchemaBindingProperty( 3243 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3244 ) 3245 ) 3246 3247 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3248 return self.expression( 3249 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3250 ) 3251 3252 if not self._next: 3253 return None 3254 3255 return self._parse_withisolatedloading() 3256 3257 def _parse_procedure_option(self) -> exp.Expr | None: 3258 if self._match_text_seq("EXECUTE", "AS"): 3259 return self.expression( 3260 exp.ExecuteAsProperty( 3261 this=self._parse_var_from_options( 3262 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3263 ) 3264 or self._parse_string() 3265 ) 3266 ) 3267 3268 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3269 3270 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3271 def _parse_definer(self) -> exp.DefinerProperty | None: 3272 self._match(TokenType.EQ) 3273 3274 user = self._parse_id_var() 3275 self._match(TokenType.PARAMETER) 3276 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3277 3278 if not user or not host: 3279 return None 3280 3281 return exp.DefinerProperty(this=f"{user}@{host}") 3282 3283 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3284 self._match(TokenType.TABLE) 3285 self._match(TokenType.EQ) 3286 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3287 3288 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3289 return self.expression(exp.LogProperty(no=no)) 3290 3291 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3292 return self.expression(exp.JournalProperty(**kwargs)) 3293 3294 def _parse_checksum(self) -> exp.ChecksumProperty: 3295 self._match(TokenType.EQ) 3296 3297 on = None 3298 if self._match(TokenType.ON): 3299 on = True 3300 elif self._match_text_seq("OFF"): 3301 on = False 3302 3303 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3304 3305 def _parse_cluster(self) -> exp.Cluster: 3306 self._match(TokenType.CLUSTER_BY) 3307 return self.expression( 3308 exp.Cluster( 3309 expressions=self._parse_csv(self._parse_column), 3310 ) 3311 ) 3312 3313 def _parse_cluster_property(self) -> exp.ClusterProperty: 3314 return self.expression( 3315 exp.ClusterProperty( 3316 expressions=self._parse_wrapped_csv(self._parse_column), 3317 ) 3318 ) 3319 3320 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3321 self._match_text_seq("BY") 3322 3323 self._match_l_paren() 3324 expressions = self._parse_csv(self._parse_column) 3325 self._match_r_paren() 3326 3327 if self._match_text_seq("SORTED", "BY"): 3328 self._match_l_paren() 3329 sorted_by = self._parse_csv(self._parse_ordered) 3330 self._match_r_paren() 3331 else: 3332 sorted_by = None 3333 3334 self._match(TokenType.INTO) 3335 buckets = self._parse_number() 3336 self._match_text_seq("BUCKETS") 3337 3338 return self.expression( 3339 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3340 ) 3341 3342 def _parse_copy_property(self) -> exp.Property | None: 3343 if not self._match_text_seq("GRANTS"): 3344 self._retreat(self._index - 1) 3345 return None 3346 3347 return self.expression(exp.CopyGrantsProperty()) 3348 3349 def _parse_freespace(self) -> exp.FreespaceProperty: 3350 self._match(TokenType.EQ) 3351 return self.expression( 3352 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3353 ) 3354 3355 def _parse_mergeblockratio( 3356 self, no: bool = False, default: bool = False 3357 ) -> exp.MergeBlockRatioProperty: 3358 if self._match(TokenType.EQ): 3359 return self.expression( 3360 exp.MergeBlockRatioProperty( 3361 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3362 ) 3363 ) 3364 3365 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3366 3367 def _parse_datablocksize( 3368 self, 3369 default: bool | None = None, 3370 minimum: bool | None = None, 3371 maximum: bool | None = None, 3372 ) -> exp.DataBlocksizeProperty: 3373 self._match(TokenType.EQ) 3374 size = self._parse_number() 3375 3376 units = None 3377 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3378 units = self._prev.text 3379 3380 return self.expression( 3381 exp.DataBlocksizeProperty( 3382 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3383 ) 3384 ) 3385 3386 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3387 self._match(TokenType.EQ) 3388 always = self._match_text_seq("ALWAYS") 3389 manual = self._match_text_seq("MANUAL") 3390 never = self._match_text_seq("NEVER") 3391 default = self._match_text_seq("DEFAULT") 3392 3393 autotemp = None 3394 if self._match_text_seq("AUTOTEMP"): 3395 autotemp = self._parse_schema() 3396 3397 return self.expression( 3398 exp.BlockCompressionProperty( 3399 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3400 ) 3401 ) 3402 3403 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3404 index = self._index 3405 no = self._match_text_seq("NO") 3406 concurrent = self._match_text_seq("CONCURRENT") 3407 3408 if not self._match_text_seq("ISOLATED", "LOADING"): 3409 self._retreat(index) 3410 return None 3411 3412 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3413 return self.expression( 3414 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3415 ) 3416 3417 def _parse_locking(self) -> exp.LockingProperty: 3418 if self._match(TokenType.TABLE): 3419 kind = "TABLE" 3420 elif self._match(TokenType.VIEW): 3421 kind = "VIEW" 3422 elif self._match(TokenType.ROW): 3423 kind = "ROW" 3424 elif self._match_text_seq("DATABASE"): 3425 kind = "DATABASE" 3426 else: 3427 kind = None 3428 3429 if kind in ("DATABASE", "TABLE", "VIEW"): 3430 this = self._parse_table_parts() 3431 else: 3432 this = None 3433 3434 if self._match(TokenType.FOR): 3435 for_or_in = "FOR" 3436 elif self._match(TokenType.IN): 3437 for_or_in = "IN" 3438 else: 3439 for_or_in = None 3440 3441 if self._match_text_seq("ACCESS"): 3442 lock_type = "ACCESS" 3443 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3444 lock_type = "EXCLUSIVE" 3445 elif self._match_text_seq("SHARE"): 3446 lock_type = "SHARE" 3447 elif self._match_text_seq("READ"): 3448 lock_type = "READ" 3449 elif self._match_text_seq("WRITE"): 3450 lock_type = "WRITE" 3451 elif self._match_text_seq("CHECKSUM"): 3452 lock_type = "CHECKSUM" 3453 else: 3454 lock_type = None 3455 3456 override = self._match_text_seq("OVERRIDE") 3457 3458 return self.expression( 3459 exp.LockingProperty( 3460 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3461 ) 3462 ) 3463 3464 def _parse_partition_by(self) -> list[exp.Expr]: 3465 if not self._match(TokenType.PARTITION_BY): 3466 return [] 3467 3468 if self._next.token_type in self.ID_VAR_TOKENS and self._match_text_seq("EXCLUDING"): 3469 return [ 3470 self.expression( 3471 exp.PartitionByExcluding(expressions=self._parse_csv(self._parse_disjunction)) 3472 ) 3473 ] 3474 3475 return self._parse_csv(self._parse_disjunction) 3476 3477 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3478 def _parse_partition_bound_expr() -> exp.Expr | None: 3479 if self._match_text_seq("MINVALUE"): 3480 return exp.var("MINVALUE") 3481 if self._match_text_seq("MAXVALUE"): 3482 return exp.var("MAXVALUE") 3483 return self._parse_bitwise() 3484 3485 this: exp.Expr | list[exp.Expr] | None = None 3486 expression = None 3487 from_expressions = None 3488 to_expressions = None 3489 3490 if self._match(TokenType.IN): 3491 this = self._parse_wrapped_csv(self._parse_bitwise) 3492 elif self._match(TokenType.FROM): 3493 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3494 self._match_text_seq("TO") 3495 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3496 elif self._match_text_seq("WITH", "(", "MODULUS"): 3497 this = self._parse_number() 3498 self._match_text_seq(",", "REMAINDER") 3499 expression = self._parse_number() 3500 self._match_r_paren() 3501 else: 3502 self.raise_error("Failed to parse partition bound spec.") 3503 3504 return self.expression( 3505 exp.PartitionBoundSpec( 3506 this=this, 3507 expression=expression, 3508 from_expressions=from_expressions, 3509 to_expressions=to_expressions, 3510 ) 3511 ) 3512 3513 # https://www.postgresql.org/docs/current/sql-createtable.html 3514 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3515 if not self._match_text_seq("OF"): 3516 self._retreat(self._index - 1) 3517 return None 3518 3519 this = self._parse_table(schema=True) 3520 3521 if self._match(TokenType.DEFAULT): 3522 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3523 elif self._match_text_seq("FOR", "VALUES"): 3524 expression = self._parse_partition_bound_spec() 3525 else: 3526 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3527 3528 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3529 3530 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3531 self._match(TokenType.EQ) 3532 return self.expression( 3533 exp.PartitionedByProperty( 3534 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3535 ) 3536 ) 3537 3538 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3539 if self._match_text_seq("AND", "STATISTICS"): 3540 statistics = True 3541 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3542 statistics = False 3543 else: 3544 statistics = None 3545 3546 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3547 3548 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3549 if self._match_text_seq("SQL"): 3550 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3551 return None 3552 3553 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3554 if self._match_text_seq("SQL", "DATA"): 3555 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3556 return None 3557 3558 def _parse_no_property(self) -> exp.Expr | None: 3559 if self._match_text_seq("PRIMARY", "INDEX"): 3560 return exp.NoPrimaryIndexProperty() 3561 if self._match_text_seq("SQL"): 3562 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3563 return None 3564 3565 def _parse_on_property(self) -> exp.Expr | None: 3566 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3567 return exp.OnCommitProperty() 3568 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3569 return exp.OnCommitProperty(delete=True) 3570 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3571 3572 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3573 if self._match_text_seq("SQL", "DATA"): 3574 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3575 return None 3576 3577 def _parse_distkey(self) -> exp.DistKeyProperty: 3578 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3579 3580 def _parse_create_like(self) -> exp.LikeProperty | None: 3581 table = self._parse_table(schema=True) 3582 3583 options = [] 3584 while self._match_texts(("INCLUDING", "EXCLUDING")): 3585 this = self._prev.text.upper() 3586 3587 id_var = self._parse_id_var() 3588 if not id_var: 3589 return None 3590 3591 options.append( 3592 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3593 ) 3594 3595 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3596 3597 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3598 return self.expression( 3599 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3600 ) 3601 3602 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3603 self._match(TokenType.EQ) 3604 return self.expression( 3605 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3606 ) 3607 3608 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3609 self._match_text_seq("WITH", "CONNECTION") 3610 return self.expression( 3611 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3612 ) 3613 3614 def _parse_returns(self) -> exp.ReturnsProperty: 3615 value: exp.Expr | None 3616 null = None 3617 is_table = self._match(TokenType.TABLE) 3618 3619 if is_table: 3620 if self._match(TokenType.LT): 3621 value = self.expression( 3622 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3623 ) 3624 if not self._match(TokenType.GT): 3625 self.raise_error("Expecting >") 3626 else: 3627 value = self._parse_schema(exp.var("TABLE")) 3628 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3629 null = True 3630 value = None 3631 else: 3632 value = self._parse_types() 3633 3634 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3635 3636 def _parse_describe(self) -> exp.Describe: 3637 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3638 style: str | None = ( 3639 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3640 ) 3641 if self._match(TokenType.DOT): 3642 style = None 3643 self._retreat(self._index - 2) 3644 3645 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3646 3647 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3648 this = self._parse_statement() 3649 else: 3650 this = self._parse_table(schema=True) 3651 3652 properties = self._parse_properties() 3653 expressions = properties.expressions if properties else None 3654 partition = self._parse_partition() 3655 return self.expression( 3656 exp.Describe( 3657 this=this, 3658 style=style, 3659 kind=kind, 3660 expressions=expressions, 3661 partition=partition, 3662 format=format, 3663 as_json=self._match_text_seq("AS", "JSON"), 3664 ) 3665 ) 3666 3667 def _parse_multitable_inserts( 3668 self, comments: list[str] | None, kind: str | None = None 3669 ) -> exp.MultitableInserts: 3670 kind = kind or self._prev.text.upper() 3671 expressions = [] 3672 3673 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3674 if self._match(TokenType.WHEN): 3675 expression = self._parse_disjunction() 3676 self._match(TokenType.THEN) 3677 else: 3678 expression = None 3679 3680 else_ = self._match(TokenType.ELSE) 3681 3682 if not self._match(TokenType.INTO): 3683 return None 3684 3685 return self.expression( 3686 exp.ConditionalInsert( 3687 this=self.expression( 3688 exp.Insert( 3689 this=self._parse_table(schema=True), 3690 expression=self._parse_derived_table_values(), 3691 ) 3692 ), 3693 expression=expression, 3694 else_=else_, 3695 ) 3696 ) 3697 3698 expression = parse_conditional_insert() 3699 while expression is not None: 3700 expressions.append(expression) 3701 expression = parse_conditional_insert() 3702 3703 return self.expression( 3704 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3705 comments=comments, 3706 ) 3707 3708 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3709 comments: list[str] = [] 3710 hint = self._parse_hint() 3711 overwrite = self._match(TokenType.OVERWRITE) 3712 ignore = self._match(TokenType.IGNORE) 3713 local = self._match_text_seq("LOCAL") 3714 alternative = None 3715 is_function = None 3716 3717 if self._match_text_seq("DIRECTORY"): 3718 this: exp.Expr | None = self.expression( 3719 exp.Directory( 3720 this=self._parse_var_or_string(), 3721 local=local, 3722 row_format=self._parse_row_format(match_row=True), 3723 ) 3724 ) 3725 else: 3726 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3727 comments += ensure_list(self._prev_comments) 3728 return self._parse_multitable_inserts(comments) 3729 3730 # Oracle's conditional form defaults to ALL when the keyword is omitted 3731 if self._match(TokenType.WHEN, advance=False): 3732 return self._parse_multitable_inserts(comments, kind="ALL") 3733 3734 if self._match(TokenType.OR): 3735 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3736 3737 self._match(TokenType.INTO) 3738 comments += ensure_list(self._prev_comments) 3739 self._match(TokenType.TABLE) 3740 is_function = self._match(TokenType.FUNCTION) 3741 3742 this = self._parse_function() if is_function else self._parse_insert_table() 3743 3744 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3745 set_values = None 3746 if self._match(TokenType.SET): 3747 columns = [] 3748 values = [] 3749 3750 def _parse_set_assignment() -> exp.Expr | None: 3751 target = self._parse_column() 3752 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3753 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3754 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3755 else: 3756 value = self._parse_disjunction() 3757 3758 if value: 3759 columns.append(target.this) 3760 values.append(value) 3761 return value 3762 3763 self.raise_error("Expected column assignment in INSERT ... SET") 3764 return None 3765 3766 self._parse_csv(_parse_set_assignment) 3767 3768 this = self.expression(exp.Schema(this=this, expressions=columns)) 3769 set_values = self.expression( 3770 exp.Values( 3771 expressions=[exp.Tuple(expressions=values)], 3772 alias=self._parse_table_alias(), 3773 ) 3774 ) 3775 3776 returning = self._parse_returning() # TSQL allows RETURNING before source 3777 3778 stored = self._match_text_seq("STORED") and self._parse_stored() 3779 by_name = self._match_text_seq("BY", "NAME") 3780 exists = self._parse_exists() 3781 replace_where = None 3782 replace_using = None 3783 3784 if self._match(TokenType.REPLACE): 3785 if self._match(TokenType.WHERE): 3786 replace_where = self._parse_disjunction() 3787 elif self._match(TokenType.USING): 3788 replace_using = self._parse_using_identifiers() 3789 3790 return self.expression( 3791 exp.Insert( 3792 hint=hint, 3793 is_function=is_function, 3794 this=this, 3795 stored=stored, 3796 by_name=by_name, 3797 exists=exists, 3798 where=replace_where, 3799 using=replace_using, 3800 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3801 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3802 default=self._match_text_seq("DEFAULT", "VALUES"), 3803 expression=set_values 3804 or self._parse_derived_table_values(allow_value_synonym=True) 3805 or self._parse_ddl_select(), 3806 conflict=self._parse_on_conflict(), 3807 returning=returning or self._parse_returning(), 3808 overwrite=overwrite, 3809 alternative=alternative, 3810 ignore=ignore, 3811 source=self._match(TokenType.TABLE) and self._parse_table(), 3812 ), 3813 comments=comments, 3814 ) 3815 3816 def _parse_insert_table(self) -> exp.Expr | None: 3817 this = self._parse_table(schema=True, parse_partition=True) 3818 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS): 3819 # The column list after the alias belongs to the INSERT: INSERT INTO t AS s (a, b) 3820 alias = self._parse_id_var() 3821 if alias: 3822 this.set( 3823 "alias", 3824 self.expression(exp.TableAlias(this=alias), comments=alias.pop_comments()), 3825 ) 3826 return self._parse_schema(this=this) 3827 return this 3828 3829 def _parse_kill(self) -> exp.Kill: 3830 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3831 3832 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3833 3834 def _parse_on_conflict(self) -> exp.OnConflict | None: 3835 conflict = self._match_text_seq("ON", "CONFLICT") 3836 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3837 3838 if not conflict and not duplicate: 3839 return None 3840 3841 conflict_keys = None 3842 constraint = None 3843 3844 if conflict: 3845 if self._match_text_seq("ON", "CONSTRAINT"): 3846 constraint = self._parse_id_var() 3847 elif self._match(TokenType.L_PAREN): 3848 conflict_keys = self._parse_csv(self._parse_indexed_column) 3849 self._match_r_paren() 3850 3851 index_predicate = self._parse_where() 3852 3853 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3854 if self._prev.token_type == TokenType.UPDATE: 3855 self._match(TokenType.SET) 3856 expressions = self._parse_csv(self._parse_update_assignment) 3857 else: 3858 expressions = None 3859 3860 return self.expression( 3861 exp.OnConflict( 3862 duplicate=duplicate, 3863 expressions=expressions, 3864 action=action, 3865 conflict_keys=conflict_keys, 3866 index_predicate=index_predicate, 3867 constraint=constraint, 3868 where=self._parse_where(), 3869 ) 3870 ) 3871 3872 def _parse_returning(self) -> exp.Returning | None: 3873 if not self._match(TokenType.RETURNING): 3874 return None 3875 return self.expression( 3876 exp.Returning( 3877 expressions=self._parse_csv(self._parse_expression), 3878 into=self._match(TokenType.INTO) and self._parse_table_part(), 3879 ) 3880 ) 3881 3882 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3883 if not self._match(TokenType.FORMAT): 3884 return None 3885 return self._parse_row_format() 3886 3887 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3888 index = self._index 3889 with_ = with_ or self._match_text_seq("WITH") 3890 3891 if not self._match(TokenType.SERDE_PROPERTIES): 3892 self._retreat(index) 3893 return None 3894 return self.expression( 3895 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3896 ) 3897 3898 def _parse_row_format( 3899 self, match_row: bool = False 3900 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3901 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3902 return None 3903 3904 if self._match_text_seq("SERDE"): 3905 this = self._parse_string() 3906 3907 serde_properties = self._parse_serde_properties() 3908 3909 return self.expression( 3910 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3911 ) 3912 3913 self._match_text_seq("DELIMITED") 3914 3915 kwargs = {} 3916 3917 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3918 kwargs["fields"] = self._parse_string() 3919 if self._match_text_seq("ESCAPED", "BY"): 3920 kwargs["escaped"] = self._parse_string() 3921 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3922 kwargs["collection_items"] = self._parse_string() 3923 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3924 kwargs["map_keys"] = self._parse_string() 3925 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3926 kwargs["lines"] = self._parse_string() 3927 if self._match_text_seq("NULL", "DEFINED", "AS"): 3928 kwargs["null"] = self._parse_string() 3929 3930 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3931 3932 def _parse_load(self) -> exp.LoadData | exp.Command: 3933 if self._match_text_seq("DATA"): 3934 local = self._match_text_seq("LOCAL") 3935 self._match_text_seq("INPATH") 3936 inpath = self._parse_string() 3937 overwrite = self._match(TokenType.OVERWRITE) 3938 temp: bool | None = None 3939 if self._match(TokenType.INTO): 3940 temp = self._match(TokenType.TEMPORARY) 3941 self._match(TokenType.TABLE) 3942 3943 return self.expression( 3944 exp.LoadData( 3945 this=self._parse_table(schema=True), 3946 local=local, 3947 overwrite=overwrite, 3948 temp=temp, 3949 inpath=inpath, 3950 files=self._match_text_seq("FROM", "FILES") 3951 and exp.Properties(expressions=self._parse_wrapped_properties()), 3952 partition=self._parse_partition(), 3953 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3954 serde=self._match_text_seq("SERDE") and self._parse_string(), 3955 ) 3956 ) 3957 return self._parse_as_command(self._prev) 3958 3959 def _parse_delete(self) -> exp.Delete: 3960 hint = self._parse_hint() 3961 3962 # This handles MySQL's "Multiple-Table Syntax" 3963 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3964 tables = None 3965 if not self._match(TokenType.FROM, advance=False): 3966 tables = self._parse_csv(self._parse_table) or None 3967 3968 returning = self._parse_returning() 3969 3970 return self.expression( 3971 exp.Delete( 3972 hint=hint, 3973 tables=tables, 3974 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3975 using=self._match(TokenType.USING) 3976 and self._parse_csv(lambda: self._parse_table(joins=True)), 3977 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3978 where=self._parse_where(), 3979 returning=returning or self._parse_returning(), 3980 order=self._parse_order(), 3981 limit=self._parse_limit(), 3982 ) 3983 ) 3984 3985 def _parse_update_assignment(self) -> exp.Expr | None: 3986 this = self._parse_comparison() 3987 if self._match(TokenType.EQ): 3988 comments = self._prev_comments 3989 this = self.expression( 3990 exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments 3991 ) 3992 return this 3993 3994 def _parse_update(self) -> exp.Update: 3995 hint = self._parse_hint() 3996 kwargs: dict[str, object] = { 3997 "hint": hint, 3998 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3999 } 4000 while self._curr: 4001 if self._match(TokenType.SET): 4002 kwargs["expressions"] = self._parse_csv(self._parse_update_assignment) 4003 elif self._match(TokenType.RETURNING, advance=False): 4004 kwargs["returning"] = self._parse_returning() 4005 elif self._match(TokenType.FROM, advance=False): 4006 from_ = self._parse_from(joins=True) 4007 table = from_.this if from_ else None 4008 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 4009 table.set("joins", list(self._parse_joins()) or None) 4010 4011 kwargs["from_"] = from_ 4012 elif self._match(TokenType.WHERE, advance=False): 4013 kwargs["where"] = self._parse_where() 4014 elif self._match(TokenType.ORDER_BY, advance=False): 4015 kwargs["order"] = self._parse_order() 4016 elif self._match(TokenType.LIMIT, advance=False): 4017 kwargs["limit"] = self._parse_limit() 4018 else: 4019 break 4020 4021 return self.expression(exp.Update(**kwargs)) 4022 4023 def _parse_use(self) -> exp.Use: 4024 return self.expression( 4025 exp.Use( 4026 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 4027 this=self._parse_table(schema=False), 4028 ) 4029 ) 4030 4031 def _parse_uncache(self) -> exp.Uncache: 4032 if not self._match(TokenType.TABLE): 4033 self.raise_error("Expecting TABLE after UNCACHE") 4034 4035 return self.expression( 4036 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 4037 ) 4038 4039 def _parse_cache(self) -> exp.Cache: 4040 lazy = self._match_text_seq("LAZY") 4041 self._match(TokenType.TABLE) 4042 table = self._parse_table(schema=True) 4043 4044 options = [] 4045 if self._match_text_seq("OPTIONS"): 4046 self._match_l_paren() 4047 k = self._parse_string() 4048 self._match(TokenType.EQ) 4049 v = self._parse_string() 4050 options = [k, v] 4051 self._match_r_paren() 4052 4053 self._match(TokenType.ALIAS) 4054 return self.expression( 4055 exp.Cache( 4056 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 4057 ) 4058 ) 4059 4060 def _parse_partition(self) -> exp.Partition | None: 4061 if not self._match_texts(self.PARTITION_KEYWORDS): 4062 return None 4063 4064 return self.expression( 4065 exp.Partition( 4066 subpartition=self._prev.text.upper() == "SUBPARTITION", 4067 expressions=self._parse_wrapped_csv(self._parse_disjunction), 4068 ) 4069 ) 4070 4071 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 4072 def _parse_value_expression() -> exp.Expr | None: 4073 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 4074 return exp.var(self._prev.text.upper()) 4075 return self._parse_expression() 4076 4077 if self._match(TokenType.L_PAREN): 4078 expressions = self._parse_csv(_parse_value_expression) 4079 self._match_r_paren() 4080 return self.expression(exp.Tuple(expressions=expressions)) 4081 4082 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 4083 expression = self._parse_expression() 4084 if expression: 4085 return self.expression(exp.Tuple(expressions=[expression])) 4086 return None 4087 4088 def _parse_projections( 4089 self, 4090 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 4091 return self._parse_expressions(), None 4092 4093 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 4094 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 4095 this: exp.Expr | None = self._parse_simplified_pivot( 4096 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 4097 ) 4098 else: 4099 # Support parentheses for duckdb FROM-first syntax 4100 this = ( 4101 self._parse_table(consume_pipe=True) 4102 if table and not self._match(TokenType.FROM, advance=False) 4103 else self._parse_select(nested=True, parse_set_operation=False) 4104 ) 4105 4106 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 4107 # in case a modifier (e.g. join) is following 4108 if table and isinstance(this, exp.Values) and this.alias: 4109 alias = this.args["alias"].pop() 4110 this = exp.Table(this=this, alias=alias) 4111 4112 this = self._parse_query_modifiers(self._parse_set_operations(this)) 4113 4114 return this 4115 4116 def _parse_select( 4117 self, 4118 nested: bool = False, 4119 table: bool = False, 4120 parse_subquery_alias: bool = True, 4121 parse_set_operation: bool = True, 4122 consume_pipe: bool = True, 4123 ) -> exp.Expr | None: 4124 query = self._parse_select_query( 4125 nested=nested, 4126 table=table, 4127 parse_subquery_alias=parse_subquery_alias, 4128 parse_set_operation=parse_set_operation, 4129 ) 4130 4131 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4132 if isinstance(query, exp.Query): 4133 query = self._parse_pipe_syntax_query(query) 4134 query = query.subquery(copy=False) if query and table else query 4135 4136 return query 4137 4138 def _parse_select_query( 4139 self, 4140 nested: bool = False, 4141 table: bool = False, 4142 parse_subquery_alias: bool = True, 4143 parse_set_operation: bool = True, 4144 ) -> exp.Expr | None: 4145 cte = self._parse_with() 4146 4147 if cte: 4148 this = self._parse_statement() 4149 4150 if not this: 4151 self.raise_error("Failed to parse any statement following CTE") 4152 return cte 4153 4154 while isinstance(this, exp.Subquery) and this.is_wrapper: 4155 this = this.this 4156 4157 assert this is not None 4158 if "with_" in this.arg_types: 4159 if inner_cte := this.args.get("with_"): 4160 cte.set("expressions", cte.expressions + inner_cte.expressions) 4161 if inner_cte.args.get("recursive"): 4162 cte.set("recursive", True) 4163 this.set("with_", cte) 4164 else: 4165 self.raise_error(f"{this.key} does not support CTE") 4166 this = cte 4167 4168 return this 4169 4170 # duckdb supports leading with FROM x 4171 from_ = ( 4172 self._parse_from(joins=True, consume_pipe=True) 4173 if self._match(TokenType.FROM, advance=False) 4174 else None 4175 ) 4176 laterals = list(iter(self._parse_lateral, None)) if from_ else None 4177 4178 # Hive supports multi-table inserts: FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 4179 if from_ and self._match(TokenType.INSERT, advance=False): 4180 source = from_.this 4181 source.set("laterals", laterals or None) 4182 4183 inserts = [] 4184 while self._match(TokenType.INSERT, advance=False): 4185 inserts.append(self._parse_statement()) 4186 4187 return self.expression( 4188 exp.MultitableInserts(expressions=inserts, source=source), 4189 comments=from_.comments, 4190 ) 4191 4192 if self._match(TokenType.SELECT): 4193 comments = self._prev_comments 4194 4195 hint = self._parse_hint() 4196 4197 if self._next and not self._next.token_type == TokenType.DOT: 4198 all_ = self._match(TokenType.ALL) 4199 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4200 else: 4201 all_, matched_distinct = None, False 4202 4203 kind = ( 4204 self._prev.text.upper() 4205 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4206 else None 4207 ) 4208 4209 distinct: exp.Expr | None = ( 4210 self.expression( 4211 exp.Distinct( 4212 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4213 ) 4214 ) 4215 if matched_distinct 4216 else None 4217 ) 4218 4219 operation_modifiers = [] 4220 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4221 operation_modifiers.append(exp.var(self._prev.text.upper())) 4222 4223 limit = self._parse_limit(top=True) 4224 4225 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4226 if limit and not matched_distinct and not all_: 4227 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4228 if matched_distinct: 4229 distinct = self.expression( 4230 exp.Distinct( 4231 on=self._parse_value(values=False) 4232 if self._match(TokenType.ON) 4233 else None 4234 ) 4235 ) 4236 else: 4237 all_ = self._match(TokenType.ALL) 4238 4239 if all_ and distinct: 4240 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4241 4242 projections, exclude = self._parse_projections() 4243 4244 this = self.expression( 4245 exp.Select( 4246 kind=kind, 4247 hint=hint, 4248 distinct=distinct, 4249 expressions=projections, 4250 limit=limit, 4251 exclude=exclude, 4252 operation_modifiers=operation_modifiers or None, 4253 ) 4254 ) 4255 this.comments = comments 4256 4257 into = self._parse_into() 4258 if into: 4259 this.set("into", into) 4260 4261 if not from_: 4262 from_ = self._parse_from() 4263 4264 if from_: 4265 this.set("from_", from_) 4266 4267 this = self._parse_query_modifiers(this) 4268 elif (table or nested) and self._match(TokenType.L_PAREN): 4269 comments = self._prev_comments 4270 this = self._parse_wrapped_select(table=table) 4271 4272 if this: 4273 this.add_comments(comments, prepend=True) 4274 4275 # We return early here so that the UNION isn't attached to the subquery by the 4276 # following call to _parse_set_operations, but instead becomes the parent node 4277 self._match_r_paren() 4278 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4279 elif self._match(TokenType.VALUES, advance=False): 4280 this = self._parse_derived_table_values() 4281 elif from_: 4282 this = exp.select("*").from_(from_.this, copy=False) 4283 this = self._parse_query_modifiers(this) 4284 elif self._match(TokenType.SUMMARIZE): 4285 table = self._match(TokenType.TABLE) 4286 this = self._parse_select() or self._parse_string() or self._parse_table() 4287 return self.expression(exp.Summarize(this=this, table=table)) 4288 elif self._match(TokenType.DESCRIBE): 4289 this = self._parse_describe() 4290 else: 4291 this = None 4292 4293 if laterals and isinstance(this, exp.Select): 4294 this.set("laterals", [*laterals, *(this.args.get("laterals") or [])]) 4295 4296 return self._parse_set_operations(this) if parse_set_operation else this 4297 4298 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4299 self._match_text_seq("SEARCH") 4300 4301 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4302 4303 if not kind: 4304 return None 4305 4306 self._match_text_seq("FIRST", "BY") 4307 4308 return self.expression( 4309 exp.RecursiveWithSearch( 4310 kind=kind, 4311 this=self._parse_id_var(), 4312 expression=self._match_text_seq("SET") and self._parse_id_var(), 4313 to=self._match_text_seq("TO") and self._parse_bitwise(), 4314 default=self._match(TokenType.DEFAULT) and self._parse_bitwise(), 4315 using=self._match_text_seq("USING") and self._parse_id_var(), 4316 ) 4317 ) 4318 4319 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4320 if not skip_with_token and not self._match(TokenType.WITH): 4321 return None 4322 4323 comments = self._prev_comments 4324 recursive = self._match(TokenType.RECURSIVE) 4325 4326 last_comments = None 4327 expressions = [] 4328 udfs = [] 4329 while True: 4330 cte = self._parse_cte() 4331 if cte: 4332 if isinstance(cte, exp.FunctionSpecification): 4333 udfs.append(cte) 4334 else: 4335 expressions.append(cte) 4336 4337 if last_comments: 4338 cte.add_comments(last_comments) 4339 4340 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4341 break 4342 else: 4343 self._match(TokenType.WITH) 4344 recursive = self._match(TokenType.RECURSIVE) or recursive 4345 4346 last_comments = self._prev_comments 4347 4348 search = self._parse_recursive_with_search() 4349 4350 # A SEARCH clause can be followed by a CYCLE clause 4351 cycle = ( 4352 self._parse_recursive_with_search() 4353 if search 4354 and search.args.get("kind") != "CYCLE" 4355 and self._match_text_seq("CYCLE", advance=False) 4356 else None 4357 ) 4358 4359 return self.expression( 4360 exp.With( 4361 expressions=expressions, 4362 recursive=recursive or None, 4363 search=search, 4364 cycle=cycle, 4365 udfs=udfs or None, 4366 ), 4367 comments=comments, 4368 ) 4369 4370 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4371 index = self._index 4372 4373 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4374 if not alias or not alias.this: 4375 self.raise_error("Expected CTE to have alias") 4376 4377 key_expressions = ( 4378 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4379 ) 4380 4381 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4382 self._retreat(index) 4383 return None 4384 4385 comments = self._prev_comments 4386 4387 if self._match_text_seq("NOT", "MATERIALIZED"): 4388 materialized = False 4389 elif self._match_text_seq("MATERIALIZED"): 4390 materialized = True 4391 else: 4392 materialized = None 4393 4394 cte = self.expression( 4395 exp.CTE( 4396 this=self._parse_wrapped(self._parse_statement), 4397 alias=alias, 4398 materialized=materialized, 4399 key_expressions=key_expressions, 4400 ), 4401 comments=comments, 4402 ) 4403 4404 values = cte.this 4405 if isinstance(values, exp.Values): 4406 cte.set("this", self._values_to_select(values)) 4407 4408 return cte 4409 4410 def _values_to_select(self, values: exp.Values) -> exp.Select: 4411 select = exp.select("*") 4412 for key in exp.QUERY_MODIFIERS: 4413 if modifier := values.args.get(key): 4414 values.set(key, None) 4415 select.set(key, modifier) 4416 return select.from_(values if values.alias else exp.alias_(values, "_values", table=True)) 4417 4418 def _parse_table_alias( 4419 self, alias_tokens: t.Collection[TokenType] | None = None 4420 ) -> exp.TableAlias | None: 4421 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4422 # so this section tries to parse the clause version and if it fails, it treats the token 4423 # as an identifier (alias) 4424 if self._can_parse_limit_or_offset(): 4425 return None 4426 4427 # START is never treated as an implicit alias when followed by WITH, since that 4428 # would swallow the beginning of a START WITH ... CONNECT BY clause 4429 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4430 return None 4431 4432 any_token = self._match(TokenType.ALIAS) 4433 alias = ( 4434 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4435 or self._parse_string_as_identifier() 4436 ) 4437 4438 index = self._index 4439 if self._match(TokenType.L_PAREN): 4440 columns = self._parse_csv(self._parse_function_parameter) 4441 self._match_r_paren() if columns else self._retreat(index) 4442 else: 4443 columns = None 4444 4445 if not alias and not columns: 4446 return None 4447 4448 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4449 4450 # We bubble up comments from the Identifier to the TableAlias 4451 if isinstance(alias, exp.Identifier): 4452 table_alias.add_comments(alias.pop_comments()) 4453 4454 return table_alias 4455 4456 def _parse_subquery( 4457 self, this: exp.Expr | None, parse_alias: bool = True 4458 ) -> exp.Subquery | None: 4459 if not this: 4460 return None 4461 4462 return self.expression( 4463 exp.Subquery( 4464 this=this, 4465 pivots=self._parse_pivots(), 4466 alias=self._parse_table_alias() if parse_alias else None, 4467 sample=self._parse_table_sample(), 4468 ) 4469 ) 4470 4471 def _implicit_unnests_to_explicit(self, this: E) -> E: 4472 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4473 4474 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4475 for i, join in enumerate(this.args.get("joins") or []): 4476 table = join.this 4477 normalized_table = table.copy() 4478 normalized_table.meta["maybe_column"] = True 4479 normalized_table = _norm(normalized_table, dialect=self.dialect) 4480 4481 if isinstance(table, exp.Table) and not join.args.get("on"): 4482 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4483 table_as_column = table.to_column() 4484 unnest = exp.Unnest(expressions=[table_as_column]) 4485 4486 # Table.to_column creates a parent Alias node that we want to convert to 4487 # a TableAlias and attach to the Unnest, so it matches the parser's output 4488 if isinstance(table.args.get("alias"), exp.TableAlias): 4489 table_as_column.replace(table_as_column.this) 4490 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4491 4492 table.replace(unnest) 4493 4494 refs.add(normalized_table.alias_or_name) 4495 4496 return this 4497 4498 @t.overload 4499 def _parse_query_modifiers(self, this: E) -> E: ... 4500 4501 @t.overload 4502 def _parse_query_modifiers(self, this: None) -> None: ... 4503 4504 def _parse_query_modifiers(self, this): 4505 if isinstance(this, self.MODIFIABLES): 4506 for join in self._parse_joins(): 4507 this.append("joins", join) 4508 for lateral in iter(self._parse_lateral, None): 4509 this.append("laterals", lateral) 4510 4511 while True: 4512 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4513 modifier_token = self._curr 4514 4515 # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result 4516 # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union 4517 if ( 4518 modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH) 4519 and (limit := this.args.get("limit")) 4520 and limit.meta.get("top") 4521 ): 4522 break 4523 4524 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4525 key, expression = parser(self) 4526 4527 if expression: 4528 if this.args.get(key): 4529 self.raise_error( 4530 f"Found multiple '{modifier_token.text.upper()}' clauses", 4531 token=modifier_token, 4532 ) 4533 4534 this.set(key, expression) 4535 if key == "limit": 4536 offset = expression.args.get("offset") 4537 expression.set("offset", None) 4538 4539 if offset: 4540 if this.args.get("offset"): 4541 self.raise_error( 4542 "Found multiple 'OFFSET' clauses", token=modifier_token 4543 ) 4544 4545 offset = exp.Offset(expression=offset) 4546 this.set("offset", offset) 4547 4548 limit_by_expressions = expression.expressions 4549 expression.set("expressions", None) 4550 offset.set("expressions", limit_by_expressions) 4551 continue 4552 4553 if self._curr.text.upper() == "START": 4554 modifier_token = self._curr 4555 connect = self._parse_connect() 4556 if connect: 4557 if this.args.get("connect"): 4558 self.raise_error( 4559 "Found multiple 'START WITH' clauses", token=modifier_token 4560 ) 4561 4562 this.set("connect", connect) 4563 continue 4564 break 4565 4566 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4567 this = self._implicit_unnests_to_explicit(this) 4568 4569 return this 4570 4571 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4572 start = self._curr 4573 while self._curr: 4574 self._advance() 4575 4576 end = self._tokens[self._index - 1] 4577 return exp.Hint(expressions=[self._find_sql(start, end)]) 4578 4579 def _parse_hint_function_call(self) -> exp.Expr | None: 4580 return self._parse_function_call() 4581 4582 def _parse_hint_body(self) -> exp.Hint | None: 4583 start_index = self._index 4584 should_fallback_to_string = False 4585 4586 hints = [] 4587 try: 4588 for hint in iter( 4589 lambda: self._parse_csv( 4590 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4591 ), 4592 [], 4593 ): 4594 hints.extend(hint) 4595 except ParseError: 4596 should_fallback_to_string = True 4597 4598 if should_fallback_to_string or self._curr: 4599 self._retreat(start_index) 4600 return self._parse_hint_fallback_to_string() 4601 4602 return self.expression(exp.Hint(expressions=hints)) 4603 4604 def _parse_hint(self) -> exp.Hint | None: 4605 if self._match(TokenType.HINT) and self._prev_comments: 4606 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4607 4608 return None 4609 4610 def _parse_into(self) -> exp.Into | None: 4611 if not self._match(TokenType.INTO): 4612 return None 4613 4614 temp = self._match(TokenType.TEMPORARY) 4615 unlogged = self._match_text_seq("UNLOGGED") 4616 self._match(TokenType.TABLE) 4617 4618 return self.expression( 4619 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4620 ) 4621 4622 def _parse_from( 4623 self, 4624 joins: bool = False, 4625 skip_from_token: bool = False, 4626 consume_pipe: bool = False, 4627 ) -> exp.From | None: 4628 if not skip_from_token and not self._match(TokenType.FROM): 4629 return None 4630 4631 comments = self._prev_comments 4632 return self.expression( 4633 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4634 comments=comments, 4635 ) 4636 4637 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4638 return self.expression( 4639 exp.MatchRecognizeMeasure( 4640 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4641 this=self._parse_expression(), 4642 ) 4643 ) 4644 4645 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4646 if not self._match(TokenType.MATCH_RECOGNIZE): 4647 return None 4648 4649 self._match_l_paren() 4650 4651 partition = self._parse_partition_by() 4652 order = self._parse_order() 4653 4654 measures = ( 4655 self._parse_csv(self._parse_match_recognize_measure) 4656 if self._match_text_seq("MEASURES") 4657 else None 4658 ) 4659 4660 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4661 rows = exp.var("ONE ROW PER MATCH") 4662 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4663 text = "ALL ROWS PER MATCH" 4664 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4665 text += " SHOW EMPTY MATCHES" 4666 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4667 text += " OMIT EMPTY MATCHES" 4668 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4669 text += " WITH UNMATCHED ROWS" 4670 rows = exp.var(text) 4671 else: 4672 rows = None 4673 4674 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4675 text = "AFTER MATCH SKIP" 4676 if self._match_text_seq("PAST", "LAST", "ROW"): 4677 text += " PAST LAST ROW" 4678 elif self._match_text_seq("TO", "NEXT", "ROW"): 4679 text += " TO NEXT ROW" 4680 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4681 direction = self._prev.text.upper() 4682 pattern_var = self._advance_any() 4683 if not pattern_var: 4684 self.raise_error( 4685 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4686 ) 4687 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4688 after = exp.var(text) 4689 else: 4690 after = None 4691 4692 if self._match_text_seq("PATTERN"): 4693 self._match_l_paren() 4694 4695 if not self._curr: 4696 self.raise_error("Expecting )", self._curr) 4697 4698 paren = 1 4699 start = self._curr 4700 4701 while self._curr and paren > 0: 4702 if self._curr.token_type == TokenType.L_PAREN: 4703 paren += 1 4704 if self._curr.token_type == TokenType.R_PAREN: 4705 paren -= 1 4706 4707 end = self._prev 4708 self._advance() 4709 4710 if paren > 0: 4711 self.raise_error("Expecting )", self._curr) 4712 4713 pattern = exp.var(self._find_sql(start, end)) 4714 else: 4715 pattern = None 4716 4717 define = ( 4718 self._parse_csv(self._parse_name_as_expression) 4719 if self._match_text_seq("DEFINE") 4720 else None 4721 ) 4722 4723 self._match_r_paren() 4724 4725 return self.expression( 4726 exp.MatchRecognize( 4727 partition_by=partition, 4728 order=order, 4729 measures=measures, 4730 rows=rows, 4731 after=after, 4732 pattern=pattern, 4733 define=define, 4734 alias=self._parse_table_alias(), 4735 ) 4736 ) 4737 4738 def _parse_lateral(self) -> exp.Lateral | None: 4739 cross_apply: bool | None = None 4740 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4741 cross_apply = True 4742 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4743 cross_apply = False 4744 4745 if cross_apply is not None: 4746 this = self._parse_select(table=True) 4747 view = None 4748 outer = None 4749 elif self._match(TokenType.LATERAL): 4750 this = self._parse_select(table=True) 4751 view = self._match(TokenType.VIEW) 4752 outer = self._match(TokenType.OUTER) 4753 else: 4754 return None 4755 4756 if not this: 4757 this = ( 4758 self._parse_unnest() 4759 or self._parse_function() 4760 or self._parse_id_var(any_token=False) 4761 ) 4762 4763 while self._match(TokenType.DOT): 4764 this = exp.Dot( 4765 this=this, 4766 expression=self._parse_function() or self._parse_id_var(any_token=False), 4767 ) 4768 4769 ordinality: bool | None = None 4770 4771 if view: 4772 table = self._parse_id_var(any_token=False) 4773 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4774 table_alias: exp.TableAlias | None = self.expression( 4775 exp.TableAlias(this=table, columns=columns) 4776 ) 4777 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4778 # We move the alias from the lateral's child node to the lateral itself 4779 table_alias = this.args["alias"].pop() 4780 else: 4781 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4782 table_alias = self._parse_table_alias() 4783 4784 return self.expression( 4785 exp.Lateral( 4786 this=this, 4787 view=view, 4788 outer=outer, 4789 alias=table_alias, 4790 cross_apply=cross_apply, 4791 ordinality=ordinality, 4792 ) 4793 ) 4794 4795 def _parse_stream(self) -> exp.Stream | None: 4796 index = self._index 4797 if self._match(TokenType.STREAM): 4798 if this := self._try_parse(self._parse_table): 4799 return self.expression(exp.Stream(this=this)) 4800 self._retreat(index) 4801 return None 4802 4803 def _parse_join_parts( 4804 self, 4805 ) -> tuple[Token | None, Token | None, Token | None]: 4806 return ( 4807 self._prev if self._match_set(self.JOIN_METHODS) else None, 4808 self._prev if self._match_set(self.JOIN_SIDES) else None, 4809 self._prev if self._match_set(self.JOIN_KINDS) else None, 4810 ) 4811 4812 def _parse_using_identifiers(self) -> list[exp.Expr]: 4813 def _parse_column_as_identifier() -> exp.Expr | None: 4814 this = self._parse_column() 4815 if isinstance(this, exp.Column): 4816 return this.this 4817 return this 4818 4819 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4820 4821 def _parse_join( 4822 self, 4823 skip_join_token: bool = False, 4824 parse_bracket: bool = False, 4825 alias_tokens: t.Collection[TokenType] | None = None, 4826 ) -> exp.Join | None: 4827 if self._match(TokenType.COMMA): 4828 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4829 cross_join = self.expression(exp.Join(this=table)) if table else None 4830 4831 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4832 cross_join.set("kind", "CROSS") 4833 4834 return cross_join 4835 4836 index = self._index 4837 method, side, kind = self._parse_join_parts() 4838 directed = self.SUPPORTS_DIRECTED_JOIN and self._match_text_seq("DIRECTED") 4839 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4840 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4841 join_comments = self._prev_comments 4842 4843 if not skip_join_token and not join: 4844 self._retreat(index) 4845 kind = None 4846 method = None 4847 side = None 4848 4849 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4850 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4851 4852 if not skip_join_token and not join and not outer_apply and not cross_apply: 4853 return None 4854 4855 kwargs: dict[str, t.Any] = { 4856 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4857 } 4858 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4859 kwargs["expressions"] = self._parse_csv( 4860 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4861 ) 4862 4863 if method: 4864 kwargs["method"] = method.text.upper() 4865 if side: 4866 kwargs["side"] = side.text.upper() 4867 if kind: 4868 kwargs["kind"] = kind.text.upper() 4869 if hint: 4870 kwargs["hint"] = hint 4871 4872 if self._match(TokenType.MATCH_CONDITION): 4873 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4874 4875 if self._match(TokenType.ON): 4876 kwargs["on"] = self._parse_disjunction() 4877 elif self._match(TokenType.USING): 4878 kwargs["using"] = self._parse_using_identifiers() 4879 elif ( 4880 not method 4881 and not (outer_apply or cross_apply) 4882 and not isinstance(kwargs["this"], exp.Unnest) 4883 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4884 ): 4885 index = self._index 4886 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4887 4888 if joins and self._match(TokenType.ON): 4889 kwargs["on"] = self._parse_disjunction() 4890 elif joins and self._match(TokenType.USING): 4891 kwargs["using"] = self._parse_using_identifiers() 4892 else: 4893 joins = None 4894 self._retreat(index) 4895 4896 kwargs["this"].set("joins", joins if joins else None) 4897 4898 kwargs["pivots"] = self._parse_pivots() 4899 4900 comments = [c for token in (method, side, kind) if token for c in token.comments] 4901 comments = (join_comments or []) + comments 4902 4903 if ( 4904 self.ADD_JOIN_ON_TRUE 4905 and not kwargs.get("on") 4906 and not kwargs.get("using") 4907 and not kwargs.get("method") 4908 and kwargs.get("kind") in (None, "INNER", "OUTER") 4909 ): 4910 kwargs["on"] = exp.true() 4911 4912 if directed: 4913 kwargs["directed"] = directed 4914 4915 return self.expression(exp.Join(**kwargs), comments=comments) 4916 4917 def _parse_opclass(self) -> exp.Expr | None: 4918 this = self._parse_disjunction() 4919 4920 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4921 return this 4922 4923 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4924 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4925 4926 return this 4927 4928 def _parse_index_params(self) -> exp.IndexParameters: 4929 using = ( 4930 self._parse_var(any_token=True) 4931 if not self._match_text_seq("USING", "INDEX", "TABLESPACE", advance=False) 4932 and self._match(TokenType.USING) 4933 else None 4934 ) 4935 4936 if self._match(TokenType.L_PAREN, advance=False): 4937 columns = self._parse_wrapped_csv(self._parse_with_operator) 4938 else: 4939 columns = None 4940 4941 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4942 partition_by = self._parse_partition_by() 4943 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4944 tablespace = ( 4945 self._parse_id_var() 4946 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4947 or self._match_text_seq("TABLESPACE") 4948 else None 4949 ) 4950 where = self._parse_where() 4951 4952 on = self._parse_field() if self._match(TokenType.ON) else None 4953 4954 return self.expression( 4955 exp.IndexParameters( 4956 using=using, 4957 columns=columns, 4958 include=include, 4959 partition_by=partition_by, 4960 where=where, 4961 with_storage=with_storage, 4962 tablespace=tablespace, 4963 on=on, 4964 ) 4965 ) 4966 4967 def _parse_index( 4968 self, index: exp.Expr | None = None, anonymous: bool = False 4969 ) -> exp.Index | None: 4970 if index or anonymous: 4971 unique = None 4972 primary = None 4973 amp = None 4974 4975 self._match(TokenType.ON) 4976 self._match(TokenType.TABLE) # hive 4977 table = self._parse_table_parts(schema=True) 4978 else: 4979 unique = self._match(TokenType.UNIQUE) 4980 primary = self._match_text_seq("PRIMARY") 4981 amp = self._match_text_seq("AMP") 4982 4983 if not self._match(TokenType.INDEX): 4984 return None 4985 4986 index = self._parse_id_var() 4987 table = None 4988 4989 params = self._parse_index_params() 4990 4991 return self.expression( 4992 exp.Index( 4993 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4994 ) 4995 ) 4996 4997 def _parse_table_hints(self) -> list[exp.Expr] | None: 4998 hints: list[exp.Expr] = [] 4999 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 5000 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 5001 hints.append( 5002 self.expression( 5003 exp.WithTableHint( 5004 expressions=self._parse_csv( 5005 lambda: self._parse_function() or self._parse_var(any_token=True) 5006 ) 5007 ) 5008 ) 5009 ) 5010 self._match_r_paren() 5011 else: 5012 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 5013 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 5014 hint = exp.IndexTableHint(this=self._prev.text.upper()) 5015 5016 self._match_set((TokenType.INDEX, TokenType.KEY)) 5017 if self._match(TokenType.FOR): 5018 hint.set("target", self._advance_any() and self._prev.text.upper()) 5019 5020 hint.set("expressions", self._parse_wrapped_id_vars()) 5021 hints.append(hint) 5022 5023 return hints or None 5024 5025 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 5026 return ( 5027 (not schema and self._parse_function(optional_parens=False)) 5028 or self._parse_id_var(any_token=False) 5029 or self._parse_string_as_identifier() 5030 or self._parse_placeholder() 5031 ) 5032 5033 def _parse_table_parts_fast(self) -> exp.Table | None: 5034 index = self._index 5035 parts: list[exp.Identifier] | None = None 5036 all_comments: list[str] | None = None 5037 5038 while self._match_set(self.IDENTIFIER_TOKENS): 5039 token = self._prev 5040 comments = self._prev_comments 5041 5042 has_dot = self._match(TokenType.DOT) 5043 curr_tt = self._curr.token_type 5044 5045 if not has_dot: 5046 if curr_tt in self.TABLE_POSTFIX_TOKENS: 5047 self._retreat(index) 5048 return None 5049 elif curr_tt not in self.IDENTIFIER_TOKENS: 5050 self._retreat(index) 5051 return None 5052 5053 if parts is None: 5054 parts = [] 5055 5056 if comments: 5057 if all_comments is None: 5058 all_comments = [] 5059 all_comments.extend(comments) 5060 self._prev_comments = [] 5061 5062 parts.append( 5063 self.expression( 5064 exp.Identifier( 5065 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 5066 ), 5067 token, 5068 ) 5069 ) 5070 5071 if not has_dot: 5072 break 5073 5074 if parts is None: 5075 return None 5076 5077 n = len(parts) 5078 5079 if n == 1: 5080 table: exp.Table = exp.Table(this=parts[0]) 5081 elif n == 2: 5082 table = exp.Table(this=parts[1], db=parts[0]) 5083 elif n >= 3: 5084 this: exp.Identifier | exp.Dot = parts[2] 5085 for i in range(3, n): 5086 this = exp.Dot(this=this, expression=parts[i]) 5087 5088 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 5089 5090 if table is None: 5091 self._retreat(index) 5092 elif all_comments: 5093 table.add_comments(all_comments) 5094 return table 5095 5096 def _parse_table_parts( 5097 self, 5098 schema: bool = False, 5099 is_db_reference: bool = False, 5100 wildcard: bool = False, 5101 fast: bool = False, 5102 ) -> exp.Table | exp.Dot | None: 5103 if fast: 5104 return self._parse_table_parts_fast() 5105 5106 catalog: exp.Expr | str | None = None 5107 db: exp.Expr | str | None = None 5108 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 5109 5110 while self._match(TokenType.DOT): 5111 if catalog: 5112 # This allows nesting the table in arbitrarily many dot expressions if needed 5113 table = self.expression( 5114 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 5115 ) 5116 else: 5117 catalog = db 5118 db = table 5119 # "" used for tsql FROM a..b case 5120 table = self._parse_table_part(schema=schema) or "" 5121 5122 if ( 5123 wildcard 5124 and self._is_connected() 5125 and (isinstance(table, exp.Identifier) or not table) 5126 and self._match(TokenType.STAR) 5127 ): 5128 if isinstance(table, exp.Identifier): 5129 table.args["this"] += "*" 5130 else: 5131 table = exp.Identifier(this="*") 5132 5133 if is_db_reference: 5134 catalog = db 5135 db = table 5136 table = None 5137 5138 if not table and not is_db_reference: 5139 self.raise_error(f"Expected table name but got {self._curr}") 5140 if not db and is_db_reference: 5141 self.raise_error(f"Expected database name but got {self._curr}") 5142 5143 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 5144 5145 # Bubble up comments from identifier parts to the Table 5146 comments = [] 5147 for part in table.parts: 5148 if part_comments := part.pop_comments(): 5149 comments.extend(part_comments) 5150 if comments: 5151 table.add_comments(comments) 5152 5153 changes = self._parse_changes() 5154 if changes: 5155 table.set("changes", changes) 5156 5157 at_before = self._parse_historical_data() 5158 if at_before: 5159 table.set("when", at_before) 5160 5161 pivots = self._parse_pivots() 5162 if pivots: 5163 table.set("pivots", pivots) 5164 5165 return table 5166 5167 def _parse_table( 5168 self, 5169 schema: bool = False, 5170 joins: bool = False, 5171 alias_tokens: t.Collection[TokenType] | None = None, 5172 parse_bracket: bool = False, 5173 is_db_reference: bool = False, 5174 parse_partition: bool = False, 5175 consume_pipe: bool = False, 5176 ) -> exp.Expr | None: 5177 if not schema and not is_db_reference and not consume_pipe and not joins: 5178 index = self._index 5179 table = self._parse_table_parts(fast=True) 5180 5181 if table is not None: 5182 curr_tt = self._curr.token_type 5183 next_tt = self._next.token_type 5184 5185 fast_terminators = self.TABLE_TERMINATORS 5186 5187 # only return the table if we're sure there are no other operators 5188 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5189 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5190 return table 5191 5192 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5193 5194 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5195 if alias := self._parse_table_alias( 5196 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5197 ): 5198 table.set("alias", alias) 5199 5200 if self._curr.token_type in fast_terminators: 5201 return table 5202 5203 self._retreat(index) 5204 5205 if stream := self._parse_stream(): 5206 return stream 5207 5208 if lateral := self._parse_lateral(): 5209 return lateral 5210 5211 if unnest := self._parse_unnest(): 5212 return unnest 5213 5214 if values := self._parse_derived_table_values(): 5215 return values 5216 5217 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5218 if not subquery.args.get("pivots"): 5219 subquery.set("pivots", self._parse_pivots()) 5220 if joins: 5221 for join in self._parse_joins(): 5222 subquery.append("joins", join) 5223 return subquery 5224 5225 bracket = parse_bracket and self._parse_bracket(None) 5226 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5227 5228 rows_from_tables = ( 5229 self._parse_wrapped_csv(self._parse_table) 5230 if self._match_text_seq("ROWS", "FROM") 5231 else None 5232 ) 5233 rows_from = ( 5234 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5235 ) 5236 5237 only = self._match(TokenType.ONLY) 5238 5239 this = t.cast( 5240 exp.Expr, 5241 bracket 5242 or rows_from 5243 or self._parse_bracket( 5244 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5245 ), 5246 ) 5247 5248 if only: 5249 this.set("only", only) 5250 5251 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5252 self._match(TokenType.STAR) 5253 5254 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5255 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5256 this.set("partition", self._parse_partition()) 5257 5258 if schema: 5259 return self._parse_schema(this=this) 5260 5261 if self.dialect.ALIAS_POST_VERSION: 5262 this.set("version", self._parse_version()) 5263 5264 if self.dialect.ALIAS_POST_TABLESAMPLE: 5265 this.set("sample", self._parse_table_sample()) 5266 if ( 5267 this.args.get("sample") 5268 and self.dialect.ALIAS_POST_VERSION 5269 and not this.args.get("version") 5270 ): 5271 this.set("version", self._parse_version()) 5272 5273 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5274 if alias: 5275 this.set("alias", alias) 5276 5277 # DuckDB requires the time-travel clause to come after the alias, e.g. 5278 # SELECT * FROM t AS a AT (VERSION => 1) 5279 if isinstance(this, exp.Table) and not this.args.get("when"): 5280 this.set("when", self._parse_historical_data()) 5281 5282 if self._match(TokenType.INDEXED_BY): 5283 this.set("indexed", self._parse_table_parts()) 5284 elif self._match_text_seq("NOT", "INDEXED"): 5285 this.set("indexed", False) 5286 5287 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5288 return self.expression( 5289 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5290 ) 5291 5292 this.set("hints", self._parse_table_hints()) 5293 5294 if not this.args.get("pivots"): 5295 this.set("pivots", self._parse_pivots()) 5296 5297 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5298 this.set("sample", self._parse_table_sample()) 5299 5300 if not self.dialect.ALIAS_POST_VERSION: 5301 this.set("version", self._parse_version()) 5302 5303 if joins: 5304 for join in self._parse_joins(alias_tokens=alias_tokens): 5305 this.append("joins", join) 5306 5307 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5308 this.set("ordinality", True) 5309 this.set("alias", self._parse_table_alias()) 5310 5311 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5312 # hoist the table args onto the latter and return it instead 5313 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5314 table_from_rows = this.this 5315 for arg in exp.TableFromRows.arg_types: 5316 if arg != "this": 5317 table_from_rows.set(arg, this.args.get(arg)) 5318 5319 this = table_from_rows 5320 5321 return this 5322 5323 def _parse_version(self) -> exp.Version | None: 5324 for phrase, this in self.VERSION_PHRASES.items(): 5325 if self._match_text_seq(*phrase): 5326 break 5327 else: 5328 return None 5329 5330 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5331 kind = self._prev.text.upper() 5332 start = self._parse_bitwise() 5333 self._match_texts(("TO", "AND")) 5334 end = self._parse_bitwise() 5335 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5336 elif self._match_text_seq("CONTAINED", "IN"): 5337 kind = "CONTAINED IN" 5338 expression = self.expression( 5339 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5340 ) 5341 elif self._match(TokenType.ALL): 5342 kind = "ALL" 5343 expression = None 5344 else: 5345 self._match_text_seq("AS", "OF") 5346 kind = "AS OF" 5347 expression = self._parse_bitwise() 5348 5349 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5350 5351 def _parse_historical_data(self) -> exp.HistoricalData | None: 5352 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5353 index = self._index 5354 historical_data = None 5355 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5356 this = self._prev.text.upper() 5357 kind = ( 5358 self._match(TokenType.L_PAREN) 5359 and self._match_texts(self.HISTORICAL_DATA_KIND) 5360 and self._prev.text.upper() 5361 ) 5362 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5363 5364 if expression: 5365 self._match_r_paren() 5366 historical_data = self.expression( 5367 exp.HistoricalData(this=this, kind=kind, expression=expression) 5368 ) 5369 else: 5370 self._retreat(index) 5371 5372 return historical_data 5373 5374 def _parse_changes(self) -> exp.Changes | None: 5375 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5376 return None 5377 5378 information = self._parse_var(any_token=True) 5379 self._match_r_paren() 5380 5381 return self.expression( 5382 exp.Changes( 5383 information=information, 5384 at_before=self._parse_historical_data(), 5385 end=self._parse_historical_data(), 5386 ) 5387 ) 5388 5389 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5390 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5391 return None 5392 5393 self._advance() 5394 5395 expressions = self._parse_wrapped_csv(self._parse_equality) 5396 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5397 5398 alias = self._parse_table_alias() if with_alias else None 5399 5400 if alias: 5401 if self.dialect.UNNEST_COLUMN_ONLY: 5402 if alias.args.get("columns"): 5403 self.raise_error("Unexpected extra column alias in unnest.") 5404 5405 alias.set("columns", [alias.this]) 5406 alias.set("this", None) 5407 5408 columns = alias.args.get("columns") or [] 5409 if offset and len(expressions) < len(columns): 5410 offset = columns.pop() 5411 5412 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5413 self._match(TokenType.ALIAS) 5414 offset = self._parse_id_var( 5415 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5416 ) or exp.to_identifier("offset") 5417 5418 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5419 5420 def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None: 5421 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5422 if not is_derived and not ( 5423 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5424 self._match_text_seq("VALUES") 5425 or self._match_text_seq("FORMAT", "VALUES") 5426 # MySQL accepts VALUE as a synonym for VALUES in INSERT statements. 5427 or (allow_value_synonym and self._match_text_seq("VALUE")) 5428 ): 5429 return None 5430 5431 expressions = self._parse_csv(self._parse_value) 5432 alias = self._parse_table_alias() 5433 5434 if is_derived: 5435 self._match_r_paren() 5436 5437 return self.expression( 5438 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5439 ) 5440 5441 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5442 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5443 as_modifier and self._match_text_seq("USING", "SAMPLE") 5444 ): 5445 return None 5446 5447 bucket_numerator = None 5448 bucket_denominator = None 5449 bucket_field = None 5450 percent = None 5451 size = None 5452 seed = None 5453 5454 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5455 matched_l_paren = self._match(TokenType.L_PAREN) 5456 5457 if self.TABLESAMPLE_CSV: 5458 num = None 5459 expressions = self._parse_csv(self._parse_primary) 5460 else: 5461 expressions = None 5462 num = ( 5463 self._parse_factor(parse_mod=False) 5464 if self._match(TokenType.NUMBER, advance=False) 5465 else self._parse_primary() or self._parse_placeholder() 5466 ) 5467 5468 if self._match_text_seq("BUCKET"): 5469 bucket_numerator = self._parse_number() 5470 self._match_text_seq("OUT", "OF") 5471 bucket_denominator = bucket_denominator = self._parse_number() 5472 self._match(TokenType.ON) 5473 bucket_field = self._parse_field() 5474 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5475 percent = num 5476 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5477 size = num 5478 else: 5479 percent = num 5480 5481 if matched_l_paren: 5482 self._match_r_paren() 5483 5484 if self._match(TokenType.L_PAREN): 5485 method = self._parse_var(upper=True) 5486 seed = self._match(TokenType.COMMA) and self._parse_number() 5487 self._match_r_paren() 5488 elif self._match_texts(("SEED", "REPEATABLE")): 5489 seed = self._parse_wrapped(self._parse_number) 5490 5491 if not method and self.DEFAULT_SAMPLING_METHOD: 5492 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5493 5494 return self.expression( 5495 exp.TableSample( 5496 expressions=expressions, 5497 method=method, 5498 bucket_numerator=bucket_numerator, 5499 bucket_denominator=bucket_denominator, 5500 bucket_field=bucket_field, 5501 percent=percent, 5502 size=size, 5503 seed=seed, 5504 ) 5505 ) 5506 5507 def _parse_pivots(self) -> list[exp.Pivot] | None: 5508 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5509 return None 5510 return list(iter(self._parse_pivot, None)) or None 5511 5512 def _parse_joins( 5513 self, alias_tokens: t.Collection[TokenType] | None = None 5514 ) -> t.Iterator[exp.Join]: 5515 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5516 5517 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5518 if not self._match(TokenType.INTO): 5519 return None 5520 5521 return self.expression( 5522 exp.UnpivotColumns( 5523 this=self._match_text_seq("NAME") and self._parse_column(), 5524 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5525 ) 5526 ) 5527 5528 # https://duckdb.org/docs/sql/statements/pivot 5529 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5530 def _parse_on() -> exp.Expr | None: 5531 this = self._parse_bitwise() 5532 5533 if self._match(TokenType.IN): 5534 # PIVOT ... ON col IN (row_val1, row_val2) 5535 return self._parse_in(this) 5536 if self._match(TokenType.ALIAS, advance=False): 5537 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5538 return self._parse_alias(this) 5539 5540 return this 5541 5542 this = self._parse_table() 5543 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5544 into = self._parse_unpivot_columns() 5545 using = self._match(TokenType.USING) and self._parse_csv( 5546 lambda: self._parse_alias(self._parse_column()) 5547 ) 5548 group = self._parse_group() 5549 5550 return self.expression( 5551 exp.Pivot( 5552 this=this, 5553 expressions=expressions, 5554 using=using, 5555 group=group, 5556 unpivot=is_unpivot, 5557 into=into, 5558 ) 5559 ) 5560 5561 def _parse_pivot_in(self) -> exp.In: 5562 def _parse_aliased_expression() -> exp.Expr | None: 5563 this = self._parse_select_or_expression() 5564 5565 self._match(TokenType.ALIAS) 5566 alias = self._parse_bitwise() 5567 if alias: 5568 if isinstance(alias, exp.Column) and not alias.db: 5569 alias = alias.this 5570 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5571 5572 return this 5573 5574 value = self._parse_column() 5575 5576 if not self._match(TokenType.IN): 5577 self.raise_error("Expecting IN") 5578 5579 if self._match(TokenType.L_PAREN): 5580 if self._match(TokenType.ANY): 5581 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5582 else: 5583 exprs = self._parse_csv(_parse_aliased_expression) 5584 self._match_r_paren() 5585 return self.expression(exp.In(this=value, expressions=exprs)) 5586 5587 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5588 5589 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5590 func = self._parse_function() 5591 if not func: 5592 if self._prev.token_type == TokenType.COMMA: 5593 return None 5594 self.raise_error("Expecting an aggregation function in PIVOT") 5595 5596 return self._parse_alias(func) 5597 5598 def _parse_pivot(self) -> exp.Pivot | None: 5599 index = self._index 5600 include_nulls = None 5601 5602 if self._match(TokenType.PIVOT): 5603 unpivot = False 5604 elif self._match(TokenType.UNPIVOT): 5605 unpivot = True 5606 5607 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5608 if self._match_text_seq("INCLUDE", "NULLS"): 5609 include_nulls = True 5610 elif self._match_text_seq("EXCLUDE", "NULLS"): 5611 include_nulls = False 5612 else: 5613 return None 5614 5615 expressions = [] 5616 5617 if not self._match(TokenType.L_PAREN): 5618 self._retreat(index) 5619 return None 5620 5621 if unpivot: 5622 expressions = self._parse_csv(self._parse_column) 5623 else: 5624 expressions = self._parse_csv(self._parse_pivot_aggregation) 5625 5626 if not expressions: 5627 self.raise_error("Failed to parse PIVOT's aggregation list") 5628 5629 if not self._match(TokenType.FOR): 5630 self.raise_error("Expecting FOR") 5631 5632 fields = [] 5633 while True: 5634 field = self._try_parse(self._parse_pivot_in) 5635 if not field: 5636 break 5637 fields.append(field) 5638 5639 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5640 self._parse_bitwise 5641 ) 5642 5643 group = self._parse_group() 5644 5645 self._match_r_paren() 5646 5647 pivot = self.expression( 5648 exp.Pivot( 5649 expressions=expressions, 5650 fields=fields, 5651 unpivot=unpivot, 5652 include_nulls=include_nulls, 5653 default_on_null=default_on_null, 5654 group=group, 5655 ) 5656 ) 5657 5658 if unpivot: 5659 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5660 for pivot_field in pivot.fields: 5661 if isinstance(pivot_field, exp.In): 5662 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5663 5664 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5665 5666 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5667 pivot.set("alias", self._parse_table_alias()) 5668 5669 if not unpivot: 5670 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5671 5672 columns: list[exp.Expr] = [] 5673 all_fields = [] 5674 for pivot_field in pivot.fields: 5675 pivot_field_expressions = pivot_field.expressions 5676 5677 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5678 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5679 continue 5680 5681 all_fields.append( 5682 [ 5683 # An explicit `<field> AS <alias>` names the output column directly, 5684 # so it wins over the dialect's string-identifying convention 5685 fld.sql() 5686 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5687 else fld.alias_or_name 5688 for fld in pivot_field_expressions 5689 ] 5690 ) 5691 5692 if all_fields: 5693 if names: 5694 all_fields.append(names) 5695 5696 # Generate all possible combinations of the pivot columns 5697 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5698 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5699 for fld_parts_tuple in itertools.product(*all_fields): 5700 fld_parts = list(fld_parts_tuple) 5701 5702 if names and self.PREFIXED_PIVOT_COLUMNS: 5703 # Move the "name" to the front of the list 5704 fld_parts.insert(0, fld_parts.pop(-1)) 5705 5706 columns.append(exp.to_identifier("_".join(fld_parts))) 5707 5708 pivot.set("columns", columns) 5709 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5710 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5711 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5712 5713 return pivot 5714 5715 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5716 return [agg.alias for agg in aggregations if agg.alias] 5717 5718 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5719 if not skip_where_token and not self._match(TokenType.PREWHERE): 5720 return None 5721 5722 comments = self._prev_comments 5723 return self.expression( 5724 exp.PreWhere(this=self._parse_disjunction()), 5725 comments=comments, 5726 ) 5727 5728 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5729 if not skip_where_token and not self._match(TokenType.WHERE): 5730 return None 5731 5732 comments = self._prev_comments 5733 return self.expression( 5734 exp.Where(this=self._parse_disjunction()), 5735 comments=comments, 5736 ) 5737 5738 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5739 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5740 return None 5741 comments = self._prev_comments 5742 5743 elements: dict[str, t.Any] = defaultdict(list) 5744 5745 if self._match(TokenType.ALL): 5746 elements["all"] = True 5747 elif self._match(TokenType.DISTINCT): 5748 elements["all"] = False 5749 5750 while True: 5751 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5752 # which are also valid identifiers 5753 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5754 break 5755 5756 elements["expressions"].extend( 5757 self._parse_csv( 5758 lambda: ( 5759 self._parse_grouping_sets() 5760 or self._parse_cube_or_rollup() 5761 or self._parse_disjunction() 5762 ) 5763 ) 5764 ) 5765 5766 before_with_index = self._index 5767 5768 if self._match(TokenType.WITH) and ( 5769 cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True) 5770 ): 5771 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5772 elements[key].append(cube_or_rollup) 5773 elif grouping_sets := self._parse_grouping_sets(): 5774 # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...) 5775 elements["grouping_sets"].append(grouping_sets) 5776 break 5777 elif self._match_text_seq("TOTALS"): 5778 elements["totals"] = True # type: ignore 5779 5780 if before_with_index <= self._index <= before_with_index + 1: 5781 self._retreat(before_with_index) 5782 break 5783 5784 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5785 5786 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5787 if self._match(TokenType.CUBE): 5788 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5789 elif self._match(TokenType.ROLLUP): 5790 kind = exp.Rollup 5791 else: 5792 return None 5793 5794 return self.expression( 5795 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5796 ) 5797 5798 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5799 if self._match(TokenType.GROUPING_SETS): 5800 return self.expression( 5801 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5802 ) 5803 return None 5804 5805 def _parse_grouping_set(self) -> exp.Expr | None: 5806 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5807 5808 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5809 if not skip_having_token and not self._match(TokenType.HAVING): 5810 return None 5811 comments = self._prev_comments 5812 return self.expression( 5813 exp.Having(this=self._parse_disjunction()), 5814 comments=comments, 5815 ) 5816 5817 def _parse_qualify(self) -> exp.Qualify | None: 5818 if not self._match(TokenType.QUALIFY): 5819 return None 5820 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5821 5822 def _parse_connect_with_prior(self) -> exp.Expr | None: 5823 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5824 exp.Prior(this=self._parse_bitwise()) 5825 ) 5826 connect = self._parse_disjunction() 5827 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5828 return connect 5829 5830 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5831 if skip_start_token: 5832 start = None 5833 elif self._match_text_seq("START", "WITH"): 5834 start = self._parse_disjunction() 5835 else: 5836 return None 5837 5838 self._match(TokenType.CONNECT_BY) 5839 nocycle = self._match_text_seq("NOCYCLE") 5840 connect = self._parse_connect_with_prior() 5841 5842 if not start and self._match_text_seq("START", "WITH"): 5843 start = self._parse_disjunction() 5844 5845 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5846 5847 def _parse_name_as_expression(self) -> exp.Expr | None: 5848 this = self._parse_id_var(any_token=True) 5849 if self._match(TokenType.ALIAS): 5850 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5851 return this 5852 5853 def _parse_interpolate(self) -> list[exp.Expr] | None: 5854 if self._match_text_seq("INTERPOLATE"): 5855 return self._parse_wrapped_csv(self._parse_name_as_expression) 5856 return None 5857 5858 def _parse_order( 5859 self, this: exp.Expr | None = None, skip_order_token: bool = False 5860 ) -> exp.Expr | None: 5861 siblings = None 5862 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5863 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5864 return this 5865 5866 siblings = True 5867 5868 comments = self._prev_comments 5869 return self.expression( 5870 exp.Order( 5871 this=this, 5872 expressions=self._parse_csv(self._parse_ordered), 5873 siblings=siblings, 5874 ), 5875 comments=comments, 5876 ) 5877 5878 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5879 if not self._match(token): 5880 return None 5881 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5882 5883 def _parse_ordered( 5884 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5885 ) -> exp.Ordered | None: 5886 this = parse_method() if parse_method else self._parse_disjunction() 5887 if not this: 5888 return None 5889 5890 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5891 this = exp.var("ALL") 5892 5893 asc = self._match(TokenType.ASC) 5894 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5895 5896 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5897 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5898 5899 nulls_first = is_nulls_first or False 5900 explicitly_null_ordered = is_nulls_first or is_nulls_last 5901 5902 if ( 5903 not explicitly_null_ordered 5904 and ( 5905 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5906 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5907 ) 5908 and self.dialect.NULL_ORDERING != "nulls_are_last" 5909 ): 5910 nulls_first = True 5911 5912 if self._match_text_seq("WITH", "FILL"): 5913 with_fill = self.expression( 5914 exp.WithFill( 5915 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5916 to=self._match_text_seq("TO") and self._parse_bitwise(), 5917 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5918 interpolate=self._parse_interpolate(), 5919 ) 5920 ) 5921 else: 5922 with_fill = None 5923 5924 return self.expression( 5925 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5926 ) 5927 5928 def _parse_limit_options(self) -> exp.LimitOptions | None: 5929 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5930 rows = self._match_texts(("ROW", "ROWS")) 5931 self._match_text_seq("ONLY") 5932 with_ties = self._match_text_seq("WITH", "TIES") 5933 5934 if not (percent or rows or with_ties): 5935 return None 5936 5937 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5938 5939 def _parse_limit( 5940 self, 5941 this: exp.Expr | None = None, 5942 top: bool = False, 5943 skip_limit_token: bool = False, 5944 ) -> exp.Expr | None: 5945 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5946 comments = self._prev_comments 5947 if top: 5948 limit_paren = self._match(TokenType.L_PAREN) 5949 expression = ( 5950 self._parse_term() or self._parse_select() 5951 if limit_paren 5952 else self._parse_number() 5953 ) 5954 5955 if limit_paren: 5956 self._match_r_paren() 5957 5958 else: 5959 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5960 expression = exp.var("ALL") 5961 else: 5962 expression = self._parse_term(parse_mod=False) 5963 limit_options = self._parse_limit_options() 5964 5965 if self._match(TokenType.COMMA): 5966 offset = expression 5967 expression = self._parse_term() 5968 else: 5969 offset = None 5970 5971 limit_exp = self.expression( 5972 exp.Limit( 5973 this=this, 5974 expression=expression, 5975 offset=offset, 5976 limit_options=limit_options, 5977 expressions=self._parse_limit_by(), 5978 ), 5979 comments=comments, 5980 ) 5981 5982 if top: 5983 limit_exp.meta["top"] = True 5984 5985 return limit_exp 5986 5987 if self._match(TokenType.FETCH): 5988 direction = ( 5989 self._prev.text.upper() 5990 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5991 else "FIRST" 5992 ) 5993 5994 count = ( 5995 None 5996 if self._match_texts(("ROW", "ROWS"), advance=False) 5997 else self._parse_field(tokens=self.FETCH_TOKENS) 5998 ) 5999 6000 return self.expression( 6001 exp.Fetch( 6002 direction=direction, count=count, limit_options=self._parse_limit_options() 6003 ) 6004 ) 6005 6006 return this 6007 6008 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 6009 if not self._match(TokenType.OFFSET): 6010 return this 6011 6012 count = self._parse_term() 6013 self._match_texts(("ROW", "ROWS")) 6014 6015 return self.expression( 6016 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 6017 ) 6018 6019 def _can_parse_limit_or_offset(self) -> bool: 6020 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 6021 return False 6022 6023 index = self._index 6024 result = bool( 6025 self._try_parse(self._parse_limit, retreat=True) 6026 or self._try_parse(self._parse_offset, retreat=True) 6027 ) 6028 self._retreat(index) 6029 6030 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 6031 if self._next.token_type == TokenType.MATCH_CONDITION: 6032 result = False 6033 6034 return result 6035 6036 def _can_parse_named_window(self) -> bool: 6037 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 6038 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 6039 if not self._match(TokenType.WINDOW, advance=False): 6040 return False 6041 6042 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 6043 if name is None or name.token_type not in self.ID_VAR_TOKENS: 6044 return False 6045 6046 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 6047 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 6048 return False 6049 6050 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 6051 return body is not None and body.token_type == TokenType.L_PAREN 6052 6053 def _parse_limit_by(self) -> list[exp.Expr] | None: 6054 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 6055 6056 def _parse_locks(self) -> list[exp.Lock]: 6057 locks = [] 6058 while True: 6059 update, key = None, None 6060 if self._match_text_seq("FOR", "UPDATE"): 6061 update = True 6062 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 6063 "LOCK", "IN", "SHARE", "MODE" 6064 ): 6065 update = False 6066 elif self._match_text_seq("FOR", "KEY", "SHARE"): 6067 update, key = False, True 6068 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 6069 update, key = True, True 6070 else: 6071 break 6072 6073 expressions = None 6074 if self._match_text_seq("OF"): 6075 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 6076 6077 wait: bool | exp.Expr | None = None 6078 if self._match_text_seq("NOWAIT"): 6079 wait = True 6080 elif self._match_text_seq("WAIT"): 6081 wait = self._parse_primary() 6082 elif self._match_text_seq("SKIP", "LOCKED"): 6083 wait = False 6084 6085 locks.append( 6086 self.expression( 6087 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 6088 ) 6089 ) 6090 6091 return locks 6092 6093 def parse_set_operation( 6094 self, this: exp.Expr | None, consume_pipe: bool = False, intersect_only: bool = False 6095 ) -> exp.SetOperation | None: 6096 start = self._index 6097 _, side_token, kind_token = self._parse_join_parts() 6098 6099 side = side_token.text if side_token else None 6100 kind = kind_token.text if kind_token else None 6101 6102 if not self._match_set(self.SET_OPERATIONS) or ( 6103 intersect_only and self._prev.token_type != TokenType.INTERSECT 6104 ): 6105 self._retreat(start) 6106 return None 6107 6108 token_type = self._prev.token_type 6109 6110 if token_type == TokenType.UNION: 6111 operation: type[exp.SetOperation] = exp.Union 6112 elif token_type == TokenType.EXCEPT: 6113 operation = exp.Except 6114 else: 6115 operation = exp.Intersect 6116 6117 comments = self._prev.comments 6118 6119 if self._match(TokenType.DISTINCT): 6120 distinct: bool | None = True 6121 elif self._match(TokenType.ALL): 6122 distinct = False 6123 else: 6124 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 6125 if distinct is None: 6126 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 6127 6128 by_name = ( 6129 self._match_text_seq("BY", "NAME") 6130 or self._match_text_seq("STRICT", "CORRESPONDING") 6131 or None 6132 ) 6133 if self._match_text_seq("CORRESPONDING"): 6134 by_name = True 6135 if not side and not kind: 6136 kind = "INNER" 6137 6138 on_column_list = None 6139 if by_name and self._match_texts(("ON", "BY")): 6140 on_column_list = self._parse_wrapped_csv(self._parse_column) 6141 6142 expression = self._parse_select( 6143 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 6144 ) 6145 6146 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 6147 # in _parse_cte and so that alias pushdown can reach into set operation branches 6148 if isinstance(this, exp.Values): 6149 this = self._values_to_select(this) 6150 if isinstance(expression, exp.Values): 6151 expression = self._values_to_select(expression) 6152 for operand in (this, expression): 6153 if isinstance(operand, exp.Subquery) and isinstance( 6154 values := operand.unnest(), exp.Values 6155 ): 6156 wrapper = t.cast(exp.Subquery, values.parent) 6157 wrapper.set("this", self._values_to_select(values)) 6158 6159 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 6160 subquery = this.this 6161 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 6162 subquery.add_comments(this.pop_comments()) 6163 this = subquery 6164 6165 return self.expression( 6166 operation( 6167 this=this, 6168 distinct=distinct, 6169 by_name=by_name, 6170 expression=expression, 6171 side=side, 6172 kind=kind, 6173 on=on_column_list, 6174 ), 6175 comments=comments, 6176 ) 6177 6178 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 6179 intersect_binds_tighter = self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT 6180 last_setop: exp.SetOperation | None = None 6181 6182 while this: 6183 setop = self.parse_set_operation(this) 6184 if not setop: 6185 break 6186 6187 last_setop = setop 6188 if ( 6189 isinstance(setop, exp.Intersect) 6190 and isinstance(setop.this, (exp.Union, exp.Except)) 6191 and not intersect_binds_tighter 6192 ): 6193 setop.set("this", setop.this.subquery(copy=False)) 6194 elif not isinstance(setop, exp.Intersect) and intersect_binds_tighter: 6195 # INTERSECT binds tighter than this UNION / EXCEPT, so any INTERSECTs that 6196 # follow it group with its right operand first: 6197 # a UNION b INTERSECT c INTERSECT d -> a UNION (b INTERSECT c INTERSECT d) 6198 operand = setop.expression 6199 while intersect := self.parse_set_operation(operand, intersect_only=True): 6200 last_setop = operand = intersect 6201 if operand is not setop.expression: 6202 setop.set("expression", operand.subquery(copy=False)) 6203 6204 this = setop 6205 6206 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6207 expression = last_setop.expression if last_setop else None 6208 6209 if expression: 6210 for arg in self.SET_OP_MODIFIERS: 6211 expr = expression.args.get(arg) 6212 if expr and not (arg == "limit" and expr.meta.get("top")): 6213 expression.set(arg, None) 6214 this.set(arg, expr) 6215 6216 # Consume modifiers after a parenthesized final operand (e.g., SELECT 1 UNION (SELECT 2) 6217 # ORDER BY 1), or a LIMIT/FETCH that a final operand with TOP leaves for the whole 6218 # result (e.g., SELECT 1 UNION SELECT TOP 2 2 LIMIT 1). 6219 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 6220 this = self._parse_query_modifiers(this) 6221 6222 return this 6223 6224 def _parse_expression(self) -> exp.Expr | None: 6225 return self._parse_alias(self._parse_assignment()) 6226 6227 def _parse_assignment(self) -> exp.Expr | None: 6228 this = self._parse_disjunction() 6229 if not this and self._next.token_type in self.ASSIGNMENT: 6230 # This allows us to parse <non-identifier token> := <expr> 6231 this = exp.column( 6232 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6233 ) 6234 6235 while self._match_set(self.ASSIGNMENT): 6236 if isinstance(this, exp.Column) and len(this.parts) == 1: 6237 this = this.this 6238 6239 comments = self._prev_comments 6240 this = self.expression( 6241 self.ASSIGNMENT[self._prev.token_type]( 6242 this=this, expression=self._parse_assignment() 6243 ), 6244 comments=comments, 6245 ) 6246 6247 return this 6248 6249 def _parse_disjunction(self) -> exp.Expr | None: 6250 this = self._parse_conjunction() 6251 while self._match_set(self.DISJUNCTION): 6252 comments = self._prev_comments 6253 this = self.expression( 6254 self.DISJUNCTION[self._prev.token_type]( 6255 this=this, expression=self._parse_conjunction() 6256 ), 6257 comments=comments, 6258 ) 6259 return this 6260 6261 def _parse_conjunction(self) -> exp.Expr | None: 6262 this = self._parse_equality() 6263 while self._match_set(self.CONJUNCTION): 6264 comments = self._prev_comments 6265 this = self.expression( 6266 self.CONJUNCTION[self._prev.token_type]( 6267 this=this, expression=self._parse_equality() 6268 ), 6269 comments=comments, 6270 ) 6271 return this 6272 6273 def _build_predicate( 6274 self, 6275 klass: Type[exp.Binary], 6276 this: exp.Expr | None, 6277 expression: exp.Expr | None, 6278 comments: list[str] | None = None, 6279 negate: bool = False, 6280 ) -> exp.Expr: 6281 is_predicates = (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ) 6282 if klass in is_predicates: 6283 if ( 6284 isinstance(this, (exp.Connector, exp.Not, exp.NullSafeNEQ)) 6285 or type(this) in self.EQUALITY.values() 6286 or type(this) in self.COMPARISON.values() 6287 or (klass is not exp.Is and isinstance(this, exp.Is)) 6288 ): 6289 this = exp.Paren(this=this) 6290 if isinstance(expression, (exp.Predicate, exp.Connector, exp.Not, exp.Escape)): 6291 expression = exp.Paren(this=expression) 6292 else: 6293 if isinstance(this.this if isinstance(this, exp.Not) else this, is_predicates): 6294 this = exp.Paren(this=this) 6295 if isinstance( 6296 expression.this if isinstance(expression, exp.Not) else expression, is_predicates 6297 ): 6298 expression = exp.Paren(this=expression) 6299 6300 predicate = klass(this=this, expression=expression) 6301 if negate: 6302 predicate.set("negate", True) 6303 return self.expression(predicate, comments=comments) 6304 6305 def _parse_equality(self, parse_is: bool = True) -> exp.Expr | None: 6306 this = self._parse_comparison() 6307 while True: 6308 if self._match_set(self.EQUALITY): 6309 comments = self._prev_comments 6310 this = self._build_predicate( 6311 self.EQUALITY[self._prev.token_type], 6312 this, 6313 self._parse_comparison(), 6314 comments=comments, 6315 ) 6316 elif parse_is and self._is_predicate_ahead(): 6317 index = self._index 6318 # A postfix predicate can itself be an operand, e.g. x IS NULL = FALSE. 6319 this = self._parse_comparison(self._parse_range(this, parse_is=True)) 6320 if self._index == index: 6321 break 6322 else: 6323 break 6324 return this 6325 6326 def _parse_comparison(self, this: exp.Expr | None = None) -> exp.Expr | None: 6327 this = this or self._parse_range() 6328 while self._match_set(self.COMPARISON): 6329 comments = self._prev_comments 6330 this = self._build_predicate( 6331 self.COMPARISON[self._prev.token_type], 6332 this, 6333 self._parse_range(), 6334 comments=comments, 6335 ) 6336 return this 6337 6338 def _is_predicate_ahead(self) -> bool: 6339 if ( 6340 self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.HIGH 6341 or self._curr.token_type not in self.IS_PREDICATE_TOKENS 6342 ): 6343 return False 6344 return self._curr.token_type != TokenType.NOT or self._next.token_type == TokenType.NULL 6345 6346 def _parse_range(self, this: exp.Expr | None = None, parse_is: bool = False) -> exp.Expr | None: 6347 this = this or self._parse_bitwise() 6348 6349 while True: 6350 if not parse_is and self._is_predicate_ahead(): 6351 break 6352 negate = self._match(TokenType.NOT) 6353 if self._match_set(self.RANGE_PARSERS): 6354 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6355 if not expression: 6356 return this 6357 6358 this = expression 6359 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6360 this = self._build_predicate(exp.Is, this, exp.Null()) 6361 elif self._match(TokenType.NOTNULL): 6362 # Postgres supports ISNULL and NOTNULL for conditions. 6363 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6364 if self.dialect.NORMALIZE_NOT_NULL: 6365 this = self._build_predicate(exp.Is, this, exp.Null()) 6366 this = self.expression(exp.Not(this=this)) 6367 else: 6368 this = self._build_predicate(exp.Is, this, exp.Null(), negate=True) 6369 else: 6370 if negate: 6371 self._retreat(self._index - 1) 6372 break 6373 6374 if negate: 6375 this = self._negate_range(this) 6376 if (negate or (isinstance(this, exp.Not) and isinstance(this.this, exp.Is))) and ( 6377 self._curr.token_type == TokenType.NOT 6378 or self._curr.token_type in self.RANGE_PARSERS 6379 ): 6380 this = self.expression(exp.Paren(this=this)) 6381 6382 return this 6383 6384 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6385 if not this: 6386 return this 6387 6388 expression = this.this if isinstance(this, exp.Escape) else this 6389 if isinstance(expression, (exp.Like, exp.ILike)): 6390 expression.set("negate", True) 6391 return this 6392 6393 return self.expression(exp.Not(this=this)) 6394 6395 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6396 index = self._index - 1 6397 negate = self._match(TokenType.NOT) 6398 6399 if self._match_text_seq("DISTINCT", "FROM"): 6400 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6401 return self._build_predicate(klass, this, self._parse_is_rhs(distinct=True)) 6402 6403 if self._match(TokenType.JSON): 6404 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6405 6406 if self._match_text_seq("WITH"): 6407 _with = True 6408 elif self._match_text_seq("WITHOUT"): 6409 _with = False 6410 else: 6411 _with = None 6412 6413 unique = self._match(TokenType.UNIQUE) 6414 self._match_text_seq("KEYS") 6415 expression: exp.Expr | None = self.expression( 6416 exp.JSON(this=kind, with_=_with, unique=unique) 6417 ) 6418 else: 6419 expression = self._parse_is_rhs() 6420 if not expression: 6421 self._retreat(index) 6422 return None 6423 6424 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6425 this = self._build_predicate(exp.Is, this, expression, negate=True) 6426 else: 6427 this = self._build_predicate(exp.Is, this, expression) 6428 this = self.expression(exp.Not(this=this)) if negate else this 6429 6430 return self._parse_column_ops(this) 6431 6432 def _parse_is_rhs(self, distinct: bool = False) -> exp.Expr | None: 6433 if distinct: 6434 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.LOW: 6435 return self._parse_equality(parse_is=False) 6436 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.EQUALITY: 6437 return self._parse_comparison() 6438 if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.COMPARISON: 6439 return self._parse_range() 6440 return self._parse_bitwise() 6441 return self._parse_null() or self._parse_bitwise() 6442 6443 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6444 unnest = self._parse_unnest(with_alias=False) 6445 if unnest: 6446 this = self.expression(exp.In(this=this, unnest=unnest)) 6447 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6448 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6449 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6450 6451 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6452 this = self.expression( 6453 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6454 ) 6455 else: 6456 this = self.expression(exp.In(this=this, expressions=expressions)) 6457 6458 if matched_l_paren: 6459 self._match_r_paren(this) 6460 elif not self._match(TokenType.R_BRACKET, expression=this): 6461 self.raise_error("Expecting ]") 6462 else: 6463 this = self.expression(exp.In(this=this, field=self._parse_column())) 6464 6465 return this 6466 6467 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6468 # Preserve grouping when BETWEEN is rewritten into comparisons. 6469 if isinstance(this, (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ)): 6470 this = exp.Paren(this=this) 6471 6472 symmetric = None 6473 if self._match_text_seq("SYMMETRIC"): 6474 symmetric = True 6475 elif self._match_text_seq("ASYMMETRIC"): 6476 symmetric = False 6477 6478 low = self._parse_bitwise() 6479 self._match(TokenType.AND) 6480 high = self._parse_bitwise() 6481 6482 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6483 6484 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6485 if not self._match(TokenType.ESCAPE): 6486 return this 6487 return self.expression( 6488 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6489 ) 6490 6491 def _parse_interval_span( 6492 self, this: exp.Expr, parse_function_unit: bool = True 6493 ) -> exp.Interval: 6494 # handle day-time format interval span with omitted units: 6495 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6496 interval_span_units_omitted = None 6497 fraction_in_string = False 6498 if ( 6499 this 6500 and this.is_string 6501 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6502 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6503 ): 6504 index = self._index 6505 6506 # Var "TO" Var 6507 first_unit = self._parse_var(any_token=True, upper=True) 6508 second_unit = None 6509 if first_unit and self._match_text_seq("TO"): 6510 second_unit = self._parse_var(any_token=True, upper=True) 6511 6512 interval_span_units_omitted = not (first_unit and second_unit) 6513 6514 self._retreat(index) 6515 6516 unit_index = self._index 6517 if interval_span_units_omitted: 6518 unit = None 6519 else: 6520 # Only attempt to parse a unit if the current token can actually be one, so that a 6521 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6522 is_unit = self._curr is not None and ( 6523 self._curr.token_type == TokenType.VAR 6524 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6525 ) 6526 unit = self._parse_function() if parse_function_unit and is_unit else None 6527 if not unit and is_unit: 6528 unit = self._parse_var(any_token=True, upper=True) 6529 6530 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6531 # each INTERVAL expression into this canonical form so it's easy to transpile 6532 if this and this.is_number: 6533 try: 6534 this = exp.Literal.string(this.to_py()) 6535 except ValueError: 6536 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6537 elif this and this.is_string: 6538 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6539 if parts and unit: 6540 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6541 unit = None 6542 self._retreat(unit_index) 6543 6544 if len(parts) == 1: 6545 this = exp.Literal.string(parts[0][0]) 6546 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6547 fraction_in_string = "." in parts[0][0] 6548 6549 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6550 unit = self.expression( 6551 exp.IntervalSpan( 6552 this=unit, 6553 expression=self._parse_function() 6554 or self._parse_var(any_token=True, upper=True), 6555 ) 6556 ) 6557 6558 return self.expression( 6559 exp.Interval(this=this, unit=unit, fraction_in_string=fraction_in_string or None) 6560 ) 6561 6562 def _parse_interval( 6563 self, require_interval: bool = True, parse_function_unit: bool = True 6564 ) -> exp.Add | exp.Interval | None: 6565 index = self._index 6566 6567 if not self._match(TokenType.INTERVAL) and require_interval: 6568 return None 6569 6570 if self._match(TokenType.STRING, advance=False): 6571 this = self._parse_primary() 6572 else: 6573 this = self._parse_term() 6574 6575 if not this or ( 6576 isinstance(this, exp.Column) 6577 and not this.table 6578 and not this.this.quoted 6579 and self._curr 6580 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6581 ): 6582 self._retreat(index) 6583 return None 6584 6585 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6586 6587 index = self._index 6588 self._match(TokenType.PLUS) 6589 6590 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6591 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6592 return self.expression( 6593 exp.Add( 6594 this=interval, 6595 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6596 ) 6597 ) 6598 6599 self._retreat(index) 6600 return interval 6601 6602 def _parse_bitwise(self) -> exp.Expr | None: 6603 this = self._parse_term() 6604 6605 while True: 6606 if self._match_set(self.BITWISE): 6607 this = self.expression( 6608 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6609 ) 6610 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6611 this = self.expression( 6612 exp.DPipe( 6613 this=this, 6614 expression=self._parse_term(), 6615 safe=not self.dialect.STRICT_STRING_CONCAT, 6616 ) 6617 ) 6618 elif self._match(TokenType.DQMARK): 6619 this = self.expression( 6620 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6621 ) 6622 elif self._match_pair(TokenType.LT, TokenType.LT): 6623 this = self.expression( 6624 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6625 ) 6626 elif self._match_pair(TokenType.GT, TokenType.GT): 6627 this = self.expression( 6628 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6629 ) 6630 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6631 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6632 else: 6633 break 6634 6635 return this 6636 6637 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6638 this = self._parse_factor(parse_mod=parse_mod) 6639 6640 while self._match_set(self.TERM): 6641 klass = self.TERM[self._prev.token_type] 6642 comments = self._prev_comments 6643 expression = self._parse_factor(parse_mod=parse_mod) 6644 6645 this = self.expression(klass(this=this, expression=expression), comments=comments) 6646 6647 if isinstance(this, exp.Collate): 6648 self._normalize_collate(this) 6649 6650 return this 6651 6652 def _normalize_collate(self, collate: exp.Collate) -> None: 6653 expr = collate.expression 6654 6655 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6656 # fallback to Identifier / Var 6657 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6658 ident = expr.this 6659 if isinstance(ident, exp.Identifier): 6660 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6661 6662 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6663 parse_method = self._parse_factor_operand 6664 this = self._parse_at_time_zone(parse_method()) 6665 6666 while self._match_set(self.FACTOR, advance=False): 6667 if not parse_mod and self._curr.token_type == TokenType.MOD: 6668 break 6669 6670 self._advance() 6671 klass = self.FACTOR[self._prev.token_type] 6672 comments = self._prev_comments 6673 expression = parse_method() 6674 6675 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6676 self._retreat(self._index - 1) 6677 return this 6678 6679 this = self.expression(klass(this=this, expression=expression), comments=comments) 6680 6681 if isinstance(this, exp.Div): 6682 this.set("typed", self.dialect.TYPED_DIVISION) 6683 this.set("safe", self.dialect.SAFE_DIVISION) 6684 6685 return this 6686 6687 def _parse_factor_operand(self) -> exp.Expr | None: 6688 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6689 6690 def _parse_exponent(self) -> exp.Expr | None: 6691 this = self._parse_unary() 6692 while self._match_set(self.EXPONENT): 6693 comments = self._prev_comments 6694 this = self.expression( 6695 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6696 comments=comments, 6697 ) 6698 return this 6699 6700 def _parse_unary(self) -> exp.Expr | None: 6701 if self._match_set(self.UNARY_PARSERS): 6702 return self.UNARY_PARSERS[self._prev.token_type](self) 6703 return self._parse_type() 6704 6705 def _parse_type( 6706 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6707 ) -> exp.Expr | None: 6708 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6709 return atom 6710 6711 if interval := parse_interval and self._parse_interval(): 6712 return self._parse_column_ops(interval) 6713 6714 index = self._index 6715 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6716 6717 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6718 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6719 if isinstance(data_type, exp.Cast): 6720 # This constructor can contain ops directly after it, for instance struct unnesting: 6721 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6722 return self._parse_column_ops(data_type) 6723 6724 if data_type: 6725 index2 = self._index 6726 this = self._parse_primary() 6727 6728 if isinstance(this, exp.Literal): 6729 literal = this.name 6730 this = self._parse_column_ops(this) 6731 6732 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6733 if data_type.is_type(exp.DType.TIMESTAMP): 6734 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6735 elif data_type.is_type(exp.DType.TIME): 6736 data_type = exp.DType.TIMETZ.into_expr() 6737 6738 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6739 if parser: 6740 return parser(self, this, data_type) 6741 6742 return self.expression(exp.Cast(this=this, to=data_type)) 6743 6744 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6745 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6746 # 6747 # If the index difference here is greater than 1, that means the parser itself must have 6748 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6749 # 6750 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6751 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6752 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6753 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6754 # 6755 # In these cases, we don't really want to return the converted type, but instead retreat 6756 # and try to parse a Column or Identifier in the section below. 6757 if data_type.expressions and index2 - index > 1: 6758 self._retreat(index2) 6759 return self._parse_column_ops(data_type) 6760 6761 self._retreat(index) 6762 6763 if fallback_to_identifier: 6764 return self._parse_id_var() 6765 6766 return self._parse_column() 6767 6768 def _parse_type_size(self) -> exp.DataTypeParam | None: 6769 this = self._parse_type() 6770 if not this: 6771 return None 6772 6773 if isinstance(this, exp.Column) and not this.table: 6774 this = exp.var(this.name.upper()) 6775 6776 return self.expression( 6777 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6778 ) 6779 6780 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6781 type_name = identifier.name 6782 6783 while self._match(TokenType.DOT): 6784 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6785 6786 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6787 6788 def _parse_types( 6789 self, 6790 check_func: bool = False, 6791 schema: bool = False, 6792 allow_identifiers: bool = True, 6793 with_collation: bool = False, 6794 ) -> exp.Expr | None: 6795 index = self._index 6796 this: exp.Expr | None = None 6797 6798 if self._match_set(self.TYPE_TOKENS): 6799 type_token = self._prev.token_type 6800 else: 6801 type_token = None 6802 identifier = allow_identifiers and self._parse_id_var( 6803 any_token=False, tokens=(TokenType.VAR,) 6804 ) 6805 if isinstance(identifier, exp.Identifier): 6806 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6807 this = exp.DataType.build(identifier, udt=True) 6808 else: 6809 try: 6810 tokens = self.dialect.tokenize(identifier.name) 6811 except TokenError: 6812 tokens = None 6813 6814 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6815 if len(tokens) > 1: 6816 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6817 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6818 this = self._parse_user_defined_type(identifier) 6819 else: 6820 self._retreat(self._index - 1) 6821 return None 6822 else: 6823 return None 6824 6825 if type_token == TokenType.PSEUDO_TYPE: 6826 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6827 6828 if type_token == TokenType.OBJECT_IDENTIFIER: 6829 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6830 6831 # https://materialize.com/docs/sql/types/map/ 6832 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6833 key_type = self._parse_types( 6834 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6835 ) 6836 if not self._match(TokenType.FARROW): 6837 self._retreat(index) 6838 return None 6839 6840 value_type = self._parse_types( 6841 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6842 ) 6843 if not self._match(TokenType.R_BRACKET): 6844 self._retreat(index) 6845 return None 6846 6847 return exp.DataType( 6848 this=exp.DType.MAP, 6849 expressions=[key_type, value_type], 6850 nested=True, 6851 ) 6852 6853 nested = type_token in self.NESTED_TYPE_TOKENS 6854 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6855 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6856 expressions = None 6857 maybe_func = False 6858 6859 if self._match(TokenType.L_PAREN): 6860 if is_struct: 6861 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6862 elif nested: 6863 expressions = self._parse_csv( 6864 lambda: self._parse_types( 6865 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6866 ) 6867 ) 6868 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6869 this = expressions[0] 6870 this.set("nullable", True) 6871 self._match_r_paren() 6872 return this 6873 elif type_token in self.ENUM_TYPE_TOKENS: 6874 expressions = self._parse_csv(self._parse_equality) 6875 elif type_token == TokenType.REF: 6876 # REF(schema.customer) names a target; REF(1) can still be a function call. 6877 target = self._try_parse(lambda: self._parse_table_parts(schema=True)) 6878 if target is None: 6879 self._retreat(index) 6880 return None 6881 expressions = [target] 6882 elif type_token == TokenType.JSON: 6883 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6884 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6885 expressions = self._parse_csv(self._parse_json_type_arg) 6886 elif is_aggregate: 6887 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6888 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6889 ) 6890 if not func_or_ident: 6891 return None 6892 expressions = [func_or_ident] 6893 if self._match(TokenType.COMMA): 6894 expressions.extend( 6895 self._parse_csv( 6896 lambda: self._parse_types( 6897 check_func=check_func, 6898 schema=schema, 6899 allow_identifiers=allow_identifiers, 6900 ) 6901 ) 6902 ) 6903 else: 6904 expressions = self._parse_csv(self._parse_type_size) 6905 6906 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6907 if type_token == TokenType.VECTOR and len(expressions) == 2: 6908 expressions = self._parse_vector_expressions(expressions) 6909 6910 if not self._match(TokenType.R_PAREN): 6911 self._retreat(index) 6912 return None 6913 6914 maybe_func = True 6915 6916 values: list[exp.Expr] | None = None 6917 6918 if nested and self._match(TokenType.LT): 6919 if is_struct: 6920 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6921 else: 6922 expressions = self._parse_csv( 6923 lambda: self._parse_types( 6924 check_func=check_func, 6925 schema=schema, 6926 allow_identifiers=allow_identifiers, 6927 with_collation=True, 6928 ) 6929 ) 6930 6931 if not self._match(TokenType.GT): 6932 self.raise_error("Expecting >") 6933 6934 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6935 values = self._parse_csv(self._parse_disjunction) 6936 if not values and is_struct: 6937 values = None 6938 self._retreat(self._index - 1) 6939 else: 6940 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6941 6942 if type_token in self.TIMESTAMPS: 6943 if self._match_text_seq("WITH", "TIME", "ZONE"): 6944 maybe_func = False 6945 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6946 this = exp.DataType(this=tz_type, expressions=expressions) 6947 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6948 maybe_func = False 6949 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6950 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6951 maybe_func = False 6952 elif type_token == TokenType.INTERVAL: 6953 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6954 unit = self._parse_var(upper=True) 6955 if self._match_text_seq("TO"): 6956 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6957 6958 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6959 else: 6960 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6961 elif type_token == TokenType.VOID: 6962 this = exp.DataType(this=exp.DType.NULL) 6963 6964 if maybe_func and check_func: 6965 index2 = self._index 6966 peek = self._parse_string() 6967 6968 if not peek: 6969 self._retreat(index) 6970 return None 6971 6972 self._retreat(index2) 6973 6974 if not this: 6975 assert type_token is not None 6976 if self._match_text_seq("UNSIGNED"): 6977 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6978 if not unsigned_type_token: 6979 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6980 6981 type_token = unsigned_type_token or type_token 6982 6983 # NULLABLE without parentheses can be a column (Presto/Trino) 6984 if type_token == TokenType.NULLABLE and not expressions: 6985 self._retreat(index) 6986 return None 6987 6988 this = exp.DataType( 6989 this=exp.DType[type_token.name], 6990 expressions=expressions, 6991 nested=nested, 6992 ) 6993 6994 # Empty arrays/structs are allowed 6995 if values is not None: 6996 cls = exp.Struct if is_struct else exp.Array 6997 this = exp.cast(cls(expressions=values), this, copy=False) 6998 6999 elif expressions: 7000 this.set("expressions", expressions) 7001 7002 # https://materialize.com/docs/sql/types/list/#type-name 7003 while self._match(TokenType.LIST): 7004 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 7005 7006 index = self._index 7007 7008 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 7009 matched_array = self._match(TokenType.ARRAY) 7010 7011 while self._curr: 7012 datatype_token = self._prev.token_type 7013 matched_l_bracket = self._match(TokenType.L_BRACKET) 7014 7015 if (not matched_l_bracket and not matched_array) or ( 7016 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 7017 ): 7018 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 7019 # not to be confused with the fixed size array parsing 7020 break 7021 7022 matched_array = False 7023 values = self._parse_csv(self._parse_disjunction) or None 7024 if ( 7025 values 7026 and not schema 7027 and ( 7028 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 7029 or datatype_token == TokenType.ARRAY 7030 or not self._match(TokenType.R_BRACKET, advance=False) 7031 ) 7032 ): 7033 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 7034 # 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 7035 self._retreat(index) 7036 break 7037 7038 this = exp.DataType( 7039 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 7040 ) 7041 self._match(TokenType.R_BRACKET) 7042 7043 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 7044 converter = self.TYPE_CONVERTERS.get(this.this) 7045 if converter: 7046 this = converter(t.cast(exp.DataType, this)) 7047 7048 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 7049 this.set("collate", self._parse_identifier() or self._parse_column()) 7050 7051 return this 7052 7053 def _parse_json_type_arg(self) -> exp.Expr | None: 7054 """Parse a single argument to ClickHouse's JSON type.""" 7055 7056 # SKIP col or SKIP REGEXP 'pattern' 7057 if self._match_text_seq("SKIP"): 7058 regexp = self._match(TokenType.RLIKE) 7059 arg = self._parse_column() 7060 if isinstance(arg, exp.Column): 7061 arg = arg.to_dot() 7062 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 7063 7064 param_or_col = self._parse_column() 7065 if not isinstance(param_or_col, exp.Column): 7066 return None 7067 7068 # Parameter: name=value (e.g., max_dynamic_paths=2) 7069 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 7070 param = param_or_col.name 7071 value = self._parse_primary() 7072 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 7073 7074 # Column type hint: col_name Type 7075 col = param_or_col.to_dot() 7076 kind = self._parse_types(check_func=False, allow_identifiers=False) 7077 return self.expression(exp.ColumnDef(this=col, kind=kind)) 7078 7079 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 7080 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 7081 7082 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 7083 index = self._index 7084 7085 if ( 7086 self._curr 7087 and self._next 7088 and self._curr.token_type in self.TYPE_TOKENS 7089 and self._next.token_type in self.TYPE_TOKENS 7090 ): 7091 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 7092 # type token. Without this, the list will be parsed as a type and we'll eventually crash 7093 this = self._parse_id_var() 7094 else: 7095 this = ( 7096 self._parse_type(parse_interval=False, fallback_to_identifier=True) 7097 or self._parse_id_var() 7098 ) 7099 7100 self._match(TokenType.COLON) 7101 7102 if ( 7103 type_required 7104 and not isinstance(this, exp.DataType) 7105 and not self._match_set(self.TYPE_TOKENS, advance=False) 7106 ): 7107 self._retreat(index) 7108 return self._parse_types() 7109 7110 return self._parse_column_def(this) 7111 7112 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 7113 if not self._match_text_seq("AT", "TIME", "ZONE"): 7114 return this 7115 return self._parse_at_time_zone( 7116 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 7117 ) 7118 7119 def _parse_atom(self) -> exp.Expr | None: 7120 if ( 7121 self._curr.token_type in self.IDENTIFIER_TOKENS 7122 and (column := self._parse_column()) is not None 7123 ): 7124 return column 7125 7126 token = self._curr 7127 token_type = token.token_type 7128 7129 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 7130 return None 7131 7132 next_type = self._next.token_type 7133 7134 if ( 7135 next_type in self.COLUMN_OPERATORS 7136 or next_type in self.COLUMN_POSTFIX_TOKENS 7137 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 7138 ): 7139 return None 7140 7141 self._advance() 7142 return primary_parser(self, token) 7143 7144 def _parse_column(self) -> exp.Expr | None: 7145 column: exp.Expr | None = self._parse_column_parts_fast() 7146 if column is None: 7147 this = self._parse_column_reference() 7148 if not this: 7149 this = self._parse_bracket(this) 7150 column = self._parse_column_ops(this) if this else this 7151 7152 if column: 7153 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS and self._match(TokenType.JOIN_MARKER): 7154 column.set("join_mark", True) 7155 if self.COLON_IS_VARIANT_EXTRACT: 7156 column = self._parse_colon_as_variant_extract(column) 7157 7158 return column 7159 7160 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 7161 """Fast path for simple column and dot references (a, a.b, ...). 7162 7163 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 7164 that nothing complex follows. If it does, retreats and returns None so 7165 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 7166 """ 7167 index = self._index 7168 parts: list[exp.Identifier] | None = None 7169 all_comments: list[str] | None = None 7170 7171 while self._match_set(self.IDENTIFIER_TOKENS): 7172 token = self._prev 7173 comments = self._prev_comments 7174 7175 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 7176 self._retreat(index) 7177 return None 7178 7179 has_dot = self._match(TokenType.DOT) 7180 curr_tt = self._curr.token_type 7181 7182 if not has_dot: 7183 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 7184 self._retreat(index) 7185 return None 7186 elif curr_tt not in self.IDENTIFIER_TOKENS: 7187 self._retreat(index) 7188 return None 7189 7190 if parts is None: 7191 parts = [] 7192 7193 if comments: 7194 if all_comments is None: 7195 all_comments = [] 7196 all_comments.extend(comments) 7197 self._prev_comments = [] 7198 7199 parts.append( 7200 self.expression( 7201 exp.Identifier( 7202 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 7203 ), 7204 token, 7205 ) 7206 ) 7207 7208 if not has_dot: 7209 break 7210 7211 if parts is None: 7212 return None 7213 7214 n = len(parts) 7215 7216 if n == 1: 7217 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 7218 elif n == 2: 7219 column = exp.Column(this=parts[1], table=parts[0]) 7220 elif n == 3: 7221 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 7222 else: 7223 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 7224 7225 for i in range(4, n): 7226 column = exp.Dot(this=column, expression=parts[i]) 7227 7228 if all_comments: 7229 column.add_comments(all_comments) 7230 7231 return column 7232 7233 def _parse_column_reference(self) -> exp.Expr | None: 7234 this = self._parse_field() 7235 if ( 7236 not this 7237 and self._match(TokenType.VALUES, advance=False) 7238 and self.VALUES_FOLLOWED_BY_PAREN 7239 and (not self._next or self._next.token_type != TokenType.L_PAREN) 7240 ): 7241 this = self._parse_id_var() 7242 7243 if isinstance(this, exp.Identifier): 7244 # We bubble up comments from the Identifier to the Column 7245 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 7246 7247 return this 7248 7249 def _build_json_extract( 7250 self, 7251 this: exp.Expr | None, 7252 path_parts: list[exp.JSONPathPart], 7253 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 7254 if len(path_parts) > 1: 7255 this = self.expression( 7256 exp.JSONExtract( 7257 this=this, 7258 expression=exp.JSONPath(expressions=path_parts), 7259 variant_extract=True, 7260 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 7261 ) 7262 ) 7263 path_parts = [exp.JSONPathRoot()] 7264 7265 return this, path_parts 7266 7267 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 7268 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 7269 7270 while self._match(TokenType.COLON): 7271 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 7272 this, path_parts = self._build_json_extract(this, path_parts) 7273 7274 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7275 7276 if key: 7277 quoted = isinstance(key, exp.Identifier) and key.quoted 7278 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 7279 7280 while True: 7281 if self._match(TokenType.DOT): 7282 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7283 7284 if next_key: 7285 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 7286 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 7287 elif self._match(TokenType.L_BRACKET): 7288 bracket_expr = self._parse_bracket_key_value() 7289 7290 if not self._match(TokenType.R_BRACKET): 7291 self.raise_error("Expected ]") 7292 7293 if bracket_expr: 7294 if bracket_expr.is_string: 7295 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7296 elif bracket_expr.is_star: 7297 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7298 elif bracket_expr.is_number: 7299 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7300 else: 7301 this, path_parts = self._build_json_extract(this, path_parts) 7302 7303 this = self.expression( 7304 exp.Bracket( 7305 this=this, expressions=[bracket_expr], json_access=True 7306 ), 7307 ) 7308 7309 elif self._match(TokenType.DCOLON): 7310 this, path_parts = self._build_json_extract(this, path_parts) 7311 7312 cast_type = self._parse_types() 7313 if cast_type: 7314 this = self.expression(exp.Cast(this=this, to=cast_type)) 7315 else: 7316 self.raise_error("Expected type after '::'") 7317 else: 7318 break 7319 7320 this, _ = self._build_json_extract(this, path_parts) 7321 7322 return this 7323 7324 def _parse_dcolon(self) -> exp.Expr | None: 7325 return self._parse_types() 7326 7327 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7328 while self._curr.token_type in self.BRACKETS: 7329 this = self._parse_bracket(this) 7330 7331 column_operators = self.COLUMN_OPERATORS 7332 cast_column_operators = self.CAST_COLUMN_OPERATORS 7333 while self._curr: 7334 op_token = self._curr.token_type 7335 7336 if op_token not in column_operators: 7337 break 7338 op = column_operators[op_token] 7339 self._advance() 7340 7341 if op_token in cast_column_operators: 7342 field = self._parse_dcolon() 7343 if not field: 7344 self.raise_error("Expected type") 7345 elif op and self._curr: 7346 field = self._parse_column_reference() or self._parse_bitwise() 7347 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7348 field = self._parse_column_ops(field) 7349 else: 7350 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7351 field = self._parse_field(any_token=True, anonymous_func=True) 7352 7353 # In t.true, t.null we should produce an Identifier node 7354 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7355 field = self.expression( 7356 exp.Identifier(this=self._prev.text), 7357 comments=field.comments, 7358 ) 7359 7360 # Function calls can be qualified, e.g., x.y.FOO() 7361 # This converts the final AST to a series of Dots leading to the function call 7362 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7363 if isinstance(field, (exp.Func, exp.Window)) and this: 7364 this = this.transform( 7365 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7366 ) 7367 7368 if op: 7369 this = op(self, this, field) 7370 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7371 this = self.expression( 7372 exp.Column( 7373 this=field, 7374 table=this.this, 7375 db=this.args.get("table"), 7376 catalog=this.args.get("db"), 7377 ), 7378 comments=this.comments, 7379 ) 7380 elif isinstance(field, exp.Window): 7381 # Move the exp.Dot's to the window's function 7382 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7383 field.set("this", window_func) 7384 this = field 7385 else: 7386 this = self.expression(exp.Dot(this=this, expression=field)) 7387 7388 if field and field.comments: 7389 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7390 7391 this = self._parse_bracket(this) 7392 7393 return this 7394 7395 def _parse_paren(self) -> exp.Expr | None: 7396 if not self._match(TokenType.L_PAREN): 7397 return None 7398 7399 comments = self._prev_comments 7400 query = self._parse_select() 7401 7402 if query: 7403 expressions = [query] 7404 else: 7405 expressions = self._parse_expressions() 7406 7407 this = seq_get(expressions, 0) 7408 7409 if not this and self._match(TokenType.R_PAREN, advance=False): 7410 this = self.expression(exp.Tuple()) 7411 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7412 this = self.expression(exp.Tuple(expressions=expressions)) 7413 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7414 this = self._parse_subquery(this=this, parse_alias=False) 7415 elif isinstance(this, (exp.Subquery, exp.Values)): 7416 this = self._parse_subquery( 7417 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7418 parse_alias=False, 7419 ) 7420 else: 7421 this = self.expression(exp.Paren(this=this)) 7422 7423 if this: 7424 this.add_comments(comments) 7425 7426 self._match_r_paren(expression=this) 7427 7428 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7429 return self._parse_window(this) 7430 7431 return this 7432 7433 def _parse_primary(self) -> exp.Expr | None: 7434 if self._match_set(self.PRIMARY_PARSERS): 7435 token_type = self._prev.token_type 7436 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7437 7438 if token_type == TokenType.STRING: 7439 expressions = [primary] 7440 while self._match(TokenType.STRING, advance=False): 7441 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7442 self.raise_error( 7443 "Adjacent string literals need to be separated by whitespace or comments" 7444 ) 7445 7446 self._advance() 7447 expressions.append(exp.Literal.string(self._prev.text)) 7448 7449 if len(expressions) > 1: 7450 return self.expression( 7451 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7452 ) 7453 7454 return primary 7455 7456 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7457 return exp.Literal.number(f"0.{self._prev.text}") 7458 7459 return self._parse_paren() 7460 7461 def _parse_field( 7462 self, 7463 any_token: bool = False, 7464 tokens: t.Collection[TokenType] | None = None, 7465 anonymous_func: bool = False, 7466 ) -> exp.Expr | None: 7467 after_dot = ( 7468 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7469 ) 7470 7471 if anonymous_func: 7472 field = ( 7473 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7474 or self._parse_primary() 7475 ) 7476 else: 7477 field = self._parse_primary() or self._parse_function( 7478 anonymous=anonymous_func, any_token=any_token 7479 ) 7480 7481 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7482 7483 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7484 name = field.name 7485 if self._is_connected() and self._parse_var(any_token=True): 7486 name += self._prev.text 7487 7488 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7489 7490 return field 7491 7492 def _parse_function( 7493 self, 7494 functions: dict[str, t.Callable] | None = None, 7495 anonymous: bool = False, 7496 optional_parens: bool = True, 7497 any_token: bool = False, 7498 ) -> exp.Expr | None: 7499 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7500 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7501 fn_syntax = False 7502 if ( 7503 self._match(TokenType.L_BRACE, advance=False) 7504 and self._next 7505 and self._next.text.upper() == "FN" 7506 ): 7507 self._advance(2) 7508 fn_syntax = True 7509 7510 func = self._parse_function_call( 7511 functions=functions, 7512 anonymous=anonymous, 7513 optional_parens=optional_parens, 7514 any_token=any_token, 7515 ) 7516 7517 if fn_syntax: 7518 self._match(TokenType.R_BRACE) 7519 7520 return func 7521 7522 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7523 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7524 7525 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7526 args = self._parse_function_args(alias=False) 7527 if not args: 7528 self.raise_error("Expected at least one argument") 7529 7530 # Wrapped so the connector keeps its precedence in the parent context 7531 return exp.Paren(this=connector(*args, copy=False)) 7532 7533 def _parse_function_call( 7534 self, 7535 functions: dict[str, t.Callable] | None = None, 7536 anonymous: bool = False, 7537 optional_parens: bool = True, 7538 any_token: bool = False, 7539 ) -> exp.Expr | None: 7540 if not self._curr: 7541 return None 7542 7543 comments = self._curr.comments 7544 prev = self._prev 7545 token = self._curr 7546 token_type = self._curr.token_type 7547 this: str | exp.Expr = self._curr.text 7548 upper = self._curr.text.upper() 7549 7550 after_dot = prev.token_type == TokenType.DOT 7551 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7552 if ( 7553 optional_parens 7554 and parser 7555 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7556 and not after_dot 7557 ): 7558 self._advance() 7559 return self._parse_window(parser(self)) 7560 7561 if self._next.token_type != TokenType.L_PAREN: 7562 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7563 self._advance() 7564 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7565 7566 return None 7567 7568 if any_token: 7569 if token_type in self.RESERVED_TOKENS: 7570 return None 7571 elif token_type not in self.FUNC_TOKENS: 7572 return None 7573 7574 self._advance(2) 7575 7576 parser = self.FUNCTION_PARSERS.get(upper) 7577 if parser and not anonymous: 7578 result = parser(self) 7579 else: 7580 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7581 7582 if subquery_predicate: 7583 expr = None 7584 if self._curr.token_type in self.SUBQUERY_TOKENS: 7585 expr = self._parse_select() 7586 self._match_r_paren() 7587 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7588 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7589 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7590 self._advance(-1) 7591 expr = self._parse_bitwise() 7592 7593 if expr: 7594 return self.expression(subquery_predicate(this=expr), comments=comments) 7595 7596 if functions is None: 7597 functions = self.FUNCTIONS 7598 7599 function = functions.get(upper) 7600 known_function = function and not anonymous 7601 7602 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7603 args = self._parse_function_args(alias) 7604 7605 post_func_comments = self._curr.comments if self._curr else None 7606 if known_function and post_func_comments: 7607 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7608 # call we'll construct it as exp.Anonymous, even if it's "known" 7609 if any( 7610 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7611 for comment in post_func_comments 7612 ): 7613 known_function = False 7614 7615 if alias and known_function: 7616 args = self._kv_to_prop_eq(args) 7617 7618 if known_function: 7619 func_builder = t.cast(t.Callable, function) 7620 7621 # mypyc compiled functions don't have __code__, so we use 7622 # try/except to check if func_builder accepts 'dialect'. 7623 try: 7624 func = func_builder(args) 7625 except TypeError: 7626 func = func_builder(args, dialect=self.dialect) 7627 7628 func = self.validate_expression(func, args) 7629 if name_key := self.dialect.ORIGINAL_NAME_META_KEY: 7630 func.meta[name_key] = this 7631 7632 result = func 7633 else: 7634 if token_type == TokenType.IDENTIFIER: 7635 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7636 7637 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7638 7639 result = result.update_positions(token) 7640 7641 if isinstance(result, exp.Expr): 7642 result.add_comments(comments) 7643 7644 if parser: 7645 self._match(TokenType.R_PAREN, expression=result) 7646 else: 7647 self._match_r_paren(result) 7648 return self._parse_window(result) 7649 7650 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7651 return expression 7652 7653 def _kv_to_prop_eq( 7654 self, expressions: list[exp.Expr], parse_map: bool = False 7655 ) -> list[exp.Expr]: 7656 transformed = [] 7657 7658 for index, e in enumerate(expressions): 7659 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7660 if isinstance(e, exp.Alias): 7661 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7662 7663 if not isinstance(e, exp.PropertyEQ): 7664 e = self.expression( 7665 exp.PropertyEQ( 7666 this=e.this if parse_map else exp.to_identifier(e.this.name), 7667 expression=e.expression, 7668 ) 7669 ) 7670 7671 if not parse_map and isinstance(e.this, exp.Column): 7672 e.this.replace(e.this.this) 7673 else: 7674 e = self._to_prop_eq(e, index) 7675 7676 transformed.append(e) 7677 7678 return transformed 7679 7680 def _parse_function_properties(self) -> exp.Properties | None: 7681 # Skip the generic `key = value` fallback in _parse_property since this 7682 # runs post-AS where a function body like `name = expr` can be misread 7683 # as a property. 7684 properties = [] 7685 while True: 7686 if self._match_texts(self.PROPERTY_PARSERS): 7687 keyword = self._prev.text.upper() 7688 prop = self.PROPERTY_PARSERS[keyword](self) 7689 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7690 keyword = self._prev.text.upper() 7691 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7692 else: 7693 break 7694 if not prop: 7695 self.raise_error(f"Failed to parse property '{keyword}'") 7696 break 7697 for p in ensure_list(prop): 7698 properties.append(p) 7699 7700 return self.expression(exp.Properties(expressions=properties)) if properties else None 7701 7702 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7703 return self._parse_statement() 7704 7705 def _parse_function_parameter(self) -> exp.Expr | None: 7706 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7707 7708 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7709 this = self._parse_table_parts(schema=True) 7710 7711 if not self._match(TokenType.L_PAREN): 7712 return this 7713 7714 expressions = self._parse_csv(self._parse_function_parameter) 7715 self._match_r_paren() 7716 return self.expression( 7717 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7718 ) 7719 7720 def _parse_macro_overloads( 7721 self, 7722 this: exp.UserDefinedFunction, 7723 first_body: exp.Expr, 7724 first_is_table: bool = False, 7725 ) -> exp.MacroOverloads: 7726 overloads = [ 7727 self.expression( 7728 exp.MacroOverload( 7729 this=first_body, 7730 expressions=this.expressions or None, 7731 is_table=first_is_table, 7732 ) 7733 ) 7734 ] 7735 this.set("expressions", None) 7736 this.set("wrapped", False) 7737 7738 while self._match(TokenType.COMMA): 7739 if not self._match(TokenType.L_PAREN): 7740 break 7741 7742 params = self._parse_csv(self._parse_function_parameter) 7743 self._match_r_paren() 7744 7745 if not self._match(TokenType.ALIAS): 7746 break 7747 7748 is_table = self._match(TokenType.TABLE) 7749 body = self._parse_expression() 7750 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7751 overloads.append(self.expression(macro)) 7752 7753 return self.expression(exp.MacroOverloads(expressions=overloads)) 7754 7755 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7756 literal = self._parse_primary() 7757 if literal: 7758 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7759 7760 return self._identifier_expression(token) 7761 7762 def _parse_session_parameter(self) -> exp.SessionParameter: 7763 kind = None 7764 this = self._parse_id_var() or self._parse_primary() 7765 7766 if this and self._match(TokenType.DOT): 7767 kind = this.name 7768 this = self._parse_var() or self._parse_primary() 7769 7770 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7771 7772 def _parse_lambda_arg(self) -> exp.Expr | None: 7773 return self._parse_id_var() 7774 7775 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7776 next_token_type = self._next.token_type 7777 7778 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7779 if ( 7780 next_token_type in self.LAMBDA_ARG_TERMINATORS 7781 and (atom := self._parse_atom()) is not None 7782 ): 7783 return atom 7784 7785 index = self._index 7786 7787 if self._match(TokenType.L_PAREN): 7788 expressions = t.cast( 7789 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7790 ) 7791 7792 if not self._match(TokenType.R_PAREN): 7793 self._retreat(index) 7794 elif self._match_set(self.LAMBDAS): 7795 return self.LAMBDAS[self._prev.token_type](self, expressions) 7796 else: 7797 self._retreat(index) 7798 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7799 expressions = [self._parse_lambda_arg()] 7800 7801 if self._match_set(self.LAMBDAS): 7802 return self.LAMBDAS[self._prev.token_type](self, expressions) 7803 7804 self._retreat(index) 7805 7806 this: exp.Expr | None 7807 7808 if self._match(TokenType.DISTINCT): 7809 this = self.expression( 7810 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7811 ) 7812 else: 7813 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7814 this = self._parse_select_or_expression(alias=alias) 7815 7816 return self._parse_limit( 7817 self._parse_respect_or_ignore_nulls( 7818 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7819 ) 7820 ) 7821 7822 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7823 index = self._index 7824 if not self._match(TokenType.L_PAREN): 7825 return this 7826 7827 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7828 # expr can be of both types 7829 if self._match_set(self.SELECT_START_TOKENS): 7830 self._retreat(index) 7831 return this 7832 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7833 self._match_r_paren() 7834 return self.expression(exp.Schema(this=this, expressions=args)) 7835 7836 def _parse_field_def(self) -> exp.Expr | None: 7837 return self._parse_column_def(self._parse_field(any_token=True)) 7838 7839 def _parse_column_def( 7840 self, this: exp.Expr | None, computed_column: bool = True 7841 ) -> exp.Expr | None: 7842 # column defs are not really columns, they're identifiers 7843 if isinstance(this, exp.Column): 7844 this = this.this 7845 7846 if not computed_column: 7847 self._match(TokenType.ALIAS) 7848 7849 kind = self._parse_types(schema=True) 7850 7851 if self._match_text_seq("FOR", "ORDINALITY"): 7852 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7853 7854 constraints: list[exp.Expr] = [] 7855 7856 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7857 ("ALIAS", "MATERIALIZED") 7858 ): 7859 # Match storage before _parse_types so STORED is not treated as a data type 7860 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7861 persisted = self._prev.text.upper() == "MATERIALIZED" 7862 expression = self._parse_disjunction() 7863 if not persisted: 7864 if self._match_text_seq("PERSISTED"): 7865 persisted = True 7866 elif self._match_texts(("STORED", "VIRTUAL")): 7867 persisted = self._prev.text.upper() == "STORED" 7868 constraint_kind = exp.ComputedColumnConstraint( 7869 this=expression, 7870 persisted=persisted, 7871 data_type=exp.Var(this="AUTO") 7872 if self._match_text_seq("AUTO") 7873 else self._parse_types(), 7874 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7875 ) 7876 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7877 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7878 in_out_constraint = self.expression( 7879 exp.InOutColumnConstraint( 7880 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7881 ) 7882 ) 7883 constraints.append(in_out_constraint) 7884 kind = self._parse_types() 7885 elif ( 7886 kind 7887 and self._match(TokenType.ALIAS, advance=False) 7888 and ( 7889 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7890 or self._next.token_type == TokenType.L_PAREN 7891 ) 7892 ): 7893 self._advance() 7894 constraints.append( 7895 self.expression( 7896 exp.ColumnConstraint( 7897 kind=exp.ComputedColumnConstraint( 7898 this=self._parse_disjunction(), 7899 persisted=self._match_texts(("STORED", "VIRTUAL")) 7900 and self._prev.text.upper() == "STORED", 7901 ) 7902 ) 7903 ) 7904 ) 7905 7906 while True: 7907 constraint = self._parse_column_constraint() 7908 if not constraint: 7909 break 7910 constraints.append(constraint) 7911 7912 if not kind and not constraints: 7913 return this 7914 7915 position = None 7916 if self._match_texts(("FIRST", "AFTER")): 7917 pos = self._prev.text 7918 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7919 7920 return self.expression( 7921 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7922 ) 7923 7924 def _parse_auto_increment( 7925 self, 7926 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7927 start = None 7928 increment = None 7929 order = None 7930 7931 if self._match(TokenType.L_PAREN, advance=False): 7932 args = self._parse_wrapped_csv(self._parse_bitwise) 7933 start = seq_get(args, 0) 7934 increment = seq_get(args, 1) 7935 7936 # The remaining parts form an unordered bag and any of them can be omitted, in which 7937 # case the engine falls back to its own default, so they're parsed independently. 7938 while True: 7939 if self._match_text_seq("START"): 7940 start = self._parse_bitwise() 7941 elif self._match_text_seq("INCREMENT"): 7942 increment = self._parse_bitwise() 7943 elif self._match_text_seq("ORDER"): 7944 order = True 7945 elif self._match_text_seq("NOORDER"): 7946 order = False 7947 else: 7948 break 7949 7950 if start or increment or order is not None: 7951 return exp.GeneratedAsIdentityColumnConstraint( 7952 start=start, increment=increment, this=False, order=order 7953 ) 7954 7955 return exp.AutoIncrementColumnConstraint() 7956 7957 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7958 if not self._match(TokenType.L_PAREN, advance=False): 7959 return None 7960 7961 return self.expression( 7962 exp.CheckColumnConstraint( 7963 this=self._parse_wrapped(self._parse_assignment), 7964 enforced=self._match_text_seq("ENFORCED"), 7965 ) 7966 ) 7967 7968 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7969 if not self._match_text_seq("REFRESH"): 7970 self._retreat(self._index - 1) 7971 return None 7972 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7973 7974 def _parse_compress(self) -> exp.CompressColumnConstraint: 7975 if self._match(TokenType.L_PAREN, advance=False): 7976 return self.expression( 7977 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7978 ) 7979 7980 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7981 7982 def _parse_generated_as_identity( 7983 self, 7984 ) -> ( 7985 exp.GeneratedAsIdentityColumnConstraint 7986 | exp.ComputedColumnConstraint 7987 | exp.GeneratedAsRowColumnConstraint 7988 ): 7989 if self._match_text_seq("BY", "DEFAULT"): 7990 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7991 this = self.expression( 7992 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7993 ) 7994 else: 7995 self._match_text_seq("ALWAYS") 7996 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7997 7998 self._match(TokenType.ALIAS) 7999 8000 if self._match_text_seq("ROW"): 8001 start = self._match_text_seq("START") 8002 if not start: 8003 self._match(TokenType.END) 8004 hidden = self._match_text_seq("HIDDEN") 8005 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 8006 8007 identity = self._match_text_seq("IDENTITY") 8008 8009 if self._match(TokenType.L_PAREN): 8010 if self._match_text_seq("START", "WITH"): 8011 this.set("start", self._parse_bitwise()) 8012 if self._match_text_seq("INCREMENT", "BY"): 8013 this.set("increment", self._parse_bitwise()) 8014 if self._match_text_seq("MINVALUE"): 8015 this.set("minvalue", self._parse_bitwise()) 8016 if self._match_text_seq("MAXVALUE"): 8017 this.set("maxvalue", self._parse_bitwise()) 8018 8019 if self._match_text_seq("CYCLE"): 8020 this.set("cycle", True) 8021 elif self._match_text_seq("NO", "CYCLE"): 8022 this.set("cycle", False) 8023 8024 if not identity: 8025 this.set("expression", self._parse_disjunction()) 8026 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 8027 args = self._parse_csv(self._parse_bitwise) 8028 this.set("start", seq_get(args, 0)) 8029 this.set("increment", seq_get(args, 1)) 8030 8031 self._match_r_paren() 8032 8033 return this 8034 8035 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 8036 self._match_text_seq("LENGTH") 8037 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 8038 8039 def _parse_not_constraint(self) -> exp.Expr | None: 8040 if self._match_text_seq("NULL"): 8041 return self.expression(exp.NotNullColumnConstraint()) 8042 if self._match_text_seq("CASESPECIFIC"): 8043 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 8044 if self._match_text_seq("FOR", "REPLICATION"): 8045 return self.expression(exp.NotForReplicationColumnConstraint()) 8046 8047 # Unconsume the `NOT` token 8048 self._retreat(self._index - 1) 8049 return None 8050 8051 def _parse_column_constraint(self) -> exp.Expr | None: 8052 this = ( 8053 self._parse_id_var() 8054 if self._match(TokenType.CONSTRAINT) 8055 and not self._match_texts(self.RESERVED_CONSTRAINT_KINDS, advance=False) 8056 else None 8057 ) 8058 8059 procedure_option_follows = ( 8060 self._match(TokenType.WITH, advance=False) 8061 and self._next 8062 and self._next.text.upper() in self.PROCEDURE_OPTIONS 8063 ) 8064 8065 index = self._index 8066 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 8067 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 8068 if not constraint: 8069 self._retreat(index) 8070 return None 8071 8072 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 8073 8074 if self._match(TokenType.CHARACTER_SET) or self._match_pair(TokenType.CHAR, TokenType.SET): 8075 return self.expression( 8076 exp.ColumnConstraint( 8077 this=this, 8078 kind=self.expression( 8079 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 8080 ), 8081 ) 8082 ) 8083 8084 return this 8085 8086 def _parse_constraint(self) -> exp.Expr | None: 8087 if not self._match(TokenType.CONSTRAINT) or self._match_texts( 8088 self.RESERVED_CONSTRAINT_KINDS, advance=False 8089 ): 8090 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 8091 8092 return self.expression( 8093 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 8094 ) 8095 8096 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 8097 constraints = [] 8098 while True: 8099 constraint = self._parse_unnamed_constraint() or self._parse_function() 8100 if not constraint: 8101 break 8102 constraints.append(constraint) 8103 8104 return constraints 8105 8106 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 8107 index = self._index 8108 8109 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 8110 constraints or self.CONSTRAINT_PARSERS 8111 ): 8112 return None 8113 8114 constraint_key = self._prev.text.upper() 8115 if constraint_key not in self.CONSTRAINT_PARSERS: 8116 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 8117 8118 result = self.CONSTRAINT_PARSERS[constraint_key](self) 8119 if not result: 8120 self._retreat(index) 8121 8122 return result 8123 8124 def _parse_unique_key(self) -> exp.Expr | None: 8125 if ( 8126 self._curr 8127 and self._curr.token_type != TokenType.IDENTIFIER 8128 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 8129 ): 8130 return None 8131 return self._parse_id_var(any_token=False) 8132 8133 def _parse_unique(self) -> exp.UniqueColumnConstraint: 8134 self._match_texts(("KEY", "INDEX")) 8135 return self.expression( 8136 exp.UniqueColumnConstraint( 8137 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 8138 this=self._parse_schema(self._parse_unique_key()), 8139 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 8140 on_conflict=self._parse_on_conflict(), 8141 options=self._parse_key_constraint_options(), 8142 ) 8143 ) 8144 8145 def _parse_key_constraint_options(self) -> list[str]: 8146 options = [] 8147 while True: 8148 if not self._curr: 8149 break 8150 8151 if self._match(TokenType.ON): 8152 action = None 8153 on = self._advance_any() and self._prev.text 8154 8155 if self._match_text_seq("NO", "ACTION"): 8156 action = "NO ACTION" 8157 elif self._match_text_seq("CASCADE"): 8158 action = "CASCADE" 8159 elif self._match_text_seq("RESTRICT"): 8160 action = "RESTRICT" 8161 elif self._match_pair(TokenType.SET, TokenType.NULL): 8162 action = "SET NULL" 8163 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 8164 action = "SET DEFAULT" 8165 else: 8166 self.raise_error("Invalid key constraint") 8167 8168 options.append(f"ON {on} {action}") 8169 else: 8170 var = self._parse_var_from_options( 8171 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 8172 ) 8173 if not var: 8174 break 8175 options.append(var.name) 8176 8177 return options 8178 8179 def _parse_references(self, match: bool = True) -> exp.Reference | None: 8180 if match and not self._match(TokenType.REFERENCES): 8181 return None 8182 8183 expressions: list | None = None 8184 this = self._parse_table(schema=True) 8185 options = self._parse_key_constraint_options() 8186 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 8187 8188 def _parse_foreign_key(self) -> exp.ForeignKey: 8189 expressions = ( 8190 self._parse_wrapped_id_vars() 8191 if not self._match(TokenType.REFERENCES, advance=False) 8192 else None 8193 ) 8194 reference = self._parse_references() 8195 on_options = {} 8196 8197 while self._match(TokenType.ON): 8198 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 8199 self.raise_error("Expected DELETE or UPDATE") 8200 8201 kind = self._prev.text.lower() 8202 8203 if self._match_text_seq("NO", "ACTION"): 8204 action = "NO ACTION" 8205 elif self._match(TokenType.SET): 8206 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 8207 action = "SET " + self._prev.text.upper() 8208 else: 8209 self._advance() 8210 action = self._prev.text.upper() 8211 8212 on_options[kind] = action 8213 8214 return self.expression( 8215 exp.ForeignKey( 8216 expressions=expressions, 8217 reference=reference, 8218 options=self._parse_key_constraint_options(), 8219 **on_options, 8220 ) 8221 ) 8222 8223 def _parse_primary_key_part(self) -> exp.Expr | None: 8224 return self._parse_field() 8225 8226 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 8227 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 8228 self._retreat(self._index - 1) 8229 return None 8230 8231 id_vars = self._parse_wrapped_id_vars() 8232 return self.expression( 8233 exp.PeriodForSystemTimeConstraint( 8234 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 8235 ) 8236 ) 8237 8238 def _parse_primary_key( 8239 self, 8240 wrapped_optional: bool = False, 8241 in_props: bool = False, 8242 named_primary_key: bool = False, 8243 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 8244 desc = ( 8245 self._prev.token_type == TokenType.DESC 8246 if self._match_set((TokenType.ASC, TokenType.DESC)) 8247 else None 8248 ) 8249 8250 this = None 8251 if ( 8252 named_primary_key 8253 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 8254 and self._next 8255 and self._next.token_type == TokenType.L_PAREN 8256 ): 8257 this = self._parse_id_var() 8258 8259 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 8260 return self.expression( 8261 exp.PrimaryKeyColumnConstraint( 8262 desc=desc, options=self._parse_key_constraint_options() 8263 ) 8264 ) 8265 8266 expressions = self._parse_wrapped_csv( 8267 self._parse_primary_key_part, optional=wrapped_optional 8268 ) 8269 8270 return self.expression( 8271 exp.PrimaryKey( 8272 this=this, 8273 expressions=expressions, 8274 include=self._parse_index_params(), 8275 options=self._parse_key_constraint_options(), 8276 ) 8277 ) 8278 8279 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 8280 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 8281 8282 def _parse_odbc_datetime_literal(self) -> exp.Expr: 8283 """ 8284 Parses a datetime column in ODBC format. We parse the column into the corresponding 8285 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 8286 same as we did for `DATE('yyyy-mm-dd')`. 8287 8288 Reference: 8289 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 8290 """ 8291 self._match(TokenType.VAR) 8292 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 8293 expression = self.expression(exp_class(this=self._parse_string())) 8294 if not self._match(TokenType.R_BRACE): 8295 self.raise_error("Expected }") 8296 return expression 8297 8298 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 8299 if not self._match_set(self.BRACKETS): 8300 return this 8301 8302 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8303 map_token = seq_get(self._tokens, self._index - 2) 8304 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8305 else: 8306 parse_map = False 8307 8308 bracket_kind = self._prev.token_type 8309 if ( 8310 bracket_kind == TokenType.L_BRACE 8311 and self._curr 8312 and self._curr.token_type == TokenType.VAR 8313 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8314 ): 8315 return self._parse_odbc_datetime_literal() 8316 8317 expressions = self._parse_csv( 8318 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8319 ) 8320 8321 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8322 self.raise_error("Expected ]") 8323 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8324 self.raise_error("Expected }") 8325 8326 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8327 if bracket_kind == TokenType.L_BRACE: 8328 this = self.expression( 8329 exp.Struct( 8330 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8331 ) 8332 ) 8333 elif not this: 8334 this = build_array_constructor( 8335 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8336 ) 8337 else: 8338 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8339 if constructor_type: 8340 return build_array_constructor( 8341 constructor_type, 8342 args=expressions, 8343 bracket_kind=bracket_kind, 8344 dialect=self.dialect, 8345 ) 8346 8347 expressions = apply_index_offset( 8348 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8349 ) 8350 this = self.expression( 8351 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8352 ) 8353 8354 self._add_comments(this) 8355 return self._parse_bracket(this) 8356 8357 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8358 if not self._match(TokenType.COLON): 8359 return this 8360 8361 # DuckDB's `-` in `[a:-:step]` is the default end, which depends on the sign of step 8362 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8363 self._advance() 8364 end: exp.Expr | None = None 8365 else: 8366 end = self._parse_assignment() 8367 step = self._parse_unary() if self._match(TokenType.COLON) else None 8368 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8369 8370 def _parse_case(self) -> exp.Expr | None: 8371 if self._match(TokenType.DOT, advance=False): 8372 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8373 self._retreat(self._index - 1) 8374 return None 8375 8376 ifs = [] 8377 default = None 8378 8379 comments = self._prev_comments 8380 expression = self._parse_disjunction() 8381 8382 while self._match(TokenType.WHEN): 8383 this = self._parse_disjunction() 8384 self._match(TokenType.THEN) 8385 then = self._parse_disjunction() 8386 ifs.append(self.expression(exp.If(this=this, true=then))) 8387 8388 if self._match(TokenType.ELSE): 8389 default = self._parse_disjunction() 8390 8391 if not self._match(TokenType.END): 8392 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8393 default = exp.column("interval") 8394 else: 8395 self.raise_error("Expected END after CASE", self._prev) 8396 8397 return self.expression( 8398 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8399 ) 8400 8401 def _parse_if(self) -> exp.Expr | None: 8402 if self._match(TokenType.L_PAREN): 8403 args = self._parse_csv( 8404 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8405 ) 8406 this = self.validate_expression(exp.If.from_arg_list(args), args) 8407 self._match_r_paren() 8408 else: 8409 index = self._index - 1 8410 8411 if self.NO_PAREN_IF_COMMANDS and index == 0: 8412 return self._parse_as_command(self._prev) 8413 8414 condition = self._parse_disjunction() 8415 8416 if not condition or not self._match(TokenType.THEN): 8417 self._retreat(index) 8418 return None 8419 8420 true = self._parse_disjunction() 8421 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8422 self._match(TokenType.END) 8423 this = self.expression(exp.If(this=condition, true=true, false=false)) 8424 8425 return this 8426 8427 def _parse_next_value_for(self) -> exp.Expr | None: 8428 if not self._match_text_seq("VALUE", "FOR"): 8429 self._retreat(self._index - 1) 8430 return None 8431 8432 return self.expression( 8433 exp.NextValueFor( 8434 this=self._parse_column(), 8435 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8436 ) 8437 ) 8438 8439 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8440 this = self._parse_function() or self._parse_var_or_string(upper=True) 8441 8442 if self._match(TokenType.FROM): 8443 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8444 8445 if not self._match(TokenType.COMMA): 8446 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8447 8448 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8449 8450 def _parse_gap_fill(self) -> exp.GapFill: 8451 self._match(TokenType.TABLE) 8452 this = self._parse_table() 8453 8454 self._match(TokenType.COMMA) 8455 args = [this, *self._parse_csv(self._parse_lambda)] 8456 8457 gap_fill = exp.GapFill.from_arg_list(args) 8458 return self.validate_expression(gap_fill, args) 8459 8460 def _parse_char(self) -> exp.Chr: 8461 return self.expression( 8462 exp.Chr( 8463 expressions=self._parse_csv(self._parse_assignment), 8464 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8465 ) 8466 ) 8467 8468 def _parse_charset_name(self) -> exp.Expr | None: 8469 """ 8470 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8471 for specific name shapes override this. 8472 """ 8473 return self._parse_var( 8474 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8475 ) 8476 8477 def _parse_weight_string(self) -> exp.WeightString | exp.Anonymous: 8478 name = self._tokens[self._index - 2].text 8479 args = self._parse_csv(self._parse_assignment) 8480 8481 # MySQL also accepts an undocumented WEIGHT_STRING(str, n, n, n) form 8482 if len(args) != 1: 8483 return self.expression(exp.Anonymous(this=name, expressions=args)) 8484 8485 return self.expression( 8486 exp.WeightString(this=args[0], to=self._match(TokenType.ALIAS) and self._parse_types()) 8487 ) 8488 8489 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8490 this = self._parse_assignment() 8491 8492 if not self._match(TokenType.ALIAS): 8493 if self._match(TokenType.COMMA): 8494 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8495 8496 self.raise_error("Expected AS after CAST") 8497 8498 fmt = None 8499 to = self._parse_types(with_collation=True) 8500 8501 default = None 8502 if self._match(TokenType.DEFAULT): 8503 default = self._parse_bitwise() 8504 self._match_text_seq("ON", "CONVERSION", "ERROR") 8505 8506 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8507 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8508 fmt = self._parse_at_time_zone(fmt_string) 8509 8510 if not to: 8511 to = exp.DType.UNKNOWN.into_expr() 8512 if to.this in exp.DataType.TEMPORAL_TYPES: 8513 this = self.expression( 8514 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8515 this=this, 8516 format=exp.Literal.string( 8517 format_time( 8518 fmt_string.this if fmt_string else "", 8519 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8520 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8521 ) 8522 ), 8523 safe=safe, 8524 ) 8525 ) 8526 8527 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8528 this.set("zone", fmt.args["zone"]) 8529 return this 8530 elif not to: 8531 self.raise_error("Expected TYPE after CAST") 8532 elif isinstance(to, exp.Identifier): 8533 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8534 else: 8535 to = self._parse_char_charset(to) 8536 8537 return self.build_cast( 8538 strict=strict, 8539 this=this, 8540 to=to, 8541 format=fmt, 8542 safe=safe, 8543 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8544 default=default, 8545 ) 8546 8547 def _parse_string_agg(self) -> exp.GroupConcat: 8548 if self._match(TokenType.DISTINCT): 8549 args: list[exp.Expr | None] = [ 8550 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8551 ] 8552 if self._match(TokenType.COMMA): 8553 args.extend(self._parse_csv(self._parse_disjunction)) 8554 else: 8555 self._match(TokenType.ALL) 8556 args = self._parse_csv(self._parse_disjunction) # type: ignore 8557 8558 if self._match_text_seq("ON", "OVERFLOW"): 8559 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8560 if self._match_text_seq("ERROR"): 8561 on_overflow: exp.Expr | None = exp.var("ERROR") 8562 else: 8563 self._match_text_seq("TRUNCATE") 8564 on_overflow = self.expression( 8565 exp.OverflowTruncateBehavior( 8566 this=self._parse_string(), 8567 with_count=( 8568 self._match_text_seq("WITH", "COUNT") 8569 or not self._match_text_seq("WITHOUT", "COUNT") 8570 ), 8571 ) 8572 ) 8573 else: 8574 on_overflow = None 8575 8576 index = self._index 8577 if not self._match(TokenType.R_PAREN) and args: 8578 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8579 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8580 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8581 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8582 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8583 8584 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8585 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8586 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8587 if not self._match_text_seq("WITHIN", "GROUP"): 8588 self._retreat(index) 8589 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8590 8591 # The corresponding match_r_paren will be called in parse_function (caller) 8592 self._match_l_paren() 8593 8594 return self.expression( 8595 exp.GroupConcat( 8596 this=self._parse_order(this=seq_get(args, 0)), 8597 separator=seq_get(args, 1), 8598 on_overflow=on_overflow, 8599 ) 8600 ) 8601 8602 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8603 this = self._parse_bitwise() 8604 8605 if self._match(TokenType.USING): 8606 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8607 elif self._match(TokenType.COMMA): 8608 to = self._parse_char_charset(self._parse_types()) 8609 else: 8610 to = None 8611 8612 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8613 8614 def _parse_char_charset(self, to: exp.Expr | None) -> exp.Expr | None: 8615 # MySQL: CHAR[(N)] CHARACTER SET charset_name, the length is kept 8616 if ( 8617 isinstance(to, exp.DataType) 8618 and to.this == exp.DType.CHAR 8619 and (self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")) 8620 ): 8621 to.set("this", exp.DType.CHARACTER_SET) 8622 to.set("kind", self._parse_var_or_string()) 8623 return to 8624 8625 def _parse_xml_element(self) -> exp.XMLElement: 8626 if self._match_text_seq("EVALNAME"): 8627 evalname = True 8628 this = self._parse_bitwise() 8629 else: 8630 evalname = None 8631 self._match_text_seq("NAME") 8632 this = self._parse_id_var() 8633 8634 return self.expression( 8635 exp.XMLElement( 8636 this=this, 8637 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8638 evalname=evalname, 8639 ) 8640 ) 8641 8642 def _parse_xml_table(self) -> exp.XMLTable: 8643 namespaces = None 8644 passing = None 8645 columns = None 8646 8647 if self._match_text_seq("XMLNAMESPACES", "("): 8648 namespaces = self._parse_xml_namespace() 8649 self._match_text_seq(")", ",") 8650 8651 this = self._parse_string() 8652 8653 if self._match_text_seq("PASSING"): 8654 # The BY VALUE keywords are optional and are provided for semantic clarity 8655 self._match_text_seq("BY", "VALUE") 8656 passing = self._parse_csv(self._parse_column) 8657 8658 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8659 8660 if self._match_text_seq("COLUMNS"): 8661 columns = self._parse_csv(self._parse_field_def) 8662 8663 return self.expression( 8664 exp.XMLTable( 8665 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8666 ) 8667 ) 8668 8669 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8670 namespaces = [] 8671 8672 while True: 8673 if self._match(TokenType.DEFAULT): 8674 uri = self._parse_string() 8675 else: 8676 uri = self._parse_alias(self._parse_string()) 8677 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8678 if not self._match(TokenType.COMMA): 8679 break 8680 8681 return namespaces 8682 8683 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8684 args = self._parse_csv(self._parse_disjunction) 8685 8686 if len(args) < 3: 8687 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8688 8689 return self.expression(exp.DecodeCase(expressions=args)) 8690 8691 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8692 self._match_text_seq("KEY") 8693 key = self._parse_column() 8694 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8695 self._match_text_seq("VALUE") 8696 value = self._parse_bitwise() 8697 8698 if not key and not value: 8699 return None 8700 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8701 8702 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8703 if not this or not self._match_text_seq("FORMAT", "JSON"): 8704 return this 8705 8706 return self.expression(exp.FormatJson(this=this)) 8707 8708 def _parse_on_condition(self) -> exp.OnCondition | None: 8709 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8710 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8711 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8712 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8713 else: 8714 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8715 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8716 8717 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8718 8719 if not empty and not error and not null: 8720 return None 8721 8722 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8723 8724 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8725 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8726 for value in values: 8727 if self._match_text_seq(value, "ON", on): 8728 return f"{value} ON {on}" 8729 8730 index = self._index 8731 if self._match(TokenType.DEFAULT): 8732 default_value = self._parse_bitwise() 8733 if self._match_text_seq("ON", on): 8734 return default_value 8735 8736 self._retreat(index) 8737 8738 return None 8739 8740 @t.overload 8741 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8742 8743 @t.overload 8744 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8745 8746 def _parse_json_object(self, agg=False): 8747 star = self._parse_star() 8748 expressions = ( 8749 [star] 8750 if star 8751 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8752 ) 8753 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8754 8755 unique_keys = None 8756 if self._match_text_seq("WITH", "UNIQUE"): 8757 unique_keys = True 8758 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8759 unique_keys = False 8760 8761 self._match_text_seq("KEYS") 8762 8763 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8764 self._parse_type() 8765 ) 8766 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8767 8768 return self.expression( 8769 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8770 expressions=expressions, 8771 null_handling=null_handling, 8772 unique_keys=unique_keys, 8773 return_type=return_type, 8774 encoding=encoding, 8775 ) 8776 ) 8777 8778 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8779 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8780 if not self._match_text_seq("NESTED"): 8781 this = self._parse_id_var() 8782 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8783 kind = self._parse_types(allow_identifiers=False) 8784 nested = None 8785 else: 8786 this = None 8787 ordinality = None 8788 kind = None 8789 nested = True 8790 8791 format_json = self._match_text_seq("FORMAT", "JSON") 8792 path = self._match_text_seq("PATH") and self._parse_string() 8793 nested_schema = nested and self._parse_json_schema() 8794 8795 return self.expression( 8796 exp.JSONColumnDef( 8797 this=this, 8798 kind=kind, 8799 path=path, 8800 nested_schema=nested_schema, 8801 ordinality=ordinality, 8802 format_json=format_json, 8803 ) 8804 ) 8805 8806 def _parse_json_schema(self) -> exp.JSONSchema: 8807 self._match_text_seq("COLUMNS") 8808 return self.expression( 8809 exp.JSONSchema( 8810 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8811 ) 8812 ) 8813 8814 def _parse_json_table(self) -> exp.JSONTable: 8815 this = self._parse_format_json(self._parse_bitwise()) 8816 path = self._match(TokenType.COMMA) and self._parse_string() 8817 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8818 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8819 schema = self._parse_json_schema() 8820 8821 return exp.JSONTable( 8822 this=this, 8823 schema=schema, 8824 path=path, 8825 error_handling=error_handling, 8826 empty_handling=empty_handling, 8827 ) 8828 8829 def _parse_match_against(self) -> exp.MatchAgainst: 8830 if self._match_text_seq("TABLE"): 8831 # parse SingleStore MATCH(TABLE ...) syntax 8832 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8833 expressions = [] 8834 table = self._parse_table() 8835 if table: 8836 expressions = [table] 8837 else: 8838 expressions = self._parse_csv(self._parse_column) 8839 8840 self._match_text_seq(")", "AGAINST", "(") 8841 8842 this = self._parse_string() 8843 8844 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8845 modifier = "IN NATURAL LANGUAGE MODE" 8846 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8847 modifier = f"{modifier} WITH QUERY EXPANSION" 8848 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8849 modifier = "IN BOOLEAN MODE" 8850 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8851 modifier = "WITH QUERY EXPANSION" 8852 else: 8853 modifier = None 8854 8855 return self.expression( 8856 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8857 ) 8858 8859 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8860 def _parse_open_json(self) -> exp.OpenJSON: 8861 this = self._parse_bitwise() 8862 path = self._match(TokenType.COMMA) and self._parse_string() 8863 8864 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8865 this = self._parse_field(any_token=True) 8866 kind = self._parse_types() 8867 path = self._parse_string() 8868 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8869 8870 return self.expression( 8871 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8872 ) 8873 8874 expressions = None 8875 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8876 self._match_l_paren() 8877 expressions = self._parse_csv(_parse_open_json_column_def) 8878 8879 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8880 8881 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8882 args = self._parse_csv(self._parse_bitwise) 8883 8884 if self._match(TokenType.IN): 8885 return self.expression( 8886 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8887 ) 8888 8889 if haystack_first: 8890 haystack = seq_get(args, 0) 8891 needle = seq_get(args, 1) 8892 else: 8893 haystack = seq_get(args, 1) 8894 needle = seq_get(args, 0) 8895 8896 return self.expression( 8897 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8898 ) 8899 8900 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8901 args = self._parse_csv(self._parse_table) 8902 return exp.JoinHint(this=func_name.upper(), expressions=args) 8903 8904 def _parse_substring(self) -> exp.Substring: 8905 # Postgres supports the form: substring(string [from int] [for int]) 8906 # (despite being undocumented, the reverse order also works) 8907 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8908 8909 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8910 8911 start, length = None, None 8912 8913 while self._curr: 8914 if self._match(TokenType.FROM): 8915 start = self._parse_bitwise() 8916 elif self._match(TokenType.FOR): 8917 if not start: 8918 start = exp.Literal.number(1) 8919 length = self._parse_bitwise() 8920 else: 8921 break 8922 8923 if start: 8924 args.append(start) 8925 if length: 8926 args.append(length) 8927 8928 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8929 8930 def _parse_trim(self) -> exp.Trim: 8931 # https://www.w3resource.com/sql/character-functions/trim.php 8932 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8933 8934 position = None 8935 collation = None 8936 expression = None 8937 8938 if self._match_texts(self.TRIM_TYPES): 8939 position = self._prev.text.upper() 8940 8941 this = self._parse_bitwise() 8942 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8943 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8944 expression = self._parse_bitwise() 8945 8946 if invert_order: 8947 this, expression = expression, this 8948 8949 if self._match(TokenType.COLLATE): 8950 collation = self._parse_bitwise() 8951 8952 return self.expression( 8953 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8954 ) 8955 8956 def _parse_window_clause(self) -> list[exp.Expr] | None: 8957 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8958 8959 def _parse_named_window(self) -> exp.Expr | None: 8960 return self._parse_window(self._parse_id_var(), alias=True) 8961 8962 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8963 if self._curr.token_type == TokenType.VAR: 8964 if self._match_text_seq("IGNORE", "NULLS"): 8965 return self.expression(exp.IgnoreNulls(this=this)) 8966 if self._match_text_seq("RESPECT", "NULLS"): 8967 return self.expression(exp.RespectNulls(this=this)) 8968 return this 8969 8970 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8971 if self._match(TokenType.HAVING): 8972 self._match_texts(("MAX", "MIN")) 8973 max = self._prev.text.upper() != "MIN" 8974 return self.expression( 8975 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8976 ) 8977 8978 return this 8979 8980 def _parse_per(self, this: exp.Expr | None) -> exp.Expr | None: 8981 if self._next.token_type == TokenType.L_PAREN and self._match_text_seq("PER"): 8982 return self.expression( 8983 exp.Per(this=this, expressions=self._parse_wrapped_csv(self._parse_disjunction)) 8984 ) 8985 8986 return this 8987 8988 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8989 func = this 8990 comments = func.comments if isinstance(func, exp.Expr) else None 8991 8992 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8993 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8994 if self._match_text_seq("FROM", "FIRST"): 8995 this.set("from_first", True) 8996 elif self._match_text_seq("FROM", "LAST"): 8997 this.set("from_first", False) 8998 8999 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 9000 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 9001 if self._match_text_seq("WITHIN", "GROUP"): 9002 order = self._parse_wrapped(self._parse_order) 9003 this = self.expression(exp.WithinGroup(this=this, expression=order)) 9004 9005 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 9006 self._match(TokenType.WHERE) 9007 this = self.expression( 9008 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 9009 ) 9010 self._match_r_paren() 9011 9012 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 9013 # Some dialects choose to implement and some do not. 9014 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 9015 9016 # There is some code above in _parse_lambda that handles 9017 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 9018 9019 # The below changes handle 9020 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 9021 9022 # Oracle allows both formats 9023 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 9024 # and Snowflake chose to do the same for familiarity 9025 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 9026 if isinstance(this, exp.AggFunc): 9027 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 9028 9029 if ignore_respect and ignore_respect is not this: 9030 ignore_respect.replace(ignore_respect.this) 9031 this = self.expression(ignore_respect.__class__(this=this)) 9032 9033 this = self._parse_respect_or_ignore_nulls(this) 9034 9035 # bigquery select from window x AS (partition by ...) 9036 if alias: 9037 over = None 9038 self._match(TokenType.ALIAS) 9039 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 9040 return self._parse_per(this) 9041 else: 9042 over = self._prev.text.upper() 9043 9044 if comments and isinstance(func, exp.Expr): 9045 func.pop_comments() 9046 9047 if not self._match(TokenType.L_PAREN): 9048 return self._parse_per( 9049 self.expression( 9050 exp.Window(this=this, alias=self._parse_id_var(False), over=over), 9051 comments=comments, 9052 ) 9053 ) 9054 9055 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 9056 9057 first: bool | None = True if self._match(TokenType.FIRST) else None 9058 if self._match_text_seq("LAST"): 9059 first = False 9060 9061 partition, order = self._parse_partition_and_order() 9062 kind = ( 9063 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 9064 ) and self._prev.text 9065 9066 if kind: 9067 self._match(TokenType.BETWEEN) 9068 start = self._parse_window_spec() 9069 9070 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 9071 exclude = ( 9072 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 9073 if self._match_text_seq("EXCLUDE") 9074 else None 9075 ) 9076 9077 spec = self.expression( 9078 exp.WindowSpec( 9079 kind=kind, 9080 start=start["value"], 9081 start_side=start["side"], 9082 end=end.get("value"), 9083 end_side=end.get("side"), 9084 exclude=exclude, 9085 ) 9086 ) 9087 else: 9088 spec = None 9089 9090 self._match_r_paren() 9091 9092 window = self.expression( 9093 exp.Window( 9094 this=this, 9095 partition_by=partition, 9096 order=order, 9097 spec=spec, 9098 alias=window_alias, 9099 over=over, 9100 first=first, 9101 ), 9102 comments=comments, 9103 ) 9104 9105 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 9106 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 9107 return self._parse_window(window, alias=alias) 9108 9109 return self._parse_per(window) 9110 9111 def _parse_partition_and_order( 9112 self, 9113 ) -> tuple[list[exp.Expr], exp.Expr | None]: 9114 return self._parse_partition_by(), self._parse_order() 9115 9116 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 9117 self._match(TokenType.BETWEEN) 9118 9119 return { 9120 "value": ( 9121 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 9122 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 9123 or self._parse_bitwise() 9124 ), 9125 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 9126 } 9127 9128 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 9129 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 9130 # so this section tries to parse the clause version and if it fails, it treats the token 9131 # as an identifier (alias) 9132 if self._can_parse_limit_or_offset(): 9133 return this 9134 9135 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 9136 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 9137 if self._can_parse_named_window(): 9138 return this 9139 9140 any_token = self._match(TokenType.ALIAS) 9141 comments = self._prev_comments 9142 9143 if explicit and not any_token: 9144 return this 9145 9146 if self._match(TokenType.L_PAREN): 9147 aliases = self.expression( 9148 exp.Aliases( 9149 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 9150 ), 9151 comments=comments, 9152 ) 9153 self._match_r_paren(aliases) 9154 return aliases 9155 9156 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 9157 self.STRING_ALIASES and self._parse_string_as_identifier() 9158 ) 9159 9160 if alias: 9161 comments.extend(alias.pop_comments()) 9162 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 9163 column = this.this 9164 9165 # Moves the comment next to the alias in `expr /* comment */ AS alias` 9166 if not this.comments and column and column.comments: 9167 this.comments = column.pop_comments() 9168 9169 return this 9170 9171 def _parse_id_var( 9172 self, 9173 any_token: bool = True, 9174 tokens: t.Collection[TokenType] | None = None, 9175 ) -> exp.Expr | None: 9176 expression = self._parse_identifier() 9177 if not expression and ( 9178 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 9179 ): 9180 quoted = self._prev.token_type == TokenType.STRING 9181 expression = self._identifier_expression(quoted=quoted) 9182 9183 return expression 9184 9185 def _parse_string(self) -> exp.Expr | None: 9186 if self._match_set(self.STRING_PARSERS): 9187 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 9188 return self._parse_placeholder() 9189 9190 def _parse_string_as_identifier(self) -> exp.Identifier | None: 9191 if not self._match(TokenType.STRING): 9192 return None 9193 output = exp.to_identifier(self._prev.text, quoted=True) 9194 output.update_positions(self._prev) 9195 return output 9196 9197 def _parse_number(self) -> exp.Expr | None: 9198 if self._match_set(self.NUMERIC_PARSERS): 9199 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 9200 return self._parse_placeholder() 9201 9202 def _parse_identifier(self) -> exp.Expr | None: 9203 if self._match(TokenType.IDENTIFIER): 9204 return self._identifier_expression(quoted=True) 9205 return self._parse_placeholder() 9206 9207 def _parse_var( 9208 self, 9209 any_token: bool = False, 9210 tokens: t.Collection[TokenType] | None = None, 9211 upper: bool = False, 9212 ) -> exp.Expr | None: 9213 if ( 9214 (any_token and self._advance_any()) 9215 or self._match(TokenType.VAR) 9216 or (self._match_set(tokens) if tokens else False) 9217 ): 9218 return self.expression( 9219 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 9220 ) 9221 return self._parse_placeholder() 9222 9223 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 9224 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 9225 self._advance() 9226 return self._prev 9227 return None 9228 9229 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 9230 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 9231 9232 def _parse_primary_or_var(self) -> exp.Expr | None: 9233 return self._parse_primary() or self._parse_var(any_token=True) 9234 9235 def _parse_null(self) -> exp.Expr | None: 9236 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 9237 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 9238 return self._parse_placeholder() 9239 9240 def _parse_boolean(self) -> exp.Expr | None: 9241 if self._match(TokenType.TRUE): 9242 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 9243 if self._match(TokenType.FALSE): 9244 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 9245 return self._parse_placeholder() 9246 9247 def _parse_star(self) -> exp.Expr | None: 9248 if self._match(TokenType.STAR): 9249 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 9250 return self._parse_placeholder() 9251 9252 def _parse_parameter(self) -> exp.Parameter: 9253 this = self._parse_identifier() or self._parse_primary_or_var() 9254 return self.expression(exp.Parameter(this=this)) 9255 9256 def _parse_placeholder(self) -> exp.Expr | None: 9257 if self._match_set(self.PLACEHOLDER_PARSERS): 9258 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 9259 if placeholder: 9260 return placeholder 9261 self._advance(-1) 9262 return None 9263 9264 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 9265 if not self._match_texts(keywords): 9266 return None 9267 if self._match(TokenType.L_PAREN, advance=False): 9268 return self._parse_wrapped_csv(self._parse_expression) 9269 9270 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 9271 return [expression] if expression else None 9272 9273 def _parse_csv( 9274 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 9275 ) -> list[T]: 9276 parse_result = parse_method() 9277 items = [parse_result] if parse_result is not None else [] 9278 9279 while self._match(sep): 9280 if isinstance(parse_result, exp.Expr): 9281 self._add_comments(parse_result) 9282 parse_result = parse_method() 9283 if parse_result is not None: 9284 items.append(parse_result) 9285 9286 return items 9287 9288 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 9289 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 9290 9291 def _parse_wrapped_csv( 9292 self, 9293 parse_method: t.Callable[[], T | None], 9294 sep: TokenType = TokenType.COMMA, 9295 optional: bool = False, 9296 ) -> list[T]: 9297 return self._parse_wrapped( 9298 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 9299 ) 9300 9301 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 9302 wrapped = self._match(TokenType.L_PAREN) 9303 if not wrapped and not optional: 9304 self.raise_error("Expecting (") 9305 parse_result = parse_method() 9306 if wrapped: 9307 self._match_r_paren() 9308 return parse_result 9309 9310 def _parse_expressions(self) -> list[exp.Expr]: 9311 return self._parse_csv(self._parse_expression) 9312 9313 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 9314 return ( 9315 self._parse_set_operations( 9316 self._parse_alias(self._parse_assignment(), explicit=True) 9317 if alias 9318 else self._parse_assignment() 9319 ) 9320 or self._parse_select() 9321 ) 9322 9323 def _parse_ddl_select(self) -> exp.Expr | None: 9324 return self._parse_query_modifiers( 9325 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 9326 ) 9327 9328 def _parse_transaction(self) -> exp.Transaction | exp.Command: 9329 this = None 9330 if self._match_texts(self.TRANSACTION_KIND): 9331 this = self._prev.text 9332 9333 self._match_texts(("TRANSACTION", "WORK")) 9334 9335 modes = [] 9336 while True: 9337 mode = [] 9338 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 9339 mode.append(self._prev.text) 9340 9341 if mode: 9342 modes.append(" ".join(mode)) 9343 if not self._match(TokenType.COMMA): 9344 break 9345 9346 return self.expression(exp.Transaction(this=this, modes=modes)) 9347 9348 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 9349 chain = None 9350 savepoint = None 9351 is_rollback = self._prev.token_type == TokenType.ROLLBACK 9352 9353 self._match_texts(("TRANSACTION", "WORK")) 9354 9355 if self._match_text_seq("TO"): 9356 self._match_text_seq("SAVEPOINT") 9357 savepoint = self._parse_id_var() 9358 9359 if self._match(TokenType.AND): 9360 chain = not self._match_text_seq("NO") 9361 self._match_text_seq("CHAIN") 9362 9363 if is_rollback: 9364 return self.expression(exp.Rollback(savepoint=savepoint)) 9365 9366 return self.expression(exp.Commit(chain=chain)) 9367 9368 def _parse_refresh(self) -> exp.Refresh | exp.Command: 9369 start = self._prev 9370 9371 if self._match_text_seq("EXTERNAL", "TABLE"): 9372 kind = "EXTERNAL TABLE" 9373 elif self._match(TokenType.TABLE): 9374 kind = "TABLE" 9375 elif self._match_text_seq("MATERIALIZED", "VIEW"): 9376 kind = "MATERIALIZED VIEW" 9377 else: 9378 kind = "" 9379 9380 this = self._parse_string() 9381 if not kind and not isinstance(this, exp.Literal): 9382 return self._parse_as_command(start) 9383 9384 return self.expression(exp.Refresh(this=this or self._parse_table(), kind=kind)) 9385 9386 def _parse_column_def_with_exists(self): 9387 start = self._index 9388 self._match(TokenType.COLUMN) 9389 9390 exists_column = self._parse_exists(not_=True) 9391 expression = self._parse_field_def() 9392 9393 if not isinstance(expression, exp.ColumnDef): 9394 self._retreat(start) 9395 return None 9396 9397 expression.set("exists", exists_column) 9398 9399 return expression 9400 9401 def _parse_add_column(self) -> exp.ColumnDef | None: 9402 if not self._prev.text.upper() == "ADD": 9403 return None 9404 9405 return self._parse_column_def_with_exists() 9406 9407 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9408 if not self._match(TokenType.DROP): 9409 return None 9410 9411 kind = None 9412 if not self.ALTER_DROP_REQUIRES_COLUMN: 9413 # A bare `DROP [IF EXISTS] c [CASCADE | RESTRICT]` drops a column 9414 index = self._index 9415 self._parse_exists() 9416 if ( 9417 self._curr.token_type not in (TokenType.COLUMN, TokenType.CONSTRAINT) 9418 and self._match_set(self.ID_VAR_TOKENS) 9419 and ( 9420 not self._curr 9421 or self._match(TokenType.COMMA, advance=False) 9422 or self._match_texts(("CASCADE", "RESTRICT"), advance=False) 9423 ) 9424 ): 9425 kind = "COLUMN" 9426 self._retreat(index) 9427 9428 return self._parse_drop(kind=kind) 9429 9430 def _parse_alter_drop_action(self) -> exp.Expr | None: 9431 drop = self._parse_drop_column() 9432 9433 if ( 9434 isinstance(drop, exp.Drop) 9435 and drop.kind == "COLUMN" 9436 and not self.dialect.ALTER_TABLE_DROP_REQUIRED_FOR_EACH_COLUMN 9437 and self._match(TokenType.COMMA) 9438 ): 9439 drop.set("tables", [*drop.args["tables"], *self._parse_csv(self._parse_column)]) 9440 9441 return drop 9442 9443 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9444 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9445 return self.expression( 9446 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9447 ) 9448 9449 def _parse_alter_table_add(self) -> list[exp.Expr]: 9450 def _parse_add_alteration() -> exp.Expr | None: 9451 self._match_text_seq("ADD") 9452 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) or self._match_texts( 9453 self.ADD_CONSTRAINT_KEYWORDS, advance=False 9454 ): 9455 return self.expression( 9456 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9457 ) 9458 9459 column_def = self._parse_add_column() 9460 if isinstance(column_def, exp.ColumnDef): 9461 return column_def 9462 9463 exists = self._parse_exists(not_=True) 9464 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9465 return self.expression( 9466 exp.AddPartition( 9467 exists=exists, 9468 this=self._parse_field(any_token=True), 9469 location=self._match_text_seq("LOCATION", advance=False) 9470 and self._parse_property(), 9471 ) 9472 ) 9473 9474 return None 9475 9476 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9477 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9478 or self._match_text_seq("COLUMNS") 9479 ): 9480 schema = self._parse_schema() 9481 9482 return ( 9483 ensure_list(schema) 9484 if schema 9485 else self._parse_csv(self._parse_column_def_with_exists) 9486 ) 9487 9488 return self._parse_csv(_parse_add_alteration) 9489 9490 def _parse_alter_table_alter(self) -> exp.Expr | None: 9491 if self._match_texts(self.ALTER_ALTER_PARSERS): 9492 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9493 9494 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9495 # keyword after ALTER we default to parsing this statement 9496 self._match(TokenType.COLUMN) 9497 exists = self._parse_exists() 9498 column = self._parse_field(any_token=True) 9499 9500 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9501 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9502 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9503 return self.expression( 9504 exp.AlterColumn( 9505 this=column, default=self._parse_disjunction(), exists=exists or None 9506 ) 9507 ) 9508 if self._match(TokenType.COMMENT): 9509 return self.expression( 9510 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9511 ) 9512 if self._match_text_seq("DROP", "NOT", "NULL"): 9513 return self.expression( 9514 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9515 ) 9516 if self._match_text_seq("SET", "NOT", "NULL"): 9517 return self.expression( 9518 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9519 ) 9520 9521 if self._match_text_seq("SET", "VISIBLE"): 9522 return self.expression( 9523 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9524 ) 9525 if self._match_text_seq("SET", "INVISIBLE"): 9526 return self.expression( 9527 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9528 ) 9529 9530 self._match_text_seq("SET", "DATA") 9531 dtype = ( 9532 self._parse_types() 9533 if self._match_text_seq("TYPE") or not self.ALTER_COLUMN_TYPE_REQUIRES_KEYWORD 9534 else None 9535 ) 9536 if not dtype: 9537 return None 9538 9539 return self.expression( 9540 exp.AlterColumn( 9541 this=column, 9542 dtype=dtype, 9543 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9544 using=self._match(TokenType.USING) and self._parse_disjunction(), 9545 exists=exists or None, 9546 ) 9547 ) 9548 9549 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9550 if self._match_texts(("ALL", "EVEN", "AUTO")): 9551 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9552 9553 self._match_text_seq("KEY", "DISTKEY") 9554 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9555 9556 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9557 if compound: 9558 self._match_text_seq("SORTKEY") 9559 9560 if self._match(TokenType.L_PAREN, advance=False): 9561 return self.expression( 9562 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9563 ) 9564 9565 self._match_texts(("AUTO", "NONE")) 9566 return self.expression( 9567 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9568 ) 9569 9570 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9571 index = self._index - 1 9572 9573 partition_exists = self._parse_exists() 9574 if self._match(TokenType.PARTITION, advance=False): 9575 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9576 9577 self._retreat(index) 9578 return self._parse_csv(self._parse_alter_drop_action) 9579 9580 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9581 if self._match(TokenType.COLUMN) or ( 9582 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9583 ): 9584 exists = self._parse_exists() 9585 old_column = self._parse_column() 9586 to = self._match_text_seq("TO") 9587 new_column = self._parse_column() 9588 9589 if old_column is None or not to or new_column is None: 9590 return None 9591 9592 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9593 9594 self._match_text_seq("TO") 9595 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9596 9597 def _parse_alter_table_set(self) -> exp.AlterSet: 9598 alter_set = self.expression(exp.AlterSet()) 9599 9600 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9601 "TABLE", "PROPERTIES" 9602 ): 9603 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9604 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9605 alter_set.set("expressions", [self._parse_assignment()]) 9606 elif self._match_texts(("LOGGED", "UNLOGGED")): 9607 alter_set.set("option", exp.var(self._prev.text.upper())) 9608 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9609 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9610 elif self._match_text_seq("LOCATION"): 9611 alter_set.set("location", self._parse_field()) 9612 elif self._match_text_seq("ACCESS", "METHOD"): 9613 alter_set.set("access_method", self._parse_field()) 9614 elif self._match_text_seq("TABLESPACE"): 9615 alter_set.set("tablespace", self._parse_field()) 9616 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9617 alter_set.set("file_format", [self._parse_field()]) 9618 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9619 alter_set.set("file_format", self._parse_wrapped_options()) 9620 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9621 alter_set.set("copy_options", self._parse_wrapped_options()) 9622 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9623 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9624 else: 9625 if self._match_text_seq("SERDE"): 9626 alter_set.set("serde", self._parse_field()) 9627 9628 properties = self._parse_wrapped(self._parse_properties, optional=True) 9629 alter_set.set("expressions", [properties]) 9630 9631 return alter_set 9632 9633 def _parse_alter_session(self) -> exp.AlterSession: 9634 """Parse ALTER SESSION SET/UNSET statements.""" 9635 if self._match(TokenType.SET): 9636 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9637 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9638 9639 self._match_text_seq("UNSET") 9640 expressions = self._parse_csv( 9641 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9642 ) 9643 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9644 9645 def _parse_alter(self) -> exp.Alter | exp.Command: 9646 start = self._prev 9647 9648 iceberg = self._match_text_seq("ICEBERG") 9649 9650 alter_token = self._match_set(self.ALTERABLES) and self._prev 9651 if not alter_token: 9652 return self._parse_as_command(start) 9653 if iceberg and alter_token.token_type != TokenType.TABLE: 9654 return self._parse_as_command(start) 9655 9656 exists = self._parse_exists() 9657 only = self._match_text_seq("ONLY") 9658 9659 if alter_token.token_type == TokenType.SESSION: 9660 this = None 9661 check = None 9662 cluster = None 9663 parser = self.ALTER_PARSERS.get(alter_token.text.upper()) 9664 else: 9665 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9666 check = self._match_text_seq("WITH", "CHECK") 9667 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9668 parser = ( 9669 self.ALTER_PARSERS[self._prev.text.upper()] 9670 if self._match_texts(self.ALTER_PARSERS) 9671 else None 9672 ) 9673 9674 options: list[exp.Expr] = [] 9675 if ( 9676 not parser 9677 and alter_token.token_type == TokenType.TABLE 9678 and not self.ALTER_TABLE_REQUIRES_ACTION 9679 ): 9680 # Table options may precede the actions, e.g. MySQL's ENGINE=InnoDB, ADD COLUMN a INT. 9681 # A clause the property parser rejects, like PARTITION BY RANGE ... MAXVALUE, stays a Command 9682 properties = self._try_parse(self._parse_properties) 9683 options = properties.expressions if properties else [] 9684 9685 actions = ensure_list(parser(self) if parser else None) 9686 if actions or options: 9687 # Actions of different kinds and table options may alternate, e.g. MySQL's 9688 # ADD c INT, AUTO_INCREMENT=5, ADD d INT. The comma before an item may have already 9689 # been consumed by the previous parser 9690 while self.ALTER_TABLE_MIXED_ACTIONS and ( 9691 self._match(TokenType.COMMA) or self._prev.token_type == TokenType.COMMA 9692 ): 9693 if not self._match_texts( 9694 self.PROPERTY_PARSERS, advance=False 9695 ) and self._match_texts(self.ALTER_PARSERS): 9696 parser = self.ALTER_PARSERS[self._prev.text.upper()] 9697 parsed = ensure_list(self._try_parse(lambda: parser(self))) 9698 if not parsed or any(isinstance(action, exp.Command) for action in parsed): 9699 return self._parse_as_command(start) 9700 9701 actions.extend(parsed) 9702 elif properties := self._try_parse(self._parse_properties): 9703 options.extend(properties.expressions) 9704 else: 9705 break 9706 9707 not_valid = self._match_text_seq("NOT", "VALID") 9708 if properties := self._try_parse(self._parse_properties): 9709 options.extend(properties.expressions) 9710 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9711 9712 if not self._curr: 9713 return self.expression( 9714 exp.Alter( 9715 this=this, 9716 kind=alter_token.text.upper(), 9717 exists=exists, 9718 actions=actions, 9719 only=only, 9720 options=options, 9721 cluster=cluster, 9722 not_valid=not_valid, 9723 check=check, 9724 cascade=cascade, 9725 iceberg=iceberg, 9726 ) 9727 ) 9728 9729 return self._parse_as_command(start) 9730 9731 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9732 start = self._prev 9733 # https://duckdb.org/docs/sql/statements/analyze 9734 if not self._curr: 9735 return self.expression(exp.Analyze()) 9736 9737 options = [] 9738 while self._match_texts(self.ANALYZE_STYLES): 9739 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9740 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9741 else: 9742 options.append(self._prev.text.upper()) 9743 9744 tables: exp.Expr | list[exp.Expr] | None = None 9745 inner_expression: exp.Expr | None = None 9746 9747 kind = self._curr.text.upper() if self._curr else None 9748 9749 if self._match(TokenType.TABLE): 9750 tables = self._parse_csv(self._parse_table_parts) 9751 elif self._match(TokenType.INDEX): 9752 tables = self._parse_table_parts() 9753 elif self._match_text_seq("TABLES"): 9754 if self._match_set((TokenType.FROM, TokenType.IN)): 9755 kind = f"{kind} {self._prev.text.upper()}" 9756 tables = self._parse_table(schema=True, is_db_reference=True) 9757 elif self._match_text_seq("DATABASE"): 9758 tables = self._parse_table(schema=True, is_db_reference=True) 9759 elif self._match_text_seq("CLUSTER"): 9760 tables = self._parse_table() 9761 # Try matching inner expr keywords before fallback to parse table. 9762 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9763 kind = None 9764 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9765 else: 9766 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9767 kind = None 9768 tables = self._parse_csv(self._parse_table_parts) 9769 9770 partition = self._try_parse(self._parse_partition) 9771 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9772 return self._parse_as_command(start) 9773 9774 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9775 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9776 "WITH", "ASYNC", "MODE" 9777 ): 9778 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9779 else: 9780 mode = None 9781 9782 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9783 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9784 9785 properties = self._parse_properties() 9786 return self.expression( 9787 exp.Analyze( 9788 kind=kind, 9789 tables=ensure_list(tables), 9790 mode=mode, 9791 partition=partition, 9792 properties=properties, 9793 expression=inner_expression, 9794 options=options, 9795 ) 9796 ) 9797 9798 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9799 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9800 this = None 9801 kind = self._prev.text.upper() 9802 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9803 expressions = [] 9804 9805 if not self._match_text_seq("STATISTICS"): 9806 self.raise_error("Expecting token STATISTICS") 9807 9808 if self._match_text_seq("NOSCAN"): 9809 this = "NOSCAN" 9810 elif self._match(TokenType.FOR): 9811 if self._match_text_seq("ALL", "COLUMNS"): 9812 this = "FOR ALL COLUMNS" 9813 if self._match_text_seq("COLUMNS"): 9814 this = "FOR COLUMNS" 9815 expressions = self._parse_csv(self._parse_column_reference) 9816 elif self._match_text_seq("SAMPLE"): 9817 sample = self._parse_number() 9818 expressions = [ 9819 self.expression( 9820 exp.AnalyzeSample( 9821 sample=sample, 9822 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9823 ) 9824 ) 9825 ] 9826 9827 return self.expression( 9828 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9829 ) 9830 9831 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9832 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9833 kind = None 9834 this = None 9835 expression: exp.Expr | None = None 9836 if self._match_text_seq("REF", "UPDATE"): 9837 kind = "REF" 9838 this = "UPDATE" 9839 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9840 this = "UPDATE SET DANGLING TO NULL" 9841 elif self._match_text_seq("STRUCTURE"): 9842 kind = "STRUCTURE" 9843 if self._match_text_seq("CASCADE", "FAST"): 9844 this = "CASCADE FAST" 9845 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9846 ("ONLINE", "OFFLINE") 9847 ): 9848 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9849 expression = self._parse_into() 9850 9851 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9852 9853 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9854 this = self._prev.text.upper() 9855 if self._match_text_seq("COLUMNS"): 9856 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9857 return None 9858 9859 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9860 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9861 if self._match_text_seq("STATISTICS"): 9862 return self.expression(exp.AnalyzeDelete(kind=kind)) 9863 return None 9864 9865 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9866 if self._match_text_seq("CHAINED", "ROWS"): 9867 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9868 return None 9869 9870 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9871 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9872 this = self._prev.text.upper() 9873 expression: exp.Expr | None = None 9874 expressions = [] 9875 update_options = None 9876 9877 if self._match_text_seq("HISTOGRAM", "ON"): 9878 expressions = self._parse_csv(self._parse_column_reference) 9879 with_expressions = [] 9880 while self._match(TokenType.WITH): 9881 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9882 if self._match_texts(("SYNC", "ASYNC")): 9883 if self._match_text_seq("MODE", advance=False): 9884 with_expressions.append(f"{self._prev.text.upper()} MODE") 9885 self._advance() 9886 else: 9887 buckets = self._parse_number() 9888 if self._match_text_seq("BUCKETS"): 9889 with_expressions.append(f"{buckets} BUCKETS") 9890 if with_expressions: 9891 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9892 9893 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9894 TokenType.UPDATE, advance=False 9895 ): 9896 update_options = self._prev.text.upper() 9897 self._advance() 9898 elif self._match_text_seq("USING", "DATA"): 9899 expression = self.expression(exp.UsingData(this=self._parse_string())) 9900 9901 return self.expression( 9902 exp.AnalyzeHistogram( 9903 this=this, 9904 expressions=expressions, 9905 expression=expression, 9906 update_options=update_options, 9907 ) 9908 ) 9909 9910 def _parse_merge(self) -> exp.Merge: 9911 self._match(TokenType.INTO) 9912 target = self._parse_table() 9913 9914 if target and self._match(TokenType.ALIAS, advance=False): 9915 target.set("alias", self._parse_table_alias()) 9916 9917 self._match(TokenType.USING) 9918 using = self._parse_table() 9919 9920 return self.expression( 9921 exp.Merge( 9922 this=target, 9923 using=using, 9924 on=self._match(TokenType.ON) and self._parse_disjunction(), 9925 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9926 whens=self._parse_when_matched(), 9927 returning=self._parse_returning(), 9928 ) 9929 ) 9930 9931 def _parse_when_matched(self) -> exp.Whens: 9932 whens = [] 9933 9934 while self._match(TokenType.WHEN): 9935 matched = not self._match(TokenType.NOT) 9936 self._match_text_seq("MATCHED") 9937 source = ( 9938 False 9939 if self._match_text_seq("BY", "TARGET") 9940 else self._match_text_seq("BY", "SOURCE") 9941 ) 9942 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9943 9944 self._match(TokenType.THEN) 9945 9946 if self._match(TokenType.INSERT): 9947 this = ( 9948 exp.Star() if self._match_text_seq("ALL", "BY", "NAME") else self._parse_star() 9949 ) 9950 if this: 9951 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9952 else: 9953 then = self.expression( 9954 exp.Insert( 9955 this=exp.var("ROW") 9956 if self._match_text_seq("ROW") 9957 else self._parse_value(values=False), 9958 expression=self._match_text_seq("VALUES") and self._parse_value(), 9959 where=self._parse_where(), 9960 ) 9961 ) 9962 elif self._match(TokenType.UPDATE): 9963 expressions = self._parse_star() 9964 if expressions: 9965 then = self.expression(exp.Update(expressions=expressions)) 9966 elif self._match_text_seq("ALL", "BY", "NAME"): 9967 then = self.expression(exp.Update(expressions=[exp.Star()])) 9968 else: 9969 then = self.expression( 9970 exp.Update( 9971 expressions=self._match(TokenType.SET) 9972 and self._parse_csv(self._parse_update_assignment), 9973 where=self._parse_where(), 9974 ) 9975 ) 9976 elif self._match(TokenType.DELETE): 9977 then = self.expression(exp.Var(this=self._prev.text)) 9978 else: 9979 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9980 9981 whens.append( 9982 self.expression( 9983 exp.When(matched=matched, source=source, condition=condition, then=then) 9984 ) 9985 ) 9986 return self.expression(exp.Whens(expressions=whens)) 9987 9988 def _parse_show(self) -> exp.Expr | None: 9989 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9990 if parser: 9991 return parser(self) 9992 return self._parse_as_command(self._prev) 9993 9994 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9995 index = self._index 9996 9997 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9998 return self._parse_set_transaction(global_=kind == "GLOBAL") 9999 10000 left = self._parse_primary() or self._parse_column() 10001 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 10002 10003 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 10004 self._retreat(index) 10005 return None 10006 10007 right = self._parse_statement() or self._parse_id_var() 10008 if isinstance(right, (exp.Column, exp.Identifier)): 10009 right = exp.var(right.name) 10010 10011 this = self.expression(exp.EQ(this=left, expression=right)) 10012 return self.expression(exp.SetItem(this=this, kind=kind)) 10013 10014 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 10015 self._match_text_seq("TRANSACTION") 10016 characteristics = self._parse_csv( 10017 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 10018 ) 10019 return self.expression( 10020 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 10021 ) 10022 10023 def _parse_set_item(self) -> exp.Expr | None: 10024 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 10025 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 10026 10027 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 10028 index = self._index 10029 set_ = self.expression( 10030 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 10031 ) 10032 10033 if self._curr: 10034 self._retreat(index) 10035 return self._parse_as_command(self._prev) 10036 10037 return set_ 10038 10039 def _parse_var_from_options( 10040 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 10041 ) -> exp.Var | None: 10042 start = self._curr 10043 if not start: 10044 return None 10045 10046 option = start.text.upper() 10047 continuations = ( 10048 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 10049 ) 10050 10051 index = self._index 10052 self._advance() 10053 for keywords in continuations or []: 10054 if isinstance(keywords, str): 10055 keywords = (keywords,) 10056 10057 if self._match_text_seq(*keywords): 10058 option = f"{option} {' '.join(keywords)}" 10059 break 10060 else: 10061 if continuations or continuations is None: 10062 if raise_unmatched: 10063 self.raise_error(f"Unknown option {option}") 10064 10065 self._retreat(index) 10066 return None 10067 10068 return exp.var(option) 10069 10070 def _parse_as_command(self, start: Token) -> exp.Command: 10071 while self._curr: 10072 self._advance() 10073 text = self._find_sql(start, self._prev) 10074 size = len(start.text) 10075 self._warn_unsupported() 10076 return exp.Command(this=text[:size], expression=text[size:]) 10077 10078 def _parse_dict_property(self, this: str) -> exp.DictProperty: 10079 settings = [] 10080 10081 self._match_l_paren() 10082 kind = self._parse_id_var() 10083 10084 if self._match(TokenType.L_PAREN): 10085 while True: 10086 key = self._parse_id_var() 10087 value = self._parse_function() or self._parse_primary_or_var() 10088 if not key and value is None: 10089 break 10090 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 10091 self._match(TokenType.R_PAREN) 10092 10093 self._match_r_paren() 10094 10095 return self.expression( 10096 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 10097 ) 10098 10099 def _parse_dict_range(self, this: str) -> exp.DictRange: 10100 self._match_l_paren() 10101 has_min = self._match_text_seq("MIN") 10102 if has_min: 10103 min = self._parse_var() or self._parse_primary() 10104 self._match_text_seq("MAX") 10105 max = self._parse_var() or self._parse_primary() 10106 else: 10107 max = self._parse_var() or self._parse_primary() 10108 min = exp.Literal.number(0) 10109 self._match_r_paren() 10110 return self.expression(exp.DictRange(this=this, min=min, max=max)) 10111 10112 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 10113 index = self._index 10114 expression = self._parse_id_var() 10115 position = self._parse_id_var() if self._match(TokenType.COMMA) else None 10116 10117 if not self._match(TokenType.IN): 10118 self._retreat(index - 1) 10119 return None 10120 iterator = self._parse_column() 10121 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 10122 return self.expression( 10123 exp.Comprehension( 10124 this=this, 10125 expression=expression, 10126 position=position, 10127 iterator=iterator, 10128 condition=condition, 10129 ) 10130 ) 10131 10132 def _parse_heredoc(self) -> exp.Heredoc | None: 10133 if self._match(TokenType.HEREDOC_STRING): 10134 return self.expression(exp.Heredoc(this=self._prev.text)) 10135 10136 if not self._match_text_seq("$"): 10137 return None 10138 10139 tags = ["$"] 10140 tag_text = None 10141 10142 if self._is_connected(): 10143 self._advance() 10144 tags.append(self._prev.text.upper()) 10145 else: 10146 self.raise_error("No closing $ found") 10147 10148 if tags[-1] != "$": 10149 if self._is_connected() and self._match_text_seq("$"): 10150 tag_text = tags[-1] 10151 tags.append("$") 10152 else: 10153 self.raise_error("No closing $ found") 10154 10155 heredoc_start = self._curr 10156 10157 while self._curr: 10158 if self._match_text_seq(*tags, advance=False): 10159 this = self._find_sql(heredoc_start, self._prev) 10160 self._advance(len(tags)) 10161 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 10162 10163 self._advance() 10164 10165 self.raise_error(f"No closing {''.join(tags)} found") 10166 return None 10167 10168 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 10169 if not self._curr: 10170 return None 10171 10172 index = self._index 10173 this = [] 10174 while True: 10175 # The current token might be multiple words 10176 curr = self._curr.text.upper() 10177 key = curr.split(" ") 10178 this.append(curr) 10179 10180 self._advance() 10181 result, trie = in_trie(trie, key) 10182 if result == TrieResult.FAILED: 10183 break 10184 10185 if result == TrieResult.EXISTS: 10186 subparser = parsers[" ".join(this)] 10187 return subparser 10188 10189 self._retreat(index) 10190 return None 10191 10192 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 10193 if not self._match(TokenType.L_PAREN, expression=expression): 10194 self.raise_error("Expecting (") 10195 10196 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 10197 if not self._match(TokenType.R_PAREN, expression=expression): 10198 self.raise_error("Expecting )") 10199 10200 def _replace_lambda( 10201 self, node: exp.Expr | None, expressions: list[exp.Expr] 10202 ) -> exp.Expr | None: 10203 if not node: 10204 return node 10205 10206 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 10207 10208 for column in node.find_all(exp.Column): 10209 typ = lambda_types.get(column.parts[0].name) 10210 if typ is not None: 10211 dot_or_id = column.to_dot() if column.table else column.this 10212 10213 if isinstance(typ, exp.DataType): 10214 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ.copy())) 10215 10216 parent = column.parent 10217 10218 while isinstance(parent, exp.Dot): 10219 if not isinstance(parent.parent, exp.Dot): 10220 parent.replace(dot_or_id) 10221 break 10222 parent = parent.parent 10223 else: 10224 if column is node: 10225 node = dot_or_id 10226 else: 10227 column.replace(dot_or_id) 10228 return node 10229 10230 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 10231 start = self._prev 10232 10233 # Not to be confused with TRUNCATE(number, decimals) function call 10234 if self._match(TokenType.L_PAREN): 10235 self._retreat(self._index - 2) 10236 return self._parse_function() 10237 10238 # Clickhouse supports TRUNCATE DATABASE as well 10239 is_database = self._match(TokenType.DATABASE) 10240 10241 self._match(TokenType.TABLE) 10242 10243 exists = self._parse_exists(not_=False) 10244 10245 expressions = self._parse_csv( 10246 lambda: self._parse_table(schema=True, is_db_reference=is_database) 10247 ) 10248 10249 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 10250 10251 if self._match_text_seq("RESTART", "IDENTITY"): 10252 identity = "RESTART" 10253 elif self._match_text_seq("CONTINUE", "IDENTITY"): 10254 identity = "CONTINUE" 10255 else: 10256 identity = None 10257 10258 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 10259 option = self._prev.text 10260 else: 10261 option = None 10262 10263 partition = self._parse_partition() 10264 10265 # Fallback case 10266 if self._curr: 10267 return self._parse_as_command(start) 10268 10269 return self.expression( 10270 exp.TruncateTable( 10271 expressions=expressions, 10272 is_database=is_database, 10273 exists=exists, 10274 cluster=cluster, 10275 identity=identity, 10276 option=option, 10277 partition=partition, 10278 ) 10279 ) 10280 10281 def _parse_indexed_column(self) -> exp.Expr | None: 10282 return self._parse_ordered(self._parse_opclass) 10283 10284 def _parse_with_operator(self) -> exp.Expr | None: 10285 this = self._parse_indexed_column() 10286 10287 if not self._match(TokenType.WITH): 10288 return this 10289 10290 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 10291 10292 return self.expression(exp.WithOperator(this=this, op=op)) 10293 10294 def _parse_wrapped_options(self) -> list[exp.Expr]: 10295 self._match(TokenType.EQ) 10296 self._match(TokenType.L_PAREN) 10297 10298 opts: list[exp.Expr] = [] 10299 option: exp.Expr | list[exp.Expr] | None 10300 while self._curr and not self._match(TokenType.R_PAREN): 10301 if self._match_text_seq("FORMAT_NAME", "="): 10302 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 10303 option = self._parse_format_name() 10304 else: 10305 option = self._parse_property() 10306 10307 if option is None: 10308 self.raise_error("Unable to parse option") 10309 break 10310 10311 opts.extend(ensure_list(option)) 10312 10313 return opts 10314 10315 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 10316 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 10317 10318 options = [] 10319 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 10320 option = self._parse_var(any_token=True) 10321 if option is None: 10322 break 10323 10324 prev = self._prev.text.upper() 10325 10326 # Different dialects might separate options and values by white space, "=" and "AS" 10327 self._match(TokenType.EQ) 10328 self._match(TokenType.ALIAS) 10329 10330 param = self.expression(exp.CopyParameter(this=option)) 10331 10332 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 10333 TokenType.L_PAREN, advance=False 10334 ): 10335 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 10336 param.set("expressions", self._parse_wrapped_options()) 10337 elif prev == "FILE_FORMAT": 10338 # T-SQL's external file format case 10339 param.set("expression", self._parse_field()) 10340 elif ( 10341 prev == "FORMAT" 10342 and self._prev.token_type == TokenType.ALIAS 10343 and self._match_texts(("AVRO", "JSON")) 10344 ): 10345 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 10346 param.set("expression", self._parse_field()) 10347 else: 10348 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 10349 10350 options.append(param) 10351 10352 if sep: 10353 self._match(sep) 10354 10355 return options 10356 10357 def _parse_credentials(self) -> exp.Credentials | None: 10358 expr = self.expression(exp.Credentials()) 10359 10360 if self._match_text_seq("STORAGE_INTEGRATION", "="): 10361 expr.set("storage", self._parse_field()) 10362 if self._match_text_seq("CREDENTIALS"): 10363 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 10364 creds = ( 10365 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 10366 ) 10367 expr.set("credentials", creds) 10368 if self._match_text_seq("ENCRYPTION"): 10369 expr.set("encryption", self._parse_wrapped_options()) 10370 if self._match_text_seq("IAM_ROLE"): 10371 expr.set( 10372 "iam_role", 10373 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 10374 ) 10375 if self._match_text_seq("REGION"): 10376 expr.set("region", self._parse_field()) 10377 10378 return expr 10379 10380 def _parse_file_location(self) -> exp.Expr | None: 10381 return self._parse_field() 10382 10383 def _parse_copy(self) -> exp.Copy | exp.Command: 10384 start = self._prev 10385 10386 self._match(TokenType.INTO) 10387 10388 this = ( 10389 self._parse_select(nested=True, parse_subquery_alias=False) 10390 if self._match(TokenType.L_PAREN, advance=False) 10391 else self._parse_table(schema=True) 10392 ) 10393 10394 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 10395 10396 files = self._parse_csv(self._parse_file_location) 10397 if self._match(TokenType.EQ, advance=False): 10398 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 10399 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 10400 # list via `_parse_wrapped(..)` below. 10401 self._advance(-1) 10402 files = [] 10403 10404 credentials = self._parse_credentials() 10405 10406 self._match_text_seq("WITH") 10407 10408 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 10409 10410 # Fallback case 10411 if self._curr: 10412 return self._parse_as_command(start) 10413 10414 return self.expression( 10415 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 10416 ) 10417 10418 def _parse_normalize(self) -> exp.Normalize: 10419 return self.expression( 10420 exp.Normalize( 10421 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 10422 ) 10423 ) 10424 10425 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 10426 args = self._parse_csv(lambda: self._parse_lambda()) 10427 10428 this = seq_get(args, 0) 10429 decimals = seq_get(args, 1) 10430 10431 return expr_type( 10432 this=this, 10433 decimals=decimals, 10434 to=self._parse_var() if self._match_text_seq("TO") else None, 10435 ) 10436 10437 def _parse_star_ops(self) -> exp.Expr | None: 10438 star_token = self._prev 10439 10440 if self._match_text_seq("COLUMNS", "(", advance=False): 10441 this = self._parse_function() 10442 if isinstance(this, exp.Columns): 10443 this.set("unpack", True) 10444 return this 10445 10446 index = self._index 10447 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 10448 if not ilike: 10449 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 10450 self._retreat(index) 10451 10452 return self.expression( 10453 exp.Star( 10454 ilike=ilike, 10455 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 10456 replace=self._parse_star_op("REPLACE"), 10457 rename=self._parse_star_op("RENAME"), 10458 ) 10459 ).update_positions(star_token) 10460 10461 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 10462 privilege_parts = [] 10463 10464 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 10465 # (end of privilege list) or L_PAREN (start of column list) are met 10466 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 10467 privilege_parts.append(self._curr.text.upper()) 10468 self._advance() 10469 10470 if not privilege_parts: 10471 self.raise_error("Expected privilege") 10472 return None 10473 10474 this = exp.var(" ".join(privilege_parts)) 10475 expressions = ( 10476 self._parse_wrapped_csv(self._parse_column) 10477 if self._match(TokenType.L_PAREN, advance=False) 10478 else None 10479 ) 10480 10481 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10482 10483 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10484 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10485 principal = self._parse_id_var() 10486 10487 if not principal: 10488 return None 10489 10490 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10491 10492 def _parse_grant_revoke_common( 10493 self, 10494 ) -> tuple[list | None, str | None, exp.Expr | None]: 10495 privileges = self._parse_csv(self._parse_grant_privilege) 10496 10497 self._match(TokenType.ON) 10498 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10499 10500 # Attempt to parse the securable e.g. MySQL allows names 10501 # such as "foo.*", "*.*" which are not easily parseable yet 10502 securable = self._try_parse(self._parse_table_parts) 10503 10504 return privileges, kind, securable 10505 10506 def _parse_grant(self) -> exp.Grant | exp.Command: 10507 start = self._prev 10508 10509 privileges, kind, securable = self._parse_grant_revoke_common() 10510 10511 if not securable or not self._match_text_seq("TO"): 10512 return self._parse_as_command(start) 10513 10514 principals = self._parse_csv(self._parse_grant_principal) 10515 10516 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10517 10518 if self._curr: 10519 return self._parse_as_command(start) 10520 10521 return self.expression( 10522 exp.Grant( 10523 privileges=privileges, 10524 kind=kind, 10525 securable=securable, 10526 principals=principals, 10527 grant_option=grant_option, 10528 ) 10529 ) 10530 10531 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10532 start = self._prev 10533 10534 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10535 10536 privileges, kind, securable = self._parse_grant_revoke_common() 10537 10538 if not securable or not self._match_text_seq("FROM"): 10539 return self._parse_as_command(start) 10540 10541 principals = self._parse_csv(self._parse_grant_principal) 10542 10543 cascade = None 10544 if self._match_texts(("CASCADE", "RESTRICT")): 10545 cascade = self._prev.text.upper() 10546 10547 if self._curr: 10548 return self._parse_as_command(start) 10549 10550 return self.expression( 10551 exp.Revoke( 10552 privileges=privileges, 10553 kind=kind, 10554 securable=securable, 10555 principals=principals, 10556 grant_option=grant_option, 10557 cascade=cascade, 10558 ) 10559 ) 10560 10561 def _parse_overlay(self) -> exp.Overlay: 10562 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10563 return ( 10564 self._parse_bitwise() 10565 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10566 else None 10567 ) 10568 10569 return self.expression( 10570 exp.Overlay( 10571 this=self._parse_bitwise(), 10572 expression=_parse_overlay_arg("PLACING"), 10573 from_=_parse_overlay_arg("FROM"), 10574 for_=_parse_overlay_arg("FOR"), 10575 ) 10576 ) 10577 10578 def _parse_format_name(self) -> exp.Property: 10579 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10580 # for FILE_FORMAT = <format_name> 10581 return self.expression( 10582 exp.Property( 10583 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10584 ) 10585 ) 10586 10587 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10588 is_distinct = self._match(TokenType.DISTINCT) 10589 if not is_distinct: 10590 self._match(TokenType.ALL) 10591 10592 args = [self._parse_lambda()] 10593 if self._match(TokenType.COMMA): 10594 args.extend(self._parse_function_args()) 10595 10596 target = seq_get(args, distinct_index) 10597 if is_distinct and target: 10598 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10599 10600 return func.from_arg_list(args) 10601 10602 def _identifier_expression( 10603 self, token: Token | None = None, quoted: bool | None = None 10604 ) -> exp.Identifier: 10605 token = token or self._prev 10606 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10607 10608 def _build_pipe_cte( 10609 self, 10610 query: exp.Query, 10611 expressions: list[exp.Expr], 10612 alias_cte: exp.TableAlias | None = None, 10613 ) -> exp.Select: 10614 new_cte: str | exp.TableAlias | None 10615 if alias_cte: 10616 new_cte = alias_cte 10617 else: 10618 self._pipe_cte_counter += 1 10619 new_cte = f"__tmp{self._pipe_cte_counter}" 10620 10621 with_ = query.args.get("with_") 10622 ctes = with_.pop() if with_ else None 10623 10624 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10625 if ctes: 10626 new_select.set("with_", ctes) 10627 10628 return new_select.with_(new_cte, as_=query, copy=False) 10629 10630 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10631 select = self._parse_select(consume_pipe=False) 10632 if not select: 10633 return query 10634 10635 return self._build_pipe_cte( 10636 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10637 ) 10638 10639 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10640 limit = self._parse_limit() 10641 offset = self._parse_offset() 10642 if isinstance(limit, exp.Limit): 10643 curr_limit = query.args.get("limit", limit) 10644 curr_is_limit_all = isinstance(curr_limit, exp.Limit) and curr_limit.is_limit_all 10645 10646 # LIMIT ALL means "no cap", so it's never tighter than a numeric limit. 10647 if limit.is_limit_all: 10648 should_replace = curr_is_limit_all 10649 else: 10650 should_replace = ( 10651 curr_is_limit_all or curr_limit.expression.to_py() >= limit.expression.to_py() 10652 ) 10653 10654 if should_replace: 10655 query.limit(limit, copy=False) 10656 if offset: 10657 curr_offset = query.args.get("offset") 10658 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10659 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10660 10661 return query 10662 10663 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10664 this = self._parse_disjunction() 10665 if self._match_text_seq("GROUP", "AND", advance=False): 10666 return this 10667 10668 this = self._parse_alias(this) 10669 10670 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10671 return self._parse_ordered(lambda: this) 10672 10673 return this 10674 10675 def _parse_pipe_syntax_aggregate_group_order_by( 10676 self, query: exp.Select, group_by_exists: bool = True 10677 ) -> exp.Select: 10678 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10679 aggregates_or_groups, orders = [], [] 10680 for element in expr: 10681 if isinstance(element, exp.Ordered): 10682 this = element.this 10683 if isinstance(this, exp.Alias): 10684 element.set("this", this.args["alias"]) 10685 orders.append(element) 10686 else: 10687 this = element 10688 aggregates_or_groups.append(this) 10689 10690 if group_by_exists: 10691 query.select( 10692 *aggregates_or_groups, *query.expressions, append=False, copy=False 10693 ).group_by( 10694 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10695 copy=False, 10696 ) 10697 else: 10698 query.select(*aggregates_or_groups, append=False, copy=False) 10699 10700 if orders: 10701 return query.order_by(*orders, append=False, copy=False) 10702 10703 return query 10704 10705 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10706 self._match_text_seq("AGGREGATE") 10707 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10708 10709 if self._match(TokenType.GROUP_BY) or ( 10710 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10711 ): 10712 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10713 10714 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10715 10716 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10717 first_setop = self.parse_set_operation(this=query) 10718 if not first_setop: 10719 return None 10720 10721 def _parse_and_unwrap_query() -> exp.Expr | None: 10722 expr = self._parse_paren() 10723 return expr.assert_is(exp.Subquery).unnest() if expr else None 10724 10725 first_setop.this.pop() 10726 10727 setops = [ 10728 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10729 *self._parse_csv(_parse_and_unwrap_query), 10730 ] 10731 10732 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10733 with_ = query.args.get("with_") 10734 ctes = with_.pop() if with_ else None 10735 10736 if isinstance(first_setop, exp.Union): 10737 query = query.union(*setops, copy=False, **first_setop.args) 10738 elif isinstance(first_setop, exp.Except): 10739 query = query.except_(*setops, copy=False, **first_setop.args) 10740 else: 10741 query = query.intersect(*setops, copy=False, **first_setop.args) 10742 10743 query.set("with_", ctes) 10744 10745 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10746 10747 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10748 join = self._parse_join() 10749 if not join: 10750 return None 10751 10752 if isinstance(query, exp.Select): 10753 return query.join(join, copy=False) 10754 10755 return query 10756 10757 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10758 pivots = self._parse_pivots() 10759 if not pivots: 10760 return query 10761 10762 from_ = query.args.get("from_") 10763 if from_: 10764 from_.this.set("pivots", pivots) 10765 10766 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10767 10768 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10769 self._match_text_seq("EXTEND") 10770 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10771 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10772 10773 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10774 sample = self._parse_table_sample() 10775 10776 with_ = query.args.get("with_") 10777 if with_: 10778 with_.expressions[-1].this.set("sample", sample) 10779 else: 10780 query.set("sample", sample) 10781 10782 return query 10783 10784 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10785 if isinstance(query, exp.Subquery): 10786 query = exp.select("*").from_(query, copy=False) 10787 10788 if not query.args.get("from_"): 10789 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10790 10791 while self._match(TokenType.PIPE_GT): 10792 start_index = self._index 10793 start_text = self._curr.text.upper() 10794 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10795 if not parser: 10796 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10797 # keywords, making it tricky to disambiguate them without lookahead. The approach 10798 # here is to try and parse a set operation and if that fails, then try to parse a 10799 # join operator. If that fails as well, then the operator is not supported. 10800 parsed_query = self._parse_pipe_syntax_set_operator(query) 10801 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10802 if not parsed_query: 10803 self._retreat(start_index) 10804 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10805 break 10806 query = parsed_query 10807 else: 10808 query = parser(self, query) 10809 10810 return query 10811 10812 def _parse_declareitem(self) -> exp.DeclareItem | None: 10813 self._match_texts(("VAR", "VARIABLE")) 10814 10815 vars = self._parse_csv(self._parse_id_var) 10816 if not vars: 10817 return None 10818 10819 self._match(TokenType.ALIAS) 10820 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10821 default = ( 10822 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10823 ) and self._parse_bitwise() 10824 10825 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10826 10827 def _parse_declare(self) -> exp.Declare | exp.Command: 10828 start = self._prev 10829 replace = self._match_text_seq("OR", "REPLACE") 10830 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10831 10832 if not expressions or self._curr: 10833 return self._parse_as_command(start) 10834 10835 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10836 10837 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10838 exp_class = exp.Cast if strict else exp.TryCast 10839 10840 if exp_class == exp.TryCast: 10841 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10842 10843 return self.expression(exp_class(**kwargs)) 10844 10845 def _parse_json_value(self, json_path: bool = True) -> exp.JSONValue: 10846 this = self._parse_bitwise() 10847 self._match(TokenType.COMMA) 10848 path = self._parse_bitwise() 10849 10850 returning = self._match(TokenType.RETURNING) and self._parse_types() 10851 10852 return self.expression( 10853 exp.JSONValue( 10854 this=this, 10855 path=self.dialect.to_json_path(path) if json_path else path, 10856 returning=returning, 10857 on_condition=self._parse_on_condition(), 10858 ) 10859 ) 10860 10861 def _parse_group_concat(self) -> exp.Expr | None: 10862 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10863 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10864 concat_exprs = [ 10865 self.expression( 10866 exp.Concat( 10867 expressions=node.expressions, 10868 safe=True, 10869 coalesce=self.dialect.CONCAT_COALESCE, 10870 ) 10871 ) 10872 ] 10873 node.set("expressions", concat_exprs) 10874 return node 10875 if len(exprs) == 1: 10876 return exprs[0] 10877 return self.expression( 10878 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10879 ) 10880 10881 args = self._parse_csv(self._parse_lambda) 10882 10883 if args: 10884 order = args[-1] if isinstance(args[-1], exp.Order) else None 10885 10886 if order: 10887 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10888 # remove 'expr' from exp.Order and add it back to args 10889 args[-1] = order.this 10890 order.set("this", concat_exprs(order.this, args)) 10891 10892 this = order or concat_exprs(args[0], args) 10893 else: 10894 this = None 10895 10896 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10897 10898 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10899 10900 def _parse_initcap(self) -> exp.Initcap: 10901 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10902 10903 # attach dialect's default delimiters 10904 if expr.args.get("expression") is None: 10905 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10906 10907 return expr 10908 10909 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10910 if not self._match(TokenType.L_PAREN): 10911 self._retreat(self._index - 1) 10912 return None 10913 10914 op = "" 10915 while self._curr and not self._match(TokenType.R_PAREN): 10916 op += self._curr.text 10917 self._advance() 10918 10919 comments = self._prev_comments 10920 return self.expression( 10921 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10922 comments=comments, 10923 )
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.
2024 def __init__( 2025 self, 2026 error_level: ErrorLevel | None = None, 2027 error_message_context: int = 100, 2028 max_errors: int = 3, 2029 max_nodes: int = -1, 2030 dialect: DialectType = None, 2031 ): 2032 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 2033 self.error_message_context: int = error_message_context 2034 self.max_errors: int = max_errors 2035 self.max_nodes: int = max_nodes 2036 self.dialect: t.Any = _resolve_dialect(dialect) 2037 self.sql: str = "" 2038 self.errors: list[ParseError] = [] 2039 self._tokens: list[Token] = [] 2040 self._tokens_size: i64 = 0 2041 self._index: i64 = 0 2042 self._curr: Token = SENTINEL_NONE 2043 self._next: Token = SENTINEL_NONE 2044 self._prev: Token = SENTINEL_NONE 2045 self._prev_comments: list[str] = [] 2046 self._pipe_cte_counter: int = 0 2047 self._chunks: list[list[Token]] = [] 2048 self._chunk_index: i64 = 0 2049 self._node_count: int = 0
2051 def reset(self) -> None: 2052 self.sql = "" 2053 self.errors = [] 2054 self._tokens = [] 2055 self._tokens_size = 0 2056 self._index = 0 2057 self._curr = SENTINEL_NONE 2058 self._next = SENTINEL_NONE 2059 self._prev = SENTINEL_NONE 2060 self._prev_comments = [] 2061 self._pipe_cte_counter = 0 2062 self._chunks = [] 2063 self._chunk_index = 0 2064 self._node_count = 0
2157 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2158 token = token or self._curr or self._prev or Token.string("") 2159 formatted_sql, start_context, highlight, end_context = highlight_sql( 2160 sql=self.sql, 2161 positions=[(token.start, token.end)], 2162 context_length=self.error_message_context, 2163 ) 2164 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2165 2166 error = ParseError.new( 2167 formatted_message, 2168 description=message, 2169 line=token.line, 2170 col=token.col, 2171 start_context=start_context, 2172 highlight=highlight, 2173 end_context=end_context, 2174 ) 2175 2176 if self.error_level == ErrorLevel.IMMEDIATE: 2177 raise error 2178 2179 self.errors.append(error)
2181 def validate_expression(self, expression: E, args: list | None = None) -> E: 2182 if self.max_nodes > -1: 2183 self._node_count += 1 2184 if self._node_count > self.max_nodes: 2185 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2186 if self.error_level != ErrorLevel.IGNORE: 2187 for error_message in expression.error_messages(args): 2188 self.raise_error(error_message) 2189 return expression
2208 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2209 """ 2210 Parses a list of tokens and returns a list of syntax trees, one tree 2211 per parsed SQL statement. 2212 2213 Args: 2214 raw_tokens: The list of tokens. 2215 sql: The original SQL string. 2216 2217 Returns: 2218 The list of the produced syntax trees. 2219 """ 2220 return self._parse( 2221 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2222 )
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.
2224 def parse_into( 2225 self, 2226 expression_types: exp.IntoType, 2227 raw_tokens: list[Token], 2228 sql: str | None = None, 2229 ) -> list[exp.Expr | None]: 2230 """ 2231 Parses a list of tokens into a given Expr type. If a collection of Expr 2232 types is given instead, this method will try to parse the token list into each one 2233 of them, stopping at the first for which the parsing succeeds. 2234 2235 Args: 2236 expression_types: The expression type(s) to try and parse the token list into. 2237 raw_tokens: The list of tokens. 2238 sql: The original SQL string, used to produce helpful debug messages. 2239 2240 Returns: 2241 The target Expr. 2242 """ 2243 errors = [] 2244 for expression_type in ensure_list(expression_types): 2245 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2246 if not parser: 2247 raise TypeError(f"No parser registered for {expression_type}") 2248 2249 try: 2250 return self._parse(parser, raw_tokens, sql) 2251 except ParseError as e: 2252 e.errors[0]["into_expression"] = expression_type 2253 errors.append(e) 2254 2255 raise ParseError( 2256 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2257 errors=merge_errors(errors), 2258 ) 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.
2260 def check_errors(self) -> None: 2261 """Logs or raises any found errors, depending on the chosen error level setting.""" 2262 if self.error_level == ErrorLevel.WARN: 2263 for error in self.errors: 2264 logger.error(str(error)) 2265 elif self.error_level == ErrorLevel.RAISE and self.errors: 2266 raise ParseError( 2267 concat_messages(self.errors, self.max_errors), 2268 errors=merge_errors(self.errors), 2269 )
Logs or raises any found errors, depending on the chosen error level setting.
2271 def expression( 2272 self, 2273 instance: E, 2274 token: Token | None = None, 2275 comments: list[str] | None = None, 2276 ) -> E: 2277 if token: 2278 instance.update_positions(token) 2279 instance.add_comments(comments) if comments else self._add_comments(instance) 2280 if not instance.is_primitive: 2281 instance = self.validate_expression(instance) 2282 return instance
6093 def parse_set_operation( 6094 self, this: exp.Expr | None, consume_pipe: bool = False, intersect_only: bool = False 6095 ) -> exp.SetOperation | None: 6096 start = self._index 6097 _, side_token, kind_token = self._parse_join_parts() 6098 6099 side = side_token.text if side_token else None 6100 kind = kind_token.text if kind_token else None 6101 6102 if not self._match_set(self.SET_OPERATIONS) or ( 6103 intersect_only and self._prev.token_type != TokenType.INTERSECT 6104 ): 6105 self._retreat(start) 6106 return None 6107 6108 token_type = self._prev.token_type 6109 6110 if token_type == TokenType.UNION: 6111 operation: type[exp.SetOperation] = exp.Union 6112 elif token_type == TokenType.EXCEPT: 6113 operation = exp.Except 6114 else: 6115 operation = exp.Intersect 6116 6117 comments = self._prev.comments 6118 6119 if self._match(TokenType.DISTINCT): 6120 distinct: bool | None = True 6121 elif self._match(TokenType.ALL): 6122 distinct = False 6123 else: 6124 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 6125 if distinct is None: 6126 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 6127 6128 by_name = ( 6129 self._match_text_seq("BY", "NAME") 6130 or self._match_text_seq("STRICT", "CORRESPONDING") 6131 or None 6132 ) 6133 if self._match_text_seq("CORRESPONDING"): 6134 by_name = True 6135 if not side and not kind: 6136 kind = "INNER" 6137 6138 on_column_list = None 6139 if by_name and self._match_texts(("ON", "BY")): 6140 on_column_list = self._parse_wrapped_csv(self._parse_column) 6141 6142 expression = self._parse_select( 6143 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 6144 ) 6145 6146 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 6147 # in _parse_cte and so that alias pushdown can reach into set operation branches 6148 if isinstance(this, exp.Values): 6149 this = self._values_to_select(this) 6150 if isinstance(expression, exp.Values): 6151 expression = self._values_to_select(expression) 6152 for operand in (this, expression): 6153 if isinstance(operand, exp.Subquery) and isinstance( 6154 values := operand.unnest(), exp.Values 6155 ): 6156 wrapper = t.cast(exp.Subquery, values.parent) 6157 wrapper.set("this", self._values_to_select(values)) 6158 6159 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 6160 subquery = this.this 6161 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 6162 subquery.add_comments(this.pop_comments()) 6163 this = subquery 6164 6165 return self.expression( 6166 operation( 6167 this=this, 6168 distinct=distinct, 6169 by_name=by_name, 6170 expression=expression, 6171 side=side, 6172 kind=kind, 6173 on=on_column_list, 6174 ), 6175 comments=comments, 6176 )