sqlglot.parser
1from __future__ import annotations 2 3import itertools 4import logging 5import re 6import typing as t 7from builtins import type as Type 8from collections import defaultdict 9from collections.abc import Sequence 10 11from sqlglot import exp 12from sqlglot._typing import F 13from sqlglot.errors import ( 14 ErrorLevel, 15 ParseError, 16 TokenError, 17 concat_messages, 18 highlight_sql, 19 merge_errors, 20) 21from sqlglot.expressions import apply_index_offset 22from sqlglot.helper import ensure_list, i64, seq_get 23from sqlglot.optimizer.scope import find_in_scope 24from sqlglot.time import format_time 25from sqlglot.tokens import Token, Tokenizer, TokenType 26from sqlglot.trie import TrieResult, in_trie, new_trie 27 28if t.TYPE_CHECKING: 29 from re import Pattern 30 31 from sqlglot._typing import BuilderArgs, E 32 from sqlglot.dialects.dialect import Dialect, DialectType 33 from sqlglot.expressions import ExpOrStr 34 35 T = t.TypeVar("T") 36 TCeilFloor = t.TypeVar("TCeilFloor", exp.Ceil, exp.Floor) 37 38logger = logging.getLogger("sqlglot") 39 40OPTIONS_TYPE = dict[str, Sequence[t.Union[Sequence[str], str]]] 41 42# Excludes bare strings, which are also collections of strings, so that a single keyword 43# can't accidentally be matched with substring semantics (e.g. _match_texts("FOO")) 44TEXTS_TYPE = t.Union[tuple[str, ...], list[str], t.AbstractSet[str], t.Mapping[str, t.Any]] 45 46# Used to detect alphabetical characters and +/- in timestamp literals 47TIME_ZONE_RE: Pattern[str] = re.compile(r":.*?[a-zA-Z\+\-]") 48 49 50def build_var_map(args: BuilderArgs) -> exp.StarMap | exp.VarMap: 51 if len(args) == 1 and args[0].is_star: 52 return exp.StarMap(this=args[0]) 53 54 keys: list[ExpOrStr] = [] 55 values: list[ExpOrStr] = [] 56 for i in range(0, len(args), 2): 57 keys.append(args[i]) 58 values.append(args[i + 1]) 59 60 return exp.VarMap(keys=exp.array(*keys, copy=False), values=exp.array(*values, copy=False)) 61 62 63def build_like(args: BuilderArgs) -> exp.Escape | exp.Like: 64 like = exp.Like(this=seq_get(args, 1), expression=seq_get(args, 0)) 65 return exp.Escape(this=like, expression=seq_get(args, 2)) if len(args) > 2 else like 66 67 68def binary_range_parser( 69 expr_type: Type[exp.Expr], reverse_args: bool = False 70) -> t.Callable[[Parser, exp.Expr | None], exp.Expr | None]: 71 def _parse_binary_range(self: Parser, this: exp.Expr | None) -> exp.Expr | None: 72 expression = self._parse_bitwise() 73 if reverse_args: 74 this, expression = expression, this 75 return self._parse_escape(self.expression(expr_type(this=this, expression=expression))) 76 77 return _parse_binary_range 78 79 80def build_logarithm(args: BuilderArgs, dialect: Dialect) -> exp.Func: 81 # Default argument order is base, expression 82 this = seq_get(args, 0) 83 expression = seq_get(args, 1) 84 85 if expression: 86 if not dialect.LOG_BASE_FIRST: 87 this, expression = expression, this 88 return exp.Log(this=this, expression=expression) 89 90 return (exp.Ln if dialect.parser_class.LOG_DEFAULTS_TO_LN else exp.Log)(this=this) 91 92 93def build_hex(args: BuilderArgs, dialect: Dialect) -> exp.Hex | exp.LowerHex: 94 arg = seq_get(args, 0) 95 return exp.LowerHex(this=arg) if dialect.HEX_LOWERCASE else exp.Hex(this=arg) 96 97 98def build_lower(args: BuilderArgs) -> exp.Lower | exp.Hex: 99 # LOWER(HEX(..)) can be simplified to LowerHex to simplify its transpilation 100 arg = seq_get(args, 0) 101 return exp.LowerHex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Lower(this=arg) 102 103 104def build_upper(args: BuilderArgs) -> exp.Upper | exp.Hex: 105 # UPPER(HEX(..)) can be simplified to Hex to simplify its transpilation 106 arg = seq_get(args, 0) 107 return exp.Hex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Upper(this=arg) 108 109 110def build_extract_json_with_path( 111 expr_type: Type[E], 112) -> t.Callable[[BuilderArgs, Dialect], E]: 113 def _builder(args: BuilderArgs, dialect: Dialect) -> E: 114 expression = expr_type( 115 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 116 ) 117 if len(args) > 2 and expr_type is exp.JSONExtract: 118 expression.set("expressions", args[2:]) 119 if expr_type is exp.JSONExtractScalar: 120 expression.set("scalar_only", dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY) 121 122 return expression 123 124 return _builder 125 126 127def build_mod(args: BuilderArgs) -> exp.Mod: 128 this = seq_get(args, 0) 129 expression = seq_get(args, 1) 130 131 # Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7 132 this = exp.Paren(this=this) if isinstance(this, exp.Binary) else this 133 expression = exp.Paren(this=expression) if isinstance(expression, exp.Binary) else expression 134 135 return exp.Mod(this=this, expression=expression) 136 137 138def build_pad(args: BuilderArgs, is_left: bool = True): 139 return exp.Pad( 140 this=seq_get(args, 0), 141 expression=seq_get(args, 1), 142 fill_pattern=seq_get(args, 2), 143 is_left=is_left, 144 ) 145 146 147def build_array_constructor( 148 exp_class: Type[E], args: list[t.Any], bracket_kind: TokenType, dialect: Dialect 149) -> exp.Expr: 150 array_exp = exp_class(expressions=args) 151 152 if exp_class == exp.Array and dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS: 153 array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET) 154 155 return array_exp 156 157 158def build_convert_timezone( 159 args: BuilderArgs, default_source_tz: str | None = None 160) -> exp.ConvertTimezone | exp.Anonymous: 161 if len(args) == 2: 162 source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None 163 return exp.ConvertTimezone( 164 source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1) 165 ) 166 167 return exp.ConvertTimezone.from_arg_list(args) 168 169 170def build_trim(args: BuilderArgs, is_left: bool = True, reverse_args: bool = False) -> exp.Trim: 171 this, expression = seq_get(args, 0), seq_get(args, 1) 172 173 if expression and reverse_args: 174 this, expression = expression, this 175 176 return exp.Trim(this=this, expression=expression, position="LEADING" if is_left else "TRAILING") 177 178 179def build_coalesce( 180 args: BuilderArgs, is_nvl: bool | None = None, is_null: bool | None = None 181) -> exp.Coalesce: 182 return exp.Coalesce(this=seq_get(args, 0), expressions=args[1:], is_nvl=is_nvl, is_null=is_null) 183 184 185def build_locate_strposition(args: BuilderArgs) -> exp.StrPosition: 186 return exp.StrPosition( 187 this=seq_get(args, 1), 188 substr=seq_get(args, 0), 189 position=seq_get(args, 2), 190 ) 191 192 193def build_array_append(args: BuilderArgs, dialect: Dialect) -> exp.ArrayAppend: 194 """ 195 Builds ArrayAppend with NULL propagation semantics based on the dialect configuration. 196 197 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 198 Others (DuckDB, PostgreSQL) create a new single-element array instead. 199 200 Args: 201 args: Function arguments [array, element] 202 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 203 204 Returns: 205 ArrayAppend expression with appropriate null_propagation flag 206 """ 207 return exp.ArrayAppend( 208 this=seq_get(args, 0), 209 expression=seq_get(args, 1), 210 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 211 ) 212 213 214def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend: 215 """ 216 Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration. 217 218 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 219 Others (DuckDB, PostgreSQL) create a new single-element array instead. 220 221 Args: 222 args: Function arguments [array, element] 223 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 224 225 Returns: 226 ArrayPrepend expression with appropriate null_propagation flag 227 """ 228 return exp.ArrayPrepend( 229 this=seq_get(args, 0), 230 expression=seq_get(args, 1), 231 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 232 ) 233 234 235def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat: 236 """ 237 Builds ArrayConcat with NULL propagation semantics based on the dialect configuration. 238 239 Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. 240 Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation. 241 242 Args: 243 args: Function arguments [array1, array2, ...] (variadic) 244 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 245 246 Returns: 247 ArrayConcat expression with appropriate null_propagation flag 248 """ 249 return exp.ArrayConcat( 250 this=seq_get(args, 0), 251 expressions=args[1:], 252 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 253 ) 254 255 256def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove: 257 """ 258 Builds ArrayRemove with NULL propagation semantics based on the dialect configuration. 259 260 Some dialects (Snowflake) return NULL when the removal value is NULL. 261 Others (DuckDB) may return empty array due to NULL comparison semantics. 262 263 Args: 264 args: Function arguments [array, value_to_remove] 265 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 266 267 Returns: 268 ArrayRemove expression with appropriate null_propagation flag 269 """ 270 return exp.ArrayRemove( 271 this=seq_get(args, 0), 272 expression=seq_get(args, 1), 273 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 274 ) 275 276 277def _resolve_dialect(dialect: DialectType) -> Dialect: 278 from sqlglot.dialects.dialect import Dialect 279 280 return Dialect.get_or_raise(dialect) 281 282 283def _unpivot_target(expr: exp.Expr) -> exp.Expr: 284 # UNPIVOT's pre-FOR values and FOR field are new output names, not column references. 285 if isinstance(expr, exp.Column) and not expr.table: 286 return expr.this 287 if isinstance(expr, exp.Tuple): 288 expr.set("expressions", [_unpivot_target(e) for e in expr.expressions]) 289 return expr 290 291 292# Builders for the JSON `->` / `->>` / `#>` / `#>>` / `?` operators, shared between 293# COLUMN_OPERATORS (accessor-tier dialects) and JSON_OPERATORS (Postgres/DuckDB's 294# binary-operator tier). 295def build_json_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONExtract: 296 return self.expression( 297 exp.JSONExtract( 298 this=this, 299 expression=self.dialect.to_json_path(path), 300 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 301 ) 302 ) 303 304 305def build_json_extract_scalar( 306 self: Parser, this: exp.Expr, path: exp.Expr 307) -> exp.JSONExtractScalar: 308 return self.expression( 309 exp.JSONExtractScalar( 310 this=this, 311 expression=self.dialect.to_json_path(path), 312 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 313 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 314 ) 315 ) 316 317 318def build_jsonb_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONBExtract: 319 return self.expression(exp.JSONBExtract(this=this, expression=path)) 320 321 322def build_jsonb_extract_scalar( 323 self: Parser, this: exp.Expr, path: exp.Expr 324) -> exp.JSONBExtractScalar: 325 return self.expression(exp.JSONBExtractScalar(this=this, expression=path)) 326 327 328def build_jsonb_contains_top_key( 329 self: Parser, this: exp.Expr, key: exp.Expr 330) -> exp.JSONBContainsTopKey: 331 return self.expression(exp.JSONBContainsTopKey(this=this, expression=key)) 332 333 334SENTINEL_NONE: Token = Token(TokenType.SENTINEL, "SENTINEL") 335 336 337class Parser: 338 """ 339 Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree. 340 341 Args: 342 error_level: The desired error level. 343 Default: ErrorLevel.IMMEDIATE 344 error_message_context: The amount of context to capture from a query string when displaying 345 the error message (in number of characters). 346 Default: 100 347 max_errors: Maximum number of error messages to include in a raised ParseError. 348 This is only relevant if error_level is ErrorLevel.RAISE. 349 Default: 3 350 max_nodes: Maximum number of AST nodes to prevent memory exhaustion. 351 Set to -1 (default) to disable the check. 352 """ 353 354 __slots__ = ( 355 "error_level", 356 "error_message_context", 357 "max_errors", 358 "max_nodes", 359 "dialect", 360 "sql", 361 "errors", 362 "_tokens", 363 "_index", 364 "_curr", 365 "_next", 366 "_prev", 367 "_prev_comments", 368 "_pipe_cte_counter", 369 "_chunks", 370 "_chunk_index", 371 "_tokens_size", 372 "_node_count", 373 ) 374 375 FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = { 376 **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()}, 377 **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce), 378 "ARRAY": lambda args, dialect: exp.Array(expressions=args), 379 "ARRAYAGG": lambda args, dialect: exp.ArrayAgg( 380 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 381 ), 382 "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg( 383 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 384 ), 385 "ARRAY_APPEND": build_array_append, 386 "ARRAY_CAT": build_array_concat, 387 "ARRAY_CONCAT": build_array_concat, 388 "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args), 389 "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args), 390 "ARRAY_PREPEND": build_array_prepend, 391 "ARRAY_REMOVE": build_array_remove, 392 "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True), 393 "CONCAT": lambda args, dialect: exp.Concat( 394 expressions=args, 395 safe=not dialect.STRICT_STRING_CONCAT, 396 coalesce=dialect.CONCAT_COALESCE, 397 ), 398 "CONCAT_WS": lambda args, dialect: exp.ConcatWs( 399 expressions=args, 400 safe=not dialect.STRICT_STRING_CONCAT, 401 coalesce=dialect.CONCAT_WS_COALESCE, 402 ), 403 "CONVERT_TIMEZONE": build_convert_timezone, 404 "DATE_TO_DATE_STR": lambda args: exp.Cast( 405 this=seq_get(args, 0), 406 to=exp.DataType(this=exp.DType.TEXT), 407 ), 408 "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray( 409 start=seq_get(args, 0), 410 end=seq_get(args, 1), 411 step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")), 412 ), 413 "GENERATE_UUID": lambda args, dialect: exp.Uuid( 414 is_string=dialect.UUID_IS_STRING_TYPE or None 415 ), 416 "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)), 417 "GREATEST": lambda args, dialect: exp.Greatest( 418 this=seq_get(args, 0), 419 expressions=args[1:], 420 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 421 ), 422 "LEAST": lambda args, dialect: exp.Least( 423 this=seq_get(args, 0), 424 expressions=args[1:], 425 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 426 ), 427 "HEX": build_hex, 428 "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract), 429 "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar), 430 "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar), 431 "JSON_KEYS": lambda args, dialect: exp.JSONKeys( 432 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 433 ), 434 "LIKE": build_like, 435 "LOG": build_logarithm, 436 "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)), 437 "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)), 438 "LOWER": build_lower, 439 "LPAD": lambda args: build_pad(args), 440 "LEFTPAD": lambda args: build_pad(args), 441 "LTRIM": lambda args: build_trim(args), 442 "MOD": build_mod, 443 "RIGHTPAD": lambda args: build_pad(args, is_left=False), 444 "RPAD": lambda args: build_pad(args, is_left=False), 445 "RTRIM": lambda args: build_trim(args, is_left=False), 446 "SCOPE_RESOLUTION": lambda args: ( 447 exp.ScopeResolution(expression=seq_get(args, 0)) 448 if len(args) != 2 449 else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1)) 450 ), 451 "STRPOS": exp.StrPosition.from_arg_list, 452 "CHARINDEX": lambda args: build_locate_strposition(args), 453 "INSTR": exp.StrPosition.from_arg_list, 454 "LOCATE": lambda args: build_locate_strposition(args), 455 "TIME_TO_TIME_STR": lambda args: exp.Cast( 456 this=seq_get(args, 0), 457 to=exp.DataType(this=exp.DType.TEXT), 458 ), 459 "TO_HEX": build_hex, 460 "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring( 461 this=exp.Cast( 462 this=seq_get(args, 0), 463 to=exp.DataType(this=exp.DType.TEXT), 464 ), 465 start=exp.Literal.number(1), 466 length=exp.Literal.number(10), 467 ), 468 "UNNEST": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))), 469 "UPPER": build_upper, 470 "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None), 471 "UUID_STRING": lambda args, dialect: exp.Uuid( 472 this=seq_get(args, 0), 473 name=seq_get(args, 1), 474 is_string=dialect.UUID_IS_STRING_TYPE or None, 475 ), 476 "VAR_MAP": build_var_map, 477 } 478 479 NO_PAREN_FUNCTIONS: t.ClassVar[dict] = { 480 TokenType.CURRENT_DATE: exp.CurrentDate, 481 TokenType.CURRENT_DATETIME: exp.CurrentDate, 482 TokenType.CURRENT_TIME: exp.CurrentTime, 483 TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp, 484 TokenType.CURRENT_USER: exp.CurrentUser, 485 TokenType.CURRENT_ROLE: exp.CurrentRole, 486 } 487 488 STRUCT_TYPE_TOKENS: t.ClassVar = { 489 TokenType.NESTED, 490 TokenType.OBJECT, 491 TokenType.STRUCT, 492 TokenType.UNION, 493 } 494 495 NESTED_TYPE_TOKENS: t.ClassVar = { 496 TokenType.ARRAY, 497 TokenType.LIST, 498 TokenType.LOWCARDINALITY, 499 TokenType.MAP, 500 TokenType.NULLABLE, 501 TokenType.RANGE, 502 *STRUCT_TYPE_TOKENS, 503 } 504 505 ENUM_TYPE_TOKENS: t.ClassVar = { 506 TokenType.DYNAMIC, 507 TokenType.ENUM, 508 TokenType.ENUM8, 509 TokenType.ENUM16, 510 } 511 512 AGGREGATE_TYPE_TOKENS: t.ClassVar = { 513 TokenType.AGGREGATEFUNCTION, 514 TokenType.SIMPLEAGGREGATEFUNCTION, 515 } 516 517 TYPE_TOKENS: t.ClassVar = { 518 TokenType.BIT, 519 TokenType.BOOLEAN, 520 TokenType.TINYINT, 521 TokenType.UTINYINT, 522 TokenType.SMALLINT, 523 TokenType.USMALLINT, 524 TokenType.INT, 525 TokenType.UINT, 526 TokenType.BIGINT, 527 TokenType.UBIGINT, 528 TokenType.BIGNUM, 529 TokenType.INT128, 530 TokenType.UINT128, 531 TokenType.INT256, 532 TokenType.UINT256, 533 TokenType.MEDIUMINT, 534 TokenType.UMEDIUMINT, 535 TokenType.FIXEDSTRING, 536 TokenType.FLOAT, 537 TokenType.DOUBLE, 538 TokenType.UDOUBLE, 539 TokenType.CHAR, 540 TokenType.NCHAR, 541 TokenType.VARCHAR, 542 TokenType.NVARCHAR, 543 TokenType.BPCHAR, 544 TokenType.TEXT, 545 TokenType.MEDIUMTEXT, 546 TokenType.LONGTEXT, 547 TokenType.BLOB, 548 TokenType.MEDIUMBLOB, 549 TokenType.LONGBLOB, 550 TokenType.BINARY, 551 TokenType.VARBINARY, 552 TokenType.JSON, 553 TokenType.JSONB, 554 TokenType.INTERVAL, 555 TokenType.TINYBLOB, 556 TokenType.TINYTEXT, 557 TokenType.TIME, 558 TokenType.TIMETZ, 559 TokenType.TIME_NS, 560 TokenType.TIMESTAMP, 561 TokenType.TIMESTAMP_S, 562 TokenType.TIMESTAMP_MS, 563 TokenType.TIMESTAMP_NS, 564 TokenType.TIMESTAMPTZ, 565 TokenType.TIMESTAMPLTZ, 566 TokenType.TIMESTAMPNTZ, 567 TokenType.DATETIME, 568 TokenType.DATETIME2, 569 TokenType.DATETIME64, 570 TokenType.SMALLDATETIME, 571 TokenType.DATE, 572 TokenType.DATE32, 573 TokenType.INT4RANGE, 574 TokenType.INT4MULTIRANGE, 575 TokenType.INT8RANGE, 576 TokenType.INT8MULTIRANGE, 577 TokenType.NUMRANGE, 578 TokenType.NUMMULTIRANGE, 579 TokenType.TSRANGE, 580 TokenType.TSMULTIRANGE, 581 TokenType.TSTZRANGE, 582 TokenType.TSTZMULTIRANGE, 583 TokenType.DATERANGE, 584 TokenType.DATEMULTIRANGE, 585 TokenType.DECIMAL, 586 TokenType.DECIMAL32, 587 TokenType.DECIMAL64, 588 TokenType.DECIMAL128, 589 TokenType.DECIMAL256, 590 TokenType.DECFLOAT, 591 TokenType.UDECIMAL, 592 TokenType.BIGDECIMAL, 593 TokenType.UUID, 594 TokenType.GEOGRAPHY, 595 TokenType.GEOGRAPHYPOINT, 596 TokenType.GEOMETRY, 597 TokenType.POINT, 598 TokenType.RING, 599 TokenType.LINESTRING, 600 TokenType.MULTILINESTRING, 601 TokenType.POLYGON, 602 TokenType.MULTIPOLYGON, 603 TokenType.HLLSKETCH, 604 TokenType.HSTORE, 605 TokenType.PSEUDO_TYPE, 606 TokenType.SUPER, 607 TokenType.SERIAL, 608 TokenType.SMALLSERIAL, 609 TokenType.BIGSERIAL, 610 TokenType.XML, 611 TokenType.YEAR, 612 TokenType.USERDEFINED, 613 TokenType.MONEY, 614 TokenType.SMALLMONEY, 615 TokenType.ROWVERSION, 616 TokenType.IMAGE, 617 TokenType.VARIANT, 618 TokenType.VECTOR, 619 TokenType.VOID, 620 TokenType.OBJECT, 621 TokenType.OBJECT_IDENTIFIER, 622 TokenType.INET, 623 TokenType.IPADDRESS, 624 TokenType.IPPREFIX, 625 TokenType.IPV4, 626 TokenType.IPV6, 627 TokenType.UNKNOWN, 628 TokenType.NOTHING, 629 TokenType.NULL, 630 TokenType.NAME, 631 TokenType.TDIGEST, 632 TokenType.DYNAMIC, 633 *ENUM_TYPE_TOKENS, 634 *NESTED_TYPE_TOKENS, 635 *AGGREGATE_TYPE_TOKENS, 636 } 637 638 SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = { 639 TokenType.BIGINT: TokenType.UBIGINT, 640 TokenType.INT: TokenType.UINT, 641 TokenType.MEDIUMINT: TokenType.UMEDIUMINT, 642 TokenType.SMALLINT: TokenType.USMALLINT, 643 TokenType.TINYINT: TokenType.UTINYINT, 644 TokenType.DECIMAL: TokenType.UDECIMAL, 645 TokenType.DOUBLE: TokenType.UDOUBLE, 646 } 647 648 SUBQUERY_PREDICATES: t.ClassVar = { 649 TokenType.ANY: exp.Any, 650 TokenType.ALL: exp.All, 651 TokenType.EXISTS: exp.Exists, 652 TokenType.SOME: exp.Any, 653 } 654 655 SUBQUERY_TOKENS: t.ClassVar = { 656 TokenType.SELECT, 657 TokenType.WITH, 658 TokenType.FROM, 659 } 660 661 RESERVED_TOKENS: t.ClassVar = { 662 *Tokenizer.SINGLE_TOKENS.values(), 663 TokenType.SELECT, 664 } - {TokenType.IDENTIFIER} 665 666 # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers, 667 # string literals), so they must never be treated as keywords when matching by text 668 TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset( 669 { 670 TokenType.BIT_STRING, 671 TokenType.BYTE_STRING, 672 TokenType.HEREDOC_STRING, 673 TokenType.HEX_STRING, 674 TokenType.IDENTIFIER, 675 TokenType.NATIONAL_STRING, 676 TokenType.RAW_STRING, 677 TokenType.STRING, 678 TokenType.UNICODE_STRING, 679 } 680 ) 681 682 DB_CREATABLES: t.ClassVar = { 683 TokenType.DATABASE, 684 TokenType.DICTIONARY, 685 TokenType.FILE_FORMAT, 686 TokenType.MODEL, 687 TokenType.NAMESPACE, 688 TokenType.SCHEMA, 689 TokenType.SEMANTIC_VIEW, 690 TokenType.SEQUENCE, 691 TokenType.SINK, 692 TokenType.SOURCE, 693 TokenType.STAGE, 694 TokenType.STORAGE_INTEGRATION, 695 TokenType.STREAMLIT, 696 TokenType.TABLE, 697 TokenType.TAG, 698 TokenType.VIEW, 699 TokenType.WAREHOUSE, 700 } 701 702 CREATABLES: t.ClassVar = { 703 TokenType.COLUMN, 704 TokenType.CONSTRAINT, 705 TokenType.FOREIGN_KEY, 706 TokenType.FUNCTION, 707 TokenType.INDEX, 708 TokenType.PROCEDURE, 709 TokenType.TRIGGER, 710 TokenType.TYPE, 711 *DB_CREATABLES, 712 } 713 714 TRIGGER_EVENTS: t.ClassVar = { 715 TokenType.INSERT, 716 TokenType.UPDATE, 717 TokenType.DELETE, 718 TokenType.TRUNCATE, 719 } 720 721 ALTERABLES: t.ClassVar = { 722 TokenType.INDEX, 723 TokenType.TABLE, 724 TokenType.VIEW, 725 TokenType.SESSION, 726 } 727 728 # Tokens that can represent identifiers 729 ID_VAR_TOKENS: t.ClassVar[set] = { 730 TokenType.ALL, 731 TokenType.ANALYZE, 732 TokenType.ATTACH, 733 TokenType.VAR, 734 TokenType.ANTI, 735 TokenType.APPLY, 736 TokenType.ASC, 737 TokenType.ASOF, 738 TokenType.AUTO_INCREMENT, 739 TokenType.BEGIN, 740 TokenType.BPCHAR, 741 TokenType.CACHE, 742 TokenType.CASE, 743 TokenType.COLLATE, 744 TokenType.COMMAND, 745 TokenType.COMMENT, 746 TokenType.COMMIT, 747 TokenType.CONSTRAINT, 748 TokenType.COPY, 749 TokenType.CUBE, 750 TokenType.CURRENT_SCHEMA, 751 TokenType.DECLARE, 752 TokenType.DEFAULT, 753 TokenType.DELETE, 754 TokenType.DESC, 755 TokenType.DESCRIBE, 756 TokenType.DETACH, 757 TokenType.DICTIONARY, 758 TokenType.DIV, 759 TokenType.END, 760 TokenType.EXECUTE, 761 TokenType.EXPORT, 762 TokenType.ESCAPE, 763 TokenType.FALSE, 764 TokenType.FIRST, 765 TokenType.FILE, 766 TokenType.FILTER, 767 TokenType.FINAL, 768 TokenType.FORMAT, 769 TokenType.FULL, 770 TokenType.GET, 771 TokenType.IDENTIFIER, 772 TokenType.INOUT, 773 TokenType.IS, 774 TokenType.ISNULL, 775 TokenType.INTERVAL, 776 TokenType.KEEP, 777 TokenType.KILL, 778 TokenType.LEFT, 779 TokenType.LIMIT, 780 TokenType.LOAD, 781 TokenType.LOCK, 782 TokenType.MATCH, 783 TokenType.MERGE, 784 TokenType.NATURAL, 785 TokenType.NEXT, 786 TokenType.OFFSET, 787 TokenType.OPERATOR, 788 TokenType.ORDINALITY, 789 TokenType.OUT, 790 TokenType.OVER, 791 TokenType.OVERLAPS, 792 TokenType.OVERWRITE, 793 TokenType.PARTITION, 794 TokenType.PERCENT, 795 TokenType.PIVOT, 796 TokenType.PROJECTION, 797 TokenType.PRAGMA, 798 TokenType.PUT, 799 TokenType.RANGE, 800 TokenType.RECURSIVE, 801 TokenType.REFERENCES, 802 TokenType.REFRESH, 803 TokenType.RENAME, 804 TokenType.REPLACE, 805 TokenType.RIGHT, 806 TokenType.ROLLUP, 807 TokenType.ROW, 808 TokenType.ROWS, 809 TokenType.SEMI, 810 TokenType.SET, 811 TokenType.SETTINGS, 812 TokenType.SHOW, 813 TokenType.STREAM, 814 TokenType.STREAMLIT, 815 TokenType.TEMPORARY, 816 TokenType.TOP, 817 TokenType.TRUE, 818 TokenType.TRUNCATE, 819 TokenType.UNIQUE, 820 TokenType.UNNEST, 821 TokenType.UNPIVOT, 822 TokenType.UPDATE, 823 TokenType.USE, 824 TokenType.VOLATILE, 825 TokenType.WINDOW, 826 TokenType.CURRENT_CATALOG, 827 TokenType.LOCALTIME, 828 TokenType.LOCALTIMESTAMP, 829 TokenType.SESSION_USER, 830 TokenType.STRAIGHT_JOIN, 831 *ALTERABLES, 832 *CREATABLES, 833 *SUBQUERY_PREDICATES, 834 *TYPE_TOKENS, 835 *NO_PAREN_FUNCTIONS, 836 } - {TokenType.UNION} 837 838 TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - { 839 TokenType.ANTI, 840 TokenType.ASOF, 841 TokenType.FULL, 842 TokenType.LEFT, 843 TokenType.LOCK, 844 TokenType.NATURAL, 845 TokenType.RIGHT, 846 TokenType.SEMI, 847 TokenType.WINDOW, 848 } 849 850 ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS 851 852 COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS 853 854 ARRAY_CONSTRUCTORS: t.ClassVar = { 855 "ARRAY": exp.Array, 856 "LIST": exp.List, 857 } 858 859 COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS} 860 861 UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET} 862 863 TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"} 864 865 # Tokens that indicate a simple column reference 866 IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER}) 867 868 BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE}) 869 870 # Postfix tokens that prevent the bare column fast path 871 COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 872 { 873 TokenType.L_PAREN, 874 TokenType.L_BRACKET, 875 TokenType.L_BRACE, 876 TokenType.COLON, 877 TokenType.JOIN_MARKER, 878 } 879 ) 880 881 TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 882 { 883 TokenType.L_PAREN, 884 TokenType.L_BRACKET, 885 TokenType.L_BRACE, 886 TokenType.PIVOT, 887 TokenType.UNPIVOT, 888 TokenType.TABLE_SAMPLE, 889 } 890 ) 891 892 FUNC_TOKENS: t.ClassVar = { 893 TokenType.COLLATE, 894 TokenType.COMMAND, 895 TokenType.CURRENT_DATE, 896 TokenType.CURRENT_DATETIME, 897 TokenType.CURRENT_SCHEMA, 898 TokenType.CURRENT_TIMESTAMP, 899 TokenType.CURRENT_TIME, 900 TokenType.CURRENT_USER, 901 TokenType.CURRENT_CATALOG, 902 TokenType.DECLARE, 903 TokenType.FILTER, 904 TokenType.FIRST, 905 TokenType.FORMAT, 906 TokenType.GET, 907 TokenType.GLOB, 908 TokenType.IDENTIFIER, 909 TokenType.INDEX, 910 TokenType.ISNULL, 911 TokenType.ILIKE, 912 TokenType.INSERT, 913 TokenType.LIKE, 914 TokenType.LOCALTIME, 915 TokenType.LOCALTIMESTAMP, 916 TokenType.MERGE, 917 TokenType.NEXT, 918 TokenType.OFFSET, 919 TokenType.PRIMARY_KEY, 920 TokenType.RANGE, 921 TokenType.REPLACE, 922 TokenType.RLIKE, 923 TokenType.ROW, 924 TokenType.SESSION_USER, 925 TokenType.UNNEST, 926 TokenType.VAR, 927 TokenType.LEFT, 928 TokenType.RIGHT, 929 TokenType.SEQUENCE, 930 TokenType.DATE, 931 TokenType.DATETIME, 932 TokenType.TABLE, 933 TokenType.TIMESTAMP, 934 TokenType.TIMESTAMPTZ, 935 TokenType.TRUNCATE, 936 TokenType.UTC_DATE, 937 TokenType.UTC_TIME, 938 TokenType.UTC_TIMESTAMP, 939 TokenType.WINDOW, 940 TokenType.XOR, 941 *TYPE_TOKENS, 942 *SUBQUERY_PREDICATES, 943 } 944 945 CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 946 TokenType.AND: exp.And, 947 } 948 949 ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 950 TokenType.COLON_EQ: exp.PropertyEQ, 951 } 952 953 DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 954 TokenType.OR: exp.Or, 955 } 956 957 EQUALITY: t.ClassVar = { 958 TokenType.EQ: exp.EQ, 959 TokenType.NEQ: exp.NEQ, 960 TokenType.NULLSAFE_EQ: exp.NullSafeEQ, 961 } 962 963 COMPARISON: t.ClassVar = { 964 TokenType.GT: exp.GT, 965 TokenType.GTE: exp.GTE, 966 TokenType.LT: exp.LT, 967 TokenType.LTE: exp.LTE, 968 } 969 970 BITWISE: t.ClassVar = { 971 TokenType.AMP: exp.BitwiseAnd, 972 TokenType.CARET: exp.BitwiseXor, 973 TokenType.PIPE: exp.BitwiseOr, 974 } 975 976 TERM: t.ClassVar = { 977 TokenType.DASH: exp.Sub, 978 TokenType.PLUS: exp.Add, 979 TokenType.COLLATE: exp.Collate, 980 } 981 982 FACTOR: t.ClassVar = { 983 TokenType.DIV: exp.IntDiv, 984 TokenType.LR_ARROW: exp.Distance, 985 TokenType.LLRR_ARROW: exp.DistanceNd, 986 TokenType.MOD: exp.Mod, 987 TokenType.SLASH: exp.Div, 988 TokenType.STAR: exp.Mul, 989 } 990 991 EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {} 992 993 TIMES: t.ClassVar = { 994 TokenType.TIME, 995 TokenType.TIMETZ, 996 } 997 998 TIMESTAMPS: t.ClassVar = { 999 TokenType.TIMESTAMP, 1000 TokenType.TIMESTAMPNTZ, 1001 TokenType.TIMESTAMPTZ, 1002 TokenType.TIMESTAMPLTZ, 1003 *TIMES, 1004 } 1005 1006 SET_OPERATIONS: t.ClassVar = { 1007 TokenType.UNION, 1008 TokenType.INTERSECT, 1009 TokenType.EXCEPT, 1010 } 1011 1012 JOIN_METHODS: t.ClassVar = { 1013 TokenType.ASOF, 1014 TokenType.NATURAL, 1015 TokenType.POSITIONAL, 1016 } 1017 1018 JOIN_SIDES: t.ClassVar = { 1019 TokenType.LEFT, 1020 TokenType.RIGHT, 1021 TokenType.FULL, 1022 } 1023 1024 JOIN_KINDS: t.ClassVar = { 1025 TokenType.ANTI, 1026 TokenType.CROSS, 1027 TokenType.INNER, 1028 TokenType.OUTER, 1029 TokenType.SEMI, 1030 TokenType.STRAIGHT_JOIN, 1031 } 1032 1033 JOIN_HINTS: t.ClassVar[set[str]] = set() 1034 1035 # Tokens that unambiguously end a table reference on the fast path 1036 TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset( 1037 { 1038 TokenType.COMMA, 1039 TokenType.GROUP_BY, 1040 TokenType.HAVING, 1041 TokenType.JOIN, 1042 TokenType.LIMIT, 1043 TokenType.ON, 1044 TokenType.ORDER_BY, 1045 TokenType.R_PAREN, 1046 TokenType.SEMICOLON, 1047 TokenType.SENTINEL, 1048 TokenType.WHERE, 1049 *SET_OPERATIONS, 1050 *JOIN_KINDS, 1051 *JOIN_METHODS, 1052 *JOIN_SIDES, 1053 } 1054 ) 1055 1056 LAMBDAS: t.ClassVar = { 1057 TokenType.ARROW: lambda self, expressions: self.expression( 1058 exp.Lambda( 1059 this=self._replace_lambda( 1060 self._parse_disjunction(), 1061 expressions, 1062 ), 1063 expressions=expressions, 1064 ) 1065 ), 1066 TokenType.FARROW: lambda self, expressions: self.expression( 1067 exp.Kwarg( 1068 this=exp.var(expressions[0].name), 1069 expression=self._parse_disjunction() or self._parse_select(), 1070 ) 1071 ), 1072 } 1073 1074 # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake 1075 TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False 1076 1077 LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN}) 1078 1079 COLUMN_OPERATORS: t.ClassVar = { 1080 TokenType.DOT: None, 1081 TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)), 1082 TokenType.DCOLON: lambda self, this, to: self.build_cast( 1083 strict=self.STRICT_CAST, this=this, to=to 1084 ), 1085 TokenType.ARROW: lambda self, this, path: self.expression( 1086 exp.JSONExtract( 1087 this=this, 1088 expression=self.dialect.to_json_path(path), 1089 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1090 ) 1091 ), 1092 TokenType.DARROW: lambda self, this, path: self.expression( 1093 exp.JSONExtractScalar( 1094 this=this, 1095 expression=self.dialect.to_json_path(path), 1096 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1097 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 1098 ) 1099 ), 1100 TokenType.HASH_ARROW: lambda self, this, path: self.expression( 1101 exp.JSONBExtract(this=this, expression=path) 1102 ), 1103 TokenType.DHASH_ARROW: lambda self, this, path: self.expression( 1104 exp.JSONBExtractScalar(this=this, expression=path) 1105 ), 1106 TokenType.PLACEHOLDER: lambda self, this, key: self.expression( 1107 exp.JSONBContainsTopKey(this=this, expression=key) 1108 ), 1109 } 1110 1111 # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator" 1112 # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs). 1113 JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {} 1114 1115 CAST_COLUMN_OPERATORS: t.ClassVar = { 1116 TokenType.DOTCOLON, 1117 TokenType.DCOLON, 1118 } 1119 1120 EXPRESSION_PARSERS: t.ClassVar = { 1121 exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), 1122 exp.Column: lambda self: self._parse_column(), 1123 exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()), 1124 exp.Condition: lambda self: self._parse_disjunction(), 1125 exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True), 1126 exp.Expr: lambda self: self._parse_expression(), 1127 exp.From: lambda self: self._parse_from(joins=True), 1128 exp.GrantPrincipal: lambda self: self._parse_grant_principal(), 1129 exp.GrantPrivilege: lambda self: self._parse_grant_privilege(), 1130 exp.Group: lambda self: self._parse_group(), 1131 exp.Having: lambda self: self._parse_having(), 1132 exp.Hint: lambda self: self._parse_hint_body(), 1133 exp.Identifier: lambda self: self._parse_id_var(), 1134 exp.Join: lambda self: self._parse_join(), 1135 exp.Lambda: lambda self: self._parse_lambda(), 1136 exp.Lateral: lambda self: self._parse_lateral(), 1137 exp.Limit: lambda self: self._parse_limit(), 1138 exp.Offset: lambda self: self._parse_offset(), 1139 exp.Order: lambda self: self._parse_order(), 1140 exp.Ordered: lambda self: self._parse_ordered(), 1141 exp.Properties: lambda self: self._parse_properties(), 1142 exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(), 1143 exp.Qualify: lambda self: self._parse_qualify(), 1144 exp.Returning: lambda self: self._parse_returning(), 1145 exp.Select: lambda self: self._parse_select(), 1146 exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), 1147 exp.Table: lambda self: self._parse_table_parts(), 1148 exp.TableAlias: lambda self: self._parse_table_alias(), 1149 exp.Tuple: lambda self: self._parse_value(values=False), 1150 exp.Whens: lambda self: self._parse_when_matched(), 1151 exp.Where: lambda self: self._parse_where(), 1152 exp.Window: lambda self: self._parse_named_window(), 1153 exp.With: lambda self: self._parse_with(), 1154 } 1155 1156 STATEMENT_PARSERS: t.ClassVar = { 1157 TokenType.ALTER: lambda self: self._parse_alter(), 1158 TokenType.ANALYZE: lambda self: self._parse_analyze(), 1159 TokenType.BEGIN: lambda self: self._parse_transaction(), 1160 TokenType.CACHE: lambda self: self._parse_cache(), 1161 TokenType.COMMENT: lambda self: self._parse_comment(), 1162 TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(), 1163 TokenType.COPY: lambda self: self._parse_copy(), 1164 TokenType.CREATE: lambda self: self._parse_create(), 1165 TokenType.DECLARE: lambda self: self._parse_declare(), 1166 TokenType.DELETE: lambda self: self._parse_delete(), 1167 TokenType.DESC: lambda self: self._parse_describe(), 1168 TokenType.DESCRIBE: lambda self: self._parse_describe(), 1169 TokenType.DROP: lambda self: self._parse_drop(), 1170 TokenType.GRANT: lambda self: self._parse_grant(), 1171 TokenType.REVOKE: lambda self: self._parse_revoke(), 1172 TokenType.INSERT: lambda self: self._parse_insert(), 1173 TokenType.KILL: lambda self: self._parse_kill(), 1174 TokenType.LOAD: lambda self: self._parse_load(), 1175 TokenType.MERGE: lambda self: self._parse_merge(), 1176 TokenType.PIVOT: lambda self: self._parse_simplified_pivot(), 1177 TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())), 1178 TokenType.REFRESH: lambda self: self._parse_refresh(), 1179 TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(), 1180 TokenType.SET: lambda self: self._parse_set(), 1181 TokenType.TRUNCATE: lambda self: self._parse_truncate_table(), 1182 TokenType.UNCACHE: lambda self: self._parse_uncache(), 1183 TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True), 1184 TokenType.UPDATE: lambda self: self._parse_update(), 1185 TokenType.USE: lambda self: self._parse_use(), 1186 TokenType.SEMICOLON: lambda self: exp.Semicolon(), 1187 } 1188 1189 UNARY_PARSERS: t.ClassVar = { 1190 TokenType.PLUS: lambda self: self._parse_unary(), # Unary + is handled as a no-op 1191 TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())), 1192 TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())), 1193 TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())), 1194 TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())), 1195 TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())), 1196 } 1197 1198 STRING_PARSERS: t.ClassVar = { 1199 TokenType.HEREDOC_STRING: lambda self, token: self.expression( 1200 exp.RawString(this=token.text), token 1201 ), 1202 TokenType.NATIONAL_STRING: lambda self, token: self.expression( 1203 exp.National(this=token.text), token 1204 ), 1205 TokenType.RAW_STRING: lambda self, token: self.expression( 1206 exp.RawString(this=token.text), token 1207 ), 1208 TokenType.STRING: lambda self, token: self.expression( 1209 exp.Literal(this=token.text, is_string=True), token 1210 ), 1211 TokenType.UNICODE_STRING: lambda self, token: self.expression( 1212 exp.UnicodeString( 1213 this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string() 1214 ), 1215 token, 1216 ), 1217 } 1218 1219 NUMERIC_PARSERS: t.ClassVar = { 1220 TokenType.BIT_STRING: lambda self, token: self.expression( 1221 exp.BitString(this=token.text), token 1222 ), 1223 TokenType.BYTE_STRING: lambda self, token: self.expression( 1224 exp.ByteString( 1225 this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None 1226 ), 1227 token, 1228 ), 1229 TokenType.HEX_STRING: lambda self, token: self.expression( 1230 exp.HexString( 1231 this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None 1232 ), 1233 token, 1234 ), 1235 TokenType.NUMBER: lambda self, token: self.expression( 1236 exp.Literal(this=token.text, is_string=False), token 1237 ), 1238 } 1239 1240 PRIMARY_PARSERS: t.ClassVar = { 1241 **STRING_PARSERS, 1242 **NUMERIC_PARSERS, 1243 TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token), 1244 TokenType.NULL: lambda self, _: self.expression(exp.Null()), 1245 TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)), 1246 TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)), 1247 TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(), 1248 TokenType.STAR: lambda self, _: self._parse_star_ops(), 1249 } 1250 1251 PLACEHOLDER_PARSERS: t.ClassVar = { 1252 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()), 1253 TokenType.PARAMETER: lambda self: self._parse_parameter(), 1254 TokenType.COLON: lambda self: ( 1255 self.expression(exp.Placeholder(this=self._prev.text)) 1256 if self._match_set(self.COLON_PLACEHOLDER_TOKENS) 1257 else None 1258 ), 1259 } 1260 1261 RANGE_PARSERS: t.ClassVar = { 1262 TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll), 1263 TokenType.BETWEEN: lambda self, this: self._parse_between(this), 1264 TokenType.GLOB: binary_range_parser(exp.Glob), 1265 TokenType.ILIKE: binary_range_parser(exp.ILike), 1266 TokenType.IN: lambda self, this: self._parse_in(this), 1267 TokenType.IRLIKE: binary_range_parser(exp.RegexpILike), 1268 TokenType.IS: lambda self, this: self._parse_is(this), 1269 TokenType.LIKE: binary_range_parser(exp.Like), 1270 TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy), 1271 TokenType.OVERLAPS: binary_range_parser(exp.Overlaps), 1272 TokenType.RLIKE: binary_range_parser(exp.RegexpLike), 1273 TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo), 1274 TokenType.FOR: lambda self, this: self._parse_comprehension(this), 1275 TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys), 1276 TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys), 1277 TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath), 1278 TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists), 1279 TokenType.ADJACENT: binary_range_parser(exp.Adjacent), 1280 TokenType.OPERATOR: lambda self, this: self._parse_operator(this), 1281 TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft), 1282 TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight), 1283 } 1284 1285 PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = { 1286 "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query), 1287 "AS": lambda self, query: self._build_pipe_cte( 1288 query, [exp.Star()], self._parse_table_alias() 1289 ), 1290 "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False), 1291 "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query), 1292 "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query), 1293 "ORDER BY": lambda self, query: query.order_by( 1294 self._parse_order(), append=False, copy=False 1295 ), 1296 "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1297 "SELECT": lambda self, query: self._parse_pipe_syntax_select(query), 1298 "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query), 1299 "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1300 "WHERE": lambda self, query: query.where(self._parse_where(), copy=False), 1301 } 1302 1303 PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1304 "ALLOWED_VALUES": lambda self: self.expression( 1305 exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary)) 1306 ), 1307 "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty), 1308 "AUTO": lambda self: self._parse_auto_property(), 1309 "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty), 1310 "BACKUP": lambda self: self.expression( 1311 exp.BackupProperty(this=self._parse_var(any_token=True)) 1312 ), 1313 "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(), 1314 "CALLED": lambda self: self._parse_called_on_null_input_property(), 1315 "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs), 1316 "CHECKSUM": lambda self: self._parse_checksum(), 1317 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1318 "CLUSTERED": lambda self: self._parse_clustered_by(), 1319 "COLLATE": lambda self, **kwargs: self._parse_property_assignment( 1320 exp.CollateProperty, **kwargs 1321 ), 1322 "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty), 1323 "CONTAINS": lambda self: self._parse_contains_property(), 1324 "COPY": lambda self: self._parse_copy_property(), 1325 "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs), 1326 "DATA_DELETION": lambda self: self._parse_data_deletion_property(), 1327 "DEFINER": lambda self: self._parse_definer(), 1328 "DETERMINISTIC": lambda self: self.expression( 1329 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1330 ), 1331 "DISTRIBUTED": lambda self: self._parse_distributed_property(), 1332 "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty), 1333 "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()), 1334 "DISTKEY": lambda self: self._parse_distkey(), 1335 "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty), 1336 "EMPTY": lambda self: self.expression(exp.EmptyProperty()), 1337 "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty), 1338 "ENVIRONMENT": lambda self: self.expression( 1339 exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment)) 1340 ), 1341 "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty), 1342 "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty), 1343 "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()), 1344 "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs), 1345 "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1346 "FREESPACE": lambda self: self._parse_freespace(), 1347 "GLOBAL": lambda self: self.expression(exp.GlobalProperty()), 1348 "HEAP": lambda self: self.expression(exp.HeapProperty()), 1349 "ICEBERG": lambda self: self.expression(exp.IcebergProperty()), 1350 "IMMUTABLE": lambda self: self.expression( 1351 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1352 ), 1353 "INHERITS": lambda self: self.expression( 1354 exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table)) 1355 ), 1356 "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())), 1357 "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs), 1358 "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty), 1359 "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"), 1360 "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"), 1361 "LIKE": lambda self: self._parse_create_like(), 1362 "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty), 1363 "LOCK": lambda self: self._parse_locking(), 1364 "LOCKING": lambda self: self._parse_locking(), 1365 "LOG": lambda self, **kwargs: self._parse_log(**kwargs), 1366 "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()), 1367 "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs), 1368 "MODIFIES": lambda self: self._parse_modifies_property(), 1369 "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)), 1370 "NO": lambda self: self._parse_no_property(), 1371 "ON": lambda self: self._parse_on_property(), 1372 "ORDER BY": lambda self: self._parse_order(skip_order_token=True), 1373 "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())), 1374 "PARTITION": lambda self: self._parse_partitioned_of(), 1375 "PARTITION BY": lambda self: self._parse_partitioned_by(), 1376 "PARTITIONED BY": lambda self: self._parse_partitioned_by(), 1377 "PARTITIONED_BY": lambda self: self._parse_partitioned_by(), 1378 "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True), 1379 "RANGE": lambda self: self._parse_dict_range(this="RANGE"), 1380 "READS": lambda self: self._parse_reads_property(), 1381 "REMOTE": lambda self: self._parse_remote_with_connection(), 1382 "RETURNS": lambda self: self._parse_returns(), 1383 "STRICT": lambda self: self.expression(exp.StrictProperty()), 1384 "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()), 1385 "ROW": lambda self: self._parse_row(), 1386 "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty), 1387 "SAMPLE": lambda self: self.expression( 1388 exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise()) 1389 ), 1390 "SECURE": lambda self: self.expression(exp.SecureProperty()), 1391 "SECURITY": lambda self: self._parse_sql_security(), 1392 "SQL SECURITY": lambda self: self._parse_sql_security(), 1393 "SET": lambda self: self.expression(exp.SetProperty(multi=False)), 1394 "SETTINGS": lambda self: self._parse_settings_property(), 1395 "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty), 1396 "SORTKEY": lambda self: self._parse_sortkey(), 1397 "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"), 1398 "STABLE": lambda self: self.expression( 1399 exp.StabilityProperty(this=exp.Literal.string("STABLE")) 1400 ), 1401 "STORED": lambda self: self._parse_stored(), 1402 "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(), 1403 "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(), 1404 "TEMP": lambda self: self.expression(exp.TemporaryProperty()), 1405 "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()), 1406 "TO": lambda self: self._parse_to_table(), 1407 "TRANSIENT": lambda self: self.expression(exp.TransientProperty()), 1408 "TRANSFORM": lambda self: self.expression( 1409 exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression)) 1410 ), 1411 "TTL": lambda self: self._parse_ttl(), 1412 "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1413 "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()), 1414 "VOLATILE": lambda self: self._parse_volatile_property(), 1415 "WITH": lambda self: self._parse_with_property(), 1416 } 1417 1418 CONSTRAINT_PARSERS: t.ClassVar = { 1419 "AUTOINCREMENT": lambda self: self._parse_auto_increment(), 1420 "AUTO_INCREMENT": lambda self: self._parse_auto_increment(), 1421 "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)), 1422 "CHECK": lambda self: self._parse_check_constraint(), 1423 "COLLATE": lambda self: self.expression( 1424 exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column()) 1425 ), 1426 "COMMENT": lambda self: self.expression( 1427 exp.CommentColumnConstraint(this=self._parse_string()) 1428 ), 1429 "COMPRESS": lambda self: self._parse_compress(), 1430 "CLUSTERED": lambda self: self.expression( 1431 exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1432 ), 1433 "NONCLUSTERED": lambda self: self.expression( 1434 exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1435 ), 1436 "DEFAULT": lambda self: self.expression( 1437 exp.DefaultColumnConstraint(this=self._parse_bitwise()) 1438 ), 1439 "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())), 1440 "EPHEMERAL": lambda self: self.expression( 1441 exp.EphemeralColumnConstraint(this=self._parse_bitwise()) 1442 ), 1443 "EXCLUDE": lambda self: self.expression( 1444 exp.ExcludeColumnConstraint(this=self._parse_index_params()) 1445 ), 1446 "FOREIGN KEY": lambda self: self._parse_foreign_key(), 1447 "FORMAT": lambda self: self.expression( 1448 exp.DateFormatColumnConstraint(this=self._parse_var_or_string()) 1449 ), 1450 "GENERATED": lambda self: self._parse_generated_as_identity(), 1451 "IDENTITY": lambda self: self._parse_auto_increment(), 1452 "INLINE": lambda self: self._parse_inline(), 1453 "LIKE": lambda self: self._parse_create_like(), 1454 "NOT": lambda self: self._parse_not_constraint(), 1455 "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)), 1456 "ON": lambda self: ( 1457 ( 1458 self._match(TokenType.UPDATE) 1459 and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function())) 1460 ) 1461 or self.expression(exp.OnProperty(this=self._parse_id_var())) 1462 ), 1463 "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())), 1464 "PERIOD": lambda self: self._parse_period_for_system_time(), 1465 "PRIMARY KEY": lambda self: self._parse_primary_key(), 1466 "REFERENCES": lambda self: self._parse_references(match=False), 1467 "TITLE": lambda self: self.expression( 1468 exp.TitleColumnConstraint(this=self._parse_var_or_string()) 1469 ), 1470 "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])), 1471 "UNIQUE": lambda self: self._parse_unique(), 1472 "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()), 1473 "WITH": lambda self: self.expression( 1474 exp.Properties(expressions=self._parse_wrapped_properties()) 1475 ), 1476 "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1477 "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1478 } 1479 1480 def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None: 1481 if not self._match(TokenType.L_PAREN, advance=False): 1482 # Partitioning by bucket or truncate follows the syntax: 1483 # PARTITION BY (BUCKET(..) | TRUNCATE(..)) 1484 # If we don't have parenthesis after each keyword, we should instead parse this as an identifier 1485 self._retreat(self._index - 1) 1486 return None 1487 1488 klass = ( 1489 exp.PartitionedByBucket 1490 if self._prev.text.upper() == "BUCKET" 1491 else exp.PartitionByTruncate 1492 ) 1493 1494 args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column()) 1495 this, expression = seq_get(args, 0), seq_get(args, 1) 1496 1497 if isinstance(this, exp.Literal): 1498 # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order 1499 # - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)` 1500 # - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)` 1501 # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)` 1502 # 1503 # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning 1504 # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties 1505 this, expression = expression, this 1506 1507 return self.expression(klass(this=this, expression=expression)) 1508 1509 ALTER_PARSERS: t.ClassVar = { 1510 "ADD": lambda self: self._parse_alter_table_add(), 1511 "AS": lambda self: self._parse_select(), 1512 "ALTER": lambda self: self._parse_alter_table_alter(), 1513 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1514 "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())), 1515 "DROP": lambda self: self._parse_alter_table_drop(), 1516 "RENAME": lambda self: self._parse_alter_table_rename(), 1517 "SET": lambda self: self._parse_alter_table_set(), 1518 "SWAP": lambda self: self.expression( 1519 exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True)) 1520 ), 1521 } 1522 1523 ALTER_ALTER_PARSERS: t.ClassVar = { 1524 "DISTKEY": lambda self: self._parse_alter_diststyle(), 1525 "DISTSTYLE": lambda self: self._parse_alter_diststyle(), 1526 "SORTKEY": lambda self: self._parse_alter_sortkey(), 1527 "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True), 1528 } 1529 1530 SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = { 1531 "CHECK", 1532 "EXCLUDE", 1533 "FOREIGN KEY", 1534 "LIKE", 1535 "PERIOD", 1536 "PRIMARY KEY", 1537 "UNIQUE", 1538 "BUCKET", 1539 "TRUNCATE", 1540 } 1541 1542 NO_PAREN_FUNCTION_PARSERS: t.ClassVar = { 1543 "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())), 1544 "CASE": lambda self: self._parse_case(), 1545 "CONNECT_BY_ROOT": lambda self: self.expression( 1546 exp.ConnectByRoot(this=self._parse_column()) 1547 ), 1548 "IF": lambda self: self._parse_if(), 1549 } 1550 1551 INVALID_FUNC_NAME_TOKENS: t.ClassVar = { 1552 TokenType.IDENTIFIER, 1553 TokenType.STRING, 1554 } 1555 1556 FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"} 1557 1558 KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice) 1559 1560 FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1561 **{ 1562 name: lambda self: self._parse_distinct_arg_function(exp.ArgMax) 1563 for name in exp.ArgMax.sql_names() 1564 }, 1565 **{ 1566 name: lambda self: self._parse_distinct_arg_function(exp.ArgMin) 1567 for name in exp.ArgMin.sql_names() 1568 }, 1569 "CAST": lambda self: self._parse_cast(self.STRICT_CAST), 1570 "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), 1571 "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), 1572 "CHAR": lambda self: self._parse_char(), 1573 "CHR": lambda self: self._parse_char(), 1574 "DECODE": lambda self: self._parse_decode(), 1575 "EXTRACT": lambda self: self._parse_extract(), 1576 "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), 1577 "GAP_FILL": lambda self: self._parse_gap_fill(), 1578 "INITCAP": lambda self: self._parse_initcap(), 1579 "JSON_OBJECT": lambda self: self._parse_json_object(), 1580 "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True), 1581 "JSON_TABLE": lambda self: self._parse_json_table(), 1582 "MATCH": lambda self: self._parse_match_against(), 1583 "NORMALIZE": lambda self: self._parse_normalize(), 1584 "OPENJSON": lambda self: self._parse_open_json(), 1585 "OVERLAY": lambda self: self._parse_overlay(), 1586 "POSITION": lambda self: self._parse_position(), 1587 "SAFE_CAST": lambda self: self._parse_cast(False, safe=True), 1588 "STRING_AGG": lambda self: self._parse_string_agg(), 1589 "SUBSTRING": lambda self: self._parse_substring(), 1590 "TRIM": lambda self: self._parse_trim(), 1591 "TRY_CAST": lambda self: self._parse_cast(False, safe=True), 1592 "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True), 1593 "XMLELEMENT": lambda self: self._parse_xml_element(), 1594 "XMLTABLE": lambda self: self._parse_xml_table(), 1595 } 1596 1597 QUERY_MODIFIER_PARSERS: t.ClassVar = { 1598 TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()), 1599 TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()), 1600 TokenType.WHERE: lambda self: ("where", self._parse_where()), 1601 TokenType.GROUP_BY: lambda self: ("group", self._parse_group()), 1602 TokenType.HAVING: lambda self: ("having", self._parse_having()), 1603 TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()), 1604 TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()), 1605 TokenType.ORDER_BY: lambda self: ("order", self._parse_order()), 1606 TokenType.LIMIT: lambda self: ("limit", self._parse_limit()), 1607 TokenType.FETCH: lambda self: ("limit", self._parse_limit()), 1608 TokenType.OFFSET: lambda self: ("offset", self._parse_offset()), 1609 TokenType.FOR: lambda self: ("locks", self._parse_locks()), 1610 TokenType.LOCK: lambda self: ("locks", self._parse_locks()), 1611 TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1612 TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1613 TokenType.CLUSTER_BY: lambda self: ( 1614 "cluster", 1615 self._parse_cluster(), 1616 ), 1617 TokenType.DISTRIBUTE_BY: lambda self: ( 1618 "distribute", 1619 self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), 1620 ), 1621 TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)), 1622 TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)), 1623 } 1624 QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS) 1625 1626 SET_PARSERS: t.ClassVar = { 1627 "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"), 1628 "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"), 1629 "SESSION": lambda self: self._parse_set_item_assignment("SESSION"), 1630 "TRANSACTION": lambda self: self._parse_set_transaction(), 1631 } 1632 1633 SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {} 1634 1635 TYPE_LITERAL_PARSERS: t.ClassVar = { 1636 exp.DType.JSON: lambda self, this, _: self.expression(exp.ParseJSON(this=this)), 1637 } 1638 1639 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1640 1641 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1642 1643 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1644 1645 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1646 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1647 "ISOLATION": ( 1648 ("LEVEL", "REPEATABLE", "READ"), 1649 ("LEVEL", "READ", "COMMITTED"), 1650 ("LEVEL", "READ", "UNCOMITTED"), 1651 ("LEVEL", "SERIALIZABLE"), 1652 ), 1653 "READ": ("WRITE", "ONLY"), 1654 } 1655 1656 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1657 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1658 "DO": ("NOTHING", "UPDATE"), 1659 } 1660 1661 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1662 "INSTEAD": (("OF",),), 1663 "BEFORE": tuple(), 1664 "AFTER": tuple(), 1665 } 1666 1667 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1668 "NOT": (("DEFERRABLE",),), 1669 "DEFERRABLE": tuple(), 1670 } 1671 1672 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1673 "SCALE": ("EXTEND", "NOEXTEND"), 1674 "SHARD": ("EXTEND", "NOEXTEND"), 1675 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1676 **dict.fromkeys( 1677 ( 1678 "SESSION", 1679 "GLOBAL", 1680 "KEEP", 1681 "NOKEEP", 1682 "ORDER", 1683 "NOORDER", 1684 "NOCACHE", 1685 "CYCLE", 1686 "NOCYCLE", 1687 "NOMINVALUE", 1688 "NOMAXVALUE", 1689 "NOSCALE", 1690 "NOSHARD", 1691 ), 1692 tuple(), 1693 ), 1694 } 1695 1696 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1697 1698 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1699 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1700 ) 1701 1702 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1703 1704 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1705 "TYPE": ("EVOLUTION",), 1706 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1707 } 1708 1709 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1710 1711 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1712 ("CALLER", "SELF", "OWNER"), tuple() 1713 ) 1714 1715 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1716 "NOT": ("ENFORCED",), 1717 "MATCH": ( 1718 "FULL", 1719 "PARTIAL", 1720 "SIMPLE", 1721 ), 1722 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1723 "USING": ( 1724 "BTREE", 1725 "HASH", 1726 ), 1727 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1728 } 1729 1730 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1731 "NO": ("OTHERS",), 1732 "CURRENT": ("ROW",), 1733 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1734 } 1735 1736 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1737 1738 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1739 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1740 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1741 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1742 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1743 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1744 ("FOR", "VERSION"): "VERSION", 1745 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1746 ("VERSION", "AS", "OF"): "VERSION", 1747 } 1748 1749 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1750 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1751 1752 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1753 1754 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1755 1756 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1757 1758 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1759 1760 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1761 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1762 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1763 1764 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1765 1766 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1767 1768 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1769 TokenType.CONSTRAINT, 1770 TokenType.FOREIGN_KEY, 1771 TokenType.INDEX, 1772 TokenType.KEY, 1773 TokenType.PRIMARY_KEY, 1774 TokenType.UNIQUE, 1775 } 1776 1777 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1778 1779 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1780 1781 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1782 1783 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1784 "FILE_FORMAT", 1785 "COPY_OPTIONS", 1786 "FORMAT_OPTIONS", 1787 "CREDENTIAL", 1788 } 1789 1790 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1791 1792 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1793 1794 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1795 1796 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1797 1798 # The style options for the DESCRIBE statement 1799 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1800 1801 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1802 1803 # The style options for the ANALYZE statement 1804 ANALYZE_STYLES: t.ClassVar = { 1805 "BUFFER_USAGE_LIMIT", 1806 "FULL", 1807 "LOCAL", 1808 "NO_WRITE_TO_BINLOG", 1809 "SAMPLE", 1810 "SKIP_LOCKED", 1811 "VERBOSE", 1812 } 1813 1814 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1815 "ALL": lambda self: self._parse_analyze_columns(), 1816 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1817 "DELETE": lambda self: self._parse_analyze_delete(), 1818 "DROP": lambda self: self._parse_analyze_histogram(), 1819 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1820 "LIST": lambda self: self._parse_analyze_list(), 1821 "PREDICATE": lambda self: self._parse_analyze_columns(), 1822 "UPDATE": lambda self: self._parse_analyze_histogram(), 1823 "VALIDATE": lambda self: self._parse_analyze_validate(), 1824 } 1825 1826 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1827 1828 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1829 1830 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1831 1832 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1833 1834 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1835 1836 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1837 1838 STRICT_CAST: t.ClassVar = True 1839 1840 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1841 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1842 # Whether an UNPIVOT outputs its value column(s) before the name column 1843 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1844 # Controls when an aggregation's name is included in a pivoted column's name: 1845 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1846 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1847 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1848 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1849 1850 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1851 1852 # Whether the table sample clause expects CSV syntax 1853 TABLESAMPLE_CSV: t.ClassVar = False 1854 1855 # The default method used for table sampling 1856 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1857 1858 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1859 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1860 1861 # Whether the TRIM function expects the characters to trim as its first argument 1862 TRIM_PATTERN_FIRST: t.ClassVar = False 1863 1864 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1865 STRING_ALIASES: t.ClassVar = False 1866 1867 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1868 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1869 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"} 1870 1871 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1872 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1873 1874 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1875 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1876 1877 # Whether the `:` operator is used to extract a value from a VARIANT column 1878 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1879 1880 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1881 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1882 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1883 1884 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1885 # If this is True and '(' is not found, the keyword will be treated as an identifier 1886 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1887 1888 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1889 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1890 1891 # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery) 1892 SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False 1893 1894 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1895 INTERVAL_SPANS: t.ClassVar = True 1896 1897 # Whether a PARTITION clause can follow a table reference 1898 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1899 1900 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1901 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1902 1903 # Whether the 'AS' keyword is optional in the CTE definition syntax 1904 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1905 1906 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1907 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1908 1909 # Whether Alter statements are allowed to contain Partition specifications 1910 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1911 1912 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1913 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1914 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1915 # as BigQuery, where all joins have the same precedence. 1916 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1917 1918 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1919 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1920 1921 # Whether map literals support arbitrary expressions as keys. 1922 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1923 # When False, keys are typically restricted to identifiers. 1924 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1925 1926 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1927 # is true for Snowflake but not for BigQuery which can also process strings 1928 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1929 1930 # Dialects like Databricks support JOINS without join criteria 1931 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 1932 ADD_JOIN_ON_TRUE: t.ClassVar = False 1933 1934 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 1935 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 1936 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 1937 1938 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 1939 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 1940 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 1941 1942 # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause, 1943 # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake) 1944 SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False 1945 1946 # Type names that denote a different type when they're quoted, so quoting has to be 1947 # preserved instead of resolving them into the built-in type of the same name. These 1948 # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR 1949 QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set() 1950 1951 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 1952 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 1953 1954 def __init__( 1955 self, 1956 error_level: ErrorLevel | None = None, 1957 error_message_context: int = 100, 1958 max_errors: int = 3, 1959 max_nodes: int = -1, 1960 dialect: DialectType = None, 1961 ): 1962 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 1963 self.error_message_context: int = error_message_context 1964 self.max_errors: int = max_errors 1965 self.max_nodes: int = max_nodes 1966 self.dialect: t.Any = _resolve_dialect(dialect) 1967 self.sql: str = "" 1968 self.errors: list[ParseError] = [] 1969 self._tokens: list[Token] = [] 1970 self._tokens_size: i64 = 0 1971 self._index: i64 = 0 1972 self._curr: Token = SENTINEL_NONE 1973 self._next: Token = SENTINEL_NONE 1974 self._prev: Token = SENTINEL_NONE 1975 self._prev_comments: list[str] = [] 1976 self._pipe_cte_counter: int = 0 1977 self._chunks: list[list[Token]] = [] 1978 self._chunk_index: i64 = 0 1979 self._node_count: int = 0 1980 1981 def reset(self) -> None: 1982 self.sql = "" 1983 self.errors = [] 1984 self._tokens = [] 1985 self._tokens_size = 0 1986 self._index = 0 1987 self._curr = SENTINEL_NONE 1988 self._next = SENTINEL_NONE 1989 self._prev = SENTINEL_NONE 1990 self._prev_comments = [] 1991 self._pipe_cte_counter = 0 1992 self._chunks = [] 1993 self._chunk_index = 0 1994 self._node_count = 0 1995 1996 def _advance(self, times: i64 = 1) -> None: 1997 index = self._index + times 1998 self._index = index 1999 tokens = self._tokens 2000 size = self._tokens_size 2001 self._curr = tokens[index] if index < size else SENTINEL_NONE 2002 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 2003 2004 if index > 0: 2005 prev = tokens[index - 1] 2006 self._prev = prev 2007 self._prev_comments = prev.comments 2008 else: 2009 self._prev = SENTINEL_NONE 2010 self._prev_comments = [] 2011 2012 def _advance_chunk(self) -> None: 2013 self._index = -1 2014 self._tokens = self._chunks[self._chunk_index] 2015 self._tokens_size = i64(len(self._tokens)) 2016 self._chunk_index += 1 2017 self._advance() 2018 2019 def _retreat(self, index: i64) -> None: 2020 if index != self._index: 2021 self._advance(index - self._index) 2022 2023 def _add_comments(self, expression: exp.Expr | None) -> None: 2024 if expression and self._prev_comments: 2025 expression.add_comments(self._prev_comments) 2026 self._prev_comments = [] 2027 2028 def _match( 2029 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2030 ) -> bool: 2031 if self._curr.token_type == token_type: 2032 if advance: 2033 self._advance() 2034 self._add_comments(expression) 2035 return True 2036 return False 2037 2038 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2039 if self._curr.token_type in types: 2040 if advance: 2041 self._advance() 2042 return True 2043 return False 2044 2045 def _match_pair( 2046 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2047 ) -> bool: 2048 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2049 if advance: 2050 self._advance(2) 2051 return True 2052 return False 2053 2054 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2055 if ( 2056 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2057 and self._curr.text.upper() in texts 2058 ): 2059 if advance: 2060 self._advance() 2061 return True 2062 return False 2063 2064 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2065 index = self._index 2066 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2067 for text in texts: 2068 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2069 self._advance() 2070 else: 2071 self._retreat(index) 2072 return False 2073 2074 if not advance: 2075 self._retreat(index) 2076 2077 return True 2078 2079 def _is_connected(self) -> bool: 2080 prev = self._prev 2081 curr = self._curr 2082 return bool(prev and curr and prev.end + 1 == curr.start) 2083 2084 def _find_sql(self, start: Token, end: Token) -> str: 2085 return self.sql[start.start : end.end + 1] 2086 2087 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2088 token = token or self._curr or self._prev or Token.string("") 2089 formatted_sql, start_context, highlight, end_context = highlight_sql( 2090 sql=self.sql, 2091 positions=[(token.start, token.end)], 2092 context_length=self.error_message_context, 2093 ) 2094 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2095 2096 error = ParseError.new( 2097 formatted_message, 2098 description=message, 2099 line=token.line, 2100 col=token.col, 2101 start_context=start_context, 2102 highlight=highlight, 2103 end_context=end_context, 2104 ) 2105 2106 if self.error_level == ErrorLevel.IMMEDIATE: 2107 raise error 2108 2109 self.errors.append(error) 2110 2111 def validate_expression(self, expression: E, args: list | None = None) -> E: 2112 if self.max_nodes > -1: 2113 self._node_count += 1 2114 if self._node_count > self.max_nodes: 2115 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2116 if self.error_level != ErrorLevel.IGNORE: 2117 for error_message in expression.error_messages(args): 2118 self.raise_error(error_message) 2119 return expression 2120 2121 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2122 index = self._index 2123 error_level = self.error_level 2124 this: T | None = None 2125 2126 self.error_level = ErrorLevel.IMMEDIATE 2127 try: 2128 this = parse_method() 2129 except ParseError: 2130 this = None 2131 finally: 2132 if not this or retreat: 2133 self._retreat(index) 2134 self.error_level = error_level 2135 2136 return this 2137 2138 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2139 """ 2140 Parses a list of tokens and returns a list of syntax trees, one tree 2141 per parsed SQL statement. 2142 2143 Args: 2144 raw_tokens: The list of tokens. 2145 sql: The original SQL string. 2146 2147 Returns: 2148 The list of the produced syntax trees. 2149 """ 2150 return self._parse( 2151 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2152 ) 2153 2154 def parse_into( 2155 self, 2156 expression_types: exp.IntoType, 2157 raw_tokens: list[Token], 2158 sql: str | None = None, 2159 ) -> list[exp.Expr | None]: 2160 """ 2161 Parses a list of tokens into a given Expr type. If a collection of Expr 2162 types is given instead, this method will try to parse the token list into each one 2163 of them, stopping at the first for which the parsing succeeds. 2164 2165 Args: 2166 expression_types: The expression type(s) to try and parse the token list into. 2167 raw_tokens: The list of tokens. 2168 sql: The original SQL string, used to produce helpful debug messages. 2169 2170 Returns: 2171 The target Expr. 2172 """ 2173 errors = [] 2174 for expression_type in ensure_list(expression_types): 2175 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2176 if not parser: 2177 raise TypeError(f"No parser registered for {expression_type}") 2178 2179 try: 2180 return self._parse(parser, raw_tokens, sql) 2181 except ParseError as e: 2182 e.errors[0]["into_expression"] = expression_type 2183 errors.append(e) 2184 2185 raise ParseError( 2186 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2187 errors=merge_errors(errors), 2188 ) from errors[-1] 2189 2190 def check_errors(self) -> None: 2191 """Logs or raises any found errors, depending on the chosen error level setting.""" 2192 if self.error_level == ErrorLevel.WARN: 2193 for error in self.errors: 2194 logger.error(str(error)) 2195 elif self.error_level == ErrorLevel.RAISE and self.errors: 2196 raise ParseError( 2197 concat_messages(self.errors, self.max_errors), 2198 errors=merge_errors(self.errors), 2199 ) 2200 2201 def expression( 2202 self, 2203 instance: E, 2204 token: Token | None = None, 2205 comments: list[str] | None = None, 2206 ) -> E: 2207 if token: 2208 instance.update_positions(token) 2209 instance.add_comments(comments) if comments else self._add_comments(instance) 2210 if not instance.is_primitive: 2211 instance = self.validate_expression(instance) 2212 return instance 2213 2214 def _parse_batch_statements( 2215 self, 2216 parse_method: t.Callable[[Parser], exp.Expr | None], 2217 sep_first_statement: bool = True, 2218 ) -> list[exp.Expr | None]: 2219 expressions = [] 2220 2221 # Chunkification binds if/while statements with the first statement of the body 2222 if sep_first_statement: 2223 self._match(TokenType.BEGIN) 2224 expressions.append(parse_method(self)) 2225 2226 chunks_length = len(self._chunks) 2227 while self._chunk_index < chunks_length: 2228 self._advance_chunk() 2229 2230 if self._match(TokenType.ELSE, advance=False): 2231 return expressions 2232 2233 if expressions and not self._next and self._match(TokenType.END): 2234 expressions.append(exp.EndStatement()) 2235 continue 2236 2237 expressions.append(parse_method(self)) 2238 2239 if self._index < self._tokens_size: 2240 self.raise_error("Invalid expression / Unexpected token") 2241 2242 self.check_errors() 2243 2244 return expressions 2245 2246 def _parse( 2247 self, 2248 parse_method: t.Callable[[Parser], exp.Expr | None], 2249 raw_tokens: list[Token], 2250 sql: str | None = None, 2251 ) -> list[exp.Expr | None]: 2252 self.reset() 2253 self.sql = sql or "" 2254 2255 total = len(raw_tokens) 2256 chunks: list[list[Token]] = [[]] 2257 2258 for i, token in enumerate(raw_tokens): 2259 if token.token_type == TokenType.SEMICOLON: 2260 if token.comments: 2261 chunks.append([token]) 2262 2263 if i < total - 1: 2264 chunks.append([]) 2265 else: 2266 chunks[-1].append(token) 2267 2268 self._chunks = chunks 2269 2270 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2271 2272 def _warn_unsupported(self) -> None: 2273 if self._tokens_size <= 1: 2274 return 2275 2276 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2277 # interested in emitting a warning for the one being currently processed. 2278 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2279 2280 logger.warning( 2281 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2282 ) 2283 2284 def _parse_command(self) -> exp.Command: 2285 self._warn_unsupported() 2286 comments = self._prev_comments 2287 return self.expression( 2288 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2289 comments=comments, 2290 ) 2291 2292 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2293 start = self._prev 2294 exists = self._parse_exists() if allow_exists else None 2295 2296 self._match(TokenType.ON) 2297 2298 materialized = self._match_text_seq("MATERIALIZED") 2299 kind = self._match_set(self.CREATABLES) and self._prev 2300 if not kind: 2301 return self._parse_as_command(start) 2302 2303 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2304 this = self._parse_user_defined_function(kind=kind.token_type) 2305 elif kind.token_type == TokenType.TABLE: 2306 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2307 elif kind.token_type == TokenType.COLUMN: 2308 this = self._parse_column() 2309 else: 2310 this = self._parse_table_parts(schema=True) 2311 2312 self._match(TokenType.IS) 2313 2314 return self.expression( 2315 exp.Comment( 2316 this=this, 2317 kind=kind.text, 2318 expression=self._parse_string(), 2319 exists=exists, 2320 materialized=materialized, 2321 ) 2322 ) 2323 2324 def _parse_to_table( 2325 self, 2326 ) -> exp.ToTableProperty: 2327 table = self._parse_table_parts(schema=True) 2328 return self.expression(exp.ToTableProperty(this=table)) 2329 2330 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2331 def _parse_ttl(self) -> exp.Expr: 2332 def _parse_ttl_action() -> exp.Expr | None: 2333 this = self._parse_bitwise() 2334 2335 if self._match_text_seq("DELETE"): 2336 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2337 if self._match_text_seq("RECOMPRESS"): 2338 return self.expression( 2339 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2340 ) 2341 if self._match_text_seq("TO", "DISK"): 2342 return self.expression( 2343 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2344 ) 2345 if self._match_text_seq("TO", "VOLUME"): 2346 return self.expression( 2347 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2348 ) 2349 2350 return this 2351 2352 expressions = self._parse_csv(_parse_ttl_action) 2353 where = self._parse_where() 2354 group = self._parse_group() 2355 2356 aggregates = None 2357 if group and self._match(TokenType.SET): 2358 aggregates = self._parse_csv(self._parse_set_item) 2359 2360 return self.expression( 2361 exp.MergeTreeTTL( 2362 expressions=expressions, where=where, group=group, aggregates=aggregates 2363 ) 2364 ) 2365 2366 def _parse_condition(self) -> exp.Expr | None: 2367 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2368 2369 def _parse_block(self) -> exp.Block: 2370 return self.expression( 2371 exp.Block( 2372 expressions=self._parse_batch_statements( 2373 parse_method=lambda self: self._parse_statement() 2374 ) 2375 ) 2376 ) 2377 2378 def _parse_whileblock(self) -> exp.WhileBlock: 2379 return self.expression( 2380 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2381 ) 2382 2383 def _parse_statement(self) -> exp.Expr | None: 2384 if not self._curr: 2385 return None 2386 2387 if self._match_set(self.STATEMENT_PARSERS): 2388 comments = self._prev_comments 2389 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2390 stmt.add_comments(comments, prepend=True) 2391 return stmt 2392 2393 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2394 return self._parse_command() 2395 2396 if self._match_text_seq("WHILE"): 2397 return self._parse_whileblock() 2398 2399 expression = self._parse_expression() 2400 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2401 2402 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2403 expression = self._parse_pipe_syntax_query(expression) 2404 2405 return self._parse_query_modifiers(expression) 2406 2407 def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command: 2408 start = self._prev 2409 temporary = self._match(TokenType.TEMPORARY) 2410 materialized = self._match_text_seq("MATERIALIZED") 2411 iceberg = self._match_text_seq("ICEBERG") 2412 2413 kind = self._match_set(self.CREATABLES) and self._prev.text.upper() 2414 if not kind or (iceberg and kind and kind != "TABLE"): 2415 return self._parse_as_command(start) 2416 2417 concurrently = self._match_text_seq("CONCURRENTLY") 2418 if_exists = exists or self._parse_exists() 2419 2420 tables: exp.Expr | list[exp.Expr] | None 2421 if kind == "COLUMN": 2422 tables = self._parse_column() 2423 elif kind in ("TABLE", "VIEW"): 2424 tables = self._parse_csv(lambda: self._parse_table_parts(schema=True)) 2425 else: 2426 tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2427 2428 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 2429 2430 if self._match(TokenType.L_PAREN, advance=False): 2431 expressions = self._parse_wrapped_csv(self._parse_types) 2432 else: 2433 expressions = None 2434 2435 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2436 2437 return self.expression( 2438 exp.Drop( 2439 exists=if_exists, 2440 tables=ensure_list(tables), 2441 expressions=expressions, 2442 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2443 temporary=temporary, 2444 materialized=materialized, 2445 cascade=cascade_or_restrict == "CASCADE", 2446 restrict=cascade_or_restrict == "RESTRICT", 2447 constraints=self._match_text_seq("CONSTRAINTS"), 2448 purge=self._match_text_seq("PURGE"), 2449 cluster=cluster, 2450 concurrently=concurrently, 2451 sync=self._match_text_seq("SYNC"), 2452 iceberg=iceberg, 2453 force=self._match_text_seq("FORCE"), 2454 ) 2455 ) 2456 2457 def _parse_exists(self, not_: bool = False) -> bool | None: 2458 return ( 2459 self._match_text_seq("IF") 2460 and (not not_ or self._match(TokenType.NOT)) 2461 and self._match(TokenType.EXISTS) 2462 ) 2463 2464 def _parse_create(self) -> exp.Create | exp.Command: 2465 # Note: this can't be None because we've matched a statement parser 2466 start = self._prev 2467 2468 replace = ( 2469 start.token_type == TokenType.REPLACE 2470 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2471 or self._match_pair(TokenType.OR, TokenType.ALTER) 2472 ) 2473 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2474 2475 unique = self._match(TokenType.UNIQUE) 2476 2477 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2478 clustered = True 2479 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2480 "COLUMNSTORE" 2481 ): 2482 clustered = False 2483 else: 2484 clustered = None 2485 2486 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2487 self._advance() 2488 2489 properties = None 2490 create_token = self._match_set(self.CREATABLES) and self._prev 2491 2492 if not create_token: 2493 # exp.Properties.Location.POST_CREATE 2494 properties = self._parse_properties() 2495 create_token = self._match_set(self.CREATABLES) and self._prev 2496 2497 if not properties or not create_token: 2498 return self._parse_as_command(start) 2499 2500 create_token_type = t.cast(Token, create_token).token_type 2501 2502 concurrently = self._match_text_seq("CONCURRENTLY") 2503 exists = self._parse_exists(not_=True) 2504 this = None 2505 expression: exp.Expr | None = None 2506 indexes = None 2507 no_schema_binding = None 2508 begin = None 2509 clone = None 2510 2511 def extend_props(temp_props: exp.Properties | None) -> None: 2512 nonlocal properties 2513 if properties and temp_props: 2514 properties.expressions.extend(temp_props.expressions) 2515 elif temp_props: 2516 properties = temp_props 2517 2518 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2519 this = self._parse_user_defined_function(kind=create_token_type) 2520 2521 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2522 extend_props(self._parse_properties()) 2523 2524 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2525 2526 if ( 2527 not expression 2528 and create_token_type == TokenType.FUNCTION 2529 and isinstance(this, exp.UserDefinedFunction) 2530 and this.args.get("wrapped") 2531 ): 2532 pre_table_index = self._index 2533 is_table = self._match(TokenType.TABLE) 2534 2535 expression = self._parse_expression() 2536 overload_mode = bool( 2537 expression 2538 and self._curr.token_type == TokenType.COMMA 2539 and self._next.token_type == TokenType.L_PAREN 2540 ) 2541 if not overload_mode: 2542 self._retreat(pre_table_index) 2543 is_table = False 2544 expression = None 2545 else: 2546 is_table = False 2547 overload_mode = False 2548 2549 extend_props(self._parse_function_properties()) 2550 2551 if not expression: 2552 if self._match(TokenType.COMMAND): 2553 expression = self._parse_as_command(self._prev) 2554 else: 2555 begin = self._match(TokenType.BEGIN) 2556 return_ = self._match_text_seq("RETURN") 2557 2558 if self._match(TokenType.STRING, advance=False): 2559 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2560 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2561 expression = self._parse_string() 2562 extend_props(self._parse_properties()) 2563 else: 2564 expression = ( 2565 self._parse_user_defined_function_expression() 2566 if create_token_type == TokenType.FUNCTION 2567 else self._parse_block() 2568 ) 2569 2570 if return_: 2571 expression = self.expression(exp.Return(this=expression)) 2572 2573 if overload_mode and expression: 2574 expression = self._parse_macro_overloads( 2575 t.cast(exp.UserDefinedFunction, this), expression, is_table 2576 ) 2577 elif create_token_type == TokenType.INDEX: 2578 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2579 if not self._match(TokenType.ON): 2580 index = self._parse_id_var() 2581 anonymous = False 2582 else: 2583 index = None 2584 anonymous = True 2585 2586 this = self._parse_index(index=index, anonymous=anonymous) 2587 elif ( 2588 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2589 ) or create_token_type == TokenType.TRIGGER: 2590 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2591 create_token = self._prev 2592 2593 trigger_name = self._parse_id_var() 2594 if not trigger_name: 2595 return self._parse_as_command(start) 2596 2597 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2598 timing = timing_var.this if timing_var else None 2599 if not timing: 2600 return self._parse_as_command(start) 2601 2602 events = self._parse_trigger_events() 2603 if not self._match(TokenType.ON): 2604 self.raise_error("Expected ON in trigger definition") 2605 2606 table = self._parse_table_parts() 2607 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2608 deferrable, initially = self._parse_trigger_deferrable() 2609 referencing = self._parse_trigger_referencing() 2610 for_each = self._parse_trigger_for_each() 2611 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2612 self._parse_disjunction, optional=True 2613 ) 2614 execute = self._parse_trigger_execute() 2615 2616 if execute is None: 2617 return self._parse_as_command(start) 2618 2619 trigger_props = self.expression( 2620 exp.TriggerProperties( 2621 table=table, 2622 timing=timing, 2623 events=events, 2624 execute=execute, 2625 constraint=is_constraint, 2626 referenced_table=referenced_table, 2627 deferrable=deferrable, 2628 initially=initially, 2629 referencing=referencing, 2630 for_each=for_each, 2631 when=when, 2632 ) 2633 ) 2634 2635 this = trigger_name 2636 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2637 elif create_token_type == TokenType.TYPE: 2638 this = self._parse_table_parts(schema=True) 2639 if not this or not self._match(TokenType.ALIAS): 2640 return self._parse_as_command(start) 2641 2642 if self._match(TokenType.ENUM): 2643 expression = exp.DataType( 2644 this=exp.DType.ENUM, 2645 expressions=self._parse_wrapped_csv(self._parse_string), 2646 ) 2647 elif self._match(TokenType.L_PAREN, advance=False): 2648 expression = self._parse_schema() 2649 else: 2650 return self._parse_as_command(start) 2651 elif create_token_type in self.DB_CREATABLES: 2652 table_parts = self._parse_table_parts( 2653 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2654 ) 2655 2656 # exp.Properties.Location.POST_NAME 2657 self._match(TokenType.COMMA) 2658 extend_props(self._parse_properties(before=True)) 2659 2660 this = self._parse_schema(this=table_parts) 2661 2662 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2663 extend_props(self._parse_properties()) 2664 2665 has_alias = self._match(TokenType.ALIAS) 2666 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2667 # exp.Properties.Location.POST_ALIAS 2668 extend_props(self._parse_properties()) 2669 2670 if create_token_type == TokenType.SEQUENCE: 2671 expression = self._parse_types() 2672 props = self._parse_properties() 2673 if props: 2674 sequence_props = exp.SequenceProperties() 2675 options = [] 2676 for prop in props: 2677 if isinstance(prop, exp.SequenceProperties): 2678 for arg, value in prop.args.items(): 2679 if arg == "options": 2680 options.extend(value) 2681 else: 2682 sequence_props.set(arg, value) 2683 prop.pop() 2684 2685 if options: 2686 sequence_props.set("options", options) 2687 2688 props.append("expressions", sequence_props) 2689 extend_props(props) 2690 else: 2691 expression = self._parse_ddl_select() 2692 2693 # Some dialects also support using a table as an alias instead of a SELECT. 2694 # Here we fallback to this as an alternative. 2695 if not expression and has_alias: 2696 expression = self._try_parse(self._parse_table_parts) 2697 2698 if create_token_type == TokenType.TABLE: 2699 # exp.Properties.Location.POST_EXPRESSION 2700 extend_props(self._parse_properties()) 2701 2702 indexes = [] 2703 while True: 2704 index = self._parse_index() 2705 2706 # exp.Properties.Location.POST_INDEX 2707 extend_props(self._parse_properties()) 2708 if not index: 2709 break 2710 else: 2711 self._match(TokenType.COMMA) 2712 indexes.append(index) 2713 elif create_token_type == TokenType.VIEW: 2714 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2715 no_schema_binding = True 2716 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2717 extend_props(self._parse_properties()) 2718 2719 shallow = self._match_text_seq("SHALLOW") 2720 2721 if self._match_texts(self.CLONE_KEYWORDS): 2722 copy = self._prev.text.lower() == "copy" 2723 clone = self.expression( 2724 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2725 ) 2726 2727 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2728 return self._parse_as_command(start) 2729 2730 create_kind_text = create_token.text.upper() 2731 return self.expression( 2732 exp.Create( 2733 this=this, 2734 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2735 replace=replace, 2736 refresh=refresh, 2737 unique=unique, 2738 expression=expression, 2739 exists=exists, 2740 properties=properties, 2741 indexes=indexes, 2742 no_schema_binding=no_schema_binding, 2743 begin=begin, 2744 clone=clone, 2745 concurrently=concurrently, 2746 clustered=clustered, 2747 ) 2748 ) 2749 2750 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2751 seq = exp.SequenceProperties() 2752 2753 options = [] 2754 index = self._index 2755 2756 while self._curr: 2757 self._match(TokenType.COMMA) 2758 if self._match_text_seq("INCREMENT"): 2759 self._match_text_seq("BY") 2760 self._match_text_seq("=") 2761 seq.set("increment", self._parse_term()) 2762 elif self._match_text_seq("MINVALUE"): 2763 seq.set("minvalue", self._parse_term()) 2764 elif self._match_text_seq("MAXVALUE"): 2765 seq.set("maxvalue", self._parse_term()) 2766 elif self._match_text_seq("START"): 2767 self._match_text_seq("WITH") 2768 self._match_text_seq("=") 2769 seq.set("start", self._parse_term()) 2770 elif self._match_text_seq("CACHE"): 2771 # T-SQL allows empty CACHE which is initialized dynamically 2772 seq.set("cache", self._parse_number() or True) 2773 elif self._match_text_seq("OWNED", "BY"): 2774 # "OWNED BY NONE" is the default 2775 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2776 else: 2777 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2778 if opt: 2779 options.append(opt) 2780 else: 2781 break 2782 2783 seq.set("options", options if options else None) 2784 return None if self._index == index else seq 2785 2786 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2787 events = [] 2788 2789 while True: 2790 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2791 2792 if not event_type: 2793 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2794 2795 columns = ( 2796 self._parse_csv(self._parse_column) 2797 if event_type == "UPDATE" and self._match_text_seq("OF") 2798 else None 2799 ) 2800 2801 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2802 2803 if not self._match(TokenType.OR): 2804 break 2805 2806 return events 2807 2808 def _parse_trigger_deferrable( 2809 self, 2810 ) -> tuple[str | None, str | None]: 2811 deferrable_var = self._parse_var_from_options( 2812 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2813 ) 2814 deferrable = deferrable_var.this if deferrable_var else None 2815 2816 initially = None 2817 if deferrable and self._match_text_seq("INITIALLY"): 2818 initially = ( 2819 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2820 ) 2821 2822 return deferrable, initially 2823 2824 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2825 if not self._match_text_seq(keyword): 2826 return None 2827 if not self._match_text_seq("TABLE"): 2828 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2829 self._match_text_seq("AS") 2830 return self._parse_id_var() 2831 2832 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2833 if not self._match_text_seq("REFERENCING"): 2834 return None 2835 2836 old_alias = None 2837 new_alias = None 2838 2839 while True: 2840 if alias := self._parse_trigger_referencing_clause("OLD"): 2841 if old_alias is not None: 2842 self.raise_error("Duplicate OLD clause in REFERENCING") 2843 old_alias = alias 2844 elif alias := self._parse_trigger_referencing_clause("NEW"): 2845 if new_alias is not None: 2846 self.raise_error("Duplicate NEW clause in REFERENCING") 2847 new_alias = alias 2848 else: 2849 break 2850 2851 if old_alias is None and new_alias is None: 2852 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2853 2854 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2855 2856 def _parse_trigger_for_each(self) -> str | None: 2857 if not self._match_text_seq("FOR", "EACH"): 2858 return None 2859 2860 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2861 2862 def _parse_trigger_execute(self) -> exp.TriggerExecute | None: 2863 if not self._match(TokenType.EXECUTE): 2864 return None 2865 2866 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2867 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2868 2869 func_call = self._parse_column() 2870 return self.expression(exp.TriggerExecute(this=func_call)) 2871 2872 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2873 # only used for teradata currently 2874 self._match(TokenType.COMMA) 2875 2876 kwargs = { 2877 "no": self._match_text_seq("NO"), 2878 "dual": self._match_text_seq("DUAL"), 2879 "before": self._match_text_seq("BEFORE"), 2880 "default": self._match_text_seq("DEFAULT"), 2881 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2882 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2883 "after": self._match_text_seq("AFTER"), 2884 "minimum": self._match_texts(("MIN", "MINIMUM")), 2885 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2886 } 2887 2888 if self._match_texts(self.PROPERTY_PARSERS): 2889 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2890 try: 2891 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2892 except TypeError: 2893 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2894 2895 if self._match_text_seq("CHARACTER", "SET"): 2896 return self._parse_character_set(default=bool(kwargs["default"])) 2897 2898 return None 2899 2900 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2901 return self._parse_wrapped_csv(self._parse_property) 2902 2903 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2904 if self._match_texts(self.PROPERTY_PARSERS): 2905 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2906 2907 if self._match_text_seq("CHARACTER", "SET"): 2908 return self._parse_character_set() 2909 2910 if self._match(TokenType.DEFAULT): 2911 if self._match_texts(self.PROPERTY_PARSERS): 2912 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 2913 2914 if self._match_text_seq("CHARACTER", "SET"): 2915 return self._parse_character_set(default=True) 2916 2917 if self._match_text_seq("COMPOUND", "SORTKEY"): 2918 return self._parse_sortkey(compound=True) 2919 2920 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 2921 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 2922 2923 if self._match_text_seq("NOT", "DETERMINISTIC"): 2924 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 2925 2926 index = self._index 2927 2928 seq_props = self._parse_sequence_properties() 2929 if seq_props: 2930 return seq_props 2931 2932 self._retreat(index) 2933 return self._parse_key_value_property() 2934 2935 def _parse_key_value_property( 2936 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 2937 ) -> exp.Property | None: 2938 index = self._index 2939 key = self._parse_column() 2940 2941 if not self._match(TokenType.EQ): 2942 self._retreat(index) 2943 return None 2944 2945 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 2946 if isinstance(key, exp.Column): 2947 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 2948 2949 value = ( 2950 parse_value() 2951 if parse_value 2952 else self._parse_bitwise() or self._parse_var(any_token=True) 2953 ) 2954 2955 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 2956 if isinstance(value, exp.Column): 2957 value = exp.var(value.name) 2958 2959 return self.expression(exp.Property(this=key, value=value)) 2960 2961 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 2962 if self._match_text_seq("BY"): 2963 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 2964 2965 self._match(TokenType.ALIAS) 2966 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 2967 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 2968 2969 return self.expression( 2970 exp.FileFormatProperty( 2971 this=( 2972 self.expression( 2973 exp.InputOutputFormat( 2974 input_format=input_format, output_format=output_format 2975 ) 2976 ) 2977 if input_format or output_format 2978 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 2979 ), 2980 hive_format=True, 2981 ) 2982 ) 2983 2984 def _parse_unquoted_field(self) -> exp.Expr | None: 2985 field = self._parse_field() 2986 if isinstance(field, exp.Identifier) and not field.quoted: 2987 field = exp.var(field) 2988 2989 return field 2990 2991 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 2992 self._match(TokenType.EQ) 2993 self._match(TokenType.ALIAS) 2994 2995 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 2996 2997 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 2998 properties = [] 2999 while True: 3000 if before: 3001 prop = self._parse_property_before() 3002 else: 3003 prop = self._parse_property() 3004 if not prop: 3005 break 3006 for p in ensure_list(prop): 3007 properties.append(p) 3008 3009 if properties: 3010 return self.expression(exp.Properties(expressions=properties)) 3011 3012 return None 3013 3014 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3015 return self.expression( 3016 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3017 ) 3018 3019 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3020 return self.expression( 3021 exp.SqlSecurityProperty( 3022 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3023 ) 3024 ) 3025 3026 def _parse_settings_property(self) -> exp.SettingsProperty: 3027 return self.expression( 3028 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3029 ) 3030 3031 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3032 if not self._match_text_seq("ON", "NULL", "INPUT"): 3033 self._retreat(self._index - 1) 3034 return None 3035 3036 return self.expression(exp.CalledOnNullInputProperty()) 3037 3038 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3039 if self._index >= 2: 3040 pre_volatile_token = self._tokens[self._index - 2] 3041 else: 3042 pre_volatile_token = None 3043 3044 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3045 return exp.VolatileProperty() 3046 3047 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3048 3049 def _parse_retention_period(self) -> exp.Var: 3050 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3051 number = self._parse_number() 3052 number_str = f"{number} " if number else "" 3053 unit = self._parse_var(any_token=True) 3054 return exp.var(f"{number_str}{unit}") 3055 3056 def _parse_system_versioning_property( 3057 self, with_: bool = False 3058 ) -> exp.WithSystemVersioningProperty: 3059 self._match(TokenType.EQ) 3060 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3061 3062 if self._match_text_seq("OFF"): 3063 prop.set("on", False) 3064 return prop 3065 3066 self._match(TokenType.ON) 3067 if self._match(TokenType.L_PAREN): 3068 while self._curr and not self._match(TokenType.R_PAREN): 3069 if self._match_text_seq("HISTORY_TABLE", "="): 3070 prop.set("this", self._parse_table_parts()) 3071 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3072 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3073 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3074 prop.set("retention_period", self._parse_retention_period()) 3075 3076 self._match(TokenType.COMMA) 3077 3078 return prop 3079 3080 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3081 self._match(TokenType.EQ) 3082 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3083 prop = self.expression(exp.DataDeletionProperty(on=on)) 3084 3085 if self._match(TokenType.L_PAREN): 3086 while self._curr and not self._match(TokenType.R_PAREN): 3087 if self._match_text_seq("FILTER_COLUMN", "="): 3088 prop.set("filter_column", self._parse_column()) 3089 elif self._match_text_seq("RETENTION_PERIOD", "="): 3090 prop.set("retention_period", self._parse_retention_period()) 3091 3092 self._match(TokenType.COMMA) 3093 3094 return prop 3095 3096 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3097 kind = "HASH" 3098 expressions: list[exp.Expr] | None = None 3099 if self._match_text_seq("BY", "HASH"): 3100 expressions = self._parse_wrapped_csv(self._parse_id_var) 3101 elif self._match_text_seq("BY", "RANDOM"): 3102 kind = "RANDOM" 3103 3104 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3105 buckets: exp.Expr | None = None 3106 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3107 buckets = self._parse_number() 3108 3109 return self.expression( 3110 exp.DistributedByProperty( 3111 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3112 ) 3113 ) 3114 3115 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3116 self._match_text_seq("KEY") 3117 expressions = self._parse_wrapped_id_vars() 3118 return self.expression(expr_type(expressions=expressions)) 3119 3120 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3121 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3122 prop = self._parse_system_versioning_property(with_=True) 3123 self._match_r_paren() 3124 return prop 3125 3126 if self._match(TokenType.L_PAREN, advance=False): 3127 result: list[exp.Expr] = [] 3128 for i in self._parse_wrapped_properties(): 3129 result.extend(i) if isinstance(i, list) else result.append(i) 3130 return result 3131 3132 if self._match_text_seq("JOURNAL"): 3133 return self._parse_withjournaltable() 3134 3135 if self._match_texts(self.VIEW_ATTRIBUTES): 3136 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3137 3138 if self._match_text_seq("DATA"): 3139 return self._parse_withdata(no=False) 3140 elif self._match_text_seq("NO", "DATA"): 3141 return self._parse_withdata(no=True) 3142 3143 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3144 return self._parse_serde_properties(with_=True) 3145 3146 if self._match(TokenType.SCHEMA): 3147 return self.expression( 3148 exp.WithSchemaBindingProperty( 3149 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3150 ) 3151 ) 3152 3153 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3154 return self.expression( 3155 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3156 ) 3157 3158 if not self._next: 3159 return None 3160 3161 return self._parse_withisolatedloading() 3162 3163 def _parse_procedure_option(self) -> exp.Expr | None: 3164 if self._match_text_seq("EXECUTE", "AS"): 3165 return self.expression( 3166 exp.ExecuteAsProperty( 3167 this=self._parse_var_from_options( 3168 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3169 ) 3170 or self._parse_string() 3171 ) 3172 ) 3173 3174 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3175 3176 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3177 def _parse_definer(self) -> exp.DefinerProperty | None: 3178 self._match(TokenType.EQ) 3179 3180 user = self._parse_id_var() 3181 self._match(TokenType.PARAMETER) 3182 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3183 3184 if not user or not host: 3185 return None 3186 3187 return exp.DefinerProperty(this=f"{user}@{host}") 3188 3189 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3190 self._match(TokenType.TABLE) 3191 self._match(TokenType.EQ) 3192 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3193 3194 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3195 return self.expression(exp.LogProperty(no=no)) 3196 3197 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3198 return self.expression(exp.JournalProperty(**kwargs)) 3199 3200 def _parse_checksum(self) -> exp.ChecksumProperty: 3201 self._match(TokenType.EQ) 3202 3203 on = None 3204 if self._match(TokenType.ON): 3205 on = True 3206 elif self._match_text_seq("OFF"): 3207 on = False 3208 3209 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3210 3211 def _parse_cluster(self) -> exp.Cluster: 3212 self._match(TokenType.CLUSTER_BY) 3213 return self.expression( 3214 exp.Cluster( 3215 expressions=self._parse_csv(self._parse_column), 3216 ) 3217 ) 3218 3219 def _parse_cluster_property(self) -> exp.ClusterProperty: 3220 return self.expression( 3221 exp.ClusterProperty( 3222 expressions=self._parse_wrapped_csv(self._parse_column), 3223 ) 3224 ) 3225 3226 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3227 self._match_text_seq("BY") 3228 3229 self._match_l_paren() 3230 expressions = self._parse_csv(self._parse_column) 3231 self._match_r_paren() 3232 3233 if self._match_text_seq("SORTED", "BY"): 3234 self._match_l_paren() 3235 sorted_by = self._parse_csv(self._parse_ordered) 3236 self._match_r_paren() 3237 else: 3238 sorted_by = None 3239 3240 self._match(TokenType.INTO) 3241 buckets = self._parse_number() 3242 self._match_text_seq("BUCKETS") 3243 3244 return self.expression( 3245 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3246 ) 3247 3248 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3249 if not self._match_text_seq("GRANTS"): 3250 self._retreat(self._index - 1) 3251 return None 3252 3253 return self.expression(exp.CopyGrantsProperty()) 3254 3255 def _parse_freespace(self) -> exp.FreespaceProperty: 3256 self._match(TokenType.EQ) 3257 return self.expression( 3258 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3259 ) 3260 3261 def _parse_mergeblockratio( 3262 self, no: bool = False, default: bool = False 3263 ) -> exp.MergeBlockRatioProperty: 3264 if self._match(TokenType.EQ): 3265 return self.expression( 3266 exp.MergeBlockRatioProperty( 3267 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3268 ) 3269 ) 3270 3271 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3272 3273 def _parse_datablocksize( 3274 self, 3275 default: bool | None = None, 3276 minimum: bool | None = None, 3277 maximum: bool | None = None, 3278 ) -> exp.DataBlocksizeProperty: 3279 self._match(TokenType.EQ) 3280 size = self._parse_number() 3281 3282 units = None 3283 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3284 units = self._prev.text 3285 3286 return self.expression( 3287 exp.DataBlocksizeProperty( 3288 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3289 ) 3290 ) 3291 3292 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3293 self._match(TokenType.EQ) 3294 always = self._match_text_seq("ALWAYS") 3295 manual = self._match_text_seq("MANUAL") 3296 never = self._match_text_seq("NEVER") 3297 default = self._match_text_seq("DEFAULT") 3298 3299 autotemp = None 3300 if self._match_text_seq("AUTOTEMP"): 3301 autotemp = self._parse_schema() 3302 3303 return self.expression( 3304 exp.BlockCompressionProperty( 3305 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3306 ) 3307 ) 3308 3309 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3310 index = self._index 3311 no = self._match_text_seq("NO") 3312 concurrent = self._match_text_seq("CONCURRENT") 3313 3314 if not self._match_text_seq("ISOLATED", "LOADING"): 3315 self._retreat(index) 3316 return None 3317 3318 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3319 return self.expression( 3320 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3321 ) 3322 3323 def _parse_locking(self) -> exp.LockingProperty: 3324 if self._match(TokenType.TABLE): 3325 kind = "TABLE" 3326 elif self._match(TokenType.VIEW): 3327 kind = "VIEW" 3328 elif self._match(TokenType.ROW): 3329 kind = "ROW" 3330 elif self._match_text_seq("DATABASE"): 3331 kind = "DATABASE" 3332 else: 3333 kind = None 3334 3335 if kind in ("DATABASE", "TABLE", "VIEW"): 3336 this = self._parse_table_parts() 3337 else: 3338 this = None 3339 3340 if self._match(TokenType.FOR): 3341 for_or_in = "FOR" 3342 elif self._match(TokenType.IN): 3343 for_or_in = "IN" 3344 else: 3345 for_or_in = None 3346 3347 if self._match_text_seq("ACCESS"): 3348 lock_type = "ACCESS" 3349 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3350 lock_type = "EXCLUSIVE" 3351 elif self._match_text_seq("SHARE"): 3352 lock_type = "SHARE" 3353 elif self._match_text_seq("READ"): 3354 lock_type = "READ" 3355 elif self._match_text_seq("WRITE"): 3356 lock_type = "WRITE" 3357 elif self._match_text_seq("CHECKSUM"): 3358 lock_type = "CHECKSUM" 3359 else: 3360 lock_type = None 3361 3362 override = self._match_text_seq("OVERRIDE") 3363 3364 return self.expression( 3365 exp.LockingProperty( 3366 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3367 ) 3368 ) 3369 3370 def _parse_partition_by(self) -> list[exp.Expr]: 3371 if self._match(TokenType.PARTITION_BY): 3372 return self._parse_csv(self._parse_disjunction) 3373 return [] 3374 3375 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3376 def _parse_partition_bound_expr() -> exp.Expr | None: 3377 if self._match_text_seq("MINVALUE"): 3378 return exp.var("MINVALUE") 3379 if self._match_text_seq("MAXVALUE"): 3380 return exp.var("MAXVALUE") 3381 return self._parse_bitwise() 3382 3383 this: exp.Expr | list[exp.Expr] | None = None 3384 expression = None 3385 from_expressions = None 3386 to_expressions = None 3387 3388 if self._match(TokenType.IN): 3389 this = self._parse_wrapped_csv(self._parse_bitwise) 3390 elif self._match(TokenType.FROM): 3391 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3392 self._match_text_seq("TO") 3393 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3394 elif self._match_text_seq("WITH", "(", "MODULUS"): 3395 this = self._parse_number() 3396 self._match_text_seq(",", "REMAINDER") 3397 expression = self._parse_number() 3398 self._match_r_paren() 3399 else: 3400 self.raise_error("Failed to parse partition bound spec.") 3401 3402 return self.expression( 3403 exp.PartitionBoundSpec( 3404 this=this, 3405 expression=expression, 3406 from_expressions=from_expressions, 3407 to_expressions=to_expressions, 3408 ) 3409 ) 3410 3411 # https://www.postgresql.org/docs/current/sql-createtable.html 3412 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3413 if not self._match_text_seq("OF"): 3414 self._retreat(self._index - 1) 3415 return None 3416 3417 this = self._parse_table(schema=True) 3418 3419 if self._match(TokenType.DEFAULT): 3420 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3421 elif self._match_text_seq("FOR", "VALUES"): 3422 expression = self._parse_partition_bound_spec() 3423 else: 3424 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3425 3426 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3427 3428 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3429 self._match(TokenType.EQ) 3430 return self.expression( 3431 exp.PartitionedByProperty( 3432 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3433 ) 3434 ) 3435 3436 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3437 if self._match_text_seq("AND", "STATISTICS"): 3438 statistics = True 3439 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3440 statistics = False 3441 else: 3442 statistics = None 3443 3444 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3445 3446 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3447 if self._match_text_seq("SQL"): 3448 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3449 return None 3450 3451 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3452 if self._match_text_seq("SQL", "DATA"): 3453 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3454 return None 3455 3456 def _parse_no_property(self) -> exp.Expr | None: 3457 if self._match_text_seq("PRIMARY", "INDEX"): 3458 return exp.NoPrimaryIndexProperty() 3459 if self._match_text_seq("SQL"): 3460 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3461 return None 3462 3463 def _parse_on_property(self) -> exp.Expr | None: 3464 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3465 return exp.OnCommitProperty() 3466 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3467 return exp.OnCommitProperty(delete=True) 3468 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3469 3470 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3471 if self._match_text_seq("SQL", "DATA"): 3472 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3473 return None 3474 3475 def _parse_distkey(self) -> exp.DistKeyProperty: 3476 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3477 3478 def _parse_create_like(self) -> exp.LikeProperty | None: 3479 table = self._parse_table(schema=True) 3480 3481 options = [] 3482 while self._match_texts(("INCLUDING", "EXCLUDING")): 3483 this = self._prev.text.upper() 3484 3485 id_var = self._parse_id_var() 3486 if not id_var: 3487 return None 3488 3489 options.append( 3490 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3491 ) 3492 3493 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3494 3495 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3496 return self.expression( 3497 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3498 ) 3499 3500 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3501 self._match(TokenType.EQ) 3502 return self.expression( 3503 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3504 ) 3505 3506 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3507 self._match_text_seq("WITH", "CONNECTION") 3508 return self.expression( 3509 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3510 ) 3511 3512 def _parse_returns(self) -> exp.ReturnsProperty: 3513 value: exp.Expr | None 3514 null = None 3515 is_table = self._match(TokenType.TABLE) 3516 3517 if is_table: 3518 if self._match(TokenType.LT): 3519 value = self.expression( 3520 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3521 ) 3522 if not self._match(TokenType.GT): 3523 self.raise_error("Expecting >") 3524 else: 3525 value = self._parse_schema(exp.var("TABLE")) 3526 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3527 null = True 3528 value = None 3529 else: 3530 value = self._parse_types() 3531 3532 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3533 3534 def _parse_describe(self) -> exp.Describe: 3535 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3536 style: str | None = ( 3537 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3538 ) 3539 if self._match(TokenType.DOT): 3540 style = None 3541 self._retreat(self._index - 2) 3542 3543 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3544 3545 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3546 this = self._parse_statement() 3547 else: 3548 this = self._parse_table(schema=True) 3549 3550 properties = self._parse_properties() 3551 expressions = properties.expressions if properties else None 3552 partition = self._parse_partition() 3553 return self.expression( 3554 exp.Describe( 3555 this=this, 3556 style=style, 3557 kind=kind, 3558 expressions=expressions, 3559 partition=partition, 3560 format=format, 3561 as_json=self._match_text_seq("AS", "JSON"), 3562 ) 3563 ) 3564 3565 def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts: 3566 kind = self._prev.text.upper() 3567 expressions = [] 3568 3569 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3570 if self._match(TokenType.WHEN): 3571 expression = self._parse_disjunction() 3572 self._match(TokenType.THEN) 3573 else: 3574 expression = None 3575 3576 else_ = self._match(TokenType.ELSE) 3577 3578 if not self._match(TokenType.INTO): 3579 return None 3580 3581 return self.expression( 3582 exp.ConditionalInsert( 3583 this=self.expression( 3584 exp.Insert( 3585 this=self._parse_table(schema=True), 3586 expression=self._parse_derived_table_values(), 3587 ) 3588 ), 3589 expression=expression, 3590 else_=else_, 3591 ) 3592 ) 3593 3594 expression = parse_conditional_insert() 3595 while expression is not None: 3596 expressions.append(expression) 3597 expression = parse_conditional_insert() 3598 3599 return self.expression( 3600 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3601 comments=comments, 3602 ) 3603 3604 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3605 comments: list[str] = [] 3606 hint = self._parse_hint() 3607 overwrite = self._match(TokenType.OVERWRITE) 3608 ignore = self._match(TokenType.IGNORE) 3609 local = self._match_text_seq("LOCAL") 3610 alternative = None 3611 is_function = None 3612 3613 if self._match_text_seq("DIRECTORY"): 3614 this: exp.Expr | None = self.expression( 3615 exp.Directory( 3616 this=self._parse_var_or_string(), 3617 local=local, 3618 row_format=self._parse_row_format(match_row=True), 3619 ) 3620 ) 3621 else: 3622 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3623 comments += ensure_list(self._prev_comments) 3624 return self._parse_multitable_inserts(comments) 3625 3626 if self._match(TokenType.OR): 3627 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3628 3629 self._match(TokenType.INTO) 3630 comments += ensure_list(self._prev_comments) 3631 self._match(TokenType.TABLE) 3632 is_function = self._match(TokenType.FUNCTION) 3633 3634 this = self._parse_function() if is_function else self._parse_insert_table() 3635 3636 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3637 set_values = None 3638 if self._match(TokenType.SET): 3639 columns = [] 3640 values = [] 3641 3642 def _parse_set_assignment() -> exp.Expr | None: 3643 target = self._parse_column() 3644 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3645 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3646 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3647 else: 3648 value = self._parse_disjunction() 3649 3650 if value: 3651 columns.append(target.this) 3652 values.append(value) 3653 return value 3654 3655 self.raise_error("Expected column assignment in INSERT ... SET") 3656 return None 3657 3658 self._parse_csv(_parse_set_assignment) 3659 3660 this = self.expression(exp.Schema(this=this, expressions=columns)) 3661 set_values = self.expression( 3662 exp.Values( 3663 expressions=[exp.Tuple(expressions=values)], 3664 alias=self._parse_table_alias(), 3665 ) 3666 ) 3667 3668 returning = self._parse_returning() # TSQL allows RETURNING before source 3669 3670 stored = self._match_text_seq("STORED") and self._parse_stored() 3671 by_name = self._match_text_seq("BY", "NAME") 3672 exists = self._parse_exists() 3673 replace_where = None 3674 replace_using = None 3675 3676 if self._match(TokenType.REPLACE): 3677 if self._match(TokenType.WHERE): 3678 replace_where = self._parse_disjunction() 3679 elif self._match(TokenType.USING): 3680 replace_using = self._parse_using_identifiers() 3681 3682 return self.expression( 3683 exp.Insert( 3684 hint=hint, 3685 is_function=is_function, 3686 this=this, 3687 stored=stored, 3688 by_name=by_name, 3689 exists=exists, 3690 where=replace_where, 3691 using=replace_using, 3692 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3693 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3694 default=self._match_text_seq("DEFAULT", "VALUES"), 3695 expression=set_values 3696 or self._parse_derived_table_values() 3697 or self._parse_ddl_select(), 3698 conflict=self._parse_on_conflict(), 3699 returning=returning or self._parse_returning(), 3700 overwrite=overwrite, 3701 alternative=alternative, 3702 ignore=ignore, 3703 source=self._match(TokenType.TABLE) and self._parse_table(), 3704 ), 3705 comments=comments, 3706 ) 3707 3708 def _parse_insert_table(self) -> exp.Expr | None: 3709 this = self._parse_table(schema=True, parse_partition=True) 3710 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3711 this.set("alias", self._parse_table_alias()) 3712 return this 3713 3714 def _parse_kill(self) -> exp.Kill: 3715 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3716 3717 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3718 3719 def _parse_on_conflict(self) -> exp.OnConflict | None: 3720 conflict = self._match_text_seq("ON", "CONFLICT") 3721 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3722 3723 if not conflict and not duplicate: 3724 return None 3725 3726 conflict_keys = None 3727 constraint = None 3728 3729 if conflict: 3730 if self._match_text_seq("ON", "CONSTRAINT"): 3731 constraint = self._parse_id_var() 3732 elif self._match(TokenType.L_PAREN): 3733 conflict_keys = self._parse_csv(self._parse_indexed_column) 3734 self._match_r_paren() 3735 3736 index_predicate = self._parse_where() 3737 3738 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3739 if self._prev.token_type == TokenType.UPDATE: 3740 self._match(TokenType.SET) 3741 expressions = self._parse_csv(self._parse_equality) 3742 else: 3743 expressions = None 3744 3745 return self.expression( 3746 exp.OnConflict( 3747 duplicate=duplicate, 3748 expressions=expressions, 3749 action=action, 3750 conflict_keys=conflict_keys, 3751 index_predicate=index_predicate, 3752 constraint=constraint, 3753 where=self._parse_where(), 3754 ) 3755 ) 3756 3757 def _parse_returning(self) -> exp.Returning | None: 3758 if not self._match(TokenType.RETURNING): 3759 return None 3760 return self.expression( 3761 exp.Returning( 3762 expressions=self._parse_csv(self._parse_expression), 3763 into=self._match(TokenType.INTO) and self._parse_table_part(), 3764 ) 3765 ) 3766 3767 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3768 if not self._match(TokenType.FORMAT): 3769 return None 3770 return self._parse_row_format() 3771 3772 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3773 index = self._index 3774 with_ = with_ or self._match_text_seq("WITH") 3775 3776 if not self._match(TokenType.SERDE_PROPERTIES): 3777 self._retreat(index) 3778 return None 3779 return self.expression( 3780 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3781 ) 3782 3783 def _parse_row_format( 3784 self, match_row: bool = False 3785 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3786 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3787 return None 3788 3789 if self._match_text_seq("SERDE"): 3790 this = self._parse_string() 3791 3792 serde_properties = self._parse_serde_properties() 3793 3794 return self.expression( 3795 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3796 ) 3797 3798 self._match_text_seq("DELIMITED") 3799 3800 kwargs = {} 3801 3802 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3803 kwargs["fields"] = self._parse_string() 3804 if self._match_text_seq("ESCAPED", "BY"): 3805 kwargs["escaped"] = self._parse_string() 3806 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3807 kwargs["collection_items"] = self._parse_string() 3808 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3809 kwargs["map_keys"] = self._parse_string() 3810 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3811 kwargs["lines"] = self._parse_string() 3812 if self._match_text_seq("NULL", "DEFINED", "AS"): 3813 kwargs["null"] = self._parse_string() 3814 3815 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3816 3817 def _parse_load(self) -> exp.LoadData | exp.Command: 3818 if self._match_text_seq("DATA"): 3819 local = self._match_text_seq("LOCAL") 3820 self._match_text_seq("INPATH") 3821 inpath = self._parse_string() 3822 overwrite = self._match(TokenType.OVERWRITE) 3823 temp: bool | None = None 3824 if self._match(TokenType.INTO): 3825 temp = self._match(TokenType.TEMPORARY) 3826 self._match(TokenType.TABLE) 3827 3828 return self.expression( 3829 exp.LoadData( 3830 this=self._parse_table(schema=True), 3831 local=local, 3832 overwrite=overwrite, 3833 temp=temp, 3834 inpath=inpath, 3835 files=self._match_text_seq("FROM", "FILES") 3836 and exp.Properties(expressions=self._parse_wrapped_properties()), 3837 partition=self._parse_partition(), 3838 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3839 serde=self._match_text_seq("SERDE") and self._parse_string(), 3840 ) 3841 ) 3842 return self._parse_as_command(self._prev) 3843 3844 def _parse_delete(self) -> exp.Delete: 3845 hint = self._parse_hint() 3846 3847 # This handles MySQL's "Multiple-Table Syntax" 3848 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3849 tables = None 3850 if not self._match(TokenType.FROM, advance=False): 3851 tables = self._parse_csv(self._parse_table) or None 3852 3853 returning = self._parse_returning() 3854 3855 return self.expression( 3856 exp.Delete( 3857 hint=hint, 3858 tables=tables, 3859 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3860 using=self._match(TokenType.USING) 3861 and self._parse_csv(lambda: self._parse_table(joins=True)), 3862 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3863 where=self._parse_where(), 3864 returning=returning or self._parse_returning(), 3865 order=self._parse_order(), 3866 limit=self._parse_limit(), 3867 ) 3868 ) 3869 3870 def _parse_update(self) -> exp.Update: 3871 hint = self._parse_hint() 3872 kwargs: dict[str, object] = { 3873 "hint": hint, 3874 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3875 } 3876 while self._curr: 3877 if self._match(TokenType.SET): 3878 kwargs["expressions"] = self._parse_csv(self._parse_equality) 3879 elif self._match(TokenType.RETURNING, advance=False): 3880 kwargs["returning"] = self._parse_returning() 3881 elif self._match(TokenType.FROM, advance=False): 3882 from_ = self._parse_from(joins=True) 3883 table = from_.this if from_ else None 3884 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3885 table.set("joins", list(self._parse_joins()) or None) 3886 3887 kwargs["from_"] = from_ 3888 elif self._match(TokenType.WHERE, advance=False): 3889 kwargs["where"] = self._parse_where() 3890 elif self._match(TokenType.ORDER_BY, advance=False): 3891 kwargs["order"] = self._parse_order() 3892 elif self._match(TokenType.LIMIT, advance=False): 3893 kwargs["limit"] = self._parse_limit() 3894 else: 3895 break 3896 3897 return self.expression(exp.Update(**kwargs)) 3898 3899 def _parse_use(self) -> exp.Use: 3900 return self.expression( 3901 exp.Use( 3902 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 3903 this=self._parse_table(schema=False), 3904 ) 3905 ) 3906 3907 def _parse_uncache(self) -> exp.Uncache: 3908 if not self._match(TokenType.TABLE): 3909 self.raise_error("Expecting TABLE after UNCACHE") 3910 3911 return self.expression( 3912 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 3913 ) 3914 3915 def _parse_cache(self) -> exp.Cache: 3916 lazy = self._match_text_seq("LAZY") 3917 self._match(TokenType.TABLE) 3918 table = self._parse_table(schema=True) 3919 3920 options = [] 3921 if self._match_text_seq("OPTIONS"): 3922 self._match_l_paren() 3923 k = self._parse_string() 3924 self._match(TokenType.EQ) 3925 v = self._parse_string() 3926 options = [k, v] 3927 self._match_r_paren() 3928 3929 self._match(TokenType.ALIAS) 3930 return self.expression( 3931 exp.Cache( 3932 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 3933 ) 3934 ) 3935 3936 def _parse_partition(self) -> exp.Partition | None: 3937 if not self._match_texts(self.PARTITION_KEYWORDS): 3938 return None 3939 3940 return self.expression( 3941 exp.Partition( 3942 subpartition=self._prev.text.upper() == "SUBPARTITION", 3943 expressions=self._parse_wrapped_csv(self._parse_disjunction), 3944 ) 3945 ) 3946 3947 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 3948 def _parse_value_expression() -> exp.Expr | None: 3949 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3950 return exp.var(self._prev.text.upper()) 3951 return self._parse_expression() 3952 3953 if self._match(TokenType.L_PAREN): 3954 expressions = self._parse_csv(_parse_value_expression) 3955 self._match_r_paren() 3956 return self.expression(exp.Tuple(expressions=expressions)) 3957 3958 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 3959 expression = self._parse_expression() 3960 if expression: 3961 return self.expression(exp.Tuple(expressions=[expression])) 3962 return None 3963 3964 def _parse_projections( 3965 self, 3966 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 3967 return self._parse_expressions(), None 3968 3969 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 3970 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 3971 this: exp.Expr | None = self._parse_simplified_pivot( 3972 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 3973 ) 3974 elif self._match(TokenType.FROM): 3975 from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True) 3976 # Support parentheses for duckdb FROM-first syntax 3977 select = self._parse_select(from_=from_) 3978 if select: 3979 if not select.args.get("from_"): 3980 select.set("from_", from_) 3981 this = select 3982 else: 3983 this = exp.select("*").from_(t.cast(exp.From, from_)) 3984 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3985 else: 3986 this = ( 3987 self._parse_table(consume_pipe=True) 3988 if table 3989 else self._parse_select(nested=True, parse_set_operation=False) 3990 ) 3991 3992 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 3993 # in case a modifier (e.g. join) is following 3994 if table and isinstance(this, exp.Values) and this.alias: 3995 alias = this.args["alias"].pop() 3996 this = exp.Table(this=this, alias=alias) 3997 3998 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3999 4000 return this 4001 4002 def _parse_select( 4003 self, 4004 nested: bool = False, 4005 table: bool = False, 4006 parse_subquery_alias: bool = True, 4007 parse_set_operation: bool = True, 4008 consume_pipe: bool = True, 4009 from_: exp.From | None = None, 4010 ) -> exp.Expr | None: 4011 query = self._parse_select_query( 4012 nested=nested, 4013 table=table, 4014 parse_subquery_alias=parse_subquery_alias, 4015 parse_set_operation=parse_set_operation, 4016 ) 4017 4018 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4019 if not query and from_: 4020 query = exp.select("*").from_(from_) 4021 if isinstance(query, exp.Query): 4022 query = self._parse_pipe_syntax_query(query) 4023 query = query.subquery(copy=False) if query and table else query 4024 4025 return query 4026 4027 def _parse_select_query( 4028 self, 4029 nested: bool = False, 4030 table: bool = False, 4031 parse_subquery_alias: bool = True, 4032 parse_set_operation: bool = True, 4033 ) -> exp.Expr | None: 4034 cte = self._parse_with() 4035 4036 if cte: 4037 this = self._parse_statement() 4038 4039 if not this: 4040 self.raise_error("Failed to parse any statement following CTE") 4041 return cte 4042 4043 while isinstance(this, exp.Subquery) and this.is_wrapper: 4044 this = this.this 4045 4046 assert this is not None 4047 if "with_" in this.arg_types: 4048 if inner_cte := this.args.get("with_"): 4049 cte.set("expressions", cte.expressions + inner_cte.expressions) 4050 if inner_cte.args.get("recursive"): 4051 cte.set("recursive", True) 4052 this.set("with_", cte) 4053 else: 4054 self.raise_error(f"{this.key} does not support CTE") 4055 this = cte 4056 4057 return this 4058 4059 # duckdb supports leading with FROM x 4060 from_ = ( 4061 self._parse_from(joins=True, consume_pipe=True) 4062 if self._match(TokenType.FROM, advance=False) 4063 else None 4064 ) 4065 4066 if self._match(TokenType.SELECT): 4067 comments = self._prev_comments 4068 4069 hint = self._parse_hint() 4070 4071 if self._next and not self._next.token_type == TokenType.DOT: 4072 all_ = self._match(TokenType.ALL) 4073 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4074 else: 4075 all_, matched_distinct = None, False 4076 4077 kind = ( 4078 self._prev.text.upper() 4079 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4080 else None 4081 ) 4082 4083 distinct: exp.Expr | None = ( 4084 self.expression( 4085 exp.Distinct( 4086 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4087 ) 4088 ) 4089 if matched_distinct 4090 else None 4091 ) 4092 4093 operation_modifiers = [] 4094 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4095 operation_modifiers.append(exp.var(self._prev.text.upper())) 4096 4097 limit = self._parse_limit(top=True) 4098 4099 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4100 if limit and not matched_distinct and not all_: 4101 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4102 if matched_distinct: 4103 distinct = self.expression( 4104 exp.Distinct( 4105 on=self._parse_value(values=False) 4106 if self._match(TokenType.ON) 4107 else None 4108 ) 4109 ) 4110 else: 4111 all_ = self._match(TokenType.ALL) 4112 4113 if all_ and distinct: 4114 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4115 4116 projections, exclude = self._parse_projections() 4117 4118 this = self.expression( 4119 exp.Select( 4120 kind=kind, 4121 hint=hint, 4122 distinct=distinct, 4123 expressions=projections, 4124 limit=limit, 4125 exclude=exclude, 4126 operation_modifiers=operation_modifiers or None, 4127 ) 4128 ) 4129 this.comments = comments 4130 4131 into = self._parse_into() 4132 if into: 4133 this.set("into", into) 4134 4135 if not from_: 4136 from_ = self._parse_from() 4137 4138 if from_: 4139 this.set("from_", from_) 4140 4141 this = self._parse_query_modifiers(this) 4142 elif (table or nested) and self._match(TokenType.L_PAREN): 4143 comments = self._prev_comments 4144 this = self._parse_wrapped_select(table=table) 4145 4146 if this: 4147 this.add_comments(comments, prepend=True) 4148 4149 # We return early here so that the UNION isn't attached to the subquery by the 4150 # following call to _parse_set_operations, but instead becomes the parent node 4151 self._match_r_paren() 4152 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4153 elif self._match(TokenType.VALUES, advance=False): 4154 this = self._parse_derived_table_values() 4155 elif from_: 4156 this = exp.select("*").from_(from_.this, copy=False) 4157 this = self._parse_query_modifiers(this) 4158 elif self._match(TokenType.SUMMARIZE): 4159 table = self._match(TokenType.TABLE) 4160 this = self._parse_select() or self._parse_string() or self._parse_table() 4161 return self.expression(exp.Summarize(this=this, table=table)) 4162 elif self._match(TokenType.DESCRIBE): 4163 this = self._parse_describe() 4164 else: 4165 this = None 4166 4167 return self._parse_set_operations(this) if parse_set_operation else this 4168 4169 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4170 self._match_text_seq("SEARCH") 4171 4172 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4173 4174 if not kind: 4175 return None 4176 4177 self._match_text_seq("FIRST", "BY") 4178 4179 return self.expression( 4180 exp.RecursiveWithSearch( 4181 kind=kind, 4182 this=self._parse_id_var(), 4183 expression=self._match_text_seq("SET") and self._parse_id_var(), 4184 using=self._match_text_seq("USING") and self._parse_id_var(), 4185 ) 4186 ) 4187 4188 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4189 if not skip_with_token and not self._match(TokenType.WITH): 4190 return None 4191 4192 comments = self._prev_comments 4193 recursive = self._match(TokenType.RECURSIVE) 4194 4195 last_comments = None 4196 expressions = [] 4197 udfs = [] 4198 while True: 4199 cte = self._parse_cte() 4200 if cte: 4201 if isinstance(cte, exp.FunctionSpecification): 4202 udfs.append(cte) 4203 else: 4204 expressions.append(cte) 4205 4206 if last_comments: 4207 cte.add_comments(last_comments) 4208 4209 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4210 break 4211 else: 4212 self._match(TokenType.WITH) 4213 recursive = self._match(TokenType.RECURSIVE) or recursive 4214 4215 last_comments = self._prev_comments 4216 4217 return self.expression( 4218 exp.With( 4219 expressions=expressions, 4220 recursive=recursive or None, 4221 search=self._parse_recursive_with_search(), 4222 udfs=udfs or None, 4223 ), 4224 comments=comments, 4225 ) 4226 4227 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4228 index = self._index 4229 4230 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4231 if not alias or not alias.this: 4232 self.raise_error("Expected CTE to have alias") 4233 4234 key_expressions = ( 4235 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4236 ) 4237 4238 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4239 self._retreat(index) 4240 return None 4241 4242 comments = self._prev_comments 4243 4244 if self._match_text_seq("NOT", "MATERIALIZED"): 4245 materialized = False 4246 elif self._match_text_seq("MATERIALIZED"): 4247 materialized = True 4248 else: 4249 materialized = None 4250 4251 cte = self.expression( 4252 exp.CTE( 4253 this=self._parse_wrapped(self._parse_statement), 4254 alias=alias, 4255 materialized=materialized, 4256 key_expressions=key_expressions, 4257 ), 4258 comments=comments, 4259 ) 4260 4261 values = cte.this 4262 if isinstance(values, exp.Values): 4263 cte.set("this", self._values_to_select(values)) 4264 4265 return cte 4266 4267 def _values_to_select(self, values: exp.Values) -> exp.Select: 4268 if values.alias: 4269 return exp.select("*").from_(values) 4270 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4271 4272 def _parse_table_alias( 4273 self, alias_tokens: t.Collection[TokenType] | None = None 4274 ) -> exp.TableAlias | None: 4275 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4276 # so this section tries to parse the clause version and if it fails, it treats the token 4277 # as an identifier (alias) 4278 if self._can_parse_limit_or_offset(): 4279 return None 4280 4281 # START is never treated as an implicit alias when followed by WITH, since that 4282 # would swallow the beginning of a START WITH ... CONNECT BY clause 4283 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4284 return None 4285 4286 any_token = self._match(TokenType.ALIAS) 4287 alias = ( 4288 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4289 or self._parse_string_as_identifier() 4290 ) 4291 4292 index = self._index 4293 if self._match(TokenType.L_PAREN): 4294 columns = self._parse_csv(self._parse_function_parameter) 4295 self._match_r_paren() if columns else self._retreat(index) 4296 else: 4297 columns = None 4298 4299 if not alias and not columns: 4300 return None 4301 4302 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4303 4304 # We bubble up comments from the Identifier to the TableAlias 4305 if isinstance(alias, exp.Identifier): 4306 table_alias.add_comments(alias.pop_comments()) 4307 4308 return table_alias 4309 4310 def _parse_subquery( 4311 self, this: exp.Expr | None, parse_alias: bool = True 4312 ) -> exp.Subquery | None: 4313 if not this: 4314 return None 4315 4316 return self.expression( 4317 exp.Subquery( 4318 this=this, 4319 pivots=self._parse_pivots(), 4320 alias=self._parse_table_alias() if parse_alias else None, 4321 sample=self._parse_table_sample(), 4322 ) 4323 ) 4324 4325 def _implicit_unnests_to_explicit(self, this: E) -> E: 4326 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4327 4328 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4329 for i, join in enumerate(this.args.get("joins") or []): 4330 table = join.this 4331 normalized_table = table.copy() 4332 normalized_table.meta["maybe_column"] = True 4333 normalized_table = _norm(normalized_table, dialect=self.dialect) 4334 4335 if isinstance(table, exp.Table) and not join.args.get("on"): 4336 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4337 table_as_column = table.to_column() 4338 unnest = exp.Unnest(expressions=[table_as_column]) 4339 4340 # Table.to_column creates a parent Alias node that we want to convert to 4341 # a TableAlias and attach to the Unnest, so it matches the parser's output 4342 if isinstance(table.args.get("alias"), exp.TableAlias): 4343 table_as_column.replace(table_as_column.this) 4344 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4345 4346 table.replace(unnest) 4347 4348 refs.add(normalized_table.alias_or_name) 4349 4350 return this 4351 4352 @t.overload 4353 def _parse_query_modifiers(self, this: E) -> E: ... 4354 4355 @t.overload 4356 def _parse_query_modifiers(self, this: None) -> None: ... 4357 4358 def _parse_query_modifiers(self, this): 4359 if isinstance(this, self.MODIFIABLES): 4360 for join in self._parse_joins(): 4361 this.append("joins", join) 4362 for lateral in iter(self._parse_lateral, None): 4363 this.append("laterals", lateral) 4364 4365 while True: 4366 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4367 modifier_token = self._curr 4368 4369 # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result 4370 # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union 4371 if ( 4372 modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH) 4373 and (limit := this.args.get("limit")) 4374 and limit.meta.get("top") 4375 ): 4376 break 4377 4378 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4379 key, expression = parser(self) 4380 4381 if expression: 4382 if this.args.get(key): 4383 self.raise_error( 4384 f"Found multiple '{modifier_token.text.upper()}' clauses", 4385 token=modifier_token, 4386 ) 4387 4388 this.set(key, expression) 4389 if key == "limit": 4390 offset = expression.args.get("offset") 4391 expression.set("offset", None) 4392 4393 if offset: 4394 if this.args.get("offset"): 4395 self.raise_error( 4396 "Found multiple 'OFFSET' clauses", token=modifier_token 4397 ) 4398 4399 offset = exp.Offset(expression=offset) 4400 this.set("offset", offset) 4401 4402 limit_by_expressions = expression.expressions 4403 expression.set("expressions", None) 4404 offset.set("expressions", limit_by_expressions) 4405 continue 4406 4407 if self._curr.text.upper() == "START": 4408 modifier_token = self._curr 4409 connect = self._parse_connect() 4410 if connect: 4411 if this.args.get("connect"): 4412 self.raise_error( 4413 "Found multiple 'START WITH' clauses", token=modifier_token 4414 ) 4415 4416 this.set("connect", connect) 4417 continue 4418 break 4419 4420 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4421 this = self._implicit_unnests_to_explicit(this) 4422 4423 return this 4424 4425 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4426 start = self._curr 4427 while self._curr: 4428 self._advance() 4429 4430 end = self._tokens[self._index - 1] 4431 return exp.Hint(expressions=[self._find_sql(start, end)]) 4432 4433 def _parse_hint_function_call(self) -> exp.Expr | None: 4434 return self._parse_function_call() 4435 4436 def _parse_hint_body(self) -> exp.Hint | None: 4437 start_index = self._index 4438 should_fallback_to_string = False 4439 4440 hints = [] 4441 try: 4442 for hint in iter( 4443 lambda: self._parse_csv( 4444 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4445 ), 4446 [], 4447 ): 4448 hints.extend(hint) 4449 except ParseError: 4450 should_fallback_to_string = True 4451 4452 if should_fallback_to_string or self._curr: 4453 self._retreat(start_index) 4454 return self._parse_hint_fallback_to_string() 4455 4456 return self.expression(exp.Hint(expressions=hints)) 4457 4458 def _parse_hint(self) -> exp.Hint | None: 4459 if self._match(TokenType.HINT) and self._prev_comments: 4460 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4461 4462 return None 4463 4464 def _parse_into(self) -> exp.Into | None: 4465 if not self._match(TokenType.INTO): 4466 return None 4467 4468 temp = self._match(TokenType.TEMPORARY) 4469 unlogged = self._match_text_seq("UNLOGGED") 4470 self._match(TokenType.TABLE) 4471 4472 return self.expression( 4473 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4474 ) 4475 4476 def _parse_from( 4477 self, 4478 joins: bool = False, 4479 skip_from_token: bool = False, 4480 consume_pipe: bool = False, 4481 ) -> exp.From | None: 4482 if not skip_from_token and not self._match(TokenType.FROM): 4483 return None 4484 4485 comments = self._prev_comments 4486 return self.expression( 4487 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4488 comments=comments, 4489 ) 4490 4491 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4492 return self.expression( 4493 exp.MatchRecognizeMeasure( 4494 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4495 this=self._parse_expression(), 4496 ) 4497 ) 4498 4499 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4500 if not self._match(TokenType.MATCH_RECOGNIZE): 4501 return None 4502 4503 self._match_l_paren() 4504 4505 partition = self._parse_partition_by() 4506 order = self._parse_order() 4507 4508 measures = ( 4509 self._parse_csv(self._parse_match_recognize_measure) 4510 if self._match_text_seq("MEASURES") 4511 else None 4512 ) 4513 4514 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4515 rows = exp.var("ONE ROW PER MATCH") 4516 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4517 text = "ALL ROWS PER MATCH" 4518 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4519 text += " SHOW EMPTY MATCHES" 4520 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4521 text += " OMIT EMPTY MATCHES" 4522 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4523 text += " WITH UNMATCHED ROWS" 4524 rows = exp.var(text) 4525 else: 4526 rows = None 4527 4528 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4529 text = "AFTER MATCH SKIP" 4530 if self._match_text_seq("PAST", "LAST", "ROW"): 4531 text += " PAST LAST ROW" 4532 elif self._match_text_seq("TO", "NEXT", "ROW"): 4533 text += " TO NEXT ROW" 4534 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4535 direction = self._prev.text.upper() 4536 pattern_var = self._advance_any() 4537 if not pattern_var: 4538 self.raise_error( 4539 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4540 ) 4541 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4542 after = exp.var(text) 4543 else: 4544 after = None 4545 4546 if self._match_text_seq("PATTERN"): 4547 self._match_l_paren() 4548 4549 if not self._curr: 4550 self.raise_error("Expecting )", self._curr) 4551 4552 paren = 1 4553 start = self._curr 4554 4555 while self._curr and paren > 0: 4556 if self._curr.token_type == TokenType.L_PAREN: 4557 paren += 1 4558 if self._curr.token_type == TokenType.R_PAREN: 4559 paren -= 1 4560 4561 end = self._prev 4562 self._advance() 4563 4564 if paren > 0: 4565 self.raise_error("Expecting )", self._curr) 4566 4567 pattern = exp.var(self._find_sql(start, end)) 4568 else: 4569 pattern = None 4570 4571 define = ( 4572 self._parse_csv(self._parse_name_as_expression) 4573 if self._match_text_seq("DEFINE") 4574 else None 4575 ) 4576 4577 self._match_r_paren() 4578 4579 return self.expression( 4580 exp.MatchRecognize( 4581 partition_by=partition, 4582 order=order, 4583 measures=measures, 4584 rows=rows, 4585 after=after, 4586 pattern=pattern, 4587 define=define, 4588 alias=self._parse_table_alias(), 4589 ) 4590 ) 4591 4592 def _parse_lateral(self) -> exp.Lateral | None: 4593 cross_apply: bool | None = None 4594 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4595 cross_apply = True 4596 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4597 cross_apply = False 4598 4599 if cross_apply is not None: 4600 this = self._parse_select(table=True) 4601 view = None 4602 outer = None 4603 elif self._match(TokenType.LATERAL): 4604 this = self._parse_select(table=True) 4605 view = self._match(TokenType.VIEW) 4606 outer = self._match(TokenType.OUTER) 4607 else: 4608 return None 4609 4610 if not this: 4611 this = ( 4612 self._parse_unnest() 4613 or self._parse_function() 4614 or self._parse_id_var(any_token=False) 4615 ) 4616 4617 while self._match(TokenType.DOT): 4618 this = exp.Dot( 4619 this=this, 4620 expression=self._parse_function() or self._parse_id_var(any_token=False), 4621 ) 4622 4623 ordinality: bool | None = None 4624 4625 if view: 4626 table = self._parse_id_var(any_token=False) 4627 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4628 table_alias: exp.TableAlias | None = self.expression( 4629 exp.TableAlias(this=table, columns=columns) 4630 ) 4631 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4632 # We move the alias from the lateral's child node to the lateral itself 4633 table_alias = this.args["alias"].pop() 4634 else: 4635 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4636 table_alias = self._parse_table_alias() 4637 4638 return self.expression( 4639 exp.Lateral( 4640 this=this, 4641 view=view, 4642 outer=outer, 4643 alias=table_alias, 4644 cross_apply=cross_apply, 4645 ordinality=ordinality, 4646 ) 4647 ) 4648 4649 def _parse_stream(self) -> exp.Stream | None: 4650 index = self._index 4651 if self._match(TokenType.STREAM): 4652 if this := self._try_parse(self._parse_table): 4653 return self.expression(exp.Stream(this=this)) 4654 self._retreat(index) 4655 return None 4656 4657 def _parse_join_parts( 4658 self, 4659 ) -> tuple[Token | None, Token | None, Token | None]: 4660 return ( 4661 self._prev if self._match_set(self.JOIN_METHODS) else None, 4662 self._prev if self._match_set(self.JOIN_SIDES) else None, 4663 self._prev if self._match_set(self.JOIN_KINDS) else None, 4664 ) 4665 4666 def _parse_using_identifiers(self) -> list[exp.Expr]: 4667 def _parse_column_as_identifier() -> exp.Expr | None: 4668 this = self._parse_column() 4669 if isinstance(this, exp.Column): 4670 return this.this 4671 return this 4672 4673 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4674 4675 def _parse_join( 4676 self, 4677 skip_join_token: bool = False, 4678 parse_bracket: bool = False, 4679 alias_tokens: t.Collection[TokenType] | None = None, 4680 ) -> exp.Join | None: 4681 if self._match(TokenType.COMMA): 4682 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4683 cross_join = self.expression(exp.Join(this=table)) if table else None 4684 4685 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4686 cross_join.set("kind", "CROSS") 4687 4688 return cross_join 4689 4690 index = self._index 4691 method, side, kind = self._parse_join_parts() 4692 directed = self._match_text_seq("DIRECTED") 4693 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4694 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4695 join_comments = self._prev_comments 4696 4697 if not skip_join_token and not join: 4698 self._retreat(index) 4699 kind = None 4700 method = None 4701 side = None 4702 4703 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4704 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4705 4706 if not skip_join_token and not join and not outer_apply and not cross_apply: 4707 return None 4708 4709 kwargs: dict[str, t.Any] = { 4710 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4711 } 4712 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4713 kwargs["expressions"] = self._parse_csv( 4714 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4715 ) 4716 4717 if method: 4718 kwargs["method"] = method.text.upper() 4719 if side: 4720 kwargs["side"] = side.text.upper() 4721 if kind: 4722 kwargs["kind"] = kind.text.upper() 4723 if hint: 4724 kwargs["hint"] = hint 4725 4726 if self._match(TokenType.MATCH_CONDITION): 4727 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4728 4729 if self._match(TokenType.ON): 4730 kwargs["on"] = self._parse_disjunction() 4731 elif self._match(TokenType.USING): 4732 kwargs["using"] = self._parse_using_identifiers() 4733 elif ( 4734 not method 4735 and not (outer_apply or cross_apply) 4736 and not isinstance(kwargs["this"], exp.Unnest) 4737 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4738 ): 4739 index = self._index 4740 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4741 4742 if joins and self._match(TokenType.ON): 4743 kwargs["on"] = self._parse_disjunction() 4744 elif joins and self._match(TokenType.USING): 4745 kwargs["using"] = self._parse_using_identifiers() 4746 else: 4747 joins = None 4748 self._retreat(index) 4749 4750 kwargs["this"].set("joins", joins if joins else None) 4751 4752 kwargs["pivots"] = self._parse_pivots() 4753 4754 comments = [c for token in (method, side, kind) if token for c in token.comments] 4755 comments = (join_comments or []) + comments 4756 4757 if ( 4758 self.ADD_JOIN_ON_TRUE 4759 and not kwargs.get("on") 4760 and not kwargs.get("using") 4761 and not kwargs.get("method") 4762 and kwargs.get("kind") in (None, "INNER", "OUTER") 4763 ): 4764 kwargs["on"] = exp.true() 4765 4766 if directed: 4767 kwargs["directed"] = directed 4768 4769 return self.expression(exp.Join(**kwargs), comments=comments) 4770 4771 def _parse_opclass(self) -> exp.Expr | None: 4772 this = self._parse_disjunction() 4773 4774 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4775 return this 4776 4777 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4778 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4779 4780 return this 4781 4782 def _parse_index_params(self) -> exp.IndexParameters: 4783 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4784 4785 if self._match(TokenType.L_PAREN, advance=False): 4786 columns = self._parse_wrapped_csv(self._parse_with_operator) 4787 else: 4788 columns = None 4789 4790 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4791 partition_by = self._parse_partition_by() 4792 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4793 tablespace = ( 4794 self._parse_var(any_token=True) 4795 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4796 else None 4797 ) 4798 where = self._parse_where() 4799 4800 on = self._parse_field() if self._match(TokenType.ON) else None 4801 4802 return self.expression( 4803 exp.IndexParameters( 4804 using=using, 4805 columns=columns, 4806 include=include, 4807 partition_by=partition_by, 4808 where=where, 4809 with_storage=with_storage, 4810 tablespace=tablespace, 4811 on=on, 4812 ) 4813 ) 4814 4815 def _parse_index( 4816 self, index: exp.Expr | None = None, anonymous: bool = False 4817 ) -> exp.Index | None: 4818 if index or anonymous: 4819 unique = None 4820 primary = None 4821 amp = None 4822 4823 self._match(TokenType.ON) 4824 self._match(TokenType.TABLE) # hive 4825 table = self._parse_table_parts(schema=True) 4826 else: 4827 unique = self._match(TokenType.UNIQUE) 4828 primary = self._match_text_seq("PRIMARY") 4829 amp = self._match_text_seq("AMP") 4830 4831 if not self._match(TokenType.INDEX): 4832 return None 4833 4834 index = self._parse_id_var() 4835 table = None 4836 4837 params = self._parse_index_params() 4838 4839 return self.expression( 4840 exp.Index( 4841 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4842 ) 4843 ) 4844 4845 def _parse_table_hints(self) -> list[exp.Expr] | None: 4846 hints: list[exp.Expr] = [] 4847 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4848 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4849 hints.append( 4850 self.expression( 4851 exp.WithTableHint( 4852 expressions=self._parse_csv( 4853 lambda: self._parse_function() or self._parse_var(any_token=True) 4854 ) 4855 ) 4856 ) 4857 ) 4858 self._match_r_paren() 4859 else: 4860 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4861 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4862 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4863 4864 self._match_set((TokenType.INDEX, TokenType.KEY)) 4865 if self._match(TokenType.FOR): 4866 hint.set("target", self._advance_any() and self._prev.text.upper()) 4867 4868 hint.set("expressions", self._parse_wrapped_id_vars()) 4869 hints.append(hint) 4870 4871 return hints or None 4872 4873 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 4874 return ( 4875 (not schema and self._parse_function(optional_parens=False)) 4876 or self._parse_id_var(any_token=False) 4877 or self._parse_string_as_identifier() 4878 or self._parse_placeholder() 4879 ) 4880 4881 def _parse_table_parts_fast(self) -> exp.Table | None: 4882 index = self._index 4883 parts: list[exp.Identifier] | None = None 4884 all_comments: list[str] | None = None 4885 4886 while self._match_set(self.IDENTIFIER_TOKENS): 4887 token = self._prev 4888 comments = self._prev_comments 4889 4890 has_dot = self._match(TokenType.DOT) 4891 curr_tt = self._curr.token_type 4892 4893 if not has_dot: 4894 if curr_tt in self.TABLE_POSTFIX_TOKENS: 4895 self._retreat(index) 4896 return None 4897 elif curr_tt not in self.IDENTIFIER_TOKENS: 4898 self._retreat(index) 4899 return None 4900 4901 if parts is None: 4902 parts = [] 4903 4904 if comments: 4905 if all_comments is None: 4906 all_comments = [] 4907 all_comments.extend(comments) 4908 self._prev_comments = [] 4909 4910 parts.append( 4911 self.expression( 4912 exp.Identifier( 4913 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 4914 ), 4915 token, 4916 ) 4917 ) 4918 4919 if not has_dot: 4920 break 4921 4922 if parts is None: 4923 return None 4924 4925 n = len(parts) 4926 4927 if n == 1: 4928 table: exp.Table = exp.Table(this=parts[0]) 4929 elif n == 2: 4930 table = exp.Table(this=parts[1], db=parts[0]) 4931 elif n >= 3: 4932 this: exp.Identifier | exp.Dot = parts[2] 4933 for i in range(3, n): 4934 this = exp.Dot(this=this, expression=parts[i]) 4935 4936 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 4937 4938 if table is None: 4939 self._retreat(index) 4940 elif all_comments: 4941 table.add_comments(all_comments) 4942 return table 4943 4944 def _parse_table_parts( 4945 self, 4946 schema: bool = False, 4947 is_db_reference: bool = False, 4948 wildcard: bool = False, 4949 fast: bool = False, 4950 ) -> exp.Table | exp.Dot | None: 4951 if fast: 4952 return self._parse_table_parts_fast() 4953 4954 catalog: exp.Expr | str | None = None 4955 db: exp.Expr | str | None = None 4956 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 4957 4958 while self._match(TokenType.DOT): 4959 if catalog: 4960 # This allows nesting the table in arbitrarily many dot expressions if needed 4961 table = self.expression( 4962 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 4963 ) 4964 else: 4965 catalog = db 4966 db = table 4967 # "" used for tsql FROM a..b case 4968 table = self._parse_table_part(schema=schema) or "" 4969 4970 if ( 4971 wildcard 4972 and self._is_connected() 4973 and (isinstance(table, exp.Identifier) or not table) 4974 and self._match(TokenType.STAR) 4975 ): 4976 if isinstance(table, exp.Identifier): 4977 table.args["this"] += "*" 4978 else: 4979 table = exp.Identifier(this="*") 4980 4981 if is_db_reference: 4982 catalog = db 4983 db = table 4984 table = None 4985 4986 if not table and not is_db_reference: 4987 self.raise_error(f"Expected table name but got {self._curr}") 4988 if not db and is_db_reference: 4989 self.raise_error(f"Expected database name but got {self._curr}") 4990 4991 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 4992 4993 # Bubble up comments from identifier parts to the Table 4994 comments = [] 4995 for part in table.parts: 4996 if part_comments := part.pop_comments(): 4997 comments.extend(part_comments) 4998 if comments: 4999 table.add_comments(comments) 5000 5001 changes = self._parse_changes() 5002 if changes: 5003 table.set("changes", changes) 5004 5005 at_before = self._parse_historical_data() 5006 if at_before: 5007 table.set("when", at_before) 5008 5009 pivots = self._parse_pivots() 5010 if pivots: 5011 table.set("pivots", pivots) 5012 5013 return table 5014 5015 def _parse_table( 5016 self, 5017 schema: bool = False, 5018 joins: bool = False, 5019 alias_tokens: t.Collection[TokenType] | None = None, 5020 parse_bracket: bool = False, 5021 is_db_reference: bool = False, 5022 parse_partition: bool = False, 5023 consume_pipe: bool = False, 5024 ) -> exp.Expr | None: 5025 if not schema and not is_db_reference and not consume_pipe and not joins: 5026 index = self._index 5027 table = self._parse_table_parts(fast=True) 5028 5029 if table is not None: 5030 curr_tt = self._curr.token_type 5031 next_tt = self._next.token_type 5032 5033 fast_terminators = self.TABLE_TERMINATORS 5034 5035 # only return the table if we're sure there are no other operators 5036 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5037 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5038 return table 5039 5040 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5041 5042 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5043 if alias := self._parse_table_alias( 5044 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5045 ): 5046 table.set("alias", alias) 5047 5048 if self._curr.token_type in fast_terminators: 5049 return table 5050 5051 self._retreat(index) 5052 5053 if stream := self._parse_stream(): 5054 return stream 5055 5056 if lateral := self._parse_lateral(): 5057 return lateral 5058 5059 if unnest := self._parse_unnest(): 5060 return unnest 5061 5062 if values := self._parse_derived_table_values(): 5063 return values 5064 5065 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5066 if not subquery.args.get("pivots"): 5067 subquery.set("pivots", self._parse_pivots()) 5068 if joins: 5069 for join in self._parse_joins(): 5070 subquery.append("joins", join) 5071 return subquery 5072 5073 bracket = parse_bracket and self._parse_bracket(None) 5074 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5075 5076 rows_from_tables = ( 5077 self._parse_wrapped_csv(self._parse_table) 5078 if self._match_text_seq("ROWS", "FROM") 5079 else None 5080 ) 5081 rows_from = ( 5082 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5083 ) 5084 5085 only = self._match(TokenType.ONLY) 5086 5087 this = t.cast( 5088 exp.Expr, 5089 bracket 5090 or rows_from 5091 or self._parse_bracket( 5092 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5093 ), 5094 ) 5095 5096 if only: 5097 this.set("only", only) 5098 5099 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5100 self._match(TokenType.STAR) 5101 5102 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5103 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5104 this.set("partition", self._parse_partition()) 5105 5106 if schema: 5107 return self._parse_schema(this=this) 5108 5109 if self.dialect.ALIAS_POST_VERSION: 5110 this.set("version", self._parse_version()) 5111 5112 if self.dialect.ALIAS_POST_TABLESAMPLE: 5113 this.set("sample", self._parse_table_sample()) 5114 5115 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5116 if alias: 5117 this.set("alias", alias) 5118 5119 # DuckDB requires the time-travel clause to come after the alias, e.g. 5120 # SELECT * FROM t AS a AT (VERSION => 1) 5121 if isinstance(this, exp.Table) and not this.args.get("when"): 5122 this.set("when", self._parse_historical_data()) 5123 5124 if self._match(TokenType.INDEXED_BY): 5125 this.set("indexed", self._parse_table_parts()) 5126 elif self._match_text_seq("NOT", "INDEXED"): 5127 this.set("indexed", False) 5128 5129 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5130 return self.expression( 5131 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5132 ) 5133 5134 this.set("hints", self._parse_table_hints()) 5135 5136 if not this.args.get("pivots"): 5137 this.set("pivots", self._parse_pivots()) 5138 5139 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5140 this.set("sample", self._parse_table_sample()) 5141 5142 if not self.dialect.ALIAS_POST_VERSION: 5143 this.set("version", self._parse_version()) 5144 5145 if joins: 5146 for join in self._parse_joins(alias_tokens=alias_tokens): 5147 this.append("joins", join) 5148 5149 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5150 this.set("ordinality", True) 5151 this.set("alias", self._parse_table_alias()) 5152 5153 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5154 # hoist the table args onto the latter and return it instead 5155 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5156 table_from_rows = this.this 5157 for arg in exp.TableFromRows.arg_types: 5158 if arg != "this": 5159 table_from_rows.set(arg, this.args.get(arg)) 5160 5161 this = table_from_rows 5162 5163 return this 5164 5165 def _parse_version(self) -> exp.Version | None: 5166 for phrase, this in self.VERSION_PHRASES.items(): 5167 if self._match_text_seq(*phrase): 5168 break 5169 else: 5170 return None 5171 5172 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5173 kind = self._prev.text.upper() 5174 start = self._parse_bitwise() 5175 self._match_texts(("TO", "AND")) 5176 end = self._parse_bitwise() 5177 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5178 elif self._match_text_seq("CONTAINED", "IN"): 5179 kind = "CONTAINED IN" 5180 expression = self.expression( 5181 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5182 ) 5183 elif self._match(TokenType.ALL): 5184 kind = "ALL" 5185 expression = None 5186 else: 5187 self._match_text_seq("AS", "OF") 5188 kind = "AS OF" 5189 expression = self._parse_type() 5190 5191 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5192 5193 def _parse_historical_data(self) -> exp.HistoricalData | None: 5194 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5195 index = self._index 5196 historical_data = None 5197 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5198 this = self._prev.text.upper() 5199 kind = ( 5200 self._match(TokenType.L_PAREN) 5201 and self._match_texts(self.HISTORICAL_DATA_KIND) 5202 and self._prev.text.upper() 5203 ) 5204 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5205 5206 if expression: 5207 self._match_r_paren() 5208 historical_data = self.expression( 5209 exp.HistoricalData(this=this, kind=kind, expression=expression) 5210 ) 5211 else: 5212 self._retreat(index) 5213 5214 return historical_data 5215 5216 def _parse_changes(self) -> exp.Changes | None: 5217 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5218 return None 5219 5220 information = self._parse_var(any_token=True) 5221 self._match_r_paren() 5222 5223 return self.expression( 5224 exp.Changes( 5225 information=information, 5226 at_before=self._parse_historical_data(), 5227 end=self._parse_historical_data(), 5228 ) 5229 ) 5230 5231 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5232 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5233 return None 5234 5235 self._advance() 5236 5237 expressions = self._parse_wrapped_csv(self._parse_equality) 5238 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5239 5240 alias = self._parse_table_alias() if with_alias else None 5241 5242 if alias: 5243 if self.dialect.UNNEST_COLUMN_ONLY: 5244 if alias.args.get("columns"): 5245 self.raise_error("Unexpected extra column alias in unnest.") 5246 5247 alias.set("columns", [alias.this]) 5248 alias.set("this", None) 5249 5250 columns = alias.args.get("columns") or [] 5251 if offset and len(expressions) < len(columns): 5252 offset = columns.pop() 5253 5254 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5255 self._match(TokenType.ALIAS) 5256 offset = self._parse_id_var( 5257 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5258 ) or exp.to_identifier("offset") 5259 5260 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5261 5262 def _parse_derived_table_values(self) -> exp.Values | None: 5263 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5264 if not is_derived and not ( 5265 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5266 self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES") 5267 ): 5268 return None 5269 5270 expressions = self._parse_csv(self._parse_value) 5271 alias = self._parse_table_alias() 5272 5273 if is_derived: 5274 self._match_r_paren() 5275 5276 return self.expression( 5277 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5278 ) 5279 5280 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5281 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5282 as_modifier and self._match_text_seq("USING", "SAMPLE") 5283 ): 5284 return None 5285 5286 bucket_numerator = None 5287 bucket_denominator = None 5288 bucket_field = None 5289 percent = None 5290 size = None 5291 seed = None 5292 5293 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5294 matched_l_paren = self._match(TokenType.L_PAREN) 5295 5296 if self.TABLESAMPLE_CSV: 5297 num = None 5298 expressions = self._parse_csv(self._parse_primary) 5299 else: 5300 expressions = None 5301 num = ( 5302 self._parse_factor(parse_mod=False) 5303 if self._match(TokenType.NUMBER, advance=False) 5304 else self._parse_primary() or self._parse_placeholder() 5305 ) 5306 5307 if self._match_text_seq("BUCKET"): 5308 bucket_numerator = self._parse_number() 5309 self._match_text_seq("OUT", "OF") 5310 bucket_denominator = bucket_denominator = self._parse_number() 5311 self._match(TokenType.ON) 5312 bucket_field = self._parse_field() 5313 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5314 percent = num 5315 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5316 size = num 5317 else: 5318 percent = num 5319 5320 if matched_l_paren: 5321 self._match_r_paren() 5322 5323 if self._match(TokenType.L_PAREN): 5324 method = self._parse_var(upper=True) 5325 seed = self._match(TokenType.COMMA) and self._parse_number() 5326 self._match_r_paren() 5327 elif self._match_texts(("SEED", "REPEATABLE")): 5328 seed = self._parse_wrapped(self._parse_number) 5329 5330 if not method and self.DEFAULT_SAMPLING_METHOD: 5331 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5332 5333 return self.expression( 5334 exp.TableSample( 5335 expressions=expressions, 5336 method=method, 5337 bucket_numerator=bucket_numerator, 5338 bucket_denominator=bucket_denominator, 5339 bucket_field=bucket_field, 5340 percent=percent, 5341 size=size, 5342 seed=seed, 5343 ) 5344 ) 5345 5346 def _parse_pivots(self) -> list[exp.Pivot] | None: 5347 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5348 return None 5349 return list(iter(self._parse_pivot, None)) or None 5350 5351 def _parse_joins( 5352 self, alias_tokens: t.Collection[TokenType] | None = None 5353 ) -> t.Iterator[exp.Join]: 5354 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5355 5356 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5357 if not self._match(TokenType.INTO): 5358 return None 5359 5360 return self.expression( 5361 exp.UnpivotColumns( 5362 this=self._match_text_seq("NAME") and self._parse_column(), 5363 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5364 ) 5365 ) 5366 5367 # https://duckdb.org/docs/sql/statements/pivot 5368 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5369 def _parse_on() -> exp.Expr | None: 5370 this = self._parse_bitwise() 5371 5372 if self._match(TokenType.IN): 5373 # PIVOT ... ON col IN (row_val1, row_val2) 5374 return self._parse_in(this) 5375 if self._match(TokenType.ALIAS, advance=False): 5376 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5377 return self._parse_alias(this) 5378 5379 return this 5380 5381 this = self._parse_table() 5382 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5383 into = self._parse_unpivot_columns() 5384 using = self._match(TokenType.USING) and self._parse_csv( 5385 lambda: self._parse_alias(self._parse_column()) 5386 ) 5387 group = self._parse_group() 5388 5389 return self.expression( 5390 exp.Pivot( 5391 this=this, 5392 expressions=expressions, 5393 using=using, 5394 group=group, 5395 unpivot=is_unpivot, 5396 into=into, 5397 ) 5398 ) 5399 5400 def _parse_pivot_in(self) -> exp.In: 5401 def _parse_aliased_expression() -> exp.Expr | None: 5402 this = self._parse_select_or_expression() 5403 5404 self._match(TokenType.ALIAS) 5405 alias = self._parse_bitwise() 5406 if alias: 5407 if isinstance(alias, exp.Column) and not alias.db: 5408 alias = alias.this 5409 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5410 5411 return this 5412 5413 value = self._parse_column() 5414 5415 if not self._match(TokenType.IN): 5416 self.raise_error("Expecting IN") 5417 5418 if self._match(TokenType.L_PAREN): 5419 if self._match(TokenType.ANY): 5420 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5421 else: 5422 exprs = self._parse_csv(_parse_aliased_expression) 5423 self._match_r_paren() 5424 return self.expression(exp.In(this=value, expressions=exprs)) 5425 5426 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5427 5428 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5429 func = self._parse_function() 5430 if not func: 5431 if self._prev.token_type == TokenType.COMMA: 5432 return None 5433 self.raise_error("Expecting an aggregation function in PIVOT") 5434 5435 return self._parse_alias(func) 5436 5437 def _parse_pivot(self) -> exp.Pivot | None: 5438 index = self._index 5439 include_nulls = None 5440 5441 if self._match(TokenType.PIVOT): 5442 unpivot = False 5443 elif self._match(TokenType.UNPIVOT): 5444 unpivot = True 5445 5446 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5447 if self._match_text_seq("INCLUDE", "NULLS"): 5448 include_nulls = True 5449 elif self._match_text_seq("EXCLUDE", "NULLS"): 5450 include_nulls = False 5451 else: 5452 return None 5453 5454 expressions = [] 5455 5456 if not self._match(TokenType.L_PAREN): 5457 self._retreat(index) 5458 return None 5459 5460 if unpivot: 5461 expressions = self._parse_csv(self._parse_column) 5462 else: 5463 expressions = self._parse_csv(self._parse_pivot_aggregation) 5464 5465 if not expressions: 5466 self.raise_error("Failed to parse PIVOT's aggregation list") 5467 5468 if not self._match(TokenType.FOR): 5469 self.raise_error("Expecting FOR") 5470 5471 fields = [] 5472 while True: 5473 field = self._try_parse(self._parse_pivot_in) 5474 if not field: 5475 break 5476 fields.append(field) 5477 5478 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5479 self._parse_bitwise 5480 ) 5481 5482 group = self._parse_group() 5483 5484 self._match_r_paren() 5485 5486 pivot = self.expression( 5487 exp.Pivot( 5488 expressions=expressions, 5489 fields=fields, 5490 unpivot=unpivot, 5491 include_nulls=include_nulls, 5492 default_on_null=default_on_null, 5493 group=group, 5494 ) 5495 ) 5496 5497 if unpivot: 5498 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5499 for pivot_field in pivot.fields: 5500 if isinstance(pivot_field, exp.In): 5501 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5502 5503 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5504 5505 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5506 pivot.set("alias", self._parse_table_alias()) 5507 5508 if not unpivot: 5509 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5510 5511 columns: list[exp.Expr] = [] 5512 all_fields = [] 5513 for pivot_field in pivot.fields: 5514 pivot_field_expressions = pivot_field.expressions 5515 5516 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5517 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5518 continue 5519 5520 all_fields.append( 5521 [ 5522 # An explicit `<field> AS <alias>` names the output column directly, 5523 # so it wins over the dialect's string-identifying convention 5524 fld.sql() 5525 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5526 else fld.alias_or_name 5527 for fld in pivot_field_expressions 5528 ] 5529 ) 5530 5531 if all_fields: 5532 if names: 5533 all_fields.append(names) 5534 5535 # Generate all possible combinations of the pivot columns 5536 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5537 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5538 for fld_parts_tuple in itertools.product(*all_fields): 5539 fld_parts = list(fld_parts_tuple) 5540 5541 if names and self.PREFIXED_PIVOT_COLUMNS: 5542 # Move the "name" to the front of the list 5543 fld_parts.insert(0, fld_parts.pop(-1)) 5544 5545 columns.append(exp.to_identifier("_".join(fld_parts))) 5546 5547 pivot.set("columns", columns) 5548 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5549 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5550 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5551 5552 return pivot 5553 5554 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5555 return [agg.alias for agg in aggregations if agg.alias] 5556 5557 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5558 if not skip_where_token and not self._match(TokenType.PREWHERE): 5559 return None 5560 5561 comments = self._prev_comments 5562 return self.expression( 5563 exp.PreWhere(this=self._parse_disjunction()), 5564 comments=comments, 5565 ) 5566 5567 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5568 if not skip_where_token and not self._match(TokenType.WHERE): 5569 return None 5570 5571 comments = self._prev_comments 5572 return self.expression( 5573 exp.Where(this=self._parse_disjunction()), 5574 comments=comments, 5575 ) 5576 5577 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5578 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5579 return None 5580 comments = self._prev_comments 5581 5582 elements: dict[str, t.Any] = defaultdict(list) 5583 5584 if self._match(TokenType.ALL): 5585 elements["all"] = True 5586 elif self._match(TokenType.DISTINCT): 5587 elements["all"] = False 5588 5589 while True: 5590 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5591 # which are also valid identifiers 5592 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5593 break 5594 5595 elements["expressions"].extend( 5596 self._parse_csv( 5597 lambda: ( 5598 self._parse_grouping_sets() 5599 or self._parse_cube_or_rollup() 5600 or self._parse_disjunction() 5601 ) 5602 ) 5603 ) 5604 5605 before_with_index = self._index 5606 5607 if self._match(TokenType.WITH) and ( 5608 cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True) 5609 ): 5610 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5611 elements[key].append(cube_or_rollup) 5612 elif grouping_sets := self._parse_grouping_sets(): 5613 # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...) 5614 elements["grouping_sets"].append(grouping_sets) 5615 break 5616 elif self._match_text_seq("TOTALS"): 5617 elements["totals"] = True # type: ignore 5618 5619 if before_with_index <= self._index <= before_with_index + 1: 5620 self._retreat(before_with_index) 5621 break 5622 5623 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5624 5625 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5626 if self._match(TokenType.CUBE): 5627 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5628 elif self._match(TokenType.ROLLUP): 5629 kind = exp.Rollup 5630 else: 5631 return None 5632 5633 return self.expression( 5634 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5635 ) 5636 5637 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5638 if self._match(TokenType.GROUPING_SETS): 5639 return self.expression( 5640 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5641 ) 5642 return None 5643 5644 def _parse_grouping_set(self) -> exp.Expr | None: 5645 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5646 5647 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5648 if not skip_having_token and not self._match(TokenType.HAVING): 5649 return None 5650 comments = self._prev_comments 5651 return self.expression( 5652 exp.Having(this=self._parse_disjunction()), 5653 comments=comments, 5654 ) 5655 5656 def _parse_qualify(self) -> exp.Qualify | None: 5657 if not self._match(TokenType.QUALIFY): 5658 return None 5659 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5660 5661 def _parse_connect_with_prior(self) -> exp.Expr | None: 5662 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5663 exp.Prior(this=self._parse_bitwise()) 5664 ) 5665 connect = self._parse_disjunction() 5666 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5667 return connect 5668 5669 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5670 if skip_start_token: 5671 start = None 5672 elif self._match_text_seq("START", "WITH"): 5673 start = self._parse_disjunction() 5674 else: 5675 return None 5676 5677 self._match(TokenType.CONNECT_BY) 5678 nocycle = self._match_text_seq("NOCYCLE") 5679 connect = self._parse_connect_with_prior() 5680 5681 if not start and self._match_text_seq("START", "WITH"): 5682 start = self._parse_disjunction() 5683 5684 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5685 5686 def _parse_name_as_expression(self) -> exp.Expr | None: 5687 this = self._parse_id_var(any_token=True) 5688 if self._match(TokenType.ALIAS): 5689 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5690 return this 5691 5692 def _parse_interpolate(self) -> list[exp.Expr] | None: 5693 if self._match_text_seq("INTERPOLATE"): 5694 return self._parse_wrapped_csv(self._parse_name_as_expression) 5695 return None 5696 5697 def _parse_order( 5698 self, this: exp.Expr | None = None, skip_order_token: bool = False 5699 ) -> exp.Expr | None: 5700 siblings = None 5701 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5702 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5703 return this 5704 5705 siblings = True 5706 5707 comments = self._prev_comments 5708 return self.expression( 5709 exp.Order( 5710 this=this, 5711 expressions=self._parse_csv(self._parse_ordered), 5712 siblings=siblings, 5713 ), 5714 comments=comments, 5715 ) 5716 5717 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5718 if not self._match(token): 5719 return None 5720 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5721 5722 def _parse_ordered( 5723 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5724 ) -> exp.Ordered | None: 5725 this = parse_method() if parse_method else self._parse_disjunction() 5726 if not this: 5727 return None 5728 5729 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5730 this = exp.var("ALL") 5731 5732 asc = self._match(TokenType.ASC) 5733 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5734 5735 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5736 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5737 5738 nulls_first = is_nulls_first or False 5739 explicitly_null_ordered = is_nulls_first or is_nulls_last 5740 5741 if ( 5742 not explicitly_null_ordered 5743 and ( 5744 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5745 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5746 ) 5747 and self.dialect.NULL_ORDERING != "nulls_are_last" 5748 ): 5749 nulls_first = True 5750 5751 if self._match_text_seq("WITH", "FILL"): 5752 with_fill = self.expression( 5753 exp.WithFill( 5754 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5755 to=self._match_text_seq("TO") and self._parse_bitwise(), 5756 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5757 interpolate=self._parse_interpolate(), 5758 ) 5759 ) 5760 else: 5761 with_fill = None 5762 5763 return self.expression( 5764 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5765 ) 5766 5767 def _parse_limit_options(self) -> exp.LimitOptions | None: 5768 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5769 rows = self._match_set((TokenType.ROW, TokenType.ROWS)) 5770 self._match_text_seq("ONLY") 5771 with_ties = self._match_text_seq("WITH", "TIES") 5772 5773 if not (percent or rows or with_ties): 5774 return None 5775 5776 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5777 5778 def _parse_limit( 5779 self, 5780 this: exp.Expr | None = None, 5781 top: bool = False, 5782 skip_limit_token: bool = False, 5783 ) -> exp.Expr | None: 5784 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5785 comments = self._prev_comments 5786 if top: 5787 limit_paren = self._match(TokenType.L_PAREN) 5788 expression = ( 5789 self._parse_term() or self._parse_select() 5790 if limit_paren 5791 else self._parse_number() 5792 ) 5793 5794 if limit_paren: 5795 self._match_r_paren() 5796 5797 else: 5798 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5799 return this 5800 5801 expression = self._parse_term(parse_mod=False) 5802 limit_options = self._parse_limit_options() 5803 5804 if self._match(TokenType.COMMA): 5805 offset = expression 5806 expression = self._parse_term() 5807 else: 5808 offset = None 5809 5810 limit_exp = self.expression( 5811 exp.Limit( 5812 this=this, 5813 expression=expression, 5814 offset=offset, 5815 limit_options=limit_options, 5816 expressions=self._parse_limit_by(), 5817 ), 5818 comments=comments, 5819 ) 5820 5821 if top: 5822 limit_exp.meta["top"] = True 5823 5824 return limit_exp 5825 5826 if self._match(TokenType.FETCH): 5827 direction = ( 5828 self._prev.text.upper() 5829 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5830 else "FIRST" 5831 ) 5832 5833 count = self._parse_field(tokens=self.FETCH_TOKENS) 5834 5835 return self.expression( 5836 exp.Fetch( 5837 direction=direction, count=count, limit_options=self._parse_limit_options() 5838 ) 5839 ) 5840 5841 return this 5842 5843 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5844 if not self._match(TokenType.OFFSET): 5845 return this 5846 5847 count = self._parse_term() 5848 self._match_set((TokenType.ROW, TokenType.ROWS)) 5849 5850 return self.expression( 5851 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5852 ) 5853 5854 def _can_parse_limit_or_offset(self) -> bool: 5855 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5856 return False 5857 5858 index = self._index 5859 result = bool( 5860 self._try_parse(self._parse_limit, retreat=True) 5861 or self._try_parse(self._parse_offset, retreat=True) 5862 ) 5863 self._retreat(index) 5864 5865 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 5866 if self._next.token_type == TokenType.MATCH_CONDITION: 5867 result = False 5868 5869 return result 5870 5871 def _can_parse_named_window(self) -> bool: 5872 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 5873 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 5874 if not self._match(TokenType.WINDOW, advance=False): 5875 return False 5876 5877 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 5878 if name is None or name.token_type not in self.ID_VAR_TOKENS: 5879 return False 5880 5881 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 5882 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 5883 return False 5884 5885 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 5886 return body is not None and body.token_type == TokenType.L_PAREN 5887 5888 def _parse_limit_by(self) -> list[exp.Expr] | None: 5889 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 5890 5891 def _parse_locks(self) -> list[exp.Lock]: 5892 locks = [] 5893 while True: 5894 update, key = None, None 5895 if self._match_text_seq("FOR", "UPDATE"): 5896 update = True 5897 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 5898 "LOCK", "IN", "SHARE", "MODE" 5899 ): 5900 update = False 5901 elif self._match_text_seq("FOR", "KEY", "SHARE"): 5902 update, key = False, True 5903 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 5904 update, key = True, True 5905 else: 5906 break 5907 5908 expressions = None 5909 if self._match_text_seq("OF"): 5910 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 5911 5912 wait: bool | exp.Expr | None = None 5913 if self._match_text_seq("NOWAIT"): 5914 wait = True 5915 elif self._match_text_seq("WAIT"): 5916 wait = self._parse_primary() 5917 elif self._match_text_seq("SKIP", "LOCKED"): 5918 wait = False 5919 5920 locks.append( 5921 self.expression( 5922 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 5923 ) 5924 ) 5925 5926 return locks 5927 5928 def parse_set_operation( 5929 self, this: exp.Expr | None, consume_pipe: bool = False 5930 ) -> exp.Expr | None: 5931 start = self._index 5932 _, side_token, kind_token = self._parse_join_parts() 5933 5934 side = side_token.text if side_token else None 5935 kind = kind_token.text if kind_token else None 5936 5937 if not self._match_set(self.SET_OPERATIONS): 5938 self._retreat(start) 5939 return None 5940 5941 token_type = self._prev.token_type 5942 5943 if token_type == TokenType.UNION: 5944 operation: type[exp.SetOperation] = exp.Union 5945 elif token_type == TokenType.EXCEPT: 5946 operation = exp.Except 5947 else: 5948 operation = exp.Intersect 5949 5950 comments = self._prev.comments 5951 5952 if self._match(TokenType.DISTINCT): 5953 distinct: bool | None = True 5954 elif self._match(TokenType.ALL): 5955 distinct = False 5956 else: 5957 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5958 if distinct is None: 5959 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5960 5961 by_name = ( 5962 self._match_text_seq("BY", "NAME") 5963 or self._match_text_seq("STRICT", "CORRESPONDING") 5964 or None 5965 ) 5966 if self._match_text_seq("CORRESPONDING"): 5967 by_name = True 5968 if not side and not kind: 5969 kind = "INNER" 5970 5971 on_column_list = None 5972 if by_name and self._match_texts(("ON", "BY")): 5973 on_column_list = self._parse_wrapped_csv(self._parse_column) 5974 5975 expression = self._parse_select( 5976 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5977 ) 5978 5979 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5980 # in _parse_cte and so that alias pushdown can reach into set operation branches 5981 if isinstance(this, exp.Values): 5982 this = self._values_to_select(this) 5983 if isinstance(expression, exp.Values): 5984 expression = self._values_to_select(expression) 5985 5986 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 5987 subquery = this.this 5988 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 5989 subquery.add_comments(this.pop_comments()) 5990 this = subquery 5991 5992 return self.expression( 5993 operation( 5994 this=this, 5995 distinct=distinct, 5996 by_name=by_name, 5997 expression=expression, 5998 side=side, 5999 kind=kind, 6000 on=on_column_list, 6001 ), 6002 comments=comments, 6003 ) 6004 6005 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 6006 while this: 6007 setop = self.parse_set_operation(this) 6008 if not setop: 6009 break 6010 this = setop 6011 6012 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6013 expression = this.expression 6014 6015 if expression: 6016 for arg in self.SET_OP_MODIFIERS: 6017 expr = expression.args.get(arg) 6018 if expr and not (arg == "limit" and expr.meta.get("top")): 6019 expression.set(arg, None) 6020 this.set(arg, expr) 6021 6022 # A trailing LIMIT/FETCH can coexist with TOP on the final operand. 6023 if self._curr.token_type in (TokenType.LIMIT, TokenType.FETCH): 6024 this = self._parse_query_modifiers(this) 6025 6026 return this 6027 6028 def _parse_expression(self) -> exp.Expr | None: 6029 return self._parse_alias(self._parse_assignment()) 6030 6031 def _parse_assignment(self) -> exp.Expr | None: 6032 this = self._parse_disjunction() 6033 if not this and self._next.token_type in self.ASSIGNMENT: 6034 # This allows us to parse <non-identifier token> := <expr> 6035 this = exp.column( 6036 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6037 ) 6038 6039 while self._match_set(self.ASSIGNMENT): 6040 if isinstance(this, exp.Column) and len(this.parts) == 1: 6041 this = this.this 6042 6043 comments = self._prev_comments 6044 this = self.expression( 6045 self.ASSIGNMENT[self._prev.token_type]( 6046 this=this, expression=self._parse_assignment() 6047 ), 6048 comments=comments, 6049 ) 6050 6051 return this 6052 6053 def _parse_disjunction(self) -> exp.Expr | None: 6054 this = self._parse_conjunction() 6055 while self._match_set(self.DISJUNCTION): 6056 comments = self._prev_comments 6057 this = self.expression( 6058 self.DISJUNCTION[self._prev.token_type]( 6059 this=this, expression=self._parse_conjunction() 6060 ), 6061 comments=comments, 6062 ) 6063 return this 6064 6065 def _parse_conjunction(self) -> exp.Expr | None: 6066 this = self._parse_equality() 6067 while self._match_set(self.CONJUNCTION): 6068 comments = self._prev_comments 6069 this = self.expression( 6070 self.CONJUNCTION[self._prev.token_type]( 6071 this=this, expression=self._parse_equality() 6072 ), 6073 comments=comments, 6074 ) 6075 return this 6076 6077 def _parse_equality(self) -> exp.Expr | None: 6078 this = self._parse_comparison() 6079 while self._match_set(self.EQUALITY): 6080 comments = self._prev_comments 6081 this = self.expression( 6082 self.EQUALITY[self._prev.token_type]( 6083 this=this, expression=self._parse_comparison() 6084 ), 6085 comments=comments, 6086 ) 6087 return this 6088 6089 def _parse_comparison(self) -> exp.Expr | None: 6090 this = self._parse_range() 6091 while self._match_set(self.COMPARISON): 6092 comments = self._prev_comments 6093 this = self.expression( 6094 self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()), 6095 comments=comments, 6096 ) 6097 return this 6098 6099 def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6100 this = this or self._parse_bitwise() 6101 6102 while True: 6103 negate = self._match(TokenType.NOT) 6104 if self._match_set(self.RANGE_PARSERS): 6105 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6106 if not expression: 6107 return this 6108 6109 this = expression 6110 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6111 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6112 elif self._match(TokenType.NOTNULL): 6113 # Postgres supports ISNULL and NOTNULL for conditions. 6114 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6115 if self.dialect.NORMALIZE_NOT_NULL: 6116 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6117 this = self.expression(exp.Not(this=this)) 6118 else: 6119 this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True)) 6120 else: 6121 if negate: 6122 self._retreat(self._index - 1) 6123 break 6124 6125 if negate: 6126 this = self._negate_range(this) 6127 if self._curr and ( 6128 self._curr.token_type == TokenType.NOT 6129 or self._curr.token_type in self.RANGE_PARSERS 6130 ): 6131 this = self.expression(exp.Paren(this=this)) 6132 6133 return this 6134 6135 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6136 if not this: 6137 return this 6138 6139 expression = this.this if isinstance(this, exp.Escape) else this 6140 if isinstance(expression, (exp.Like, exp.ILike)): 6141 expression.set("negate", True) 6142 return this 6143 6144 return self.expression(exp.Not(this=this)) 6145 6146 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6147 index = self._index - 1 6148 negate = self._match(TokenType.NOT) 6149 6150 if self._match_text_seq("DISTINCT", "FROM"): 6151 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6152 return self.expression(klass(this=this, expression=self._parse_bitwise())) 6153 6154 if self._match(TokenType.JSON): 6155 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6156 6157 if self._match_text_seq("WITH"): 6158 _with = True 6159 elif self._match_text_seq("WITHOUT"): 6160 _with = False 6161 else: 6162 _with = None 6163 6164 unique = self._match(TokenType.UNIQUE) 6165 self._match_text_seq("KEYS") 6166 expression: exp.Expr | None = self.expression( 6167 exp.JSON(this=kind, with_=_with, unique=unique) 6168 ) 6169 else: 6170 expression = self._parse_null() or self._parse_bitwise() 6171 if not expression: 6172 self._retreat(index) 6173 return None 6174 6175 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6176 this = self.expression(exp.Is(this=this, expression=expression, negate=True)) 6177 else: 6178 this = self.expression(exp.Is(this=this, expression=expression)) 6179 this = self.expression(exp.Not(this=this)) if negate else this 6180 6181 return self._parse_column_ops(this) 6182 6183 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6184 unnest = self._parse_unnest(with_alias=False) 6185 if unnest: 6186 this = self.expression(exp.In(this=this, unnest=unnest)) 6187 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6188 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6189 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6190 6191 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6192 this = self.expression( 6193 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6194 ) 6195 else: 6196 this = self.expression(exp.In(this=this, expressions=expressions)) 6197 6198 if matched_l_paren: 6199 self._match_r_paren(this) 6200 elif not self._match(TokenType.R_BRACKET, expression=this): 6201 self.raise_error("Expecting ]") 6202 else: 6203 this = self.expression(exp.In(this=this, field=self._parse_column())) 6204 6205 return this 6206 6207 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6208 symmetric = None 6209 if self._match_text_seq("SYMMETRIC"): 6210 symmetric = True 6211 elif self._match_text_seq("ASYMMETRIC"): 6212 symmetric = False 6213 6214 low = self._parse_bitwise() 6215 self._match(TokenType.AND) 6216 high = self._parse_bitwise() 6217 6218 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6219 6220 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6221 if not self._match(TokenType.ESCAPE): 6222 return this 6223 return self.expression( 6224 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6225 ) 6226 6227 def _parse_interval_span( 6228 self, this: exp.Expr, parse_function_unit: bool = True 6229 ) -> exp.Interval: 6230 # handle day-time format interval span with omitted units: 6231 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6232 interval_span_units_omitted = None 6233 if ( 6234 this 6235 and this.is_string 6236 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6237 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6238 ): 6239 index = self._index 6240 6241 # Var "TO" Var 6242 first_unit = self._parse_var(any_token=True, upper=True) 6243 second_unit = None 6244 if first_unit and self._match_text_seq("TO"): 6245 second_unit = self._parse_var(any_token=True, upper=True) 6246 6247 interval_span_units_omitted = not (first_unit and second_unit) 6248 6249 self._retreat(index) 6250 6251 unit_index = self._index 6252 if interval_span_units_omitted: 6253 unit = None 6254 else: 6255 # Only attempt to parse a unit if the current token can actually be one, so that a 6256 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6257 is_unit = self._curr is not None and ( 6258 self._curr.token_type == TokenType.VAR 6259 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6260 ) 6261 unit = self._parse_function() if parse_function_unit and is_unit else None 6262 if not unit and is_unit: 6263 unit = self._parse_var(any_token=True, upper=True) 6264 6265 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6266 # each INTERVAL expression into this canonical form so it's easy to transpile 6267 if this and this.is_number: 6268 try: 6269 this = exp.Literal.string(this.to_py()) 6270 except ValueError: 6271 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6272 elif this and this.is_string: 6273 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6274 if parts and unit: 6275 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6276 unit = None 6277 self._retreat(unit_index) 6278 6279 if len(parts) == 1: 6280 this = exp.Literal.string(parts[0][0]) 6281 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6282 6283 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6284 unit = self.expression( 6285 exp.IntervalSpan( 6286 this=unit, 6287 expression=self._parse_function() 6288 or self._parse_var(any_token=True, upper=True), 6289 ) 6290 ) 6291 6292 return self.expression(exp.Interval(this=this, unit=unit)) 6293 6294 def _parse_interval( 6295 self, require_interval: bool = True, parse_function_unit: bool = True 6296 ) -> exp.Add | exp.Interval | None: 6297 index = self._index 6298 6299 if not self._match(TokenType.INTERVAL) and require_interval: 6300 return None 6301 6302 if self._match(TokenType.STRING, advance=False): 6303 this = self._parse_primary() 6304 else: 6305 this = self._parse_term() 6306 6307 if not this or ( 6308 isinstance(this, exp.Column) 6309 and not this.table 6310 and not this.this.quoted 6311 and self._curr 6312 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6313 ): 6314 self._retreat(index) 6315 return None 6316 6317 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6318 6319 index = self._index 6320 self._match(TokenType.PLUS) 6321 6322 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6323 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6324 return self.expression( 6325 exp.Add( 6326 this=interval, 6327 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6328 ) 6329 ) 6330 6331 self._retreat(index) 6332 return interval 6333 6334 def _parse_bitwise(self) -> exp.Expr | None: 6335 this = self._parse_term() 6336 6337 while True: 6338 if self._match_set(self.BITWISE): 6339 this = self.expression( 6340 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6341 ) 6342 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6343 this = self.expression( 6344 exp.DPipe( 6345 this=this, 6346 expression=self._parse_term(), 6347 safe=not self.dialect.STRICT_STRING_CONCAT, 6348 ) 6349 ) 6350 elif self._match(TokenType.DQMARK): 6351 this = self.expression( 6352 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6353 ) 6354 elif self._match_pair(TokenType.LT, TokenType.LT): 6355 this = self.expression( 6356 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6357 ) 6358 elif self._match_pair(TokenType.GT, TokenType.GT): 6359 this = self.expression( 6360 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6361 ) 6362 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6363 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6364 else: 6365 break 6366 6367 return this 6368 6369 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6370 this = self._parse_factor(parse_mod=parse_mod) 6371 6372 while self._match_set(self.TERM): 6373 klass = self.TERM[self._prev.token_type] 6374 comments = self._prev_comments 6375 expression = self._parse_factor(parse_mod=parse_mod) 6376 6377 this = self.expression(klass(this=this, expression=expression), comments=comments) 6378 6379 if isinstance(this, exp.Collate): 6380 self._normalize_collate(this) 6381 6382 return this 6383 6384 def _normalize_collate(self, collate: exp.Collate) -> None: 6385 expr = collate.expression 6386 6387 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6388 # fallback to Identifier / Var 6389 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6390 ident = expr.this 6391 if isinstance(ident, exp.Identifier): 6392 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6393 6394 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6395 parse_method = self._parse_factor_operand 6396 this = self._parse_at_time_zone(parse_method()) 6397 6398 while self._match_set(self.FACTOR, advance=False): 6399 if not parse_mod and self._curr.token_type == TokenType.MOD: 6400 break 6401 6402 self._advance() 6403 klass = self.FACTOR[self._prev.token_type] 6404 comments = self._prev_comments 6405 expression = parse_method() 6406 6407 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6408 self._retreat(self._index - 1) 6409 return this 6410 6411 this = self.expression(klass(this=this, expression=expression), comments=comments) 6412 6413 if isinstance(this, exp.Div): 6414 this.set("typed", self.dialect.TYPED_DIVISION) 6415 this.set("safe", self.dialect.SAFE_DIVISION) 6416 6417 return this 6418 6419 def _parse_factor_operand(self) -> exp.Expr | None: 6420 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6421 6422 def _parse_exponent(self) -> exp.Expr | None: 6423 this = self._parse_unary() 6424 while self._match_set(self.EXPONENT): 6425 comments = self._prev_comments 6426 this = self.expression( 6427 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6428 comments=comments, 6429 ) 6430 return this 6431 6432 def _parse_unary(self) -> exp.Expr | None: 6433 if self._match_set(self.UNARY_PARSERS): 6434 return self.UNARY_PARSERS[self._prev.token_type](self) 6435 return self._parse_type() 6436 6437 def _parse_type( 6438 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6439 ) -> exp.Expr | None: 6440 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6441 return atom 6442 6443 if interval := parse_interval and self._parse_interval(): 6444 return self._parse_column_ops(interval) 6445 6446 index = self._index 6447 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6448 6449 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6450 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6451 if isinstance(data_type, exp.Cast): 6452 # This constructor can contain ops directly after it, for instance struct unnesting: 6453 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6454 return self._parse_column_ops(data_type) 6455 6456 if data_type: 6457 index2 = self._index 6458 this = self._parse_primary() 6459 6460 if isinstance(this, exp.Literal): 6461 literal = this.name 6462 this = self._parse_column_ops(this) 6463 6464 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6465 if parser: 6466 return parser(self, this, data_type) 6467 6468 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6469 if data_type.is_type(exp.DType.TIMESTAMP): 6470 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6471 elif data_type.is_type(exp.DType.TIME): 6472 data_type = exp.DType.TIMETZ.into_expr() 6473 6474 return self.expression(exp.Cast(this=this, to=data_type)) 6475 6476 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6477 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6478 # 6479 # If the index difference here is greater than 1, that means the parser itself must have 6480 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6481 # 6482 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6483 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6484 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6485 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6486 # 6487 # In these cases, we don't really want to return the converted type, but instead retreat 6488 # and try to parse a Column or Identifier in the section below. 6489 if data_type.expressions and index2 - index > 1: 6490 self._retreat(index2) 6491 return self._parse_column_ops(data_type) 6492 6493 self._retreat(index) 6494 6495 if fallback_to_identifier: 6496 return self._parse_id_var() 6497 6498 return self._parse_column() 6499 6500 def _parse_type_size(self) -> exp.DataTypeParam | None: 6501 this = self._parse_type() 6502 if not this: 6503 return None 6504 6505 if isinstance(this, exp.Column) and not this.table: 6506 this = exp.var(this.name.upper()) 6507 6508 return self.expression( 6509 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6510 ) 6511 6512 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6513 type_name = identifier.name 6514 6515 while self._match(TokenType.DOT): 6516 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6517 6518 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6519 6520 def _parse_types( 6521 self, 6522 check_func: bool = False, 6523 schema: bool = False, 6524 allow_identifiers: bool = True, 6525 with_collation: bool = False, 6526 ) -> exp.Expr | None: 6527 index = self._index 6528 this: exp.Expr | None = None 6529 6530 if self._match_set(self.TYPE_TOKENS): 6531 type_token = self._prev.token_type 6532 else: 6533 type_token = None 6534 identifier = allow_identifiers and self._parse_id_var( 6535 any_token=False, tokens=(TokenType.VAR,) 6536 ) 6537 if isinstance(identifier, exp.Identifier): 6538 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6539 this = exp.DataType.build(identifier, udt=True) 6540 else: 6541 try: 6542 tokens = self.dialect.tokenize(identifier.name) 6543 except TokenError: 6544 tokens = None 6545 6546 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6547 if len(tokens) > 1: 6548 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6549 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6550 this = self._parse_user_defined_type(identifier) 6551 else: 6552 self._retreat(self._index - 1) 6553 return None 6554 else: 6555 return None 6556 6557 if type_token == TokenType.PSEUDO_TYPE: 6558 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6559 6560 if type_token == TokenType.OBJECT_IDENTIFIER: 6561 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6562 6563 # https://materialize.com/docs/sql/types/map/ 6564 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6565 key_type = self._parse_types( 6566 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6567 ) 6568 if not self._match(TokenType.FARROW): 6569 self._retreat(index) 6570 return None 6571 6572 value_type = self._parse_types( 6573 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6574 ) 6575 if not self._match(TokenType.R_BRACKET): 6576 self._retreat(index) 6577 return None 6578 6579 return exp.DataType( 6580 this=exp.DType.MAP, 6581 expressions=[key_type, value_type], 6582 nested=True, 6583 ) 6584 6585 nested = type_token in self.NESTED_TYPE_TOKENS 6586 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6587 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6588 expressions = None 6589 maybe_func = False 6590 6591 if self._match(TokenType.L_PAREN): 6592 if is_struct: 6593 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6594 elif nested: 6595 expressions = self._parse_csv( 6596 lambda: self._parse_types( 6597 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6598 ) 6599 ) 6600 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6601 this = expressions[0] 6602 this.set("nullable", True) 6603 self._match_r_paren() 6604 return this 6605 elif type_token in self.ENUM_TYPE_TOKENS: 6606 expressions = self._parse_csv(self._parse_equality) 6607 elif type_token == TokenType.JSON: 6608 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6609 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6610 expressions = self._parse_csv(self._parse_json_type_arg) 6611 elif is_aggregate: 6612 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6613 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6614 ) 6615 if not func_or_ident: 6616 return None 6617 expressions = [func_or_ident] 6618 if self._match(TokenType.COMMA): 6619 expressions.extend( 6620 self._parse_csv( 6621 lambda: self._parse_types( 6622 check_func=check_func, 6623 schema=schema, 6624 allow_identifiers=allow_identifiers, 6625 ) 6626 ) 6627 ) 6628 else: 6629 expressions = self._parse_csv(self._parse_type_size) 6630 6631 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6632 if type_token == TokenType.VECTOR and len(expressions) == 2: 6633 expressions = self._parse_vector_expressions(expressions) 6634 6635 if not self._match(TokenType.R_PAREN): 6636 self._retreat(index) 6637 return None 6638 6639 maybe_func = True 6640 6641 values: list[exp.Expr] | None = None 6642 6643 if nested and self._match(TokenType.LT): 6644 if is_struct: 6645 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6646 else: 6647 expressions = self._parse_csv( 6648 lambda: self._parse_types( 6649 check_func=check_func, 6650 schema=schema, 6651 allow_identifiers=allow_identifiers, 6652 with_collation=True, 6653 ) 6654 ) 6655 6656 if not self._match(TokenType.GT): 6657 self.raise_error("Expecting >") 6658 6659 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6660 values = self._parse_csv(self._parse_disjunction) 6661 if not values and is_struct: 6662 values = None 6663 self._retreat(self._index - 1) 6664 else: 6665 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6666 6667 if type_token in self.TIMESTAMPS: 6668 if self._match_text_seq("WITH", "TIME", "ZONE"): 6669 maybe_func = False 6670 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6671 this = exp.DataType(this=tz_type, expressions=expressions) 6672 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6673 maybe_func = False 6674 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6675 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6676 maybe_func = False 6677 elif type_token == TokenType.INTERVAL: 6678 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6679 unit = self._parse_var(upper=True) 6680 if self._match_text_seq("TO"): 6681 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6682 6683 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6684 else: 6685 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6686 elif type_token == TokenType.VOID: 6687 this = exp.DataType(this=exp.DType.NULL) 6688 6689 if maybe_func and check_func: 6690 index2 = self._index 6691 peek = self._parse_string() 6692 6693 if not peek: 6694 self._retreat(index) 6695 return None 6696 6697 self._retreat(index2) 6698 6699 if not this: 6700 assert type_token is not None 6701 if self._match_text_seq("UNSIGNED"): 6702 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6703 if not unsigned_type_token: 6704 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6705 6706 type_token = unsigned_type_token or type_token 6707 6708 # NULLABLE without parentheses can be a column (Presto/Trino) 6709 if type_token == TokenType.NULLABLE and not expressions: 6710 self._retreat(index) 6711 return None 6712 6713 this = exp.DataType( 6714 this=exp.DType[type_token.name], 6715 expressions=expressions, 6716 nested=nested, 6717 ) 6718 6719 # Empty arrays/structs are allowed 6720 if values is not None: 6721 cls = exp.Struct if is_struct else exp.Array 6722 this = exp.cast(cls(expressions=values), this, copy=False) 6723 6724 elif expressions: 6725 this.set("expressions", expressions) 6726 6727 # https://materialize.com/docs/sql/types/list/#type-name 6728 while self._match(TokenType.LIST): 6729 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6730 6731 index = self._index 6732 6733 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6734 matched_array = self._match(TokenType.ARRAY) 6735 6736 while self._curr: 6737 datatype_token = self._prev.token_type 6738 matched_l_bracket = self._match(TokenType.L_BRACKET) 6739 6740 if (not matched_l_bracket and not matched_array) or ( 6741 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6742 ): 6743 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6744 # not to be confused with the fixed size array parsing 6745 break 6746 6747 matched_array = False 6748 values = self._parse_csv(self._parse_disjunction) or None 6749 if ( 6750 values 6751 and not schema 6752 and ( 6753 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6754 or datatype_token == TokenType.ARRAY 6755 or not self._match(TokenType.R_BRACKET, advance=False) 6756 ) 6757 ): 6758 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6759 # 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 6760 self._retreat(index) 6761 break 6762 6763 this = exp.DataType( 6764 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6765 ) 6766 self._match(TokenType.R_BRACKET) 6767 6768 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6769 converter = self.TYPE_CONVERTERS.get(this.this) 6770 if converter: 6771 this = converter(t.cast(exp.DataType, this)) 6772 6773 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6774 this.set("collate", self._parse_identifier() or self._parse_column()) 6775 6776 return this 6777 6778 def _parse_json_type_arg(self) -> exp.Expr | None: 6779 """Parse a single argument to ClickHouse's JSON type.""" 6780 6781 # SKIP col or SKIP REGEXP 'pattern' 6782 if self._match_text_seq("SKIP"): 6783 regexp = self._match(TokenType.RLIKE) 6784 arg = self._parse_column() 6785 if isinstance(arg, exp.Column): 6786 arg = arg.to_dot() 6787 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6788 6789 param_or_col = self._parse_column() 6790 if not isinstance(param_or_col, exp.Column): 6791 return None 6792 6793 # Parameter: name=value (e.g., max_dynamic_paths=2) 6794 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 6795 param = param_or_col.name 6796 value = self._parse_primary() 6797 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 6798 6799 # Column type hint: col_name Type 6800 col = param_or_col.to_dot() 6801 kind = self._parse_types(check_func=False, allow_identifiers=False) 6802 return self.expression(exp.ColumnDef(this=col, kind=kind)) 6803 6804 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 6805 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 6806 6807 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 6808 index = self._index 6809 6810 if ( 6811 self._curr 6812 and self._next 6813 and self._curr.token_type in self.TYPE_TOKENS 6814 and self._next.token_type in self.TYPE_TOKENS 6815 ): 6816 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 6817 # type token. Without this, the list will be parsed as a type and we'll eventually crash 6818 this = self._parse_id_var() 6819 else: 6820 this = ( 6821 self._parse_type(parse_interval=False, fallback_to_identifier=True) 6822 or self._parse_id_var() 6823 ) 6824 6825 self._match(TokenType.COLON) 6826 6827 if ( 6828 type_required 6829 and not isinstance(this, exp.DataType) 6830 and not self._match_set(self.TYPE_TOKENS, advance=False) 6831 ): 6832 self._retreat(index) 6833 return self._parse_types() 6834 6835 return self._parse_column_def(this) 6836 6837 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 6838 if not self._match_text_seq("AT", "TIME", "ZONE"): 6839 return this 6840 return self._parse_at_time_zone( 6841 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 6842 ) 6843 6844 def _parse_atom(self) -> exp.Expr | None: 6845 if ( 6846 self._curr.token_type in self.IDENTIFIER_TOKENS 6847 and (column := self._parse_column()) is not None 6848 ): 6849 return column 6850 6851 token = self._curr 6852 token_type = token.token_type 6853 6854 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 6855 return None 6856 6857 next_type = self._next.token_type 6858 6859 if ( 6860 next_type in self.COLUMN_OPERATORS 6861 or next_type in self.COLUMN_POSTFIX_TOKENS 6862 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 6863 ): 6864 return None 6865 6866 self._advance() 6867 return primary_parser(self, token) 6868 6869 def _parse_column(self) -> exp.Expr | None: 6870 column: exp.Expr | None = self._parse_column_parts_fast() 6871 if column is None: 6872 this = self._parse_column_reference() 6873 if not this: 6874 this = self._parse_bracket(this) 6875 column = self._parse_column_ops(this) if this else this 6876 6877 if column: 6878 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 6879 column.set("join_mark", self._match(TokenType.JOIN_MARKER)) 6880 if self.COLON_IS_VARIANT_EXTRACT: 6881 column = self._parse_colon_as_variant_extract(column) 6882 6883 return column 6884 6885 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 6886 """Fast path for simple column and dot references (a, a.b, ...). 6887 6888 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 6889 that nothing complex follows. If it does, retreats and returns None so 6890 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 6891 """ 6892 index = self._index 6893 parts: list[exp.Identifier] | None = None 6894 all_comments: list[str] | None = None 6895 6896 while self._match_set(self.IDENTIFIER_TOKENS): 6897 token = self._prev 6898 comments = self._prev_comments 6899 6900 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 6901 self._retreat(index) 6902 return None 6903 6904 has_dot = self._match(TokenType.DOT) 6905 curr_tt = self._curr.token_type 6906 6907 if not has_dot: 6908 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 6909 self._retreat(index) 6910 return None 6911 elif curr_tt not in self.IDENTIFIER_TOKENS: 6912 self._retreat(index) 6913 return None 6914 6915 if parts is None: 6916 parts = [] 6917 6918 if comments: 6919 if all_comments is None: 6920 all_comments = [] 6921 all_comments.extend(comments) 6922 self._prev_comments = [] 6923 6924 parts.append( 6925 self.expression( 6926 exp.Identifier( 6927 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 6928 ), 6929 token, 6930 ) 6931 ) 6932 6933 if not has_dot: 6934 break 6935 6936 if parts is None: 6937 return None 6938 6939 n = len(parts) 6940 6941 if n == 1: 6942 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 6943 elif n == 2: 6944 column = exp.Column(this=parts[1], table=parts[0]) 6945 elif n == 3: 6946 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 6947 else: 6948 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 6949 6950 for i in range(4, n): 6951 column = exp.Dot(this=column, expression=parts[i]) 6952 6953 if all_comments: 6954 column.add_comments(all_comments) 6955 6956 return column 6957 6958 def _parse_column_reference(self) -> exp.Expr | None: 6959 this = self._parse_field() 6960 if ( 6961 not this 6962 and self._match(TokenType.VALUES, advance=False) 6963 and self.VALUES_FOLLOWED_BY_PAREN 6964 and (not self._next or self._next.token_type != TokenType.L_PAREN) 6965 ): 6966 this = self._parse_id_var() 6967 6968 if isinstance(this, exp.Identifier): 6969 # We bubble up comments from the Identifier to the Column 6970 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 6971 6972 return this 6973 6974 def _build_json_extract( 6975 self, 6976 this: exp.Expr | None, 6977 path_parts: list[exp.JSONPathPart], 6978 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 6979 if len(path_parts) > 1: 6980 this = self.expression( 6981 exp.JSONExtract( 6982 this=this, 6983 expression=exp.JSONPath(expressions=path_parts), 6984 variant_extract=True, 6985 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 6986 ) 6987 ) 6988 path_parts = [exp.JSONPathRoot()] 6989 6990 return this, path_parts 6991 6992 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 6993 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 6994 6995 while self._match(TokenType.COLON): 6996 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 6997 this, path_parts = self._build_json_extract(this, path_parts) 6998 6999 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7000 7001 if key: 7002 quoted = isinstance(key, exp.Identifier) and key.quoted 7003 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 7004 7005 while True: 7006 if self._match(TokenType.DOT): 7007 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7008 7009 if next_key: 7010 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 7011 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 7012 elif self._match(TokenType.L_BRACKET): 7013 bracket_expr = self._parse_bracket_key_value() 7014 7015 if not self._match(TokenType.R_BRACKET): 7016 self.raise_error("Expected ]") 7017 7018 if bracket_expr: 7019 if bracket_expr.is_string: 7020 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7021 elif bracket_expr.is_star: 7022 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7023 elif bracket_expr.is_number: 7024 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7025 else: 7026 this, path_parts = self._build_json_extract(this, path_parts) 7027 7028 this = self.expression( 7029 exp.Bracket( 7030 this=this, expressions=[bracket_expr], json_access=True 7031 ), 7032 ) 7033 7034 elif self._match(TokenType.DCOLON): 7035 this, path_parts = self._build_json_extract(this, path_parts) 7036 7037 cast_type = self._parse_types() 7038 if cast_type: 7039 this = self.expression(exp.Cast(this=this, to=cast_type)) 7040 else: 7041 self.raise_error("Expected type after '::'") 7042 else: 7043 break 7044 7045 this, _ = self._build_json_extract(this, path_parts) 7046 7047 return this 7048 7049 def _parse_dcolon(self) -> exp.Expr | None: 7050 return self._parse_types() 7051 7052 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7053 while self._curr.token_type in self.BRACKETS: 7054 this = self._parse_bracket(this) 7055 7056 column_operators = self.COLUMN_OPERATORS 7057 cast_column_operators = self.CAST_COLUMN_OPERATORS 7058 while self._curr: 7059 op_token = self._curr.token_type 7060 7061 if op_token not in column_operators: 7062 break 7063 op = column_operators[op_token] 7064 self._advance() 7065 7066 if op_token in cast_column_operators: 7067 field = self._parse_dcolon() 7068 if not field: 7069 self.raise_error("Expected type") 7070 elif op and self._curr: 7071 field = self._parse_column_reference() or self._parse_bitwise() 7072 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7073 field = self._parse_column_ops(field) 7074 else: 7075 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7076 field = self._parse_field(any_token=True, anonymous_func=True) 7077 7078 # In t.true, t.null we should produce an Identifier node 7079 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7080 field = self.expression( 7081 exp.Identifier(this=self._prev.text), 7082 comments=field.comments, 7083 ) 7084 7085 # Function calls can be qualified, e.g., x.y.FOO() 7086 # This converts the final AST to a series of Dots leading to the function call 7087 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7088 if isinstance(field, (exp.Func, exp.Window)) and this: 7089 this = this.transform( 7090 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7091 ) 7092 7093 if op: 7094 this = op(self, this, field) 7095 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7096 this = self.expression( 7097 exp.Column( 7098 this=field, 7099 table=this.this, 7100 db=this.args.get("table"), 7101 catalog=this.args.get("db"), 7102 ), 7103 comments=this.comments, 7104 ) 7105 elif isinstance(field, exp.Window): 7106 # Move the exp.Dot's to the window's function 7107 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7108 field.set("this", window_func) 7109 this = field 7110 else: 7111 this = self.expression(exp.Dot(this=this, expression=field)) 7112 7113 if field and field.comments: 7114 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7115 7116 this = self._parse_bracket(this) 7117 7118 return this 7119 7120 def _parse_paren(self) -> exp.Expr | None: 7121 if not self._match(TokenType.L_PAREN): 7122 return None 7123 7124 comments = self._prev_comments 7125 query = self._parse_select() 7126 7127 if query: 7128 expressions = [query] 7129 else: 7130 expressions = self._parse_expressions() 7131 7132 this = seq_get(expressions, 0) 7133 7134 if not this and self._match(TokenType.R_PAREN, advance=False): 7135 this = self.expression(exp.Tuple()) 7136 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7137 this = self.expression(exp.Tuple(expressions=expressions)) 7138 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7139 this = self._parse_subquery(this=this, parse_alias=False) 7140 elif isinstance(this, (exp.Subquery, exp.Values)): 7141 this = self._parse_subquery( 7142 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7143 parse_alias=False, 7144 ) 7145 else: 7146 this = self.expression(exp.Paren(this=this)) 7147 7148 if this: 7149 this.add_comments(comments) 7150 7151 self._match_r_paren(expression=this) 7152 7153 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7154 return self._parse_window(this) 7155 7156 return this 7157 7158 def _parse_primary(self) -> exp.Expr | None: 7159 if self._match_set(self.PRIMARY_PARSERS): 7160 token_type = self._prev.token_type 7161 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7162 7163 if token_type == TokenType.STRING: 7164 expressions = [primary] 7165 while self._match(TokenType.STRING, advance=False): 7166 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7167 self.raise_error( 7168 "Adjacent string literals need to be separated by whitespace or comments" 7169 ) 7170 7171 self._advance() 7172 expressions.append(exp.Literal.string(self._prev.text)) 7173 7174 if len(expressions) > 1: 7175 return self.expression( 7176 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7177 ) 7178 7179 return primary 7180 7181 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7182 return exp.Literal.number(f"0.{self._prev.text}") 7183 7184 return self._parse_paren() 7185 7186 def _parse_field( 7187 self, 7188 any_token: bool = False, 7189 tokens: t.Collection[TokenType] | None = None, 7190 anonymous_func: bool = False, 7191 ) -> exp.Expr | None: 7192 after_dot = ( 7193 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7194 ) 7195 7196 if anonymous_func: 7197 field = ( 7198 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7199 or self._parse_primary() 7200 ) 7201 else: 7202 field = self._parse_primary() or self._parse_function( 7203 anonymous=anonymous_func, any_token=any_token 7204 ) 7205 7206 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7207 7208 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7209 name = field.name 7210 if self._is_connected() and self._parse_var(any_token=True): 7211 name += self._prev.text 7212 7213 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7214 7215 return field 7216 7217 def _parse_function( 7218 self, 7219 functions: dict[str, t.Callable] | None = None, 7220 anonymous: bool = False, 7221 optional_parens: bool = True, 7222 any_token: bool = False, 7223 ) -> exp.Expr | None: 7224 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7225 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7226 fn_syntax = False 7227 if ( 7228 self._match(TokenType.L_BRACE, advance=False) 7229 and self._next 7230 and self._next.text.upper() == "FN" 7231 ): 7232 self._advance(2) 7233 fn_syntax = True 7234 7235 func = self._parse_function_call( 7236 functions=functions, 7237 anonymous=anonymous, 7238 optional_parens=optional_parens, 7239 any_token=any_token, 7240 ) 7241 7242 if fn_syntax: 7243 self._match(TokenType.R_BRACE) 7244 7245 return func 7246 7247 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7248 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7249 7250 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7251 args = self._parse_function_args(alias=False) 7252 if not args: 7253 self.raise_error("Expected at least one argument") 7254 7255 # Wrapped so the connector keeps its precedence in the parent context 7256 return exp.Paren(this=connector(*args, copy=False)) 7257 7258 def _parse_function_call( 7259 self, 7260 functions: dict[str, t.Callable] | None = None, 7261 anonymous: bool = False, 7262 optional_parens: bool = True, 7263 any_token: bool = False, 7264 ) -> exp.Expr | None: 7265 if not self._curr: 7266 return None 7267 7268 comments = self._curr.comments 7269 prev = self._prev 7270 token = self._curr 7271 token_type = self._curr.token_type 7272 this: str | exp.Expr = self._curr.text 7273 upper = self._curr.text.upper() 7274 7275 after_dot = prev.token_type == TokenType.DOT 7276 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7277 if ( 7278 optional_parens 7279 and parser 7280 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7281 and not after_dot 7282 ): 7283 self._advance() 7284 return self._parse_window(parser(self)) 7285 7286 if self._next.token_type != TokenType.L_PAREN: 7287 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7288 self._advance() 7289 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7290 7291 return None 7292 7293 if any_token: 7294 if token_type in self.RESERVED_TOKENS: 7295 return None 7296 elif token_type not in self.FUNC_TOKENS: 7297 return None 7298 7299 self._advance(2) 7300 7301 parser = self.FUNCTION_PARSERS.get(upper) 7302 if parser and not anonymous: 7303 result = parser(self) 7304 else: 7305 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7306 7307 if subquery_predicate: 7308 expr = None 7309 if self._curr.token_type in self.SUBQUERY_TOKENS: 7310 expr = self._parse_select() 7311 self._match_r_paren() 7312 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7313 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7314 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7315 self._advance(-1) 7316 expr = self._parse_bitwise() 7317 7318 if expr: 7319 return self.expression(subquery_predicate(this=expr), comments=comments) 7320 7321 if functions is None: 7322 functions = self.FUNCTIONS 7323 7324 function = functions.get(upper) 7325 known_function = function and not anonymous 7326 7327 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7328 args = self._parse_function_args(alias) 7329 7330 post_func_comments = self._curr.comments if self._curr else None 7331 if known_function and post_func_comments: 7332 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7333 # call we'll construct it as exp.Anonymous, even if it's "known" 7334 if any( 7335 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7336 for comment in post_func_comments 7337 ): 7338 known_function = False 7339 7340 if alias and known_function: 7341 args = self._kv_to_prop_eq(args) 7342 7343 if known_function: 7344 func_builder = t.cast(t.Callable, function) 7345 7346 # mypyc compiled functions don't have __code__, so we use 7347 # try/except to check if func_builder accepts 'dialect'. 7348 try: 7349 func = func_builder(args) 7350 except TypeError: 7351 func = func_builder(args, dialect=self.dialect) 7352 7353 func = self.validate_expression(func, args) 7354 if self.dialect.PRESERVE_ORIGINAL_NAMES: 7355 func.meta["name"] = this 7356 7357 result = func 7358 else: 7359 if token_type == TokenType.IDENTIFIER: 7360 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7361 7362 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7363 7364 result = result.update_positions(token) 7365 7366 if isinstance(result, exp.Expr): 7367 result.add_comments(comments) 7368 7369 if parser: 7370 self._match(TokenType.R_PAREN, expression=result) 7371 else: 7372 self._match_r_paren(result) 7373 return self._parse_window(result) 7374 7375 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7376 return expression 7377 7378 def _kv_to_prop_eq( 7379 self, expressions: list[exp.Expr], parse_map: bool = False 7380 ) -> list[exp.Expr]: 7381 transformed = [] 7382 7383 for index, e in enumerate(expressions): 7384 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7385 if isinstance(e, exp.Alias): 7386 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7387 7388 if not isinstance(e, exp.PropertyEQ): 7389 e = self.expression( 7390 exp.PropertyEQ( 7391 this=e.this if parse_map else exp.to_identifier(e.this.name), 7392 expression=e.expression, 7393 ) 7394 ) 7395 7396 if isinstance(e.this, exp.Column): 7397 e.this.replace(e.this.this) 7398 else: 7399 e = self._to_prop_eq(e, index) 7400 7401 transformed.append(e) 7402 7403 return transformed 7404 7405 def _parse_function_properties(self) -> exp.Properties | None: 7406 # Skip the generic `key = value` fallback in _parse_property since this 7407 # runs post-AS where a function body like `name = expr` can be misread 7408 # as a property. 7409 properties = [] 7410 while True: 7411 if self._match_texts(self.PROPERTY_PARSERS): 7412 keyword = self._prev.text.upper() 7413 prop = self.PROPERTY_PARSERS[keyword](self) 7414 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7415 keyword = self._prev.text.upper() 7416 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7417 else: 7418 break 7419 if not prop: 7420 self.raise_error(f"Failed to parse property '{keyword}'") 7421 break 7422 for p in ensure_list(prop): 7423 properties.append(p) 7424 7425 return self.expression(exp.Properties(expressions=properties)) if properties else None 7426 7427 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7428 return self._parse_statement() 7429 7430 def _parse_function_parameter(self) -> exp.Expr | None: 7431 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7432 7433 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7434 this = self._parse_table_parts(schema=True) 7435 7436 if not self._match(TokenType.L_PAREN): 7437 return this 7438 7439 expressions = self._parse_csv(self._parse_function_parameter) 7440 self._match_r_paren() 7441 return self.expression( 7442 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7443 ) 7444 7445 def _parse_macro_overloads( 7446 self, 7447 this: exp.UserDefinedFunction, 7448 first_body: exp.Expr, 7449 first_is_table: bool = False, 7450 ) -> exp.MacroOverloads: 7451 overloads = [ 7452 self.expression( 7453 exp.MacroOverload( 7454 this=first_body, 7455 expressions=this.expressions or None, 7456 is_table=first_is_table, 7457 ) 7458 ) 7459 ] 7460 this.set("expressions", None) 7461 this.set("wrapped", False) 7462 7463 while self._match(TokenType.COMMA): 7464 if not self._match(TokenType.L_PAREN): 7465 break 7466 7467 params = self._parse_csv(self._parse_function_parameter) 7468 self._match_r_paren() 7469 7470 if not self._match(TokenType.ALIAS): 7471 break 7472 7473 is_table = self._match(TokenType.TABLE) 7474 body = self._parse_expression() 7475 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7476 overloads.append(self.expression(macro)) 7477 7478 return self.expression(exp.MacroOverloads(expressions=overloads)) 7479 7480 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7481 literal = self._parse_primary() 7482 if literal: 7483 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7484 7485 return self._identifier_expression(token) 7486 7487 def _parse_session_parameter(self) -> exp.SessionParameter: 7488 kind = None 7489 this = self._parse_id_var() or self._parse_primary() 7490 7491 if this and self._match(TokenType.DOT): 7492 kind = this.name 7493 this = self._parse_var() or self._parse_primary() 7494 7495 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7496 7497 def _parse_lambda_arg(self) -> exp.Expr | None: 7498 return self._parse_id_var() 7499 7500 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7501 next_token_type = self._next.token_type 7502 7503 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7504 if ( 7505 next_token_type in self.LAMBDA_ARG_TERMINATORS 7506 and (atom := self._parse_atom()) is not None 7507 ): 7508 return atom 7509 7510 index = self._index 7511 7512 if self._match(TokenType.L_PAREN): 7513 expressions = t.cast( 7514 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7515 ) 7516 7517 if not self._match(TokenType.R_PAREN): 7518 self._retreat(index) 7519 elif self._match_set(self.LAMBDAS): 7520 return self.LAMBDAS[self._prev.token_type](self, expressions) 7521 else: 7522 self._retreat(index) 7523 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7524 expressions = [self._parse_lambda_arg()] 7525 7526 if self._match_set(self.LAMBDAS): 7527 return self.LAMBDAS[self._prev.token_type](self, expressions) 7528 7529 self._retreat(index) 7530 7531 this: exp.Expr | None 7532 7533 if self._match(TokenType.DISTINCT): 7534 this = self.expression( 7535 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7536 ) 7537 else: 7538 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7539 this = self._parse_select_or_expression(alias=alias) 7540 7541 return self._parse_limit( 7542 self._parse_respect_or_ignore_nulls( 7543 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7544 ) 7545 ) 7546 7547 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7548 index = self._index 7549 if not self._match(TokenType.L_PAREN): 7550 return this 7551 7552 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7553 # expr can be of both types 7554 if self._match_set(self.SELECT_START_TOKENS): 7555 self._retreat(index) 7556 return this 7557 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7558 self._match_r_paren() 7559 return self.expression(exp.Schema(this=this, expressions=args)) 7560 7561 def _parse_field_def(self) -> exp.Expr | None: 7562 return self._parse_column_def(self._parse_field(any_token=True)) 7563 7564 def _parse_column_def( 7565 self, this: exp.Expr | None, computed_column: bool = True 7566 ) -> exp.Expr | None: 7567 # column defs are not really columns, they're identifiers 7568 if isinstance(this, exp.Column): 7569 this = this.this 7570 7571 if not computed_column: 7572 self._match(TokenType.ALIAS) 7573 7574 kind = self._parse_types(schema=True) 7575 7576 if self._match_text_seq("FOR", "ORDINALITY"): 7577 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7578 7579 constraints: list[exp.Expr] = [] 7580 7581 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7582 ("ALIAS", "MATERIALIZED") 7583 ): 7584 # Match storage before _parse_types so STORED is not treated as a data type 7585 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7586 persisted = self._prev.text.upper() == "MATERIALIZED" 7587 expression = self._parse_disjunction() 7588 if not persisted: 7589 if self._match_text_seq("PERSISTED"): 7590 persisted = True 7591 elif self._match_texts(("STORED", "VIRTUAL")): 7592 persisted = self._prev.text.upper() == "STORED" 7593 constraint_kind = exp.ComputedColumnConstraint( 7594 this=expression, 7595 persisted=persisted, 7596 data_type=exp.Var(this="AUTO") 7597 if self._match_text_seq("AUTO") 7598 else self._parse_types(), 7599 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7600 ) 7601 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7602 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7603 in_out_constraint = self.expression( 7604 exp.InOutColumnConstraint( 7605 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7606 ) 7607 ) 7608 constraints.append(in_out_constraint) 7609 kind = self._parse_types() 7610 elif ( 7611 kind 7612 and self._match(TokenType.ALIAS, advance=False) 7613 and ( 7614 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7615 or self._next.token_type == TokenType.L_PAREN 7616 ) 7617 ): 7618 self._advance() 7619 constraints.append( 7620 self.expression( 7621 exp.ColumnConstraint( 7622 kind=exp.ComputedColumnConstraint( 7623 this=self._parse_disjunction(), 7624 persisted=self._match_texts(("STORED", "VIRTUAL")) 7625 and self._prev.text.upper() == "STORED", 7626 ) 7627 ) 7628 ) 7629 ) 7630 7631 while True: 7632 constraint = self._parse_column_constraint() 7633 if not constraint: 7634 break 7635 constraints.append(constraint) 7636 7637 if not kind and not constraints: 7638 return this 7639 7640 position = None 7641 if self._match_texts(("FIRST", "AFTER")): 7642 pos = self._prev.text 7643 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7644 7645 return self.expression( 7646 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7647 ) 7648 7649 def _parse_auto_increment( 7650 self, 7651 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7652 start = None 7653 increment = None 7654 order = None 7655 7656 if self._match(TokenType.L_PAREN, advance=False): 7657 args = self._parse_wrapped_csv(self._parse_bitwise) 7658 start = seq_get(args, 0) 7659 increment = seq_get(args, 1) 7660 7661 # The remaining parts form an unordered bag and any of them can be omitted, in which 7662 # case the engine falls back to its own default, so they're parsed independently. 7663 while True: 7664 if self._match_text_seq("START"): 7665 start = self._parse_bitwise() 7666 elif self._match_text_seq("INCREMENT"): 7667 increment = self._parse_bitwise() 7668 elif self._match_text_seq("ORDER"): 7669 order = True 7670 elif self._match_text_seq("NOORDER"): 7671 order = False 7672 else: 7673 break 7674 7675 if start or increment or order is not None: 7676 return exp.GeneratedAsIdentityColumnConstraint( 7677 start=start, increment=increment, this=False, order=order 7678 ) 7679 7680 return exp.AutoIncrementColumnConstraint() 7681 7682 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7683 if not self._match(TokenType.L_PAREN, advance=False): 7684 return None 7685 7686 return self.expression( 7687 exp.CheckColumnConstraint( 7688 this=self._parse_wrapped(self._parse_assignment), 7689 enforced=self._match_text_seq("ENFORCED"), 7690 ) 7691 ) 7692 7693 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7694 if not self._match_text_seq("REFRESH"): 7695 self._retreat(self._index - 1) 7696 return None 7697 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7698 7699 def _parse_compress(self) -> exp.CompressColumnConstraint: 7700 if self._match(TokenType.L_PAREN, advance=False): 7701 return self.expression( 7702 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7703 ) 7704 7705 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7706 7707 def _parse_generated_as_identity( 7708 self, 7709 ) -> ( 7710 exp.GeneratedAsIdentityColumnConstraint 7711 | exp.ComputedColumnConstraint 7712 | exp.GeneratedAsRowColumnConstraint 7713 ): 7714 if self._match_text_seq("BY", "DEFAULT"): 7715 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7716 this = self.expression( 7717 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7718 ) 7719 else: 7720 self._match_text_seq("ALWAYS") 7721 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7722 7723 self._match(TokenType.ALIAS) 7724 7725 if self._match_text_seq("ROW"): 7726 start = self._match_text_seq("START") 7727 if not start: 7728 self._match(TokenType.END) 7729 hidden = self._match_text_seq("HIDDEN") 7730 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7731 7732 identity = self._match_text_seq("IDENTITY") 7733 7734 if self._match(TokenType.L_PAREN): 7735 if self._match_text_seq("START", "WITH"): 7736 this.set("start", self._parse_bitwise()) 7737 if self._match_text_seq("INCREMENT", "BY"): 7738 this.set("increment", self._parse_bitwise()) 7739 if self._match_text_seq("MINVALUE"): 7740 this.set("minvalue", self._parse_bitwise()) 7741 if self._match_text_seq("MAXVALUE"): 7742 this.set("maxvalue", self._parse_bitwise()) 7743 7744 if self._match_text_seq("CYCLE"): 7745 this.set("cycle", True) 7746 elif self._match_text_seq("NO", "CYCLE"): 7747 this.set("cycle", False) 7748 7749 if not identity: 7750 this.set("expression", self._parse_range()) 7751 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7752 args = self._parse_csv(self._parse_bitwise) 7753 this.set("start", seq_get(args, 0)) 7754 this.set("increment", seq_get(args, 1)) 7755 7756 self._match_r_paren() 7757 7758 return this 7759 7760 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7761 self._match_text_seq("LENGTH") 7762 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7763 7764 def _parse_not_constraint(self) -> exp.Expr | None: 7765 if self._match_text_seq("NULL"): 7766 return self.expression(exp.NotNullColumnConstraint()) 7767 if self._match_text_seq("CASESPECIFIC"): 7768 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7769 if self._match_text_seq("FOR", "REPLICATION"): 7770 return self.expression(exp.NotForReplicationColumnConstraint()) 7771 7772 # Unconsume the `NOT` token 7773 self._retreat(self._index - 1) 7774 return None 7775 7776 def _parse_column_constraint(self) -> exp.Expr | None: 7777 this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None 7778 7779 procedure_option_follows = ( 7780 self._match(TokenType.WITH, advance=False) 7781 and self._next 7782 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7783 ) 7784 7785 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 7786 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 7787 if not constraint: 7788 self._retreat(self._index - 1) 7789 return None 7790 7791 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 7792 7793 if self._match_text_seq("CHARACTER", "SET"): 7794 return self.expression( 7795 exp.ColumnConstraint( 7796 this=this, 7797 kind=self.expression( 7798 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 7799 ), 7800 ) 7801 ) 7802 7803 return this 7804 7805 def _parse_constraint(self) -> exp.Expr | None: 7806 if not self._match(TokenType.CONSTRAINT): 7807 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 7808 7809 return self.expression( 7810 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 7811 ) 7812 7813 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 7814 constraints = [] 7815 while True: 7816 constraint = self._parse_unnamed_constraint() or self._parse_function() 7817 if not constraint: 7818 break 7819 constraints.append(constraint) 7820 7821 return constraints 7822 7823 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 7824 index = self._index 7825 7826 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 7827 constraints or self.CONSTRAINT_PARSERS 7828 ): 7829 return None 7830 7831 constraint_key = self._prev.text.upper() 7832 if constraint_key not in self.CONSTRAINT_PARSERS: 7833 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 7834 7835 result = self.CONSTRAINT_PARSERS[constraint_key](self) 7836 if not result: 7837 self._retreat(index) 7838 7839 return result 7840 7841 def _parse_unique_key(self) -> exp.Expr | None: 7842 if ( 7843 self._curr 7844 and self._curr.token_type != TokenType.IDENTIFIER 7845 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 7846 ): 7847 return None 7848 return self._parse_id_var(any_token=False) 7849 7850 def _parse_unique(self) -> exp.UniqueColumnConstraint: 7851 self._match_texts(("KEY", "INDEX")) 7852 return self.expression( 7853 exp.UniqueColumnConstraint( 7854 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 7855 this=self._parse_schema(self._parse_unique_key()), 7856 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 7857 on_conflict=self._parse_on_conflict(), 7858 options=self._parse_key_constraint_options(), 7859 ) 7860 ) 7861 7862 def _parse_key_constraint_options(self) -> list[str]: 7863 options = [] 7864 while True: 7865 if not self._curr: 7866 break 7867 7868 if self._match(TokenType.ON): 7869 action = None 7870 on = self._advance_any() and self._prev.text 7871 7872 if self._match_text_seq("NO", "ACTION"): 7873 action = "NO ACTION" 7874 elif self._match_text_seq("CASCADE"): 7875 action = "CASCADE" 7876 elif self._match_text_seq("RESTRICT"): 7877 action = "RESTRICT" 7878 elif self._match_pair(TokenType.SET, TokenType.NULL): 7879 action = "SET NULL" 7880 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 7881 action = "SET DEFAULT" 7882 else: 7883 self.raise_error("Invalid key constraint") 7884 7885 options.append(f"ON {on} {action}") 7886 else: 7887 var = self._parse_var_from_options( 7888 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 7889 ) 7890 if not var: 7891 break 7892 options.append(var.name) 7893 7894 return options 7895 7896 def _parse_references(self, match: bool = True) -> exp.Reference | None: 7897 if match and not self._match(TokenType.REFERENCES): 7898 return None 7899 7900 expressions: list | None = None 7901 this = self._parse_table(schema=True) 7902 options = self._parse_key_constraint_options() 7903 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 7904 7905 def _parse_foreign_key(self) -> exp.ForeignKey: 7906 expressions = ( 7907 self._parse_wrapped_id_vars() 7908 if not self._match(TokenType.REFERENCES, advance=False) 7909 else None 7910 ) 7911 reference = self._parse_references() 7912 on_options = {} 7913 7914 while self._match(TokenType.ON): 7915 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 7916 self.raise_error("Expected DELETE or UPDATE") 7917 7918 kind = self._prev.text.lower() 7919 7920 if self._match_text_seq("NO", "ACTION"): 7921 action = "NO ACTION" 7922 elif self._match(TokenType.SET): 7923 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 7924 action = "SET " + self._prev.text.upper() 7925 else: 7926 self._advance() 7927 action = self._prev.text.upper() 7928 7929 on_options[kind] = action 7930 7931 return self.expression( 7932 exp.ForeignKey( 7933 expressions=expressions, 7934 reference=reference, 7935 options=self._parse_key_constraint_options(), 7936 **on_options, 7937 ) 7938 ) 7939 7940 def _parse_primary_key_part(self) -> exp.Expr | None: 7941 return self._parse_field() 7942 7943 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 7944 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 7945 self._retreat(self._index - 1) 7946 return None 7947 7948 id_vars = self._parse_wrapped_id_vars() 7949 return self.expression( 7950 exp.PeriodForSystemTimeConstraint( 7951 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 7952 ) 7953 ) 7954 7955 def _parse_primary_key( 7956 self, 7957 wrapped_optional: bool = False, 7958 in_props: bool = False, 7959 named_primary_key: bool = False, 7960 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 7961 desc = ( 7962 self._prev.token_type == TokenType.DESC 7963 if self._match_set((TokenType.ASC, TokenType.DESC)) 7964 else None 7965 ) 7966 7967 this = None 7968 if ( 7969 named_primary_key 7970 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 7971 and self._next 7972 and self._next.token_type == TokenType.L_PAREN 7973 ): 7974 this = self._parse_id_var() 7975 7976 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 7977 return self.expression( 7978 exp.PrimaryKeyColumnConstraint( 7979 desc=desc, options=self._parse_key_constraint_options() 7980 ) 7981 ) 7982 7983 expressions = self._parse_wrapped_csv( 7984 self._parse_primary_key_part, optional=wrapped_optional 7985 ) 7986 7987 return self.expression( 7988 exp.PrimaryKey( 7989 this=this, 7990 expressions=expressions, 7991 include=self._parse_index_params(), 7992 options=self._parse_key_constraint_options(), 7993 ) 7994 ) 7995 7996 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 7997 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 7998 7999 def _parse_odbc_datetime_literal(self) -> exp.Expr: 8000 """ 8001 Parses a datetime column in ODBC format. We parse the column into the corresponding 8002 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 8003 same as we did for `DATE('yyyy-mm-dd')`. 8004 8005 Reference: 8006 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 8007 """ 8008 self._match(TokenType.VAR) 8009 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 8010 expression = self.expression(exp_class(this=self._parse_string())) 8011 if not self._match(TokenType.R_BRACE): 8012 self.raise_error("Expected }") 8013 return expression 8014 8015 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 8016 if not self._match_set(self.BRACKETS): 8017 return this 8018 8019 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8020 map_token = seq_get(self._tokens, self._index - 2) 8021 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8022 else: 8023 parse_map = False 8024 8025 bracket_kind = self._prev.token_type 8026 if ( 8027 bracket_kind == TokenType.L_BRACE 8028 and self._curr 8029 and self._curr.token_type == TokenType.VAR 8030 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8031 ): 8032 return self._parse_odbc_datetime_literal() 8033 8034 expressions = self._parse_csv( 8035 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8036 ) 8037 8038 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8039 self.raise_error("Expected ]") 8040 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8041 self.raise_error("Expected }") 8042 8043 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8044 if bracket_kind == TokenType.L_BRACE: 8045 this = self.expression( 8046 exp.Struct( 8047 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8048 ) 8049 ) 8050 elif not this: 8051 this = build_array_constructor( 8052 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8053 ) 8054 else: 8055 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8056 if constructor_type: 8057 return build_array_constructor( 8058 constructor_type, 8059 args=expressions, 8060 bracket_kind=bracket_kind, 8061 dialect=self.dialect, 8062 ) 8063 8064 expressions = apply_index_offset( 8065 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8066 ) 8067 this = self.expression( 8068 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8069 ) 8070 8071 self._add_comments(this) 8072 return self._parse_bracket(this) 8073 8074 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8075 if not self._match(TokenType.COLON): 8076 return this 8077 8078 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8079 self._advance() 8080 end: exp.Expr | None = -exp.Literal.number("1") 8081 else: 8082 end = self._parse_assignment() 8083 step = self._parse_unary() if self._match(TokenType.COLON) else None 8084 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8085 8086 def _parse_case(self) -> exp.Expr | None: 8087 if self._match(TokenType.DOT, advance=False): 8088 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8089 self._retreat(self._index - 1) 8090 return None 8091 8092 ifs = [] 8093 default = None 8094 8095 comments = self._prev_comments 8096 expression = self._parse_disjunction() 8097 8098 while self._match(TokenType.WHEN): 8099 this = self._parse_disjunction() 8100 self._match(TokenType.THEN) 8101 then = self._parse_disjunction() 8102 ifs.append(self.expression(exp.If(this=this, true=then))) 8103 8104 if self._match(TokenType.ELSE): 8105 default = self._parse_disjunction() 8106 8107 if not self._match(TokenType.END): 8108 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8109 default = exp.column("interval") 8110 else: 8111 self.raise_error("Expected END after CASE", self._prev) 8112 8113 return self.expression( 8114 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8115 ) 8116 8117 def _parse_if(self) -> exp.Expr | None: 8118 if self._match(TokenType.L_PAREN): 8119 args = self._parse_csv( 8120 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8121 ) 8122 this = self.validate_expression(exp.If.from_arg_list(args), args) 8123 self._match_r_paren() 8124 else: 8125 index = self._index - 1 8126 8127 if self.NO_PAREN_IF_COMMANDS and index == 0: 8128 return self._parse_as_command(self._prev) 8129 8130 condition = self._parse_disjunction() 8131 8132 if not condition: 8133 self._retreat(index) 8134 return None 8135 8136 self._match(TokenType.THEN) 8137 true = self._parse_disjunction() 8138 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8139 self._match(TokenType.END) 8140 this = self.expression(exp.If(this=condition, true=true, false=false)) 8141 8142 return this 8143 8144 def _parse_next_value_for(self) -> exp.Expr | None: 8145 if not self._match_text_seq("VALUE", "FOR"): 8146 self._retreat(self._index - 1) 8147 return None 8148 8149 return self.expression( 8150 exp.NextValueFor( 8151 this=self._parse_column(), 8152 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8153 ) 8154 ) 8155 8156 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8157 this = self._parse_function() or self._parse_var_or_string(upper=True) 8158 8159 if self._match(TokenType.FROM): 8160 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8161 8162 if not self._match(TokenType.COMMA): 8163 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8164 8165 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8166 8167 def _parse_gap_fill(self) -> exp.GapFill: 8168 self._match(TokenType.TABLE) 8169 this = self._parse_table() 8170 8171 self._match(TokenType.COMMA) 8172 args = [this, *self._parse_csv(self._parse_lambda)] 8173 8174 gap_fill = exp.GapFill.from_arg_list(args) 8175 return self.validate_expression(gap_fill, args) 8176 8177 def _parse_char(self) -> exp.Chr: 8178 return self.expression( 8179 exp.Chr( 8180 expressions=self._parse_csv(self._parse_assignment), 8181 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8182 ) 8183 ) 8184 8185 def _parse_charset_name(self) -> exp.Expr | None: 8186 """ 8187 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8188 for specific name shapes override this. 8189 """ 8190 return self._parse_var( 8191 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8192 ) 8193 8194 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8195 this = self._parse_assignment() 8196 8197 if not self._match(TokenType.ALIAS): 8198 if self._match(TokenType.COMMA): 8199 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8200 8201 self.raise_error("Expected AS after CAST") 8202 8203 fmt = None 8204 to = self._parse_types(with_collation=True) 8205 8206 default = None 8207 if self._match(TokenType.DEFAULT): 8208 default = self._parse_bitwise() 8209 self._match_text_seq("ON", "CONVERSION", "ERROR") 8210 8211 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8212 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8213 fmt = self._parse_at_time_zone(fmt_string) 8214 8215 if not to: 8216 to = exp.DType.UNKNOWN.into_expr() 8217 if to.this in exp.DataType.TEMPORAL_TYPES: 8218 this = self.expression( 8219 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8220 this=this, 8221 format=exp.Literal.string( 8222 format_time( 8223 fmt_string.this if fmt_string else "", 8224 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8225 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8226 ) 8227 ), 8228 safe=safe, 8229 ) 8230 ) 8231 8232 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8233 this.set("zone", fmt.args["zone"]) 8234 return this 8235 elif not to: 8236 self.raise_error("Expected TYPE after CAST") 8237 elif isinstance(to, exp.Identifier): 8238 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8239 elif to.this == exp.DType.CHAR and ( 8240 self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET") 8241 ): 8242 to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string()) 8243 8244 return self.build_cast( 8245 strict=strict, 8246 this=this, 8247 to=to, 8248 format=fmt, 8249 safe=safe, 8250 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8251 default=default, 8252 ) 8253 8254 def _parse_string_agg(self) -> exp.GroupConcat: 8255 if self._match(TokenType.DISTINCT): 8256 args: list[exp.Expr | None] = [ 8257 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8258 ] 8259 if self._match(TokenType.COMMA): 8260 args.extend(self._parse_csv(self._parse_disjunction)) 8261 else: 8262 args = self._parse_csv(self._parse_disjunction) # type: ignore 8263 8264 if self._match_text_seq("ON", "OVERFLOW"): 8265 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8266 if self._match_text_seq("ERROR"): 8267 on_overflow: exp.Expr | None = exp.var("ERROR") 8268 else: 8269 self._match_text_seq("TRUNCATE") 8270 on_overflow = self.expression( 8271 exp.OverflowTruncateBehavior( 8272 this=self._parse_string(), 8273 with_count=( 8274 self._match_text_seq("WITH", "COUNT") 8275 or not self._match_text_seq("WITHOUT", "COUNT") 8276 ), 8277 ) 8278 ) 8279 else: 8280 on_overflow = None 8281 8282 index = self._index 8283 if not self._match(TokenType.R_PAREN) and args: 8284 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8285 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8286 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8287 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8288 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8289 8290 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8291 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8292 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8293 if not self._match_text_seq("WITHIN", "GROUP"): 8294 self._retreat(index) 8295 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8296 8297 # The corresponding match_r_paren will be called in parse_function (caller) 8298 self._match_l_paren() 8299 8300 return self.expression( 8301 exp.GroupConcat( 8302 this=self._parse_order(this=seq_get(args, 0)), 8303 separator=seq_get(args, 1), 8304 on_overflow=on_overflow, 8305 ) 8306 ) 8307 8308 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8309 this = self._parse_bitwise() 8310 8311 if self._match(TokenType.USING): 8312 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8313 elif self._match(TokenType.COMMA): 8314 to = self._parse_types() 8315 else: 8316 to = None 8317 8318 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8319 8320 def _parse_xml_element(self) -> exp.XMLElement: 8321 if self._match_text_seq("EVALNAME"): 8322 evalname = True 8323 this = self._parse_bitwise() 8324 else: 8325 evalname = None 8326 self._match_text_seq("NAME") 8327 this = self._parse_id_var() 8328 8329 return self.expression( 8330 exp.XMLElement( 8331 this=this, 8332 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8333 evalname=evalname, 8334 ) 8335 ) 8336 8337 def _parse_xml_table(self) -> exp.XMLTable: 8338 namespaces = None 8339 passing = None 8340 columns = None 8341 8342 if self._match_text_seq("XMLNAMESPACES", "("): 8343 namespaces = self._parse_xml_namespace() 8344 self._match_text_seq(")", ",") 8345 8346 this = self._parse_string() 8347 8348 if self._match_text_seq("PASSING"): 8349 # The BY VALUE keywords are optional and are provided for semantic clarity 8350 self._match_text_seq("BY", "VALUE") 8351 passing = self._parse_csv(self._parse_column) 8352 8353 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8354 8355 if self._match_text_seq("COLUMNS"): 8356 columns = self._parse_csv(self._parse_field_def) 8357 8358 return self.expression( 8359 exp.XMLTable( 8360 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8361 ) 8362 ) 8363 8364 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8365 namespaces = [] 8366 8367 while True: 8368 if self._match(TokenType.DEFAULT): 8369 uri = self._parse_string() 8370 else: 8371 uri = self._parse_alias(self._parse_string()) 8372 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8373 if not self._match(TokenType.COMMA): 8374 break 8375 8376 return namespaces 8377 8378 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8379 args = self._parse_csv(self._parse_disjunction) 8380 8381 if len(args) < 3: 8382 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8383 8384 return self.expression(exp.DecodeCase(expressions=args)) 8385 8386 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8387 self._match_text_seq("KEY") 8388 key = self._parse_column() 8389 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8390 self._match_text_seq("VALUE") 8391 value = self._parse_bitwise() 8392 8393 if not key and not value: 8394 return None 8395 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8396 8397 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8398 if not this or not self._match_text_seq("FORMAT", "JSON"): 8399 return this 8400 8401 return self.expression(exp.FormatJson(this=this)) 8402 8403 def _parse_on_condition(self) -> exp.OnCondition | None: 8404 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8405 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8406 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8407 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8408 else: 8409 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8410 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8411 8412 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8413 8414 if not empty and not error and not null: 8415 return None 8416 8417 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8418 8419 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8420 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8421 for value in values: 8422 if self._match_text_seq(value, "ON", on): 8423 return f"{value} ON {on}" 8424 8425 index = self._index 8426 if self._match(TokenType.DEFAULT): 8427 default_value = self._parse_bitwise() 8428 if self._match_text_seq("ON", on): 8429 return default_value 8430 8431 self._retreat(index) 8432 8433 return None 8434 8435 @t.overload 8436 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8437 8438 @t.overload 8439 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8440 8441 def _parse_json_object(self, agg=False): 8442 star = self._parse_star() 8443 expressions = ( 8444 [star] 8445 if star 8446 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8447 ) 8448 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8449 8450 unique_keys = None 8451 if self._match_text_seq("WITH", "UNIQUE"): 8452 unique_keys = True 8453 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8454 unique_keys = False 8455 8456 self._match_text_seq("KEYS") 8457 8458 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8459 self._parse_type() 8460 ) 8461 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8462 8463 return self.expression( 8464 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8465 expressions=expressions, 8466 null_handling=null_handling, 8467 unique_keys=unique_keys, 8468 return_type=return_type, 8469 encoding=encoding, 8470 ) 8471 ) 8472 8473 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8474 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8475 if not self._match_text_seq("NESTED"): 8476 this = self._parse_id_var() 8477 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8478 kind = self._parse_types(allow_identifiers=False) 8479 nested = None 8480 else: 8481 this = None 8482 ordinality = None 8483 kind = None 8484 nested = True 8485 8486 format_json = self._match_text_seq("FORMAT", "JSON") 8487 path = self._match_text_seq("PATH") and self._parse_string() 8488 nested_schema = nested and self._parse_json_schema() 8489 8490 return self.expression( 8491 exp.JSONColumnDef( 8492 this=this, 8493 kind=kind, 8494 path=path, 8495 nested_schema=nested_schema, 8496 ordinality=ordinality, 8497 format_json=format_json, 8498 ) 8499 ) 8500 8501 def _parse_json_schema(self) -> exp.JSONSchema: 8502 self._match_text_seq("COLUMNS") 8503 return self.expression( 8504 exp.JSONSchema( 8505 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8506 ) 8507 ) 8508 8509 def _parse_json_table(self) -> exp.JSONTable: 8510 this = self._parse_format_json(self._parse_bitwise()) 8511 path = self._match(TokenType.COMMA) and self._parse_string() 8512 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8513 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8514 schema = self._parse_json_schema() 8515 8516 return exp.JSONTable( 8517 this=this, 8518 schema=schema, 8519 path=path, 8520 error_handling=error_handling, 8521 empty_handling=empty_handling, 8522 ) 8523 8524 def _parse_match_against(self) -> exp.MatchAgainst: 8525 if self._match_text_seq("TABLE"): 8526 # parse SingleStore MATCH(TABLE ...) syntax 8527 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8528 expressions = [] 8529 table = self._parse_table() 8530 if table: 8531 expressions = [table] 8532 else: 8533 expressions = self._parse_csv(self._parse_column) 8534 8535 self._match_text_seq(")", "AGAINST", "(") 8536 8537 this = self._parse_string() 8538 8539 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8540 modifier = "IN NATURAL LANGUAGE MODE" 8541 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8542 modifier = f"{modifier} WITH QUERY EXPANSION" 8543 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8544 modifier = "IN BOOLEAN MODE" 8545 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8546 modifier = "WITH QUERY EXPANSION" 8547 else: 8548 modifier = None 8549 8550 return self.expression( 8551 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8552 ) 8553 8554 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8555 def _parse_open_json(self) -> exp.OpenJSON: 8556 this = self._parse_bitwise() 8557 path = self._match(TokenType.COMMA) and self._parse_string() 8558 8559 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8560 this = self._parse_field(any_token=True) 8561 kind = self._parse_types() 8562 path = self._parse_string() 8563 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8564 8565 return self.expression( 8566 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8567 ) 8568 8569 expressions = None 8570 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8571 self._match_l_paren() 8572 expressions = self._parse_csv(_parse_open_json_column_def) 8573 8574 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8575 8576 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8577 args = self._parse_csv(self._parse_bitwise) 8578 8579 if self._match(TokenType.IN): 8580 return self.expression( 8581 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8582 ) 8583 8584 if haystack_first: 8585 haystack = seq_get(args, 0) 8586 needle = seq_get(args, 1) 8587 else: 8588 haystack = seq_get(args, 1) 8589 needle = seq_get(args, 0) 8590 8591 return self.expression( 8592 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8593 ) 8594 8595 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8596 args = self._parse_csv(self._parse_table) 8597 return exp.JoinHint(this=func_name.upper(), expressions=args) 8598 8599 def _parse_substring(self) -> exp.Substring: 8600 # Postgres supports the form: substring(string [from int] [for int]) 8601 # (despite being undocumented, the reverse order also works) 8602 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8603 8604 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8605 8606 start, length = None, None 8607 8608 while self._curr: 8609 if self._match(TokenType.FROM): 8610 start = self._parse_bitwise() 8611 elif self._match(TokenType.FOR): 8612 if not start: 8613 start = exp.Literal.number(1) 8614 length = self._parse_bitwise() 8615 else: 8616 break 8617 8618 if start: 8619 args.append(start) 8620 if length: 8621 args.append(length) 8622 8623 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8624 8625 def _parse_trim(self) -> exp.Trim: 8626 # https://www.w3resource.com/sql/character-functions/trim.php 8627 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8628 8629 position = None 8630 collation = None 8631 expression = None 8632 8633 if self._match_texts(self.TRIM_TYPES): 8634 position = self._prev.text.upper() 8635 8636 this = self._parse_bitwise() 8637 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8638 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8639 expression = self._parse_bitwise() 8640 8641 if invert_order: 8642 this, expression = expression, this 8643 8644 if self._match(TokenType.COLLATE): 8645 collation = self._parse_bitwise() 8646 8647 return self.expression( 8648 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8649 ) 8650 8651 def _parse_window_clause(self) -> list[exp.Expr] | None: 8652 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8653 8654 def _parse_named_window(self) -> exp.Expr | None: 8655 return self._parse_window(self._parse_id_var(), alias=True) 8656 8657 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8658 if self._curr.token_type == TokenType.VAR: 8659 if self._match_text_seq("IGNORE", "NULLS"): 8660 return self.expression(exp.IgnoreNulls(this=this)) 8661 if self._match_text_seq("RESPECT", "NULLS"): 8662 return self.expression(exp.RespectNulls(this=this)) 8663 return this 8664 8665 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8666 if self._match(TokenType.HAVING): 8667 self._match_texts(("MAX", "MIN")) 8668 max = self._prev.text.upper() != "MIN" 8669 return self.expression( 8670 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8671 ) 8672 8673 return this 8674 8675 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8676 func = this 8677 comments = func.comments if isinstance(func, exp.Expr) else None 8678 8679 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8680 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8681 if self._match_text_seq("FROM", "FIRST"): 8682 this.set("from_first", True) 8683 elif self._match_text_seq("FROM", "LAST"): 8684 this.set("from_first", False) 8685 8686 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8687 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8688 if self._match_text_seq("WITHIN", "GROUP"): 8689 order = self._parse_wrapped(self._parse_order) 8690 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8691 8692 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8693 self._match(TokenType.WHERE) 8694 this = self.expression( 8695 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8696 ) 8697 self._match_r_paren() 8698 8699 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8700 # Some dialects choose to implement and some do not. 8701 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8702 8703 # There is some code above in _parse_lambda that handles 8704 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8705 8706 # The below changes handle 8707 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8708 8709 # Oracle allows both formats 8710 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8711 # and Snowflake chose to do the same for familiarity 8712 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8713 if isinstance(this, exp.AggFunc): 8714 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8715 8716 if ignore_respect and ignore_respect is not this: 8717 ignore_respect.replace(ignore_respect.this) 8718 this = self.expression(ignore_respect.__class__(this=this)) 8719 8720 this = self._parse_respect_or_ignore_nulls(this) 8721 8722 # bigquery select from window x AS (partition by ...) 8723 if alias: 8724 over = None 8725 self._match(TokenType.ALIAS) 8726 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8727 return this 8728 else: 8729 over = self._prev.text.upper() 8730 8731 if comments and isinstance(func, exp.Expr): 8732 func.pop_comments() 8733 8734 if not self._match(TokenType.L_PAREN): 8735 return self.expression( 8736 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8737 ) 8738 8739 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8740 8741 first: bool | None = True if self._match(TokenType.FIRST) else None 8742 if self._match_text_seq("LAST"): 8743 first = False 8744 8745 partition, order = self._parse_partition_and_order() 8746 kind = ( 8747 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8748 ) and self._prev.text 8749 8750 if kind: 8751 self._match(TokenType.BETWEEN) 8752 start = self._parse_window_spec() 8753 8754 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8755 exclude = ( 8756 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8757 if self._match_text_seq("EXCLUDE") 8758 else None 8759 ) 8760 8761 spec = self.expression( 8762 exp.WindowSpec( 8763 kind=kind, 8764 start=start["value"], 8765 start_side=start["side"], 8766 end=end.get("value"), 8767 end_side=end.get("side"), 8768 exclude=exclude, 8769 ) 8770 ) 8771 else: 8772 spec = None 8773 8774 self._match_r_paren() 8775 8776 window = self.expression( 8777 exp.Window( 8778 this=this, 8779 partition_by=partition, 8780 order=order, 8781 spec=spec, 8782 alias=window_alias, 8783 over=over, 8784 first=first, 8785 ), 8786 comments=comments, 8787 ) 8788 8789 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 8790 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 8791 return self._parse_window(window, alias=alias) 8792 8793 return window 8794 8795 def _parse_partition_and_order( 8796 self, 8797 ) -> tuple[list[exp.Expr], exp.Expr | None]: 8798 return self._parse_partition_by(), self._parse_order() 8799 8800 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 8801 self._match(TokenType.BETWEEN) 8802 8803 return { 8804 "value": ( 8805 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 8806 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 8807 or self._parse_bitwise() 8808 ), 8809 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 8810 } 8811 8812 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 8813 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 8814 # so this section tries to parse the clause version and if it fails, it treats the token 8815 # as an identifier (alias) 8816 if self._can_parse_limit_or_offset(): 8817 return this 8818 8819 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 8820 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 8821 if self._can_parse_named_window(): 8822 return this 8823 8824 any_token = self._match(TokenType.ALIAS) 8825 comments = self._prev_comments 8826 8827 if explicit and not any_token: 8828 return this 8829 8830 if self._match(TokenType.L_PAREN): 8831 aliases = self.expression( 8832 exp.Aliases( 8833 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 8834 ), 8835 comments=comments, 8836 ) 8837 self._match_r_paren(aliases) 8838 return aliases 8839 8840 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 8841 self.STRING_ALIASES and self._parse_string_as_identifier() 8842 ) 8843 8844 if alias: 8845 comments.extend(alias.pop_comments()) 8846 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 8847 column = this.this 8848 8849 # Moves the comment next to the alias in `expr /* comment */ AS alias` 8850 if not this.comments and column and column.comments: 8851 this.comments = column.pop_comments() 8852 8853 return this 8854 8855 def _parse_id_var( 8856 self, 8857 any_token: bool = True, 8858 tokens: t.Collection[TokenType] | None = None, 8859 ) -> exp.Expr | None: 8860 expression = self._parse_identifier() 8861 if not expression and ( 8862 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 8863 ): 8864 quoted = self._prev.token_type == TokenType.STRING 8865 expression = self._identifier_expression(quoted=quoted) 8866 8867 return expression 8868 8869 def _parse_string(self) -> exp.Expr | None: 8870 if self._match_set(self.STRING_PARSERS): 8871 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 8872 return self._parse_placeholder() 8873 8874 def _parse_string_as_identifier(self) -> exp.Identifier | None: 8875 if not self._match(TokenType.STRING): 8876 return None 8877 output = exp.to_identifier(self._prev.text, quoted=True) 8878 output.update_positions(self._prev) 8879 return output 8880 8881 def _parse_number(self) -> exp.Expr | None: 8882 if self._match_set(self.NUMERIC_PARSERS): 8883 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 8884 return self._parse_placeholder() 8885 8886 def _parse_identifier(self) -> exp.Expr | None: 8887 if self._match(TokenType.IDENTIFIER): 8888 return self._identifier_expression(quoted=True) 8889 return self._parse_placeholder() 8890 8891 def _parse_var( 8892 self, 8893 any_token: bool = False, 8894 tokens: t.Collection[TokenType] | None = None, 8895 upper: bool = False, 8896 ) -> exp.Expr | None: 8897 if ( 8898 (any_token and self._advance_any()) 8899 or self._match(TokenType.VAR) 8900 or (self._match_set(tokens) if tokens else False) 8901 ): 8902 return self.expression( 8903 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 8904 ) 8905 return self._parse_placeholder() 8906 8907 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 8908 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 8909 self._advance() 8910 return self._prev 8911 return None 8912 8913 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 8914 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 8915 8916 def _parse_primary_or_var(self) -> exp.Expr | None: 8917 return self._parse_primary() or self._parse_var(any_token=True) 8918 8919 def _parse_null(self) -> exp.Expr | None: 8920 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 8921 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 8922 return self._parse_placeholder() 8923 8924 def _parse_boolean(self) -> exp.Expr | None: 8925 if self._match(TokenType.TRUE): 8926 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 8927 if self._match(TokenType.FALSE): 8928 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 8929 return self._parse_placeholder() 8930 8931 def _parse_star(self) -> exp.Expr | None: 8932 if self._match(TokenType.STAR): 8933 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 8934 return self._parse_placeholder() 8935 8936 def _parse_parameter(self) -> exp.Parameter: 8937 this = self._parse_identifier() or self._parse_primary_or_var() 8938 return self.expression(exp.Parameter(this=this)) 8939 8940 def _parse_placeholder(self) -> exp.Expr | None: 8941 if self._match_set(self.PLACEHOLDER_PARSERS): 8942 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 8943 if placeholder: 8944 return placeholder 8945 self._advance(-1) 8946 return None 8947 8948 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 8949 if not self._match_texts(keywords): 8950 return None 8951 if self._match(TokenType.L_PAREN, advance=False): 8952 return self._parse_wrapped_csv(self._parse_expression) 8953 8954 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 8955 return [expression] if expression else None 8956 8957 def _parse_csv( 8958 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 8959 ) -> list[T]: 8960 parse_result = parse_method() 8961 items = [parse_result] if parse_result is not None else [] 8962 8963 while self._match(sep): 8964 if isinstance(parse_result, exp.Expr): 8965 self._add_comments(parse_result) 8966 parse_result = parse_method() 8967 if parse_result is not None: 8968 items.append(parse_result) 8969 8970 return items 8971 8972 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 8973 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 8974 8975 def _parse_wrapped_csv( 8976 self, 8977 parse_method: t.Callable[[], T | None], 8978 sep: TokenType = TokenType.COMMA, 8979 optional: bool = False, 8980 ) -> list[T]: 8981 return self._parse_wrapped( 8982 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 8983 ) 8984 8985 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 8986 wrapped = self._match(TokenType.L_PAREN) 8987 if not wrapped and not optional: 8988 self.raise_error("Expecting (") 8989 parse_result = parse_method() 8990 if wrapped: 8991 self._match_r_paren() 8992 return parse_result 8993 8994 def _parse_expressions(self) -> list[exp.Expr]: 8995 return self._parse_csv(self._parse_expression) 8996 8997 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 8998 return ( 8999 self._parse_set_operations( 9000 self._parse_alias(self._parse_assignment(), explicit=True) 9001 if alias 9002 else self._parse_assignment() 9003 ) 9004 or self._parse_select() 9005 ) 9006 9007 def _parse_ddl_select(self) -> exp.Expr | None: 9008 return self._parse_query_modifiers( 9009 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 9010 ) 9011 9012 def _parse_transaction(self) -> exp.Transaction | exp.Command: 9013 this = None 9014 if self._match_texts(self.TRANSACTION_KIND): 9015 this = self._prev.text 9016 9017 self._match_texts(("TRANSACTION", "WORK")) 9018 9019 modes = [] 9020 while True: 9021 mode = [] 9022 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 9023 mode.append(self._prev.text) 9024 9025 if mode: 9026 modes.append(" ".join(mode)) 9027 if not self._match(TokenType.COMMA): 9028 break 9029 9030 return self.expression(exp.Transaction(this=this, modes=modes)) 9031 9032 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 9033 chain = None 9034 savepoint = None 9035 is_rollback = self._prev.token_type == TokenType.ROLLBACK 9036 9037 self._match_texts(("TRANSACTION", "WORK")) 9038 9039 if self._match_text_seq("TO"): 9040 self._match_text_seq("SAVEPOINT") 9041 savepoint = self._parse_id_var() 9042 9043 if self._match(TokenType.AND): 9044 chain = not self._match_text_seq("NO") 9045 self._match_text_seq("CHAIN") 9046 9047 if is_rollback: 9048 return self.expression(exp.Rollback(savepoint=savepoint)) 9049 9050 return self.expression(exp.Commit(chain=chain)) 9051 9052 def _parse_refresh(self) -> exp.Refresh | exp.Command: 9053 if self._match_text_seq("EXTERNAL", "TABLE"): 9054 kind = "EXTERNAL TABLE" 9055 elif self._match(TokenType.TABLE): 9056 kind = "TABLE" 9057 elif self._match_text_seq("MATERIALIZED", "VIEW"): 9058 kind = "MATERIALIZED VIEW" 9059 else: 9060 kind = "" 9061 9062 this = self._parse_string() or self._parse_table() 9063 if not kind and not isinstance(this, exp.Literal): 9064 return self._parse_as_command(self._prev) 9065 9066 return self.expression(exp.Refresh(this=this, kind=kind)) 9067 9068 def _parse_column_def_with_exists(self): 9069 start = self._index 9070 self._match(TokenType.COLUMN) 9071 9072 exists_column = self._parse_exists(not_=True) 9073 expression = self._parse_field_def() 9074 9075 if not isinstance(expression, exp.ColumnDef): 9076 self._retreat(start) 9077 return None 9078 9079 expression.set("exists", exists_column) 9080 9081 return expression 9082 9083 def _parse_add_column(self) -> exp.ColumnDef | None: 9084 if not self._prev.text.upper() == "ADD": 9085 return None 9086 9087 return self._parse_column_def_with_exists() 9088 9089 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9090 drop = self._parse_drop() if self._match(TokenType.DROP) else None 9091 if drop and not isinstance(drop, exp.Command): 9092 drop.set("kind", drop.args.get("kind", "COLUMN")) 9093 return drop 9094 9095 def _parse_alter_drop_action(self) -> exp.Expr | None: 9096 return self._parse_drop_column() 9097 9098 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9099 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9100 return self.expression( 9101 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9102 ) 9103 9104 def _parse_alter_table_add(self) -> list[exp.Expr]: 9105 def _parse_add_alteration() -> exp.Expr | None: 9106 self._match_text_seq("ADD") 9107 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False): 9108 return self.expression( 9109 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9110 ) 9111 9112 column_def = self._parse_add_column() 9113 if isinstance(column_def, exp.ColumnDef): 9114 return column_def 9115 9116 exists = self._parse_exists(not_=True) 9117 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9118 return self.expression( 9119 exp.AddPartition( 9120 exists=exists, 9121 this=self._parse_field(any_token=True), 9122 location=self._match_text_seq("LOCATION", advance=False) 9123 and self._parse_property(), 9124 ) 9125 ) 9126 9127 return None 9128 9129 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9130 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9131 or self._match_text_seq("COLUMNS") 9132 ): 9133 schema = self._parse_schema() 9134 9135 return ( 9136 ensure_list(schema) 9137 if schema 9138 else self._parse_csv(self._parse_column_def_with_exists) 9139 ) 9140 9141 return self._parse_csv(_parse_add_alteration) 9142 9143 def _parse_alter_table_alter(self) -> exp.Expr | None: 9144 if self._match_texts(self.ALTER_ALTER_PARSERS): 9145 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9146 9147 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9148 # keyword after ALTER we default to parsing this statement 9149 self._match(TokenType.COLUMN) 9150 exists = self._parse_exists() 9151 column = self._parse_field(any_token=True) 9152 9153 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9154 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9155 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9156 return self.expression( 9157 exp.AlterColumn( 9158 this=column, default=self._parse_disjunction(), exists=exists or None 9159 ) 9160 ) 9161 if self._match(TokenType.COMMENT): 9162 return self.expression( 9163 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9164 ) 9165 if self._match_text_seq("DROP", "NOT", "NULL"): 9166 return self.expression( 9167 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9168 ) 9169 if self._match_text_seq("SET", "NOT", "NULL"): 9170 return self.expression( 9171 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9172 ) 9173 9174 if self._match_text_seq("SET", "VISIBLE"): 9175 return self.expression( 9176 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9177 ) 9178 if self._match_text_seq("SET", "INVISIBLE"): 9179 return self.expression( 9180 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9181 ) 9182 9183 self._match_text_seq("SET", "DATA") 9184 self._match_text_seq("TYPE") 9185 return self.expression( 9186 exp.AlterColumn( 9187 this=column, 9188 dtype=self._parse_types(), 9189 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9190 using=self._match(TokenType.USING) and self._parse_disjunction(), 9191 exists=exists or None, 9192 ) 9193 ) 9194 9195 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9196 if self._match_texts(("ALL", "EVEN", "AUTO")): 9197 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9198 9199 self._match_text_seq("KEY", "DISTKEY") 9200 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9201 9202 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9203 if compound: 9204 self._match_text_seq("SORTKEY") 9205 9206 if self._match(TokenType.L_PAREN, advance=False): 9207 return self.expression( 9208 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9209 ) 9210 9211 self._match_texts(("AUTO", "NONE")) 9212 return self.expression( 9213 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9214 ) 9215 9216 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9217 index = self._index - 1 9218 9219 partition_exists = self._parse_exists() 9220 if self._match(TokenType.PARTITION, advance=False): 9221 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9222 9223 self._retreat(index) 9224 return self._parse_csv(self._parse_alter_drop_action) 9225 9226 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9227 if self._match(TokenType.COLUMN) or ( 9228 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9229 ): 9230 exists = self._parse_exists() 9231 old_column = self._parse_column() 9232 to = self._match_text_seq("TO") 9233 new_column = self._parse_column() 9234 9235 if old_column is None or not to or new_column is None: 9236 return None 9237 9238 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9239 9240 self._match_text_seq("TO") 9241 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9242 9243 def _parse_alter_table_set(self) -> exp.AlterSet: 9244 alter_set = self.expression(exp.AlterSet()) 9245 9246 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9247 "TABLE", "PROPERTIES" 9248 ): 9249 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9250 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9251 alter_set.set("expressions", [self._parse_assignment()]) 9252 elif self._match_texts(("LOGGED", "UNLOGGED")): 9253 alter_set.set("option", exp.var(self._prev.text.upper())) 9254 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9255 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9256 elif self._match_text_seq("LOCATION"): 9257 alter_set.set("location", self._parse_field()) 9258 elif self._match_text_seq("ACCESS", "METHOD"): 9259 alter_set.set("access_method", self._parse_field()) 9260 elif self._match_text_seq("TABLESPACE"): 9261 alter_set.set("tablespace", self._parse_field()) 9262 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9263 alter_set.set("file_format", [self._parse_field()]) 9264 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9265 alter_set.set("file_format", self._parse_wrapped_options()) 9266 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9267 alter_set.set("copy_options", self._parse_wrapped_options()) 9268 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9269 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9270 else: 9271 if self._match_text_seq("SERDE"): 9272 alter_set.set("serde", self._parse_field()) 9273 9274 properties = self._parse_wrapped(self._parse_properties, optional=True) 9275 alter_set.set("expressions", [properties]) 9276 9277 return alter_set 9278 9279 def _parse_alter_session(self) -> exp.AlterSession: 9280 """Parse ALTER SESSION SET/UNSET statements.""" 9281 if self._match(TokenType.SET): 9282 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9283 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9284 9285 self._match_text_seq("UNSET") 9286 expressions = self._parse_csv( 9287 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9288 ) 9289 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9290 9291 def _parse_alter(self) -> exp.Alter | exp.Command: 9292 start = self._prev 9293 9294 iceberg = self._match_text_seq("ICEBERG") 9295 9296 alter_token = self._match_set(self.ALTERABLES) and self._prev 9297 if not alter_token: 9298 return self._parse_as_command(start) 9299 if iceberg and alter_token.token_type != TokenType.TABLE: 9300 return self._parse_as_command(start) 9301 9302 exists = self._parse_exists() 9303 only = self._match_text_seq("ONLY") 9304 9305 if alter_token.token_type == TokenType.SESSION: 9306 this = None 9307 check = None 9308 cluster = None 9309 else: 9310 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9311 check = self._match_text_seq("WITH", "CHECK") 9312 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9313 9314 if self._next: 9315 self._advance() 9316 9317 parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None 9318 if parser: 9319 actions = ensure_list(parser(self)) 9320 not_valid = self._match_text_seq("NOT", "VALID") 9321 options = self._parse_csv(self._parse_property) 9322 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9323 9324 if not self._curr and actions: 9325 return self.expression( 9326 exp.Alter( 9327 this=this, 9328 kind=alter_token.text.upper(), 9329 exists=exists, 9330 actions=actions, 9331 only=only, 9332 options=options, 9333 cluster=cluster, 9334 not_valid=not_valid, 9335 check=check, 9336 cascade=cascade, 9337 iceberg=iceberg, 9338 ) 9339 ) 9340 9341 return self._parse_as_command(start) 9342 9343 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9344 start = self._prev 9345 # https://duckdb.org/docs/sql/statements/analyze 9346 if not self._curr: 9347 return self.expression(exp.Analyze()) 9348 9349 options = [] 9350 while self._match_texts(self.ANALYZE_STYLES): 9351 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9352 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9353 else: 9354 options.append(self._prev.text.upper()) 9355 9356 tables: exp.Expr | list[exp.Expr] | None = None 9357 inner_expression: exp.Expr | None = None 9358 9359 kind = self._curr.text.upper() if self._curr else None 9360 9361 if self._match(TokenType.TABLE): 9362 tables = self._parse_csv(self._parse_table_parts) 9363 elif self._match(TokenType.INDEX): 9364 tables = self._parse_table_parts() 9365 elif self._match_text_seq("TABLES"): 9366 if self._match_set((TokenType.FROM, TokenType.IN)): 9367 kind = f"{kind} {self._prev.text.upper()}" 9368 tables = self._parse_table(schema=True, is_db_reference=True) 9369 elif self._match_text_seq("DATABASE"): 9370 tables = self._parse_table(schema=True, is_db_reference=True) 9371 elif self._match_text_seq("CLUSTER"): 9372 tables = self._parse_table() 9373 # Try matching inner expr keywords before fallback to parse table. 9374 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9375 kind = None 9376 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9377 else: 9378 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9379 kind = None 9380 tables = self._parse_csv(self._parse_table_parts) 9381 9382 partition = self._try_parse(self._parse_partition) 9383 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9384 return self._parse_as_command(start) 9385 9386 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9387 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9388 "WITH", "ASYNC", "MODE" 9389 ): 9390 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9391 else: 9392 mode = None 9393 9394 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9395 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9396 9397 properties = self._parse_properties() 9398 return self.expression( 9399 exp.Analyze( 9400 kind=kind, 9401 tables=ensure_list(tables), 9402 mode=mode, 9403 partition=partition, 9404 properties=properties, 9405 expression=inner_expression, 9406 options=options, 9407 ) 9408 ) 9409 9410 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9411 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9412 this = None 9413 kind = self._prev.text.upper() 9414 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9415 expressions = [] 9416 9417 if not self._match_text_seq("STATISTICS"): 9418 self.raise_error("Expecting token STATISTICS") 9419 9420 if self._match_text_seq("NOSCAN"): 9421 this = "NOSCAN" 9422 elif self._match(TokenType.FOR): 9423 if self._match_text_seq("ALL", "COLUMNS"): 9424 this = "FOR ALL COLUMNS" 9425 if self._match_text_seq("COLUMNS"): 9426 this = "FOR COLUMNS" 9427 expressions = self._parse_csv(self._parse_column_reference) 9428 elif self._match_text_seq("SAMPLE"): 9429 sample = self._parse_number() 9430 expressions = [ 9431 self.expression( 9432 exp.AnalyzeSample( 9433 sample=sample, 9434 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9435 ) 9436 ) 9437 ] 9438 9439 return self.expression( 9440 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9441 ) 9442 9443 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9444 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9445 kind = None 9446 this = None 9447 expression: exp.Expr | None = None 9448 if self._match_text_seq("REF", "UPDATE"): 9449 kind = "REF" 9450 this = "UPDATE" 9451 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9452 this = "UPDATE SET DANGLING TO NULL" 9453 elif self._match_text_seq("STRUCTURE"): 9454 kind = "STRUCTURE" 9455 if self._match_text_seq("CASCADE", "FAST"): 9456 this = "CASCADE FAST" 9457 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9458 ("ONLINE", "OFFLINE") 9459 ): 9460 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9461 expression = self._parse_into() 9462 9463 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9464 9465 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9466 this = self._prev.text.upper() 9467 if self._match_text_seq("COLUMNS"): 9468 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9469 return None 9470 9471 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9472 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9473 if self._match_text_seq("STATISTICS"): 9474 return self.expression(exp.AnalyzeDelete(kind=kind)) 9475 return None 9476 9477 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9478 if self._match_text_seq("CHAINED", "ROWS"): 9479 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9480 return None 9481 9482 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9483 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9484 this = self._prev.text.upper() 9485 expression: exp.Expr | None = None 9486 expressions = [] 9487 update_options = None 9488 9489 if self._match_text_seq("HISTOGRAM", "ON"): 9490 expressions = self._parse_csv(self._parse_column_reference) 9491 with_expressions = [] 9492 while self._match(TokenType.WITH): 9493 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9494 if self._match_texts(("SYNC", "ASYNC")): 9495 if self._match_text_seq("MODE", advance=False): 9496 with_expressions.append(f"{self._prev.text.upper()} MODE") 9497 self._advance() 9498 else: 9499 buckets = self._parse_number() 9500 if self._match_text_seq("BUCKETS"): 9501 with_expressions.append(f"{buckets} BUCKETS") 9502 if with_expressions: 9503 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9504 9505 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9506 TokenType.UPDATE, advance=False 9507 ): 9508 update_options = self._prev.text.upper() 9509 self._advance() 9510 elif self._match_text_seq("USING", "DATA"): 9511 expression = self.expression(exp.UsingData(this=self._parse_string())) 9512 9513 return self.expression( 9514 exp.AnalyzeHistogram( 9515 this=this, 9516 expressions=expressions, 9517 expression=expression, 9518 update_options=update_options, 9519 ) 9520 ) 9521 9522 def _parse_merge(self) -> exp.Merge: 9523 self._match(TokenType.INTO) 9524 target = self._parse_table() 9525 9526 if target and self._match(TokenType.ALIAS, advance=False): 9527 target.set("alias", self._parse_table_alias()) 9528 9529 self._match(TokenType.USING) 9530 using = self._parse_table() 9531 9532 return self.expression( 9533 exp.Merge( 9534 this=target, 9535 using=using, 9536 on=self._match(TokenType.ON) and self._parse_disjunction(), 9537 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9538 whens=self._parse_when_matched(), 9539 returning=self._parse_returning(), 9540 ) 9541 ) 9542 9543 def _parse_when_matched(self) -> exp.Whens: 9544 whens = [] 9545 9546 while self._match(TokenType.WHEN): 9547 matched = not self._match(TokenType.NOT) 9548 self._match_text_seq("MATCHED") 9549 source = ( 9550 False 9551 if self._match_text_seq("BY", "TARGET") 9552 else self._match_text_seq("BY", "SOURCE") 9553 ) 9554 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9555 9556 self._match(TokenType.THEN) 9557 9558 if self._match(TokenType.INSERT): 9559 this = self._parse_star() 9560 if this: 9561 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9562 else: 9563 then = self.expression( 9564 exp.Insert( 9565 this=exp.var("ROW") 9566 if self._match_text_seq("ROW") 9567 else self._parse_value(values=False), 9568 expression=self._match_text_seq("VALUES") and self._parse_value(), 9569 where=self._parse_where(), 9570 ) 9571 ) 9572 elif self._match(TokenType.UPDATE): 9573 expressions = self._parse_star() 9574 if expressions: 9575 then = self.expression(exp.Update(expressions=expressions)) 9576 else: 9577 then = self.expression( 9578 exp.Update( 9579 expressions=self._match(TokenType.SET) 9580 and self._parse_csv(self._parse_equality), 9581 where=self._parse_where(), 9582 ) 9583 ) 9584 elif self._match(TokenType.DELETE): 9585 then = self.expression(exp.Var(this=self._prev.text)) 9586 else: 9587 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9588 9589 whens.append( 9590 self.expression( 9591 exp.When(matched=matched, source=source, condition=condition, then=then) 9592 ) 9593 ) 9594 return self.expression(exp.Whens(expressions=whens)) 9595 9596 def _parse_show(self) -> exp.Expr | None: 9597 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9598 if parser: 9599 return parser(self) 9600 return self._parse_as_command(self._prev) 9601 9602 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9603 index = self._index 9604 9605 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9606 return self._parse_set_transaction(global_=kind == "GLOBAL") 9607 9608 left = self._parse_primary() or self._parse_column() 9609 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 9610 9611 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 9612 self._retreat(index) 9613 return None 9614 9615 right = self._parse_statement() or self._parse_id_var() 9616 if isinstance(right, (exp.Column, exp.Identifier)): 9617 right = exp.var(right.name) 9618 9619 this = self.expression(exp.EQ(this=left, expression=right)) 9620 return self.expression(exp.SetItem(this=this, kind=kind)) 9621 9622 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 9623 self._match_text_seq("TRANSACTION") 9624 characteristics = self._parse_csv( 9625 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 9626 ) 9627 return self.expression( 9628 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 9629 ) 9630 9631 def _parse_set_item(self) -> exp.Expr | None: 9632 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 9633 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 9634 9635 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 9636 index = self._index 9637 set_ = self.expression( 9638 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 9639 ) 9640 9641 if self._curr: 9642 self._retreat(index) 9643 return self._parse_as_command(self._prev) 9644 9645 return set_ 9646 9647 def _parse_var_from_options( 9648 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 9649 ) -> exp.Var | None: 9650 start = self._curr 9651 if not start: 9652 return None 9653 9654 option = start.text.upper() 9655 continuations = ( 9656 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 9657 ) 9658 9659 index = self._index 9660 self._advance() 9661 for keywords in continuations or []: 9662 if isinstance(keywords, str): 9663 keywords = (keywords,) 9664 9665 if self._match_text_seq(*keywords): 9666 option = f"{option} {' '.join(keywords)}" 9667 break 9668 else: 9669 if continuations or continuations is None: 9670 if raise_unmatched: 9671 self.raise_error(f"Unknown option {option}") 9672 9673 self._retreat(index) 9674 return None 9675 9676 return exp.var(option) 9677 9678 def _parse_as_command(self, start: Token) -> exp.Command: 9679 while self._curr: 9680 self._advance() 9681 text = self._find_sql(start, self._prev) 9682 size = len(start.text) 9683 self._warn_unsupported() 9684 return exp.Command(this=text[:size], expression=text[size:]) 9685 9686 def _parse_dict_property(self, this: str) -> exp.DictProperty: 9687 settings = [] 9688 9689 self._match_l_paren() 9690 kind = self._parse_id_var() 9691 9692 if self._match(TokenType.L_PAREN): 9693 while True: 9694 key = self._parse_id_var() 9695 value = self._parse_function() or self._parse_primary_or_var() 9696 if not key and value is None: 9697 break 9698 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 9699 self._match(TokenType.R_PAREN) 9700 9701 self._match_r_paren() 9702 9703 return self.expression( 9704 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 9705 ) 9706 9707 def _parse_dict_range(self, this: str) -> exp.DictRange: 9708 self._match_l_paren() 9709 has_min = self._match_text_seq("MIN") 9710 if has_min: 9711 min = self._parse_var() or self._parse_primary() 9712 self._match_text_seq("MAX") 9713 max = self._parse_var() or self._parse_primary() 9714 else: 9715 max = self._parse_var() or self._parse_primary() 9716 min = exp.Literal.number(0) 9717 self._match_r_paren() 9718 return self.expression(exp.DictRange(this=this, min=min, max=max)) 9719 9720 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 9721 index = self._index 9722 expression = self._parse_column() 9723 position = self._match(TokenType.COMMA) and self._parse_column() 9724 9725 if not self._match(TokenType.IN): 9726 self._retreat(index - 1) 9727 return None 9728 iterator = self._parse_column() 9729 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 9730 return self.expression( 9731 exp.Comprehension( 9732 this=this, 9733 expression=expression, 9734 position=position, 9735 iterator=iterator, 9736 condition=condition, 9737 ) 9738 ) 9739 9740 def _parse_heredoc(self) -> exp.Heredoc | None: 9741 if self._match(TokenType.HEREDOC_STRING): 9742 return self.expression(exp.Heredoc(this=self._prev.text)) 9743 9744 if not self._match_text_seq("$"): 9745 return None 9746 9747 tags = ["$"] 9748 tag_text = None 9749 9750 if self._is_connected(): 9751 self._advance() 9752 tags.append(self._prev.text.upper()) 9753 else: 9754 self.raise_error("No closing $ found") 9755 9756 if tags[-1] != "$": 9757 if self._is_connected() and self._match_text_seq("$"): 9758 tag_text = tags[-1] 9759 tags.append("$") 9760 else: 9761 self.raise_error("No closing $ found") 9762 9763 heredoc_start = self._curr 9764 9765 while self._curr: 9766 if self._match_text_seq(*tags, advance=False): 9767 this = self._find_sql(heredoc_start, self._prev) 9768 self._advance(len(tags)) 9769 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 9770 9771 self._advance() 9772 9773 self.raise_error(f"No closing {''.join(tags)} found") 9774 return None 9775 9776 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 9777 if not self._curr: 9778 return None 9779 9780 index = self._index 9781 this = [] 9782 while True: 9783 # The current token might be multiple words 9784 curr = self._curr.text.upper() 9785 key = curr.split(" ") 9786 this.append(curr) 9787 9788 self._advance() 9789 result, trie = in_trie(trie, key) 9790 if result == TrieResult.FAILED: 9791 break 9792 9793 if result == TrieResult.EXISTS: 9794 subparser = parsers[" ".join(this)] 9795 return subparser 9796 9797 self._retreat(index) 9798 return None 9799 9800 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 9801 if not self._match(TokenType.L_PAREN, expression=expression): 9802 self.raise_error("Expecting (") 9803 9804 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 9805 if not self._match(TokenType.R_PAREN, expression=expression): 9806 self.raise_error("Expecting )") 9807 9808 def _replace_lambda( 9809 self, node: exp.Expr | None, expressions: list[exp.Expr] 9810 ) -> exp.Expr | None: 9811 if not node: 9812 return node 9813 9814 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 9815 9816 for column in node.find_all(exp.Column): 9817 typ = lambda_types.get(column.parts[0].name) 9818 if typ is not None: 9819 dot_or_id = column.to_dot() if column.table else column.this 9820 9821 if typ: 9822 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ)) 9823 9824 parent = column.parent 9825 9826 while isinstance(parent, exp.Dot): 9827 if not isinstance(parent.parent, exp.Dot): 9828 parent.replace(dot_or_id) 9829 break 9830 parent = parent.parent 9831 else: 9832 if column is node: 9833 node = dot_or_id 9834 else: 9835 column.replace(dot_or_id) 9836 return node 9837 9838 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 9839 start = self._prev 9840 9841 # Not to be confused with TRUNCATE(number, decimals) function call 9842 if self._match(TokenType.L_PAREN): 9843 self._retreat(self._index - 2) 9844 return self._parse_function() 9845 9846 # Clickhouse supports TRUNCATE DATABASE as well 9847 is_database = self._match(TokenType.DATABASE) 9848 9849 self._match(TokenType.TABLE) 9850 9851 exists = self._parse_exists(not_=False) 9852 9853 expressions = self._parse_csv( 9854 lambda: self._parse_table(schema=True, is_db_reference=is_database) 9855 ) 9856 9857 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9858 9859 if self._match_text_seq("RESTART", "IDENTITY"): 9860 identity = "RESTART" 9861 elif self._match_text_seq("CONTINUE", "IDENTITY"): 9862 identity = "CONTINUE" 9863 else: 9864 identity = None 9865 9866 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 9867 option = self._prev.text 9868 else: 9869 option = None 9870 9871 partition = self._parse_partition() 9872 9873 # Fallback case 9874 if self._curr: 9875 return self._parse_as_command(start) 9876 9877 return self.expression( 9878 exp.TruncateTable( 9879 expressions=expressions, 9880 is_database=is_database, 9881 exists=exists, 9882 cluster=cluster, 9883 identity=identity, 9884 option=option, 9885 partition=partition, 9886 ) 9887 ) 9888 9889 def _parse_indexed_column(self) -> exp.Expr | None: 9890 return self._parse_ordered(self._parse_opclass) 9891 9892 def _parse_with_operator(self) -> exp.Expr | None: 9893 this = self._parse_indexed_column() 9894 9895 if not self._match(TokenType.WITH): 9896 return this 9897 9898 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 9899 9900 return self.expression(exp.WithOperator(this=this, op=op)) 9901 9902 def _parse_wrapped_options(self) -> list[exp.Expr]: 9903 self._match(TokenType.EQ) 9904 self._match(TokenType.L_PAREN) 9905 9906 opts: list[exp.Expr] = [] 9907 option: exp.Expr | list[exp.Expr] | None 9908 while self._curr and not self._match(TokenType.R_PAREN): 9909 if self._match_text_seq("FORMAT_NAME", "="): 9910 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 9911 option = self._parse_format_name() 9912 else: 9913 option = self._parse_property() 9914 9915 if option is None: 9916 self.raise_error("Unable to parse option") 9917 break 9918 9919 opts.extend(ensure_list(option)) 9920 9921 return opts 9922 9923 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 9924 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 9925 9926 options = [] 9927 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 9928 option = self._parse_var(any_token=True) 9929 prev = self._prev.text.upper() 9930 9931 # Different dialects might separate options and values by white space, "=" and "AS" 9932 self._match(TokenType.EQ) 9933 self._match(TokenType.ALIAS) 9934 9935 param = self.expression(exp.CopyParameter(this=option)) 9936 9937 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 9938 TokenType.L_PAREN, advance=False 9939 ): 9940 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 9941 param.set("expressions", self._parse_wrapped_options()) 9942 elif prev == "FILE_FORMAT": 9943 # T-SQL's external file format case 9944 param.set("expression", self._parse_field()) 9945 elif ( 9946 prev == "FORMAT" 9947 and self._prev.token_type == TokenType.ALIAS 9948 and self._match_texts(("AVRO", "JSON")) 9949 ): 9950 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 9951 param.set("expression", self._parse_field()) 9952 else: 9953 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 9954 9955 options.append(param) 9956 9957 if sep: 9958 self._match(sep) 9959 9960 return options 9961 9962 def _parse_credentials(self) -> exp.Credentials | None: 9963 expr = self.expression(exp.Credentials()) 9964 9965 if self._match_text_seq("STORAGE_INTEGRATION", "="): 9966 expr.set("storage", self._parse_field()) 9967 if self._match_text_seq("CREDENTIALS"): 9968 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 9969 creds = ( 9970 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 9971 ) 9972 expr.set("credentials", creds) 9973 if self._match_text_seq("ENCRYPTION"): 9974 expr.set("encryption", self._parse_wrapped_options()) 9975 if self._match_text_seq("IAM_ROLE"): 9976 expr.set( 9977 "iam_role", 9978 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 9979 ) 9980 if self._match_text_seq("REGION"): 9981 expr.set("region", self._parse_field()) 9982 9983 return expr 9984 9985 def _parse_file_location(self) -> exp.Expr | None: 9986 return self._parse_field() 9987 9988 def _parse_copy(self) -> exp.Copy | exp.Command: 9989 start = self._prev 9990 9991 self._match(TokenType.INTO) 9992 9993 this = ( 9994 self._parse_select(nested=True, parse_subquery_alias=False) 9995 if self._match(TokenType.L_PAREN, advance=False) 9996 else self._parse_table(schema=True) 9997 ) 9998 9999 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 10000 10001 files = self._parse_csv(self._parse_file_location) 10002 if self._match(TokenType.EQ, advance=False): 10003 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 10004 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 10005 # list via `_parse_wrapped(..)` below. 10006 self._advance(-1) 10007 files = [] 10008 10009 credentials = self._parse_credentials() 10010 10011 self._match_text_seq("WITH") 10012 10013 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 10014 10015 # Fallback case 10016 if self._curr: 10017 return self._parse_as_command(start) 10018 10019 return self.expression( 10020 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 10021 ) 10022 10023 def _parse_normalize(self) -> exp.Normalize: 10024 return self.expression( 10025 exp.Normalize( 10026 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 10027 ) 10028 ) 10029 10030 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 10031 args = self._parse_csv(lambda: self._parse_lambda()) 10032 10033 this = seq_get(args, 0) 10034 decimals = seq_get(args, 1) 10035 10036 return expr_type( 10037 this=this, 10038 decimals=decimals, 10039 to=self._parse_var() if self._match_text_seq("TO") else None, 10040 ) 10041 10042 def _parse_star_ops(self) -> exp.Expr | None: 10043 star_token = self._prev 10044 10045 if self._match_text_seq("COLUMNS", "(", advance=False): 10046 this = self._parse_function() 10047 if isinstance(this, exp.Columns): 10048 this.set("unpack", True) 10049 return this 10050 10051 index = self._index 10052 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 10053 if not ilike: 10054 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 10055 self._retreat(index) 10056 10057 return self.expression( 10058 exp.Star( 10059 ilike=ilike, 10060 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 10061 replace=self._parse_star_op("REPLACE"), 10062 rename=self._parse_star_op("RENAME"), 10063 ) 10064 ).update_positions(star_token) 10065 10066 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 10067 privilege_parts = [] 10068 10069 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 10070 # (end of privilege list) or L_PAREN (start of column list) are met 10071 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 10072 privilege_parts.append(self._curr.text.upper()) 10073 self._advance() 10074 10075 if not privilege_parts: 10076 self.raise_error("Expected privilege") 10077 return None 10078 10079 this = exp.var(" ".join(privilege_parts)) 10080 expressions = ( 10081 self._parse_wrapped_csv(self._parse_column) 10082 if self._match(TokenType.L_PAREN, advance=False) 10083 else None 10084 ) 10085 10086 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10087 10088 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10089 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10090 principal = self._parse_id_var() 10091 10092 if not principal: 10093 return None 10094 10095 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10096 10097 def _parse_grant_revoke_common( 10098 self, 10099 ) -> tuple[list | None, str | None, exp.Expr | None]: 10100 privileges = self._parse_csv(self._parse_grant_privilege) 10101 10102 self._match(TokenType.ON) 10103 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10104 10105 # Attempt to parse the securable e.g. MySQL allows names 10106 # such as "foo.*", "*.*" which are not easily parseable yet 10107 securable = self._try_parse(self._parse_table_parts) 10108 10109 return privileges, kind, securable 10110 10111 def _parse_grant(self) -> exp.Grant | exp.Command: 10112 start = self._prev 10113 10114 privileges, kind, securable = self._parse_grant_revoke_common() 10115 10116 if not securable or not self._match_text_seq("TO"): 10117 return self._parse_as_command(start) 10118 10119 principals = self._parse_csv(self._parse_grant_principal) 10120 10121 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10122 10123 if self._curr: 10124 return self._parse_as_command(start) 10125 10126 return self.expression( 10127 exp.Grant( 10128 privileges=privileges, 10129 kind=kind, 10130 securable=securable, 10131 principals=principals, 10132 grant_option=grant_option, 10133 ) 10134 ) 10135 10136 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10137 start = self._prev 10138 10139 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10140 10141 privileges, kind, securable = self._parse_grant_revoke_common() 10142 10143 if not securable or not self._match_text_seq("FROM"): 10144 return self._parse_as_command(start) 10145 10146 principals = self._parse_csv(self._parse_grant_principal) 10147 10148 cascade = None 10149 if self._match_texts(("CASCADE", "RESTRICT")): 10150 cascade = self._prev.text.upper() 10151 10152 if self._curr: 10153 return self._parse_as_command(start) 10154 10155 return self.expression( 10156 exp.Revoke( 10157 privileges=privileges, 10158 kind=kind, 10159 securable=securable, 10160 principals=principals, 10161 grant_option=grant_option, 10162 cascade=cascade, 10163 ) 10164 ) 10165 10166 def _parse_overlay(self) -> exp.Overlay: 10167 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10168 return ( 10169 self._parse_bitwise() 10170 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10171 else None 10172 ) 10173 10174 return self.expression( 10175 exp.Overlay( 10176 this=self._parse_bitwise(), 10177 expression=_parse_overlay_arg("PLACING"), 10178 from_=_parse_overlay_arg("FROM"), 10179 for_=_parse_overlay_arg("FOR"), 10180 ) 10181 ) 10182 10183 def _parse_format_name(self) -> exp.Property: 10184 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10185 # for FILE_FORMAT = <format_name> 10186 return self.expression( 10187 exp.Property( 10188 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10189 ) 10190 ) 10191 10192 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10193 is_distinct = self._match(TokenType.DISTINCT) 10194 if not is_distinct: 10195 self._match(TokenType.ALL) 10196 10197 args = [self._parse_lambda()] 10198 if self._match(TokenType.COMMA): 10199 args.extend(self._parse_function_args()) 10200 10201 target = seq_get(args, distinct_index) 10202 if is_distinct and target: 10203 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10204 10205 return func.from_arg_list(args) 10206 10207 def _identifier_expression( 10208 self, token: Token | None = None, quoted: bool | None = None 10209 ) -> exp.Identifier: 10210 token = token or self._prev 10211 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10212 10213 def _build_pipe_cte( 10214 self, 10215 query: exp.Query, 10216 expressions: list[exp.Expr], 10217 alias_cte: exp.TableAlias | None = None, 10218 ) -> exp.Select: 10219 new_cte: str | exp.TableAlias | None 10220 if alias_cte: 10221 new_cte = alias_cte 10222 else: 10223 self._pipe_cte_counter += 1 10224 new_cte = f"__tmp{self._pipe_cte_counter}" 10225 10226 with_ = query.args.get("with_") 10227 ctes = with_.pop() if with_ else None 10228 10229 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10230 if ctes: 10231 new_select.set("with_", ctes) 10232 10233 return new_select.with_(new_cte, as_=query, copy=False) 10234 10235 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10236 select = self._parse_select(consume_pipe=False) 10237 if not select: 10238 return query 10239 10240 return self._build_pipe_cte( 10241 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10242 ) 10243 10244 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10245 limit = self._parse_limit() 10246 offset = self._parse_offset() 10247 if limit: 10248 curr_limit = query.args.get("limit", limit) 10249 if curr_limit.expression.to_py() >= limit.expression.to_py(): 10250 query.limit(limit, copy=False) 10251 if offset: 10252 curr_offset = query.args.get("offset") 10253 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10254 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10255 10256 return query 10257 10258 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10259 this = self._parse_disjunction() 10260 if self._match_text_seq("GROUP", "AND", advance=False): 10261 return this 10262 10263 this = self._parse_alias(this) 10264 10265 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10266 return self._parse_ordered(lambda: this) 10267 10268 return this 10269 10270 def _parse_pipe_syntax_aggregate_group_order_by( 10271 self, query: exp.Select, group_by_exists: bool = True 10272 ) -> exp.Select: 10273 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10274 aggregates_or_groups, orders = [], [] 10275 for element in expr: 10276 if isinstance(element, exp.Ordered): 10277 this = element.this 10278 if isinstance(this, exp.Alias): 10279 element.set("this", this.args["alias"]) 10280 orders.append(element) 10281 else: 10282 this = element 10283 aggregates_or_groups.append(this) 10284 10285 if group_by_exists: 10286 query.select( 10287 *aggregates_or_groups, *query.expressions, append=False, copy=False 10288 ).group_by( 10289 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10290 copy=False, 10291 ) 10292 else: 10293 query.select(*aggregates_or_groups, append=False, copy=False) 10294 10295 if orders: 10296 return query.order_by(*orders, append=False, copy=False) 10297 10298 return query 10299 10300 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10301 self._match_text_seq("AGGREGATE") 10302 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10303 10304 if self._match(TokenType.GROUP_BY) or ( 10305 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10306 ): 10307 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10308 10309 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10310 10311 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10312 first_setop = self.parse_set_operation(this=query) 10313 if not first_setop: 10314 return None 10315 10316 def _parse_and_unwrap_query() -> exp.Expr | None: 10317 expr = self._parse_paren() 10318 return expr.assert_is(exp.Subquery).unnest() if expr else None 10319 10320 first_setop.this.pop() 10321 10322 setops = [ 10323 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10324 *self._parse_csv(_parse_and_unwrap_query), 10325 ] 10326 10327 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10328 with_ = query.args.get("with_") 10329 ctes = with_.pop() if with_ else None 10330 10331 if isinstance(first_setop, exp.Union): 10332 query = query.union(*setops, copy=False, **first_setop.args) 10333 elif isinstance(first_setop, exp.Except): 10334 query = query.except_(*setops, copy=False, **first_setop.args) 10335 else: 10336 query = query.intersect(*setops, copy=False, **first_setop.args) 10337 10338 query.set("with_", ctes) 10339 10340 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10341 10342 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10343 join = self._parse_join() 10344 if not join: 10345 return None 10346 10347 if isinstance(query, exp.Select): 10348 return query.join(join, copy=False) 10349 10350 return query 10351 10352 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10353 pivots = self._parse_pivots() 10354 if not pivots: 10355 return query 10356 10357 from_ = query.args.get("from_") 10358 if from_: 10359 from_.this.set("pivots", pivots) 10360 10361 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10362 10363 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10364 self._match_text_seq("EXTEND") 10365 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10366 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10367 10368 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10369 sample = self._parse_table_sample() 10370 10371 with_ = query.args.get("with_") 10372 if with_: 10373 with_.expressions[-1].this.set("sample", sample) 10374 else: 10375 query.set("sample", sample) 10376 10377 return query 10378 10379 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10380 if isinstance(query, exp.Subquery): 10381 query = exp.select("*").from_(query, copy=False) 10382 10383 if not query.args.get("from_"): 10384 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10385 10386 while self._match(TokenType.PIPE_GT): 10387 start_index = self._index 10388 start_text = self._curr.text.upper() 10389 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10390 if not parser: 10391 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10392 # keywords, making it tricky to disambiguate them without lookahead. The approach 10393 # here is to try and parse a set operation and if that fails, then try to parse a 10394 # join operator. If that fails as well, then the operator is not supported. 10395 parsed_query = self._parse_pipe_syntax_set_operator(query) 10396 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10397 if not parsed_query: 10398 self._retreat(start_index) 10399 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10400 break 10401 query = parsed_query 10402 else: 10403 query = parser(self, query) 10404 10405 return query 10406 10407 def _parse_declareitem(self) -> exp.DeclareItem | None: 10408 self._match_texts(("VAR", "VARIABLE")) 10409 10410 vars = self._parse_csv(self._parse_id_var) 10411 if not vars: 10412 return None 10413 10414 self._match(TokenType.ALIAS) 10415 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10416 default = ( 10417 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10418 ) and self._parse_bitwise() 10419 10420 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10421 10422 def _parse_declare(self) -> exp.Declare | exp.Command: 10423 start = self._prev 10424 replace = self._match_text_seq("OR", "REPLACE") 10425 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10426 10427 if not expressions or self._curr: 10428 return self._parse_as_command(start) 10429 10430 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10431 10432 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10433 exp_class = exp.Cast if strict else exp.TryCast 10434 10435 if exp_class == exp.TryCast: 10436 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10437 10438 return self.expression(exp_class(**kwargs)) 10439 10440 def _parse_json_value(self) -> exp.JSONValue: 10441 this = self._parse_bitwise() 10442 self._match(TokenType.COMMA) 10443 path = self._parse_bitwise() 10444 10445 returning = self._match(TokenType.RETURNING) and self._parse_type() 10446 10447 return self.expression( 10448 exp.JSONValue( 10449 this=this, 10450 path=self.dialect.to_json_path(path), 10451 returning=returning, 10452 on_condition=self._parse_on_condition(), 10453 ) 10454 ) 10455 10456 def _parse_group_concat(self) -> exp.Expr | None: 10457 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10458 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10459 concat_exprs = [ 10460 self.expression( 10461 exp.Concat( 10462 expressions=node.expressions, 10463 safe=True, 10464 coalesce=self.dialect.CONCAT_COALESCE, 10465 ) 10466 ) 10467 ] 10468 node.set("expressions", concat_exprs) 10469 return node 10470 if len(exprs) == 1: 10471 return exprs[0] 10472 return self.expression( 10473 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10474 ) 10475 10476 args = self._parse_csv(self._parse_lambda) 10477 10478 if args: 10479 order = args[-1] if isinstance(args[-1], exp.Order) else None 10480 10481 if order: 10482 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10483 # remove 'expr' from exp.Order and add it back to args 10484 args[-1] = order.this 10485 order.set("this", concat_exprs(order.this, args)) 10486 10487 this = order or concat_exprs(args[0], args) 10488 else: 10489 this = None 10490 10491 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10492 10493 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10494 10495 def _parse_initcap(self) -> exp.Initcap: 10496 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10497 10498 # attach dialect's default delimiters 10499 if expr.args.get("expression") is None: 10500 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10501 10502 return expr 10503 10504 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10505 if not self._match(TokenType.L_PAREN): 10506 self._retreat(self._index - 1) 10507 return None 10508 10509 op = "" 10510 while self._curr and not self._match(TokenType.R_PAREN): 10511 op += self._curr.text 10512 self._advance() 10513 10514 comments = self._prev_comments 10515 return self.expression( 10516 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10517 comments=comments, 10518 )
51def build_var_map(args: BuilderArgs) -> exp.StarMap | exp.VarMap: 52 if len(args) == 1 and args[0].is_star: 53 return exp.StarMap(this=args[0]) 54 55 keys: list[ExpOrStr] = [] 56 values: list[ExpOrStr] = [] 57 for i in range(0, len(args), 2): 58 keys.append(args[i]) 59 values.append(args[i + 1]) 60 61 return exp.VarMap(keys=exp.array(*keys, copy=False), values=exp.array(*values, copy=False))
69def binary_range_parser( 70 expr_type: Type[exp.Expr], reverse_args: bool = False 71) -> t.Callable[[Parser, exp.Expr | None], exp.Expr | None]: 72 def _parse_binary_range(self: Parser, this: exp.Expr | None) -> exp.Expr | None: 73 expression = self._parse_bitwise() 74 if reverse_args: 75 this, expression = expression, this 76 return self._parse_escape(self.expression(expr_type(this=this, expression=expression))) 77 78 return _parse_binary_range
81def build_logarithm(args: BuilderArgs, dialect: Dialect) -> exp.Func: 82 # Default argument order is base, expression 83 this = seq_get(args, 0) 84 expression = seq_get(args, 1) 85 86 if expression: 87 if not dialect.LOG_BASE_FIRST: 88 this, expression = expression, this 89 return exp.Log(this=this, expression=expression) 90 91 return (exp.Ln if dialect.parser_class.LOG_DEFAULTS_TO_LN else exp.Log)(this=this)
111def build_extract_json_with_path( 112 expr_type: Type[E], 113) -> t.Callable[[BuilderArgs, Dialect], E]: 114 def _builder(args: BuilderArgs, dialect: Dialect) -> E: 115 expression = expr_type( 116 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 117 ) 118 if len(args) > 2 and expr_type is exp.JSONExtract: 119 expression.set("expressions", args[2:]) 120 if expr_type is exp.JSONExtractScalar: 121 expression.set("scalar_only", dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY) 122 123 return expression 124 125 return _builder
128def build_mod(args: BuilderArgs) -> exp.Mod: 129 this = seq_get(args, 0) 130 expression = seq_get(args, 1) 131 132 # Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7 133 this = exp.Paren(this=this) if isinstance(this, exp.Binary) else this 134 expression = exp.Paren(this=expression) if isinstance(expression, exp.Binary) else expression 135 136 return exp.Mod(this=this, expression=expression)
148def build_array_constructor( 149 exp_class: Type[E], args: list[t.Any], bracket_kind: TokenType, dialect: Dialect 150) -> exp.Expr: 151 array_exp = exp_class(expressions=args) 152 153 if exp_class == exp.Array and dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS: 154 array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET) 155 156 return array_exp
159def build_convert_timezone( 160 args: BuilderArgs, default_source_tz: str | None = None 161) -> exp.ConvertTimezone | exp.Anonymous: 162 if len(args) == 2: 163 source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None 164 return exp.ConvertTimezone( 165 source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1) 166 ) 167 168 return exp.ConvertTimezone.from_arg_list(args)
171def build_trim(args: BuilderArgs, is_left: bool = True, reverse_args: bool = False) -> exp.Trim: 172 this, expression = seq_get(args, 0), seq_get(args, 1) 173 174 if expression and reverse_args: 175 this, expression = expression, this 176 177 return exp.Trim(this=this, expression=expression, position="LEADING" if is_left else "TRAILING")
194def build_array_append(args: BuilderArgs, dialect: Dialect) -> exp.ArrayAppend: 195 """ 196 Builds ArrayAppend with NULL propagation semantics based on the dialect configuration. 197 198 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 199 Others (DuckDB, PostgreSQL) create a new single-element array instead. 200 201 Args: 202 args: Function arguments [array, element] 203 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 204 205 Returns: 206 ArrayAppend expression with appropriate null_propagation flag 207 """ 208 return exp.ArrayAppend( 209 this=seq_get(args, 0), 210 expression=seq_get(args, 1), 211 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 212 )
Builds ArrayAppend with NULL propagation semantics based on the dialect configuration.
Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. Others (DuckDB, PostgreSQL) create a new single-element array instead.
Arguments:
- args: Function arguments [array, element]
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayAppend expression with appropriate null_propagation flag
215def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend: 216 """ 217 Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration. 218 219 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 220 Others (DuckDB, PostgreSQL) create a new single-element array instead. 221 222 Args: 223 args: Function arguments [array, element] 224 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 225 226 Returns: 227 ArrayPrepend expression with appropriate null_propagation flag 228 """ 229 return exp.ArrayPrepend( 230 this=seq_get(args, 0), 231 expression=seq_get(args, 1), 232 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 233 )
Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration.
Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. Others (DuckDB, PostgreSQL) create a new single-element array instead.
Arguments:
- args: Function arguments [array, element]
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayPrepend expression with appropriate null_propagation flag
236def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat: 237 """ 238 Builds ArrayConcat with NULL propagation semantics based on the dialect configuration. 239 240 Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. 241 Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation. 242 243 Args: 244 args: Function arguments [array1, array2, ...] (variadic) 245 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 246 247 Returns: 248 ArrayConcat expression with appropriate null_propagation flag 249 """ 250 return exp.ArrayConcat( 251 this=seq_get(args, 0), 252 expressions=args[1:], 253 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 254 )
Builds ArrayConcat with NULL propagation semantics based on the dialect configuration.
Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation.
Arguments:
- args: Function arguments [array1, array2, ...] (variadic)
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayConcat expression with appropriate null_propagation flag
257def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove: 258 """ 259 Builds ArrayRemove with NULL propagation semantics based on the dialect configuration. 260 261 Some dialects (Snowflake) return NULL when the removal value is NULL. 262 Others (DuckDB) may return empty array due to NULL comparison semantics. 263 264 Args: 265 args: Function arguments [array, value_to_remove] 266 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 267 268 Returns: 269 ArrayRemove expression with appropriate null_propagation flag 270 """ 271 return exp.ArrayRemove( 272 this=seq_get(args, 0), 273 expression=seq_get(args, 1), 274 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 275 )
Builds ArrayRemove with NULL propagation semantics based on the dialect configuration.
Some dialects (Snowflake) return NULL when the removal value is NULL. Others (DuckDB) may return empty array due to NULL comparison semantics.
Arguments:
- args: Function arguments [array, value_to_remove]
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayRemove expression with appropriate null_propagation flag
306def build_json_extract_scalar( 307 self: Parser, this: exp.Expr, path: exp.Expr 308) -> exp.JSONExtractScalar: 309 return self.expression( 310 exp.JSONExtractScalar( 311 this=this, 312 expression=self.dialect.to_json_path(path), 313 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 314 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 315 ) 316 )
338class Parser: 339 """ 340 Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree. 341 342 Args: 343 error_level: The desired error level. 344 Default: ErrorLevel.IMMEDIATE 345 error_message_context: The amount of context to capture from a query string when displaying 346 the error message (in number of characters). 347 Default: 100 348 max_errors: Maximum number of error messages to include in a raised ParseError. 349 This is only relevant if error_level is ErrorLevel.RAISE. 350 Default: 3 351 max_nodes: Maximum number of AST nodes to prevent memory exhaustion. 352 Set to -1 (default) to disable the check. 353 """ 354 355 __slots__ = ( 356 "error_level", 357 "error_message_context", 358 "max_errors", 359 "max_nodes", 360 "dialect", 361 "sql", 362 "errors", 363 "_tokens", 364 "_index", 365 "_curr", 366 "_next", 367 "_prev", 368 "_prev_comments", 369 "_pipe_cte_counter", 370 "_chunks", 371 "_chunk_index", 372 "_tokens_size", 373 "_node_count", 374 ) 375 376 FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = { 377 **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()}, 378 **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce), 379 "ARRAY": lambda args, dialect: exp.Array(expressions=args), 380 "ARRAYAGG": lambda args, dialect: exp.ArrayAgg( 381 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 382 ), 383 "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg( 384 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 385 ), 386 "ARRAY_APPEND": build_array_append, 387 "ARRAY_CAT": build_array_concat, 388 "ARRAY_CONCAT": build_array_concat, 389 "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args), 390 "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args), 391 "ARRAY_PREPEND": build_array_prepend, 392 "ARRAY_REMOVE": build_array_remove, 393 "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True), 394 "CONCAT": lambda args, dialect: exp.Concat( 395 expressions=args, 396 safe=not dialect.STRICT_STRING_CONCAT, 397 coalesce=dialect.CONCAT_COALESCE, 398 ), 399 "CONCAT_WS": lambda args, dialect: exp.ConcatWs( 400 expressions=args, 401 safe=not dialect.STRICT_STRING_CONCAT, 402 coalesce=dialect.CONCAT_WS_COALESCE, 403 ), 404 "CONVERT_TIMEZONE": build_convert_timezone, 405 "DATE_TO_DATE_STR": lambda args: exp.Cast( 406 this=seq_get(args, 0), 407 to=exp.DataType(this=exp.DType.TEXT), 408 ), 409 "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray( 410 start=seq_get(args, 0), 411 end=seq_get(args, 1), 412 step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")), 413 ), 414 "GENERATE_UUID": lambda args, dialect: exp.Uuid( 415 is_string=dialect.UUID_IS_STRING_TYPE or None 416 ), 417 "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)), 418 "GREATEST": lambda args, dialect: exp.Greatest( 419 this=seq_get(args, 0), 420 expressions=args[1:], 421 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 422 ), 423 "LEAST": lambda args, dialect: exp.Least( 424 this=seq_get(args, 0), 425 expressions=args[1:], 426 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 427 ), 428 "HEX": build_hex, 429 "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract), 430 "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar), 431 "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar), 432 "JSON_KEYS": lambda args, dialect: exp.JSONKeys( 433 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 434 ), 435 "LIKE": build_like, 436 "LOG": build_logarithm, 437 "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)), 438 "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)), 439 "LOWER": build_lower, 440 "LPAD": lambda args: build_pad(args), 441 "LEFTPAD": lambda args: build_pad(args), 442 "LTRIM": lambda args: build_trim(args), 443 "MOD": build_mod, 444 "RIGHTPAD": lambda args: build_pad(args, is_left=False), 445 "RPAD": lambda args: build_pad(args, is_left=False), 446 "RTRIM": lambda args: build_trim(args, is_left=False), 447 "SCOPE_RESOLUTION": lambda args: ( 448 exp.ScopeResolution(expression=seq_get(args, 0)) 449 if len(args) != 2 450 else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1)) 451 ), 452 "STRPOS": exp.StrPosition.from_arg_list, 453 "CHARINDEX": lambda args: build_locate_strposition(args), 454 "INSTR": exp.StrPosition.from_arg_list, 455 "LOCATE": lambda args: build_locate_strposition(args), 456 "TIME_TO_TIME_STR": lambda args: exp.Cast( 457 this=seq_get(args, 0), 458 to=exp.DataType(this=exp.DType.TEXT), 459 ), 460 "TO_HEX": build_hex, 461 "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring( 462 this=exp.Cast( 463 this=seq_get(args, 0), 464 to=exp.DataType(this=exp.DType.TEXT), 465 ), 466 start=exp.Literal.number(1), 467 length=exp.Literal.number(10), 468 ), 469 "UNNEST": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))), 470 "UPPER": build_upper, 471 "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None), 472 "UUID_STRING": lambda args, dialect: exp.Uuid( 473 this=seq_get(args, 0), 474 name=seq_get(args, 1), 475 is_string=dialect.UUID_IS_STRING_TYPE or None, 476 ), 477 "VAR_MAP": build_var_map, 478 } 479 480 NO_PAREN_FUNCTIONS: t.ClassVar[dict] = { 481 TokenType.CURRENT_DATE: exp.CurrentDate, 482 TokenType.CURRENT_DATETIME: exp.CurrentDate, 483 TokenType.CURRENT_TIME: exp.CurrentTime, 484 TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp, 485 TokenType.CURRENT_USER: exp.CurrentUser, 486 TokenType.CURRENT_ROLE: exp.CurrentRole, 487 } 488 489 STRUCT_TYPE_TOKENS: t.ClassVar = { 490 TokenType.NESTED, 491 TokenType.OBJECT, 492 TokenType.STRUCT, 493 TokenType.UNION, 494 } 495 496 NESTED_TYPE_TOKENS: t.ClassVar = { 497 TokenType.ARRAY, 498 TokenType.LIST, 499 TokenType.LOWCARDINALITY, 500 TokenType.MAP, 501 TokenType.NULLABLE, 502 TokenType.RANGE, 503 *STRUCT_TYPE_TOKENS, 504 } 505 506 ENUM_TYPE_TOKENS: t.ClassVar = { 507 TokenType.DYNAMIC, 508 TokenType.ENUM, 509 TokenType.ENUM8, 510 TokenType.ENUM16, 511 } 512 513 AGGREGATE_TYPE_TOKENS: t.ClassVar = { 514 TokenType.AGGREGATEFUNCTION, 515 TokenType.SIMPLEAGGREGATEFUNCTION, 516 } 517 518 TYPE_TOKENS: t.ClassVar = { 519 TokenType.BIT, 520 TokenType.BOOLEAN, 521 TokenType.TINYINT, 522 TokenType.UTINYINT, 523 TokenType.SMALLINT, 524 TokenType.USMALLINT, 525 TokenType.INT, 526 TokenType.UINT, 527 TokenType.BIGINT, 528 TokenType.UBIGINT, 529 TokenType.BIGNUM, 530 TokenType.INT128, 531 TokenType.UINT128, 532 TokenType.INT256, 533 TokenType.UINT256, 534 TokenType.MEDIUMINT, 535 TokenType.UMEDIUMINT, 536 TokenType.FIXEDSTRING, 537 TokenType.FLOAT, 538 TokenType.DOUBLE, 539 TokenType.UDOUBLE, 540 TokenType.CHAR, 541 TokenType.NCHAR, 542 TokenType.VARCHAR, 543 TokenType.NVARCHAR, 544 TokenType.BPCHAR, 545 TokenType.TEXT, 546 TokenType.MEDIUMTEXT, 547 TokenType.LONGTEXT, 548 TokenType.BLOB, 549 TokenType.MEDIUMBLOB, 550 TokenType.LONGBLOB, 551 TokenType.BINARY, 552 TokenType.VARBINARY, 553 TokenType.JSON, 554 TokenType.JSONB, 555 TokenType.INTERVAL, 556 TokenType.TINYBLOB, 557 TokenType.TINYTEXT, 558 TokenType.TIME, 559 TokenType.TIMETZ, 560 TokenType.TIME_NS, 561 TokenType.TIMESTAMP, 562 TokenType.TIMESTAMP_S, 563 TokenType.TIMESTAMP_MS, 564 TokenType.TIMESTAMP_NS, 565 TokenType.TIMESTAMPTZ, 566 TokenType.TIMESTAMPLTZ, 567 TokenType.TIMESTAMPNTZ, 568 TokenType.DATETIME, 569 TokenType.DATETIME2, 570 TokenType.DATETIME64, 571 TokenType.SMALLDATETIME, 572 TokenType.DATE, 573 TokenType.DATE32, 574 TokenType.INT4RANGE, 575 TokenType.INT4MULTIRANGE, 576 TokenType.INT8RANGE, 577 TokenType.INT8MULTIRANGE, 578 TokenType.NUMRANGE, 579 TokenType.NUMMULTIRANGE, 580 TokenType.TSRANGE, 581 TokenType.TSMULTIRANGE, 582 TokenType.TSTZRANGE, 583 TokenType.TSTZMULTIRANGE, 584 TokenType.DATERANGE, 585 TokenType.DATEMULTIRANGE, 586 TokenType.DECIMAL, 587 TokenType.DECIMAL32, 588 TokenType.DECIMAL64, 589 TokenType.DECIMAL128, 590 TokenType.DECIMAL256, 591 TokenType.DECFLOAT, 592 TokenType.UDECIMAL, 593 TokenType.BIGDECIMAL, 594 TokenType.UUID, 595 TokenType.GEOGRAPHY, 596 TokenType.GEOGRAPHYPOINT, 597 TokenType.GEOMETRY, 598 TokenType.POINT, 599 TokenType.RING, 600 TokenType.LINESTRING, 601 TokenType.MULTILINESTRING, 602 TokenType.POLYGON, 603 TokenType.MULTIPOLYGON, 604 TokenType.HLLSKETCH, 605 TokenType.HSTORE, 606 TokenType.PSEUDO_TYPE, 607 TokenType.SUPER, 608 TokenType.SERIAL, 609 TokenType.SMALLSERIAL, 610 TokenType.BIGSERIAL, 611 TokenType.XML, 612 TokenType.YEAR, 613 TokenType.USERDEFINED, 614 TokenType.MONEY, 615 TokenType.SMALLMONEY, 616 TokenType.ROWVERSION, 617 TokenType.IMAGE, 618 TokenType.VARIANT, 619 TokenType.VECTOR, 620 TokenType.VOID, 621 TokenType.OBJECT, 622 TokenType.OBJECT_IDENTIFIER, 623 TokenType.INET, 624 TokenType.IPADDRESS, 625 TokenType.IPPREFIX, 626 TokenType.IPV4, 627 TokenType.IPV6, 628 TokenType.UNKNOWN, 629 TokenType.NOTHING, 630 TokenType.NULL, 631 TokenType.NAME, 632 TokenType.TDIGEST, 633 TokenType.DYNAMIC, 634 *ENUM_TYPE_TOKENS, 635 *NESTED_TYPE_TOKENS, 636 *AGGREGATE_TYPE_TOKENS, 637 } 638 639 SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = { 640 TokenType.BIGINT: TokenType.UBIGINT, 641 TokenType.INT: TokenType.UINT, 642 TokenType.MEDIUMINT: TokenType.UMEDIUMINT, 643 TokenType.SMALLINT: TokenType.USMALLINT, 644 TokenType.TINYINT: TokenType.UTINYINT, 645 TokenType.DECIMAL: TokenType.UDECIMAL, 646 TokenType.DOUBLE: TokenType.UDOUBLE, 647 } 648 649 SUBQUERY_PREDICATES: t.ClassVar = { 650 TokenType.ANY: exp.Any, 651 TokenType.ALL: exp.All, 652 TokenType.EXISTS: exp.Exists, 653 TokenType.SOME: exp.Any, 654 } 655 656 SUBQUERY_TOKENS: t.ClassVar = { 657 TokenType.SELECT, 658 TokenType.WITH, 659 TokenType.FROM, 660 } 661 662 RESERVED_TOKENS: t.ClassVar = { 663 *Tokenizer.SINGLE_TOKENS.values(), 664 TokenType.SELECT, 665 } - {TokenType.IDENTIFIER} 666 667 # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers, 668 # string literals), so they must never be treated as keywords when matching by text 669 TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset( 670 { 671 TokenType.BIT_STRING, 672 TokenType.BYTE_STRING, 673 TokenType.HEREDOC_STRING, 674 TokenType.HEX_STRING, 675 TokenType.IDENTIFIER, 676 TokenType.NATIONAL_STRING, 677 TokenType.RAW_STRING, 678 TokenType.STRING, 679 TokenType.UNICODE_STRING, 680 } 681 ) 682 683 DB_CREATABLES: t.ClassVar = { 684 TokenType.DATABASE, 685 TokenType.DICTIONARY, 686 TokenType.FILE_FORMAT, 687 TokenType.MODEL, 688 TokenType.NAMESPACE, 689 TokenType.SCHEMA, 690 TokenType.SEMANTIC_VIEW, 691 TokenType.SEQUENCE, 692 TokenType.SINK, 693 TokenType.SOURCE, 694 TokenType.STAGE, 695 TokenType.STORAGE_INTEGRATION, 696 TokenType.STREAMLIT, 697 TokenType.TABLE, 698 TokenType.TAG, 699 TokenType.VIEW, 700 TokenType.WAREHOUSE, 701 } 702 703 CREATABLES: t.ClassVar = { 704 TokenType.COLUMN, 705 TokenType.CONSTRAINT, 706 TokenType.FOREIGN_KEY, 707 TokenType.FUNCTION, 708 TokenType.INDEX, 709 TokenType.PROCEDURE, 710 TokenType.TRIGGER, 711 TokenType.TYPE, 712 *DB_CREATABLES, 713 } 714 715 TRIGGER_EVENTS: t.ClassVar = { 716 TokenType.INSERT, 717 TokenType.UPDATE, 718 TokenType.DELETE, 719 TokenType.TRUNCATE, 720 } 721 722 ALTERABLES: t.ClassVar = { 723 TokenType.INDEX, 724 TokenType.TABLE, 725 TokenType.VIEW, 726 TokenType.SESSION, 727 } 728 729 # Tokens that can represent identifiers 730 ID_VAR_TOKENS: t.ClassVar[set] = { 731 TokenType.ALL, 732 TokenType.ANALYZE, 733 TokenType.ATTACH, 734 TokenType.VAR, 735 TokenType.ANTI, 736 TokenType.APPLY, 737 TokenType.ASC, 738 TokenType.ASOF, 739 TokenType.AUTO_INCREMENT, 740 TokenType.BEGIN, 741 TokenType.BPCHAR, 742 TokenType.CACHE, 743 TokenType.CASE, 744 TokenType.COLLATE, 745 TokenType.COMMAND, 746 TokenType.COMMENT, 747 TokenType.COMMIT, 748 TokenType.CONSTRAINT, 749 TokenType.COPY, 750 TokenType.CUBE, 751 TokenType.CURRENT_SCHEMA, 752 TokenType.DECLARE, 753 TokenType.DEFAULT, 754 TokenType.DELETE, 755 TokenType.DESC, 756 TokenType.DESCRIBE, 757 TokenType.DETACH, 758 TokenType.DICTIONARY, 759 TokenType.DIV, 760 TokenType.END, 761 TokenType.EXECUTE, 762 TokenType.EXPORT, 763 TokenType.ESCAPE, 764 TokenType.FALSE, 765 TokenType.FIRST, 766 TokenType.FILE, 767 TokenType.FILTER, 768 TokenType.FINAL, 769 TokenType.FORMAT, 770 TokenType.FULL, 771 TokenType.GET, 772 TokenType.IDENTIFIER, 773 TokenType.INOUT, 774 TokenType.IS, 775 TokenType.ISNULL, 776 TokenType.INTERVAL, 777 TokenType.KEEP, 778 TokenType.KILL, 779 TokenType.LEFT, 780 TokenType.LIMIT, 781 TokenType.LOAD, 782 TokenType.LOCK, 783 TokenType.MATCH, 784 TokenType.MERGE, 785 TokenType.NATURAL, 786 TokenType.NEXT, 787 TokenType.OFFSET, 788 TokenType.OPERATOR, 789 TokenType.ORDINALITY, 790 TokenType.OUT, 791 TokenType.OVER, 792 TokenType.OVERLAPS, 793 TokenType.OVERWRITE, 794 TokenType.PARTITION, 795 TokenType.PERCENT, 796 TokenType.PIVOT, 797 TokenType.PROJECTION, 798 TokenType.PRAGMA, 799 TokenType.PUT, 800 TokenType.RANGE, 801 TokenType.RECURSIVE, 802 TokenType.REFERENCES, 803 TokenType.REFRESH, 804 TokenType.RENAME, 805 TokenType.REPLACE, 806 TokenType.RIGHT, 807 TokenType.ROLLUP, 808 TokenType.ROW, 809 TokenType.ROWS, 810 TokenType.SEMI, 811 TokenType.SET, 812 TokenType.SETTINGS, 813 TokenType.SHOW, 814 TokenType.STREAM, 815 TokenType.STREAMLIT, 816 TokenType.TEMPORARY, 817 TokenType.TOP, 818 TokenType.TRUE, 819 TokenType.TRUNCATE, 820 TokenType.UNIQUE, 821 TokenType.UNNEST, 822 TokenType.UNPIVOT, 823 TokenType.UPDATE, 824 TokenType.USE, 825 TokenType.VOLATILE, 826 TokenType.WINDOW, 827 TokenType.CURRENT_CATALOG, 828 TokenType.LOCALTIME, 829 TokenType.LOCALTIMESTAMP, 830 TokenType.SESSION_USER, 831 TokenType.STRAIGHT_JOIN, 832 *ALTERABLES, 833 *CREATABLES, 834 *SUBQUERY_PREDICATES, 835 *TYPE_TOKENS, 836 *NO_PAREN_FUNCTIONS, 837 } - {TokenType.UNION} 838 839 TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - { 840 TokenType.ANTI, 841 TokenType.ASOF, 842 TokenType.FULL, 843 TokenType.LEFT, 844 TokenType.LOCK, 845 TokenType.NATURAL, 846 TokenType.RIGHT, 847 TokenType.SEMI, 848 TokenType.WINDOW, 849 } 850 851 ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS 852 853 COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS 854 855 ARRAY_CONSTRUCTORS: t.ClassVar = { 856 "ARRAY": exp.Array, 857 "LIST": exp.List, 858 } 859 860 COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS} 861 862 UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET} 863 864 TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"} 865 866 # Tokens that indicate a simple column reference 867 IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER}) 868 869 BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE}) 870 871 # Postfix tokens that prevent the bare column fast path 872 COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 873 { 874 TokenType.L_PAREN, 875 TokenType.L_BRACKET, 876 TokenType.L_BRACE, 877 TokenType.COLON, 878 TokenType.JOIN_MARKER, 879 } 880 ) 881 882 TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 883 { 884 TokenType.L_PAREN, 885 TokenType.L_BRACKET, 886 TokenType.L_BRACE, 887 TokenType.PIVOT, 888 TokenType.UNPIVOT, 889 TokenType.TABLE_SAMPLE, 890 } 891 ) 892 893 FUNC_TOKENS: t.ClassVar = { 894 TokenType.COLLATE, 895 TokenType.COMMAND, 896 TokenType.CURRENT_DATE, 897 TokenType.CURRENT_DATETIME, 898 TokenType.CURRENT_SCHEMA, 899 TokenType.CURRENT_TIMESTAMP, 900 TokenType.CURRENT_TIME, 901 TokenType.CURRENT_USER, 902 TokenType.CURRENT_CATALOG, 903 TokenType.DECLARE, 904 TokenType.FILTER, 905 TokenType.FIRST, 906 TokenType.FORMAT, 907 TokenType.GET, 908 TokenType.GLOB, 909 TokenType.IDENTIFIER, 910 TokenType.INDEX, 911 TokenType.ISNULL, 912 TokenType.ILIKE, 913 TokenType.INSERT, 914 TokenType.LIKE, 915 TokenType.LOCALTIME, 916 TokenType.LOCALTIMESTAMP, 917 TokenType.MERGE, 918 TokenType.NEXT, 919 TokenType.OFFSET, 920 TokenType.PRIMARY_KEY, 921 TokenType.RANGE, 922 TokenType.REPLACE, 923 TokenType.RLIKE, 924 TokenType.ROW, 925 TokenType.SESSION_USER, 926 TokenType.UNNEST, 927 TokenType.VAR, 928 TokenType.LEFT, 929 TokenType.RIGHT, 930 TokenType.SEQUENCE, 931 TokenType.DATE, 932 TokenType.DATETIME, 933 TokenType.TABLE, 934 TokenType.TIMESTAMP, 935 TokenType.TIMESTAMPTZ, 936 TokenType.TRUNCATE, 937 TokenType.UTC_DATE, 938 TokenType.UTC_TIME, 939 TokenType.UTC_TIMESTAMP, 940 TokenType.WINDOW, 941 TokenType.XOR, 942 *TYPE_TOKENS, 943 *SUBQUERY_PREDICATES, 944 } 945 946 CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 947 TokenType.AND: exp.And, 948 } 949 950 ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 951 TokenType.COLON_EQ: exp.PropertyEQ, 952 } 953 954 DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 955 TokenType.OR: exp.Or, 956 } 957 958 EQUALITY: t.ClassVar = { 959 TokenType.EQ: exp.EQ, 960 TokenType.NEQ: exp.NEQ, 961 TokenType.NULLSAFE_EQ: exp.NullSafeEQ, 962 } 963 964 COMPARISON: t.ClassVar = { 965 TokenType.GT: exp.GT, 966 TokenType.GTE: exp.GTE, 967 TokenType.LT: exp.LT, 968 TokenType.LTE: exp.LTE, 969 } 970 971 BITWISE: t.ClassVar = { 972 TokenType.AMP: exp.BitwiseAnd, 973 TokenType.CARET: exp.BitwiseXor, 974 TokenType.PIPE: exp.BitwiseOr, 975 } 976 977 TERM: t.ClassVar = { 978 TokenType.DASH: exp.Sub, 979 TokenType.PLUS: exp.Add, 980 TokenType.COLLATE: exp.Collate, 981 } 982 983 FACTOR: t.ClassVar = { 984 TokenType.DIV: exp.IntDiv, 985 TokenType.LR_ARROW: exp.Distance, 986 TokenType.LLRR_ARROW: exp.DistanceNd, 987 TokenType.MOD: exp.Mod, 988 TokenType.SLASH: exp.Div, 989 TokenType.STAR: exp.Mul, 990 } 991 992 EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {} 993 994 TIMES: t.ClassVar = { 995 TokenType.TIME, 996 TokenType.TIMETZ, 997 } 998 999 TIMESTAMPS: t.ClassVar = { 1000 TokenType.TIMESTAMP, 1001 TokenType.TIMESTAMPNTZ, 1002 TokenType.TIMESTAMPTZ, 1003 TokenType.TIMESTAMPLTZ, 1004 *TIMES, 1005 } 1006 1007 SET_OPERATIONS: t.ClassVar = { 1008 TokenType.UNION, 1009 TokenType.INTERSECT, 1010 TokenType.EXCEPT, 1011 } 1012 1013 JOIN_METHODS: t.ClassVar = { 1014 TokenType.ASOF, 1015 TokenType.NATURAL, 1016 TokenType.POSITIONAL, 1017 } 1018 1019 JOIN_SIDES: t.ClassVar = { 1020 TokenType.LEFT, 1021 TokenType.RIGHT, 1022 TokenType.FULL, 1023 } 1024 1025 JOIN_KINDS: t.ClassVar = { 1026 TokenType.ANTI, 1027 TokenType.CROSS, 1028 TokenType.INNER, 1029 TokenType.OUTER, 1030 TokenType.SEMI, 1031 TokenType.STRAIGHT_JOIN, 1032 } 1033 1034 JOIN_HINTS: t.ClassVar[set[str]] = set() 1035 1036 # Tokens that unambiguously end a table reference on the fast path 1037 TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset( 1038 { 1039 TokenType.COMMA, 1040 TokenType.GROUP_BY, 1041 TokenType.HAVING, 1042 TokenType.JOIN, 1043 TokenType.LIMIT, 1044 TokenType.ON, 1045 TokenType.ORDER_BY, 1046 TokenType.R_PAREN, 1047 TokenType.SEMICOLON, 1048 TokenType.SENTINEL, 1049 TokenType.WHERE, 1050 *SET_OPERATIONS, 1051 *JOIN_KINDS, 1052 *JOIN_METHODS, 1053 *JOIN_SIDES, 1054 } 1055 ) 1056 1057 LAMBDAS: t.ClassVar = { 1058 TokenType.ARROW: lambda self, expressions: self.expression( 1059 exp.Lambda( 1060 this=self._replace_lambda( 1061 self._parse_disjunction(), 1062 expressions, 1063 ), 1064 expressions=expressions, 1065 ) 1066 ), 1067 TokenType.FARROW: lambda self, expressions: self.expression( 1068 exp.Kwarg( 1069 this=exp.var(expressions[0].name), 1070 expression=self._parse_disjunction() or self._parse_select(), 1071 ) 1072 ), 1073 } 1074 1075 # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake 1076 TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False 1077 1078 LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN}) 1079 1080 COLUMN_OPERATORS: t.ClassVar = { 1081 TokenType.DOT: None, 1082 TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)), 1083 TokenType.DCOLON: lambda self, this, to: self.build_cast( 1084 strict=self.STRICT_CAST, this=this, to=to 1085 ), 1086 TokenType.ARROW: lambda self, this, path: self.expression( 1087 exp.JSONExtract( 1088 this=this, 1089 expression=self.dialect.to_json_path(path), 1090 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1091 ) 1092 ), 1093 TokenType.DARROW: lambda self, this, path: self.expression( 1094 exp.JSONExtractScalar( 1095 this=this, 1096 expression=self.dialect.to_json_path(path), 1097 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1098 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 1099 ) 1100 ), 1101 TokenType.HASH_ARROW: lambda self, this, path: self.expression( 1102 exp.JSONBExtract(this=this, expression=path) 1103 ), 1104 TokenType.DHASH_ARROW: lambda self, this, path: self.expression( 1105 exp.JSONBExtractScalar(this=this, expression=path) 1106 ), 1107 TokenType.PLACEHOLDER: lambda self, this, key: self.expression( 1108 exp.JSONBContainsTopKey(this=this, expression=key) 1109 ), 1110 } 1111 1112 # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator" 1113 # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs). 1114 JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {} 1115 1116 CAST_COLUMN_OPERATORS: t.ClassVar = { 1117 TokenType.DOTCOLON, 1118 TokenType.DCOLON, 1119 } 1120 1121 EXPRESSION_PARSERS: t.ClassVar = { 1122 exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), 1123 exp.Column: lambda self: self._parse_column(), 1124 exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()), 1125 exp.Condition: lambda self: self._parse_disjunction(), 1126 exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True), 1127 exp.Expr: lambda self: self._parse_expression(), 1128 exp.From: lambda self: self._parse_from(joins=True), 1129 exp.GrantPrincipal: lambda self: self._parse_grant_principal(), 1130 exp.GrantPrivilege: lambda self: self._parse_grant_privilege(), 1131 exp.Group: lambda self: self._parse_group(), 1132 exp.Having: lambda self: self._parse_having(), 1133 exp.Hint: lambda self: self._parse_hint_body(), 1134 exp.Identifier: lambda self: self._parse_id_var(), 1135 exp.Join: lambda self: self._parse_join(), 1136 exp.Lambda: lambda self: self._parse_lambda(), 1137 exp.Lateral: lambda self: self._parse_lateral(), 1138 exp.Limit: lambda self: self._parse_limit(), 1139 exp.Offset: lambda self: self._parse_offset(), 1140 exp.Order: lambda self: self._parse_order(), 1141 exp.Ordered: lambda self: self._parse_ordered(), 1142 exp.Properties: lambda self: self._parse_properties(), 1143 exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(), 1144 exp.Qualify: lambda self: self._parse_qualify(), 1145 exp.Returning: lambda self: self._parse_returning(), 1146 exp.Select: lambda self: self._parse_select(), 1147 exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), 1148 exp.Table: lambda self: self._parse_table_parts(), 1149 exp.TableAlias: lambda self: self._parse_table_alias(), 1150 exp.Tuple: lambda self: self._parse_value(values=False), 1151 exp.Whens: lambda self: self._parse_when_matched(), 1152 exp.Where: lambda self: self._parse_where(), 1153 exp.Window: lambda self: self._parse_named_window(), 1154 exp.With: lambda self: self._parse_with(), 1155 } 1156 1157 STATEMENT_PARSERS: t.ClassVar = { 1158 TokenType.ALTER: lambda self: self._parse_alter(), 1159 TokenType.ANALYZE: lambda self: self._parse_analyze(), 1160 TokenType.BEGIN: lambda self: self._parse_transaction(), 1161 TokenType.CACHE: lambda self: self._parse_cache(), 1162 TokenType.COMMENT: lambda self: self._parse_comment(), 1163 TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(), 1164 TokenType.COPY: lambda self: self._parse_copy(), 1165 TokenType.CREATE: lambda self: self._parse_create(), 1166 TokenType.DECLARE: lambda self: self._parse_declare(), 1167 TokenType.DELETE: lambda self: self._parse_delete(), 1168 TokenType.DESC: lambda self: self._parse_describe(), 1169 TokenType.DESCRIBE: lambda self: self._parse_describe(), 1170 TokenType.DROP: lambda self: self._parse_drop(), 1171 TokenType.GRANT: lambda self: self._parse_grant(), 1172 TokenType.REVOKE: lambda self: self._parse_revoke(), 1173 TokenType.INSERT: lambda self: self._parse_insert(), 1174 TokenType.KILL: lambda self: self._parse_kill(), 1175 TokenType.LOAD: lambda self: self._parse_load(), 1176 TokenType.MERGE: lambda self: self._parse_merge(), 1177 TokenType.PIVOT: lambda self: self._parse_simplified_pivot(), 1178 TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())), 1179 TokenType.REFRESH: lambda self: self._parse_refresh(), 1180 TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(), 1181 TokenType.SET: lambda self: self._parse_set(), 1182 TokenType.TRUNCATE: lambda self: self._parse_truncate_table(), 1183 TokenType.UNCACHE: lambda self: self._parse_uncache(), 1184 TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True), 1185 TokenType.UPDATE: lambda self: self._parse_update(), 1186 TokenType.USE: lambda self: self._parse_use(), 1187 TokenType.SEMICOLON: lambda self: exp.Semicolon(), 1188 } 1189 1190 UNARY_PARSERS: t.ClassVar = { 1191 TokenType.PLUS: lambda self: self._parse_unary(), # Unary + is handled as a no-op 1192 TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())), 1193 TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())), 1194 TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())), 1195 TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())), 1196 TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())), 1197 } 1198 1199 STRING_PARSERS: t.ClassVar = { 1200 TokenType.HEREDOC_STRING: lambda self, token: self.expression( 1201 exp.RawString(this=token.text), token 1202 ), 1203 TokenType.NATIONAL_STRING: lambda self, token: self.expression( 1204 exp.National(this=token.text), token 1205 ), 1206 TokenType.RAW_STRING: lambda self, token: self.expression( 1207 exp.RawString(this=token.text), token 1208 ), 1209 TokenType.STRING: lambda self, token: self.expression( 1210 exp.Literal(this=token.text, is_string=True), token 1211 ), 1212 TokenType.UNICODE_STRING: lambda self, token: self.expression( 1213 exp.UnicodeString( 1214 this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string() 1215 ), 1216 token, 1217 ), 1218 } 1219 1220 NUMERIC_PARSERS: t.ClassVar = { 1221 TokenType.BIT_STRING: lambda self, token: self.expression( 1222 exp.BitString(this=token.text), token 1223 ), 1224 TokenType.BYTE_STRING: lambda self, token: self.expression( 1225 exp.ByteString( 1226 this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None 1227 ), 1228 token, 1229 ), 1230 TokenType.HEX_STRING: lambda self, token: self.expression( 1231 exp.HexString( 1232 this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None 1233 ), 1234 token, 1235 ), 1236 TokenType.NUMBER: lambda self, token: self.expression( 1237 exp.Literal(this=token.text, is_string=False), token 1238 ), 1239 } 1240 1241 PRIMARY_PARSERS: t.ClassVar = { 1242 **STRING_PARSERS, 1243 **NUMERIC_PARSERS, 1244 TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token), 1245 TokenType.NULL: lambda self, _: self.expression(exp.Null()), 1246 TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)), 1247 TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)), 1248 TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(), 1249 TokenType.STAR: lambda self, _: self._parse_star_ops(), 1250 } 1251 1252 PLACEHOLDER_PARSERS: t.ClassVar = { 1253 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()), 1254 TokenType.PARAMETER: lambda self: self._parse_parameter(), 1255 TokenType.COLON: lambda self: ( 1256 self.expression(exp.Placeholder(this=self._prev.text)) 1257 if self._match_set(self.COLON_PLACEHOLDER_TOKENS) 1258 else None 1259 ), 1260 } 1261 1262 RANGE_PARSERS: t.ClassVar = { 1263 TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll), 1264 TokenType.BETWEEN: lambda self, this: self._parse_between(this), 1265 TokenType.GLOB: binary_range_parser(exp.Glob), 1266 TokenType.ILIKE: binary_range_parser(exp.ILike), 1267 TokenType.IN: lambda self, this: self._parse_in(this), 1268 TokenType.IRLIKE: binary_range_parser(exp.RegexpILike), 1269 TokenType.IS: lambda self, this: self._parse_is(this), 1270 TokenType.LIKE: binary_range_parser(exp.Like), 1271 TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy), 1272 TokenType.OVERLAPS: binary_range_parser(exp.Overlaps), 1273 TokenType.RLIKE: binary_range_parser(exp.RegexpLike), 1274 TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo), 1275 TokenType.FOR: lambda self, this: self._parse_comprehension(this), 1276 TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys), 1277 TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys), 1278 TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath), 1279 TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists), 1280 TokenType.ADJACENT: binary_range_parser(exp.Adjacent), 1281 TokenType.OPERATOR: lambda self, this: self._parse_operator(this), 1282 TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft), 1283 TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight), 1284 } 1285 1286 PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = { 1287 "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query), 1288 "AS": lambda self, query: self._build_pipe_cte( 1289 query, [exp.Star()], self._parse_table_alias() 1290 ), 1291 "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False), 1292 "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query), 1293 "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query), 1294 "ORDER BY": lambda self, query: query.order_by( 1295 self._parse_order(), append=False, copy=False 1296 ), 1297 "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1298 "SELECT": lambda self, query: self._parse_pipe_syntax_select(query), 1299 "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query), 1300 "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1301 "WHERE": lambda self, query: query.where(self._parse_where(), copy=False), 1302 } 1303 1304 PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1305 "ALLOWED_VALUES": lambda self: self.expression( 1306 exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary)) 1307 ), 1308 "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty), 1309 "AUTO": lambda self: self._parse_auto_property(), 1310 "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty), 1311 "BACKUP": lambda self: self.expression( 1312 exp.BackupProperty(this=self._parse_var(any_token=True)) 1313 ), 1314 "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(), 1315 "CALLED": lambda self: self._parse_called_on_null_input_property(), 1316 "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs), 1317 "CHECKSUM": lambda self: self._parse_checksum(), 1318 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1319 "CLUSTERED": lambda self: self._parse_clustered_by(), 1320 "COLLATE": lambda self, **kwargs: self._parse_property_assignment( 1321 exp.CollateProperty, **kwargs 1322 ), 1323 "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty), 1324 "CONTAINS": lambda self: self._parse_contains_property(), 1325 "COPY": lambda self: self._parse_copy_property(), 1326 "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs), 1327 "DATA_DELETION": lambda self: self._parse_data_deletion_property(), 1328 "DEFINER": lambda self: self._parse_definer(), 1329 "DETERMINISTIC": lambda self: self.expression( 1330 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1331 ), 1332 "DISTRIBUTED": lambda self: self._parse_distributed_property(), 1333 "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty), 1334 "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()), 1335 "DISTKEY": lambda self: self._parse_distkey(), 1336 "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty), 1337 "EMPTY": lambda self: self.expression(exp.EmptyProperty()), 1338 "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty), 1339 "ENVIRONMENT": lambda self: self.expression( 1340 exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment)) 1341 ), 1342 "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty), 1343 "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty), 1344 "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()), 1345 "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs), 1346 "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1347 "FREESPACE": lambda self: self._parse_freespace(), 1348 "GLOBAL": lambda self: self.expression(exp.GlobalProperty()), 1349 "HEAP": lambda self: self.expression(exp.HeapProperty()), 1350 "ICEBERG": lambda self: self.expression(exp.IcebergProperty()), 1351 "IMMUTABLE": lambda self: self.expression( 1352 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1353 ), 1354 "INHERITS": lambda self: self.expression( 1355 exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table)) 1356 ), 1357 "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())), 1358 "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs), 1359 "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty), 1360 "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"), 1361 "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"), 1362 "LIKE": lambda self: self._parse_create_like(), 1363 "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty), 1364 "LOCK": lambda self: self._parse_locking(), 1365 "LOCKING": lambda self: self._parse_locking(), 1366 "LOG": lambda self, **kwargs: self._parse_log(**kwargs), 1367 "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()), 1368 "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs), 1369 "MODIFIES": lambda self: self._parse_modifies_property(), 1370 "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)), 1371 "NO": lambda self: self._parse_no_property(), 1372 "ON": lambda self: self._parse_on_property(), 1373 "ORDER BY": lambda self: self._parse_order(skip_order_token=True), 1374 "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())), 1375 "PARTITION": lambda self: self._parse_partitioned_of(), 1376 "PARTITION BY": lambda self: self._parse_partitioned_by(), 1377 "PARTITIONED BY": lambda self: self._parse_partitioned_by(), 1378 "PARTITIONED_BY": lambda self: self._parse_partitioned_by(), 1379 "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True), 1380 "RANGE": lambda self: self._parse_dict_range(this="RANGE"), 1381 "READS": lambda self: self._parse_reads_property(), 1382 "REMOTE": lambda self: self._parse_remote_with_connection(), 1383 "RETURNS": lambda self: self._parse_returns(), 1384 "STRICT": lambda self: self.expression(exp.StrictProperty()), 1385 "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()), 1386 "ROW": lambda self: self._parse_row(), 1387 "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty), 1388 "SAMPLE": lambda self: self.expression( 1389 exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise()) 1390 ), 1391 "SECURE": lambda self: self.expression(exp.SecureProperty()), 1392 "SECURITY": lambda self: self._parse_sql_security(), 1393 "SQL SECURITY": lambda self: self._parse_sql_security(), 1394 "SET": lambda self: self.expression(exp.SetProperty(multi=False)), 1395 "SETTINGS": lambda self: self._parse_settings_property(), 1396 "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty), 1397 "SORTKEY": lambda self: self._parse_sortkey(), 1398 "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"), 1399 "STABLE": lambda self: self.expression( 1400 exp.StabilityProperty(this=exp.Literal.string("STABLE")) 1401 ), 1402 "STORED": lambda self: self._parse_stored(), 1403 "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(), 1404 "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(), 1405 "TEMP": lambda self: self.expression(exp.TemporaryProperty()), 1406 "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()), 1407 "TO": lambda self: self._parse_to_table(), 1408 "TRANSIENT": lambda self: self.expression(exp.TransientProperty()), 1409 "TRANSFORM": lambda self: self.expression( 1410 exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression)) 1411 ), 1412 "TTL": lambda self: self._parse_ttl(), 1413 "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1414 "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()), 1415 "VOLATILE": lambda self: self._parse_volatile_property(), 1416 "WITH": lambda self: self._parse_with_property(), 1417 } 1418 1419 CONSTRAINT_PARSERS: t.ClassVar = { 1420 "AUTOINCREMENT": lambda self: self._parse_auto_increment(), 1421 "AUTO_INCREMENT": lambda self: self._parse_auto_increment(), 1422 "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)), 1423 "CHECK": lambda self: self._parse_check_constraint(), 1424 "COLLATE": lambda self: self.expression( 1425 exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column()) 1426 ), 1427 "COMMENT": lambda self: self.expression( 1428 exp.CommentColumnConstraint(this=self._parse_string()) 1429 ), 1430 "COMPRESS": lambda self: self._parse_compress(), 1431 "CLUSTERED": lambda self: self.expression( 1432 exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1433 ), 1434 "NONCLUSTERED": lambda self: self.expression( 1435 exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1436 ), 1437 "DEFAULT": lambda self: self.expression( 1438 exp.DefaultColumnConstraint(this=self._parse_bitwise()) 1439 ), 1440 "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())), 1441 "EPHEMERAL": lambda self: self.expression( 1442 exp.EphemeralColumnConstraint(this=self._parse_bitwise()) 1443 ), 1444 "EXCLUDE": lambda self: self.expression( 1445 exp.ExcludeColumnConstraint(this=self._parse_index_params()) 1446 ), 1447 "FOREIGN KEY": lambda self: self._parse_foreign_key(), 1448 "FORMAT": lambda self: self.expression( 1449 exp.DateFormatColumnConstraint(this=self._parse_var_or_string()) 1450 ), 1451 "GENERATED": lambda self: self._parse_generated_as_identity(), 1452 "IDENTITY": lambda self: self._parse_auto_increment(), 1453 "INLINE": lambda self: self._parse_inline(), 1454 "LIKE": lambda self: self._parse_create_like(), 1455 "NOT": lambda self: self._parse_not_constraint(), 1456 "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)), 1457 "ON": lambda self: ( 1458 ( 1459 self._match(TokenType.UPDATE) 1460 and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function())) 1461 ) 1462 or self.expression(exp.OnProperty(this=self._parse_id_var())) 1463 ), 1464 "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())), 1465 "PERIOD": lambda self: self._parse_period_for_system_time(), 1466 "PRIMARY KEY": lambda self: self._parse_primary_key(), 1467 "REFERENCES": lambda self: self._parse_references(match=False), 1468 "TITLE": lambda self: self.expression( 1469 exp.TitleColumnConstraint(this=self._parse_var_or_string()) 1470 ), 1471 "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])), 1472 "UNIQUE": lambda self: self._parse_unique(), 1473 "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()), 1474 "WITH": lambda self: self.expression( 1475 exp.Properties(expressions=self._parse_wrapped_properties()) 1476 ), 1477 "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1478 "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1479 } 1480 1481 def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None: 1482 if not self._match(TokenType.L_PAREN, advance=False): 1483 # Partitioning by bucket or truncate follows the syntax: 1484 # PARTITION BY (BUCKET(..) | TRUNCATE(..)) 1485 # If we don't have parenthesis after each keyword, we should instead parse this as an identifier 1486 self._retreat(self._index - 1) 1487 return None 1488 1489 klass = ( 1490 exp.PartitionedByBucket 1491 if self._prev.text.upper() == "BUCKET" 1492 else exp.PartitionByTruncate 1493 ) 1494 1495 args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column()) 1496 this, expression = seq_get(args, 0), seq_get(args, 1) 1497 1498 if isinstance(this, exp.Literal): 1499 # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order 1500 # - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)` 1501 # - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)` 1502 # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)` 1503 # 1504 # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning 1505 # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties 1506 this, expression = expression, this 1507 1508 return self.expression(klass(this=this, expression=expression)) 1509 1510 ALTER_PARSERS: t.ClassVar = { 1511 "ADD": lambda self: self._parse_alter_table_add(), 1512 "AS": lambda self: self._parse_select(), 1513 "ALTER": lambda self: self._parse_alter_table_alter(), 1514 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1515 "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())), 1516 "DROP": lambda self: self._parse_alter_table_drop(), 1517 "RENAME": lambda self: self._parse_alter_table_rename(), 1518 "SET": lambda self: self._parse_alter_table_set(), 1519 "SWAP": lambda self: self.expression( 1520 exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True)) 1521 ), 1522 } 1523 1524 ALTER_ALTER_PARSERS: t.ClassVar = { 1525 "DISTKEY": lambda self: self._parse_alter_diststyle(), 1526 "DISTSTYLE": lambda self: self._parse_alter_diststyle(), 1527 "SORTKEY": lambda self: self._parse_alter_sortkey(), 1528 "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True), 1529 } 1530 1531 SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = { 1532 "CHECK", 1533 "EXCLUDE", 1534 "FOREIGN KEY", 1535 "LIKE", 1536 "PERIOD", 1537 "PRIMARY KEY", 1538 "UNIQUE", 1539 "BUCKET", 1540 "TRUNCATE", 1541 } 1542 1543 NO_PAREN_FUNCTION_PARSERS: t.ClassVar = { 1544 "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())), 1545 "CASE": lambda self: self._parse_case(), 1546 "CONNECT_BY_ROOT": lambda self: self.expression( 1547 exp.ConnectByRoot(this=self._parse_column()) 1548 ), 1549 "IF": lambda self: self._parse_if(), 1550 } 1551 1552 INVALID_FUNC_NAME_TOKENS: t.ClassVar = { 1553 TokenType.IDENTIFIER, 1554 TokenType.STRING, 1555 } 1556 1557 FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"} 1558 1559 KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice) 1560 1561 FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1562 **{ 1563 name: lambda self: self._parse_distinct_arg_function(exp.ArgMax) 1564 for name in exp.ArgMax.sql_names() 1565 }, 1566 **{ 1567 name: lambda self: self._parse_distinct_arg_function(exp.ArgMin) 1568 for name in exp.ArgMin.sql_names() 1569 }, 1570 "CAST": lambda self: self._parse_cast(self.STRICT_CAST), 1571 "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), 1572 "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), 1573 "CHAR": lambda self: self._parse_char(), 1574 "CHR": lambda self: self._parse_char(), 1575 "DECODE": lambda self: self._parse_decode(), 1576 "EXTRACT": lambda self: self._parse_extract(), 1577 "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), 1578 "GAP_FILL": lambda self: self._parse_gap_fill(), 1579 "INITCAP": lambda self: self._parse_initcap(), 1580 "JSON_OBJECT": lambda self: self._parse_json_object(), 1581 "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True), 1582 "JSON_TABLE": lambda self: self._parse_json_table(), 1583 "MATCH": lambda self: self._parse_match_against(), 1584 "NORMALIZE": lambda self: self._parse_normalize(), 1585 "OPENJSON": lambda self: self._parse_open_json(), 1586 "OVERLAY": lambda self: self._parse_overlay(), 1587 "POSITION": lambda self: self._parse_position(), 1588 "SAFE_CAST": lambda self: self._parse_cast(False, safe=True), 1589 "STRING_AGG": lambda self: self._parse_string_agg(), 1590 "SUBSTRING": lambda self: self._parse_substring(), 1591 "TRIM": lambda self: self._parse_trim(), 1592 "TRY_CAST": lambda self: self._parse_cast(False, safe=True), 1593 "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True), 1594 "XMLELEMENT": lambda self: self._parse_xml_element(), 1595 "XMLTABLE": lambda self: self._parse_xml_table(), 1596 } 1597 1598 QUERY_MODIFIER_PARSERS: t.ClassVar = { 1599 TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()), 1600 TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()), 1601 TokenType.WHERE: lambda self: ("where", self._parse_where()), 1602 TokenType.GROUP_BY: lambda self: ("group", self._parse_group()), 1603 TokenType.HAVING: lambda self: ("having", self._parse_having()), 1604 TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()), 1605 TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()), 1606 TokenType.ORDER_BY: lambda self: ("order", self._parse_order()), 1607 TokenType.LIMIT: lambda self: ("limit", self._parse_limit()), 1608 TokenType.FETCH: lambda self: ("limit", self._parse_limit()), 1609 TokenType.OFFSET: lambda self: ("offset", self._parse_offset()), 1610 TokenType.FOR: lambda self: ("locks", self._parse_locks()), 1611 TokenType.LOCK: lambda self: ("locks", self._parse_locks()), 1612 TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1613 TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1614 TokenType.CLUSTER_BY: lambda self: ( 1615 "cluster", 1616 self._parse_cluster(), 1617 ), 1618 TokenType.DISTRIBUTE_BY: lambda self: ( 1619 "distribute", 1620 self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), 1621 ), 1622 TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)), 1623 TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)), 1624 } 1625 QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS) 1626 1627 SET_PARSERS: t.ClassVar = { 1628 "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"), 1629 "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"), 1630 "SESSION": lambda self: self._parse_set_item_assignment("SESSION"), 1631 "TRANSACTION": lambda self: self._parse_set_transaction(), 1632 } 1633 1634 SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {} 1635 1636 TYPE_LITERAL_PARSERS: t.ClassVar = { 1637 exp.DType.JSON: lambda self, this, _: self.expression(exp.ParseJSON(this=this)), 1638 } 1639 1640 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1641 1642 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1643 1644 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1645 1646 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1647 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1648 "ISOLATION": ( 1649 ("LEVEL", "REPEATABLE", "READ"), 1650 ("LEVEL", "READ", "COMMITTED"), 1651 ("LEVEL", "READ", "UNCOMITTED"), 1652 ("LEVEL", "SERIALIZABLE"), 1653 ), 1654 "READ": ("WRITE", "ONLY"), 1655 } 1656 1657 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1658 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1659 "DO": ("NOTHING", "UPDATE"), 1660 } 1661 1662 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1663 "INSTEAD": (("OF",),), 1664 "BEFORE": tuple(), 1665 "AFTER": tuple(), 1666 } 1667 1668 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1669 "NOT": (("DEFERRABLE",),), 1670 "DEFERRABLE": tuple(), 1671 } 1672 1673 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1674 "SCALE": ("EXTEND", "NOEXTEND"), 1675 "SHARD": ("EXTEND", "NOEXTEND"), 1676 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1677 **dict.fromkeys( 1678 ( 1679 "SESSION", 1680 "GLOBAL", 1681 "KEEP", 1682 "NOKEEP", 1683 "ORDER", 1684 "NOORDER", 1685 "NOCACHE", 1686 "CYCLE", 1687 "NOCYCLE", 1688 "NOMINVALUE", 1689 "NOMAXVALUE", 1690 "NOSCALE", 1691 "NOSHARD", 1692 ), 1693 tuple(), 1694 ), 1695 } 1696 1697 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1698 1699 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1700 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1701 ) 1702 1703 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1704 1705 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1706 "TYPE": ("EVOLUTION",), 1707 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1708 } 1709 1710 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1711 1712 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1713 ("CALLER", "SELF", "OWNER"), tuple() 1714 ) 1715 1716 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1717 "NOT": ("ENFORCED",), 1718 "MATCH": ( 1719 "FULL", 1720 "PARTIAL", 1721 "SIMPLE", 1722 ), 1723 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1724 "USING": ( 1725 "BTREE", 1726 "HASH", 1727 ), 1728 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1729 } 1730 1731 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1732 "NO": ("OTHERS",), 1733 "CURRENT": ("ROW",), 1734 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1735 } 1736 1737 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1738 1739 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1740 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1741 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1742 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1743 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1744 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1745 ("FOR", "VERSION"): "VERSION", 1746 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1747 ("VERSION", "AS", "OF"): "VERSION", 1748 } 1749 1750 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1751 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1752 1753 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1754 1755 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1756 1757 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1758 1759 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1760 1761 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1762 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1763 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1764 1765 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1766 1767 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1768 1769 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1770 TokenType.CONSTRAINT, 1771 TokenType.FOREIGN_KEY, 1772 TokenType.INDEX, 1773 TokenType.KEY, 1774 TokenType.PRIMARY_KEY, 1775 TokenType.UNIQUE, 1776 } 1777 1778 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1779 1780 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1781 1782 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1783 1784 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1785 "FILE_FORMAT", 1786 "COPY_OPTIONS", 1787 "FORMAT_OPTIONS", 1788 "CREDENTIAL", 1789 } 1790 1791 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1792 1793 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1794 1795 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1796 1797 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1798 1799 # The style options for the DESCRIBE statement 1800 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1801 1802 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1803 1804 # The style options for the ANALYZE statement 1805 ANALYZE_STYLES: t.ClassVar = { 1806 "BUFFER_USAGE_LIMIT", 1807 "FULL", 1808 "LOCAL", 1809 "NO_WRITE_TO_BINLOG", 1810 "SAMPLE", 1811 "SKIP_LOCKED", 1812 "VERBOSE", 1813 } 1814 1815 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1816 "ALL": lambda self: self._parse_analyze_columns(), 1817 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1818 "DELETE": lambda self: self._parse_analyze_delete(), 1819 "DROP": lambda self: self._parse_analyze_histogram(), 1820 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1821 "LIST": lambda self: self._parse_analyze_list(), 1822 "PREDICATE": lambda self: self._parse_analyze_columns(), 1823 "UPDATE": lambda self: self._parse_analyze_histogram(), 1824 "VALIDATE": lambda self: self._parse_analyze_validate(), 1825 } 1826 1827 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1828 1829 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1830 1831 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1832 1833 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1834 1835 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1836 1837 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1838 1839 STRICT_CAST: t.ClassVar = True 1840 1841 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1842 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1843 # Whether an UNPIVOT outputs its value column(s) before the name column 1844 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1845 # Controls when an aggregation's name is included in a pivoted column's name: 1846 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1847 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1848 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1849 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1850 1851 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1852 1853 # Whether the table sample clause expects CSV syntax 1854 TABLESAMPLE_CSV: t.ClassVar = False 1855 1856 # The default method used for table sampling 1857 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1858 1859 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1860 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1861 1862 # Whether the TRIM function expects the characters to trim as its first argument 1863 TRIM_PATTERN_FIRST: t.ClassVar = False 1864 1865 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1866 STRING_ALIASES: t.ClassVar = False 1867 1868 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1869 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1870 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"} 1871 1872 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1873 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1874 1875 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1876 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1877 1878 # Whether the `:` operator is used to extract a value from a VARIANT column 1879 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1880 1881 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1882 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1883 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1884 1885 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1886 # If this is True and '(' is not found, the keyword will be treated as an identifier 1887 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1888 1889 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1890 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1891 1892 # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery) 1893 SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False 1894 1895 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1896 INTERVAL_SPANS: t.ClassVar = True 1897 1898 # Whether a PARTITION clause can follow a table reference 1899 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1900 1901 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1902 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1903 1904 # Whether the 'AS' keyword is optional in the CTE definition syntax 1905 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1906 1907 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1908 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1909 1910 # Whether Alter statements are allowed to contain Partition specifications 1911 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1912 1913 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1914 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1915 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1916 # as BigQuery, where all joins have the same precedence. 1917 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1918 1919 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1920 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1921 1922 # Whether map literals support arbitrary expressions as keys. 1923 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1924 # When False, keys are typically restricted to identifiers. 1925 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1926 1927 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1928 # is true for Snowflake but not for BigQuery which can also process strings 1929 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1930 1931 # Dialects like Databricks support JOINS without join criteria 1932 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 1933 ADD_JOIN_ON_TRUE: t.ClassVar = False 1934 1935 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 1936 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 1937 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 1938 1939 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 1940 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 1941 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 1942 1943 # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause, 1944 # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake) 1945 SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False 1946 1947 # Type names that denote a different type when they're quoted, so quoting has to be 1948 # preserved instead of resolving them into the built-in type of the same name. These 1949 # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR 1950 QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set() 1951 1952 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 1953 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 1954 1955 def __init__( 1956 self, 1957 error_level: ErrorLevel | None = None, 1958 error_message_context: int = 100, 1959 max_errors: int = 3, 1960 max_nodes: int = -1, 1961 dialect: DialectType = None, 1962 ): 1963 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 1964 self.error_message_context: int = error_message_context 1965 self.max_errors: int = max_errors 1966 self.max_nodes: int = max_nodes 1967 self.dialect: t.Any = _resolve_dialect(dialect) 1968 self.sql: str = "" 1969 self.errors: list[ParseError] = [] 1970 self._tokens: list[Token] = [] 1971 self._tokens_size: i64 = 0 1972 self._index: i64 = 0 1973 self._curr: Token = SENTINEL_NONE 1974 self._next: Token = SENTINEL_NONE 1975 self._prev: Token = SENTINEL_NONE 1976 self._prev_comments: list[str] = [] 1977 self._pipe_cte_counter: int = 0 1978 self._chunks: list[list[Token]] = [] 1979 self._chunk_index: i64 = 0 1980 self._node_count: int = 0 1981 1982 def reset(self) -> None: 1983 self.sql = "" 1984 self.errors = [] 1985 self._tokens = [] 1986 self._tokens_size = 0 1987 self._index = 0 1988 self._curr = SENTINEL_NONE 1989 self._next = SENTINEL_NONE 1990 self._prev = SENTINEL_NONE 1991 self._prev_comments = [] 1992 self._pipe_cte_counter = 0 1993 self._chunks = [] 1994 self._chunk_index = 0 1995 self._node_count = 0 1996 1997 def _advance(self, times: i64 = 1) -> None: 1998 index = self._index + times 1999 self._index = index 2000 tokens = self._tokens 2001 size = self._tokens_size 2002 self._curr = tokens[index] if index < size else SENTINEL_NONE 2003 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 2004 2005 if index > 0: 2006 prev = tokens[index - 1] 2007 self._prev = prev 2008 self._prev_comments = prev.comments 2009 else: 2010 self._prev = SENTINEL_NONE 2011 self._prev_comments = [] 2012 2013 def _advance_chunk(self) -> None: 2014 self._index = -1 2015 self._tokens = self._chunks[self._chunk_index] 2016 self._tokens_size = i64(len(self._tokens)) 2017 self._chunk_index += 1 2018 self._advance() 2019 2020 def _retreat(self, index: i64) -> None: 2021 if index != self._index: 2022 self._advance(index - self._index) 2023 2024 def _add_comments(self, expression: exp.Expr | None) -> None: 2025 if expression and self._prev_comments: 2026 expression.add_comments(self._prev_comments) 2027 self._prev_comments = [] 2028 2029 def _match( 2030 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2031 ) -> bool: 2032 if self._curr.token_type == token_type: 2033 if advance: 2034 self._advance() 2035 self._add_comments(expression) 2036 return True 2037 return False 2038 2039 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2040 if self._curr.token_type in types: 2041 if advance: 2042 self._advance() 2043 return True 2044 return False 2045 2046 def _match_pair( 2047 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2048 ) -> bool: 2049 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2050 if advance: 2051 self._advance(2) 2052 return True 2053 return False 2054 2055 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2056 if ( 2057 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2058 and self._curr.text.upper() in texts 2059 ): 2060 if advance: 2061 self._advance() 2062 return True 2063 return False 2064 2065 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2066 index = self._index 2067 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2068 for text in texts: 2069 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2070 self._advance() 2071 else: 2072 self._retreat(index) 2073 return False 2074 2075 if not advance: 2076 self._retreat(index) 2077 2078 return True 2079 2080 def _is_connected(self) -> bool: 2081 prev = self._prev 2082 curr = self._curr 2083 return bool(prev and curr and prev.end + 1 == curr.start) 2084 2085 def _find_sql(self, start: Token, end: Token) -> str: 2086 return self.sql[start.start : end.end + 1] 2087 2088 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2089 token = token or self._curr or self._prev or Token.string("") 2090 formatted_sql, start_context, highlight, end_context = highlight_sql( 2091 sql=self.sql, 2092 positions=[(token.start, token.end)], 2093 context_length=self.error_message_context, 2094 ) 2095 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2096 2097 error = ParseError.new( 2098 formatted_message, 2099 description=message, 2100 line=token.line, 2101 col=token.col, 2102 start_context=start_context, 2103 highlight=highlight, 2104 end_context=end_context, 2105 ) 2106 2107 if self.error_level == ErrorLevel.IMMEDIATE: 2108 raise error 2109 2110 self.errors.append(error) 2111 2112 def validate_expression(self, expression: E, args: list | None = None) -> E: 2113 if self.max_nodes > -1: 2114 self._node_count += 1 2115 if self._node_count > self.max_nodes: 2116 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2117 if self.error_level != ErrorLevel.IGNORE: 2118 for error_message in expression.error_messages(args): 2119 self.raise_error(error_message) 2120 return expression 2121 2122 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2123 index = self._index 2124 error_level = self.error_level 2125 this: T | None = None 2126 2127 self.error_level = ErrorLevel.IMMEDIATE 2128 try: 2129 this = parse_method() 2130 except ParseError: 2131 this = None 2132 finally: 2133 if not this or retreat: 2134 self._retreat(index) 2135 self.error_level = error_level 2136 2137 return this 2138 2139 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2140 """ 2141 Parses a list of tokens and returns a list of syntax trees, one tree 2142 per parsed SQL statement. 2143 2144 Args: 2145 raw_tokens: The list of tokens. 2146 sql: The original SQL string. 2147 2148 Returns: 2149 The list of the produced syntax trees. 2150 """ 2151 return self._parse( 2152 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2153 ) 2154 2155 def parse_into( 2156 self, 2157 expression_types: exp.IntoType, 2158 raw_tokens: list[Token], 2159 sql: str | None = None, 2160 ) -> list[exp.Expr | None]: 2161 """ 2162 Parses a list of tokens into a given Expr type. If a collection of Expr 2163 types is given instead, this method will try to parse the token list into each one 2164 of them, stopping at the first for which the parsing succeeds. 2165 2166 Args: 2167 expression_types: The expression type(s) to try and parse the token list into. 2168 raw_tokens: The list of tokens. 2169 sql: The original SQL string, used to produce helpful debug messages. 2170 2171 Returns: 2172 The target Expr. 2173 """ 2174 errors = [] 2175 for expression_type in ensure_list(expression_types): 2176 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2177 if not parser: 2178 raise TypeError(f"No parser registered for {expression_type}") 2179 2180 try: 2181 return self._parse(parser, raw_tokens, sql) 2182 except ParseError as e: 2183 e.errors[0]["into_expression"] = expression_type 2184 errors.append(e) 2185 2186 raise ParseError( 2187 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2188 errors=merge_errors(errors), 2189 ) from errors[-1] 2190 2191 def check_errors(self) -> None: 2192 """Logs or raises any found errors, depending on the chosen error level setting.""" 2193 if self.error_level == ErrorLevel.WARN: 2194 for error in self.errors: 2195 logger.error(str(error)) 2196 elif self.error_level == ErrorLevel.RAISE and self.errors: 2197 raise ParseError( 2198 concat_messages(self.errors, self.max_errors), 2199 errors=merge_errors(self.errors), 2200 ) 2201 2202 def expression( 2203 self, 2204 instance: E, 2205 token: Token | None = None, 2206 comments: list[str] | None = None, 2207 ) -> E: 2208 if token: 2209 instance.update_positions(token) 2210 instance.add_comments(comments) if comments else self._add_comments(instance) 2211 if not instance.is_primitive: 2212 instance = self.validate_expression(instance) 2213 return instance 2214 2215 def _parse_batch_statements( 2216 self, 2217 parse_method: t.Callable[[Parser], exp.Expr | None], 2218 sep_first_statement: bool = True, 2219 ) -> list[exp.Expr | None]: 2220 expressions = [] 2221 2222 # Chunkification binds if/while statements with the first statement of the body 2223 if sep_first_statement: 2224 self._match(TokenType.BEGIN) 2225 expressions.append(parse_method(self)) 2226 2227 chunks_length = len(self._chunks) 2228 while self._chunk_index < chunks_length: 2229 self._advance_chunk() 2230 2231 if self._match(TokenType.ELSE, advance=False): 2232 return expressions 2233 2234 if expressions and not self._next and self._match(TokenType.END): 2235 expressions.append(exp.EndStatement()) 2236 continue 2237 2238 expressions.append(parse_method(self)) 2239 2240 if self._index < self._tokens_size: 2241 self.raise_error("Invalid expression / Unexpected token") 2242 2243 self.check_errors() 2244 2245 return expressions 2246 2247 def _parse( 2248 self, 2249 parse_method: t.Callable[[Parser], exp.Expr | None], 2250 raw_tokens: list[Token], 2251 sql: str | None = None, 2252 ) -> list[exp.Expr | None]: 2253 self.reset() 2254 self.sql = sql or "" 2255 2256 total = len(raw_tokens) 2257 chunks: list[list[Token]] = [[]] 2258 2259 for i, token in enumerate(raw_tokens): 2260 if token.token_type == TokenType.SEMICOLON: 2261 if token.comments: 2262 chunks.append([token]) 2263 2264 if i < total - 1: 2265 chunks.append([]) 2266 else: 2267 chunks[-1].append(token) 2268 2269 self._chunks = chunks 2270 2271 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2272 2273 def _warn_unsupported(self) -> None: 2274 if self._tokens_size <= 1: 2275 return 2276 2277 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2278 # interested in emitting a warning for the one being currently processed. 2279 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2280 2281 logger.warning( 2282 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2283 ) 2284 2285 def _parse_command(self) -> exp.Command: 2286 self._warn_unsupported() 2287 comments = self._prev_comments 2288 return self.expression( 2289 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2290 comments=comments, 2291 ) 2292 2293 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2294 start = self._prev 2295 exists = self._parse_exists() if allow_exists else None 2296 2297 self._match(TokenType.ON) 2298 2299 materialized = self._match_text_seq("MATERIALIZED") 2300 kind = self._match_set(self.CREATABLES) and self._prev 2301 if not kind: 2302 return self._parse_as_command(start) 2303 2304 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2305 this = self._parse_user_defined_function(kind=kind.token_type) 2306 elif kind.token_type == TokenType.TABLE: 2307 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2308 elif kind.token_type == TokenType.COLUMN: 2309 this = self._parse_column() 2310 else: 2311 this = self._parse_table_parts(schema=True) 2312 2313 self._match(TokenType.IS) 2314 2315 return self.expression( 2316 exp.Comment( 2317 this=this, 2318 kind=kind.text, 2319 expression=self._parse_string(), 2320 exists=exists, 2321 materialized=materialized, 2322 ) 2323 ) 2324 2325 def _parse_to_table( 2326 self, 2327 ) -> exp.ToTableProperty: 2328 table = self._parse_table_parts(schema=True) 2329 return self.expression(exp.ToTableProperty(this=table)) 2330 2331 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2332 def _parse_ttl(self) -> exp.Expr: 2333 def _parse_ttl_action() -> exp.Expr | None: 2334 this = self._parse_bitwise() 2335 2336 if self._match_text_seq("DELETE"): 2337 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2338 if self._match_text_seq("RECOMPRESS"): 2339 return self.expression( 2340 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2341 ) 2342 if self._match_text_seq("TO", "DISK"): 2343 return self.expression( 2344 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2345 ) 2346 if self._match_text_seq("TO", "VOLUME"): 2347 return self.expression( 2348 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2349 ) 2350 2351 return this 2352 2353 expressions = self._parse_csv(_parse_ttl_action) 2354 where = self._parse_where() 2355 group = self._parse_group() 2356 2357 aggregates = None 2358 if group and self._match(TokenType.SET): 2359 aggregates = self._parse_csv(self._parse_set_item) 2360 2361 return self.expression( 2362 exp.MergeTreeTTL( 2363 expressions=expressions, where=where, group=group, aggregates=aggregates 2364 ) 2365 ) 2366 2367 def _parse_condition(self) -> exp.Expr | None: 2368 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2369 2370 def _parse_block(self) -> exp.Block: 2371 return self.expression( 2372 exp.Block( 2373 expressions=self._parse_batch_statements( 2374 parse_method=lambda self: self._parse_statement() 2375 ) 2376 ) 2377 ) 2378 2379 def _parse_whileblock(self) -> exp.WhileBlock: 2380 return self.expression( 2381 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2382 ) 2383 2384 def _parse_statement(self) -> exp.Expr | None: 2385 if not self._curr: 2386 return None 2387 2388 if self._match_set(self.STATEMENT_PARSERS): 2389 comments = self._prev_comments 2390 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2391 stmt.add_comments(comments, prepend=True) 2392 return stmt 2393 2394 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2395 return self._parse_command() 2396 2397 if self._match_text_seq("WHILE"): 2398 return self._parse_whileblock() 2399 2400 expression = self._parse_expression() 2401 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2402 2403 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2404 expression = self._parse_pipe_syntax_query(expression) 2405 2406 return self._parse_query_modifiers(expression) 2407 2408 def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command: 2409 start = self._prev 2410 temporary = self._match(TokenType.TEMPORARY) 2411 materialized = self._match_text_seq("MATERIALIZED") 2412 iceberg = self._match_text_seq("ICEBERG") 2413 2414 kind = self._match_set(self.CREATABLES) and self._prev.text.upper() 2415 if not kind or (iceberg and kind and kind != "TABLE"): 2416 return self._parse_as_command(start) 2417 2418 concurrently = self._match_text_seq("CONCURRENTLY") 2419 if_exists = exists or self._parse_exists() 2420 2421 tables: exp.Expr | list[exp.Expr] | None 2422 if kind == "COLUMN": 2423 tables = self._parse_column() 2424 elif kind in ("TABLE", "VIEW"): 2425 tables = self._parse_csv(lambda: self._parse_table_parts(schema=True)) 2426 else: 2427 tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2428 2429 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 2430 2431 if self._match(TokenType.L_PAREN, advance=False): 2432 expressions = self._parse_wrapped_csv(self._parse_types) 2433 else: 2434 expressions = None 2435 2436 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2437 2438 return self.expression( 2439 exp.Drop( 2440 exists=if_exists, 2441 tables=ensure_list(tables), 2442 expressions=expressions, 2443 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2444 temporary=temporary, 2445 materialized=materialized, 2446 cascade=cascade_or_restrict == "CASCADE", 2447 restrict=cascade_or_restrict == "RESTRICT", 2448 constraints=self._match_text_seq("CONSTRAINTS"), 2449 purge=self._match_text_seq("PURGE"), 2450 cluster=cluster, 2451 concurrently=concurrently, 2452 sync=self._match_text_seq("SYNC"), 2453 iceberg=iceberg, 2454 force=self._match_text_seq("FORCE"), 2455 ) 2456 ) 2457 2458 def _parse_exists(self, not_: bool = False) -> bool | None: 2459 return ( 2460 self._match_text_seq("IF") 2461 and (not not_ or self._match(TokenType.NOT)) 2462 and self._match(TokenType.EXISTS) 2463 ) 2464 2465 def _parse_create(self) -> exp.Create | exp.Command: 2466 # Note: this can't be None because we've matched a statement parser 2467 start = self._prev 2468 2469 replace = ( 2470 start.token_type == TokenType.REPLACE 2471 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2472 or self._match_pair(TokenType.OR, TokenType.ALTER) 2473 ) 2474 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2475 2476 unique = self._match(TokenType.UNIQUE) 2477 2478 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2479 clustered = True 2480 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2481 "COLUMNSTORE" 2482 ): 2483 clustered = False 2484 else: 2485 clustered = None 2486 2487 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2488 self._advance() 2489 2490 properties = None 2491 create_token = self._match_set(self.CREATABLES) and self._prev 2492 2493 if not create_token: 2494 # exp.Properties.Location.POST_CREATE 2495 properties = self._parse_properties() 2496 create_token = self._match_set(self.CREATABLES) and self._prev 2497 2498 if not properties or not create_token: 2499 return self._parse_as_command(start) 2500 2501 create_token_type = t.cast(Token, create_token).token_type 2502 2503 concurrently = self._match_text_seq("CONCURRENTLY") 2504 exists = self._parse_exists(not_=True) 2505 this = None 2506 expression: exp.Expr | None = None 2507 indexes = None 2508 no_schema_binding = None 2509 begin = None 2510 clone = None 2511 2512 def extend_props(temp_props: exp.Properties | None) -> None: 2513 nonlocal properties 2514 if properties and temp_props: 2515 properties.expressions.extend(temp_props.expressions) 2516 elif temp_props: 2517 properties = temp_props 2518 2519 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2520 this = self._parse_user_defined_function(kind=create_token_type) 2521 2522 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2523 extend_props(self._parse_properties()) 2524 2525 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2526 2527 if ( 2528 not expression 2529 and create_token_type == TokenType.FUNCTION 2530 and isinstance(this, exp.UserDefinedFunction) 2531 and this.args.get("wrapped") 2532 ): 2533 pre_table_index = self._index 2534 is_table = self._match(TokenType.TABLE) 2535 2536 expression = self._parse_expression() 2537 overload_mode = bool( 2538 expression 2539 and self._curr.token_type == TokenType.COMMA 2540 and self._next.token_type == TokenType.L_PAREN 2541 ) 2542 if not overload_mode: 2543 self._retreat(pre_table_index) 2544 is_table = False 2545 expression = None 2546 else: 2547 is_table = False 2548 overload_mode = False 2549 2550 extend_props(self._parse_function_properties()) 2551 2552 if not expression: 2553 if self._match(TokenType.COMMAND): 2554 expression = self._parse_as_command(self._prev) 2555 else: 2556 begin = self._match(TokenType.BEGIN) 2557 return_ = self._match_text_seq("RETURN") 2558 2559 if self._match(TokenType.STRING, advance=False): 2560 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2561 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2562 expression = self._parse_string() 2563 extend_props(self._parse_properties()) 2564 else: 2565 expression = ( 2566 self._parse_user_defined_function_expression() 2567 if create_token_type == TokenType.FUNCTION 2568 else self._parse_block() 2569 ) 2570 2571 if return_: 2572 expression = self.expression(exp.Return(this=expression)) 2573 2574 if overload_mode and expression: 2575 expression = self._parse_macro_overloads( 2576 t.cast(exp.UserDefinedFunction, this), expression, is_table 2577 ) 2578 elif create_token_type == TokenType.INDEX: 2579 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2580 if not self._match(TokenType.ON): 2581 index = self._parse_id_var() 2582 anonymous = False 2583 else: 2584 index = None 2585 anonymous = True 2586 2587 this = self._parse_index(index=index, anonymous=anonymous) 2588 elif ( 2589 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2590 ) or create_token_type == TokenType.TRIGGER: 2591 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2592 create_token = self._prev 2593 2594 trigger_name = self._parse_id_var() 2595 if not trigger_name: 2596 return self._parse_as_command(start) 2597 2598 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2599 timing = timing_var.this if timing_var else None 2600 if not timing: 2601 return self._parse_as_command(start) 2602 2603 events = self._parse_trigger_events() 2604 if not self._match(TokenType.ON): 2605 self.raise_error("Expected ON in trigger definition") 2606 2607 table = self._parse_table_parts() 2608 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2609 deferrable, initially = self._parse_trigger_deferrable() 2610 referencing = self._parse_trigger_referencing() 2611 for_each = self._parse_trigger_for_each() 2612 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2613 self._parse_disjunction, optional=True 2614 ) 2615 execute = self._parse_trigger_execute() 2616 2617 if execute is None: 2618 return self._parse_as_command(start) 2619 2620 trigger_props = self.expression( 2621 exp.TriggerProperties( 2622 table=table, 2623 timing=timing, 2624 events=events, 2625 execute=execute, 2626 constraint=is_constraint, 2627 referenced_table=referenced_table, 2628 deferrable=deferrable, 2629 initially=initially, 2630 referencing=referencing, 2631 for_each=for_each, 2632 when=when, 2633 ) 2634 ) 2635 2636 this = trigger_name 2637 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2638 elif create_token_type == TokenType.TYPE: 2639 this = self._parse_table_parts(schema=True) 2640 if not this or not self._match(TokenType.ALIAS): 2641 return self._parse_as_command(start) 2642 2643 if self._match(TokenType.ENUM): 2644 expression = exp.DataType( 2645 this=exp.DType.ENUM, 2646 expressions=self._parse_wrapped_csv(self._parse_string), 2647 ) 2648 elif self._match(TokenType.L_PAREN, advance=False): 2649 expression = self._parse_schema() 2650 else: 2651 return self._parse_as_command(start) 2652 elif create_token_type in self.DB_CREATABLES: 2653 table_parts = self._parse_table_parts( 2654 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2655 ) 2656 2657 # exp.Properties.Location.POST_NAME 2658 self._match(TokenType.COMMA) 2659 extend_props(self._parse_properties(before=True)) 2660 2661 this = self._parse_schema(this=table_parts) 2662 2663 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2664 extend_props(self._parse_properties()) 2665 2666 has_alias = self._match(TokenType.ALIAS) 2667 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2668 # exp.Properties.Location.POST_ALIAS 2669 extend_props(self._parse_properties()) 2670 2671 if create_token_type == TokenType.SEQUENCE: 2672 expression = self._parse_types() 2673 props = self._parse_properties() 2674 if props: 2675 sequence_props = exp.SequenceProperties() 2676 options = [] 2677 for prop in props: 2678 if isinstance(prop, exp.SequenceProperties): 2679 for arg, value in prop.args.items(): 2680 if arg == "options": 2681 options.extend(value) 2682 else: 2683 sequence_props.set(arg, value) 2684 prop.pop() 2685 2686 if options: 2687 sequence_props.set("options", options) 2688 2689 props.append("expressions", sequence_props) 2690 extend_props(props) 2691 else: 2692 expression = self._parse_ddl_select() 2693 2694 # Some dialects also support using a table as an alias instead of a SELECT. 2695 # Here we fallback to this as an alternative. 2696 if not expression and has_alias: 2697 expression = self._try_parse(self._parse_table_parts) 2698 2699 if create_token_type == TokenType.TABLE: 2700 # exp.Properties.Location.POST_EXPRESSION 2701 extend_props(self._parse_properties()) 2702 2703 indexes = [] 2704 while True: 2705 index = self._parse_index() 2706 2707 # exp.Properties.Location.POST_INDEX 2708 extend_props(self._parse_properties()) 2709 if not index: 2710 break 2711 else: 2712 self._match(TokenType.COMMA) 2713 indexes.append(index) 2714 elif create_token_type == TokenType.VIEW: 2715 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2716 no_schema_binding = True 2717 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2718 extend_props(self._parse_properties()) 2719 2720 shallow = self._match_text_seq("SHALLOW") 2721 2722 if self._match_texts(self.CLONE_KEYWORDS): 2723 copy = self._prev.text.lower() == "copy" 2724 clone = self.expression( 2725 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2726 ) 2727 2728 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2729 return self._parse_as_command(start) 2730 2731 create_kind_text = create_token.text.upper() 2732 return self.expression( 2733 exp.Create( 2734 this=this, 2735 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2736 replace=replace, 2737 refresh=refresh, 2738 unique=unique, 2739 expression=expression, 2740 exists=exists, 2741 properties=properties, 2742 indexes=indexes, 2743 no_schema_binding=no_schema_binding, 2744 begin=begin, 2745 clone=clone, 2746 concurrently=concurrently, 2747 clustered=clustered, 2748 ) 2749 ) 2750 2751 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2752 seq = exp.SequenceProperties() 2753 2754 options = [] 2755 index = self._index 2756 2757 while self._curr: 2758 self._match(TokenType.COMMA) 2759 if self._match_text_seq("INCREMENT"): 2760 self._match_text_seq("BY") 2761 self._match_text_seq("=") 2762 seq.set("increment", self._parse_term()) 2763 elif self._match_text_seq("MINVALUE"): 2764 seq.set("minvalue", self._parse_term()) 2765 elif self._match_text_seq("MAXVALUE"): 2766 seq.set("maxvalue", self._parse_term()) 2767 elif self._match_text_seq("START"): 2768 self._match_text_seq("WITH") 2769 self._match_text_seq("=") 2770 seq.set("start", self._parse_term()) 2771 elif self._match_text_seq("CACHE"): 2772 # T-SQL allows empty CACHE which is initialized dynamically 2773 seq.set("cache", self._parse_number() or True) 2774 elif self._match_text_seq("OWNED", "BY"): 2775 # "OWNED BY NONE" is the default 2776 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2777 else: 2778 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2779 if opt: 2780 options.append(opt) 2781 else: 2782 break 2783 2784 seq.set("options", options if options else None) 2785 return None if self._index == index else seq 2786 2787 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2788 events = [] 2789 2790 while True: 2791 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2792 2793 if not event_type: 2794 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2795 2796 columns = ( 2797 self._parse_csv(self._parse_column) 2798 if event_type == "UPDATE" and self._match_text_seq("OF") 2799 else None 2800 ) 2801 2802 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2803 2804 if not self._match(TokenType.OR): 2805 break 2806 2807 return events 2808 2809 def _parse_trigger_deferrable( 2810 self, 2811 ) -> tuple[str | None, str | None]: 2812 deferrable_var = self._parse_var_from_options( 2813 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2814 ) 2815 deferrable = deferrable_var.this if deferrable_var else None 2816 2817 initially = None 2818 if deferrable and self._match_text_seq("INITIALLY"): 2819 initially = ( 2820 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2821 ) 2822 2823 return deferrable, initially 2824 2825 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2826 if not self._match_text_seq(keyword): 2827 return None 2828 if not self._match_text_seq("TABLE"): 2829 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2830 self._match_text_seq("AS") 2831 return self._parse_id_var() 2832 2833 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2834 if not self._match_text_seq("REFERENCING"): 2835 return None 2836 2837 old_alias = None 2838 new_alias = None 2839 2840 while True: 2841 if alias := self._parse_trigger_referencing_clause("OLD"): 2842 if old_alias is not None: 2843 self.raise_error("Duplicate OLD clause in REFERENCING") 2844 old_alias = alias 2845 elif alias := self._parse_trigger_referencing_clause("NEW"): 2846 if new_alias is not None: 2847 self.raise_error("Duplicate NEW clause in REFERENCING") 2848 new_alias = alias 2849 else: 2850 break 2851 2852 if old_alias is None and new_alias is None: 2853 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2854 2855 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2856 2857 def _parse_trigger_for_each(self) -> str | None: 2858 if not self._match_text_seq("FOR", "EACH"): 2859 return None 2860 2861 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2862 2863 def _parse_trigger_execute(self) -> exp.TriggerExecute | None: 2864 if not self._match(TokenType.EXECUTE): 2865 return None 2866 2867 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2868 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2869 2870 func_call = self._parse_column() 2871 return self.expression(exp.TriggerExecute(this=func_call)) 2872 2873 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2874 # only used for teradata currently 2875 self._match(TokenType.COMMA) 2876 2877 kwargs = { 2878 "no": self._match_text_seq("NO"), 2879 "dual": self._match_text_seq("DUAL"), 2880 "before": self._match_text_seq("BEFORE"), 2881 "default": self._match_text_seq("DEFAULT"), 2882 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2883 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2884 "after": self._match_text_seq("AFTER"), 2885 "minimum": self._match_texts(("MIN", "MINIMUM")), 2886 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2887 } 2888 2889 if self._match_texts(self.PROPERTY_PARSERS): 2890 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2891 try: 2892 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2893 except TypeError: 2894 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2895 2896 if self._match_text_seq("CHARACTER", "SET"): 2897 return self._parse_character_set(default=bool(kwargs["default"])) 2898 2899 return None 2900 2901 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2902 return self._parse_wrapped_csv(self._parse_property) 2903 2904 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2905 if self._match_texts(self.PROPERTY_PARSERS): 2906 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2907 2908 if self._match_text_seq("CHARACTER", "SET"): 2909 return self._parse_character_set() 2910 2911 if self._match(TokenType.DEFAULT): 2912 if self._match_texts(self.PROPERTY_PARSERS): 2913 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 2914 2915 if self._match_text_seq("CHARACTER", "SET"): 2916 return self._parse_character_set(default=True) 2917 2918 if self._match_text_seq("COMPOUND", "SORTKEY"): 2919 return self._parse_sortkey(compound=True) 2920 2921 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 2922 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 2923 2924 if self._match_text_seq("NOT", "DETERMINISTIC"): 2925 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 2926 2927 index = self._index 2928 2929 seq_props = self._parse_sequence_properties() 2930 if seq_props: 2931 return seq_props 2932 2933 self._retreat(index) 2934 return self._parse_key_value_property() 2935 2936 def _parse_key_value_property( 2937 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 2938 ) -> exp.Property | None: 2939 index = self._index 2940 key = self._parse_column() 2941 2942 if not self._match(TokenType.EQ): 2943 self._retreat(index) 2944 return None 2945 2946 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 2947 if isinstance(key, exp.Column): 2948 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 2949 2950 value = ( 2951 parse_value() 2952 if parse_value 2953 else self._parse_bitwise() or self._parse_var(any_token=True) 2954 ) 2955 2956 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 2957 if isinstance(value, exp.Column): 2958 value = exp.var(value.name) 2959 2960 return self.expression(exp.Property(this=key, value=value)) 2961 2962 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 2963 if self._match_text_seq("BY"): 2964 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 2965 2966 self._match(TokenType.ALIAS) 2967 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 2968 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 2969 2970 return self.expression( 2971 exp.FileFormatProperty( 2972 this=( 2973 self.expression( 2974 exp.InputOutputFormat( 2975 input_format=input_format, output_format=output_format 2976 ) 2977 ) 2978 if input_format or output_format 2979 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 2980 ), 2981 hive_format=True, 2982 ) 2983 ) 2984 2985 def _parse_unquoted_field(self) -> exp.Expr | None: 2986 field = self._parse_field() 2987 if isinstance(field, exp.Identifier) and not field.quoted: 2988 field = exp.var(field) 2989 2990 return field 2991 2992 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 2993 self._match(TokenType.EQ) 2994 self._match(TokenType.ALIAS) 2995 2996 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 2997 2998 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 2999 properties = [] 3000 while True: 3001 if before: 3002 prop = self._parse_property_before() 3003 else: 3004 prop = self._parse_property() 3005 if not prop: 3006 break 3007 for p in ensure_list(prop): 3008 properties.append(p) 3009 3010 if properties: 3011 return self.expression(exp.Properties(expressions=properties)) 3012 3013 return None 3014 3015 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3016 return self.expression( 3017 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3018 ) 3019 3020 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3021 return self.expression( 3022 exp.SqlSecurityProperty( 3023 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3024 ) 3025 ) 3026 3027 def _parse_settings_property(self) -> exp.SettingsProperty: 3028 return self.expression( 3029 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3030 ) 3031 3032 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3033 if not self._match_text_seq("ON", "NULL", "INPUT"): 3034 self._retreat(self._index - 1) 3035 return None 3036 3037 return self.expression(exp.CalledOnNullInputProperty()) 3038 3039 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3040 if self._index >= 2: 3041 pre_volatile_token = self._tokens[self._index - 2] 3042 else: 3043 pre_volatile_token = None 3044 3045 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3046 return exp.VolatileProperty() 3047 3048 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3049 3050 def _parse_retention_period(self) -> exp.Var: 3051 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3052 number = self._parse_number() 3053 number_str = f"{number} " if number else "" 3054 unit = self._parse_var(any_token=True) 3055 return exp.var(f"{number_str}{unit}") 3056 3057 def _parse_system_versioning_property( 3058 self, with_: bool = False 3059 ) -> exp.WithSystemVersioningProperty: 3060 self._match(TokenType.EQ) 3061 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3062 3063 if self._match_text_seq("OFF"): 3064 prop.set("on", False) 3065 return prop 3066 3067 self._match(TokenType.ON) 3068 if self._match(TokenType.L_PAREN): 3069 while self._curr and not self._match(TokenType.R_PAREN): 3070 if self._match_text_seq("HISTORY_TABLE", "="): 3071 prop.set("this", self._parse_table_parts()) 3072 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3073 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3074 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3075 prop.set("retention_period", self._parse_retention_period()) 3076 3077 self._match(TokenType.COMMA) 3078 3079 return prop 3080 3081 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3082 self._match(TokenType.EQ) 3083 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3084 prop = self.expression(exp.DataDeletionProperty(on=on)) 3085 3086 if self._match(TokenType.L_PAREN): 3087 while self._curr and not self._match(TokenType.R_PAREN): 3088 if self._match_text_seq("FILTER_COLUMN", "="): 3089 prop.set("filter_column", self._parse_column()) 3090 elif self._match_text_seq("RETENTION_PERIOD", "="): 3091 prop.set("retention_period", self._parse_retention_period()) 3092 3093 self._match(TokenType.COMMA) 3094 3095 return prop 3096 3097 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3098 kind = "HASH" 3099 expressions: list[exp.Expr] | None = None 3100 if self._match_text_seq("BY", "HASH"): 3101 expressions = self._parse_wrapped_csv(self._parse_id_var) 3102 elif self._match_text_seq("BY", "RANDOM"): 3103 kind = "RANDOM" 3104 3105 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3106 buckets: exp.Expr | None = None 3107 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3108 buckets = self._parse_number() 3109 3110 return self.expression( 3111 exp.DistributedByProperty( 3112 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3113 ) 3114 ) 3115 3116 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3117 self._match_text_seq("KEY") 3118 expressions = self._parse_wrapped_id_vars() 3119 return self.expression(expr_type(expressions=expressions)) 3120 3121 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3122 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3123 prop = self._parse_system_versioning_property(with_=True) 3124 self._match_r_paren() 3125 return prop 3126 3127 if self._match(TokenType.L_PAREN, advance=False): 3128 result: list[exp.Expr] = [] 3129 for i in self._parse_wrapped_properties(): 3130 result.extend(i) if isinstance(i, list) else result.append(i) 3131 return result 3132 3133 if self._match_text_seq("JOURNAL"): 3134 return self._parse_withjournaltable() 3135 3136 if self._match_texts(self.VIEW_ATTRIBUTES): 3137 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3138 3139 if self._match_text_seq("DATA"): 3140 return self._parse_withdata(no=False) 3141 elif self._match_text_seq("NO", "DATA"): 3142 return self._parse_withdata(no=True) 3143 3144 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3145 return self._parse_serde_properties(with_=True) 3146 3147 if self._match(TokenType.SCHEMA): 3148 return self.expression( 3149 exp.WithSchemaBindingProperty( 3150 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3151 ) 3152 ) 3153 3154 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3155 return self.expression( 3156 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3157 ) 3158 3159 if not self._next: 3160 return None 3161 3162 return self._parse_withisolatedloading() 3163 3164 def _parse_procedure_option(self) -> exp.Expr | None: 3165 if self._match_text_seq("EXECUTE", "AS"): 3166 return self.expression( 3167 exp.ExecuteAsProperty( 3168 this=self._parse_var_from_options( 3169 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3170 ) 3171 or self._parse_string() 3172 ) 3173 ) 3174 3175 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3176 3177 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3178 def _parse_definer(self) -> exp.DefinerProperty | None: 3179 self._match(TokenType.EQ) 3180 3181 user = self._parse_id_var() 3182 self._match(TokenType.PARAMETER) 3183 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3184 3185 if not user or not host: 3186 return None 3187 3188 return exp.DefinerProperty(this=f"{user}@{host}") 3189 3190 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3191 self._match(TokenType.TABLE) 3192 self._match(TokenType.EQ) 3193 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3194 3195 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3196 return self.expression(exp.LogProperty(no=no)) 3197 3198 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3199 return self.expression(exp.JournalProperty(**kwargs)) 3200 3201 def _parse_checksum(self) -> exp.ChecksumProperty: 3202 self._match(TokenType.EQ) 3203 3204 on = None 3205 if self._match(TokenType.ON): 3206 on = True 3207 elif self._match_text_seq("OFF"): 3208 on = False 3209 3210 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3211 3212 def _parse_cluster(self) -> exp.Cluster: 3213 self._match(TokenType.CLUSTER_BY) 3214 return self.expression( 3215 exp.Cluster( 3216 expressions=self._parse_csv(self._parse_column), 3217 ) 3218 ) 3219 3220 def _parse_cluster_property(self) -> exp.ClusterProperty: 3221 return self.expression( 3222 exp.ClusterProperty( 3223 expressions=self._parse_wrapped_csv(self._parse_column), 3224 ) 3225 ) 3226 3227 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3228 self._match_text_seq("BY") 3229 3230 self._match_l_paren() 3231 expressions = self._parse_csv(self._parse_column) 3232 self._match_r_paren() 3233 3234 if self._match_text_seq("SORTED", "BY"): 3235 self._match_l_paren() 3236 sorted_by = self._parse_csv(self._parse_ordered) 3237 self._match_r_paren() 3238 else: 3239 sorted_by = None 3240 3241 self._match(TokenType.INTO) 3242 buckets = self._parse_number() 3243 self._match_text_seq("BUCKETS") 3244 3245 return self.expression( 3246 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3247 ) 3248 3249 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3250 if not self._match_text_seq("GRANTS"): 3251 self._retreat(self._index - 1) 3252 return None 3253 3254 return self.expression(exp.CopyGrantsProperty()) 3255 3256 def _parse_freespace(self) -> exp.FreespaceProperty: 3257 self._match(TokenType.EQ) 3258 return self.expression( 3259 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3260 ) 3261 3262 def _parse_mergeblockratio( 3263 self, no: bool = False, default: bool = False 3264 ) -> exp.MergeBlockRatioProperty: 3265 if self._match(TokenType.EQ): 3266 return self.expression( 3267 exp.MergeBlockRatioProperty( 3268 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3269 ) 3270 ) 3271 3272 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3273 3274 def _parse_datablocksize( 3275 self, 3276 default: bool | None = None, 3277 minimum: bool | None = None, 3278 maximum: bool | None = None, 3279 ) -> exp.DataBlocksizeProperty: 3280 self._match(TokenType.EQ) 3281 size = self._parse_number() 3282 3283 units = None 3284 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3285 units = self._prev.text 3286 3287 return self.expression( 3288 exp.DataBlocksizeProperty( 3289 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3290 ) 3291 ) 3292 3293 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3294 self._match(TokenType.EQ) 3295 always = self._match_text_seq("ALWAYS") 3296 manual = self._match_text_seq("MANUAL") 3297 never = self._match_text_seq("NEVER") 3298 default = self._match_text_seq("DEFAULT") 3299 3300 autotemp = None 3301 if self._match_text_seq("AUTOTEMP"): 3302 autotemp = self._parse_schema() 3303 3304 return self.expression( 3305 exp.BlockCompressionProperty( 3306 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3307 ) 3308 ) 3309 3310 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3311 index = self._index 3312 no = self._match_text_seq("NO") 3313 concurrent = self._match_text_seq("CONCURRENT") 3314 3315 if not self._match_text_seq("ISOLATED", "LOADING"): 3316 self._retreat(index) 3317 return None 3318 3319 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3320 return self.expression( 3321 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3322 ) 3323 3324 def _parse_locking(self) -> exp.LockingProperty: 3325 if self._match(TokenType.TABLE): 3326 kind = "TABLE" 3327 elif self._match(TokenType.VIEW): 3328 kind = "VIEW" 3329 elif self._match(TokenType.ROW): 3330 kind = "ROW" 3331 elif self._match_text_seq("DATABASE"): 3332 kind = "DATABASE" 3333 else: 3334 kind = None 3335 3336 if kind in ("DATABASE", "TABLE", "VIEW"): 3337 this = self._parse_table_parts() 3338 else: 3339 this = None 3340 3341 if self._match(TokenType.FOR): 3342 for_or_in = "FOR" 3343 elif self._match(TokenType.IN): 3344 for_or_in = "IN" 3345 else: 3346 for_or_in = None 3347 3348 if self._match_text_seq("ACCESS"): 3349 lock_type = "ACCESS" 3350 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3351 lock_type = "EXCLUSIVE" 3352 elif self._match_text_seq("SHARE"): 3353 lock_type = "SHARE" 3354 elif self._match_text_seq("READ"): 3355 lock_type = "READ" 3356 elif self._match_text_seq("WRITE"): 3357 lock_type = "WRITE" 3358 elif self._match_text_seq("CHECKSUM"): 3359 lock_type = "CHECKSUM" 3360 else: 3361 lock_type = None 3362 3363 override = self._match_text_seq("OVERRIDE") 3364 3365 return self.expression( 3366 exp.LockingProperty( 3367 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3368 ) 3369 ) 3370 3371 def _parse_partition_by(self) -> list[exp.Expr]: 3372 if self._match(TokenType.PARTITION_BY): 3373 return self._parse_csv(self._parse_disjunction) 3374 return [] 3375 3376 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3377 def _parse_partition_bound_expr() -> exp.Expr | None: 3378 if self._match_text_seq("MINVALUE"): 3379 return exp.var("MINVALUE") 3380 if self._match_text_seq("MAXVALUE"): 3381 return exp.var("MAXVALUE") 3382 return self._parse_bitwise() 3383 3384 this: exp.Expr | list[exp.Expr] | None = None 3385 expression = None 3386 from_expressions = None 3387 to_expressions = None 3388 3389 if self._match(TokenType.IN): 3390 this = self._parse_wrapped_csv(self._parse_bitwise) 3391 elif self._match(TokenType.FROM): 3392 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3393 self._match_text_seq("TO") 3394 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3395 elif self._match_text_seq("WITH", "(", "MODULUS"): 3396 this = self._parse_number() 3397 self._match_text_seq(",", "REMAINDER") 3398 expression = self._parse_number() 3399 self._match_r_paren() 3400 else: 3401 self.raise_error("Failed to parse partition bound spec.") 3402 3403 return self.expression( 3404 exp.PartitionBoundSpec( 3405 this=this, 3406 expression=expression, 3407 from_expressions=from_expressions, 3408 to_expressions=to_expressions, 3409 ) 3410 ) 3411 3412 # https://www.postgresql.org/docs/current/sql-createtable.html 3413 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3414 if not self._match_text_seq("OF"): 3415 self._retreat(self._index - 1) 3416 return None 3417 3418 this = self._parse_table(schema=True) 3419 3420 if self._match(TokenType.DEFAULT): 3421 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3422 elif self._match_text_seq("FOR", "VALUES"): 3423 expression = self._parse_partition_bound_spec() 3424 else: 3425 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3426 3427 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3428 3429 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3430 self._match(TokenType.EQ) 3431 return self.expression( 3432 exp.PartitionedByProperty( 3433 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3434 ) 3435 ) 3436 3437 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3438 if self._match_text_seq("AND", "STATISTICS"): 3439 statistics = True 3440 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3441 statistics = False 3442 else: 3443 statistics = None 3444 3445 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3446 3447 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3448 if self._match_text_seq("SQL"): 3449 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3450 return None 3451 3452 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3453 if self._match_text_seq("SQL", "DATA"): 3454 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3455 return None 3456 3457 def _parse_no_property(self) -> exp.Expr | None: 3458 if self._match_text_seq("PRIMARY", "INDEX"): 3459 return exp.NoPrimaryIndexProperty() 3460 if self._match_text_seq("SQL"): 3461 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3462 return None 3463 3464 def _parse_on_property(self) -> exp.Expr | None: 3465 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3466 return exp.OnCommitProperty() 3467 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3468 return exp.OnCommitProperty(delete=True) 3469 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3470 3471 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3472 if self._match_text_seq("SQL", "DATA"): 3473 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3474 return None 3475 3476 def _parse_distkey(self) -> exp.DistKeyProperty: 3477 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3478 3479 def _parse_create_like(self) -> exp.LikeProperty | None: 3480 table = self._parse_table(schema=True) 3481 3482 options = [] 3483 while self._match_texts(("INCLUDING", "EXCLUDING")): 3484 this = self._prev.text.upper() 3485 3486 id_var = self._parse_id_var() 3487 if not id_var: 3488 return None 3489 3490 options.append( 3491 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3492 ) 3493 3494 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3495 3496 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3497 return self.expression( 3498 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3499 ) 3500 3501 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3502 self._match(TokenType.EQ) 3503 return self.expression( 3504 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3505 ) 3506 3507 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3508 self._match_text_seq("WITH", "CONNECTION") 3509 return self.expression( 3510 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3511 ) 3512 3513 def _parse_returns(self) -> exp.ReturnsProperty: 3514 value: exp.Expr | None 3515 null = None 3516 is_table = self._match(TokenType.TABLE) 3517 3518 if is_table: 3519 if self._match(TokenType.LT): 3520 value = self.expression( 3521 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3522 ) 3523 if not self._match(TokenType.GT): 3524 self.raise_error("Expecting >") 3525 else: 3526 value = self._parse_schema(exp.var("TABLE")) 3527 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3528 null = True 3529 value = None 3530 else: 3531 value = self._parse_types() 3532 3533 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3534 3535 def _parse_describe(self) -> exp.Describe: 3536 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3537 style: str | None = ( 3538 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3539 ) 3540 if self._match(TokenType.DOT): 3541 style = None 3542 self._retreat(self._index - 2) 3543 3544 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3545 3546 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3547 this = self._parse_statement() 3548 else: 3549 this = self._parse_table(schema=True) 3550 3551 properties = self._parse_properties() 3552 expressions = properties.expressions if properties else None 3553 partition = self._parse_partition() 3554 return self.expression( 3555 exp.Describe( 3556 this=this, 3557 style=style, 3558 kind=kind, 3559 expressions=expressions, 3560 partition=partition, 3561 format=format, 3562 as_json=self._match_text_seq("AS", "JSON"), 3563 ) 3564 ) 3565 3566 def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts: 3567 kind = self._prev.text.upper() 3568 expressions = [] 3569 3570 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3571 if self._match(TokenType.WHEN): 3572 expression = self._parse_disjunction() 3573 self._match(TokenType.THEN) 3574 else: 3575 expression = None 3576 3577 else_ = self._match(TokenType.ELSE) 3578 3579 if not self._match(TokenType.INTO): 3580 return None 3581 3582 return self.expression( 3583 exp.ConditionalInsert( 3584 this=self.expression( 3585 exp.Insert( 3586 this=self._parse_table(schema=True), 3587 expression=self._parse_derived_table_values(), 3588 ) 3589 ), 3590 expression=expression, 3591 else_=else_, 3592 ) 3593 ) 3594 3595 expression = parse_conditional_insert() 3596 while expression is not None: 3597 expressions.append(expression) 3598 expression = parse_conditional_insert() 3599 3600 return self.expression( 3601 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3602 comments=comments, 3603 ) 3604 3605 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3606 comments: list[str] = [] 3607 hint = self._parse_hint() 3608 overwrite = self._match(TokenType.OVERWRITE) 3609 ignore = self._match(TokenType.IGNORE) 3610 local = self._match_text_seq("LOCAL") 3611 alternative = None 3612 is_function = None 3613 3614 if self._match_text_seq("DIRECTORY"): 3615 this: exp.Expr | None = self.expression( 3616 exp.Directory( 3617 this=self._parse_var_or_string(), 3618 local=local, 3619 row_format=self._parse_row_format(match_row=True), 3620 ) 3621 ) 3622 else: 3623 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3624 comments += ensure_list(self._prev_comments) 3625 return self._parse_multitable_inserts(comments) 3626 3627 if self._match(TokenType.OR): 3628 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3629 3630 self._match(TokenType.INTO) 3631 comments += ensure_list(self._prev_comments) 3632 self._match(TokenType.TABLE) 3633 is_function = self._match(TokenType.FUNCTION) 3634 3635 this = self._parse_function() if is_function else self._parse_insert_table() 3636 3637 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3638 set_values = None 3639 if self._match(TokenType.SET): 3640 columns = [] 3641 values = [] 3642 3643 def _parse_set_assignment() -> exp.Expr | None: 3644 target = self._parse_column() 3645 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3646 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3647 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3648 else: 3649 value = self._parse_disjunction() 3650 3651 if value: 3652 columns.append(target.this) 3653 values.append(value) 3654 return value 3655 3656 self.raise_error("Expected column assignment in INSERT ... SET") 3657 return None 3658 3659 self._parse_csv(_parse_set_assignment) 3660 3661 this = self.expression(exp.Schema(this=this, expressions=columns)) 3662 set_values = self.expression( 3663 exp.Values( 3664 expressions=[exp.Tuple(expressions=values)], 3665 alias=self._parse_table_alias(), 3666 ) 3667 ) 3668 3669 returning = self._parse_returning() # TSQL allows RETURNING before source 3670 3671 stored = self._match_text_seq("STORED") and self._parse_stored() 3672 by_name = self._match_text_seq("BY", "NAME") 3673 exists = self._parse_exists() 3674 replace_where = None 3675 replace_using = None 3676 3677 if self._match(TokenType.REPLACE): 3678 if self._match(TokenType.WHERE): 3679 replace_where = self._parse_disjunction() 3680 elif self._match(TokenType.USING): 3681 replace_using = self._parse_using_identifiers() 3682 3683 return self.expression( 3684 exp.Insert( 3685 hint=hint, 3686 is_function=is_function, 3687 this=this, 3688 stored=stored, 3689 by_name=by_name, 3690 exists=exists, 3691 where=replace_where, 3692 using=replace_using, 3693 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3694 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3695 default=self._match_text_seq("DEFAULT", "VALUES"), 3696 expression=set_values 3697 or self._parse_derived_table_values() 3698 or self._parse_ddl_select(), 3699 conflict=self._parse_on_conflict(), 3700 returning=returning or self._parse_returning(), 3701 overwrite=overwrite, 3702 alternative=alternative, 3703 ignore=ignore, 3704 source=self._match(TokenType.TABLE) and self._parse_table(), 3705 ), 3706 comments=comments, 3707 ) 3708 3709 def _parse_insert_table(self) -> exp.Expr | None: 3710 this = self._parse_table(schema=True, parse_partition=True) 3711 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3712 this.set("alias", self._parse_table_alias()) 3713 return this 3714 3715 def _parse_kill(self) -> exp.Kill: 3716 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3717 3718 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3719 3720 def _parse_on_conflict(self) -> exp.OnConflict | None: 3721 conflict = self._match_text_seq("ON", "CONFLICT") 3722 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3723 3724 if not conflict and not duplicate: 3725 return None 3726 3727 conflict_keys = None 3728 constraint = None 3729 3730 if conflict: 3731 if self._match_text_seq("ON", "CONSTRAINT"): 3732 constraint = self._parse_id_var() 3733 elif self._match(TokenType.L_PAREN): 3734 conflict_keys = self._parse_csv(self._parse_indexed_column) 3735 self._match_r_paren() 3736 3737 index_predicate = self._parse_where() 3738 3739 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3740 if self._prev.token_type == TokenType.UPDATE: 3741 self._match(TokenType.SET) 3742 expressions = self._parse_csv(self._parse_equality) 3743 else: 3744 expressions = None 3745 3746 return self.expression( 3747 exp.OnConflict( 3748 duplicate=duplicate, 3749 expressions=expressions, 3750 action=action, 3751 conflict_keys=conflict_keys, 3752 index_predicate=index_predicate, 3753 constraint=constraint, 3754 where=self._parse_where(), 3755 ) 3756 ) 3757 3758 def _parse_returning(self) -> exp.Returning | None: 3759 if not self._match(TokenType.RETURNING): 3760 return None 3761 return self.expression( 3762 exp.Returning( 3763 expressions=self._parse_csv(self._parse_expression), 3764 into=self._match(TokenType.INTO) and self._parse_table_part(), 3765 ) 3766 ) 3767 3768 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3769 if not self._match(TokenType.FORMAT): 3770 return None 3771 return self._parse_row_format() 3772 3773 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3774 index = self._index 3775 with_ = with_ or self._match_text_seq("WITH") 3776 3777 if not self._match(TokenType.SERDE_PROPERTIES): 3778 self._retreat(index) 3779 return None 3780 return self.expression( 3781 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3782 ) 3783 3784 def _parse_row_format( 3785 self, match_row: bool = False 3786 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3787 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3788 return None 3789 3790 if self._match_text_seq("SERDE"): 3791 this = self._parse_string() 3792 3793 serde_properties = self._parse_serde_properties() 3794 3795 return self.expression( 3796 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3797 ) 3798 3799 self._match_text_seq("DELIMITED") 3800 3801 kwargs = {} 3802 3803 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3804 kwargs["fields"] = self._parse_string() 3805 if self._match_text_seq("ESCAPED", "BY"): 3806 kwargs["escaped"] = self._parse_string() 3807 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3808 kwargs["collection_items"] = self._parse_string() 3809 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3810 kwargs["map_keys"] = self._parse_string() 3811 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3812 kwargs["lines"] = self._parse_string() 3813 if self._match_text_seq("NULL", "DEFINED", "AS"): 3814 kwargs["null"] = self._parse_string() 3815 3816 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3817 3818 def _parse_load(self) -> exp.LoadData | exp.Command: 3819 if self._match_text_seq("DATA"): 3820 local = self._match_text_seq("LOCAL") 3821 self._match_text_seq("INPATH") 3822 inpath = self._parse_string() 3823 overwrite = self._match(TokenType.OVERWRITE) 3824 temp: bool | None = None 3825 if self._match(TokenType.INTO): 3826 temp = self._match(TokenType.TEMPORARY) 3827 self._match(TokenType.TABLE) 3828 3829 return self.expression( 3830 exp.LoadData( 3831 this=self._parse_table(schema=True), 3832 local=local, 3833 overwrite=overwrite, 3834 temp=temp, 3835 inpath=inpath, 3836 files=self._match_text_seq("FROM", "FILES") 3837 and exp.Properties(expressions=self._parse_wrapped_properties()), 3838 partition=self._parse_partition(), 3839 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3840 serde=self._match_text_seq("SERDE") and self._parse_string(), 3841 ) 3842 ) 3843 return self._parse_as_command(self._prev) 3844 3845 def _parse_delete(self) -> exp.Delete: 3846 hint = self._parse_hint() 3847 3848 # This handles MySQL's "Multiple-Table Syntax" 3849 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3850 tables = None 3851 if not self._match(TokenType.FROM, advance=False): 3852 tables = self._parse_csv(self._parse_table) or None 3853 3854 returning = self._parse_returning() 3855 3856 return self.expression( 3857 exp.Delete( 3858 hint=hint, 3859 tables=tables, 3860 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3861 using=self._match(TokenType.USING) 3862 and self._parse_csv(lambda: self._parse_table(joins=True)), 3863 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3864 where=self._parse_where(), 3865 returning=returning or self._parse_returning(), 3866 order=self._parse_order(), 3867 limit=self._parse_limit(), 3868 ) 3869 ) 3870 3871 def _parse_update(self) -> exp.Update: 3872 hint = self._parse_hint() 3873 kwargs: dict[str, object] = { 3874 "hint": hint, 3875 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3876 } 3877 while self._curr: 3878 if self._match(TokenType.SET): 3879 kwargs["expressions"] = self._parse_csv(self._parse_equality) 3880 elif self._match(TokenType.RETURNING, advance=False): 3881 kwargs["returning"] = self._parse_returning() 3882 elif self._match(TokenType.FROM, advance=False): 3883 from_ = self._parse_from(joins=True) 3884 table = from_.this if from_ else None 3885 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3886 table.set("joins", list(self._parse_joins()) or None) 3887 3888 kwargs["from_"] = from_ 3889 elif self._match(TokenType.WHERE, advance=False): 3890 kwargs["where"] = self._parse_where() 3891 elif self._match(TokenType.ORDER_BY, advance=False): 3892 kwargs["order"] = self._parse_order() 3893 elif self._match(TokenType.LIMIT, advance=False): 3894 kwargs["limit"] = self._parse_limit() 3895 else: 3896 break 3897 3898 return self.expression(exp.Update(**kwargs)) 3899 3900 def _parse_use(self) -> exp.Use: 3901 return self.expression( 3902 exp.Use( 3903 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 3904 this=self._parse_table(schema=False), 3905 ) 3906 ) 3907 3908 def _parse_uncache(self) -> exp.Uncache: 3909 if not self._match(TokenType.TABLE): 3910 self.raise_error("Expecting TABLE after UNCACHE") 3911 3912 return self.expression( 3913 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 3914 ) 3915 3916 def _parse_cache(self) -> exp.Cache: 3917 lazy = self._match_text_seq("LAZY") 3918 self._match(TokenType.TABLE) 3919 table = self._parse_table(schema=True) 3920 3921 options = [] 3922 if self._match_text_seq("OPTIONS"): 3923 self._match_l_paren() 3924 k = self._parse_string() 3925 self._match(TokenType.EQ) 3926 v = self._parse_string() 3927 options = [k, v] 3928 self._match_r_paren() 3929 3930 self._match(TokenType.ALIAS) 3931 return self.expression( 3932 exp.Cache( 3933 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 3934 ) 3935 ) 3936 3937 def _parse_partition(self) -> exp.Partition | None: 3938 if not self._match_texts(self.PARTITION_KEYWORDS): 3939 return None 3940 3941 return self.expression( 3942 exp.Partition( 3943 subpartition=self._prev.text.upper() == "SUBPARTITION", 3944 expressions=self._parse_wrapped_csv(self._parse_disjunction), 3945 ) 3946 ) 3947 3948 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 3949 def _parse_value_expression() -> exp.Expr | None: 3950 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3951 return exp.var(self._prev.text.upper()) 3952 return self._parse_expression() 3953 3954 if self._match(TokenType.L_PAREN): 3955 expressions = self._parse_csv(_parse_value_expression) 3956 self._match_r_paren() 3957 return self.expression(exp.Tuple(expressions=expressions)) 3958 3959 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 3960 expression = self._parse_expression() 3961 if expression: 3962 return self.expression(exp.Tuple(expressions=[expression])) 3963 return None 3964 3965 def _parse_projections( 3966 self, 3967 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 3968 return self._parse_expressions(), None 3969 3970 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 3971 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 3972 this: exp.Expr | None = self._parse_simplified_pivot( 3973 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 3974 ) 3975 elif self._match(TokenType.FROM): 3976 from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True) 3977 # Support parentheses for duckdb FROM-first syntax 3978 select = self._parse_select(from_=from_) 3979 if select: 3980 if not select.args.get("from_"): 3981 select.set("from_", from_) 3982 this = select 3983 else: 3984 this = exp.select("*").from_(t.cast(exp.From, from_)) 3985 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3986 else: 3987 this = ( 3988 self._parse_table(consume_pipe=True) 3989 if table 3990 else self._parse_select(nested=True, parse_set_operation=False) 3991 ) 3992 3993 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 3994 # in case a modifier (e.g. join) is following 3995 if table and isinstance(this, exp.Values) and this.alias: 3996 alias = this.args["alias"].pop() 3997 this = exp.Table(this=this, alias=alias) 3998 3999 this = self._parse_query_modifiers(self._parse_set_operations(this)) 4000 4001 return this 4002 4003 def _parse_select( 4004 self, 4005 nested: bool = False, 4006 table: bool = False, 4007 parse_subquery_alias: bool = True, 4008 parse_set_operation: bool = True, 4009 consume_pipe: bool = True, 4010 from_: exp.From | None = None, 4011 ) -> exp.Expr | None: 4012 query = self._parse_select_query( 4013 nested=nested, 4014 table=table, 4015 parse_subquery_alias=parse_subquery_alias, 4016 parse_set_operation=parse_set_operation, 4017 ) 4018 4019 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4020 if not query and from_: 4021 query = exp.select("*").from_(from_) 4022 if isinstance(query, exp.Query): 4023 query = self._parse_pipe_syntax_query(query) 4024 query = query.subquery(copy=False) if query and table else query 4025 4026 return query 4027 4028 def _parse_select_query( 4029 self, 4030 nested: bool = False, 4031 table: bool = False, 4032 parse_subquery_alias: bool = True, 4033 parse_set_operation: bool = True, 4034 ) -> exp.Expr | None: 4035 cte = self._parse_with() 4036 4037 if cte: 4038 this = self._parse_statement() 4039 4040 if not this: 4041 self.raise_error("Failed to parse any statement following CTE") 4042 return cte 4043 4044 while isinstance(this, exp.Subquery) and this.is_wrapper: 4045 this = this.this 4046 4047 assert this is not None 4048 if "with_" in this.arg_types: 4049 if inner_cte := this.args.get("with_"): 4050 cte.set("expressions", cte.expressions + inner_cte.expressions) 4051 if inner_cte.args.get("recursive"): 4052 cte.set("recursive", True) 4053 this.set("with_", cte) 4054 else: 4055 self.raise_error(f"{this.key} does not support CTE") 4056 this = cte 4057 4058 return this 4059 4060 # duckdb supports leading with FROM x 4061 from_ = ( 4062 self._parse_from(joins=True, consume_pipe=True) 4063 if self._match(TokenType.FROM, advance=False) 4064 else None 4065 ) 4066 4067 if self._match(TokenType.SELECT): 4068 comments = self._prev_comments 4069 4070 hint = self._parse_hint() 4071 4072 if self._next and not self._next.token_type == TokenType.DOT: 4073 all_ = self._match(TokenType.ALL) 4074 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4075 else: 4076 all_, matched_distinct = None, False 4077 4078 kind = ( 4079 self._prev.text.upper() 4080 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4081 else None 4082 ) 4083 4084 distinct: exp.Expr | None = ( 4085 self.expression( 4086 exp.Distinct( 4087 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4088 ) 4089 ) 4090 if matched_distinct 4091 else None 4092 ) 4093 4094 operation_modifiers = [] 4095 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4096 operation_modifiers.append(exp.var(self._prev.text.upper())) 4097 4098 limit = self._parse_limit(top=True) 4099 4100 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4101 if limit and not matched_distinct and not all_: 4102 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4103 if matched_distinct: 4104 distinct = self.expression( 4105 exp.Distinct( 4106 on=self._parse_value(values=False) 4107 if self._match(TokenType.ON) 4108 else None 4109 ) 4110 ) 4111 else: 4112 all_ = self._match(TokenType.ALL) 4113 4114 if all_ and distinct: 4115 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4116 4117 projections, exclude = self._parse_projections() 4118 4119 this = self.expression( 4120 exp.Select( 4121 kind=kind, 4122 hint=hint, 4123 distinct=distinct, 4124 expressions=projections, 4125 limit=limit, 4126 exclude=exclude, 4127 operation_modifiers=operation_modifiers or None, 4128 ) 4129 ) 4130 this.comments = comments 4131 4132 into = self._parse_into() 4133 if into: 4134 this.set("into", into) 4135 4136 if not from_: 4137 from_ = self._parse_from() 4138 4139 if from_: 4140 this.set("from_", from_) 4141 4142 this = self._parse_query_modifiers(this) 4143 elif (table or nested) and self._match(TokenType.L_PAREN): 4144 comments = self._prev_comments 4145 this = self._parse_wrapped_select(table=table) 4146 4147 if this: 4148 this.add_comments(comments, prepend=True) 4149 4150 # We return early here so that the UNION isn't attached to the subquery by the 4151 # following call to _parse_set_operations, but instead becomes the parent node 4152 self._match_r_paren() 4153 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4154 elif self._match(TokenType.VALUES, advance=False): 4155 this = self._parse_derived_table_values() 4156 elif from_: 4157 this = exp.select("*").from_(from_.this, copy=False) 4158 this = self._parse_query_modifiers(this) 4159 elif self._match(TokenType.SUMMARIZE): 4160 table = self._match(TokenType.TABLE) 4161 this = self._parse_select() or self._parse_string() or self._parse_table() 4162 return self.expression(exp.Summarize(this=this, table=table)) 4163 elif self._match(TokenType.DESCRIBE): 4164 this = self._parse_describe() 4165 else: 4166 this = None 4167 4168 return self._parse_set_operations(this) if parse_set_operation else this 4169 4170 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4171 self._match_text_seq("SEARCH") 4172 4173 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4174 4175 if not kind: 4176 return None 4177 4178 self._match_text_seq("FIRST", "BY") 4179 4180 return self.expression( 4181 exp.RecursiveWithSearch( 4182 kind=kind, 4183 this=self._parse_id_var(), 4184 expression=self._match_text_seq("SET") and self._parse_id_var(), 4185 using=self._match_text_seq("USING") and self._parse_id_var(), 4186 ) 4187 ) 4188 4189 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4190 if not skip_with_token and not self._match(TokenType.WITH): 4191 return None 4192 4193 comments = self._prev_comments 4194 recursive = self._match(TokenType.RECURSIVE) 4195 4196 last_comments = None 4197 expressions = [] 4198 udfs = [] 4199 while True: 4200 cte = self._parse_cte() 4201 if cte: 4202 if isinstance(cte, exp.FunctionSpecification): 4203 udfs.append(cte) 4204 else: 4205 expressions.append(cte) 4206 4207 if last_comments: 4208 cte.add_comments(last_comments) 4209 4210 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4211 break 4212 else: 4213 self._match(TokenType.WITH) 4214 recursive = self._match(TokenType.RECURSIVE) or recursive 4215 4216 last_comments = self._prev_comments 4217 4218 return self.expression( 4219 exp.With( 4220 expressions=expressions, 4221 recursive=recursive or None, 4222 search=self._parse_recursive_with_search(), 4223 udfs=udfs or None, 4224 ), 4225 comments=comments, 4226 ) 4227 4228 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4229 index = self._index 4230 4231 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4232 if not alias or not alias.this: 4233 self.raise_error("Expected CTE to have alias") 4234 4235 key_expressions = ( 4236 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4237 ) 4238 4239 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4240 self._retreat(index) 4241 return None 4242 4243 comments = self._prev_comments 4244 4245 if self._match_text_seq("NOT", "MATERIALIZED"): 4246 materialized = False 4247 elif self._match_text_seq("MATERIALIZED"): 4248 materialized = True 4249 else: 4250 materialized = None 4251 4252 cte = self.expression( 4253 exp.CTE( 4254 this=self._parse_wrapped(self._parse_statement), 4255 alias=alias, 4256 materialized=materialized, 4257 key_expressions=key_expressions, 4258 ), 4259 comments=comments, 4260 ) 4261 4262 values = cte.this 4263 if isinstance(values, exp.Values): 4264 cte.set("this", self._values_to_select(values)) 4265 4266 return cte 4267 4268 def _values_to_select(self, values: exp.Values) -> exp.Select: 4269 if values.alias: 4270 return exp.select("*").from_(values) 4271 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4272 4273 def _parse_table_alias( 4274 self, alias_tokens: t.Collection[TokenType] | None = None 4275 ) -> exp.TableAlias | None: 4276 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4277 # so this section tries to parse the clause version and if it fails, it treats the token 4278 # as an identifier (alias) 4279 if self._can_parse_limit_or_offset(): 4280 return None 4281 4282 # START is never treated as an implicit alias when followed by WITH, since that 4283 # would swallow the beginning of a START WITH ... CONNECT BY clause 4284 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4285 return None 4286 4287 any_token = self._match(TokenType.ALIAS) 4288 alias = ( 4289 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4290 or self._parse_string_as_identifier() 4291 ) 4292 4293 index = self._index 4294 if self._match(TokenType.L_PAREN): 4295 columns = self._parse_csv(self._parse_function_parameter) 4296 self._match_r_paren() if columns else self._retreat(index) 4297 else: 4298 columns = None 4299 4300 if not alias and not columns: 4301 return None 4302 4303 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4304 4305 # We bubble up comments from the Identifier to the TableAlias 4306 if isinstance(alias, exp.Identifier): 4307 table_alias.add_comments(alias.pop_comments()) 4308 4309 return table_alias 4310 4311 def _parse_subquery( 4312 self, this: exp.Expr | None, parse_alias: bool = True 4313 ) -> exp.Subquery | None: 4314 if not this: 4315 return None 4316 4317 return self.expression( 4318 exp.Subquery( 4319 this=this, 4320 pivots=self._parse_pivots(), 4321 alias=self._parse_table_alias() if parse_alias else None, 4322 sample=self._parse_table_sample(), 4323 ) 4324 ) 4325 4326 def _implicit_unnests_to_explicit(self, this: E) -> E: 4327 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4328 4329 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4330 for i, join in enumerate(this.args.get("joins") or []): 4331 table = join.this 4332 normalized_table = table.copy() 4333 normalized_table.meta["maybe_column"] = True 4334 normalized_table = _norm(normalized_table, dialect=self.dialect) 4335 4336 if isinstance(table, exp.Table) and not join.args.get("on"): 4337 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4338 table_as_column = table.to_column() 4339 unnest = exp.Unnest(expressions=[table_as_column]) 4340 4341 # Table.to_column creates a parent Alias node that we want to convert to 4342 # a TableAlias and attach to the Unnest, so it matches the parser's output 4343 if isinstance(table.args.get("alias"), exp.TableAlias): 4344 table_as_column.replace(table_as_column.this) 4345 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4346 4347 table.replace(unnest) 4348 4349 refs.add(normalized_table.alias_or_name) 4350 4351 return this 4352 4353 @t.overload 4354 def _parse_query_modifiers(self, this: E) -> E: ... 4355 4356 @t.overload 4357 def _parse_query_modifiers(self, this: None) -> None: ... 4358 4359 def _parse_query_modifiers(self, this): 4360 if isinstance(this, self.MODIFIABLES): 4361 for join in self._parse_joins(): 4362 this.append("joins", join) 4363 for lateral in iter(self._parse_lateral, None): 4364 this.append("laterals", lateral) 4365 4366 while True: 4367 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4368 modifier_token = self._curr 4369 4370 # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result 4371 # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union 4372 if ( 4373 modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH) 4374 and (limit := this.args.get("limit")) 4375 and limit.meta.get("top") 4376 ): 4377 break 4378 4379 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4380 key, expression = parser(self) 4381 4382 if expression: 4383 if this.args.get(key): 4384 self.raise_error( 4385 f"Found multiple '{modifier_token.text.upper()}' clauses", 4386 token=modifier_token, 4387 ) 4388 4389 this.set(key, expression) 4390 if key == "limit": 4391 offset = expression.args.get("offset") 4392 expression.set("offset", None) 4393 4394 if offset: 4395 if this.args.get("offset"): 4396 self.raise_error( 4397 "Found multiple 'OFFSET' clauses", token=modifier_token 4398 ) 4399 4400 offset = exp.Offset(expression=offset) 4401 this.set("offset", offset) 4402 4403 limit_by_expressions = expression.expressions 4404 expression.set("expressions", None) 4405 offset.set("expressions", limit_by_expressions) 4406 continue 4407 4408 if self._curr.text.upper() == "START": 4409 modifier_token = self._curr 4410 connect = self._parse_connect() 4411 if connect: 4412 if this.args.get("connect"): 4413 self.raise_error( 4414 "Found multiple 'START WITH' clauses", token=modifier_token 4415 ) 4416 4417 this.set("connect", connect) 4418 continue 4419 break 4420 4421 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4422 this = self._implicit_unnests_to_explicit(this) 4423 4424 return this 4425 4426 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4427 start = self._curr 4428 while self._curr: 4429 self._advance() 4430 4431 end = self._tokens[self._index - 1] 4432 return exp.Hint(expressions=[self._find_sql(start, end)]) 4433 4434 def _parse_hint_function_call(self) -> exp.Expr | None: 4435 return self._parse_function_call() 4436 4437 def _parse_hint_body(self) -> exp.Hint | None: 4438 start_index = self._index 4439 should_fallback_to_string = False 4440 4441 hints = [] 4442 try: 4443 for hint in iter( 4444 lambda: self._parse_csv( 4445 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4446 ), 4447 [], 4448 ): 4449 hints.extend(hint) 4450 except ParseError: 4451 should_fallback_to_string = True 4452 4453 if should_fallback_to_string or self._curr: 4454 self._retreat(start_index) 4455 return self._parse_hint_fallback_to_string() 4456 4457 return self.expression(exp.Hint(expressions=hints)) 4458 4459 def _parse_hint(self) -> exp.Hint | None: 4460 if self._match(TokenType.HINT) and self._prev_comments: 4461 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4462 4463 return None 4464 4465 def _parse_into(self) -> exp.Into | None: 4466 if not self._match(TokenType.INTO): 4467 return None 4468 4469 temp = self._match(TokenType.TEMPORARY) 4470 unlogged = self._match_text_seq("UNLOGGED") 4471 self._match(TokenType.TABLE) 4472 4473 return self.expression( 4474 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4475 ) 4476 4477 def _parse_from( 4478 self, 4479 joins: bool = False, 4480 skip_from_token: bool = False, 4481 consume_pipe: bool = False, 4482 ) -> exp.From | None: 4483 if not skip_from_token and not self._match(TokenType.FROM): 4484 return None 4485 4486 comments = self._prev_comments 4487 return self.expression( 4488 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4489 comments=comments, 4490 ) 4491 4492 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4493 return self.expression( 4494 exp.MatchRecognizeMeasure( 4495 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4496 this=self._parse_expression(), 4497 ) 4498 ) 4499 4500 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4501 if not self._match(TokenType.MATCH_RECOGNIZE): 4502 return None 4503 4504 self._match_l_paren() 4505 4506 partition = self._parse_partition_by() 4507 order = self._parse_order() 4508 4509 measures = ( 4510 self._parse_csv(self._parse_match_recognize_measure) 4511 if self._match_text_seq("MEASURES") 4512 else None 4513 ) 4514 4515 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4516 rows = exp.var("ONE ROW PER MATCH") 4517 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4518 text = "ALL ROWS PER MATCH" 4519 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4520 text += " SHOW EMPTY MATCHES" 4521 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4522 text += " OMIT EMPTY MATCHES" 4523 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4524 text += " WITH UNMATCHED ROWS" 4525 rows = exp.var(text) 4526 else: 4527 rows = None 4528 4529 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4530 text = "AFTER MATCH SKIP" 4531 if self._match_text_seq("PAST", "LAST", "ROW"): 4532 text += " PAST LAST ROW" 4533 elif self._match_text_seq("TO", "NEXT", "ROW"): 4534 text += " TO NEXT ROW" 4535 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4536 direction = self._prev.text.upper() 4537 pattern_var = self._advance_any() 4538 if not pattern_var: 4539 self.raise_error( 4540 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4541 ) 4542 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4543 after = exp.var(text) 4544 else: 4545 after = None 4546 4547 if self._match_text_seq("PATTERN"): 4548 self._match_l_paren() 4549 4550 if not self._curr: 4551 self.raise_error("Expecting )", self._curr) 4552 4553 paren = 1 4554 start = self._curr 4555 4556 while self._curr and paren > 0: 4557 if self._curr.token_type == TokenType.L_PAREN: 4558 paren += 1 4559 if self._curr.token_type == TokenType.R_PAREN: 4560 paren -= 1 4561 4562 end = self._prev 4563 self._advance() 4564 4565 if paren > 0: 4566 self.raise_error("Expecting )", self._curr) 4567 4568 pattern = exp.var(self._find_sql(start, end)) 4569 else: 4570 pattern = None 4571 4572 define = ( 4573 self._parse_csv(self._parse_name_as_expression) 4574 if self._match_text_seq("DEFINE") 4575 else None 4576 ) 4577 4578 self._match_r_paren() 4579 4580 return self.expression( 4581 exp.MatchRecognize( 4582 partition_by=partition, 4583 order=order, 4584 measures=measures, 4585 rows=rows, 4586 after=after, 4587 pattern=pattern, 4588 define=define, 4589 alias=self._parse_table_alias(), 4590 ) 4591 ) 4592 4593 def _parse_lateral(self) -> exp.Lateral | None: 4594 cross_apply: bool | None = None 4595 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4596 cross_apply = True 4597 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4598 cross_apply = False 4599 4600 if cross_apply is not None: 4601 this = self._parse_select(table=True) 4602 view = None 4603 outer = None 4604 elif self._match(TokenType.LATERAL): 4605 this = self._parse_select(table=True) 4606 view = self._match(TokenType.VIEW) 4607 outer = self._match(TokenType.OUTER) 4608 else: 4609 return None 4610 4611 if not this: 4612 this = ( 4613 self._parse_unnest() 4614 or self._parse_function() 4615 or self._parse_id_var(any_token=False) 4616 ) 4617 4618 while self._match(TokenType.DOT): 4619 this = exp.Dot( 4620 this=this, 4621 expression=self._parse_function() or self._parse_id_var(any_token=False), 4622 ) 4623 4624 ordinality: bool | None = None 4625 4626 if view: 4627 table = self._parse_id_var(any_token=False) 4628 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4629 table_alias: exp.TableAlias | None = self.expression( 4630 exp.TableAlias(this=table, columns=columns) 4631 ) 4632 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4633 # We move the alias from the lateral's child node to the lateral itself 4634 table_alias = this.args["alias"].pop() 4635 else: 4636 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4637 table_alias = self._parse_table_alias() 4638 4639 return self.expression( 4640 exp.Lateral( 4641 this=this, 4642 view=view, 4643 outer=outer, 4644 alias=table_alias, 4645 cross_apply=cross_apply, 4646 ordinality=ordinality, 4647 ) 4648 ) 4649 4650 def _parse_stream(self) -> exp.Stream | None: 4651 index = self._index 4652 if self._match(TokenType.STREAM): 4653 if this := self._try_parse(self._parse_table): 4654 return self.expression(exp.Stream(this=this)) 4655 self._retreat(index) 4656 return None 4657 4658 def _parse_join_parts( 4659 self, 4660 ) -> tuple[Token | None, Token | None, Token | None]: 4661 return ( 4662 self._prev if self._match_set(self.JOIN_METHODS) else None, 4663 self._prev if self._match_set(self.JOIN_SIDES) else None, 4664 self._prev if self._match_set(self.JOIN_KINDS) else None, 4665 ) 4666 4667 def _parse_using_identifiers(self) -> list[exp.Expr]: 4668 def _parse_column_as_identifier() -> exp.Expr | None: 4669 this = self._parse_column() 4670 if isinstance(this, exp.Column): 4671 return this.this 4672 return this 4673 4674 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4675 4676 def _parse_join( 4677 self, 4678 skip_join_token: bool = False, 4679 parse_bracket: bool = False, 4680 alias_tokens: t.Collection[TokenType] | None = None, 4681 ) -> exp.Join | None: 4682 if self._match(TokenType.COMMA): 4683 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4684 cross_join = self.expression(exp.Join(this=table)) if table else None 4685 4686 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4687 cross_join.set("kind", "CROSS") 4688 4689 return cross_join 4690 4691 index = self._index 4692 method, side, kind = self._parse_join_parts() 4693 directed = self._match_text_seq("DIRECTED") 4694 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4695 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4696 join_comments = self._prev_comments 4697 4698 if not skip_join_token and not join: 4699 self._retreat(index) 4700 kind = None 4701 method = None 4702 side = None 4703 4704 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4705 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4706 4707 if not skip_join_token and not join and not outer_apply and not cross_apply: 4708 return None 4709 4710 kwargs: dict[str, t.Any] = { 4711 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4712 } 4713 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4714 kwargs["expressions"] = self._parse_csv( 4715 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4716 ) 4717 4718 if method: 4719 kwargs["method"] = method.text.upper() 4720 if side: 4721 kwargs["side"] = side.text.upper() 4722 if kind: 4723 kwargs["kind"] = kind.text.upper() 4724 if hint: 4725 kwargs["hint"] = hint 4726 4727 if self._match(TokenType.MATCH_CONDITION): 4728 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4729 4730 if self._match(TokenType.ON): 4731 kwargs["on"] = self._parse_disjunction() 4732 elif self._match(TokenType.USING): 4733 kwargs["using"] = self._parse_using_identifiers() 4734 elif ( 4735 not method 4736 and not (outer_apply or cross_apply) 4737 and not isinstance(kwargs["this"], exp.Unnest) 4738 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4739 ): 4740 index = self._index 4741 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4742 4743 if joins and self._match(TokenType.ON): 4744 kwargs["on"] = self._parse_disjunction() 4745 elif joins and self._match(TokenType.USING): 4746 kwargs["using"] = self._parse_using_identifiers() 4747 else: 4748 joins = None 4749 self._retreat(index) 4750 4751 kwargs["this"].set("joins", joins if joins else None) 4752 4753 kwargs["pivots"] = self._parse_pivots() 4754 4755 comments = [c for token in (method, side, kind) if token for c in token.comments] 4756 comments = (join_comments or []) + comments 4757 4758 if ( 4759 self.ADD_JOIN_ON_TRUE 4760 and not kwargs.get("on") 4761 and not kwargs.get("using") 4762 and not kwargs.get("method") 4763 and kwargs.get("kind") in (None, "INNER", "OUTER") 4764 ): 4765 kwargs["on"] = exp.true() 4766 4767 if directed: 4768 kwargs["directed"] = directed 4769 4770 return self.expression(exp.Join(**kwargs), comments=comments) 4771 4772 def _parse_opclass(self) -> exp.Expr | None: 4773 this = self._parse_disjunction() 4774 4775 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4776 return this 4777 4778 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4779 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4780 4781 return this 4782 4783 def _parse_index_params(self) -> exp.IndexParameters: 4784 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4785 4786 if self._match(TokenType.L_PAREN, advance=False): 4787 columns = self._parse_wrapped_csv(self._parse_with_operator) 4788 else: 4789 columns = None 4790 4791 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4792 partition_by = self._parse_partition_by() 4793 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4794 tablespace = ( 4795 self._parse_var(any_token=True) 4796 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4797 else None 4798 ) 4799 where = self._parse_where() 4800 4801 on = self._parse_field() if self._match(TokenType.ON) else None 4802 4803 return self.expression( 4804 exp.IndexParameters( 4805 using=using, 4806 columns=columns, 4807 include=include, 4808 partition_by=partition_by, 4809 where=where, 4810 with_storage=with_storage, 4811 tablespace=tablespace, 4812 on=on, 4813 ) 4814 ) 4815 4816 def _parse_index( 4817 self, index: exp.Expr | None = None, anonymous: bool = False 4818 ) -> exp.Index | None: 4819 if index or anonymous: 4820 unique = None 4821 primary = None 4822 amp = None 4823 4824 self._match(TokenType.ON) 4825 self._match(TokenType.TABLE) # hive 4826 table = self._parse_table_parts(schema=True) 4827 else: 4828 unique = self._match(TokenType.UNIQUE) 4829 primary = self._match_text_seq("PRIMARY") 4830 amp = self._match_text_seq("AMP") 4831 4832 if not self._match(TokenType.INDEX): 4833 return None 4834 4835 index = self._parse_id_var() 4836 table = None 4837 4838 params = self._parse_index_params() 4839 4840 return self.expression( 4841 exp.Index( 4842 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4843 ) 4844 ) 4845 4846 def _parse_table_hints(self) -> list[exp.Expr] | None: 4847 hints: list[exp.Expr] = [] 4848 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4849 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4850 hints.append( 4851 self.expression( 4852 exp.WithTableHint( 4853 expressions=self._parse_csv( 4854 lambda: self._parse_function() or self._parse_var(any_token=True) 4855 ) 4856 ) 4857 ) 4858 ) 4859 self._match_r_paren() 4860 else: 4861 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4862 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4863 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4864 4865 self._match_set((TokenType.INDEX, TokenType.KEY)) 4866 if self._match(TokenType.FOR): 4867 hint.set("target", self._advance_any() and self._prev.text.upper()) 4868 4869 hint.set("expressions", self._parse_wrapped_id_vars()) 4870 hints.append(hint) 4871 4872 return hints or None 4873 4874 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 4875 return ( 4876 (not schema and self._parse_function(optional_parens=False)) 4877 or self._parse_id_var(any_token=False) 4878 or self._parse_string_as_identifier() 4879 or self._parse_placeholder() 4880 ) 4881 4882 def _parse_table_parts_fast(self) -> exp.Table | None: 4883 index = self._index 4884 parts: list[exp.Identifier] | None = None 4885 all_comments: list[str] | None = None 4886 4887 while self._match_set(self.IDENTIFIER_TOKENS): 4888 token = self._prev 4889 comments = self._prev_comments 4890 4891 has_dot = self._match(TokenType.DOT) 4892 curr_tt = self._curr.token_type 4893 4894 if not has_dot: 4895 if curr_tt in self.TABLE_POSTFIX_TOKENS: 4896 self._retreat(index) 4897 return None 4898 elif curr_tt not in self.IDENTIFIER_TOKENS: 4899 self._retreat(index) 4900 return None 4901 4902 if parts is None: 4903 parts = [] 4904 4905 if comments: 4906 if all_comments is None: 4907 all_comments = [] 4908 all_comments.extend(comments) 4909 self._prev_comments = [] 4910 4911 parts.append( 4912 self.expression( 4913 exp.Identifier( 4914 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 4915 ), 4916 token, 4917 ) 4918 ) 4919 4920 if not has_dot: 4921 break 4922 4923 if parts is None: 4924 return None 4925 4926 n = len(parts) 4927 4928 if n == 1: 4929 table: exp.Table = exp.Table(this=parts[0]) 4930 elif n == 2: 4931 table = exp.Table(this=parts[1], db=parts[0]) 4932 elif n >= 3: 4933 this: exp.Identifier | exp.Dot = parts[2] 4934 for i in range(3, n): 4935 this = exp.Dot(this=this, expression=parts[i]) 4936 4937 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 4938 4939 if table is None: 4940 self._retreat(index) 4941 elif all_comments: 4942 table.add_comments(all_comments) 4943 return table 4944 4945 def _parse_table_parts( 4946 self, 4947 schema: bool = False, 4948 is_db_reference: bool = False, 4949 wildcard: bool = False, 4950 fast: bool = False, 4951 ) -> exp.Table | exp.Dot | None: 4952 if fast: 4953 return self._parse_table_parts_fast() 4954 4955 catalog: exp.Expr | str | None = None 4956 db: exp.Expr | str | None = None 4957 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 4958 4959 while self._match(TokenType.DOT): 4960 if catalog: 4961 # This allows nesting the table in arbitrarily many dot expressions if needed 4962 table = self.expression( 4963 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 4964 ) 4965 else: 4966 catalog = db 4967 db = table 4968 # "" used for tsql FROM a..b case 4969 table = self._parse_table_part(schema=schema) or "" 4970 4971 if ( 4972 wildcard 4973 and self._is_connected() 4974 and (isinstance(table, exp.Identifier) or not table) 4975 and self._match(TokenType.STAR) 4976 ): 4977 if isinstance(table, exp.Identifier): 4978 table.args["this"] += "*" 4979 else: 4980 table = exp.Identifier(this="*") 4981 4982 if is_db_reference: 4983 catalog = db 4984 db = table 4985 table = None 4986 4987 if not table and not is_db_reference: 4988 self.raise_error(f"Expected table name but got {self._curr}") 4989 if not db and is_db_reference: 4990 self.raise_error(f"Expected database name but got {self._curr}") 4991 4992 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 4993 4994 # Bubble up comments from identifier parts to the Table 4995 comments = [] 4996 for part in table.parts: 4997 if part_comments := part.pop_comments(): 4998 comments.extend(part_comments) 4999 if comments: 5000 table.add_comments(comments) 5001 5002 changes = self._parse_changes() 5003 if changes: 5004 table.set("changes", changes) 5005 5006 at_before = self._parse_historical_data() 5007 if at_before: 5008 table.set("when", at_before) 5009 5010 pivots = self._parse_pivots() 5011 if pivots: 5012 table.set("pivots", pivots) 5013 5014 return table 5015 5016 def _parse_table( 5017 self, 5018 schema: bool = False, 5019 joins: bool = False, 5020 alias_tokens: t.Collection[TokenType] | None = None, 5021 parse_bracket: bool = False, 5022 is_db_reference: bool = False, 5023 parse_partition: bool = False, 5024 consume_pipe: bool = False, 5025 ) -> exp.Expr | None: 5026 if not schema and not is_db_reference and not consume_pipe and not joins: 5027 index = self._index 5028 table = self._parse_table_parts(fast=True) 5029 5030 if table is not None: 5031 curr_tt = self._curr.token_type 5032 next_tt = self._next.token_type 5033 5034 fast_terminators = self.TABLE_TERMINATORS 5035 5036 # only return the table if we're sure there are no other operators 5037 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5038 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5039 return table 5040 5041 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5042 5043 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5044 if alias := self._parse_table_alias( 5045 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5046 ): 5047 table.set("alias", alias) 5048 5049 if self._curr.token_type in fast_terminators: 5050 return table 5051 5052 self._retreat(index) 5053 5054 if stream := self._parse_stream(): 5055 return stream 5056 5057 if lateral := self._parse_lateral(): 5058 return lateral 5059 5060 if unnest := self._parse_unnest(): 5061 return unnest 5062 5063 if values := self._parse_derived_table_values(): 5064 return values 5065 5066 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5067 if not subquery.args.get("pivots"): 5068 subquery.set("pivots", self._parse_pivots()) 5069 if joins: 5070 for join in self._parse_joins(): 5071 subquery.append("joins", join) 5072 return subquery 5073 5074 bracket = parse_bracket and self._parse_bracket(None) 5075 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5076 5077 rows_from_tables = ( 5078 self._parse_wrapped_csv(self._parse_table) 5079 if self._match_text_seq("ROWS", "FROM") 5080 else None 5081 ) 5082 rows_from = ( 5083 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5084 ) 5085 5086 only = self._match(TokenType.ONLY) 5087 5088 this = t.cast( 5089 exp.Expr, 5090 bracket 5091 or rows_from 5092 or self._parse_bracket( 5093 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5094 ), 5095 ) 5096 5097 if only: 5098 this.set("only", only) 5099 5100 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5101 self._match(TokenType.STAR) 5102 5103 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5104 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5105 this.set("partition", self._parse_partition()) 5106 5107 if schema: 5108 return self._parse_schema(this=this) 5109 5110 if self.dialect.ALIAS_POST_VERSION: 5111 this.set("version", self._parse_version()) 5112 5113 if self.dialect.ALIAS_POST_TABLESAMPLE: 5114 this.set("sample", self._parse_table_sample()) 5115 5116 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5117 if alias: 5118 this.set("alias", alias) 5119 5120 # DuckDB requires the time-travel clause to come after the alias, e.g. 5121 # SELECT * FROM t AS a AT (VERSION => 1) 5122 if isinstance(this, exp.Table) and not this.args.get("when"): 5123 this.set("when", self._parse_historical_data()) 5124 5125 if self._match(TokenType.INDEXED_BY): 5126 this.set("indexed", self._parse_table_parts()) 5127 elif self._match_text_seq("NOT", "INDEXED"): 5128 this.set("indexed", False) 5129 5130 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5131 return self.expression( 5132 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5133 ) 5134 5135 this.set("hints", self._parse_table_hints()) 5136 5137 if not this.args.get("pivots"): 5138 this.set("pivots", self._parse_pivots()) 5139 5140 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5141 this.set("sample", self._parse_table_sample()) 5142 5143 if not self.dialect.ALIAS_POST_VERSION: 5144 this.set("version", self._parse_version()) 5145 5146 if joins: 5147 for join in self._parse_joins(alias_tokens=alias_tokens): 5148 this.append("joins", join) 5149 5150 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5151 this.set("ordinality", True) 5152 this.set("alias", self._parse_table_alias()) 5153 5154 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5155 # hoist the table args onto the latter and return it instead 5156 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5157 table_from_rows = this.this 5158 for arg in exp.TableFromRows.arg_types: 5159 if arg != "this": 5160 table_from_rows.set(arg, this.args.get(arg)) 5161 5162 this = table_from_rows 5163 5164 return this 5165 5166 def _parse_version(self) -> exp.Version | None: 5167 for phrase, this in self.VERSION_PHRASES.items(): 5168 if self._match_text_seq(*phrase): 5169 break 5170 else: 5171 return None 5172 5173 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5174 kind = self._prev.text.upper() 5175 start = self._parse_bitwise() 5176 self._match_texts(("TO", "AND")) 5177 end = self._parse_bitwise() 5178 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5179 elif self._match_text_seq("CONTAINED", "IN"): 5180 kind = "CONTAINED IN" 5181 expression = self.expression( 5182 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5183 ) 5184 elif self._match(TokenType.ALL): 5185 kind = "ALL" 5186 expression = None 5187 else: 5188 self._match_text_seq("AS", "OF") 5189 kind = "AS OF" 5190 expression = self._parse_type() 5191 5192 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5193 5194 def _parse_historical_data(self) -> exp.HistoricalData | None: 5195 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5196 index = self._index 5197 historical_data = None 5198 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5199 this = self._prev.text.upper() 5200 kind = ( 5201 self._match(TokenType.L_PAREN) 5202 and self._match_texts(self.HISTORICAL_DATA_KIND) 5203 and self._prev.text.upper() 5204 ) 5205 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5206 5207 if expression: 5208 self._match_r_paren() 5209 historical_data = self.expression( 5210 exp.HistoricalData(this=this, kind=kind, expression=expression) 5211 ) 5212 else: 5213 self._retreat(index) 5214 5215 return historical_data 5216 5217 def _parse_changes(self) -> exp.Changes | None: 5218 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5219 return None 5220 5221 information = self._parse_var(any_token=True) 5222 self._match_r_paren() 5223 5224 return self.expression( 5225 exp.Changes( 5226 information=information, 5227 at_before=self._parse_historical_data(), 5228 end=self._parse_historical_data(), 5229 ) 5230 ) 5231 5232 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5233 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5234 return None 5235 5236 self._advance() 5237 5238 expressions = self._parse_wrapped_csv(self._parse_equality) 5239 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5240 5241 alias = self._parse_table_alias() if with_alias else None 5242 5243 if alias: 5244 if self.dialect.UNNEST_COLUMN_ONLY: 5245 if alias.args.get("columns"): 5246 self.raise_error("Unexpected extra column alias in unnest.") 5247 5248 alias.set("columns", [alias.this]) 5249 alias.set("this", None) 5250 5251 columns = alias.args.get("columns") or [] 5252 if offset and len(expressions) < len(columns): 5253 offset = columns.pop() 5254 5255 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5256 self._match(TokenType.ALIAS) 5257 offset = self._parse_id_var( 5258 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5259 ) or exp.to_identifier("offset") 5260 5261 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5262 5263 def _parse_derived_table_values(self) -> exp.Values | None: 5264 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5265 if not is_derived and not ( 5266 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5267 self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES") 5268 ): 5269 return None 5270 5271 expressions = self._parse_csv(self._parse_value) 5272 alias = self._parse_table_alias() 5273 5274 if is_derived: 5275 self._match_r_paren() 5276 5277 return self.expression( 5278 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5279 ) 5280 5281 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5282 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5283 as_modifier and self._match_text_seq("USING", "SAMPLE") 5284 ): 5285 return None 5286 5287 bucket_numerator = None 5288 bucket_denominator = None 5289 bucket_field = None 5290 percent = None 5291 size = None 5292 seed = None 5293 5294 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5295 matched_l_paren = self._match(TokenType.L_PAREN) 5296 5297 if self.TABLESAMPLE_CSV: 5298 num = None 5299 expressions = self._parse_csv(self._parse_primary) 5300 else: 5301 expressions = None 5302 num = ( 5303 self._parse_factor(parse_mod=False) 5304 if self._match(TokenType.NUMBER, advance=False) 5305 else self._parse_primary() or self._parse_placeholder() 5306 ) 5307 5308 if self._match_text_seq("BUCKET"): 5309 bucket_numerator = self._parse_number() 5310 self._match_text_seq("OUT", "OF") 5311 bucket_denominator = bucket_denominator = self._parse_number() 5312 self._match(TokenType.ON) 5313 bucket_field = self._parse_field() 5314 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5315 percent = num 5316 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5317 size = num 5318 else: 5319 percent = num 5320 5321 if matched_l_paren: 5322 self._match_r_paren() 5323 5324 if self._match(TokenType.L_PAREN): 5325 method = self._parse_var(upper=True) 5326 seed = self._match(TokenType.COMMA) and self._parse_number() 5327 self._match_r_paren() 5328 elif self._match_texts(("SEED", "REPEATABLE")): 5329 seed = self._parse_wrapped(self._parse_number) 5330 5331 if not method and self.DEFAULT_SAMPLING_METHOD: 5332 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5333 5334 return self.expression( 5335 exp.TableSample( 5336 expressions=expressions, 5337 method=method, 5338 bucket_numerator=bucket_numerator, 5339 bucket_denominator=bucket_denominator, 5340 bucket_field=bucket_field, 5341 percent=percent, 5342 size=size, 5343 seed=seed, 5344 ) 5345 ) 5346 5347 def _parse_pivots(self) -> list[exp.Pivot] | None: 5348 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5349 return None 5350 return list(iter(self._parse_pivot, None)) or None 5351 5352 def _parse_joins( 5353 self, alias_tokens: t.Collection[TokenType] | None = None 5354 ) -> t.Iterator[exp.Join]: 5355 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5356 5357 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5358 if not self._match(TokenType.INTO): 5359 return None 5360 5361 return self.expression( 5362 exp.UnpivotColumns( 5363 this=self._match_text_seq("NAME") and self._parse_column(), 5364 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5365 ) 5366 ) 5367 5368 # https://duckdb.org/docs/sql/statements/pivot 5369 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5370 def _parse_on() -> exp.Expr | None: 5371 this = self._parse_bitwise() 5372 5373 if self._match(TokenType.IN): 5374 # PIVOT ... ON col IN (row_val1, row_val2) 5375 return self._parse_in(this) 5376 if self._match(TokenType.ALIAS, advance=False): 5377 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5378 return self._parse_alias(this) 5379 5380 return this 5381 5382 this = self._parse_table() 5383 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5384 into = self._parse_unpivot_columns() 5385 using = self._match(TokenType.USING) and self._parse_csv( 5386 lambda: self._parse_alias(self._parse_column()) 5387 ) 5388 group = self._parse_group() 5389 5390 return self.expression( 5391 exp.Pivot( 5392 this=this, 5393 expressions=expressions, 5394 using=using, 5395 group=group, 5396 unpivot=is_unpivot, 5397 into=into, 5398 ) 5399 ) 5400 5401 def _parse_pivot_in(self) -> exp.In: 5402 def _parse_aliased_expression() -> exp.Expr | None: 5403 this = self._parse_select_or_expression() 5404 5405 self._match(TokenType.ALIAS) 5406 alias = self._parse_bitwise() 5407 if alias: 5408 if isinstance(alias, exp.Column) and not alias.db: 5409 alias = alias.this 5410 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5411 5412 return this 5413 5414 value = self._parse_column() 5415 5416 if not self._match(TokenType.IN): 5417 self.raise_error("Expecting IN") 5418 5419 if self._match(TokenType.L_PAREN): 5420 if self._match(TokenType.ANY): 5421 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5422 else: 5423 exprs = self._parse_csv(_parse_aliased_expression) 5424 self._match_r_paren() 5425 return self.expression(exp.In(this=value, expressions=exprs)) 5426 5427 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5428 5429 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5430 func = self._parse_function() 5431 if not func: 5432 if self._prev.token_type == TokenType.COMMA: 5433 return None 5434 self.raise_error("Expecting an aggregation function in PIVOT") 5435 5436 return self._parse_alias(func) 5437 5438 def _parse_pivot(self) -> exp.Pivot | None: 5439 index = self._index 5440 include_nulls = None 5441 5442 if self._match(TokenType.PIVOT): 5443 unpivot = False 5444 elif self._match(TokenType.UNPIVOT): 5445 unpivot = True 5446 5447 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5448 if self._match_text_seq("INCLUDE", "NULLS"): 5449 include_nulls = True 5450 elif self._match_text_seq("EXCLUDE", "NULLS"): 5451 include_nulls = False 5452 else: 5453 return None 5454 5455 expressions = [] 5456 5457 if not self._match(TokenType.L_PAREN): 5458 self._retreat(index) 5459 return None 5460 5461 if unpivot: 5462 expressions = self._parse_csv(self._parse_column) 5463 else: 5464 expressions = self._parse_csv(self._parse_pivot_aggregation) 5465 5466 if not expressions: 5467 self.raise_error("Failed to parse PIVOT's aggregation list") 5468 5469 if not self._match(TokenType.FOR): 5470 self.raise_error("Expecting FOR") 5471 5472 fields = [] 5473 while True: 5474 field = self._try_parse(self._parse_pivot_in) 5475 if not field: 5476 break 5477 fields.append(field) 5478 5479 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5480 self._parse_bitwise 5481 ) 5482 5483 group = self._parse_group() 5484 5485 self._match_r_paren() 5486 5487 pivot = self.expression( 5488 exp.Pivot( 5489 expressions=expressions, 5490 fields=fields, 5491 unpivot=unpivot, 5492 include_nulls=include_nulls, 5493 default_on_null=default_on_null, 5494 group=group, 5495 ) 5496 ) 5497 5498 if unpivot: 5499 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5500 for pivot_field in pivot.fields: 5501 if isinstance(pivot_field, exp.In): 5502 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5503 5504 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5505 5506 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5507 pivot.set("alias", self._parse_table_alias()) 5508 5509 if not unpivot: 5510 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5511 5512 columns: list[exp.Expr] = [] 5513 all_fields = [] 5514 for pivot_field in pivot.fields: 5515 pivot_field_expressions = pivot_field.expressions 5516 5517 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5518 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5519 continue 5520 5521 all_fields.append( 5522 [ 5523 # An explicit `<field> AS <alias>` names the output column directly, 5524 # so it wins over the dialect's string-identifying convention 5525 fld.sql() 5526 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5527 else fld.alias_or_name 5528 for fld in pivot_field_expressions 5529 ] 5530 ) 5531 5532 if all_fields: 5533 if names: 5534 all_fields.append(names) 5535 5536 # Generate all possible combinations of the pivot columns 5537 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5538 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5539 for fld_parts_tuple in itertools.product(*all_fields): 5540 fld_parts = list(fld_parts_tuple) 5541 5542 if names and self.PREFIXED_PIVOT_COLUMNS: 5543 # Move the "name" to the front of the list 5544 fld_parts.insert(0, fld_parts.pop(-1)) 5545 5546 columns.append(exp.to_identifier("_".join(fld_parts))) 5547 5548 pivot.set("columns", columns) 5549 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5550 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5551 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5552 5553 return pivot 5554 5555 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5556 return [agg.alias for agg in aggregations if agg.alias] 5557 5558 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5559 if not skip_where_token and not self._match(TokenType.PREWHERE): 5560 return None 5561 5562 comments = self._prev_comments 5563 return self.expression( 5564 exp.PreWhere(this=self._parse_disjunction()), 5565 comments=comments, 5566 ) 5567 5568 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5569 if not skip_where_token and not self._match(TokenType.WHERE): 5570 return None 5571 5572 comments = self._prev_comments 5573 return self.expression( 5574 exp.Where(this=self._parse_disjunction()), 5575 comments=comments, 5576 ) 5577 5578 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5579 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5580 return None 5581 comments = self._prev_comments 5582 5583 elements: dict[str, t.Any] = defaultdict(list) 5584 5585 if self._match(TokenType.ALL): 5586 elements["all"] = True 5587 elif self._match(TokenType.DISTINCT): 5588 elements["all"] = False 5589 5590 while True: 5591 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5592 # which are also valid identifiers 5593 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5594 break 5595 5596 elements["expressions"].extend( 5597 self._parse_csv( 5598 lambda: ( 5599 self._parse_grouping_sets() 5600 or self._parse_cube_or_rollup() 5601 or self._parse_disjunction() 5602 ) 5603 ) 5604 ) 5605 5606 before_with_index = self._index 5607 5608 if self._match(TokenType.WITH) and ( 5609 cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True) 5610 ): 5611 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5612 elements[key].append(cube_or_rollup) 5613 elif grouping_sets := self._parse_grouping_sets(): 5614 # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...) 5615 elements["grouping_sets"].append(grouping_sets) 5616 break 5617 elif self._match_text_seq("TOTALS"): 5618 elements["totals"] = True # type: ignore 5619 5620 if before_with_index <= self._index <= before_with_index + 1: 5621 self._retreat(before_with_index) 5622 break 5623 5624 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5625 5626 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5627 if self._match(TokenType.CUBE): 5628 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5629 elif self._match(TokenType.ROLLUP): 5630 kind = exp.Rollup 5631 else: 5632 return None 5633 5634 return self.expression( 5635 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5636 ) 5637 5638 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5639 if self._match(TokenType.GROUPING_SETS): 5640 return self.expression( 5641 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5642 ) 5643 return None 5644 5645 def _parse_grouping_set(self) -> exp.Expr | None: 5646 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5647 5648 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5649 if not skip_having_token and not self._match(TokenType.HAVING): 5650 return None 5651 comments = self._prev_comments 5652 return self.expression( 5653 exp.Having(this=self._parse_disjunction()), 5654 comments=comments, 5655 ) 5656 5657 def _parse_qualify(self) -> exp.Qualify | None: 5658 if not self._match(TokenType.QUALIFY): 5659 return None 5660 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5661 5662 def _parse_connect_with_prior(self) -> exp.Expr | None: 5663 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5664 exp.Prior(this=self._parse_bitwise()) 5665 ) 5666 connect = self._parse_disjunction() 5667 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5668 return connect 5669 5670 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5671 if skip_start_token: 5672 start = None 5673 elif self._match_text_seq("START", "WITH"): 5674 start = self._parse_disjunction() 5675 else: 5676 return None 5677 5678 self._match(TokenType.CONNECT_BY) 5679 nocycle = self._match_text_seq("NOCYCLE") 5680 connect = self._parse_connect_with_prior() 5681 5682 if not start and self._match_text_seq("START", "WITH"): 5683 start = self._parse_disjunction() 5684 5685 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5686 5687 def _parse_name_as_expression(self) -> exp.Expr | None: 5688 this = self._parse_id_var(any_token=True) 5689 if self._match(TokenType.ALIAS): 5690 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5691 return this 5692 5693 def _parse_interpolate(self) -> list[exp.Expr] | None: 5694 if self._match_text_seq("INTERPOLATE"): 5695 return self._parse_wrapped_csv(self._parse_name_as_expression) 5696 return None 5697 5698 def _parse_order( 5699 self, this: exp.Expr | None = None, skip_order_token: bool = False 5700 ) -> exp.Expr | None: 5701 siblings = None 5702 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5703 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5704 return this 5705 5706 siblings = True 5707 5708 comments = self._prev_comments 5709 return self.expression( 5710 exp.Order( 5711 this=this, 5712 expressions=self._parse_csv(self._parse_ordered), 5713 siblings=siblings, 5714 ), 5715 comments=comments, 5716 ) 5717 5718 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5719 if not self._match(token): 5720 return None 5721 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5722 5723 def _parse_ordered( 5724 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5725 ) -> exp.Ordered | None: 5726 this = parse_method() if parse_method else self._parse_disjunction() 5727 if not this: 5728 return None 5729 5730 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5731 this = exp.var("ALL") 5732 5733 asc = self._match(TokenType.ASC) 5734 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5735 5736 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5737 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5738 5739 nulls_first = is_nulls_first or False 5740 explicitly_null_ordered = is_nulls_first or is_nulls_last 5741 5742 if ( 5743 not explicitly_null_ordered 5744 and ( 5745 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5746 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5747 ) 5748 and self.dialect.NULL_ORDERING != "nulls_are_last" 5749 ): 5750 nulls_first = True 5751 5752 if self._match_text_seq("WITH", "FILL"): 5753 with_fill = self.expression( 5754 exp.WithFill( 5755 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5756 to=self._match_text_seq("TO") and self._parse_bitwise(), 5757 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5758 interpolate=self._parse_interpolate(), 5759 ) 5760 ) 5761 else: 5762 with_fill = None 5763 5764 return self.expression( 5765 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5766 ) 5767 5768 def _parse_limit_options(self) -> exp.LimitOptions | None: 5769 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5770 rows = self._match_set((TokenType.ROW, TokenType.ROWS)) 5771 self._match_text_seq("ONLY") 5772 with_ties = self._match_text_seq("WITH", "TIES") 5773 5774 if not (percent or rows or with_ties): 5775 return None 5776 5777 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5778 5779 def _parse_limit( 5780 self, 5781 this: exp.Expr | None = None, 5782 top: bool = False, 5783 skip_limit_token: bool = False, 5784 ) -> exp.Expr | None: 5785 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5786 comments = self._prev_comments 5787 if top: 5788 limit_paren = self._match(TokenType.L_PAREN) 5789 expression = ( 5790 self._parse_term() or self._parse_select() 5791 if limit_paren 5792 else self._parse_number() 5793 ) 5794 5795 if limit_paren: 5796 self._match_r_paren() 5797 5798 else: 5799 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5800 return this 5801 5802 expression = self._parse_term(parse_mod=False) 5803 limit_options = self._parse_limit_options() 5804 5805 if self._match(TokenType.COMMA): 5806 offset = expression 5807 expression = self._parse_term() 5808 else: 5809 offset = None 5810 5811 limit_exp = self.expression( 5812 exp.Limit( 5813 this=this, 5814 expression=expression, 5815 offset=offset, 5816 limit_options=limit_options, 5817 expressions=self._parse_limit_by(), 5818 ), 5819 comments=comments, 5820 ) 5821 5822 if top: 5823 limit_exp.meta["top"] = True 5824 5825 return limit_exp 5826 5827 if self._match(TokenType.FETCH): 5828 direction = ( 5829 self._prev.text.upper() 5830 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5831 else "FIRST" 5832 ) 5833 5834 count = self._parse_field(tokens=self.FETCH_TOKENS) 5835 5836 return self.expression( 5837 exp.Fetch( 5838 direction=direction, count=count, limit_options=self._parse_limit_options() 5839 ) 5840 ) 5841 5842 return this 5843 5844 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5845 if not self._match(TokenType.OFFSET): 5846 return this 5847 5848 count = self._parse_term() 5849 self._match_set((TokenType.ROW, TokenType.ROWS)) 5850 5851 return self.expression( 5852 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5853 ) 5854 5855 def _can_parse_limit_or_offset(self) -> bool: 5856 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5857 return False 5858 5859 index = self._index 5860 result = bool( 5861 self._try_parse(self._parse_limit, retreat=True) 5862 or self._try_parse(self._parse_offset, retreat=True) 5863 ) 5864 self._retreat(index) 5865 5866 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 5867 if self._next.token_type == TokenType.MATCH_CONDITION: 5868 result = False 5869 5870 return result 5871 5872 def _can_parse_named_window(self) -> bool: 5873 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 5874 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 5875 if not self._match(TokenType.WINDOW, advance=False): 5876 return False 5877 5878 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 5879 if name is None or name.token_type not in self.ID_VAR_TOKENS: 5880 return False 5881 5882 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 5883 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 5884 return False 5885 5886 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 5887 return body is not None and body.token_type == TokenType.L_PAREN 5888 5889 def _parse_limit_by(self) -> list[exp.Expr] | None: 5890 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 5891 5892 def _parse_locks(self) -> list[exp.Lock]: 5893 locks = [] 5894 while True: 5895 update, key = None, None 5896 if self._match_text_seq("FOR", "UPDATE"): 5897 update = True 5898 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 5899 "LOCK", "IN", "SHARE", "MODE" 5900 ): 5901 update = False 5902 elif self._match_text_seq("FOR", "KEY", "SHARE"): 5903 update, key = False, True 5904 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 5905 update, key = True, True 5906 else: 5907 break 5908 5909 expressions = None 5910 if self._match_text_seq("OF"): 5911 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 5912 5913 wait: bool | exp.Expr | None = None 5914 if self._match_text_seq("NOWAIT"): 5915 wait = True 5916 elif self._match_text_seq("WAIT"): 5917 wait = self._parse_primary() 5918 elif self._match_text_seq("SKIP", "LOCKED"): 5919 wait = False 5920 5921 locks.append( 5922 self.expression( 5923 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 5924 ) 5925 ) 5926 5927 return locks 5928 5929 def parse_set_operation( 5930 self, this: exp.Expr | None, consume_pipe: bool = False 5931 ) -> exp.Expr | None: 5932 start = self._index 5933 _, side_token, kind_token = self._parse_join_parts() 5934 5935 side = side_token.text if side_token else None 5936 kind = kind_token.text if kind_token else None 5937 5938 if not self._match_set(self.SET_OPERATIONS): 5939 self._retreat(start) 5940 return None 5941 5942 token_type = self._prev.token_type 5943 5944 if token_type == TokenType.UNION: 5945 operation: type[exp.SetOperation] = exp.Union 5946 elif token_type == TokenType.EXCEPT: 5947 operation = exp.Except 5948 else: 5949 operation = exp.Intersect 5950 5951 comments = self._prev.comments 5952 5953 if self._match(TokenType.DISTINCT): 5954 distinct: bool | None = True 5955 elif self._match(TokenType.ALL): 5956 distinct = False 5957 else: 5958 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5959 if distinct is None: 5960 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5961 5962 by_name = ( 5963 self._match_text_seq("BY", "NAME") 5964 or self._match_text_seq("STRICT", "CORRESPONDING") 5965 or None 5966 ) 5967 if self._match_text_seq("CORRESPONDING"): 5968 by_name = True 5969 if not side and not kind: 5970 kind = "INNER" 5971 5972 on_column_list = None 5973 if by_name and self._match_texts(("ON", "BY")): 5974 on_column_list = self._parse_wrapped_csv(self._parse_column) 5975 5976 expression = self._parse_select( 5977 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5978 ) 5979 5980 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5981 # in _parse_cte and so that alias pushdown can reach into set operation branches 5982 if isinstance(this, exp.Values): 5983 this = self._values_to_select(this) 5984 if isinstance(expression, exp.Values): 5985 expression = self._values_to_select(expression) 5986 5987 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 5988 subquery = this.this 5989 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 5990 subquery.add_comments(this.pop_comments()) 5991 this = subquery 5992 5993 return self.expression( 5994 operation( 5995 this=this, 5996 distinct=distinct, 5997 by_name=by_name, 5998 expression=expression, 5999 side=side, 6000 kind=kind, 6001 on=on_column_list, 6002 ), 6003 comments=comments, 6004 ) 6005 6006 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 6007 while this: 6008 setop = self.parse_set_operation(this) 6009 if not setop: 6010 break 6011 this = setop 6012 6013 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6014 expression = this.expression 6015 6016 if expression: 6017 for arg in self.SET_OP_MODIFIERS: 6018 expr = expression.args.get(arg) 6019 if expr and not (arg == "limit" and expr.meta.get("top")): 6020 expression.set(arg, None) 6021 this.set(arg, expr) 6022 6023 # A trailing LIMIT/FETCH can coexist with TOP on the final operand. 6024 if self._curr.token_type in (TokenType.LIMIT, TokenType.FETCH): 6025 this = self._parse_query_modifiers(this) 6026 6027 return this 6028 6029 def _parse_expression(self) -> exp.Expr | None: 6030 return self._parse_alias(self._parse_assignment()) 6031 6032 def _parse_assignment(self) -> exp.Expr | None: 6033 this = self._parse_disjunction() 6034 if not this and self._next.token_type in self.ASSIGNMENT: 6035 # This allows us to parse <non-identifier token> := <expr> 6036 this = exp.column( 6037 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6038 ) 6039 6040 while self._match_set(self.ASSIGNMENT): 6041 if isinstance(this, exp.Column) and len(this.parts) == 1: 6042 this = this.this 6043 6044 comments = self._prev_comments 6045 this = self.expression( 6046 self.ASSIGNMENT[self._prev.token_type]( 6047 this=this, expression=self._parse_assignment() 6048 ), 6049 comments=comments, 6050 ) 6051 6052 return this 6053 6054 def _parse_disjunction(self) -> exp.Expr | None: 6055 this = self._parse_conjunction() 6056 while self._match_set(self.DISJUNCTION): 6057 comments = self._prev_comments 6058 this = self.expression( 6059 self.DISJUNCTION[self._prev.token_type]( 6060 this=this, expression=self._parse_conjunction() 6061 ), 6062 comments=comments, 6063 ) 6064 return this 6065 6066 def _parse_conjunction(self) -> exp.Expr | None: 6067 this = self._parse_equality() 6068 while self._match_set(self.CONJUNCTION): 6069 comments = self._prev_comments 6070 this = self.expression( 6071 self.CONJUNCTION[self._prev.token_type]( 6072 this=this, expression=self._parse_equality() 6073 ), 6074 comments=comments, 6075 ) 6076 return this 6077 6078 def _parse_equality(self) -> exp.Expr | None: 6079 this = self._parse_comparison() 6080 while self._match_set(self.EQUALITY): 6081 comments = self._prev_comments 6082 this = self.expression( 6083 self.EQUALITY[self._prev.token_type]( 6084 this=this, expression=self._parse_comparison() 6085 ), 6086 comments=comments, 6087 ) 6088 return this 6089 6090 def _parse_comparison(self) -> exp.Expr | None: 6091 this = self._parse_range() 6092 while self._match_set(self.COMPARISON): 6093 comments = self._prev_comments 6094 this = self.expression( 6095 self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()), 6096 comments=comments, 6097 ) 6098 return this 6099 6100 def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6101 this = this or self._parse_bitwise() 6102 6103 while True: 6104 negate = self._match(TokenType.NOT) 6105 if self._match_set(self.RANGE_PARSERS): 6106 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6107 if not expression: 6108 return this 6109 6110 this = expression 6111 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6112 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6113 elif self._match(TokenType.NOTNULL): 6114 # Postgres supports ISNULL and NOTNULL for conditions. 6115 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6116 if self.dialect.NORMALIZE_NOT_NULL: 6117 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6118 this = self.expression(exp.Not(this=this)) 6119 else: 6120 this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True)) 6121 else: 6122 if negate: 6123 self._retreat(self._index - 1) 6124 break 6125 6126 if negate: 6127 this = self._negate_range(this) 6128 if self._curr and ( 6129 self._curr.token_type == TokenType.NOT 6130 or self._curr.token_type in self.RANGE_PARSERS 6131 ): 6132 this = self.expression(exp.Paren(this=this)) 6133 6134 return this 6135 6136 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6137 if not this: 6138 return this 6139 6140 expression = this.this if isinstance(this, exp.Escape) else this 6141 if isinstance(expression, (exp.Like, exp.ILike)): 6142 expression.set("negate", True) 6143 return this 6144 6145 return self.expression(exp.Not(this=this)) 6146 6147 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6148 index = self._index - 1 6149 negate = self._match(TokenType.NOT) 6150 6151 if self._match_text_seq("DISTINCT", "FROM"): 6152 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6153 return self.expression(klass(this=this, expression=self._parse_bitwise())) 6154 6155 if self._match(TokenType.JSON): 6156 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6157 6158 if self._match_text_seq("WITH"): 6159 _with = True 6160 elif self._match_text_seq("WITHOUT"): 6161 _with = False 6162 else: 6163 _with = None 6164 6165 unique = self._match(TokenType.UNIQUE) 6166 self._match_text_seq("KEYS") 6167 expression: exp.Expr | None = self.expression( 6168 exp.JSON(this=kind, with_=_with, unique=unique) 6169 ) 6170 else: 6171 expression = self._parse_null() or self._parse_bitwise() 6172 if not expression: 6173 self._retreat(index) 6174 return None 6175 6176 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6177 this = self.expression(exp.Is(this=this, expression=expression, negate=True)) 6178 else: 6179 this = self.expression(exp.Is(this=this, expression=expression)) 6180 this = self.expression(exp.Not(this=this)) if negate else this 6181 6182 return self._parse_column_ops(this) 6183 6184 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6185 unnest = self._parse_unnest(with_alias=False) 6186 if unnest: 6187 this = self.expression(exp.In(this=this, unnest=unnest)) 6188 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6189 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6190 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6191 6192 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6193 this = self.expression( 6194 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6195 ) 6196 else: 6197 this = self.expression(exp.In(this=this, expressions=expressions)) 6198 6199 if matched_l_paren: 6200 self._match_r_paren(this) 6201 elif not self._match(TokenType.R_BRACKET, expression=this): 6202 self.raise_error("Expecting ]") 6203 else: 6204 this = self.expression(exp.In(this=this, field=self._parse_column())) 6205 6206 return this 6207 6208 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6209 symmetric = None 6210 if self._match_text_seq("SYMMETRIC"): 6211 symmetric = True 6212 elif self._match_text_seq("ASYMMETRIC"): 6213 symmetric = False 6214 6215 low = self._parse_bitwise() 6216 self._match(TokenType.AND) 6217 high = self._parse_bitwise() 6218 6219 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6220 6221 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6222 if not self._match(TokenType.ESCAPE): 6223 return this 6224 return self.expression( 6225 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6226 ) 6227 6228 def _parse_interval_span( 6229 self, this: exp.Expr, parse_function_unit: bool = True 6230 ) -> exp.Interval: 6231 # handle day-time format interval span with omitted units: 6232 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6233 interval_span_units_omitted = None 6234 if ( 6235 this 6236 and this.is_string 6237 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6238 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6239 ): 6240 index = self._index 6241 6242 # Var "TO" Var 6243 first_unit = self._parse_var(any_token=True, upper=True) 6244 second_unit = None 6245 if first_unit and self._match_text_seq("TO"): 6246 second_unit = self._parse_var(any_token=True, upper=True) 6247 6248 interval_span_units_omitted = not (first_unit and second_unit) 6249 6250 self._retreat(index) 6251 6252 unit_index = self._index 6253 if interval_span_units_omitted: 6254 unit = None 6255 else: 6256 # Only attempt to parse a unit if the current token can actually be one, so that a 6257 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6258 is_unit = self._curr is not None and ( 6259 self._curr.token_type == TokenType.VAR 6260 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6261 ) 6262 unit = self._parse_function() if parse_function_unit and is_unit else None 6263 if not unit and is_unit: 6264 unit = self._parse_var(any_token=True, upper=True) 6265 6266 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6267 # each INTERVAL expression into this canonical form so it's easy to transpile 6268 if this and this.is_number: 6269 try: 6270 this = exp.Literal.string(this.to_py()) 6271 except ValueError: 6272 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6273 elif this and this.is_string: 6274 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6275 if parts and unit: 6276 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6277 unit = None 6278 self._retreat(unit_index) 6279 6280 if len(parts) == 1: 6281 this = exp.Literal.string(parts[0][0]) 6282 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6283 6284 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6285 unit = self.expression( 6286 exp.IntervalSpan( 6287 this=unit, 6288 expression=self._parse_function() 6289 or self._parse_var(any_token=True, upper=True), 6290 ) 6291 ) 6292 6293 return self.expression(exp.Interval(this=this, unit=unit)) 6294 6295 def _parse_interval( 6296 self, require_interval: bool = True, parse_function_unit: bool = True 6297 ) -> exp.Add | exp.Interval | None: 6298 index = self._index 6299 6300 if not self._match(TokenType.INTERVAL) and require_interval: 6301 return None 6302 6303 if self._match(TokenType.STRING, advance=False): 6304 this = self._parse_primary() 6305 else: 6306 this = self._parse_term() 6307 6308 if not this or ( 6309 isinstance(this, exp.Column) 6310 and not this.table 6311 and not this.this.quoted 6312 and self._curr 6313 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6314 ): 6315 self._retreat(index) 6316 return None 6317 6318 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6319 6320 index = self._index 6321 self._match(TokenType.PLUS) 6322 6323 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6324 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6325 return self.expression( 6326 exp.Add( 6327 this=interval, 6328 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6329 ) 6330 ) 6331 6332 self._retreat(index) 6333 return interval 6334 6335 def _parse_bitwise(self) -> exp.Expr | None: 6336 this = self._parse_term() 6337 6338 while True: 6339 if self._match_set(self.BITWISE): 6340 this = self.expression( 6341 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6342 ) 6343 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6344 this = self.expression( 6345 exp.DPipe( 6346 this=this, 6347 expression=self._parse_term(), 6348 safe=not self.dialect.STRICT_STRING_CONCAT, 6349 ) 6350 ) 6351 elif self._match(TokenType.DQMARK): 6352 this = self.expression( 6353 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6354 ) 6355 elif self._match_pair(TokenType.LT, TokenType.LT): 6356 this = self.expression( 6357 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6358 ) 6359 elif self._match_pair(TokenType.GT, TokenType.GT): 6360 this = self.expression( 6361 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6362 ) 6363 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6364 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6365 else: 6366 break 6367 6368 return this 6369 6370 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6371 this = self._parse_factor(parse_mod=parse_mod) 6372 6373 while self._match_set(self.TERM): 6374 klass = self.TERM[self._prev.token_type] 6375 comments = self._prev_comments 6376 expression = self._parse_factor(parse_mod=parse_mod) 6377 6378 this = self.expression(klass(this=this, expression=expression), comments=comments) 6379 6380 if isinstance(this, exp.Collate): 6381 self._normalize_collate(this) 6382 6383 return this 6384 6385 def _normalize_collate(self, collate: exp.Collate) -> None: 6386 expr = collate.expression 6387 6388 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6389 # fallback to Identifier / Var 6390 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6391 ident = expr.this 6392 if isinstance(ident, exp.Identifier): 6393 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6394 6395 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6396 parse_method = self._parse_factor_operand 6397 this = self._parse_at_time_zone(parse_method()) 6398 6399 while self._match_set(self.FACTOR, advance=False): 6400 if not parse_mod and self._curr.token_type == TokenType.MOD: 6401 break 6402 6403 self._advance() 6404 klass = self.FACTOR[self._prev.token_type] 6405 comments = self._prev_comments 6406 expression = parse_method() 6407 6408 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6409 self._retreat(self._index - 1) 6410 return this 6411 6412 this = self.expression(klass(this=this, expression=expression), comments=comments) 6413 6414 if isinstance(this, exp.Div): 6415 this.set("typed", self.dialect.TYPED_DIVISION) 6416 this.set("safe", self.dialect.SAFE_DIVISION) 6417 6418 return this 6419 6420 def _parse_factor_operand(self) -> exp.Expr | None: 6421 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6422 6423 def _parse_exponent(self) -> exp.Expr | None: 6424 this = self._parse_unary() 6425 while self._match_set(self.EXPONENT): 6426 comments = self._prev_comments 6427 this = self.expression( 6428 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6429 comments=comments, 6430 ) 6431 return this 6432 6433 def _parse_unary(self) -> exp.Expr | None: 6434 if self._match_set(self.UNARY_PARSERS): 6435 return self.UNARY_PARSERS[self._prev.token_type](self) 6436 return self._parse_type() 6437 6438 def _parse_type( 6439 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6440 ) -> exp.Expr | None: 6441 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6442 return atom 6443 6444 if interval := parse_interval and self._parse_interval(): 6445 return self._parse_column_ops(interval) 6446 6447 index = self._index 6448 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6449 6450 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6451 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6452 if isinstance(data_type, exp.Cast): 6453 # This constructor can contain ops directly after it, for instance struct unnesting: 6454 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6455 return self._parse_column_ops(data_type) 6456 6457 if data_type: 6458 index2 = self._index 6459 this = self._parse_primary() 6460 6461 if isinstance(this, exp.Literal): 6462 literal = this.name 6463 this = self._parse_column_ops(this) 6464 6465 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6466 if parser: 6467 return parser(self, this, data_type) 6468 6469 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6470 if data_type.is_type(exp.DType.TIMESTAMP): 6471 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6472 elif data_type.is_type(exp.DType.TIME): 6473 data_type = exp.DType.TIMETZ.into_expr() 6474 6475 return self.expression(exp.Cast(this=this, to=data_type)) 6476 6477 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6478 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6479 # 6480 # If the index difference here is greater than 1, that means the parser itself must have 6481 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6482 # 6483 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6484 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6485 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6486 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6487 # 6488 # In these cases, we don't really want to return the converted type, but instead retreat 6489 # and try to parse a Column or Identifier in the section below. 6490 if data_type.expressions and index2 - index > 1: 6491 self._retreat(index2) 6492 return self._parse_column_ops(data_type) 6493 6494 self._retreat(index) 6495 6496 if fallback_to_identifier: 6497 return self._parse_id_var() 6498 6499 return self._parse_column() 6500 6501 def _parse_type_size(self) -> exp.DataTypeParam | None: 6502 this = self._parse_type() 6503 if not this: 6504 return None 6505 6506 if isinstance(this, exp.Column) and not this.table: 6507 this = exp.var(this.name.upper()) 6508 6509 return self.expression( 6510 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6511 ) 6512 6513 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6514 type_name = identifier.name 6515 6516 while self._match(TokenType.DOT): 6517 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6518 6519 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6520 6521 def _parse_types( 6522 self, 6523 check_func: bool = False, 6524 schema: bool = False, 6525 allow_identifiers: bool = True, 6526 with_collation: bool = False, 6527 ) -> exp.Expr | None: 6528 index = self._index 6529 this: exp.Expr | None = None 6530 6531 if self._match_set(self.TYPE_TOKENS): 6532 type_token = self._prev.token_type 6533 else: 6534 type_token = None 6535 identifier = allow_identifiers and self._parse_id_var( 6536 any_token=False, tokens=(TokenType.VAR,) 6537 ) 6538 if isinstance(identifier, exp.Identifier): 6539 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6540 this = exp.DataType.build(identifier, udt=True) 6541 else: 6542 try: 6543 tokens = self.dialect.tokenize(identifier.name) 6544 except TokenError: 6545 tokens = None 6546 6547 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6548 if len(tokens) > 1: 6549 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6550 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6551 this = self._parse_user_defined_type(identifier) 6552 else: 6553 self._retreat(self._index - 1) 6554 return None 6555 else: 6556 return None 6557 6558 if type_token == TokenType.PSEUDO_TYPE: 6559 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6560 6561 if type_token == TokenType.OBJECT_IDENTIFIER: 6562 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6563 6564 # https://materialize.com/docs/sql/types/map/ 6565 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6566 key_type = self._parse_types( 6567 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6568 ) 6569 if not self._match(TokenType.FARROW): 6570 self._retreat(index) 6571 return None 6572 6573 value_type = self._parse_types( 6574 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6575 ) 6576 if not self._match(TokenType.R_BRACKET): 6577 self._retreat(index) 6578 return None 6579 6580 return exp.DataType( 6581 this=exp.DType.MAP, 6582 expressions=[key_type, value_type], 6583 nested=True, 6584 ) 6585 6586 nested = type_token in self.NESTED_TYPE_TOKENS 6587 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6588 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6589 expressions = None 6590 maybe_func = False 6591 6592 if self._match(TokenType.L_PAREN): 6593 if is_struct: 6594 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6595 elif nested: 6596 expressions = self._parse_csv( 6597 lambda: self._parse_types( 6598 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6599 ) 6600 ) 6601 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6602 this = expressions[0] 6603 this.set("nullable", True) 6604 self._match_r_paren() 6605 return this 6606 elif type_token in self.ENUM_TYPE_TOKENS: 6607 expressions = self._parse_csv(self._parse_equality) 6608 elif type_token == TokenType.JSON: 6609 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6610 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6611 expressions = self._parse_csv(self._parse_json_type_arg) 6612 elif is_aggregate: 6613 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6614 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6615 ) 6616 if not func_or_ident: 6617 return None 6618 expressions = [func_or_ident] 6619 if self._match(TokenType.COMMA): 6620 expressions.extend( 6621 self._parse_csv( 6622 lambda: self._parse_types( 6623 check_func=check_func, 6624 schema=schema, 6625 allow_identifiers=allow_identifiers, 6626 ) 6627 ) 6628 ) 6629 else: 6630 expressions = self._parse_csv(self._parse_type_size) 6631 6632 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6633 if type_token == TokenType.VECTOR and len(expressions) == 2: 6634 expressions = self._parse_vector_expressions(expressions) 6635 6636 if not self._match(TokenType.R_PAREN): 6637 self._retreat(index) 6638 return None 6639 6640 maybe_func = True 6641 6642 values: list[exp.Expr] | None = None 6643 6644 if nested and self._match(TokenType.LT): 6645 if is_struct: 6646 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6647 else: 6648 expressions = self._parse_csv( 6649 lambda: self._parse_types( 6650 check_func=check_func, 6651 schema=schema, 6652 allow_identifiers=allow_identifiers, 6653 with_collation=True, 6654 ) 6655 ) 6656 6657 if not self._match(TokenType.GT): 6658 self.raise_error("Expecting >") 6659 6660 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6661 values = self._parse_csv(self._parse_disjunction) 6662 if not values and is_struct: 6663 values = None 6664 self._retreat(self._index - 1) 6665 else: 6666 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6667 6668 if type_token in self.TIMESTAMPS: 6669 if self._match_text_seq("WITH", "TIME", "ZONE"): 6670 maybe_func = False 6671 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6672 this = exp.DataType(this=tz_type, expressions=expressions) 6673 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6674 maybe_func = False 6675 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6676 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6677 maybe_func = False 6678 elif type_token == TokenType.INTERVAL: 6679 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6680 unit = self._parse_var(upper=True) 6681 if self._match_text_seq("TO"): 6682 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6683 6684 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6685 else: 6686 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6687 elif type_token == TokenType.VOID: 6688 this = exp.DataType(this=exp.DType.NULL) 6689 6690 if maybe_func and check_func: 6691 index2 = self._index 6692 peek = self._parse_string() 6693 6694 if not peek: 6695 self._retreat(index) 6696 return None 6697 6698 self._retreat(index2) 6699 6700 if not this: 6701 assert type_token is not None 6702 if self._match_text_seq("UNSIGNED"): 6703 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6704 if not unsigned_type_token: 6705 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6706 6707 type_token = unsigned_type_token or type_token 6708 6709 # NULLABLE without parentheses can be a column (Presto/Trino) 6710 if type_token == TokenType.NULLABLE and not expressions: 6711 self._retreat(index) 6712 return None 6713 6714 this = exp.DataType( 6715 this=exp.DType[type_token.name], 6716 expressions=expressions, 6717 nested=nested, 6718 ) 6719 6720 # Empty arrays/structs are allowed 6721 if values is not None: 6722 cls = exp.Struct if is_struct else exp.Array 6723 this = exp.cast(cls(expressions=values), this, copy=False) 6724 6725 elif expressions: 6726 this.set("expressions", expressions) 6727 6728 # https://materialize.com/docs/sql/types/list/#type-name 6729 while self._match(TokenType.LIST): 6730 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6731 6732 index = self._index 6733 6734 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6735 matched_array = self._match(TokenType.ARRAY) 6736 6737 while self._curr: 6738 datatype_token = self._prev.token_type 6739 matched_l_bracket = self._match(TokenType.L_BRACKET) 6740 6741 if (not matched_l_bracket and not matched_array) or ( 6742 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6743 ): 6744 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6745 # not to be confused with the fixed size array parsing 6746 break 6747 6748 matched_array = False 6749 values = self._parse_csv(self._parse_disjunction) or None 6750 if ( 6751 values 6752 and not schema 6753 and ( 6754 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6755 or datatype_token == TokenType.ARRAY 6756 or not self._match(TokenType.R_BRACKET, advance=False) 6757 ) 6758 ): 6759 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6760 # 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 6761 self._retreat(index) 6762 break 6763 6764 this = exp.DataType( 6765 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6766 ) 6767 self._match(TokenType.R_BRACKET) 6768 6769 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6770 converter = self.TYPE_CONVERTERS.get(this.this) 6771 if converter: 6772 this = converter(t.cast(exp.DataType, this)) 6773 6774 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6775 this.set("collate", self._parse_identifier() or self._parse_column()) 6776 6777 return this 6778 6779 def _parse_json_type_arg(self) -> exp.Expr | None: 6780 """Parse a single argument to ClickHouse's JSON type.""" 6781 6782 # SKIP col or SKIP REGEXP 'pattern' 6783 if self._match_text_seq("SKIP"): 6784 regexp = self._match(TokenType.RLIKE) 6785 arg = self._parse_column() 6786 if isinstance(arg, exp.Column): 6787 arg = arg.to_dot() 6788 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6789 6790 param_or_col = self._parse_column() 6791 if not isinstance(param_or_col, exp.Column): 6792 return None 6793 6794 # Parameter: name=value (e.g., max_dynamic_paths=2) 6795 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 6796 param = param_or_col.name 6797 value = self._parse_primary() 6798 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 6799 6800 # Column type hint: col_name Type 6801 col = param_or_col.to_dot() 6802 kind = self._parse_types(check_func=False, allow_identifiers=False) 6803 return self.expression(exp.ColumnDef(this=col, kind=kind)) 6804 6805 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 6806 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 6807 6808 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 6809 index = self._index 6810 6811 if ( 6812 self._curr 6813 and self._next 6814 and self._curr.token_type in self.TYPE_TOKENS 6815 and self._next.token_type in self.TYPE_TOKENS 6816 ): 6817 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 6818 # type token. Without this, the list will be parsed as a type and we'll eventually crash 6819 this = self._parse_id_var() 6820 else: 6821 this = ( 6822 self._parse_type(parse_interval=False, fallback_to_identifier=True) 6823 or self._parse_id_var() 6824 ) 6825 6826 self._match(TokenType.COLON) 6827 6828 if ( 6829 type_required 6830 and not isinstance(this, exp.DataType) 6831 and not self._match_set(self.TYPE_TOKENS, advance=False) 6832 ): 6833 self._retreat(index) 6834 return self._parse_types() 6835 6836 return self._parse_column_def(this) 6837 6838 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 6839 if not self._match_text_seq("AT", "TIME", "ZONE"): 6840 return this 6841 return self._parse_at_time_zone( 6842 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 6843 ) 6844 6845 def _parse_atom(self) -> exp.Expr | None: 6846 if ( 6847 self._curr.token_type in self.IDENTIFIER_TOKENS 6848 and (column := self._parse_column()) is not None 6849 ): 6850 return column 6851 6852 token = self._curr 6853 token_type = token.token_type 6854 6855 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 6856 return None 6857 6858 next_type = self._next.token_type 6859 6860 if ( 6861 next_type in self.COLUMN_OPERATORS 6862 or next_type in self.COLUMN_POSTFIX_TOKENS 6863 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 6864 ): 6865 return None 6866 6867 self._advance() 6868 return primary_parser(self, token) 6869 6870 def _parse_column(self) -> exp.Expr | None: 6871 column: exp.Expr | None = self._parse_column_parts_fast() 6872 if column is None: 6873 this = self._parse_column_reference() 6874 if not this: 6875 this = self._parse_bracket(this) 6876 column = self._parse_column_ops(this) if this else this 6877 6878 if column: 6879 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 6880 column.set("join_mark", self._match(TokenType.JOIN_MARKER)) 6881 if self.COLON_IS_VARIANT_EXTRACT: 6882 column = self._parse_colon_as_variant_extract(column) 6883 6884 return column 6885 6886 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 6887 """Fast path for simple column and dot references (a, a.b, ...). 6888 6889 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 6890 that nothing complex follows. If it does, retreats and returns None so 6891 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 6892 """ 6893 index = self._index 6894 parts: list[exp.Identifier] | None = None 6895 all_comments: list[str] | None = None 6896 6897 while self._match_set(self.IDENTIFIER_TOKENS): 6898 token = self._prev 6899 comments = self._prev_comments 6900 6901 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 6902 self._retreat(index) 6903 return None 6904 6905 has_dot = self._match(TokenType.DOT) 6906 curr_tt = self._curr.token_type 6907 6908 if not has_dot: 6909 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 6910 self._retreat(index) 6911 return None 6912 elif curr_tt not in self.IDENTIFIER_TOKENS: 6913 self._retreat(index) 6914 return None 6915 6916 if parts is None: 6917 parts = [] 6918 6919 if comments: 6920 if all_comments is None: 6921 all_comments = [] 6922 all_comments.extend(comments) 6923 self._prev_comments = [] 6924 6925 parts.append( 6926 self.expression( 6927 exp.Identifier( 6928 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 6929 ), 6930 token, 6931 ) 6932 ) 6933 6934 if not has_dot: 6935 break 6936 6937 if parts is None: 6938 return None 6939 6940 n = len(parts) 6941 6942 if n == 1: 6943 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 6944 elif n == 2: 6945 column = exp.Column(this=parts[1], table=parts[0]) 6946 elif n == 3: 6947 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 6948 else: 6949 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 6950 6951 for i in range(4, n): 6952 column = exp.Dot(this=column, expression=parts[i]) 6953 6954 if all_comments: 6955 column.add_comments(all_comments) 6956 6957 return column 6958 6959 def _parse_column_reference(self) -> exp.Expr | None: 6960 this = self._parse_field() 6961 if ( 6962 not this 6963 and self._match(TokenType.VALUES, advance=False) 6964 and self.VALUES_FOLLOWED_BY_PAREN 6965 and (not self._next or self._next.token_type != TokenType.L_PAREN) 6966 ): 6967 this = self._parse_id_var() 6968 6969 if isinstance(this, exp.Identifier): 6970 # We bubble up comments from the Identifier to the Column 6971 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 6972 6973 return this 6974 6975 def _build_json_extract( 6976 self, 6977 this: exp.Expr | None, 6978 path_parts: list[exp.JSONPathPart], 6979 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 6980 if len(path_parts) > 1: 6981 this = self.expression( 6982 exp.JSONExtract( 6983 this=this, 6984 expression=exp.JSONPath(expressions=path_parts), 6985 variant_extract=True, 6986 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 6987 ) 6988 ) 6989 path_parts = [exp.JSONPathRoot()] 6990 6991 return this, path_parts 6992 6993 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 6994 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 6995 6996 while self._match(TokenType.COLON): 6997 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 6998 this, path_parts = self._build_json_extract(this, path_parts) 6999 7000 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7001 7002 if key: 7003 quoted = isinstance(key, exp.Identifier) and key.quoted 7004 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 7005 7006 while True: 7007 if self._match(TokenType.DOT): 7008 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7009 7010 if next_key: 7011 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 7012 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 7013 elif self._match(TokenType.L_BRACKET): 7014 bracket_expr = self._parse_bracket_key_value() 7015 7016 if not self._match(TokenType.R_BRACKET): 7017 self.raise_error("Expected ]") 7018 7019 if bracket_expr: 7020 if bracket_expr.is_string: 7021 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7022 elif bracket_expr.is_star: 7023 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7024 elif bracket_expr.is_number: 7025 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7026 else: 7027 this, path_parts = self._build_json_extract(this, path_parts) 7028 7029 this = self.expression( 7030 exp.Bracket( 7031 this=this, expressions=[bracket_expr], json_access=True 7032 ), 7033 ) 7034 7035 elif self._match(TokenType.DCOLON): 7036 this, path_parts = self._build_json_extract(this, path_parts) 7037 7038 cast_type = self._parse_types() 7039 if cast_type: 7040 this = self.expression(exp.Cast(this=this, to=cast_type)) 7041 else: 7042 self.raise_error("Expected type after '::'") 7043 else: 7044 break 7045 7046 this, _ = self._build_json_extract(this, path_parts) 7047 7048 return this 7049 7050 def _parse_dcolon(self) -> exp.Expr | None: 7051 return self._parse_types() 7052 7053 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7054 while self._curr.token_type in self.BRACKETS: 7055 this = self._parse_bracket(this) 7056 7057 column_operators = self.COLUMN_OPERATORS 7058 cast_column_operators = self.CAST_COLUMN_OPERATORS 7059 while self._curr: 7060 op_token = self._curr.token_type 7061 7062 if op_token not in column_operators: 7063 break 7064 op = column_operators[op_token] 7065 self._advance() 7066 7067 if op_token in cast_column_operators: 7068 field = self._parse_dcolon() 7069 if not field: 7070 self.raise_error("Expected type") 7071 elif op and self._curr: 7072 field = self._parse_column_reference() or self._parse_bitwise() 7073 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7074 field = self._parse_column_ops(field) 7075 else: 7076 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7077 field = self._parse_field(any_token=True, anonymous_func=True) 7078 7079 # In t.true, t.null we should produce an Identifier node 7080 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7081 field = self.expression( 7082 exp.Identifier(this=self._prev.text), 7083 comments=field.comments, 7084 ) 7085 7086 # Function calls can be qualified, e.g., x.y.FOO() 7087 # This converts the final AST to a series of Dots leading to the function call 7088 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7089 if isinstance(field, (exp.Func, exp.Window)) and this: 7090 this = this.transform( 7091 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7092 ) 7093 7094 if op: 7095 this = op(self, this, field) 7096 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7097 this = self.expression( 7098 exp.Column( 7099 this=field, 7100 table=this.this, 7101 db=this.args.get("table"), 7102 catalog=this.args.get("db"), 7103 ), 7104 comments=this.comments, 7105 ) 7106 elif isinstance(field, exp.Window): 7107 # Move the exp.Dot's to the window's function 7108 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7109 field.set("this", window_func) 7110 this = field 7111 else: 7112 this = self.expression(exp.Dot(this=this, expression=field)) 7113 7114 if field and field.comments: 7115 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7116 7117 this = self._parse_bracket(this) 7118 7119 return this 7120 7121 def _parse_paren(self) -> exp.Expr | None: 7122 if not self._match(TokenType.L_PAREN): 7123 return None 7124 7125 comments = self._prev_comments 7126 query = self._parse_select() 7127 7128 if query: 7129 expressions = [query] 7130 else: 7131 expressions = self._parse_expressions() 7132 7133 this = seq_get(expressions, 0) 7134 7135 if not this and self._match(TokenType.R_PAREN, advance=False): 7136 this = self.expression(exp.Tuple()) 7137 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7138 this = self.expression(exp.Tuple(expressions=expressions)) 7139 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7140 this = self._parse_subquery(this=this, parse_alias=False) 7141 elif isinstance(this, (exp.Subquery, exp.Values)): 7142 this = self._parse_subquery( 7143 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7144 parse_alias=False, 7145 ) 7146 else: 7147 this = self.expression(exp.Paren(this=this)) 7148 7149 if this: 7150 this.add_comments(comments) 7151 7152 self._match_r_paren(expression=this) 7153 7154 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7155 return self._parse_window(this) 7156 7157 return this 7158 7159 def _parse_primary(self) -> exp.Expr | None: 7160 if self._match_set(self.PRIMARY_PARSERS): 7161 token_type = self._prev.token_type 7162 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7163 7164 if token_type == TokenType.STRING: 7165 expressions = [primary] 7166 while self._match(TokenType.STRING, advance=False): 7167 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7168 self.raise_error( 7169 "Adjacent string literals need to be separated by whitespace or comments" 7170 ) 7171 7172 self._advance() 7173 expressions.append(exp.Literal.string(self._prev.text)) 7174 7175 if len(expressions) > 1: 7176 return self.expression( 7177 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7178 ) 7179 7180 return primary 7181 7182 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7183 return exp.Literal.number(f"0.{self._prev.text}") 7184 7185 return self._parse_paren() 7186 7187 def _parse_field( 7188 self, 7189 any_token: bool = False, 7190 tokens: t.Collection[TokenType] | None = None, 7191 anonymous_func: bool = False, 7192 ) -> exp.Expr | None: 7193 after_dot = ( 7194 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7195 ) 7196 7197 if anonymous_func: 7198 field = ( 7199 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7200 or self._parse_primary() 7201 ) 7202 else: 7203 field = self._parse_primary() or self._parse_function( 7204 anonymous=anonymous_func, any_token=any_token 7205 ) 7206 7207 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7208 7209 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7210 name = field.name 7211 if self._is_connected() and self._parse_var(any_token=True): 7212 name += self._prev.text 7213 7214 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7215 7216 return field 7217 7218 def _parse_function( 7219 self, 7220 functions: dict[str, t.Callable] | None = None, 7221 anonymous: bool = False, 7222 optional_parens: bool = True, 7223 any_token: bool = False, 7224 ) -> exp.Expr | None: 7225 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7226 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7227 fn_syntax = False 7228 if ( 7229 self._match(TokenType.L_BRACE, advance=False) 7230 and self._next 7231 and self._next.text.upper() == "FN" 7232 ): 7233 self._advance(2) 7234 fn_syntax = True 7235 7236 func = self._parse_function_call( 7237 functions=functions, 7238 anonymous=anonymous, 7239 optional_parens=optional_parens, 7240 any_token=any_token, 7241 ) 7242 7243 if fn_syntax: 7244 self._match(TokenType.R_BRACE) 7245 7246 return func 7247 7248 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7249 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7250 7251 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7252 args = self._parse_function_args(alias=False) 7253 if not args: 7254 self.raise_error("Expected at least one argument") 7255 7256 # Wrapped so the connector keeps its precedence in the parent context 7257 return exp.Paren(this=connector(*args, copy=False)) 7258 7259 def _parse_function_call( 7260 self, 7261 functions: dict[str, t.Callable] | None = None, 7262 anonymous: bool = False, 7263 optional_parens: bool = True, 7264 any_token: bool = False, 7265 ) -> exp.Expr | None: 7266 if not self._curr: 7267 return None 7268 7269 comments = self._curr.comments 7270 prev = self._prev 7271 token = self._curr 7272 token_type = self._curr.token_type 7273 this: str | exp.Expr = self._curr.text 7274 upper = self._curr.text.upper() 7275 7276 after_dot = prev.token_type == TokenType.DOT 7277 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7278 if ( 7279 optional_parens 7280 and parser 7281 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7282 and not after_dot 7283 ): 7284 self._advance() 7285 return self._parse_window(parser(self)) 7286 7287 if self._next.token_type != TokenType.L_PAREN: 7288 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7289 self._advance() 7290 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7291 7292 return None 7293 7294 if any_token: 7295 if token_type in self.RESERVED_TOKENS: 7296 return None 7297 elif token_type not in self.FUNC_TOKENS: 7298 return None 7299 7300 self._advance(2) 7301 7302 parser = self.FUNCTION_PARSERS.get(upper) 7303 if parser and not anonymous: 7304 result = parser(self) 7305 else: 7306 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7307 7308 if subquery_predicate: 7309 expr = None 7310 if self._curr.token_type in self.SUBQUERY_TOKENS: 7311 expr = self._parse_select() 7312 self._match_r_paren() 7313 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7314 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7315 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7316 self._advance(-1) 7317 expr = self._parse_bitwise() 7318 7319 if expr: 7320 return self.expression(subquery_predicate(this=expr), comments=comments) 7321 7322 if functions is None: 7323 functions = self.FUNCTIONS 7324 7325 function = functions.get(upper) 7326 known_function = function and not anonymous 7327 7328 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7329 args = self._parse_function_args(alias) 7330 7331 post_func_comments = self._curr.comments if self._curr else None 7332 if known_function and post_func_comments: 7333 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7334 # call we'll construct it as exp.Anonymous, even if it's "known" 7335 if any( 7336 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7337 for comment in post_func_comments 7338 ): 7339 known_function = False 7340 7341 if alias and known_function: 7342 args = self._kv_to_prop_eq(args) 7343 7344 if known_function: 7345 func_builder = t.cast(t.Callable, function) 7346 7347 # mypyc compiled functions don't have __code__, so we use 7348 # try/except to check if func_builder accepts 'dialect'. 7349 try: 7350 func = func_builder(args) 7351 except TypeError: 7352 func = func_builder(args, dialect=self.dialect) 7353 7354 func = self.validate_expression(func, args) 7355 if self.dialect.PRESERVE_ORIGINAL_NAMES: 7356 func.meta["name"] = this 7357 7358 result = func 7359 else: 7360 if token_type == TokenType.IDENTIFIER: 7361 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7362 7363 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7364 7365 result = result.update_positions(token) 7366 7367 if isinstance(result, exp.Expr): 7368 result.add_comments(comments) 7369 7370 if parser: 7371 self._match(TokenType.R_PAREN, expression=result) 7372 else: 7373 self._match_r_paren(result) 7374 return self._parse_window(result) 7375 7376 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7377 return expression 7378 7379 def _kv_to_prop_eq( 7380 self, expressions: list[exp.Expr], parse_map: bool = False 7381 ) -> list[exp.Expr]: 7382 transformed = [] 7383 7384 for index, e in enumerate(expressions): 7385 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7386 if isinstance(e, exp.Alias): 7387 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7388 7389 if not isinstance(e, exp.PropertyEQ): 7390 e = self.expression( 7391 exp.PropertyEQ( 7392 this=e.this if parse_map else exp.to_identifier(e.this.name), 7393 expression=e.expression, 7394 ) 7395 ) 7396 7397 if isinstance(e.this, exp.Column): 7398 e.this.replace(e.this.this) 7399 else: 7400 e = self._to_prop_eq(e, index) 7401 7402 transformed.append(e) 7403 7404 return transformed 7405 7406 def _parse_function_properties(self) -> exp.Properties | None: 7407 # Skip the generic `key = value` fallback in _parse_property since this 7408 # runs post-AS where a function body like `name = expr` can be misread 7409 # as a property. 7410 properties = [] 7411 while True: 7412 if self._match_texts(self.PROPERTY_PARSERS): 7413 keyword = self._prev.text.upper() 7414 prop = self.PROPERTY_PARSERS[keyword](self) 7415 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7416 keyword = self._prev.text.upper() 7417 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7418 else: 7419 break 7420 if not prop: 7421 self.raise_error(f"Failed to parse property '{keyword}'") 7422 break 7423 for p in ensure_list(prop): 7424 properties.append(p) 7425 7426 return self.expression(exp.Properties(expressions=properties)) if properties else None 7427 7428 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7429 return self._parse_statement() 7430 7431 def _parse_function_parameter(self) -> exp.Expr | None: 7432 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7433 7434 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7435 this = self._parse_table_parts(schema=True) 7436 7437 if not self._match(TokenType.L_PAREN): 7438 return this 7439 7440 expressions = self._parse_csv(self._parse_function_parameter) 7441 self._match_r_paren() 7442 return self.expression( 7443 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7444 ) 7445 7446 def _parse_macro_overloads( 7447 self, 7448 this: exp.UserDefinedFunction, 7449 first_body: exp.Expr, 7450 first_is_table: bool = False, 7451 ) -> exp.MacroOverloads: 7452 overloads = [ 7453 self.expression( 7454 exp.MacroOverload( 7455 this=first_body, 7456 expressions=this.expressions or None, 7457 is_table=first_is_table, 7458 ) 7459 ) 7460 ] 7461 this.set("expressions", None) 7462 this.set("wrapped", False) 7463 7464 while self._match(TokenType.COMMA): 7465 if not self._match(TokenType.L_PAREN): 7466 break 7467 7468 params = self._parse_csv(self._parse_function_parameter) 7469 self._match_r_paren() 7470 7471 if not self._match(TokenType.ALIAS): 7472 break 7473 7474 is_table = self._match(TokenType.TABLE) 7475 body = self._parse_expression() 7476 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7477 overloads.append(self.expression(macro)) 7478 7479 return self.expression(exp.MacroOverloads(expressions=overloads)) 7480 7481 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7482 literal = self._parse_primary() 7483 if literal: 7484 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7485 7486 return self._identifier_expression(token) 7487 7488 def _parse_session_parameter(self) -> exp.SessionParameter: 7489 kind = None 7490 this = self._parse_id_var() or self._parse_primary() 7491 7492 if this and self._match(TokenType.DOT): 7493 kind = this.name 7494 this = self._parse_var() or self._parse_primary() 7495 7496 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7497 7498 def _parse_lambda_arg(self) -> exp.Expr | None: 7499 return self._parse_id_var() 7500 7501 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7502 next_token_type = self._next.token_type 7503 7504 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7505 if ( 7506 next_token_type in self.LAMBDA_ARG_TERMINATORS 7507 and (atom := self._parse_atom()) is not None 7508 ): 7509 return atom 7510 7511 index = self._index 7512 7513 if self._match(TokenType.L_PAREN): 7514 expressions = t.cast( 7515 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7516 ) 7517 7518 if not self._match(TokenType.R_PAREN): 7519 self._retreat(index) 7520 elif self._match_set(self.LAMBDAS): 7521 return self.LAMBDAS[self._prev.token_type](self, expressions) 7522 else: 7523 self._retreat(index) 7524 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7525 expressions = [self._parse_lambda_arg()] 7526 7527 if self._match_set(self.LAMBDAS): 7528 return self.LAMBDAS[self._prev.token_type](self, expressions) 7529 7530 self._retreat(index) 7531 7532 this: exp.Expr | None 7533 7534 if self._match(TokenType.DISTINCT): 7535 this = self.expression( 7536 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7537 ) 7538 else: 7539 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7540 this = self._parse_select_or_expression(alias=alias) 7541 7542 return self._parse_limit( 7543 self._parse_respect_or_ignore_nulls( 7544 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7545 ) 7546 ) 7547 7548 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7549 index = self._index 7550 if not self._match(TokenType.L_PAREN): 7551 return this 7552 7553 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7554 # expr can be of both types 7555 if self._match_set(self.SELECT_START_TOKENS): 7556 self._retreat(index) 7557 return this 7558 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7559 self._match_r_paren() 7560 return self.expression(exp.Schema(this=this, expressions=args)) 7561 7562 def _parse_field_def(self) -> exp.Expr | None: 7563 return self._parse_column_def(self._parse_field(any_token=True)) 7564 7565 def _parse_column_def( 7566 self, this: exp.Expr | None, computed_column: bool = True 7567 ) -> exp.Expr | None: 7568 # column defs are not really columns, they're identifiers 7569 if isinstance(this, exp.Column): 7570 this = this.this 7571 7572 if not computed_column: 7573 self._match(TokenType.ALIAS) 7574 7575 kind = self._parse_types(schema=True) 7576 7577 if self._match_text_seq("FOR", "ORDINALITY"): 7578 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7579 7580 constraints: list[exp.Expr] = [] 7581 7582 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7583 ("ALIAS", "MATERIALIZED") 7584 ): 7585 # Match storage before _parse_types so STORED is not treated as a data type 7586 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7587 persisted = self._prev.text.upper() == "MATERIALIZED" 7588 expression = self._parse_disjunction() 7589 if not persisted: 7590 if self._match_text_seq("PERSISTED"): 7591 persisted = True 7592 elif self._match_texts(("STORED", "VIRTUAL")): 7593 persisted = self._prev.text.upper() == "STORED" 7594 constraint_kind = exp.ComputedColumnConstraint( 7595 this=expression, 7596 persisted=persisted, 7597 data_type=exp.Var(this="AUTO") 7598 if self._match_text_seq("AUTO") 7599 else self._parse_types(), 7600 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7601 ) 7602 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7603 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7604 in_out_constraint = self.expression( 7605 exp.InOutColumnConstraint( 7606 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7607 ) 7608 ) 7609 constraints.append(in_out_constraint) 7610 kind = self._parse_types() 7611 elif ( 7612 kind 7613 and self._match(TokenType.ALIAS, advance=False) 7614 and ( 7615 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7616 or self._next.token_type == TokenType.L_PAREN 7617 ) 7618 ): 7619 self._advance() 7620 constraints.append( 7621 self.expression( 7622 exp.ColumnConstraint( 7623 kind=exp.ComputedColumnConstraint( 7624 this=self._parse_disjunction(), 7625 persisted=self._match_texts(("STORED", "VIRTUAL")) 7626 and self._prev.text.upper() == "STORED", 7627 ) 7628 ) 7629 ) 7630 ) 7631 7632 while True: 7633 constraint = self._parse_column_constraint() 7634 if not constraint: 7635 break 7636 constraints.append(constraint) 7637 7638 if not kind and not constraints: 7639 return this 7640 7641 position = None 7642 if self._match_texts(("FIRST", "AFTER")): 7643 pos = self._prev.text 7644 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7645 7646 return self.expression( 7647 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7648 ) 7649 7650 def _parse_auto_increment( 7651 self, 7652 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7653 start = None 7654 increment = None 7655 order = None 7656 7657 if self._match(TokenType.L_PAREN, advance=False): 7658 args = self._parse_wrapped_csv(self._parse_bitwise) 7659 start = seq_get(args, 0) 7660 increment = seq_get(args, 1) 7661 7662 # The remaining parts form an unordered bag and any of them can be omitted, in which 7663 # case the engine falls back to its own default, so they're parsed independently. 7664 while True: 7665 if self._match_text_seq("START"): 7666 start = self._parse_bitwise() 7667 elif self._match_text_seq("INCREMENT"): 7668 increment = self._parse_bitwise() 7669 elif self._match_text_seq("ORDER"): 7670 order = True 7671 elif self._match_text_seq("NOORDER"): 7672 order = False 7673 else: 7674 break 7675 7676 if start or increment or order is not None: 7677 return exp.GeneratedAsIdentityColumnConstraint( 7678 start=start, increment=increment, this=False, order=order 7679 ) 7680 7681 return exp.AutoIncrementColumnConstraint() 7682 7683 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7684 if not self._match(TokenType.L_PAREN, advance=False): 7685 return None 7686 7687 return self.expression( 7688 exp.CheckColumnConstraint( 7689 this=self._parse_wrapped(self._parse_assignment), 7690 enforced=self._match_text_seq("ENFORCED"), 7691 ) 7692 ) 7693 7694 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7695 if not self._match_text_seq("REFRESH"): 7696 self._retreat(self._index - 1) 7697 return None 7698 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7699 7700 def _parse_compress(self) -> exp.CompressColumnConstraint: 7701 if self._match(TokenType.L_PAREN, advance=False): 7702 return self.expression( 7703 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7704 ) 7705 7706 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7707 7708 def _parse_generated_as_identity( 7709 self, 7710 ) -> ( 7711 exp.GeneratedAsIdentityColumnConstraint 7712 | exp.ComputedColumnConstraint 7713 | exp.GeneratedAsRowColumnConstraint 7714 ): 7715 if self._match_text_seq("BY", "DEFAULT"): 7716 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7717 this = self.expression( 7718 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7719 ) 7720 else: 7721 self._match_text_seq("ALWAYS") 7722 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7723 7724 self._match(TokenType.ALIAS) 7725 7726 if self._match_text_seq("ROW"): 7727 start = self._match_text_seq("START") 7728 if not start: 7729 self._match(TokenType.END) 7730 hidden = self._match_text_seq("HIDDEN") 7731 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7732 7733 identity = self._match_text_seq("IDENTITY") 7734 7735 if self._match(TokenType.L_PAREN): 7736 if self._match_text_seq("START", "WITH"): 7737 this.set("start", self._parse_bitwise()) 7738 if self._match_text_seq("INCREMENT", "BY"): 7739 this.set("increment", self._parse_bitwise()) 7740 if self._match_text_seq("MINVALUE"): 7741 this.set("minvalue", self._parse_bitwise()) 7742 if self._match_text_seq("MAXVALUE"): 7743 this.set("maxvalue", self._parse_bitwise()) 7744 7745 if self._match_text_seq("CYCLE"): 7746 this.set("cycle", True) 7747 elif self._match_text_seq("NO", "CYCLE"): 7748 this.set("cycle", False) 7749 7750 if not identity: 7751 this.set("expression", self._parse_range()) 7752 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7753 args = self._parse_csv(self._parse_bitwise) 7754 this.set("start", seq_get(args, 0)) 7755 this.set("increment", seq_get(args, 1)) 7756 7757 self._match_r_paren() 7758 7759 return this 7760 7761 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7762 self._match_text_seq("LENGTH") 7763 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7764 7765 def _parse_not_constraint(self) -> exp.Expr | None: 7766 if self._match_text_seq("NULL"): 7767 return self.expression(exp.NotNullColumnConstraint()) 7768 if self._match_text_seq("CASESPECIFIC"): 7769 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7770 if self._match_text_seq("FOR", "REPLICATION"): 7771 return self.expression(exp.NotForReplicationColumnConstraint()) 7772 7773 # Unconsume the `NOT` token 7774 self._retreat(self._index - 1) 7775 return None 7776 7777 def _parse_column_constraint(self) -> exp.Expr | None: 7778 this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None 7779 7780 procedure_option_follows = ( 7781 self._match(TokenType.WITH, advance=False) 7782 and self._next 7783 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7784 ) 7785 7786 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 7787 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 7788 if not constraint: 7789 self._retreat(self._index - 1) 7790 return None 7791 7792 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 7793 7794 if self._match_text_seq("CHARACTER", "SET"): 7795 return self.expression( 7796 exp.ColumnConstraint( 7797 this=this, 7798 kind=self.expression( 7799 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 7800 ), 7801 ) 7802 ) 7803 7804 return this 7805 7806 def _parse_constraint(self) -> exp.Expr | None: 7807 if not self._match(TokenType.CONSTRAINT): 7808 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 7809 7810 return self.expression( 7811 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 7812 ) 7813 7814 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 7815 constraints = [] 7816 while True: 7817 constraint = self._parse_unnamed_constraint() or self._parse_function() 7818 if not constraint: 7819 break 7820 constraints.append(constraint) 7821 7822 return constraints 7823 7824 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 7825 index = self._index 7826 7827 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 7828 constraints or self.CONSTRAINT_PARSERS 7829 ): 7830 return None 7831 7832 constraint_key = self._prev.text.upper() 7833 if constraint_key not in self.CONSTRAINT_PARSERS: 7834 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 7835 7836 result = self.CONSTRAINT_PARSERS[constraint_key](self) 7837 if not result: 7838 self._retreat(index) 7839 7840 return result 7841 7842 def _parse_unique_key(self) -> exp.Expr | None: 7843 if ( 7844 self._curr 7845 and self._curr.token_type != TokenType.IDENTIFIER 7846 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 7847 ): 7848 return None 7849 return self._parse_id_var(any_token=False) 7850 7851 def _parse_unique(self) -> exp.UniqueColumnConstraint: 7852 self._match_texts(("KEY", "INDEX")) 7853 return self.expression( 7854 exp.UniqueColumnConstraint( 7855 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 7856 this=self._parse_schema(self._parse_unique_key()), 7857 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 7858 on_conflict=self._parse_on_conflict(), 7859 options=self._parse_key_constraint_options(), 7860 ) 7861 ) 7862 7863 def _parse_key_constraint_options(self) -> list[str]: 7864 options = [] 7865 while True: 7866 if not self._curr: 7867 break 7868 7869 if self._match(TokenType.ON): 7870 action = None 7871 on = self._advance_any() and self._prev.text 7872 7873 if self._match_text_seq("NO", "ACTION"): 7874 action = "NO ACTION" 7875 elif self._match_text_seq("CASCADE"): 7876 action = "CASCADE" 7877 elif self._match_text_seq("RESTRICT"): 7878 action = "RESTRICT" 7879 elif self._match_pair(TokenType.SET, TokenType.NULL): 7880 action = "SET NULL" 7881 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 7882 action = "SET DEFAULT" 7883 else: 7884 self.raise_error("Invalid key constraint") 7885 7886 options.append(f"ON {on} {action}") 7887 else: 7888 var = self._parse_var_from_options( 7889 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 7890 ) 7891 if not var: 7892 break 7893 options.append(var.name) 7894 7895 return options 7896 7897 def _parse_references(self, match: bool = True) -> exp.Reference | None: 7898 if match and not self._match(TokenType.REFERENCES): 7899 return None 7900 7901 expressions: list | None = None 7902 this = self._parse_table(schema=True) 7903 options = self._parse_key_constraint_options() 7904 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 7905 7906 def _parse_foreign_key(self) -> exp.ForeignKey: 7907 expressions = ( 7908 self._parse_wrapped_id_vars() 7909 if not self._match(TokenType.REFERENCES, advance=False) 7910 else None 7911 ) 7912 reference = self._parse_references() 7913 on_options = {} 7914 7915 while self._match(TokenType.ON): 7916 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 7917 self.raise_error("Expected DELETE or UPDATE") 7918 7919 kind = self._prev.text.lower() 7920 7921 if self._match_text_seq("NO", "ACTION"): 7922 action = "NO ACTION" 7923 elif self._match(TokenType.SET): 7924 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 7925 action = "SET " + self._prev.text.upper() 7926 else: 7927 self._advance() 7928 action = self._prev.text.upper() 7929 7930 on_options[kind] = action 7931 7932 return self.expression( 7933 exp.ForeignKey( 7934 expressions=expressions, 7935 reference=reference, 7936 options=self._parse_key_constraint_options(), 7937 **on_options, 7938 ) 7939 ) 7940 7941 def _parse_primary_key_part(self) -> exp.Expr | None: 7942 return self._parse_field() 7943 7944 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 7945 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 7946 self._retreat(self._index - 1) 7947 return None 7948 7949 id_vars = self._parse_wrapped_id_vars() 7950 return self.expression( 7951 exp.PeriodForSystemTimeConstraint( 7952 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 7953 ) 7954 ) 7955 7956 def _parse_primary_key( 7957 self, 7958 wrapped_optional: bool = False, 7959 in_props: bool = False, 7960 named_primary_key: bool = False, 7961 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 7962 desc = ( 7963 self._prev.token_type == TokenType.DESC 7964 if self._match_set((TokenType.ASC, TokenType.DESC)) 7965 else None 7966 ) 7967 7968 this = None 7969 if ( 7970 named_primary_key 7971 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 7972 and self._next 7973 and self._next.token_type == TokenType.L_PAREN 7974 ): 7975 this = self._parse_id_var() 7976 7977 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 7978 return self.expression( 7979 exp.PrimaryKeyColumnConstraint( 7980 desc=desc, options=self._parse_key_constraint_options() 7981 ) 7982 ) 7983 7984 expressions = self._parse_wrapped_csv( 7985 self._parse_primary_key_part, optional=wrapped_optional 7986 ) 7987 7988 return self.expression( 7989 exp.PrimaryKey( 7990 this=this, 7991 expressions=expressions, 7992 include=self._parse_index_params(), 7993 options=self._parse_key_constraint_options(), 7994 ) 7995 ) 7996 7997 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 7998 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 7999 8000 def _parse_odbc_datetime_literal(self) -> exp.Expr: 8001 """ 8002 Parses a datetime column in ODBC format. We parse the column into the corresponding 8003 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 8004 same as we did for `DATE('yyyy-mm-dd')`. 8005 8006 Reference: 8007 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 8008 """ 8009 self._match(TokenType.VAR) 8010 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 8011 expression = self.expression(exp_class(this=self._parse_string())) 8012 if not self._match(TokenType.R_BRACE): 8013 self.raise_error("Expected }") 8014 return expression 8015 8016 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 8017 if not self._match_set(self.BRACKETS): 8018 return this 8019 8020 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8021 map_token = seq_get(self._tokens, self._index - 2) 8022 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8023 else: 8024 parse_map = False 8025 8026 bracket_kind = self._prev.token_type 8027 if ( 8028 bracket_kind == TokenType.L_BRACE 8029 and self._curr 8030 and self._curr.token_type == TokenType.VAR 8031 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8032 ): 8033 return self._parse_odbc_datetime_literal() 8034 8035 expressions = self._parse_csv( 8036 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8037 ) 8038 8039 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8040 self.raise_error("Expected ]") 8041 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8042 self.raise_error("Expected }") 8043 8044 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8045 if bracket_kind == TokenType.L_BRACE: 8046 this = self.expression( 8047 exp.Struct( 8048 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8049 ) 8050 ) 8051 elif not this: 8052 this = build_array_constructor( 8053 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8054 ) 8055 else: 8056 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8057 if constructor_type: 8058 return build_array_constructor( 8059 constructor_type, 8060 args=expressions, 8061 bracket_kind=bracket_kind, 8062 dialect=self.dialect, 8063 ) 8064 8065 expressions = apply_index_offset( 8066 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8067 ) 8068 this = self.expression( 8069 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8070 ) 8071 8072 self._add_comments(this) 8073 return self._parse_bracket(this) 8074 8075 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8076 if not self._match(TokenType.COLON): 8077 return this 8078 8079 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8080 self._advance() 8081 end: exp.Expr | None = -exp.Literal.number("1") 8082 else: 8083 end = self._parse_assignment() 8084 step = self._parse_unary() if self._match(TokenType.COLON) else None 8085 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8086 8087 def _parse_case(self) -> exp.Expr | None: 8088 if self._match(TokenType.DOT, advance=False): 8089 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8090 self._retreat(self._index - 1) 8091 return None 8092 8093 ifs = [] 8094 default = None 8095 8096 comments = self._prev_comments 8097 expression = self._parse_disjunction() 8098 8099 while self._match(TokenType.WHEN): 8100 this = self._parse_disjunction() 8101 self._match(TokenType.THEN) 8102 then = self._parse_disjunction() 8103 ifs.append(self.expression(exp.If(this=this, true=then))) 8104 8105 if self._match(TokenType.ELSE): 8106 default = self._parse_disjunction() 8107 8108 if not self._match(TokenType.END): 8109 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8110 default = exp.column("interval") 8111 else: 8112 self.raise_error("Expected END after CASE", self._prev) 8113 8114 return self.expression( 8115 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8116 ) 8117 8118 def _parse_if(self) -> exp.Expr | None: 8119 if self._match(TokenType.L_PAREN): 8120 args = self._parse_csv( 8121 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8122 ) 8123 this = self.validate_expression(exp.If.from_arg_list(args), args) 8124 self._match_r_paren() 8125 else: 8126 index = self._index - 1 8127 8128 if self.NO_PAREN_IF_COMMANDS and index == 0: 8129 return self._parse_as_command(self._prev) 8130 8131 condition = self._parse_disjunction() 8132 8133 if not condition: 8134 self._retreat(index) 8135 return None 8136 8137 self._match(TokenType.THEN) 8138 true = self._parse_disjunction() 8139 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8140 self._match(TokenType.END) 8141 this = self.expression(exp.If(this=condition, true=true, false=false)) 8142 8143 return this 8144 8145 def _parse_next_value_for(self) -> exp.Expr | None: 8146 if not self._match_text_seq("VALUE", "FOR"): 8147 self._retreat(self._index - 1) 8148 return None 8149 8150 return self.expression( 8151 exp.NextValueFor( 8152 this=self._parse_column(), 8153 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8154 ) 8155 ) 8156 8157 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8158 this = self._parse_function() or self._parse_var_or_string(upper=True) 8159 8160 if self._match(TokenType.FROM): 8161 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8162 8163 if not self._match(TokenType.COMMA): 8164 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8165 8166 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8167 8168 def _parse_gap_fill(self) -> exp.GapFill: 8169 self._match(TokenType.TABLE) 8170 this = self._parse_table() 8171 8172 self._match(TokenType.COMMA) 8173 args = [this, *self._parse_csv(self._parse_lambda)] 8174 8175 gap_fill = exp.GapFill.from_arg_list(args) 8176 return self.validate_expression(gap_fill, args) 8177 8178 def _parse_char(self) -> exp.Chr: 8179 return self.expression( 8180 exp.Chr( 8181 expressions=self._parse_csv(self._parse_assignment), 8182 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8183 ) 8184 ) 8185 8186 def _parse_charset_name(self) -> exp.Expr | None: 8187 """ 8188 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8189 for specific name shapes override this. 8190 """ 8191 return self._parse_var( 8192 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8193 ) 8194 8195 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8196 this = self._parse_assignment() 8197 8198 if not self._match(TokenType.ALIAS): 8199 if self._match(TokenType.COMMA): 8200 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8201 8202 self.raise_error("Expected AS after CAST") 8203 8204 fmt = None 8205 to = self._parse_types(with_collation=True) 8206 8207 default = None 8208 if self._match(TokenType.DEFAULT): 8209 default = self._parse_bitwise() 8210 self._match_text_seq("ON", "CONVERSION", "ERROR") 8211 8212 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8213 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8214 fmt = self._parse_at_time_zone(fmt_string) 8215 8216 if not to: 8217 to = exp.DType.UNKNOWN.into_expr() 8218 if to.this in exp.DataType.TEMPORAL_TYPES: 8219 this = self.expression( 8220 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8221 this=this, 8222 format=exp.Literal.string( 8223 format_time( 8224 fmt_string.this if fmt_string else "", 8225 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8226 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8227 ) 8228 ), 8229 safe=safe, 8230 ) 8231 ) 8232 8233 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8234 this.set("zone", fmt.args["zone"]) 8235 return this 8236 elif not to: 8237 self.raise_error("Expected TYPE after CAST") 8238 elif isinstance(to, exp.Identifier): 8239 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8240 elif to.this == exp.DType.CHAR and ( 8241 self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET") 8242 ): 8243 to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string()) 8244 8245 return self.build_cast( 8246 strict=strict, 8247 this=this, 8248 to=to, 8249 format=fmt, 8250 safe=safe, 8251 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8252 default=default, 8253 ) 8254 8255 def _parse_string_agg(self) -> exp.GroupConcat: 8256 if self._match(TokenType.DISTINCT): 8257 args: list[exp.Expr | None] = [ 8258 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8259 ] 8260 if self._match(TokenType.COMMA): 8261 args.extend(self._parse_csv(self._parse_disjunction)) 8262 else: 8263 args = self._parse_csv(self._parse_disjunction) # type: ignore 8264 8265 if self._match_text_seq("ON", "OVERFLOW"): 8266 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8267 if self._match_text_seq("ERROR"): 8268 on_overflow: exp.Expr | None = exp.var("ERROR") 8269 else: 8270 self._match_text_seq("TRUNCATE") 8271 on_overflow = self.expression( 8272 exp.OverflowTruncateBehavior( 8273 this=self._parse_string(), 8274 with_count=( 8275 self._match_text_seq("WITH", "COUNT") 8276 or not self._match_text_seq("WITHOUT", "COUNT") 8277 ), 8278 ) 8279 ) 8280 else: 8281 on_overflow = None 8282 8283 index = self._index 8284 if not self._match(TokenType.R_PAREN) and args: 8285 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8286 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8287 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8288 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8289 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8290 8291 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8292 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8293 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8294 if not self._match_text_seq("WITHIN", "GROUP"): 8295 self._retreat(index) 8296 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8297 8298 # The corresponding match_r_paren will be called in parse_function (caller) 8299 self._match_l_paren() 8300 8301 return self.expression( 8302 exp.GroupConcat( 8303 this=self._parse_order(this=seq_get(args, 0)), 8304 separator=seq_get(args, 1), 8305 on_overflow=on_overflow, 8306 ) 8307 ) 8308 8309 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8310 this = self._parse_bitwise() 8311 8312 if self._match(TokenType.USING): 8313 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8314 elif self._match(TokenType.COMMA): 8315 to = self._parse_types() 8316 else: 8317 to = None 8318 8319 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8320 8321 def _parse_xml_element(self) -> exp.XMLElement: 8322 if self._match_text_seq("EVALNAME"): 8323 evalname = True 8324 this = self._parse_bitwise() 8325 else: 8326 evalname = None 8327 self._match_text_seq("NAME") 8328 this = self._parse_id_var() 8329 8330 return self.expression( 8331 exp.XMLElement( 8332 this=this, 8333 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8334 evalname=evalname, 8335 ) 8336 ) 8337 8338 def _parse_xml_table(self) -> exp.XMLTable: 8339 namespaces = None 8340 passing = None 8341 columns = None 8342 8343 if self._match_text_seq("XMLNAMESPACES", "("): 8344 namespaces = self._parse_xml_namespace() 8345 self._match_text_seq(")", ",") 8346 8347 this = self._parse_string() 8348 8349 if self._match_text_seq("PASSING"): 8350 # The BY VALUE keywords are optional and are provided for semantic clarity 8351 self._match_text_seq("BY", "VALUE") 8352 passing = self._parse_csv(self._parse_column) 8353 8354 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8355 8356 if self._match_text_seq("COLUMNS"): 8357 columns = self._parse_csv(self._parse_field_def) 8358 8359 return self.expression( 8360 exp.XMLTable( 8361 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8362 ) 8363 ) 8364 8365 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8366 namespaces = [] 8367 8368 while True: 8369 if self._match(TokenType.DEFAULT): 8370 uri = self._parse_string() 8371 else: 8372 uri = self._parse_alias(self._parse_string()) 8373 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8374 if not self._match(TokenType.COMMA): 8375 break 8376 8377 return namespaces 8378 8379 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8380 args = self._parse_csv(self._parse_disjunction) 8381 8382 if len(args) < 3: 8383 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8384 8385 return self.expression(exp.DecodeCase(expressions=args)) 8386 8387 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8388 self._match_text_seq("KEY") 8389 key = self._parse_column() 8390 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8391 self._match_text_seq("VALUE") 8392 value = self._parse_bitwise() 8393 8394 if not key and not value: 8395 return None 8396 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8397 8398 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8399 if not this or not self._match_text_seq("FORMAT", "JSON"): 8400 return this 8401 8402 return self.expression(exp.FormatJson(this=this)) 8403 8404 def _parse_on_condition(self) -> exp.OnCondition | None: 8405 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8406 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8407 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8408 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8409 else: 8410 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8411 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8412 8413 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8414 8415 if not empty and not error and not null: 8416 return None 8417 8418 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8419 8420 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8421 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8422 for value in values: 8423 if self._match_text_seq(value, "ON", on): 8424 return f"{value} ON {on}" 8425 8426 index = self._index 8427 if self._match(TokenType.DEFAULT): 8428 default_value = self._parse_bitwise() 8429 if self._match_text_seq("ON", on): 8430 return default_value 8431 8432 self._retreat(index) 8433 8434 return None 8435 8436 @t.overload 8437 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8438 8439 @t.overload 8440 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8441 8442 def _parse_json_object(self, agg=False): 8443 star = self._parse_star() 8444 expressions = ( 8445 [star] 8446 if star 8447 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8448 ) 8449 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8450 8451 unique_keys = None 8452 if self._match_text_seq("WITH", "UNIQUE"): 8453 unique_keys = True 8454 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8455 unique_keys = False 8456 8457 self._match_text_seq("KEYS") 8458 8459 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8460 self._parse_type() 8461 ) 8462 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8463 8464 return self.expression( 8465 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8466 expressions=expressions, 8467 null_handling=null_handling, 8468 unique_keys=unique_keys, 8469 return_type=return_type, 8470 encoding=encoding, 8471 ) 8472 ) 8473 8474 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8475 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8476 if not self._match_text_seq("NESTED"): 8477 this = self._parse_id_var() 8478 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8479 kind = self._parse_types(allow_identifiers=False) 8480 nested = None 8481 else: 8482 this = None 8483 ordinality = None 8484 kind = None 8485 nested = True 8486 8487 format_json = self._match_text_seq("FORMAT", "JSON") 8488 path = self._match_text_seq("PATH") and self._parse_string() 8489 nested_schema = nested and self._parse_json_schema() 8490 8491 return self.expression( 8492 exp.JSONColumnDef( 8493 this=this, 8494 kind=kind, 8495 path=path, 8496 nested_schema=nested_schema, 8497 ordinality=ordinality, 8498 format_json=format_json, 8499 ) 8500 ) 8501 8502 def _parse_json_schema(self) -> exp.JSONSchema: 8503 self._match_text_seq("COLUMNS") 8504 return self.expression( 8505 exp.JSONSchema( 8506 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8507 ) 8508 ) 8509 8510 def _parse_json_table(self) -> exp.JSONTable: 8511 this = self._parse_format_json(self._parse_bitwise()) 8512 path = self._match(TokenType.COMMA) and self._parse_string() 8513 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8514 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8515 schema = self._parse_json_schema() 8516 8517 return exp.JSONTable( 8518 this=this, 8519 schema=schema, 8520 path=path, 8521 error_handling=error_handling, 8522 empty_handling=empty_handling, 8523 ) 8524 8525 def _parse_match_against(self) -> exp.MatchAgainst: 8526 if self._match_text_seq("TABLE"): 8527 # parse SingleStore MATCH(TABLE ...) syntax 8528 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8529 expressions = [] 8530 table = self._parse_table() 8531 if table: 8532 expressions = [table] 8533 else: 8534 expressions = self._parse_csv(self._parse_column) 8535 8536 self._match_text_seq(")", "AGAINST", "(") 8537 8538 this = self._parse_string() 8539 8540 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8541 modifier = "IN NATURAL LANGUAGE MODE" 8542 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8543 modifier = f"{modifier} WITH QUERY EXPANSION" 8544 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8545 modifier = "IN BOOLEAN MODE" 8546 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8547 modifier = "WITH QUERY EXPANSION" 8548 else: 8549 modifier = None 8550 8551 return self.expression( 8552 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8553 ) 8554 8555 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8556 def _parse_open_json(self) -> exp.OpenJSON: 8557 this = self._parse_bitwise() 8558 path = self._match(TokenType.COMMA) and self._parse_string() 8559 8560 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8561 this = self._parse_field(any_token=True) 8562 kind = self._parse_types() 8563 path = self._parse_string() 8564 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8565 8566 return self.expression( 8567 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8568 ) 8569 8570 expressions = None 8571 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8572 self._match_l_paren() 8573 expressions = self._parse_csv(_parse_open_json_column_def) 8574 8575 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8576 8577 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8578 args = self._parse_csv(self._parse_bitwise) 8579 8580 if self._match(TokenType.IN): 8581 return self.expression( 8582 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8583 ) 8584 8585 if haystack_first: 8586 haystack = seq_get(args, 0) 8587 needle = seq_get(args, 1) 8588 else: 8589 haystack = seq_get(args, 1) 8590 needle = seq_get(args, 0) 8591 8592 return self.expression( 8593 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8594 ) 8595 8596 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8597 args = self._parse_csv(self._parse_table) 8598 return exp.JoinHint(this=func_name.upper(), expressions=args) 8599 8600 def _parse_substring(self) -> exp.Substring: 8601 # Postgres supports the form: substring(string [from int] [for int]) 8602 # (despite being undocumented, the reverse order also works) 8603 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8604 8605 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8606 8607 start, length = None, None 8608 8609 while self._curr: 8610 if self._match(TokenType.FROM): 8611 start = self._parse_bitwise() 8612 elif self._match(TokenType.FOR): 8613 if not start: 8614 start = exp.Literal.number(1) 8615 length = self._parse_bitwise() 8616 else: 8617 break 8618 8619 if start: 8620 args.append(start) 8621 if length: 8622 args.append(length) 8623 8624 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8625 8626 def _parse_trim(self) -> exp.Trim: 8627 # https://www.w3resource.com/sql/character-functions/trim.php 8628 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8629 8630 position = None 8631 collation = None 8632 expression = None 8633 8634 if self._match_texts(self.TRIM_TYPES): 8635 position = self._prev.text.upper() 8636 8637 this = self._parse_bitwise() 8638 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8639 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8640 expression = self._parse_bitwise() 8641 8642 if invert_order: 8643 this, expression = expression, this 8644 8645 if self._match(TokenType.COLLATE): 8646 collation = self._parse_bitwise() 8647 8648 return self.expression( 8649 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8650 ) 8651 8652 def _parse_window_clause(self) -> list[exp.Expr] | None: 8653 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8654 8655 def _parse_named_window(self) -> exp.Expr | None: 8656 return self._parse_window(self._parse_id_var(), alias=True) 8657 8658 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8659 if self._curr.token_type == TokenType.VAR: 8660 if self._match_text_seq("IGNORE", "NULLS"): 8661 return self.expression(exp.IgnoreNulls(this=this)) 8662 if self._match_text_seq("RESPECT", "NULLS"): 8663 return self.expression(exp.RespectNulls(this=this)) 8664 return this 8665 8666 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8667 if self._match(TokenType.HAVING): 8668 self._match_texts(("MAX", "MIN")) 8669 max = self._prev.text.upper() != "MIN" 8670 return self.expression( 8671 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8672 ) 8673 8674 return this 8675 8676 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8677 func = this 8678 comments = func.comments if isinstance(func, exp.Expr) else None 8679 8680 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8681 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8682 if self._match_text_seq("FROM", "FIRST"): 8683 this.set("from_first", True) 8684 elif self._match_text_seq("FROM", "LAST"): 8685 this.set("from_first", False) 8686 8687 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8688 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8689 if self._match_text_seq("WITHIN", "GROUP"): 8690 order = self._parse_wrapped(self._parse_order) 8691 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8692 8693 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8694 self._match(TokenType.WHERE) 8695 this = self.expression( 8696 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8697 ) 8698 self._match_r_paren() 8699 8700 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8701 # Some dialects choose to implement and some do not. 8702 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8703 8704 # There is some code above in _parse_lambda that handles 8705 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8706 8707 # The below changes handle 8708 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8709 8710 # Oracle allows both formats 8711 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8712 # and Snowflake chose to do the same for familiarity 8713 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8714 if isinstance(this, exp.AggFunc): 8715 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8716 8717 if ignore_respect and ignore_respect is not this: 8718 ignore_respect.replace(ignore_respect.this) 8719 this = self.expression(ignore_respect.__class__(this=this)) 8720 8721 this = self._parse_respect_or_ignore_nulls(this) 8722 8723 # bigquery select from window x AS (partition by ...) 8724 if alias: 8725 over = None 8726 self._match(TokenType.ALIAS) 8727 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8728 return this 8729 else: 8730 over = self._prev.text.upper() 8731 8732 if comments and isinstance(func, exp.Expr): 8733 func.pop_comments() 8734 8735 if not self._match(TokenType.L_PAREN): 8736 return self.expression( 8737 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8738 ) 8739 8740 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8741 8742 first: bool | None = True if self._match(TokenType.FIRST) else None 8743 if self._match_text_seq("LAST"): 8744 first = False 8745 8746 partition, order = self._parse_partition_and_order() 8747 kind = ( 8748 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8749 ) and self._prev.text 8750 8751 if kind: 8752 self._match(TokenType.BETWEEN) 8753 start = self._parse_window_spec() 8754 8755 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8756 exclude = ( 8757 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8758 if self._match_text_seq("EXCLUDE") 8759 else None 8760 ) 8761 8762 spec = self.expression( 8763 exp.WindowSpec( 8764 kind=kind, 8765 start=start["value"], 8766 start_side=start["side"], 8767 end=end.get("value"), 8768 end_side=end.get("side"), 8769 exclude=exclude, 8770 ) 8771 ) 8772 else: 8773 spec = None 8774 8775 self._match_r_paren() 8776 8777 window = self.expression( 8778 exp.Window( 8779 this=this, 8780 partition_by=partition, 8781 order=order, 8782 spec=spec, 8783 alias=window_alias, 8784 over=over, 8785 first=first, 8786 ), 8787 comments=comments, 8788 ) 8789 8790 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 8791 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 8792 return self._parse_window(window, alias=alias) 8793 8794 return window 8795 8796 def _parse_partition_and_order( 8797 self, 8798 ) -> tuple[list[exp.Expr], exp.Expr | None]: 8799 return self._parse_partition_by(), self._parse_order() 8800 8801 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 8802 self._match(TokenType.BETWEEN) 8803 8804 return { 8805 "value": ( 8806 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 8807 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 8808 or self._parse_bitwise() 8809 ), 8810 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 8811 } 8812 8813 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 8814 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 8815 # so this section tries to parse the clause version and if it fails, it treats the token 8816 # as an identifier (alias) 8817 if self._can_parse_limit_or_offset(): 8818 return this 8819 8820 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 8821 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 8822 if self._can_parse_named_window(): 8823 return this 8824 8825 any_token = self._match(TokenType.ALIAS) 8826 comments = self._prev_comments 8827 8828 if explicit and not any_token: 8829 return this 8830 8831 if self._match(TokenType.L_PAREN): 8832 aliases = self.expression( 8833 exp.Aliases( 8834 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 8835 ), 8836 comments=comments, 8837 ) 8838 self._match_r_paren(aliases) 8839 return aliases 8840 8841 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 8842 self.STRING_ALIASES and self._parse_string_as_identifier() 8843 ) 8844 8845 if alias: 8846 comments.extend(alias.pop_comments()) 8847 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 8848 column = this.this 8849 8850 # Moves the comment next to the alias in `expr /* comment */ AS alias` 8851 if not this.comments and column and column.comments: 8852 this.comments = column.pop_comments() 8853 8854 return this 8855 8856 def _parse_id_var( 8857 self, 8858 any_token: bool = True, 8859 tokens: t.Collection[TokenType] | None = None, 8860 ) -> exp.Expr | None: 8861 expression = self._parse_identifier() 8862 if not expression and ( 8863 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 8864 ): 8865 quoted = self._prev.token_type == TokenType.STRING 8866 expression = self._identifier_expression(quoted=quoted) 8867 8868 return expression 8869 8870 def _parse_string(self) -> exp.Expr | None: 8871 if self._match_set(self.STRING_PARSERS): 8872 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 8873 return self._parse_placeholder() 8874 8875 def _parse_string_as_identifier(self) -> exp.Identifier | None: 8876 if not self._match(TokenType.STRING): 8877 return None 8878 output = exp.to_identifier(self._prev.text, quoted=True) 8879 output.update_positions(self._prev) 8880 return output 8881 8882 def _parse_number(self) -> exp.Expr | None: 8883 if self._match_set(self.NUMERIC_PARSERS): 8884 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 8885 return self._parse_placeholder() 8886 8887 def _parse_identifier(self) -> exp.Expr | None: 8888 if self._match(TokenType.IDENTIFIER): 8889 return self._identifier_expression(quoted=True) 8890 return self._parse_placeholder() 8891 8892 def _parse_var( 8893 self, 8894 any_token: bool = False, 8895 tokens: t.Collection[TokenType] | None = None, 8896 upper: bool = False, 8897 ) -> exp.Expr | None: 8898 if ( 8899 (any_token and self._advance_any()) 8900 or self._match(TokenType.VAR) 8901 or (self._match_set(tokens) if tokens else False) 8902 ): 8903 return self.expression( 8904 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 8905 ) 8906 return self._parse_placeholder() 8907 8908 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 8909 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 8910 self._advance() 8911 return self._prev 8912 return None 8913 8914 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 8915 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 8916 8917 def _parse_primary_or_var(self) -> exp.Expr | None: 8918 return self._parse_primary() or self._parse_var(any_token=True) 8919 8920 def _parse_null(self) -> exp.Expr | None: 8921 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 8922 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 8923 return self._parse_placeholder() 8924 8925 def _parse_boolean(self) -> exp.Expr | None: 8926 if self._match(TokenType.TRUE): 8927 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 8928 if self._match(TokenType.FALSE): 8929 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 8930 return self._parse_placeholder() 8931 8932 def _parse_star(self) -> exp.Expr | None: 8933 if self._match(TokenType.STAR): 8934 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 8935 return self._parse_placeholder() 8936 8937 def _parse_parameter(self) -> exp.Parameter: 8938 this = self._parse_identifier() or self._parse_primary_or_var() 8939 return self.expression(exp.Parameter(this=this)) 8940 8941 def _parse_placeholder(self) -> exp.Expr | None: 8942 if self._match_set(self.PLACEHOLDER_PARSERS): 8943 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 8944 if placeholder: 8945 return placeholder 8946 self._advance(-1) 8947 return None 8948 8949 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 8950 if not self._match_texts(keywords): 8951 return None 8952 if self._match(TokenType.L_PAREN, advance=False): 8953 return self._parse_wrapped_csv(self._parse_expression) 8954 8955 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 8956 return [expression] if expression else None 8957 8958 def _parse_csv( 8959 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 8960 ) -> list[T]: 8961 parse_result = parse_method() 8962 items = [parse_result] if parse_result is not None else [] 8963 8964 while self._match(sep): 8965 if isinstance(parse_result, exp.Expr): 8966 self._add_comments(parse_result) 8967 parse_result = parse_method() 8968 if parse_result is not None: 8969 items.append(parse_result) 8970 8971 return items 8972 8973 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 8974 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 8975 8976 def _parse_wrapped_csv( 8977 self, 8978 parse_method: t.Callable[[], T | None], 8979 sep: TokenType = TokenType.COMMA, 8980 optional: bool = False, 8981 ) -> list[T]: 8982 return self._parse_wrapped( 8983 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 8984 ) 8985 8986 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 8987 wrapped = self._match(TokenType.L_PAREN) 8988 if not wrapped and not optional: 8989 self.raise_error("Expecting (") 8990 parse_result = parse_method() 8991 if wrapped: 8992 self._match_r_paren() 8993 return parse_result 8994 8995 def _parse_expressions(self) -> list[exp.Expr]: 8996 return self._parse_csv(self._parse_expression) 8997 8998 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 8999 return ( 9000 self._parse_set_operations( 9001 self._parse_alias(self._parse_assignment(), explicit=True) 9002 if alias 9003 else self._parse_assignment() 9004 ) 9005 or self._parse_select() 9006 ) 9007 9008 def _parse_ddl_select(self) -> exp.Expr | None: 9009 return self._parse_query_modifiers( 9010 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 9011 ) 9012 9013 def _parse_transaction(self) -> exp.Transaction | exp.Command: 9014 this = None 9015 if self._match_texts(self.TRANSACTION_KIND): 9016 this = self._prev.text 9017 9018 self._match_texts(("TRANSACTION", "WORK")) 9019 9020 modes = [] 9021 while True: 9022 mode = [] 9023 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 9024 mode.append(self._prev.text) 9025 9026 if mode: 9027 modes.append(" ".join(mode)) 9028 if not self._match(TokenType.COMMA): 9029 break 9030 9031 return self.expression(exp.Transaction(this=this, modes=modes)) 9032 9033 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 9034 chain = None 9035 savepoint = None 9036 is_rollback = self._prev.token_type == TokenType.ROLLBACK 9037 9038 self._match_texts(("TRANSACTION", "WORK")) 9039 9040 if self._match_text_seq("TO"): 9041 self._match_text_seq("SAVEPOINT") 9042 savepoint = self._parse_id_var() 9043 9044 if self._match(TokenType.AND): 9045 chain = not self._match_text_seq("NO") 9046 self._match_text_seq("CHAIN") 9047 9048 if is_rollback: 9049 return self.expression(exp.Rollback(savepoint=savepoint)) 9050 9051 return self.expression(exp.Commit(chain=chain)) 9052 9053 def _parse_refresh(self) -> exp.Refresh | exp.Command: 9054 if self._match_text_seq("EXTERNAL", "TABLE"): 9055 kind = "EXTERNAL TABLE" 9056 elif self._match(TokenType.TABLE): 9057 kind = "TABLE" 9058 elif self._match_text_seq("MATERIALIZED", "VIEW"): 9059 kind = "MATERIALIZED VIEW" 9060 else: 9061 kind = "" 9062 9063 this = self._parse_string() or self._parse_table() 9064 if not kind and not isinstance(this, exp.Literal): 9065 return self._parse_as_command(self._prev) 9066 9067 return self.expression(exp.Refresh(this=this, kind=kind)) 9068 9069 def _parse_column_def_with_exists(self): 9070 start = self._index 9071 self._match(TokenType.COLUMN) 9072 9073 exists_column = self._parse_exists(not_=True) 9074 expression = self._parse_field_def() 9075 9076 if not isinstance(expression, exp.ColumnDef): 9077 self._retreat(start) 9078 return None 9079 9080 expression.set("exists", exists_column) 9081 9082 return expression 9083 9084 def _parse_add_column(self) -> exp.ColumnDef | None: 9085 if not self._prev.text.upper() == "ADD": 9086 return None 9087 9088 return self._parse_column_def_with_exists() 9089 9090 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9091 drop = self._parse_drop() if self._match(TokenType.DROP) else None 9092 if drop and not isinstance(drop, exp.Command): 9093 drop.set("kind", drop.args.get("kind", "COLUMN")) 9094 return drop 9095 9096 def _parse_alter_drop_action(self) -> exp.Expr | None: 9097 return self._parse_drop_column() 9098 9099 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9100 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9101 return self.expression( 9102 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9103 ) 9104 9105 def _parse_alter_table_add(self) -> list[exp.Expr]: 9106 def _parse_add_alteration() -> exp.Expr | None: 9107 self._match_text_seq("ADD") 9108 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False): 9109 return self.expression( 9110 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9111 ) 9112 9113 column_def = self._parse_add_column() 9114 if isinstance(column_def, exp.ColumnDef): 9115 return column_def 9116 9117 exists = self._parse_exists(not_=True) 9118 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9119 return self.expression( 9120 exp.AddPartition( 9121 exists=exists, 9122 this=self._parse_field(any_token=True), 9123 location=self._match_text_seq("LOCATION", advance=False) 9124 and self._parse_property(), 9125 ) 9126 ) 9127 9128 return None 9129 9130 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9131 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9132 or self._match_text_seq("COLUMNS") 9133 ): 9134 schema = self._parse_schema() 9135 9136 return ( 9137 ensure_list(schema) 9138 if schema 9139 else self._parse_csv(self._parse_column_def_with_exists) 9140 ) 9141 9142 return self._parse_csv(_parse_add_alteration) 9143 9144 def _parse_alter_table_alter(self) -> exp.Expr | None: 9145 if self._match_texts(self.ALTER_ALTER_PARSERS): 9146 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9147 9148 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9149 # keyword after ALTER we default to parsing this statement 9150 self._match(TokenType.COLUMN) 9151 exists = self._parse_exists() 9152 column = self._parse_field(any_token=True) 9153 9154 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9155 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9156 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9157 return self.expression( 9158 exp.AlterColumn( 9159 this=column, default=self._parse_disjunction(), exists=exists or None 9160 ) 9161 ) 9162 if self._match(TokenType.COMMENT): 9163 return self.expression( 9164 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9165 ) 9166 if self._match_text_seq("DROP", "NOT", "NULL"): 9167 return self.expression( 9168 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9169 ) 9170 if self._match_text_seq("SET", "NOT", "NULL"): 9171 return self.expression( 9172 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9173 ) 9174 9175 if self._match_text_seq("SET", "VISIBLE"): 9176 return self.expression( 9177 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9178 ) 9179 if self._match_text_seq("SET", "INVISIBLE"): 9180 return self.expression( 9181 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9182 ) 9183 9184 self._match_text_seq("SET", "DATA") 9185 self._match_text_seq("TYPE") 9186 return self.expression( 9187 exp.AlterColumn( 9188 this=column, 9189 dtype=self._parse_types(), 9190 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9191 using=self._match(TokenType.USING) and self._parse_disjunction(), 9192 exists=exists or None, 9193 ) 9194 ) 9195 9196 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9197 if self._match_texts(("ALL", "EVEN", "AUTO")): 9198 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9199 9200 self._match_text_seq("KEY", "DISTKEY") 9201 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9202 9203 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9204 if compound: 9205 self._match_text_seq("SORTKEY") 9206 9207 if self._match(TokenType.L_PAREN, advance=False): 9208 return self.expression( 9209 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9210 ) 9211 9212 self._match_texts(("AUTO", "NONE")) 9213 return self.expression( 9214 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9215 ) 9216 9217 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9218 index = self._index - 1 9219 9220 partition_exists = self._parse_exists() 9221 if self._match(TokenType.PARTITION, advance=False): 9222 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9223 9224 self._retreat(index) 9225 return self._parse_csv(self._parse_alter_drop_action) 9226 9227 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9228 if self._match(TokenType.COLUMN) or ( 9229 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9230 ): 9231 exists = self._parse_exists() 9232 old_column = self._parse_column() 9233 to = self._match_text_seq("TO") 9234 new_column = self._parse_column() 9235 9236 if old_column is None or not to or new_column is None: 9237 return None 9238 9239 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9240 9241 self._match_text_seq("TO") 9242 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9243 9244 def _parse_alter_table_set(self) -> exp.AlterSet: 9245 alter_set = self.expression(exp.AlterSet()) 9246 9247 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9248 "TABLE", "PROPERTIES" 9249 ): 9250 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9251 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9252 alter_set.set("expressions", [self._parse_assignment()]) 9253 elif self._match_texts(("LOGGED", "UNLOGGED")): 9254 alter_set.set("option", exp.var(self._prev.text.upper())) 9255 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9256 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9257 elif self._match_text_seq("LOCATION"): 9258 alter_set.set("location", self._parse_field()) 9259 elif self._match_text_seq("ACCESS", "METHOD"): 9260 alter_set.set("access_method", self._parse_field()) 9261 elif self._match_text_seq("TABLESPACE"): 9262 alter_set.set("tablespace", self._parse_field()) 9263 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9264 alter_set.set("file_format", [self._parse_field()]) 9265 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9266 alter_set.set("file_format", self._parse_wrapped_options()) 9267 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9268 alter_set.set("copy_options", self._parse_wrapped_options()) 9269 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9270 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9271 else: 9272 if self._match_text_seq("SERDE"): 9273 alter_set.set("serde", self._parse_field()) 9274 9275 properties = self._parse_wrapped(self._parse_properties, optional=True) 9276 alter_set.set("expressions", [properties]) 9277 9278 return alter_set 9279 9280 def _parse_alter_session(self) -> exp.AlterSession: 9281 """Parse ALTER SESSION SET/UNSET statements.""" 9282 if self._match(TokenType.SET): 9283 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9284 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9285 9286 self._match_text_seq("UNSET") 9287 expressions = self._parse_csv( 9288 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9289 ) 9290 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9291 9292 def _parse_alter(self) -> exp.Alter | exp.Command: 9293 start = self._prev 9294 9295 iceberg = self._match_text_seq("ICEBERG") 9296 9297 alter_token = self._match_set(self.ALTERABLES) and self._prev 9298 if not alter_token: 9299 return self._parse_as_command(start) 9300 if iceberg and alter_token.token_type != TokenType.TABLE: 9301 return self._parse_as_command(start) 9302 9303 exists = self._parse_exists() 9304 only = self._match_text_seq("ONLY") 9305 9306 if alter_token.token_type == TokenType.SESSION: 9307 this = None 9308 check = None 9309 cluster = None 9310 else: 9311 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9312 check = self._match_text_seq("WITH", "CHECK") 9313 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9314 9315 if self._next: 9316 self._advance() 9317 9318 parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None 9319 if parser: 9320 actions = ensure_list(parser(self)) 9321 not_valid = self._match_text_seq("NOT", "VALID") 9322 options = self._parse_csv(self._parse_property) 9323 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9324 9325 if not self._curr and actions: 9326 return self.expression( 9327 exp.Alter( 9328 this=this, 9329 kind=alter_token.text.upper(), 9330 exists=exists, 9331 actions=actions, 9332 only=only, 9333 options=options, 9334 cluster=cluster, 9335 not_valid=not_valid, 9336 check=check, 9337 cascade=cascade, 9338 iceberg=iceberg, 9339 ) 9340 ) 9341 9342 return self._parse_as_command(start) 9343 9344 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9345 start = self._prev 9346 # https://duckdb.org/docs/sql/statements/analyze 9347 if not self._curr: 9348 return self.expression(exp.Analyze()) 9349 9350 options = [] 9351 while self._match_texts(self.ANALYZE_STYLES): 9352 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9353 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9354 else: 9355 options.append(self._prev.text.upper()) 9356 9357 tables: exp.Expr | list[exp.Expr] | None = None 9358 inner_expression: exp.Expr | None = None 9359 9360 kind = self._curr.text.upper() if self._curr else None 9361 9362 if self._match(TokenType.TABLE): 9363 tables = self._parse_csv(self._parse_table_parts) 9364 elif self._match(TokenType.INDEX): 9365 tables = self._parse_table_parts() 9366 elif self._match_text_seq("TABLES"): 9367 if self._match_set((TokenType.FROM, TokenType.IN)): 9368 kind = f"{kind} {self._prev.text.upper()}" 9369 tables = self._parse_table(schema=True, is_db_reference=True) 9370 elif self._match_text_seq("DATABASE"): 9371 tables = self._parse_table(schema=True, is_db_reference=True) 9372 elif self._match_text_seq("CLUSTER"): 9373 tables = self._parse_table() 9374 # Try matching inner expr keywords before fallback to parse table. 9375 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9376 kind = None 9377 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9378 else: 9379 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9380 kind = None 9381 tables = self._parse_csv(self._parse_table_parts) 9382 9383 partition = self._try_parse(self._parse_partition) 9384 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9385 return self._parse_as_command(start) 9386 9387 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9388 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9389 "WITH", "ASYNC", "MODE" 9390 ): 9391 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9392 else: 9393 mode = None 9394 9395 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9396 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9397 9398 properties = self._parse_properties() 9399 return self.expression( 9400 exp.Analyze( 9401 kind=kind, 9402 tables=ensure_list(tables), 9403 mode=mode, 9404 partition=partition, 9405 properties=properties, 9406 expression=inner_expression, 9407 options=options, 9408 ) 9409 ) 9410 9411 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9412 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9413 this = None 9414 kind = self._prev.text.upper() 9415 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9416 expressions = [] 9417 9418 if not self._match_text_seq("STATISTICS"): 9419 self.raise_error("Expecting token STATISTICS") 9420 9421 if self._match_text_seq("NOSCAN"): 9422 this = "NOSCAN" 9423 elif self._match(TokenType.FOR): 9424 if self._match_text_seq("ALL", "COLUMNS"): 9425 this = "FOR ALL COLUMNS" 9426 if self._match_text_seq("COLUMNS"): 9427 this = "FOR COLUMNS" 9428 expressions = self._parse_csv(self._parse_column_reference) 9429 elif self._match_text_seq("SAMPLE"): 9430 sample = self._parse_number() 9431 expressions = [ 9432 self.expression( 9433 exp.AnalyzeSample( 9434 sample=sample, 9435 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9436 ) 9437 ) 9438 ] 9439 9440 return self.expression( 9441 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9442 ) 9443 9444 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9445 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9446 kind = None 9447 this = None 9448 expression: exp.Expr | None = None 9449 if self._match_text_seq("REF", "UPDATE"): 9450 kind = "REF" 9451 this = "UPDATE" 9452 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9453 this = "UPDATE SET DANGLING TO NULL" 9454 elif self._match_text_seq("STRUCTURE"): 9455 kind = "STRUCTURE" 9456 if self._match_text_seq("CASCADE", "FAST"): 9457 this = "CASCADE FAST" 9458 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9459 ("ONLINE", "OFFLINE") 9460 ): 9461 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9462 expression = self._parse_into() 9463 9464 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9465 9466 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9467 this = self._prev.text.upper() 9468 if self._match_text_seq("COLUMNS"): 9469 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9470 return None 9471 9472 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9473 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9474 if self._match_text_seq("STATISTICS"): 9475 return self.expression(exp.AnalyzeDelete(kind=kind)) 9476 return None 9477 9478 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9479 if self._match_text_seq("CHAINED", "ROWS"): 9480 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9481 return None 9482 9483 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9484 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9485 this = self._prev.text.upper() 9486 expression: exp.Expr | None = None 9487 expressions = [] 9488 update_options = None 9489 9490 if self._match_text_seq("HISTOGRAM", "ON"): 9491 expressions = self._parse_csv(self._parse_column_reference) 9492 with_expressions = [] 9493 while self._match(TokenType.WITH): 9494 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9495 if self._match_texts(("SYNC", "ASYNC")): 9496 if self._match_text_seq("MODE", advance=False): 9497 with_expressions.append(f"{self._prev.text.upper()} MODE") 9498 self._advance() 9499 else: 9500 buckets = self._parse_number() 9501 if self._match_text_seq("BUCKETS"): 9502 with_expressions.append(f"{buckets} BUCKETS") 9503 if with_expressions: 9504 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9505 9506 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9507 TokenType.UPDATE, advance=False 9508 ): 9509 update_options = self._prev.text.upper() 9510 self._advance() 9511 elif self._match_text_seq("USING", "DATA"): 9512 expression = self.expression(exp.UsingData(this=self._parse_string())) 9513 9514 return self.expression( 9515 exp.AnalyzeHistogram( 9516 this=this, 9517 expressions=expressions, 9518 expression=expression, 9519 update_options=update_options, 9520 ) 9521 ) 9522 9523 def _parse_merge(self) -> exp.Merge: 9524 self._match(TokenType.INTO) 9525 target = self._parse_table() 9526 9527 if target and self._match(TokenType.ALIAS, advance=False): 9528 target.set("alias", self._parse_table_alias()) 9529 9530 self._match(TokenType.USING) 9531 using = self._parse_table() 9532 9533 return self.expression( 9534 exp.Merge( 9535 this=target, 9536 using=using, 9537 on=self._match(TokenType.ON) and self._parse_disjunction(), 9538 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9539 whens=self._parse_when_matched(), 9540 returning=self._parse_returning(), 9541 ) 9542 ) 9543 9544 def _parse_when_matched(self) -> exp.Whens: 9545 whens = [] 9546 9547 while self._match(TokenType.WHEN): 9548 matched = not self._match(TokenType.NOT) 9549 self._match_text_seq("MATCHED") 9550 source = ( 9551 False 9552 if self._match_text_seq("BY", "TARGET") 9553 else self._match_text_seq("BY", "SOURCE") 9554 ) 9555 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9556 9557 self._match(TokenType.THEN) 9558 9559 if self._match(TokenType.INSERT): 9560 this = self._parse_star() 9561 if this: 9562 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9563 else: 9564 then = self.expression( 9565 exp.Insert( 9566 this=exp.var("ROW") 9567 if self._match_text_seq("ROW") 9568 else self._parse_value(values=False), 9569 expression=self._match_text_seq("VALUES") and self._parse_value(), 9570 where=self._parse_where(), 9571 ) 9572 ) 9573 elif self._match(TokenType.UPDATE): 9574 expressions = self._parse_star() 9575 if expressions: 9576 then = self.expression(exp.Update(expressions=expressions)) 9577 else: 9578 then = self.expression( 9579 exp.Update( 9580 expressions=self._match(TokenType.SET) 9581 and self._parse_csv(self._parse_equality), 9582 where=self._parse_where(), 9583 ) 9584 ) 9585 elif self._match(TokenType.DELETE): 9586 then = self.expression(exp.Var(this=self._prev.text)) 9587 else: 9588 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9589 9590 whens.append( 9591 self.expression( 9592 exp.When(matched=matched, source=source, condition=condition, then=then) 9593 ) 9594 ) 9595 return self.expression(exp.Whens(expressions=whens)) 9596 9597 def _parse_show(self) -> exp.Expr | None: 9598 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9599 if parser: 9600 return parser(self) 9601 return self._parse_as_command(self._prev) 9602 9603 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9604 index = self._index 9605 9606 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9607 return self._parse_set_transaction(global_=kind == "GLOBAL") 9608 9609 left = self._parse_primary() or self._parse_column() 9610 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 9611 9612 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 9613 self._retreat(index) 9614 return None 9615 9616 right = self._parse_statement() or self._parse_id_var() 9617 if isinstance(right, (exp.Column, exp.Identifier)): 9618 right = exp.var(right.name) 9619 9620 this = self.expression(exp.EQ(this=left, expression=right)) 9621 return self.expression(exp.SetItem(this=this, kind=kind)) 9622 9623 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 9624 self._match_text_seq("TRANSACTION") 9625 characteristics = self._parse_csv( 9626 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 9627 ) 9628 return self.expression( 9629 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 9630 ) 9631 9632 def _parse_set_item(self) -> exp.Expr | None: 9633 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 9634 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 9635 9636 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 9637 index = self._index 9638 set_ = self.expression( 9639 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 9640 ) 9641 9642 if self._curr: 9643 self._retreat(index) 9644 return self._parse_as_command(self._prev) 9645 9646 return set_ 9647 9648 def _parse_var_from_options( 9649 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 9650 ) -> exp.Var | None: 9651 start = self._curr 9652 if not start: 9653 return None 9654 9655 option = start.text.upper() 9656 continuations = ( 9657 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 9658 ) 9659 9660 index = self._index 9661 self._advance() 9662 for keywords in continuations or []: 9663 if isinstance(keywords, str): 9664 keywords = (keywords,) 9665 9666 if self._match_text_seq(*keywords): 9667 option = f"{option} {' '.join(keywords)}" 9668 break 9669 else: 9670 if continuations or continuations is None: 9671 if raise_unmatched: 9672 self.raise_error(f"Unknown option {option}") 9673 9674 self._retreat(index) 9675 return None 9676 9677 return exp.var(option) 9678 9679 def _parse_as_command(self, start: Token) -> exp.Command: 9680 while self._curr: 9681 self._advance() 9682 text = self._find_sql(start, self._prev) 9683 size = len(start.text) 9684 self._warn_unsupported() 9685 return exp.Command(this=text[:size], expression=text[size:]) 9686 9687 def _parse_dict_property(self, this: str) -> exp.DictProperty: 9688 settings = [] 9689 9690 self._match_l_paren() 9691 kind = self._parse_id_var() 9692 9693 if self._match(TokenType.L_PAREN): 9694 while True: 9695 key = self._parse_id_var() 9696 value = self._parse_function() or self._parse_primary_or_var() 9697 if not key and value is None: 9698 break 9699 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 9700 self._match(TokenType.R_PAREN) 9701 9702 self._match_r_paren() 9703 9704 return self.expression( 9705 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 9706 ) 9707 9708 def _parse_dict_range(self, this: str) -> exp.DictRange: 9709 self._match_l_paren() 9710 has_min = self._match_text_seq("MIN") 9711 if has_min: 9712 min = self._parse_var() or self._parse_primary() 9713 self._match_text_seq("MAX") 9714 max = self._parse_var() or self._parse_primary() 9715 else: 9716 max = self._parse_var() or self._parse_primary() 9717 min = exp.Literal.number(0) 9718 self._match_r_paren() 9719 return self.expression(exp.DictRange(this=this, min=min, max=max)) 9720 9721 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 9722 index = self._index 9723 expression = self._parse_column() 9724 position = self._match(TokenType.COMMA) and self._parse_column() 9725 9726 if not self._match(TokenType.IN): 9727 self._retreat(index - 1) 9728 return None 9729 iterator = self._parse_column() 9730 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 9731 return self.expression( 9732 exp.Comprehension( 9733 this=this, 9734 expression=expression, 9735 position=position, 9736 iterator=iterator, 9737 condition=condition, 9738 ) 9739 ) 9740 9741 def _parse_heredoc(self) -> exp.Heredoc | None: 9742 if self._match(TokenType.HEREDOC_STRING): 9743 return self.expression(exp.Heredoc(this=self._prev.text)) 9744 9745 if not self._match_text_seq("$"): 9746 return None 9747 9748 tags = ["$"] 9749 tag_text = None 9750 9751 if self._is_connected(): 9752 self._advance() 9753 tags.append(self._prev.text.upper()) 9754 else: 9755 self.raise_error("No closing $ found") 9756 9757 if tags[-1] != "$": 9758 if self._is_connected() and self._match_text_seq("$"): 9759 tag_text = tags[-1] 9760 tags.append("$") 9761 else: 9762 self.raise_error("No closing $ found") 9763 9764 heredoc_start = self._curr 9765 9766 while self._curr: 9767 if self._match_text_seq(*tags, advance=False): 9768 this = self._find_sql(heredoc_start, self._prev) 9769 self._advance(len(tags)) 9770 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 9771 9772 self._advance() 9773 9774 self.raise_error(f"No closing {''.join(tags)} found") 9775 return None 9776 9777 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 9778 if not self._curr: 9779 return None 9780 9781 index = self._index 9782 this = [] 9783 while True: 9784 # The current token might be multiple words 9785 curr = self._curr.text.upper() 9786 key = curr.split(" ") 9787 this.append(curr) 9788 9789 self._advance() 9790 result, trie = in_trie(trie, key) 9791 if result == TrieResult.FAILED: 9792 break 9793 9794 if result == TrieResult.EXISTS: 9795 subparser = parsers[" ".join(this)] 9796 return subparser 9797 9798 self._retreat(index) 9799 return None 9800 9801 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 9802 if not self._match(TokenType.L_PAREN, expression=expression): 9803 self.raise_error("Expecting (") 9804 9805 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 9806 if not self._match(TokenType.R_PAREN, expression=expression): 9807 self.raise_error("Expecting )") 9808 9809 def _replace_lambda( 9810 self, node: exp.Expr | None, expressions: list[exp.Expr] 9811 ) -> exp.Expr | None: 9812 if not node: 9813 return node 9814 9815 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 9816 9817 for column in node.find_all(exp.Column): 9818 typ = lambda_types.get(column.parts[0].name) 9819 if typ is not None: 9820 dot_or_id = column.to_dot() if column.table else column.this 9821 9822 if typ: 9823 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ)) 9824 9825 parent = column.parent 9826 9827 while isinstance(parent, exp.Dot): 9828 if not isinstance(parent.parent, exp.Dot): 9829 parent.replace(dot_or_id) 9830 break 9831 parent = parent.parent 9832 else: 9833 if column is node: 9834 node = dot_or_id 9835 else: 9836 column.replace(dot_or_id) 9837 return node 9838 9839 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 9840 start = self._prev 9841 9842 # Not to be confused with TRUNCATE(number, decimals) function call 9843 if self._match(TokenType.L_PAREN): 9844 self._retreat(self._index - 2) 9845 return self._parse_function() 9846 9847 # Clickhouse supports TRUNCATE DATABASE as well 9848 is_database = self._match(TokenType.DATABASE) 9849 9850 self._match(TokenType.TABLE) 9851 9852 exists = self._parse_exists(not_=False) 9853 9854 expressions = self._parse_csv( 9855 lambda: self._parse_table(schema=True, is_db_reference=is_database) 9856 ) 9857 9858 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9859 9860 if self._match_text_seq("RESTART", "IDENTITY"): 9861 identity = "RESTART" 9862 elif self._match_text_seq("CONTINUE", "IDENTITY"): 9863 identity = "CONTINUE" 9864 else: 9865 identity = None 9866 9867 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 9868 option = self._prev.text 9869 else: 9870 option = None 9871 9872 partition = self._parse_partition() 9873 9874 # Fallback case 9875 if self._curr: 9876 return self._parse_as_command(start) 9877 9878 return self.expression( 9879 exp.TruncateTable( 9880 expressions=expressions, 9881 is_database=is_database, 9882 exists=exists, 9883 cluster=cluster, 9884 identity=identity, 9885 option=option, 9886 partition=partition, 9887 ) 9888 ) 9889 9890 def _parse_indexed_column(self) -> exp.Expr | None: 9891 return self._parse_ordered(self._parse_opclass) 9892 9893 def _parse_with_operator(self) -> exp.Expr | None: 9894 this = self._parse_indexed_column() 9895 9896 if not self._match(TokenType.WITH): 9897 return this 9898 9899 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 9900 9901 return self.expression(exp.WithOperator(this=this, op=op)) 9902 9903 def _parse_wrapped_options(self) -> list[exp.Expr]: 9904 self._match(TokenType.EQ) 9905 self._match(TokenType.L_PAREN) 9906 9907 opts: list[exp.Expr] = [] 9908 option: exp.Expr | list[exp.Expr] | None 9909 while self._curr and not self._match(TokenType.R_PAREN): 9910 if self._match_text_seq("FORMAT_NAME", "="): 9911 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 9912 option = self._parse_format_name() 9913 else: 9914 option = self._parse_property() 9915 9916 if option is None: 9917 self.raise_error("Unable to parse option") 9918 break 9919 9920 opts.extend(ensure_list(option)) 9921 9922 return opts 9923 9924 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 9925 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 9926 9927 options = [] 9928 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 9929 option = self._parse_var(any_token=True) 9930 prev = self._prev.text.upper() 9931 9932 # Different dialects might separate options and values by white space, "=" and "AS" 9933 self._match(TokenType.EQ) 9934 self._match(TokenType.ALIAS) 9935 9936 param = self.expression(exp.CopyParameter(this=option)) 9937 9938 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 9939 TokenType.L_PAREN, advance=False 9940 ): 9941 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 9942 param.set("expressions", self._parse_wrapped_options()) 9943 elif prev == "FILE_FORMAT": 9944 # T-SQL's external file format case 9945 param.set("expression", self._parse_field()) 9946 elif ( 9947 prev == "FORMAT" 9948 and self._prev.token_type == TokenType.ALIAS 9949 and self._match_texts(("AVRO", "JSON")) 9950 ): 9951 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 9952 param.set("expression", self._parse_field()) 9953 else: 9954 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 9955 9956 options.append(param) 9957 9958 if sep: 9959 self._match(sep) 9960 9961 return options 9962 9963 def _parse_credentials(self) -> exp.Credentials | None: 9964 expr = self.expression(exp.Credentials()) 9965 9966 if self._match_text_seq("STORAGE_INTEGRATION", "="): 9967 expr.set("storage", self._parse_field()) 9968 if self._match_text_seq("CREDENTIALS"): 9969 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 9970 creds = ( 9971 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 9972 ) 9973 expr.set("credentials", creds) 9974 if self._match_text_seq("ENCRYPTION"): 9975 expr.set("encryption", self._parse_wrapped_options()) 9976 if self._match_text_seq("IAM_ROLE"): 9977 expr.set( 9978 "iam_role", 9979 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 9980 ) 9981 if self._match_text_seq("REGION"): 9982 expr.set("region", self._parse_field()) 9983 9984 return expr 9985 9986 def _parse_file_location(self) -> exp.Expr | None: 9987 return self._parse_field() 9988 9989 def _parse_copy(self) -> exp.Copy | exp.Command: 9990 start = self._prev 9991 9992 self._match(TokenType.INTO) 9993 9994 this = ( 9995 self._parse_select(nested=True, parse_subquery_alias=False) 9996 if self._match(TokenType.L_PAREN, advance=False) 9997 else self._parse_table(schema=True) 9998 ) 9999 10000 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 10001 10002 files = self._parse_csv(self._parse_file_location) 10003 if self._match(TokenType.EQ, advance=False): 10004 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 10005 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 10006 # list via `_parse_wrapped(..)` below. 10007 self._advance(-1) 10008 files = [] 10009 10010 credentials = self._parse_credentials() 10011 10012 self._match_text_seq("WITH") 10013 10014 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 10015 10016 # Fallback case 10017 if self._curr: 10018 return self._parse_as_command(start) 10019 10020 return self.expression( 10021 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 10022 ) 10023 10024 def _parse_normalize(self) -> exp.Normalize: 10025 return self.expression( 10026 exp.Normalize( 10027 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 10028 ) 10029 ) 10030 10031 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 10032 args = self._parse_csv(lambda: self._parse_lambda()) 10033 10034 this = seq_get(args, 0) 10035 decimals = seq_get(args, 1) 10036 10037 return expr_type( 10038 this=this, 10039 decimals=decimals, 10040 to=self._parse_var() if self._match_text_seq("TO") else None, 10041 ) 10042 10043 def _parse_star_ops(self) -> exp.Expr | None: 10044 star_token = self._prev 10045 10046 if self._match_text_seq("COLUMNS", "(", advance=False): 10047 this = self._parse_function() 10048 if isinstance(this, exp.Columns): 10049 this.set("unpack", True) 10050 return this 10051 10052 index = self._index 10053 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 10054 if not ilike: 10055 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 10056 self._retreat(index) 10057 10058 return self.expression( 10059 exp.Star( 10060 ilike=ilike, 10061 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 10062 replace=self._parse_star_op("REPLACE"), 10063 rename=self._parse_star_op("RENAME"), 10064 ) 10065 ).update_positions(star_token) 10066 10067 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 10068 privilege_parts = [] 10069 10070 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 10071 # (end of privilege list) or L_PAREN (start of column list) are met 10072 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 10073 privilege_parts.append(self._curr.text.upper()) 10074 self._advance() 10075 10076 if not privilege_parts: 10077 self.raise_error("Expected privilege") 10078 return None 10079 10080 this = exp.var(" ".join(privilege_parts)) 10081 expressions = ( 10082 self._parse_wrapped_csv(self._parse_column) 10083 if self._match(TokenType.L_PAREN, advance=False) 10084 else None 10085 ) 10086 10087 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10088 10089 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10090 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10091 principal = self._parse_id_var() 10092 10093 if not principal: 10094 return None 10095 10096 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10097 10098 def _parse_grant_revoke_common( 10099 self, 10100 ) -> tuple[list | None, str | None, exp.Expr | None]: 10101 privileges = self._parse_csv(self._parse_grant_privilege) 10102 10103 self._match(TokenType.ON) 10104 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10105 10106 # Attempt to parse the securable e.g. MySQL allows names 10107 # such as "foo.*", "*.*" which are not easily parseable yet 10108 securable = self._try_parse(self._parse_table_parts) 10109 10110 return privileges, kind, securable 10111 10112 def _parse_grant(self) -> exp.Grant | exp.Command: 10113 start = self._prev 10114 10115 privileges, kind, securable = self._parse_grant_revoke_common() 10116 10117 if not securable or not self._match_text_seq("TO"): 10118 return self._parse_as_command(start) 10119 10120 principals = self._parse_csv(self._parse_grant_principal) 10121 10122 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10123 10124 if self._curr: 10125 return self._parse_as_command(start) 10126 10127 return self.expression( 10128 exp.Grant( 10129 privileges=privileges, 10130 kind=kind, 10131 securable=securable, 10132 principals=principals, 10133 grant_option=grant_option, 10134 ) 10135 ) 10136 10137 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10138 start = self._prev 10139 10140 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10141 10142 privileges, kind, securable = self._parse_grant_revoke_common() 10143 10144 if not securable or not self._match_text_seq("FROM"): 10145 return self._parse_as_command(start) 10146 10147 principals = self._parse_csv(self._parse_grant_principal) 10148 10149 cascade = None 10150 if self._match_texts(("CASCADE", "RESTRICT")): 10151 cascade = self._prev.text.upper() 10152 10153 if self._curr: 10154 return self._parse_as_command(start) 10155 10156 return self.expression( 10157 exp.Revoke( 10158 privileges=privileges, 10159 kind=kind, 10160 securable=securable, 10161 principals=principals, 10162 grant_option=grant_option, 10163 cascade=cascade, 10164 ) 10165 ) 10166 10167 def _parse_overlay(self) -> exp.Overlay: 10168 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10169 return ( 10170 self._parse_bitwise() 10171 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10172 else None 10173 ) 10174 10175 return self.expression( 10176 exp.Overlay( 10177 this=self._parse_bitwise(), 10178 expression=_parse_overlay_arg("PLACING"), 10179 from_=_parse_overlay_arg("FROM"), 10180 for_=_parse_overlay_arg("FOR"), 10181 ) 10182 ) 10183 10184 def _parse_format_name(self) -> exp.Property: 10185 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10186 # for FILE_FORMAT = <format_name> 10187 return self.expression( 10188 exp.Property( 10189 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10190 ) 10191 ) 10192 10193 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10194 is_distinct = self._match(TokenType.DISTINCT) 10195 if not is_distinct: 10196 self._match(TokenType.ALL) 10197 10198 args = [self._parse_lambda()] 10199 if self._match(TokenType.COMMA): 10200 args.extend(self._parse_function_args()) 10201 10202 target = seq_get(args, distinct_index) 10203 if is_distinct and target: 10204 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10205 10206 return func.from_arg_list(args) 10207 10208 def _identifier_expression( 10209 self, token: Token | None = None, quoted: bool | None = None 10210 ) -> exp.Identifier: 10211 token = token or self._prev 10212 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10213 10214 def _build_pipe_cte( 10215 self, 10216 query: exp.Query, 10217 expressions: list[exp.Expr], 10218 alias_cte: exp.TableAlias | None = None, 10219 ) -> exp.Select: 10220 new_cte: str | exp.TableAlias | None 10221 if alias_cte: 10222 new_cte = alias_cte 10223 else: 10224 self._pipe_cte_counter += 1 10225 new_cte = f"__tmp{self._pipe_cte_counter}" 10226 10227 with_ = query.args.get("with_") 10228 ctes = with_.pop() if with_ else None 10229 10230 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10231 if ctes: 10232 new_select.set("with_", ctes) 10233 10234 return new_select.with_(new_cte, as_=query, copy=False) 10235 10236 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10237 select = self._parse_select(consume_pipe=False) 10238 if not select: 10239 return query 10240 10241 return self._build_pipe_cte( 10242 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10243 ) 10244 10245 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10246 limit = self._parse_limit() 10247 offset = self._parse_offset() 10248 if limit: 10249 curr_limit = query.args.get("limit", limit) 10250 if curr_limit.expression.to_py() >= limit.expression.to_py(): 10251 query.limit(limit, copy=False) 10252 if offset: 10253 curr_offset = query.args.get("offset") 10254 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10255 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10256 10257 return query 10258 10259 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10260 this = self._parse_disjunction() 10261 if self._match_text_seq("GROUP", "AND", advance=False): 10262 return this 10263 10264 this = self._parse_alias(this) 10265 10266 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10267 return self._parse_ordered(lambda: this) 10268 10269 return this 10270 10271 def _parse_pipe_syntax_aggregate_group_order_by( 10272 self, query: exp.Select, group_by_exists: bool = True 10273 ) -> exp.Select: 10274 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10275 aggregates_or_groups, orders = [], [] 10276 for element in expr: 10277 if isinstance(element, exp.Ordered): 10278 this = element.this 10279 if isinstance(this, exp.Alias): 10280 element.set("this", this.args["alias"]) 10281 orders.append(element) 10282 else: 10283 this = element 10284 aggregates_or_groups.append(this) 10285 10286 if group_by_exists: 10287 query.select( 10288 *aggregates_or_groups, *query.expressions, append=False, copy=False 10289 ).group_by( 10290 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10291 copy=False, 10292 ) 10293 else: 10294 query.select(*aggregates_or_groups, append=False, copy=False) 10295 10296 if orders: 10297 return query.order_by(*orders, append=False, copy=False) 10298 10299 return query 10300 10301 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10302 self._match_text_seq("AGGREGATE") 10303 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10304 10305 if self._match(TokenType.GROUP_BY) or ( 10306 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10307 ): 10308 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10309 10310 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10311 10312 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10313 first_setop = self.parse_set_operation(this=query) 10314 if not first_setop: 10315 return None 10316 10317 def _parse_and_unwrap_query() -> exp.Expr | None: 10318 expr = self._parse_paren() 10319 return expr.assert_is(exp.Subquery).unnest() if expr else None 10320 10321 first_setop.this.pop() 10322 10323 setops = [ 10324 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10325 *self._parse_csv(_parse_and_unwrap_query), 10326 ] 10327 10328 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10329 with_ = query.args.get("with_") 10330 ctes = with_.pop() if with_ else None 10331 10332 if isinstance(first_setop, exp.Union): 10333 query = query.union(*setops, copy=False, **first_setop.args) 10334 elif isinstance(first_setop, exp.Except): 10335 query = query.except_(*setops, copy=False, **first_setop.args) 10336 else: 10337 query = query.intersect(*setops, copy=False, **first_setop.args) 10338 10339 query.set("with_", ctes) 10340 10341 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10342 10343 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10344 join = self._parse_join() 10345 if not join: 10346 return None 10347 10348 if isinstance(query, exp.Select): 10349 return query.join(join, copy=False) 10350 10351 return query 10352 10353 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10354 pivots = self._parse_pivots() 10355 if not pivots: 10356 return query 10357 10358 from_ = query.args.get("from_") 10359 if from_: 10360 from_.this.set("pivots", pivots) 10361 10362 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10363 10364 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10365 self._match_text_seq("EXTEND") 10366 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10367 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10368 10369 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10370 sample = self._parse_table_sample() 10371 10372 with_ = query.args.get("with_") 10373 if with_: 10374 with_.expressions[-1].this.set("sample", sample) 10375 else: 10376 query.set("sample", sample) 10377 10378 return query 10379 10380 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10381 if isinstance(query, exp.Subquery): 10382 query = exp.select("*").from_(query, copy=False) 10383 10384 if not query.args.get("from_"): 10385 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10386 10387 while self._match(TokenType.PIPE_GT): 10388 start_index = self._index 10389 start_text = self._curr.text.upper() 10390 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10391 if not parser: 10392 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10393 # keywords, making it tricky to disambiguate them without lookahead. The approach 10394 # here is to try and parse a set operation and if that fails, then try to parse a 10395 # join operator. If that fails as well, then the operator is not supported. 10396 parsed_query = self._parse_pipe_syntax_set_operator(query) 10397 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10398 if not parsed_query: 10399 self._retreat(start_index) 10400 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10401 break 10402 query = parsed_query 10403 else: 10404 query = parser(self, query) 10405 10406 return query 10407 10408 def _parse_declareitem(self) -> exp.DeclareItem | None: 10409 self._match_texts(("VAR", "VARIABLE")) 10410 10411 vars = self._parse_csv(self._parse_id_var) 10412 if not vars: 10413 return None 10414 10415 self._match(TokenType.ALIAS) 10416 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10417 default = ( 10418 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10419 ) and self._parse_bitwise() 10420 10421 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10422 10423 def _parse_declare(self) -> exp.Declare | exp.Command: 10424 start = self._prev 10425 replace = self._match_text_seq("OR", "REPLACE") 10426 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10427 10428 if not expressions or self._curr: 10429 return self._parse_as_command(start) 10430 10431 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10432 10433 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10434 exp_class = exp.Cast if strict else exp.TryCast 10435 10436 if exp_class == exp.TryCast: 10437 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10438 10439 return self.expression(exp_class(**kwargs)) 10440 10441 def _parse_json_value(self) -> exp.JSONValue: 10442 this = self._parse_bitwise() 10443 self._match(TokenType.COMMA) 10444 path = self._parse_bitwise() 10445 10446 returning = self._match(TokenType.RETURNING) and self._parse_type() 10447 10448 return self.expression( 10449 exp.JSONValue( 10450 this=this, 10451 path=self.dialect.to_json_path(path), 10452 returning=returning, 10453 on_condition=self._parse_on_condition(), 10454 ) 10455 ) 10456 10457 def _parse_group_concat(self) -> exp.Expr | None: 10458 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10459 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10460 concat_exprs = [ 10461 self.expression( 10462 exp.Concat( 10463 expressions=node.expressions, 10464 safe=True, 10465 coalesce=self.dialect.CONCAT_COALESCE, 10466 ) 10467 ) 10468 ] 10469 node.set("expressions", concat_exprs) 10470 return node 10471 if len(exprs) == 1: 10472 return exprs[0] 10473 return self.expression( 10474 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10475 ) 10476 10477 args = self._parse_csv(self._parse_lambda) 10478 10479 if args: 10480 order = args[-1] if isinstance(args[-1], exp.Order) else None 10481 10482 if order: 10483 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10484 # remove 'expr' from exp.Order and add it back to args 10485 args[-1] = order.this 10486 order.set("this", concat_exprs(order.this, args)) 10487 10488 this = order or concat_exprs(args[0], args) 10489 else: 10490 this = None 10491 10492 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10493 10494 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10495 10496 def _parse_initcap(self) -> exp.Initcap: 10497 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10498 10499 # attach dialect's default delimiters 10500 if expr.args.get("expression") is None: 10501 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10502 10503 return expr 10504 10505 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10506 if not self._match(TokenType.L_PAREN): 10507 self._retreat(self._index - 1) 10508 return None 10509 10510 op = "" 10511 while self._curr and not self._match(TokenType.R_PAREN): 10512 op += self._curr.text 10513 self._advance() 10514 10515 comments = self._prev_comments 10516 return self.expression( 10517 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10518 comments=comments, 10519 )
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.
1955 def __init__( 1956 self, 1957 error_level: ErrorLevel | None = None, 1958 error_message_context: int = 100, 1959 max_errors: int = 3, 1960 max_nodes: int = -1, 1961 dialect: DialectType = None, 1962 ): 1963 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 1964 self.error_message_context: int = error_message_context 1965 self.max_errors: int = max_errors 1966 self.max_nodes: int = max_nodes 1967 self.dialect: t.Any = _resolve_dialect(dialect) 1968 self.sql: str = "" 1969 self.errors: list[ParseError] = [] 1970 self._tokens: list[Token] = [] 1971 self._tokens_size: i64 = 0 1972 self._index: i64 = 0 1973 self._curr: Token = SENTINEL_NONE 1974 self._next: Token = SENTINEL_NONE 1975 self._prev: Token = SENTINEL_NONE 1976 self._prev_comments: list[str] = [] 1977 self._pipe_cte_counter: int = 0 1978 self._chunks: list[list[Token]] = [] 1979 self._chunk_index: i64 = 0 1980 self._node_count: int = 0
1982 def reset(self) -> None: 1983 self.sql = "" 1984 self.errors = [] 1985 self._tokens = [] 1986 self._tokens_size = 0 1987 self._index = 0 1988 self._curr = SENTINEL_NONE 1989 self._next = SENTINEL_NONE 1990 self._prev = SENTINEL_NONE 1991 self._prev_comments = [] 1992 self._pipe_cte_counter = 0 1993 self._chunks = [] 1994 self._chunk_index = 0 1995 self._node_count = 0
2088 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2089 token = token or self._curr or self._prev or Token.string("") 2090 formatted_sql, start_context, highlight, end_context = highlight_sql( 2091 sql=self.sql, 2092 positions=[(token.start, token.end)], 2093 context_length=self.error_message_context, 2094 ) 2095 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2096 2097 error = ParseError.new( 2098 formatted_message, 2099 description=message, 2100 line=token.line, 2101 col=token.col, 2102 start_context=start_context, 2103 highlight=highlight, 2104 end_context=end_context, 2105 ) 2106 2107 if self.error_level == ErrorLevel.IMMEDIATE: 2108 raise error 2109 2110 self.errors.append(error)
2112 def validate_expression(self, expression: E, args: list | None = None) -> E: 2113 if self.max_nodes > -1: 2114 self._node_count += 1 2115 if self._node_count > self.max_nodes: 2116 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2117 if self.error_level != ErrorLevel.IGNORE: 2118 for error_message in expression.error_messages(args): 2119 self.raise_error(error_message) 2120 return expression
2139 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2140 """ 2141 Parses a list of tokens and returns a list of syntax trees, one tree 2142 per parsed SQL statement. 2143 2144 Args: 2145 raw_tokens: The list of tokens. 2146 sql: The original SQL string. 2147 2148 Returns: 2149 The list of the produced syntax trees. 2150 """ 2151 return self._parse( 2152 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2153 )
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.
2155 def parse_into( 2156 self, 2157 expression_types: exp.IntoType, 2158 raw_tokens: list[Token], 2159 sql: str | None = None, 2160 ) -> list[exp.Expr | None]: 2161 """ 2162 Parses a list of tokens into a given Expr type. If a collection of Expr 2163 types is given instead, this method will try to parse the token list into each one 2164 of them, stopping at the first for which the parsing succeeds. 2165 2166 Args: 2167 expression_types: The expression type(s) to try and parse the token list into. 2168 raw_tokens: The list of tokens. 2169 sql: The original SQL string, used to produce helpful debug messages. 2170 2171 Returns: 2172 The target Expr. 2173 """ 2174 errors = [] 2175 for expression_type in ensure_list(expression_types): 2176 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2177 if not parser: 2178 raise TypeError(f"No parser registered for {expression_type}") 2179 2180 try: 2181 return self._parse(parser, raw_tokens, sql) 2182 except ParseError as e: 2183 e.errors[0]["into_expression"] = expression_type 2184 errors.append(e) 2185 2186 raise ParseError( 2187 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2188 errors=merge_errors(errors), 2189 ) 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.
2191 def check_errors(self) -> None: 2192 """Logs or raises any found errors, depending on the chosen error level setting.""" 2193 if self.error_level == ErrorLevel.WARN: 2194 for error in self.errors: 2195 logger.error(str(error)) 2196 elif self.error_level == ErrorLevel.RAISE and self.errors: 2197 raise ParseError( 2198 concat_messages(self.errors, self.max_errors), 2199 errors=merge_errors(self.errors), 2200 )
Logs or raises any found errors, depending on the chosen error level setting.
2202 def expression( 2203 self, 2204 instance: E, 2205 token: Token | None = None, 2206 comments: list[str] | None = None, 2207 ) -> E: 2208 if token: 2209 instance.update_positions(token) 2210 instance.add_comments(comments) if comments else self._add_comments(instance) 2211 if not instance.is_primitive: 2212 instance = self.validate_expression(instance) 2213 return instance
5929 def parse_set_operation( 5930 self, this: exp.Expr | None, consume_pipe: bool = False 5931 ) -> exp.Expr | None: 5932 start = self._index 5933 _, side_token, kind_token = self._parse_join_parts() 5934 5935 side = side_token.text if side_token else None 5936 kind = kind_token.text if kind_token else None 5937 5938 if not self._match_set(self.SET_OPERATIONS): 5939 self._retreat(start) 5940 return None 5941 5942 token_type = self._prev.token_type 5943 5944 if token_type == TokenType.UNION: 5945 operation: type[exp.SetOperation] = exp.Union 5946 elif token_type == TokenType.EXCEPT: 5947 operation = exp.Except 5948 else: 5949 operation = exp.Intersect 5950 5951 comments = self._prev.comments 5952 5953 if self._match(TokenType.DISTINCT): 5954 distinct: bool | None = True 5955 elif self._match(TokenType.ALL): 5956 distinct = False 5957 else: 5958 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5959 if distinct is None: 5960 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5961 5962 by_name = ( 5963 self._match_text_seq("BY", "NAME") 5964 or self._match_text_seq("STRICT", "CORRESPONDING") 5965 or None 5966 ) 5967 if self._match_text_seq("CORRESPONDING"): 5968 by_name = True 5969 if not side and not kind: 5970 kind = "INNER" 5971 5972 on_column_list = None 5973 if by_name and self._match_texts(("ON", "BY")): 5974 on_column_list = self._parse_wrapped_csv(self._parse_column) 5975 5976 expression = self._parse_select( 5977 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5978 ) 5979 5980 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5981 # in _parse_cte and so that alias pushdown can reach into set operation branches 5982 if isinstance(this, exp.Values): 5983 this = self._values_to_select(this) 5984 if isinstance(expression, exp.Values): 5985 expression = self._values_to_select(expression) 5986 5987 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 5988 subquery = this.this 5989 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 5990 subquery.add_comments(this.pop_comments()) 5991 this = subquery 5992 5993 return self.expression( 5994 operation( 5995 this=this, 5996 distinct=distinct, 5997 by_name=by_name, 5998 expression=expression, 5999 side=side, 6000 kind=kind, 6001 on=on_column_list, 6002 ), 6003 comments=comments, 6004 )