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"} 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 index = self._index 2924 2925 seq_props = self._parse_sequence_properties() 2926 if seq_props: 2927 return seq_props 2928 2929 self._retreat(index) 2930 return self._parse_key_value_property() 2931 2932 def _parse_key_value_property( 2933 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 2934 ) -> exp.Property | None: 2935 index = self._index 2936 key = self._parse_column() 2937 2938 if not self._match(TokenType.EQ): 2939 self._retreat(index) 2940 return None 2941 2942 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 2943 if isinstance(key, exp.Column): 2944 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 2945 2946 value = ( 2947 parse_value() 2948 if parse_value 2949 else self._parse_bitwise() or self._parse_var(any_token=True) 2950 ) 2951 2952 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 2953 if isinstance(value, exp.Column): 2954 value = exp.var(value.name) 2955 2956 return self.expression(exp.Property(this=key, value=value)) 2957 2958 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 2959 if self._match_text_seq("BY"): 2960 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 2961 2962 self._match(TokenType.ALIAS) 2963 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 2964 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 2965 2966 return self.expression( 2967 exp.FileFormatProperty( 2968 this=( 2969 self.expression( 2970 exp.InputOutputFormat( 2971 input_format=input_format, output_format=output_format 2972 ) 2973 ) 2974 if input_format or output_format 2975 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 2976 ), 2977 hive_format=True, 2978 ) 2979 ) 2980 2981 def _parse_unquoted_field(self) -> exp.Expr | None: 2982 field = self._parse_field() 2983 if isinstance(field, exp.Identifier) and not field.quoted: 2984 field = exp.var(field) 2985 2986 return field 2987 2988 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 2989 self._match(TokenType.EQ) 2990 self._match(TokenType.ALIAS) 2991 2992 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 2993 2994 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 2995 properties = [] 2996 while True: 2997 if before: 2998 prop = self._parse_property_before() 2999 else: 3000 prop = self._parse_property() 3001 if not prop: 3002 break 3003 for p in ensure_list(prop): 3004 properties.append(p) 3005 3006 if properties: 3007 return self.expression(exp.Properties(expressions=properties)) 3008 3009 return None 3010 3011 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3012 return self.expression( 3013 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3014 ) 3015 3016 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3017 return self.expression( 3018 exp.SqlSecurityProperty( 3019 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3020 ) 3021 ) 3022 3023 def _parse_settings_property(self) -> exp.SettingsProperty: 3024 return self.expression( 3025 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3026 ) 3027 3028 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3029 if not self._match_text_seq("ON", "NULL", "INPUT"): 3030 self._retreat(self._index - 1) 3031 return None 3032 3033 return self.expression(exp.CalledOnNullInputProperty()) 3034 3035 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3036 if self._index >= 2: 3037 pre_volatile_token = self._tokens[self._index - 2] 3038 else: 3039 pre_volatile_token = None 3040 3041 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3042 return exp.VolatileProperty() 3043 3044 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3045 3046 def _parse_retention_period(self) -> exp.Var: 3047 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3048 number = self._parse_number() 3049 number_str = f"{number} " if number else "" 3050 unit = self._parse_var(any_token=True) 3051 return exp.var(f"{number_str}{unit}") 3052 3053 def _parse_system_versioning_property( 3054 self, with_: bool = False 3055 ) -> exp.WithSystemVersioningProperty: 3056 self._match(TokenType.EQ) 3057 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3058 3059 if self._match_text_seq("OFF"): 3060 prop.set("on", False) 3061 return prop 3062 3063 self._match(TokenType.ON) 3064 if self._match(TokenType.L_PAREN): 3065 while self._curr and not self._match(TokenType.R_PAREN): 3066 if self._match_text_seq("HISTORY_TABLE", "="): 3067 prop.set("this", self._parse_table_parts()) 3068 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3069 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3070 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3071 prop.set("retention_period", self._parse_retention_period()) 3072 3073 self._match(TokenType.COMMA) 3074 3075 return prop 3076 3077 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3078 self._match(TokenType.EQ) 3079 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3080 prop = self.expression(exp.DataDeletionProperty(on=on)) 3081 3082 if self._match(TokenType.L_PAREN): 3083 while self._curr and not self._match(TokenType.R_PAREN): 3084 if self._match_text_seq("FILTER_COLUMN", "="): 3085 prop.set("filter_column", self._parse_column()) 3086 elif self._match_text_seq("RETENTION_PERIOD", "="): 3087 prop.set("retention_period", self._parse_retention_period()) 3088 3089 self._match(TokenType.COMMA) 3090 3091 return prop 3092 3093 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3094 kind = "HASH" 3095 expressions: list[exp.Expr] | None = None 3096 if self._match_text_seq("BY", "HASH"): 3097 expressions = self._parse_wrapped_csv(self._parse_id_var) 3098 elif self._match_text_seq("BY", "RANDOM"): 3099 kind = "RANDOM" 3100 3101 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3102 buckets: exp.Expr | None = None 3103 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3104 buckets = self._parse_number() 3105 3106 return self.expression( 3107 exp.DistributedByProperty( 3108 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3109 ) 3110 ) 3111 3112 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3113 self._match_text_seq("KEY") 3114 expressions = self._parse_wrapped_id_vars() 3115 return self.expression(expr_type(expressions=expressions)) 3116 3117 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3118 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3119 prop = self._parse_system_versioning_property(with_=True) 3120 self._match_r_paren() 3121 return prop 3122 3123 if self._match(TokenType.L_PAREN, advance=False): 3124 result: list[exp.Expr] = [] 3125 for i in self._parse_wrapped_properties(): 3126 result.extend(i) if isinstance(i, list) else result.append(i) 3127 return result 3128 3129 if self._match_text_seq("JOURNAL"): 3130 return self._parse_withjournaltable() 3131 3132 if self._match_texts(self.VIEW_ATTRIBUTES): 3133 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3134 3135 if self._match_text_seq("DATA"): 3136 return self._parse_withdata(no=False) 3137 elif self._match_text_seq("NO", "DATA"): 3138 return self._parse_withdata(no=True) 3139 3140 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3141 return self._parse_serde_properties(with_=True) 3142 3143 if self._match(TokenType.SCHEMA): 3144 return self.expression( 3145 exp.WithSchemaBindingProperty( 3146 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3147 ) 3148 ) 3149 3150 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3151 return self.expression( 3152 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3153 ) 3154 3155 if not self._next: 3156 return None 3157 3158 return self._parse_withisolatedloading() 3159 3160 def _parse_procedure_option(self) -> exp.Expr | None: 3161 if self._match_text_seq("EXECUTE", "AS"): 3162 return self.expression( 3163 exp.ExecuteAsProperty( 3164 this=self._parse_var_from_options( 3165 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3166 ) 3167 or self._parse_string() 3168 ) 3169 ) 3170 3171 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3172 3173 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3174 def _parse_definer(self) -> exp.DefinerProperty | None: 3175 self._match(TokenType.EQ) 3176 3177 user = self._parse_id_var() 3178 self._match(TokenType.PARAMETER) 3179 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3180 3181 if not user or not host: 3182 return None 3183 3184 return exp.DefinerProperty(this=f"{user}@{host}") 3185 3186 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3187 self._match(TokenType.TABLE) 3188 self._match(TokenType.EQ) 3189 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3190 3191 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3192 return self.expression(exp.LogProperty(no=no)) 3193 3194 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3195 return self.expression(exp.JournalProperty(**kwargs)) 3196 3197 def _parse_checksum(self) -> exp.ChecksumProperty: 3198 self._match(TokenType.EQ) 3199 3200 on = None 3201 if self._match(TokenType.ON): 3202 on = True 3203 elif self._match_text_seq("OFF"): 3204 on = False 3205 3206 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3207 3208 def _parse_cluster(self) -> exp.Cluster: 3209 self._match(TokenType.CLUSTER_BY) 3210 return self.expression( 3211 exp.Cluster( 3212 expressions=self._parse_csv(self._parse_column), 3213 ) 3214 ) 3215 3216 def _parse_cluster_property(self) -> exp.ClusterProperty: 3217 return self.expression( 3218 exp.ClusterProperty( 3219 expressions=self._parse_wrapped_csv(self._parse_column), 3220 ) 3221 ) 3222 3223 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3224 self._match_text_seq("BY") 3225 3226 self._match_l_paren() 3227 expressions = self._parse_csv(self._parse_column) 3228 self._match_r_paren() 3229 3230 if self._match_text_seq("SORTED", "BY"): 3231 self._match_l_paren() 3232 sorted_by = self._parse_csv(self._parse_ordered) 3233 self._match_r_paren() 3234 else: 3235 sorted_by = None 3236 3237 self._match(TokenType.INTO) 3238 buckets = self._parse_number() 3239 self._match_text_seq("BUCKETS") 3240 3241 return self.expression( 3242 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3243 ) 3244 3245 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3246 if not self._match_text_seq("GRANTS"): 3247 self._retreat(self._index - 1) 3248 return None 3249 3250 return self.expression(exp.CopyGrantsProperty()) 3251 3252 def _parse_freespace(self) -> exp.FreespaceProperty: 3253 self._match(TokenType.EQ) 3254 return self.expression( 3255 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3256 ) 3257 3258 def _parse_mergeblockratio( 3259 self, no: bool = False, default: bool = False 3260 ) -> exp.MergeBlockRatioProperty: 3261 if self._match(TokenType.EQ): 3262 return self.expression( 3263 exp.MergeBlockRatioProperty( 3264 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3265 ) 3266 ) 3267 3268 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3269 3270 def _parse_datablocksize( 3271 self, 3272 default: bool | None = None, 3273 minimum: bool | None = None, 3274 maximum: bool | None = None, 3275 ) -> exp.DataBlocksizeProperty: 3276 self._match(TokenType.EQ) 3277 size = self._parse_number() 3278 3279 units = None 3280 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3281 units = self._prev.text 3282 3283 return self.expression( 3284 exp.DataBlocksizeProperty( 3285 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3286 ) 3287 ) 3288 3289 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3290 self._match(TokenType.EQ) 3291 always = self._match_text_seq("ALWAYS") 3292 manual = self._match_text_seq("MANUAL") 3293 never = self._match_text_seq("NEVER") 3294 default = self._match_text_seq("DEFAULT") 3295 3296 autotemp = None 3297 if self._match_text_seq("AUTOTEMP"): 3298 autotemp = self._parse_schema() 3299 3300 return self.expression( 3301 exp.BlockCompressionProperty( 3302 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3303 ) 3304 ) 3305 3306 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3307 index = self._index 3308 no = self._match_text_seq("NO") 3309 concurrent = self._match_text_seq("CONCURRENT") 3310 3311 if not self._match_text_seq("ISOLATED", "LOADING"): 3312 self._retreat(index) 3313 return None 3314 3315 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3316 return self.expression( 3317 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3318 ) 3319 3320 def _parse_locking(self) -> exp.LockingProperty: 3321 if self._match(TokenType.TABLE): 3322 kind = "TABLE" 3323 elif self._match(TokenType.VIEW): 3324 kind = "VIEW" 3325 elif self._match(TokenType.ROW): 3326 kind = "ROW" 3327 elif self._match_text_seq("DATABASE"): 3328 kind = "DATABASE" 3329 else: 3330 kind = None 3331 3332 if kind in ("DATABASE", "TABLE", "VIEW"): 3333 this = self._parse_table_parts() 3334 else: 3335 this = None 3336 3337 if self._match(TokenType.FOR): 3338 for_or_in = "FOR" 3339 elif self._match(TokenType.IN): 3340 for_or_in = "IN" 3341 else: 3342 for_or_in = None 3343 3344 if self._match_text_seq("ACCESS"): 3345 lock_type = "ACCESS" 3346 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3347 lock_type = "EXCLUSIVE" 3348 elif self._match_text_seq("SHARE"): 3349 lock_type = "SHARE" 3350 elif self._match_text_seq("READ"): 3351 lock_type = "READ" 3352 elif self._match_text_seq("WRITE"): 3353 lock_type = "WRITE" 3354 elif self._match_text_seq("CHECKSUM"): 3355 lock_type = "CHECKSUM" 3356 else: 3357 lock_type = None 3358 3359 override = self._match_text_seq("OVERRIDE") 3360 3361 return self.expression( 3362 exp.LockingProperty( 3363 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3364 ) 3365 ) 3366 3367 def _parse_partition_by(self) -> list[exp.Expr]: 3368 if self._match(TokenType.PARTITION_BY): 3369 return self._parse_csv(self._parse_disjunction) 3370 return [] 3371 3372 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3373 def _parse_partition_bound_expr() -> exp.Expr | None: 3374 if self._match_text_seq("MINVALUE"): 3375 return exp.var("MINVALUE") 3376 if self._match_text_seq("MAXVALUE"): 3377 return exp.var("MAXVALUE") 3378 return self._parse_bitwise() 3379 3380 this: exp.Expr | list[exp.Expr] | None = None 3381 expression = None 3382 from_expressions = None 3383 to_expressions = None 3384 3385 if self._match(TokenType.IN): 3386 this = self._parse_wrapped_csv(self._parse_bitwise) 3387 elif self._match(TokenType.FROM): 3388 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3389 self._match_text_seq("TO") 3390 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3391 elif self._match_text_seq("WITH", "(", "MODULUS"): 3392 this = self._parse_number() 3393 self._match_text_seq(",", "REMAINDER") 3394 expression = self._parse_number() 3395 self._match_r_paren() 3396 else: 3397 self.raise_error("Failed to parse partition bound spec.") 3398 3399 return self.expression( 3400 exp.PartitionBoundSpec( 3401 this=this, 3402 expression=expression, 3403 from_expressions=from_expressions, 3404 to_expressions=to_expressions, 3405 ) 3406 ) 3407 3408 # https://www.postgresql.org/docs/current/sql-createtable.html 3409 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3410 if not self._match_text_seq("OF"): 3411 self._retreat(self._index - 1) 3412 return None 3413 3414 this = self._parse_table(schema=True) 3415 3416 if self._match(TokenType.DEFAULT): 3417 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3418 elif self._match_text_seq("FOR", "VALUES"): 3419 expression = self._parse_partition_bound_spec() 3420 else: 3421 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3422 3423 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3424 3425 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3426 self._match(TokenType.EQ) 3427 return self.expression( 3428 exp.PartitionedByProperty( 3429 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3430 ) 3431 ) 3432 3433 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3434 if self._match_text_seq("AND", "STATISTICS"): 3435 statistics = True 3436 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3437 statistics = False 3438 else: 3439 statistics = None 3440 3441 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3442 3443 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3444 if self._match_text_seq("SQL"): 3445 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3446 return None 3447 3448 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3449 if self._match_text_seq("SQL", "DATA"): 3450 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3451 return None 3452 3453 def _parse_no_property(self) -> exp.Expr | None: 3454 if self._match_text_seq("PRIMARY", "INDEX"): 3455 return exp.NoPrimaryIndexProperty() 3456 if self._match_text_seq("SQL"): 3457 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3458 return None 3459 3460 def _parse_on_property(self) -> exp.Expr | None: 3461 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3462 return exp.OnCommitProperty() 3463 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3464 return exp.OnCommitProperty(delete=True) 3465 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3466 3467 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3468 if self._match_text_seq("SQL", "DATA"): 3469 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3470 return None 3471 3472 def _parse_distkey(self) -> exp.DistKeyProperty: 3473 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3474 3475 def _parse_create_like(self) -> exp.LikeProperty | None: 3476 table = self._parse_table(schema=True) 3477 3478 options = [] 3479 while self._match_texts(("INCLUDING", "EXCLUDING")): 3480 this = self._prev.text.upper() 3481 3482 id_var = self._parse_id_var() 3483 if not id_var: 3484 return None 3485 3486 options.append( 3487 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3488 ) 3489 3490 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3491 3492 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3493 return self.expression( 3494 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3495 ) 3496 3497 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3498 self._match(TokenType.EQ) 3499 return self.expression( 3500 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3501 ) 3502 3503 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3504 self._match_text_seq("WITH", "CONNECTION") 3505 return self.expression( 3506 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3507 ) 3508 3509 def _parse_returns(self) -> exp.ReturnsProperty: 3510 value: exp.Expr | None 3511 null = None 3512 is_table = self._match(TokenType.TABLE) 3513 3514 if is_table: 3515 if self._match(TokenType.LT): 3516 value = self.expression( 3517 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3518 ) 3519 if not self._match(TokenType.GT): 3520 self.raise_error("Expecting >") 3521 else: 3522 value = self._parse_schema(exp.var("TABLE")) 3523 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3524 null = True 3525 value = None 3526 else: 3527 value = self._parse_types() 3528 3529 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3530 3531 def _parse_describe(self) -> exp.Describe: 3532 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3533 style: str | None = ( 3534 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3535 ) 3536 if self._match(TokenType.DOT): 3537 style = None 3538 self._retreat(self._index - 2) 3539 3540 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3541 3542 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3543 this = self._parse_statement() 3544 else: 3545 this = self._parse_table(schema=True) 3546 3547 properties = self._parse_properties() 3548 expressions = properties.expressions if properties else None 3549 partition = self._parse_partition() 3550 return self.expression( 3551 exp.Describe( 3552 this=this, 3553 style=style, 3554 kind=kind, 3555 expressions=expressions, 3556 partition=partition, 3557 format=format, 3558 as_json=self._match_text_seq("AS", "JSON"), 3559 ) 3560 ) 3561 3562 def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts: 3563 kind = self._prev.text.upper() 3564 expressions = [] 3565 3566 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3567 if self._match(TokenType.WHEN): 3568 expression = self._parse_disjunction() 3569 self._match(TokenType.THEN) 3570 else: 3571 expression = None 3572 3573 else_ = self._match(TokenType.ELSE) 3574 3575 if not self._match(TokenType.INTO): 3576 return None 3577 3578 return self.expression( 3579 exp.ConditionalInsert( 3580 this=self.expression( 3581 exp.Insert( 3582 this=self._parse_table(schema=True), 3583 expression=self._parse_derived_table_values(), 3584 ) 3585 ), 3586 expression=expression, 3587 else_=else_, 3588 ) 3589 ) 3590 3591 expression = parse_conditional_insert() 3592 while expression is not None: 3593 expressions.append(expression) 3594 expression = parse_conditional_insert() 3595 3596 return self.expression( 3597 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3598 comments=comments, 3599 ) 3600 3601 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3602 comments: list[str] = [] 3603 hint = self._parse_hint() 3604 overwrite = self._match(TokenType.OVERWRITE) 3605 ignore = self._match(TokenType.IGNORE) 3606 local = self._match_text_seq("LOCAL") 3607 alternative = None 3608 is_function = None 3609 3610 if self._match_text_seq("DIRECTORY"): 3611 this: exp.Expr | None = self.expression( 3612 exp.Directory( 3613 this=self._parse_var_or_string(), 3614 local=local, 3615 row_format=self._parse_row_format(match_row=True), 3616 ) 3617 ) 3618 else: 3619 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3620 comments += ensure_list(self._prev_comments) 3621 return self._parse_multitable_inserts(comments) 3622 3623 if self._match(TokenType.OR): 3624 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3625 3626 self._match(TokenType.INTO) 3627 comments += ensure_list(self._prev_comments) 3628 self._match(TokenType.TABLE) 3629 is_function = self._match(TokenType.FUNCTION) 3630 3631 this = self._parse_function() if is_function else self._parse_insert_table() 3632 3633 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3634 set_values = None 3635 if self._match(TokenType.SET): 3636 columns = [] 3637 values = [] 3638 3639 def _parse_set_assignment() -> exp.Expr | None: 3640 target = self._parse_column() 3641 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3642 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3643 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3644 else: 3645 value = self._parse_disjunction() 3646 3647 if value: 3648 columns.append(target.this) 3649 values.append(value) 3650 return value 3651 3652 self.raise_error("Expected column assignment in INSERT ... SET") 3653 return None 3654 3655 self._parse_csv(_parse_set_assignment) 3656 3657 this = self.expression(exp.Schema(this=this, expressions=columns)) 3658 set_values = self.expression( 3659 exp.Values( 3660 expressions=[exp.Tuple(expressions=values)], 3661 alias=self._parse_table_alias(), 3662 ) 3663 ) 3664 3665 returning = self._parse_returning() # TSQL allows RETURNING before source 3666 3667 stored = self._match_text_seq("STORED") and self._parse_stored() 3668 by_name = self._match_text_seq("BY", "NAME") 3669 exists = self._parse_exists() 3670 replace_where = None 3671 replace_using = None 3672 3673 if self._match(TokenType.REPLACE): 3674 if self._match(TokenType.WHERE): 3675 replace_where = self._parse_disjunction() 3676 elif self._match(TokenType.USING): 3677 replace_using = self._parse_using_identifiers() 3678 3679 return self.expression( 3680 exp.Insert( 3681 hint=hint, 3682 is_function=is_function, 3683 this=this, 3684 stored=stored, 3685 by_name=by_name, 3686 exists=exists, 3687 where=replace_where, 3688 using=replace_using, 3689 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3690 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3691 default=self._match_text_seq("DEFAULT", "VALUES"), 3692 expression=set_values 3693 or self._parse_derived_table_values() 3694 or self._parse_ddl_select(), 3695 conflict=self._parse_on_conflict(), 3696 returning=returning or self._parse_returning(), 3697 overwrite=overwrite, 3698 alternative=alternative, 3699 ignore=ignore, 3700 source=self._match(TokenType.TABLE) and self._parse_table(), 3701 ), 3702 comments=comments, 3703 ) 3704 3705 def _parse_insert_table(self) -> exp.Expr | None: 3706 this = self._parse_table(schema=True, parse_partition=True) 3707 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3708 this.set("alias", self._parse_table_alias()) 3709 return this 3710 3711 def _parse_kill(self) -> exp.Kill: 3712 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3713 3714 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3715 3716 def _parse_on_conflict(self) -> exp.OnConflict | None: 3717 conflict = self._match_text_seq("ON", "CONFLICT") 3718 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3719 3720 if not conflict and not duplicate: 3721 return None 3722 3723 conflict_keys = None 3724 constraint = None 3725 3726 if conflict: 3727 if self._match_text_seq("ON", "CONSTRAINT"): 3728 constraint = self._parse_id_var() 3729 elif self._match(TokenType.L_PAREN): 3730 conflict_keys = self._parse_csv(self._parse_indexed_column) 3731 self._match_r_paren() 3732 3733 index_predicate = self._parse_where() 3734 3735 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3736 if self._prev.token_type == TokenType.UPDATE: 3737 self._match(TokenType.SET) 3738 expressions = self._parse_csv(self._parse_equality) 3739 else: 3740 expressions = None 3741 3742 return self.expression( 3743 exp.OnConflict( 3744 duplicate=duplicate, 3745 expressions=expressions, 3746 action=action, 3747 conflict_keys=conflict_keys, 3748 index_predicate=index_predicate, 3749 constraint=constraint, 3750 where=self._parse_where(), 3751 ) 3752 ) 3753 3754 def _parse_returning(self) -> exp.Returning | None: 3755 if not self._match(TokenType.RETURNING): 3756 return None 3757 return self.expression( 3758 exp.Returning( 3759 expressions=self._parse_csv(self._parse_expression), 3760 into=self._match(TokenType.INTO) and self._parse_table_part(), 3761 ) 3762 ) 3763 3764 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3765 if not self._match(TokenType.FORMAT): 3766 return None 3767 return self._parse_row_format() 3768 3769 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3770 index = self._index 3771 with_ = with_ or self._match_text_seq("WITH") 3772 3773 if not self._match(TokenType.SERDE_PROPERTIES): 3774 self._retreat(index) 3775 return None 3776 return self.expression( 3777 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3778 ) 3779 3780 def _parse_row_format( 3781 self, match_row: bool = False 3782 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3783 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3784 return None 3785 3786 if self._match_text_seq("SERDE"): 3787 this = self._parse_string() 3788 3789 serde_properties = self._parse_serde_properties() 3790 3791 return self.expression( 3792 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3793 ) 3794 3795 self._match_text_seq("DELIMITED") 3796 3797 kwargs = {} 3798 3799 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3800 kwargs["fields"] = self._parse_string() 3801 if self._match_text_seq("ESCAPED", "BY"): 3802 kwargs["escaped"] = self._parse_string() 3803 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3804 kwargs["collection_items"] = self._parse_string() 3805 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3806 kwargs["map_keys"] = self._parse_string() 3807 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3808 kwargs["lines"] = self._parse_string() 3809 if self._match_text_seq("NULL", "DEFINED", "AS"): 3810 kwargs["null"] = self._parse_string() 3811 3812 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3813 3814 def _parse_load(self) -> exp.LoadData | exp.Command: 3815 if self._match_text_seq("DATA"): 3816 local = self._match_text_seq("LOCAL") 3817 self._match_text_seq("INPATH") 3818 inpath = self._parse_string() 3819 overwrite = self._match(TokenType.OVERWRITE) 3820 temp: bool | None = None 3821 if self._match(TokenType.INTO): 3822 temp = self._match(TokenType.TEMPORARY) 3823 self._match(TokenType.TABLE) 3824 3825 return self.expression( 3826 exp.LoadData( 3827 this=self._parse_table(schema=True), 3828 local=local, 3829 overwrite=overwrite, 3830 temp=temp, 3831 inpath=inpath, 3832 files=self._match_text_seq("FROM", "FILES") 3833 and exp.Properties(expressions=self._parse_wrapped_properties()), 3834 partition=self._parse_partition(), 3835 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3836 serde=self._match_text_seq("SERDE") and self._parse_string(), 3837 ) 3838 ) 3839 return self._parse_as_command(self._prev) 3840 3841 def _parse_delete(self) -> exp.Delete: 3842 hint = self._parse_hint() 3843 3844 # This handles MySQL's "Multiple-Table Syntax" 3845 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3846 tables = None 3847 if not self._match(TokenType.FROM, advance=False): 3848 tables = self._parse_csv(self._parse_table) or None 3849 3850 returning = self._parse_returning() 3851 3852 return self.expression( 3853 exp.Delete( 3854 hint=hint, 3855 tables=tables, 3856 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3857 using=self._match(TokenType.USING) 3858 and self._parse_csv(lambda: self._parse_table(joins=True)), 3859 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3860 where=self._parse_where(), 3861 returning=returning or self._parse_returning(), 3862 order=self._parse_order(), 3863 limit=self._parse_limit(), 3864 ) 3865 ) 3866 3867 def _parse_update(self) -> exp.Update: 3868 hint = self._parse_hint() 3869 kwargs: dict[str, object] = { 3870 "hint": hint, 3871 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3872 } 3873 while self._curr: 3874 if self._match(TokenType.SET): 3875 kwargs["expressions"] = self._parse_csv(self._parse_equality) 3876 elif self._match(TokenType.RETURNING, advance=False): 3877 kwargs["returning"] = self._parse_returning() 3878 elif self._match(TokenType.FROM, advance=False): 3879 from_ = self._parse_from(joins=True) 3880 table = from_.this if from_ else None 3881 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3882 table.set("joins", list(self._parse_joins()) or None) 3883 3884 kwargs["from_"] = from_ 3885 elif self._match(TokenType.WHERE, advance=False): 3886 kwargs["where"] = self._parse_where() 3887 elif self._match(TokenType.ORDER_BY, advance=False): 3888 kwargs["order"] = self._parse_order() 3889 elif self._match(TokenType.LIMIT, advance=False): 3890 kwargs["limit"] = self._parse_limit() 3891 else: 3892 break 3893 3894 return self.expression(exp.Update(**kwargs)) 3895 3896 def _parse_use(self) -> exp.Use: 3897 return self.expression( 3898 exp.Use( 3899 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 3900 this=self._parse_table(schema=False), 3901 ) 3902 ) 3903 3904 def _parse_uncache(self) -> exp.Uncache: 3905 if not self._match(TokenType.TABLE): 3906 self.raise_error("Expecting TABLE after UNCACHE") 3907 3908 return self.expression( 3909 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 3910 ) 3911 3912 def _parse_cache(self) -> exp.Cache: 3913 lazy = self._match_text_seq("LAZY") 3914 self._match(TokenType.TABLE) 3915 table = self._parse_table(schema=True) 3916 3917 options = [] 3918 if self._match_text_seq("OPTIONS"): 3919 self._match_l_paren() 3920 k = self._parse_string() 3921 self._match(TokenType.EQ) 3922 v = self._parse_string() 3923 options = [k, v] 3924 self._match_r_paren() 3925 3926 self._match(TokenType.ALIAS) 3927 return self.expression( 3928 exp.Cache( 3929 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 3930 ) 3931 ) 3932 3933 def _parse_partition(self) -> exp.Partition | None: 3934 if not self._match_texts(self.PARTITION_KEYWORDS): 3935 return None 3936 3937 return self.expression( 3938 exp.Partition( 3939 subpartition=self._prev.text.upper() == "SUBPARTITION", 3940 expressions=self._parse_wrapped_csv(self._parse_disjunction), 3941 ) 3942 ) 3943 3944 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 3945 def _parse_value_expression() -> exp.Expr | None: 3946 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3947 return exp.var(self._prev.text.upper()) 3948 return self._parse_expression() 3949 3950 if self._match(TokenType.L_PAREN): 3951 expressions = self._parse_csv(_parse_value_expression) 3952 self._match_r_paren() 3953 return self.expression(exp.Tuple(expressions=expressions)) 3954 3955 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 3956 expression = self._parse_expression() 3957 if expression: 3958 return self.expression(exp.Tuple(expressions=[expression])) 3959 return None 3960 3961 def _parse_projections( 3962 self, 3963 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 3964 return self._parse_expressions(), None 3965 3966 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 3967 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 3968 this: exp.Expr | None = self._parse_simplified_pivot( 3969 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 3970 ) 3971 elif self._match(TokenType.FROM): 3972 from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True) 3973 # Support parentheses for duckdb FROM-first syntax 3974 select = self._parse_select(from_=from_) 3975 if select: 3976 if not select.args.get("from_"): 3977 select.set("from_", from_) 3978 this = select 3979 else: 3980 this = exp.select("*").from_(t.cast(exp.From, from_)) 3981 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3982 else: 3983 this = ( 3984 self._parse_table(consume_pipe=True) 3985 if table 3986 else self._parse_select(nested=True, parse_set_operation=False) 3987 ) 3988 3989 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 3990 # in case a modifier (e.g. join) is following 3991 if table and isinstance(this, exp.Values) and this.alias: 3992 alias = this.args["alias"].pop() 3993 this = exp.Table(this=this, alias=alias) 3994 3995 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3996 3997 return this 3998 3999 def _parse_select( 4000 self, 4001 nested: bool = False, 4002 table: bool = False, 4003 parse_subquery_alias: bool = True, 4004 parse_set_operation: bool = True, 4005 consume_pipe: bool = True, 4006 from_: exp.From | None = None, 4007 ) -> exp.Expr | None: 4008 query = self._parse_select_query( 4009 nested=nested, 4010 table=table, 4011 parse_subquery_alias=parse_subquery_alias, 4012 parse_set_operation=parse_set_operation, 4013 ) 4014 4015 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4016 if not query and from_: 4017 query = exp.select("*").from_(from_) 4018 if isinstance(query, exp.Query): 4019 query = self._parse_pipe_syntax_query(query) 4020 query = query.subquery(copy=False) if query and table else query 4021 4022 return query 4023 4024 def _parse_select_query( 4025 self, 4026 nested: bool = False, 4027 table: bool = False, 4028 parse_subquery_alias: bool = True, 4029 parse_set_operation: bool = True, 4030 ) -> exp.Expr | None: 4031 cte = self._parse_with() 4032 4033 if cte: 4034 this = self._parse_statement() 4035 4036 if not this: 4037 self.raise_error("Failed to parse any statement following CTE") 4038 return cte 4039 4040 while isinstance(this, exp.Subquery) and this.is_wrapper: 4041 this = this.this 4042 4043 assert this is not None 4044 if "with_" in this.arg_types: 4045 if inner_cte := this.args.get("with_"): 4046 cte.set("expressions", cte.expressions + inner_cte.expressions) 4047 if inner_cte.args.get("recursive"): 4048 cte.set("recursive", True) 4049 this.set("with_", cte) 4050 else: 4051 self.raise_error(f"{this.key} does not support CTE") 4052 this = cte 4053 4054 return this 4055 4056 # duckdb supports leading with FROM x 4057 from_ = ( 4058 self._parse_from(joins=True, consume_pipe=True) 4059 if self._match(TokenType.FROM, advance=False) 4060 else None 4061 ) 4062 4063 if self._match(TokenType.SELECT): 4064 comments = self._prev_comments 4065 4066 hint = self._parse_hint() 4067 4068 if self._next and not self._next.token_type == TokenType.DOT: 4069 all_ = self._match(TokenType.ALL) 4070 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4071 else: 4072 all_, matched_distinct = None, False 4073 4074 kind = ( 4075 self._prev.text.upper() 4076 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4077 else None 4078 ) 4079 4080 distinct: exp.Expr | None = ( 4081 self.expression( 4082 exp.Distinct( 4083 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4084 ) 4085 ) 4086 if matched_distinct 4087 else None 4088 ) 4089 4090 operation_modifiers = [] 4091 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4092 operation_modifiers.append(exp.var(self._prev.text.upper())) 4093 4094 limit = self._parse_limit(top=True) 4095 4096 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4097 if limit and not matched_distinct and not all_: 4098 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4099 if matched_distinct: 4100 distinct = self.expression( 4101 exp.Distinct( 4102 on=self._parse_value(values=False) 4103 if self._match(TokenType.ON) 4104 else None 4105 ) 4106 ) 4107 else: 4108 all_ = self._match(TokenType.ALL) 4109 4110 if all_ and distinct: 4111 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4112 4113 projections, exclude = self._parse_projections() 4114 4115 this = self.expression( 4116 exp.Select( 4117 kind=kind, 4118 hint=hint, 4119 distinct=distinct, 4120 expressions=projections, 4121 limit=limit, 4122 exclude=exclude, 4123 operation_modifiers=operation_modifiers or None, 4124 ) 4125 ) 4126 this.comments = comments 4127 4128 into = self._parse_into() 4129 if into: 4130 this.set("into", into) 4131 4132 if not from_: 4133 from_ = self._parse_from() 4134 4135 if from_: 4136 this.set("from_", from_) 4137 4138 this = self._parse_query_modifiers(this) 4139 elif (table or nested) and self._match(TokenType.L_PAREN): 4140 comments = self._prev_comments 4141 this = self._parse_wrapped_select(table=table) 4142 4143 if this: 4144 this.add_comments(comments, prepend=True) 4145 4146 # We return early here so that the UNION isn't attached to the subquery by the 4147 # following call to _parse_set_operations, but instead becomes the parent node 4148 self._match_r_paren() 4149 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4150 elif self._match(TokenType.VALUES, advance=False): 4151 this = self._parse_derived_table_values() 4152 elif from_: 4153 this = exp.select("*").from_(from_.this, copy=False) 4154 this = self._parse_query_modifiers(this) 4155 elif self._match(TokenType.SUMMARIZE): 4156 table = self._match(TokenType.TABLE) 4157 this = self._parse_select() or self._parse_string() or self._parse_table() 4158 return self.expression(exp.Summarize(this=this, table=table)) 4159 elif self._match(TokenType.DESCRIBE): 4160 this = self._parse_describe() 4161 else: 4162 this = None 4163 4164 return self._parse_set_operations(this) if parse_set_operation else this 4165 4166 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4167 self._match_text_seq("SEARCH") 4168 4169 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4170 4171 if not kind: 4172 return None 4173 4174 self._match_text_seq("FIRST", "BY") 4175 4176 return self.expression( 4177 exp.RecursiveWithSearch( 4178 kind=kind, 4179 this=self._parse_id_var(), 4180 expression=self._match_text_seq("SET") and self._parse_id_var(), 4181 using=self._match_text_seq("USING") and self._parse_id_var(), 4182 ) 4183 ) 4184 4185 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4186 if not skip_with_token and not self._match(TokenType.WITH): 4187 return None 4188 4189 comments = self._prev_comments 4190 recursive = self._match(TokenType.RECURSIVE) 4191 4192 last_comments = None 4193 expressions = [] 4194 udfs = [] 4195 while True: 4196 cte = self._parse_cte() 4197 if cte: 4198 if isinstance(cte, exp.FunctionSpecification): 4199 udfs.append(cte) 4200 else: 4201 expressions.append(cte) 4202 4203 if last_comments: 4204 cte.add_comments(last_comments) 4205 4206 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4207 break 4208 else: 4209 self._match(TokenType.WITH) 4210 recursive = self._match(TokenType.RECURSIVE) or recursive 4211 4212 last_comments = self._prev_comments 4213 4214 return self.expression( 4215 exp.With( 4216 expressions=expressions, 4217 recursive=recursive or None, 4218 search=self._parse_recursive_with_search(), 4219 udfs=udfs or None, 4220 ), 4221 comments=comments, 4222 ) 4223 4224 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4225 index = self._index 4226 4227 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4228 if not alias or not alias.this: 4229 self.raise_error("Expected CTE to have alias") 4230 4231 key_expressions = ( 4232 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4233 ) 4234 4235 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4236 self._retreat(index) 4237 return None 4238 4239 comments = self._prev_comments 4240 4241 if self._match_text_seq("NOT", "MATERIALIZED"): 4242 materialized = False 4243 elif self._match_text_seq("MATERIALIZED"): 4244 materialized = True 4245 else: 4246 materialized = None 4247 4248 cte = self.expression( 4249 exp.CTE( 4250 this=self._parse_wrapped(self._parse_statement), 4251 alias=alias, 4252 materialized=materialized, 4253 key_expressions=key_expressions, 4254 ), 4255 comments=comments, 4256 ) 4257 4258 values = cte.this 4259 if isinstance(values, exp.Values): 4260 cte.set("this", self._values_to_select(values)) 4261 4262 return cte 4263 4264 def _values_to_select(self, values: exp.Values) -> exp.Select: 4265 if values.alias: 4266 return exp.select("*").from_(values) 4267 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4268 4269 def _parse_table_alias( 4270 self, alias_tokens: t.Collection[TokenType] | None = None 4271 ) -> exp.TableAlias | None: 4272 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4273 # so this section tries to parse the clause version and if it fails, it treats the token 4274 # as an identifier (alias) 4275 if self._can_parse_limit_or_offset(): 4276 return None 4277 4278 # START is never treated as an implicit alias when followed by WITH, since that 4279 # would swallow the beginning of a START WITH ... CONNECT BY clause 4280 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4281 return None 4282 4283 any_token = self._match(TokenType.ALIAS) 4284 alias = ( 4285 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4286 or self._parse_string_as_identifier() 4287 ) 4288 4289 index = self._index 4290 if self._match(TokenType.L_PAREN): 4291 columns = self._parse_csv(self._parse_function_parameter) 4292 self._match_r_paren() if columns else self._retreat(index) 4293 else: 4294 columns = None 4295 4296 if not alias and not columns: 4297 return None 4298 4299 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4300 4301 # We bubble up comments from the Identifier to the TableAlias 4302 if isinstance(alias, exp.Identifier): 4303 table_alias.add_comments(alias.pop_comments()) 4304 4305 return table_alias 4306 4307 def _parse_subquery( 4308 self, this: exp.Expr | None, parse_alias: bool = True 4309 ) -> exp.Subquery | None: 4310 if not this: 4311 return None 4312 4313 return self.expression( 4314 exp.Subquery( 4315 this=this, 4316 pivots=self._parse_pivots(), 4317 alias=self._parse_table_alias() if parse_alias else None, 4318 sample=self._parse_table_sample(), 4319 ) 4320 ) 4321 4322 def _implicit_unnests_to_explicit(self, this: E) -> E: 4323 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4324 4325 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4326 for i, join in enumerate(this.args.get("joins") or []): 4327 table = join.this 4328 normalized_table = table.copy() 4329 normalized_table.meta["maybe_column"] = True 4330 normalized_table = _norm(normalized_table, dialect=self.dialect) 4331 4332 if isinstance(table, exp.Table) and not join.args.get("on"): 4333 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4334 table_as_column = table.to_column() 4335 unnest = exp.Unnest(expressions=[table_as_column]) 4336 4337 # Table.to_column creates a parent Alias node that we want to convert to 4338 # a TableAlias and attach to the Unnest, so it matches the parser's output 4339 if isinstance(table.args.get("alias"), exp.TableAlias): 4340 table_as_column.replace(table_as_column.this) 4341 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4342 4343 table.replace(unnest) 4344 4345 refs.add(normalized_table.alias_or_name) 4346 4347 return this 4348 4349 @t.overload 4350 def _parse_query_modifiers(self, this: E) -> E: ... 4351 4352 @t.overload 4353 def _parse_query_modifiers(self, this: None) -> None: ... 4354 4355 def _parse_query_modifiers(self, this): 4356 if isinstance(this, self.MODIFIABLES): 4357 for join in self._parse_joins(): 4358 this.append("joins", join) 4359 for lateral in iter(self._parse_lateral, None): 4360 this.append("laterals", lateral) 4361 4362 while True: 4363 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4364 modifier_token = self._curr 4365 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4366 key, expression = parser(self) 4367 4368 if expression: 4369 if this.args.get(key): 4370 self.raise_error( 4371 f"Found multiple '{modifier_token.text.upper()}' clauses", 4372 token=modifier_token, 4373 ) 4374 4375 this.set(key, expression) 4376 if key == "limit": 4377 offset = expression.args.get("offset") 4378 expression.set("offset", None) 4379 4380 if offset: 4381 offset = exp.Offset(expression=offset) 4382 this.set("offset", offset) 4383 4384 limit_by_expressions = expression.expressions 4385 expression.set("expressions", None) 4386 offset.set("expressions", limit_by_expressions) 4387 continue 4388 4389 if self._curr.text.upper() == "START": 4390 modifier_token = self._curr 4391 connect = self._parse_connect() 4392 if connect: 4393 if this.args.get("connect"): 4394 self.raise_error( 4395 "Found multiple 'START WITH' clauses", token=modifier_token 4396 ) 4397 4398 this.set("connect", connect) 4399 continue 4400 break 4401 4402 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4403 this = self._implicit_unnests_to_explicit(this) 4404 4405 return this 4406 4407 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4408 start = self._curr 4409 while self._curr: 4410 self._advance() 4411 4412 end = self._tokens[self._index - 1] 4413 return exp.Hint(expressions=[self._find_sql(start, end)]) 4414 4415 def _parse_hint_function_call(self) -> exp.Expr | None: 4416 return self._parse_function_call() 4417 4418 def _parse_hint_body(self) -> exp.Hint | None: 4419 start_index = self._index 4420 should_fallback_to_string = False 4421 4422 hints = [] 4423 try: 4424 for hint in iter( 4425 lambda: self._parse_csv( 4426 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4427 ), 4428 [], 4429 ): 4430 hints.extend(hint) 4431 except ParseError: 4432 should_fallback_to_string = True 4433 4434 if should_fallback_to_string or self._curr: 4435 self._retreat(start_index) 4436 return self._parse_hint_fallback_to_string() 4437 4438 return self.expression(exp.Hint(expressions=hints)) 4439 4440 def _parse_hint(self) -> exp.Hint | None: 4441 if self._match(TokenType.HINT) and self._prev_comments: 4442 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4443 4444 return None 4445 4446 def _parse_into(self) -> exp.Into | None: 4447 if not self._match(TokenType.INTO): 4448 return None 4449 4450 temp = self._match(TokenType.TEMPORARY) 4451 unlogged = self._match_text_seq("UNLOGGED") 4452 self._match(TokenType.TABLE) 4453 4454 return self.expression( 4455 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4456 ) 4457 4458 def _parse_from( 4459 self, 4460 joins: bool = False, 4461 skip_from_token: bool = False, 4462 consume_pipe: bool = False, 4463 ) -> exp.From | None: 4464 if not skip_from_token and not self._match(TokenType.FROM): 4465 return None 4466 4467 comments = self._prev_comments 4468 return self.expression( 4469 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4470 comments=comments, 4471 ) 4472 4473 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4474 return self.expression( 4475 exp.MatchRecognizeMeasure( 4476 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4477 this=self._parse_expression(), 4478 ) 4479 ) 4480 4481 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4482 if not self._match(TokenType.MATCH_RECOGNIZE): 4483 return None 4484 4485 self._match_l_paren() 4486 4487 partition = self._parse_partition_by() 4488 order = self._parse_order() 4489 4490 measures = ( 4491 self._parse_csv(self._parse_match_recognize_measure) 4492 if self._match_text_seq("MEASURES") 4493 else None 4494 ) 4495 4496 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4497 rows = exp.var("ONE ROW PER MATCH") 4498 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4499 text = "ALL ROWS PER MATCH" 4500 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4501 text += " SHOW EMPTY MATCHES" 4502 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4503 text += " OMIT EMPTY MATCHES" 4504 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4505 text += " WITH UNMATCHED ROWS" 4506 rows = exp.var(text) 4507 else: 4508 rows = None 4509 4510 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4511 text = "AFTER MATCH SKIP" 4512 if self._match_text_seq("PAST", "LAST", "ROW"): 4513 text += " PAST LAST ROW" 4514 elif self._match_text_seq("TO", "NEXT", "ROW"): 4515 text += " TO NEXT ROW" 4516 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4517 direction = self._prev.text.upper() 4518 pattern_var = self._advance_any() 4519 if not pattern_var: 4520 self.raise_error( 4521 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4522 ) 4523 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4524 after = exp.var(text) 4525 else: 4526 after = None 4527 4528 if self._match_text_seq("PATTERN"): 4529 self._match_l_paren() 4530 4531 if not self._curr: 4532 self.raise_error("Expecting )", self._curr) 4533 4534 paren = 1 4535 start = self._curr 4536 4537 while self._curr and paren > 0: 4538 if self._curr.token_type == TokenType.L_PAREN: 4539 paren += 1 4540 if self._curr.token_type == TokenType.R_PAREN: 4541 paren -= 1 4542 4543 end = self._prev 4544 self._advance() 4545 4546 if paren > 0: 4547 self.raise_error("Expecting )", self._curr) 4548 4549 pattern = exp.var(self._find_sql(start, end)) 4550 else: 4551 pattern = None 4552 4553 define = ( 4554 self._parse_csv(self._parse_name_as_expression) 4555 if self._match_text_seq("DEFINE") 4556 else None 4557 ) 4558 4559 self._match_r_paren() 4560 4561 return self.expression( 4562 exp.MatchRecognize( 4563 partition_by=partition, 4564 order=order, 4565 measures=measures, 4566 rows=rows, 4567 after=after, 4568 pattern=pattern, 4569 define=define, 4570 alias=self._parse_table_alias(), 4571 ) 4572 ) 4573 4574 def _parse_lateral(self) -> exp.Lateral | None: 4575 cross_apply: bool | None = None 4576 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4577 cross_apply = True 4578 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4579 cross_apply = False 4580 4581 if cross_apply is not None: 4582 this = self._parse_select(table=True) 4583 view = None 4584 outer = None 4585 elif self._match(TokenType.LATERAL): 4586 this = self._parse_select(table=True) 4587 view = self._match(TokenType.VIEW) 4588 outer = self._match(TokenType.OUTER) 4589 else: 4590 return None 4591 4592 if not this: 4593 this = ( 4594 self._parse_unnest() 4595 or self._parse_function() 4596 or self._parse_id_var(any_token=False) 4597 ) 4598 4599 while self._match(TokenType.DOT): 4600 this = exp.Dot( 4601 this=this, 4602 expression=self._parse_function() or self._parse_id_var(any_token=False), 4603 ) 4604 4605 ordinality: bool | None = None 4606 4607 if view: 4608 table = self._parse_id_var(any_token=False) 4609 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4610 table_alias: exp.TableAlias | None = self.expression( 4611 exp.TableAlias(this=table, columns=columns) 4612 ) 4613 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4614 # We move the alias from the lateral's child node to the lateral itself 4615 table_alias = this.args["alias"].pop() 4616 else: 4617 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4618 table_alias = self._parse_table_alias() 4619 4620 return self.expression( 4621 exp.Lateral( 4622 this=this, 4623 view=view, 4624 outer=outer, 4625 alias=table_alias, 4626 cross_apply=cross_apply, 4627 ordinality=ordinality, 4628 ) 4629 ) 4630 4631 def _parse_stream(self) -> exp.Stream | None: 4632 index = self._index 4633 if self._match(TokenType.STREAM): 4634 if this := self._try_parse(self._parse_table): 4635 return self.expression(exp.Stream(this=this)) 4636 self._retreat(index) 4637 return None 4638 4639 def _parse_join_parts( 4640 self, 4641 ) -> tuple[Token | None, Token | None, Token | None]: 4642 return ( 4643 self._prev if self._match_set(self.JOIN_METHODS) else None, 4644 self._prev if self._match_set(self.JOIN_SIDES) else None, 4645 self._prev if self._match_set(self.JOIN_KINDS) else None, 4646 ) 4647 4648 def _parse_using_identifiers(self) -> list[exp.Expr]: 4649 def _parse_column_as_identifier() -> exp.Expr | None: 4650 this = self._parse_column() 4651 if isinstance(this, exp.Column): 4652 return this.this 4653 return this 4654 4655 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4656 4657 def _parse_join( 4658 self, 4659 skip_join_token: bool = False, 4660 parse_bracket: bool = False, 4661 alias_tokens: t.Collection[TokenType] | None = None, 4662 ) -> exp.Join | None: 4663 if self._match(TokenType.COMMA): 4664 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4665 cross_join = self.expression(exp.Join(this=table)) if table else None 4666 4667 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4668 cross_join.set("kind", "CROSS") 4669 4670 return cross_join 4671 4672 index = self._index 4673 method, side, kind = self._parse_join_parts() 4674 directed = self._match_text_seq("DIRECTED") 4675 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4676 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4677 join_comments = self._prev_comments 4678 4679 if not skip_join_token and not join: 4680 self._retreat(index) 4681 kind = None 4682 method = None 4683 side = None 4684 4685 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4686 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4687 4688 if not skip_join_token and not join and not outer_apply and not cross_apply: 4689 return None 4690 4691 kwargs: dict[str, t.Any] = { 4692 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4693 } 4694 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4695 kwargs["expressions"] = self._parse_csv( 4696 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4697 ) 4698 4699 if method: 4700 kwargs["method"] = method.text.upper() 4701 if side: 4702 kwargs["side"] = side.text.upper() 4703 if kind: 4704 kwargs["kind"] = kind.text.upper() 4705 if hint: 4706 kwargs["hint"] = hint 4707 4708 if self._match(TokenType.MATCH_CONDITION): 4709 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4710 4711 if self._match(TokenType.ON): 4712 kwargs["on"] = self._parse_disjunction() 4713 elif self._match(TokenType.USING): 4714 kwargs["using"] = self._parse_using_identifiers() 4715 elif ( 4716 not method 4717 and not (outer_apply or cross_apply) 4718 and not isinstance(kwargs["this"], exp.Unnest) 4719 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4720 ): 4721 index = self._index 4722 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4723 4724 if joins and self._match(TokenType.ON): 4725 kwargs["on"] = self._parse_disjunction() 4726 elif joins and self._match(TokenType.USING): 4727 kwargs["using"] = self._parse_using_identifiers() 4728 else: 4729 joins = None 4730 self._retreat(index) 4731 4732 kwargs["this"].set("joins", joins if joins else None) 4733 4734 kwargs["pivots"] = self._parse_pivots() 4735 4736 comments = [c for token in (method, side, kind) if token for c in token.comments] 4737 comments = (join_comments or []) + comments 4738 4739 if ( 4740 self.ADD_JOIN_ON_TRUE 4741 and not kwargs.get("on") 4742 and not kwargs.get("using") 4743 and not kwargs.get("method") 4744 and kwargs.get("kind") in (None, "INNER", "OUTER") 4745 ): 4746 kwargs["on"] = exp.true() 4747 4748 if directed: 4749 kwargs["directed"] = directed 4750 4751 return self.expression(exp.Join(**kwargs), comments=comments) 4752 4753 def _parse_opclass(self) -> exp.Expr | None: 4754 this = self._parse_disjunction() 4755 4756 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4757 return this 4758 4759 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4760 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4761 4762 return this 4763 4764 def _parse_index_params(self) -> exp.IndexParameters: 4765 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4766 4767 if self._match(TokenType.L_PAREN, advance=False): 4768 columns = self._parse_wrapped_csv(self._parse_with_operator) 4769 else: 4770 columns = None 4771 4772 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4773 partition_by = self._parse_partition_by() 4774 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4775 tablespace = ( 4776 self._parse_var(any_token=True) 4777 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4778 else None 4779 ) 4780 where = self._parse_where() 4781 4782 on = self._parse_field() if self._match(TokenType.ON) else None 4783 4784 return self.expression( 4785 exp.IndexParameters( 4786 using=using, 4787 columns=columns, 4788 include=include, 4789 partition_by=partition_by, 4790 where=where, 4791 with_storage=with_storage, 4792 tablespace=tablespace, 4793 on=on, 4794 ) 4795 ) 4796 4797 def _parse_index( 4798 self, index: exp.Expr | None = None, anonymous: bool = False 4799 ) -> exp.Index | None: 4800 if index or anonymous: 4801 unique = None 4802 primary = None 4803 amp = None 4804 4805 self._match(TokenType.ON) 4806 self._match(TokenType.TABLE) # hive 4807 table = self._parse_table_parts(schema=True) 4808 else: 4809 unique = self._match(TokenType.UNIQUE) 4810 primary = self._match_text_seq("PRIMARY") 4811 amp = self._match_text_seq("AMP") 4812 4813 if not self._match(TokenType.INDEX): 4814 return None 4815 4816 index = self._parse_id_var() 4817 table = None 4818 4819 params = self._parse_index_params() 4820 4821 return self.expression( 4822 exp.Index( 4823 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4824 ) 4825 ) 4826 4827 def _parse_table_hints(self) -> list[exp.Expr] | None: 4828 hints: list[exp.Expr] = [] 4829 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4830 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4831 hints.append( 4832 self.expression( 4833 exp.WithTableHint( 4834 expressions=self._parse_csv( 4835 lambda: self._parse_function() or self._parse_var(any_token=True) 4836 ) 4837 ) 4838 ) 4839 ) 4840 self._match_r_paren() 4841 else: 4842 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4843 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4844 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4845 4846 self._match_set((TokenType.INDEX, TokenType.KEY)) 4847 if self._match(TokenType.FOR): 4848 hint.set("target", self._advance_any() and self._prev.text.upper()) 4849 4850 hint.set("expressions", self._parse_wrapped_id_vars()) 4851 hints.append(hint) 4852 4853 return hints or None 4854 4855 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 4856 return ( 4857 (not schema and self._parse_function(optional_parens=False)) 4858 or self._parse_id_var(any_token=False) 4859 or self._parse_string_as_identifier() 4860 or self._parse_placeholder() 4861 ) 4862 4863 def _parse_table_parts_fast(self) -> exp.Table | None: 4864 index = self._index 4865 parts: list[exp.Identifier] | None = None 4866 all_comments: list[str] | None = None 4867 4868 while self._match_set(self.IDENTIFIER_TOKENS): 4869 token = self._prev 4870 comments = self._prev_comments 4871 4872 has_dot = self._match(TokenType.DOT) 4873 curr_tt = self._curr.token_type 4874 4875 if not has_dot: 4876 if curr_tt in self.TABLE_POSTFIX_TOKENS: 4877 self._retreat(index) 4878 return None 4879 elif curr_tt not in self.IDENTIFIER_TOKENS: 4880 self._retreat(index) 4881 return None 4882 4883 if parts is None: 4884 parts = [] 4885 4886 if comments: 4887 if all_comments is None: 4888 all_comments = [] 4889 all_comments.extend(comments) 4890 self._prev_comments = [] 4891 4892 parts.append( 4893 self.expression( 4894 exp.Identifier( 4895 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 4896 ), 4897 token, 4898 ) 4899 ) 4900 4901 if not has_dot: 4902 break 4903 4904 if parts is None: 4905 return None 4906 4907 n = len(parts) 4908 4909 if n == 1: 4910 table: exp.Table = exp.Table(this=parts[0]) 4911 elif n == 2: 4912 table = exp.Table(this=parts[1], db=parts[0]) 4913 elif n >= 3: 4914 this: exp.Identifier | exp.Dot = parts[2] 4915 for i in range(3, n): 4916 this = exp.Dot(this=this, expression=parts[i]) 4917 4918 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 4919 4920 if table is None: 4921 self._retreat(index) 4922 elif all_comments: 4923 table.add_comments(all_comments) 4924 return table 4925 4926 def _parse_table_parts( 4927 self, 4928 schema: bool = False, 4929 is_db_reference: bool = False, 4930 wildcard: bool = False, 4931 fast: bool = False, 4932 ) -> exp.Table | exp.Dot | None: 4933 if fast: 4934 return self._parse_table_parts_fast() 4935 4936 catalog: exp.Expr | str | None = None 4937 db: exp.Expr | str | None = None 4938 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 4939 4940 while self._match(TokenType.DOT): 4941 if catalog: 4942 # This allows nesting the table in arbitrarily many dot expressions if needed 4943 table = self.expression( 4944 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 4945 ) 4946 else: 4947 catalog = db 4948 db = table 4949 # "" used for tsql FROM a..b case 4950 table = self._parse_table_part(schema=schema) or "" 4951 4952 if ( 4953 wildcard 4954 and self._is_connected() 4955 and (isinstance(table, exp.Identifier) or not table) 4956 and self._match(TokenType.STAR) 4957 ): 4958 if isinstance(table, exp.Identifier): 4959 table.args["this"] += "*" 4960 else: 4961 table = exp.Identifier(this="*") 4962 4963 if is_db_reference: 4964 catalog = db 4965 db = table 4966 table = None 4967 4968 if not table and not is_db_reference: 4969 self.raise_error(f"Expected table name but got {self._curr}") 4970 if not db and is_db_reference: 4971 self.raise_error(f"Expected database name but got {self._curr}") 4972 4973 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 4974 4975 # Bubble up comments from identifier parts to the Table 4976 comments = [] 4977 for part in table.parts: 4978 if part_comments := part.pop_comments(): 4979 comments.extend(part_comments) 4980 if comments: 4981 table.add_comments(comments) 4982 4983 changes = self._parse_changes() 4984 if changes: 4985 table.set("changes", changes) 4986 4987 at_before = self._parse_historical_data() 4988 if at_before: 4989 table.set("when", at_before) 4990 4991 pivots = self._parse_pivots() 4992 if pivots: 4993 table.set("pivots", pivots) 4994 4995 return table 4996 4997 def _parse_table( 4998 self, 4999 schema: bool = False, 5000 joins: bool = False, 5001 alias_tokens: t.Collection[TokenType] | None = None, 5002 parse_bracket: bool = False, 5003 is_db_reference: bool = False, 5004 parse_partition: bool = False, 5005 consume_pipe: bool = False, 5006 ) -> exp.Expr | None: 5007 if not schema and not is_db_reference and not consume_pipe and not joins: 5008 index = self._index 5009 table = self._parse_table_parts(fast=True) 5010 5011 if table is not None: 5012 curr_tt = self._curr.token_type 5013 next_tt = self._next.token_type 5014 5015 fast_terminators = self.TABLE_TERMINATORS 5016 5017 # only return the table if we're sure there are no other operators 5018 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5019 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5020 return table 5021 5022 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5023 5024 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5025 if alias := self._parse_table_alias( 5026 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5027 ): 5028 table.set("alias", alias) 5029 5030 if self._curr.token_type in fast_terminators: 5031 return table 5032 5033 self._retreat(index) 5034 5035 if stream := self._parse_stream(): 5036 return stream 5037 5038 if lateral := self._parse_lateral(): 5039 return lateral 5040 5041 if unnest := self._parse_unnest(): 5042 return unnest 5043 5044 if values := self._parse_derived_table_values(): 5045 return values 5046 5047 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5048 if not subquery.args.get("pivots"): 5049 subquery.set("pivots", self._parse_pivots()) 5050 if joins: 5051 for join in self._parse_joins(): 5052 subquery.append("joins", join) 5053 return subquery 5054 5055 bracket = parse_bracket and self._parse_bracket(None) 5056 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5057 5058 rows_from_tables = ( 5059 self._parse_wrapped_csv(self._parse_table) 5060 if self._match_text_seq("ROWS", "FROM") 5061 else None 5062 ) 5063 rows_from = ( 5064 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5065 ) 5066 5067 only = self._match(TokenType.ONLY) 5068 5069 this = t.cast( 5070 exp.Expr, 5071 bracket 5072 or rows_from 5073 or self._parse_bracket( 5074 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5075 ), 5076 ) 5077 5078 if only: 5079 this.set("only", only) 5080 5081 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5082 self._match(TokenType.STAR) 5083 5084 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5085 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5086 this.set("partition", self._parse_partition()) 5087 5088 if schema: 5089 return self._parse_schema(this=this) 5090 5091 if self.dialect.ALIAS_POST_VERSION: 5092 this.set("version", self._parse_version()) 5093 5094 if self.dialect.ALIAS_POST_TABLESAMPLE: 5095 this.set("sample", self._parse_table_sample()) 5096 5097 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5098 if alias: 5099 this.set("alias", alias) 5100 5101 # DuckDB requires the time-travel clause to come after the alias, e.g. 5102 # SELECT * FROM t AS a AT (VERSION => 1) 5103 if isinstance(this, exp.Table) and not this.args.get("when"): 5104 this.set("when", self._parse_historical_data()) 5105 5106 if self._match(TokenType.INDEXED_BY): 5107 this.set("indexed", self._parse_table_parts()) 5108 elif self._match_text_seq("NOT", "INDEXED"): 5109 this.set("indexed", False) 5110 5111 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5112 return self.expression( 5113 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5114 ) 5115 5116 this.set("hints", self._parse_table_hints()) 5117 5118 if not this.args.get("pivots"): 5119 this.set("pivots", self._parse_pivots()) 5120 5121 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5122 this.set("sample", self._parse_table_sample()) 5123 5124 if not self.dialect.ALIAS_POST_VERSION: 5125 this.set("version", self._parse_version()) 5126 5127 if joins: 5128 for join in self._parse_joins(alias_tokens=alias_tokens): 5129 this.append("joins", join) 5130 5131 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5132 this.set("ordinality", True) 5133 this.set("alias", self._parse_table_alias()) 5134 5135 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5136 # hoist the table args onto the latter and return it instead 5137 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5138 table_from_rows = this.this 5139 for arg in exp.TableFromRows.arg_types: 5140 if arg != "this": 5141 table_from_rows.set(arg, this.args.get(arg)) 5142 5143 this = table_from_rows 5144 5145 return this 5146 5147 def _parse_version(self) -> exp.Version | None: 5148 for phrase, this in self.VERSION_PHRASES.items(): 5149 if self._match_text_seq(*phrase): 5150 break 5151 else: 5152 return None 5153 5154 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5155 kind = self._prev.text.upper() 5156 start = self._parse_bitwise() 5157 self._match_texts(("TO", "AND")) 5158 end = self._parse_bitwise() 5159 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5160 elif self._match_text_seq("CONTAINED", "IN"): 5161 kind = "CONTAINED IN" 5162 expression = self.expression( 5163 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5164 ) 5165 elif self._match(TokenType.ALL): 5166 kind = "ALL" 5167 expression = None 5168 else: 5169 self._match_text_seq("AS", "OF") 5170 kind = "AS OF" 5171 expression = self._parse_type() 5172 5173 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5174 5175 def _parse_historical_data(self) -> exp.HistoricalData | None: 5176 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5177 index = self._index 5178 historical_data = None 5179 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5180 this = self._prev.text.upper() 5181 kind = ( 5182 self._match(TokenType.L_PAREN) 5183 and self._match_texts(self.HISTORICAL_DATA_KIND) 5184 and self._prev.text.upper() 5185 ) 5186 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5187 5188 if expression: 5189 self._match_r_paren() 5190 historical_data = self.expression( 5191 exp.HistoricalData(this=this, kind=kind, expression=expression) 5192 ) 5193 else: 5194 self._retreat(index) 5195 5196 return historical_data 5197 5198 def _parse_changes(self) -> exp.Changes | None: 5199 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5200 return None 5201 5202 information = self._parse_var(any_token=True) 5203 self._match_r_paren() 5204 5205 return self.expression( 5206 exp.Changes( 5207 information=information, 5208 at_before=self._parse_historical_data(), 5209 end=self._parse_historical_data(), 5210 ) 5211 ) 5212 5213 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5214 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5215 return None 5216 5217 self._advance() 5218 5219 expressions = self._parse_wrapped_csv(self._parse_equality) 5220 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5221 5222 alias = self._parse_table_alias() if with_alias else None 5223 5224 if alias: 5225 if self.dialect.UNNEST_COLUMN_ONLY: 5226 if alias.args.get("columns"): 5227 self.raise_error("Unexpected extra column alias in unnest.") 5228 5229 alias.set("columns", [alias.this]) 5230 alias.set("this", None) 5231 5232 columns = alias.args.get("columns") or [] 5233 if offset and len(expressions) < len(columns): 5234 offset = columns.pop() 5235 5236 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5237 self._match(TokenType.ALIAS) 5238 offset = self._parse_id_var( 5239 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5240 ) or exp.to_identifier("offset") 5241 5242 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5243 5244 def _parse_derived_table_values(self) -> exp.Values | None: 5245 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5246 if not is_derived and not ( 5247 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5248 self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES") 5249 ): 5250 return None 5251 5252 expressions = self._parse_csv(self._parse_value) 5253 alias = self._parse_table_alias() 5254 5255 if is_derived: 5256 self._match_r_paren() 5257 5258 return self.expression( 5259 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5260 ) 5261 5262 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5263 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5264 as_modifier and self._match_text_seq("USING", "SAMPLE") 5265 ): 5266 return None 5267 5268 bucket_numerator = None 5269 bucket_denominator = None 5270 bucket_field = None 5271 percent = None 5272 size = None 5273 seed = None 5274 5275 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5276 matched_l_paren = self._match(TokenType.L_PAREN) 5277 5278 if self.TABLESAMPLE_CSV: 5279 num = None 5280 expressions = self._parse_csv(self._parse_primary) 5281 else: 5282 expressions = None 5283 num = ( 5284 self._parse_factor(parse_mod=False) 5285 if self._match(TokenType.NUMBER, advance=False) 5286 else self._parse_primary() or self._parse_placeholder() 5287 ) 5288 5289 if self._match_text_seq("BUCKET"): 5290 bucket_numerator = self._parse_number() 5291 self._match_text_seq("OUT", "OF") 5292 bucket_denominator = bucket_denominator = self._parse_number() 5293 self._match(TokenType.ON) 5294 bucket_field = self._parse_field() 5295 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5296 percent = num 5297 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5298 size = num 5299 else: 5300 percent = num 5301 5302 if matched_l_paren: 5303 self._match_r_paren() 5304 5305 if self._match(TokenType.L_PAREN): 5306 method = self._parse_var(upper=True) 5307 seed = self._match(TokenType.COMMA) and self._parse_number() 5308 self._match_r_paren() 5309 elif self._match_texts(("SEED", "REPEATABLE")): 5310 seed = self._parse_wrapped(self._parse_number) 5311 5312 if not method and self.DEFAULT_SAMPLING_METHOD: 5313 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5314 5315 return self.expression( 5316 exp.TableSample( 5317 expressions=expressions, 5318 method=method, 5319 bucket_numerator=bucket_numerator, 5320 bucket_denominator=bucket_denominator, 5321 bucket_field=bucket_field, 5322 percent=percent, 5323 size=size, 5324 seed=seed, 5325 ) 5326 ) 5327 5328 def _parse_pivots(self) -> list[exp.Pivot] | None: 5329 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5330 return None 5331 return list(iter(self._parse_pivot, None)) or None 5332 5333 def _parse_joins( 5334 self, alias_tokens: t.Collection[TokenType] | None = None 5335 ) -> t.Iterator[exp.Join]: 5336 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5337 5338 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5339 if not self._match(TokenType.INTO): 5340 return None 5341 5342 return self.expression( 5343 exp.UnpivotColumns( 5344 this=self._match_text_seq("NAME") and self._parse_column(), 5345 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5346 ) 5347 ) 5348 5349 # https://duckdb.org/docs/sql/statements/pivot 5350 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5351 def _parse_on() -> exp.Expr | None: 5352 this = self._parse_bitwise() 5353 5354 if self._match(TokenType.IN): 5355 # PIVOT ... ON col IN (row_val1, row_val2) 5356 return self._parse_in(this) 5357 if self._match(TokenType.ALIAS, advance=False): 5358 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5359 return self._parse_alias(this) 5360 5361 return this 5362 5363 this = self._parse_table() 5364 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5365 into = self._parse_unpivot_columns() 5366 using = self._match(TokenType.USING) and self._parse_csv( 5367 lambda: self._parse_alias(self._parse_column()) 5368 ) 5369 group = self._parse_group() 5370 5371 return self.expression( 5372 exp.Pivot( 5373 this=this, 5374 expressions=expressions, 5375 using=using, 5376 group=group, 5377 unpivot=is_unpivot, 5378 into=into, 5379 ) 5380 ) 5381 5382 def _parse_pivot_in(self) -> exp.In: 5383 def _parse_aliased_expression() -> exp.Expr | None: 5384 this = self._parse_select_or_expression() 5385 5386 self._match(TokenType.ALIAS) 5387 alias = self._parse_bitwise() 5388 if alias: 5389 if isinstance(alias, exp.Column) and not alias.db: 5390 alias = alias.this 5391 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5392 5393 return this 5394 5395 value = self._parse_column() 5396 5397 if not self._match(TokenType.IN): 5398 self.raise_error("Expecting IN") 5399 5400 if self._match(TokenType.L_PAREN): 5401 if self._match(TokenType.ANY): 5402 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5403 else: 5404 exprs = self._parse_csv(_parse_aliased_expression) 5405 self._match_r_paren() 5406 return self.expression(exp.In(this=value, expressions=exprs)) 5407 5408 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5409 5410 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5411 func = self._parse_function() 5412 if not func: 5413 if self._prev.token_type == TokenType.COMMA: 5414 return None 5415 self.raise_error("Expecting an aggregation function in PIVOT") 5416 5417 return self._parse_alias(func) 5418 5419 def _parse_pivot(self) -> exp.Pivot | None: 5420 index = self._index 5421 include_nulls = None 5422 5423 if self._match(TokenType.PIVOT): 5424 unpivot = False 5425 elif self._match(TokenType.UNPIVOT): 5426 unpivot = True 5427 5428 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5429 if self._match_text_seq("INCLUDE", "NULLS"): 5430 include_nulls = True 5431 elif self._match_text_seq("EXCLUDE", "NULLS"): 5432 include_nulls = False 5433 else: 5434 return None 5435 5436 expressions = [] 5437 5438 if not self._match(TokenType.L_PAREN): 5439 self._retreat(index) 5440 return None 5441 5442 if unpivot: 5443 expressions = self._parse_csv(self._parse_column) 5444 else: 5445 expressions = self._parse_csv(self._parse_pivot_aggregation) 5446 5447 if not expressions: 5448 self.raise_error("Failed to parse PIVOT's aggregation list") 5449 5450 if not self._match(TokenType.FOR): 5451 self.raise_error("Expecting FOR") 5452 5453 fields = [] 5454 while True: 5455 field = self._try_parse(self._parse_pivot_in) 5456 if not field: 5457 break 5458 fields.append(field) 5459 5460 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5461 self._parse_bitwise 5462 ) 5463 5464 group = self._parse_group() 5465 5466 self._match_r_paren() 5467 5468 pivot = self.expression( 5469 exp.Pivot( 5470 expressions=expressions, 5471 fields=fields, 5472 unpivot=unpivot, 5473 include_nulls=include_nulls, 5474 default_on_null=default_on_null, 5475 group=group, 5476 ) 5477 ) 5478 5479 if unpivot: 5480 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5481 for pivot_field in pivot.fields: 5482 if isinstance(pivot_field, exp.In): 5483 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5484 5485 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5486 5487 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5488 pivot.set("alias", self._parse_table_alias()) 5489 5490 if not unpivot: 5491 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5492 5493 columns: list[exp.Expr] = [] 5494 all_fields = [] 5495 for pivot_field in pivot.fields: 5496 pivot_field_expressions = pivot_field.expressions 5497 5498 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5499 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5500 continue 5501 5502 all_fields.append( 5503 [ 5504 # An explicit `<field> AS <alias>` names the output column directly, 5505 # so it wins over the dialect's string-identifying convention 5506 fld.sql() 5507 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5508 else fld.alias_or_name 5509 for fld in pivot_field_expressions 5510 ] 5511 ) 5512 5513 if all_fields: 5514 if names: 5515 all_fields.append(names) 5516 5517 # Generate all possible combinations of the pivot columns 5518 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5519 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5520 for fld_parts_tuple in itertools.product(*all_fields): 5521 fld_parts = list(fld_parts_tuple) 5522 5523 if names and self.PREFIXED_PIVOT_COLUMNS: 5524 # Move the "name" to the front of the list 5525 fld_parts.insert(0, fld_parts.pop(-1)) 5526 5527 columns.append(exp.to_identifier("_".join(fld_parts))) 5528 5529 pivot.set("columns", columns) 5530 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5531 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5532 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5533 5534 return pivot 5535 5536 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5537 return [agg.alias for agg in aggregations if agg.alias] 5538 5539 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5540 if not skip_where_token and not self._match(TokenType.PREWHERE): 5541 return None 5542 5543 comments = self._prev_comments 5544 return self.expression( 5545 exp.PreWhere(this=self._parse_disjunction()), 5546 comments=comments, 5547 ) 5548 5549 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5550 if not skip_where_token and not self._match(TokenType.WHERE): 5551 return None 5552 5553 comments = self._prev_comments 5554 return self.expression( 5555 exp.Where(this=self._parse_disjunction()), 5556 comments=comments, 5557 ) 5558 5559 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5560 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5561 return None 5562 comments = self._prev_comments 5563 5564 elements: dict[str, t.Any] = defaultdict(list) 5565 5566 if self._match(TokenType.ALL): 5567 elements["all"] = True 5568 elif self._match(TokenType.DISTINCT): 5569 elements["all"] = False 5570 5571 while True: 5572 index = self._index 5573 5574 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5575 # which are also valid identifiers 5576 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5577 break 5578 5579 elements["expressions"].extend( 5580 self._parse_csv( 5581 lambda: ( 5582 None 5583 if self._match_set((TokenType.CUBE, TokenType.ROLLUP), advance=False) 5584 else self._parse_disjunction() 5585 ) 5586 ) 5587 ) 5588 grouping_sets_as_group_by_element = ( 5589 not elements["expressions"] or self._prev.token_type == TokenType.COMMA 5590 ) 5591 5592 before_with_index = self._index 5593 with_prefix = self._match(TokenType.WITH) 5594 5595 if cube_or_rollup := self._parse_cube_or_rollup(with_prefix=with_prefix): 5596 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5597 elements[key].append(cube_or_rollup) 5598 elif grouping_sets := self._parse_grouping_sets(): 5599 elements["grouping_sets"].append(grouping_sets) 5600 elements["grouping_sets_as_group_by_element"] = grouping_sets_as_group_by_element 5601 if not grouping_sets_as_group_by_element: 5602 break 5603 elif self._match_text_seq("TOTALS"): 5604 elements["totals"] = True # type: ignore 5605 5606 if before_with_index <= self._index <= before_with_index + 1: 5607 self._retreat(before_with_index) 5608 break 5609 5610 if index == self._index: 5611 break 5612 5613 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5614 5615 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5616 if self._match(TokenType.CUBE): 5617 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5618 elif self._match(TokenType.ROLLUP): 5619 kind = exp.Rollup 5620 else: 5621 return None 5622 5623 return self.expression( 5624 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5625 ) 5626 5627 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5628 if self._match(TokenType.GROUPING_SETS): 5629 return self.expression( 5630 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5631 ) 5632 return None 5633 5634 def _parse_grouping_set(self) -> exp.Expr | None: 5635 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5636 5637 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5638 if not skip_having_token and not self._match(TokenType.HAVING): 5639 return None 5640 comments = self._prev_comments 5641 return self.expression( 5642 exp.Having(this=self._parse_disjunction()), 5643 comments=comments, 5644 ) 5645 5646 def _parse_qualify(self) -> exp.Qualify | None: 5647 if not self._match(TokenType.QUALIFY): 5648 return None 5649 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5650 5651 def _parse_connect_with_prior(self) -> exp.Expr | None: 5652 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5653 exp.Prior(this=self._parse_bitwise()) 5654 ) 5655 connect = self._parse_disjunction() 5656 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5657 return connect 5658 5659 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5660 if skip_start_token: 5661 start = None 5662 elif self._match_text_seq("START", "WITH"): 5663 start = self._parse_disjunction() 5664 else: 5665 return None 5666 5667 self._match(TokenType.CONNECT_BY) 5668 nocycle = self._match_text_seq("NOCYCLE") 5669 connect = self._parse_connect_with_prior() 5670 5671 if not start and self._match_text_seq("START", "WITH"): 5672 start = self._parse_disjunction() 5673 5674 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5675 5676 def _parse_name_as_expression(self) -> exp.Expr | None: 5677 this = self._parse_id_var(any_token=True) 5678 if self._match(TokenType.ALIAS): 5679 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5680 return this 5681 5682 def _parse_interpolate(self) -> list[exp.Expr] | None: 5683 if self._match_text_seq("INTERPOLATE"): 5684 return self._parse_wrapped_csv(self._parse_name_as_expression) 5685 return None 5686 5687 def _parse_order( 5688 self, this: exp.Expr | None = None, skip_order_token: bool = False 5689 ) -> exp.Expr | None: 5690 siblings = None 5691 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5692 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5693 return this 5694 5695 siblings = True 5696 5697 comments = self._prev_comments 5698 return self.expression( 5699 exp.Order( 5700 this=this, 5701 expressions=self._parse_csv(self._parse_ordered), 5702 siblings=siblings, 5703 ), 5704 comments=comments, 5705 ) 5706 5707 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5708 if not self._match(token): 5709 return None 5710 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5711 5712 def _parse_ordered( 5713 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5714 ) -> exp.Ordered | None: 5715 this = parse_method() if parse_method else self._parse_disjunction() 5716 if not this: 5717 return None 5718 5719 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5720 this = exp.var("ALL") 5721 5722 asc = self._match(TokenType.ASC) 5723 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5724 5725 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5726 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5727 5728 nulls_first = is_nulls_first or False 5729 explicitly_null_ordered = is_nulls_first or is_nulls_last 5730 5731 if ( 5732 not explicitly_null_ordered 5733 and ( 5734 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5735 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5736 ) 5737 and self.dialect.NULL_ORDERING != "nulls_are_last" 5738 ): 5739 nulls_first = True 5740 5741 if self._match_text_seq("WITH", "FILL"): 5742 with_fill = self.expression( 5743 exp.WithFill( 5744 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5745 to=self._match_text_seq("TO") and self._parse_bitwise(), 5746 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5747 interpolate=self._parse_interpolate(), 5748 ) 5749 ) 5750 else: 5751 with_fill = None 5752 5753 return self.expression( 5754 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5755 ) 5756 5757 def _parse_limit_options(self) -> exp.LimitOptions | None: 5758 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5759 rows = self._match_set((TokenType.ROW, TokenType.ROWS)) 5760 self._match_text_seq("ONLY") 5761 with_ties = self._match_text_seq("WITH", "TIES") 5762 5763 if not (percent or rows or with_ties): 5764 return None 5765 5766 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5767 5768 def _parse_limit( 5769 self, 5770 this: exp.Expr | None = None, 5771 top: bool = False, 5772 skip_limit_token: bool = False, 5773 ) -> exp.Expr | None: 5774 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5775 comments = self._prev_comments 5776 if top: 5777 limit_paren = self._match(TokenType.L_PAREN) 5778 expression = ( 5779 self._parse_term() or self._parse_select() 5780 if limit_paren 5781 else self._parse_number() 5782 ) 5783 5784 if limit_paren: 5785 self._match_r_paren() 5786 5787 else: 5788 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5789 return this 5790 5791 expression = self._parse_term(parse_mod=False) 5792 limit_options = self._parse_limit_options() 5793 5794 if self._match(TokenType.COMMA): 5795 offset = expression 5796 expression = self._parse_term() 5797 else: 5798 offset = None 5799 5800 limit_exp = self.expression( 5801 exp.Limit( 5802 this=this, 5803 expression=expression, 5804 offset=offset, 5805 limit_options=limit_options, 5806 expressions=self._parse_limit_by(), 5807 ), 5808 comments=comments, 5809 ) 5810 5811 return limit_exp 5812 5813 if self._match(TokenType.FETCH): 5814 direction = ( 5815 self._prev.text.upper() 5816 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5817 else "FIRST" 5818 ) 5819 5820 count = self._parse_field(tokens=self.FETCH_TOKENS) 5821 5822 return self.expression( 5823 exp.Fetch( 5824 direction=direction, count=count, limit_options=self._parse_limit_options() 5825 ) 5826 ) 5827 5828 return this 5829 5830 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5831 if not self._match(TokenType.OFFSET): 5832 return this 5833 5834 count = self._parse_term() 5835 self._match_set((TokenType.ROW, TokenType.ROWS)) 5836 5837 return self.expression( 5838 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5839 ) 5840 5841 def _can_parse_limit_or_offset(self) -> bool: 5842 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5843 return False 5844 5845 index = self._index 5846 result = bool( 5847 self._try_parse(self._parse_limit, retreat=True) 5848 or self._try_parse(self._parse_offset, retreat=True) 5849 ) 5850 self._retreat(index) 5851 5852 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 5853 if self._next.token_type == TokenType.MATCH_CONDITION: 5854 result = False 5855 5856 return result 5857 5858 def _can_parse_named_window(self) -> bool: 5859 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 5860 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 5861 if not self._match(TokenType.WINDOW, advance=False): 5862 return False 5863 5864 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 5865 if name is None or name.token_type not in self.ID_VAR_TOKENS: 5866 return False 5867 5868 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 5869 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 5870 return False 5871 5872 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 5873 return body is not None and body.token_type == TokenType.L_PAREN 5874 5875 def _parse_limit_by(self) -> list[exp.Expr] | None: 5876 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 5877 5878 def _parse_locks(self) -> list[exp.Lock]: 5879 locks = [] 5880 while True: 5881 update, key = None, None 5882 if self._match_text_seq("FOR", "UPDATE"): 5883 update = True 5884 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 5885 "LOCK", "IN", "SHARE", "MODE" 5886 ): 5887 update = False 5888 elif self._match_text_seq("FOR", "KEY", "SHARE"): 5889 update, key = False, True 5890 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 5891 update, key = True, True 5892 else: 5893 break 5894 5895 expressions = None 5896 if self._match_text_seq("OF"): 5897 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 5898 5899 wait: bool | exp.Expr | None = None 5900 if self._match_text_seq("NOWAIT"): 5901 wait = True 5902 elif self._match_text_seq("WAIT"): 5903 wait = self._parse_primary() 5904 elif self._match_text_seq("SKIP", "LOCKED"): 5905 wait = False 5906 5907 locks.append( 5908 self.expression( 5909 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 5910 ) 5911 ) 5912 5913 return locks 5914 5915 def parse_set_operation( 5916 self, this: exp.Expr | None, consume_pipe: bool = False 5917 ) -> exp.Expr | None: 5918 start = self._index 5919 _, side_token, kind_token = self._parse_join_parts() 5920 5921 side = side_token.text if side_token else None 5922 kind = kind_token.text if kind_token else None 5923 5924 if not self._match_set(self.SET_OPERATIONS): 5925 self._retreat(start) 5926 return None 5927 5928 token_type = self._prev.token_type 5929 5930 if token_type == TokenType.UNION: 5931 operation: type[exp.SetOperation] = exp.Union 5932 elif token_type == TokenType.EXCEPT: 5933 operation = exp.Except 5934 else: 5935 operation = exp.Intersect 5936 5937 comments = self._prev.comments 5938 5939 if self._match(TokenType.DISTINCT): 5940 distinct: bool | None = True 5941 elif self._match(TokenType.ALL): 5942 distinct = False 5943 else: 5944 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5945 if distinct is None: 5946 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5947 5948 by_name = ( 5949 self._match_text_seq("BY", "NAME") 5950 or self._match_text_seq("STRICT", "CORRESPONDING") 5951 or None 5952 ) 5953 if self._match_text_seq("CORRESPONDING"): 5954 by_name = True 5955 if not side and not kind: 5956 kind = "INNER" 5957 5958 on_column_list = None 5959 if by_name and self._match_texts(("ON", "BY")): 5960 on_column_list = self._parse_wrapped_csv(self._parse_column) 5961 5962 expression = self._parse_select( 5963 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5964 ) 5965 5966 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5967 # in _parse_cte and so that alias pushdown can reach into set operation branches 5968 if isinstance(this, exp.Values): 5969 this = self._values_to_select(this) 5970 if isinstance(expression, exp.Values): 5971 expression = self._values_to_select(expression) 5972 5973 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 5974 subquery = this.this 5975 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 5976 subquery.add_comments(this.pop_comments()) 5977 this = subquery 5978 5979 return self.expression( 5980 operation( 5981 this=this, 5982 distinct=distinct, 5983 by_name=by_name, 5984 expression=expression, 5985 side=side, 5986 kind=kind, 5987 on=on_column_list, 5988 ), 5989 comments=comments, 5990 ) 5991 5992 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 5993 while this: 5994 setop = self.parse_set_operation(this) 5995 if not setop: 5996 break 5997 this = setop 5998 5999 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6000 expression = this.expression 6001 6002 if expression: 6003 for arg in self.SET_OP_MODIFIERS: 6004 expr = expression.args.get(arg) 6005 if expr: 6006 this.set(arg, expr.pop()) 6007 6008 return this 6009 6010 def _parse_expression(self) -> exp.Expr | None: 6011 return self._parse_alias(self._parse_assignment()) 6012 6013 def _parse_assignment(self) -> exp.Expr | None: 6014 this = self._parse_disjunction() 6015 if not this and self._next.token_type in self.ASSIGNMENT: 6016 # This allows us to parse <non-identifier token> := <expr> 6017 this = exp.column( 6018 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6019 ) 6020 6021 while self._match_set(self.ASSIGNMENT): 6022 if isinstance(this, exp.Column) and len(this.parts) == 1: 6023 this = this.this 6024 6025 comments = self._prev_comments 6026 this = self.expression( 6027 self.ASSIGNMENT[self._prev.token_type]( 6028 this=this, expression=self._parse_assignment() 6029 ), 6030 comments=comments, 6031 ) 6032 6033 return this 6034 6035 def _parse_disjunction(self) -> exp.Expr | None: 6036 this = self._parse_conjunction() 6037 while self._match_set(self.DISJUNCTION): 6038 comments = self._prev_comments 6039 this = self.expression( 6040 self.DISJUNCTION[self._prev.token_type]( 6041 this=this, expression=self._parse_conjunction() 6042 ), 6043 comments=comments, 6044 ) 6045 return this 6046 6047 def _parse_conjunction(self) -> exp.Expr | None: 6048 this = self._parse_equality() 6049 while self._match_set(self.CONJUNCTION): 6050 comments = self._prev_comments 6051 this = self.expression( 6052 self.CONJUNCTION[self._prev.token_type]( 6053 this=this, expression=self._parse_equality() 6054 ), 6055 comments=comments, 6056 ) 6057 return this 6058 6059 def _parse_equality(self) -> exp.Expr | None: 6060 this = self._parse_comparison() 6061 while self._match_set(self.EQUALITY): 6062 comments = self._prev_comments 6063 this = self.expression( 6064 self.EQUALITY[self._prev.token_type]( 6065 this=this, expression=self._parse_comparison() 6066 ), 6067 comments=comments, 6068 ) 6069 return this 6070 6071 def _parse_comparison(self) -> exp.Expr | None: 6072 this = self._parse_range() 6073 while self._match_set(self.COMPARISON): 6074 comments = self._prev_comments 6075 this = self.expression( 6076 self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()), 6077 comments=comments, 6078 ) 6079 return this 6080 6081 def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6082 this = this or self._parse_bitwise() 6083 6084 while True: 6085 negate = self._match(TokenType.NOT) 6086 if self._match_set(self.RANGE_PARSERS): 6087 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6088 if not expression: 6089 return this 6090 6091 this = expression 6092 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6093 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6094 elif self._match(TokenType.NOTNULL): 6095 # Postgres supports ISNULL and NOTNULL for conditions. 6096 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6097 if self.dialect.NORMALIZE_NOT_NULL: 6098 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6099 this = self.expression(exp.Not(this=this)) 6100 else: 6101 this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True)) 6102 else: 6103 if negate: 6104 self._retreat(self._index - 1) 6105 break 6106 6107 if negate: 6108 this = self._negate_range(this) 6109 if self._curr and ( 6110 self._curr.token_type == TokenType.NOT 6111 or self._curr.token_type in self.RANGE_PARSERS 6112 ): 6113 this = self.expression(exp.Paren(this=this)) 6114 6115 return this 6116 6117 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6118 if not this: 6119 return this 6120 6121 expression = this.this if isinstance(this, exp.Escape) else this 6122 if isinstance(expression, (exp.Like, exp.ILike)): 6123 expression.set("negate", True) 6124 return this 6125 6126 return self.expression(exp.Not(this=this)) 6127 6128 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6129 index = self._index - 1 6130 negate = self._match(TokenType.NOT) 6131 6132 if self._match_text_seq("DISTINCT", "FROM"): 6133 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6134 return self.expression(klass(this=this, expression=self._parse_bitwise())) 6135 6136 if self._match(TokenType.JSON): 6137 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6138 6139 if self._match_text_seq("WITH"): 6140 _with = True 6141 elif self._match_text_seq("WITHOUT"): 6142 _with = False 6143 else: 6144 _with = None 6145 6146 unique = self._match(TokenType.UNIQUE) 6147 self._match_text_seq("KEYS") 6148 expression: exp.Expr | None = self.expression( 6149 exp.JSON(this=kind, with_=_with, unique=unique) 6150 ) 6151 else: 6152 expression = self._parse_null() or self._parse_bitwise() 6153 if not expression: 6154 self._retreat(index) 6155 return None 6156 6157 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6158 this = self.expression(exp.Is(this=this, expression=expression, negate=True)) 6159 else: 6160 this = self.expression(exp.Is(this=this, expression=expression)) 6161 this = self.expression(exp.Not(this=this)) if negate else this 6162 6163 return self._parse_column_ops(this) 6164 6165 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6166 unnest = self._parse_unnest(with_alias=False) 6167 if unnest: 6168 this = self.expression(exp.In(this=this, unnest=unnest)) 6169 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6170 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6171 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6172 6173 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6174 this = self.expression( 6175 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6176 ) 6177 else: 6178 this = self.expression(exp.In(this=this, expressions=expressions)) 6179 6180 if matched_l_paren: 6181 self._match_r_paren(this) 6182 elif not self._match(TokenType.R_BRACKET, expression=this): 6183 self.raise_error("Expecting ]") 6184 else: 6185 this = self.expression(exp.In(this=this, field=self._parse_column())) 6186 6187 return this 6188 6189 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6190 symmetric = None 6191 if self._match_text_seq("SYMMETRIC"): 6192 symmetric = True 6193 elif self._match_text_seq("ASYMMETRIC"): 6194 symmetric = False 6195 6196 low = self._parse_bitwise() 6197 self._match(TokenType.AND) 6198 high = self._parse_bitwise() 6199 6200 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6201 6202 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6203 if not self._match(TokenType.ESCAPE): 6204 return this 6205 return self.expression( 6206 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6207 ) 6208 6209 def _parse_interval_span( 6210 self, this: exp.Expr, parse_function_unit: bool = True 6211 ) -> exp.Interval: 6212 # handle day-time format interval span with omitted units: 6213 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6214 interval_span_units_omitted = None 6215 if ( 6216 this 6217 and this.is_string 6218 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6219 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6220 ): 6221 index = self._index 6222 6223 # Var "TO" Var 6224 first_unit = self._parse_var(any_token=True, upper=True) 6225 second_unit = None 6226 if first_unit and self._match_text_seq("TO"): 6227 second_unit = self._parse_var(any_token=True, upper=True) 6228 6229 interval_span_units_omitted = not (first_unit and second_unit) 6230 6231 self._retreat(index) 6232 6233 unit_index = self._index 6234 if interval_span_units_omitted: 6235 unit = None 6236 else: 6237 # Only attempt to parse a unit if the current token can actually be one, so that a 6238 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6239 is_unit = self._curr is not None and ( 6240 self._curr.token_type == TokenType.VAR 6241 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6242 ) 6243 unit = self._parse_function() if parse_function_unit and is_unit else None 6244 if not unit and is_unit: 6245 unit = self._parse_var(any_token=True, upper=True) 6246 6247 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6248 # each INTERVAL expression into this canonical form so it's easy to transpile 6249 if this and this.is_number: 6250 try: 6251 this = exp.Literal.string(this.to_py()) 6252 except ValueError: 6253 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6254 elif this and this.is_string: 6255 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6256 if parts and unit: 6257 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6258 unit = None 6259 self._retreat(unit_index) 6260 6261 if len(parts) == 1: 6262 this = exp.Literal.string(parts[0][0]) 6263 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6264 6265 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6266 unit = self.expression( 6267 exp.IntervalSpan( 6268 this=unit, 6269 expression=self._parse_function() 6270 or self._parse_var(any_token=True, upper=True), 6271 ) 6272 ) 6273 6274 return self.expression(exp.Interval(this=this, unit=unit)) 6275 6276 def _parse_interval( 6277 self, require_interval: bool = True, parse_function_unit: bool = True 6278 ) -> exp.Add | exp.Interval | None: 6279 index = self._index 6280 6281 if not self._match(TokenType.INTERVAL) and require_interval: 6282 return None 6283 6284 if self._match(TokenType.STRING, advance=False): 6285 this = self._parse_primary() 6286 else: 6287 this = self._parse_term() 6288 6289 if not this or ( 6290 isinstance(this, exp.Column) 6291 and not this.table 6292 and not this.this.quoted 6293 and self._curr 6294 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6295 ): 6296 self._retreat(index) 6297 return None 6298 6299 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6300 6301 index = self._index 6302 self._match(TokenType.PLUS) 6303 6304 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6305 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6306 return self.expression( 6307 exp.Add( 6308 this=interval, 6309 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6310 ) 6311 ) 6312 6313 self._retreat(index) 6314 return interval 6315 6316 def _parse_bitwise(self) -> exp.Expr | None: 6317 this = self._parse_term() 6318 6319 while True: 6320 if self._match_set(self.BITWISE): 6321 this = self.expression( 6322 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6323 ) 6324 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6325 this = self.expression( 6326 exp.DPipe( 6327 this=this, 6328 expression=self._parse_term(), 6329 safe=not self.dialect.STRICT_STRING_CONCAT, 6330 ) 6331 ) 6332 elif self._match(TokenType.DQMARK): 6333 this = self.expression( 6334 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6335 ) 6336 elif self._match_pair(TokenType.LT, TokenType.LT): 6337 this = self.expression( 6338 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6339 ) 6340 elif self._match_pair(TokenType.GT, TokenType.GT): 6341 this = self.expression( 6342 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6343 ) 6344 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6345 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6346 else: 6347 break 6348 6349 return this 6350 6351 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6352 this = self._parse_factor(parse_mod=parse_mod) 6353 6354 while self._match_set(self.TERM): 6355 klass = self.TERM[self._prev.token_type] 6356 comments = self._prev_comments 6357 expression = self._parse_factor(parse_mod=parse_mod) 6358 6359 this = self.expression(klass(this=this, expression=expression), comments=comments) 6360 6361 if isinstance(this, exp.Collate): 6362 self._normalize_collate(this) 6363 6364 return this 6365 6366 def _normalize_collate(self, collate: exp.Collate) -> None: 6367 expr = collate.expression 6368 6369 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6370 # fallback to Identifier / Var 6371 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6372 ident = expr.this 6373 if isinstance(ident, exp.Identifier): 6374 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6375 6376 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6377 parse_method = self._parse_factor_operand 6378 this = self._parse_at_time_zone(parse_method()) 6379 6380 while self._match_set(self.FACTOR, advance=False): 6381 if not parse_mod and self._curr.token_type == TokenType.MOD: 6382 break 6383 6384 self._advance() 6385 klass = self.FACTOR[self._prev.token_type] 6386 comments = self._prev_comments 6387 expression = parse_method() 6388 6389 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6390 self._retreat(self._index - 1) 6391 return this 6392 6393 this = self.expression(klass(this=this, expression=expression), comments=comments) 6394 6395 if isinstance(this, exp.Div): 6396 this.set("typed", self.dialect.TYPED_DIVISION) 6397 this.set("safe", self.dialect.SAFE_DIVISION) 6398 6399 return this 6400 6401 def _parse_factor_operand(self) -> exp.Expr | None: 6402 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6403 6404 def _parse_exponent(self) -> exp.Expr | None: 6405 this = self._parse_unary() 6406 while self._match_set(self.EXPONENT): 6407 comments = self._prev_comments 6408 this = self.expression( 6409 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6410 comments=comments, 6411 ) 6412 return this 6413 6414 def _parse_unary(self) -> exp.Expr | None: 6415 if self._match_set(self.UNARY_PARSERS): 6416 return self.UNARY_PARSERS[self._prev.token_type](self) 6417 return self._parse_type() 6418 6419 def _parse_type( 6420 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6421 ) -> exp.Expr | None: 6422 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6423 return atom 6424 6425 if interval := parse_interval and self._parse_interval(): 6426 return self._parse_column_ops(interval) 6427 6428 index = self._index 6429 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6430 6431 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6432 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6433 if isinstance(data_type, exp.Cast): 6434 # This constructor can contain ops directly after it, for instance struct unnesting: 6435 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6436 return self._parse_column_ops(data_type) 6437 6438 if data_type: 6439 index2 = self._index 6440 this = self._parse_primary() 6441 6442 if isinstance(this, exp.Literal): 6443 literal = this.name 6444 this = self._parse_column_ops(this) 6445 6446 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6447 if parser: 6448 return parser(self, this, data_type) 6449 6450 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6451 if data_type.is_type(exp.DType.TIMESTAMP): 6452 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6453 elif data_type.is_type(exp.DType.TIME): 6454 data_type = exp.DType.TIMETZ.into_expr() 6455 6456 return self.expression(exp.Cast(this=this, to=data_type)) 6457 6458 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6459 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6460 # 6461 # If the index difference here is greater than 1, that means the parser itself must have 6462 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6463 # 6464 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6465 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6466 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6467 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6468 # 6469 # In these cases, we don't really want to return the converted type, but instead retreat 6470 # and try to parse a Column or Identifier in the section below. 6471 if data_type.expressions and index2 - index > 1: 6472 self._retreat(index2) 6473 return self._parse_column_ops(data_type) 6474 6475 self._retreat(index) 6476 6477 if fallback_to_identifier: 6478 return self._parse_id_var() 6479 6480 return self._parse_column() 6481 6482 def _parse_type_size(self) -> exp.DataTypeParam | None: 6483 this = self._parse_type() 6484 if not this: 6485 return None 6486 6487 if isinstance(this, exp.Column) and not this.table: 6488 this = exp.var(this.name.upper()) 6489 6490 return self.expression( 6491 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6492 ) 6493 6494 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6495 type_name = identifier.name 6496 6497 while self._match(TokenType.DOT): 6498 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6499 6500 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6501 6502 def _parse_types( 6503 self, 6504 check_func: bool = False, 6505 schema: bool = False, 6506 allow_identifiers: bool = True, 6507 with_collation: bool = False, 6508 ) -> exp.Expr | None: 6509 index = self._index 6510 this: exp.Expr | None = None 6511 6512 if self._match_set(self.TYPE_TOKENS): 6513 type_token = self._prev.token_type 6514 else: 6515 type_token = None 6516 identifier = allow_identifiers and self._parse_id_var( 6517 any_token=False, tokens=(TokenType.VAR,) 6518 ) 6519 if isinstance(identifier, exp.Identifier): 6520 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6521 this = exp.DataType.build(identifier, udt=True) 6522 else: 6523 try: 6524 tokens = self.dialect.tokenize(identifier.name) 6525 except TokenError: 6526 tokens = None 6527 6528 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6529 if len(tokens) > 1: 6530 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6531 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6532 this = self._parse_user_defined_type(identifier) 6533 else: 6534 self._retreat(self._index - 1) 6535 return None 6536 else: 6537 return None 6538 6539 if type_token == TokenType.PSEUDO_TYPE: 6540 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6541 6542 if type_token == TokenType.OBJECT_IDENTIFIER: 6543 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6544 6545 # https://materialize.com/docs/sql/types/map/ 6546 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6547 key_type = self._parse_types( 6548 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6549 ) 6550 if not self._match(TokenType.FARROW): 6551 self._retreat(index) 6552 return None 6553 6554 value_type = self._parse_types( 6555 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6556 ) 6557 if not self._match(TokenType.R_BRACKET): 6558 self._retreat(index) 6559 return None 6560 6561 return exp.DataType( 6562 this=exp.DType.MAP, 6563 expressions=[key_type, value_type], 6564 nested=True, 6565 ) 6566 6567 nested = type_token in self.NESTED_TYPE_TOKENS 6568 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6569 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6570 expressions = None 6571 maybe_func = False 6572 6573 if self._match(TokenType.L_PAREN): 6574 if is_struct: 6575 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6576 elif nested: 6577 expressions = self._parse_csv( 6578 lambda: self._parse_types( 6579 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6580 ) 6581 ) 6582 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6583 this = expressions[0] 6584 this.set("nullable", True) 6585 self._match_r_paren() 6586 return this 6587 elif type_token in self.ENUM_TYPE_TOKENS: 6588 expressions = self._parse_csv(self._parse_equality) 6589 elif type_token == TokenType.JSON: 6590 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6591 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6592 expressions = self._parse_csv(self._parse_json_type_arg) 6593 elif is_aggregate: 6594 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6595 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6596 ) 6597 if not func_or_ident: 6598 return None 6599 expressions = [func_or_ident] 6600 if self._match(TokenType.COMMA): 6601 expressions.extend( 6602 self._parse_csv( 6603 lambda: self._parse_types( 6604 check_func=check_func, 6605 schema=schema, 6606 allow_identifiers=allow_identifiers, 6607 ) 6608 ) 6609 ) 6610 else: 6611 expressions = self._parse_csv(self._parse_type_size) 6612 6613 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6614 if type_token == TokenType.VECTOR and len(expressions) == 2: 6615 expressions = self._parse_vector_expressions(expressions) 6616 6617 if not self._match(TokenType.R_PAREN): 6618 self._retreat(index) 6619 return None 6620 6621 maybe_func = True 6622 6623 values: list[exp.Expr] | None = None 6624 6625 if nested and self._match(TokenType.LT): 6626 if is_struct: 6627 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6628 else: 6629 expressions = self._parse_csv( 6630 lambda: self._parse_types( 6631 check_func=check_func, 6632 schema=schema, 6633 allow_identifiers=allow_identifiers, 6634 with_collation=True, 6635 ) 6636 ) 6637 6638 if not self._match(TokenType.GT): 6639 self.raise_error("Expecting >") 6640 6641 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6642 values = self._parse_csv(self._parse_disjunction) 6643 if not values and is_struct: 6644 values = None 6645 self._retreat(self._index - 1) 6646 else: 6647 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6648 6649 if type_token in self.TIMESTAMPS: 6650 if self._match_text_seq("WITH", "TIME", "ZONE"): 6651 maybe_func = False 6652 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6653 this = exp.DataType(this=tz_type, expressions=expressions) 6654 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6655 maybe_func = False 6656 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6657 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6658 maybe_func = False 6659 elif type_token == TokenType.INTERVAL: 6660 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6661 unit = self._parse_var(upper=True) 6662 if self._match_text_seq("TO"): 6663 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6664 6665 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6666 else: 6667 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6668 elif type_token == TokenType.VOID: 6669 this = exp.DataType(this=exp.DType.NULL) 6670 6671 if maybe_func and check_func: 6672 index2 = self._index 6673 peek = self._parse_string() 6674 6675 if not peek: 6676 self._retreat(index) 6677 return None 6678 6679 self._retreat(index2) 6680 6681 if not this: 6682 assert type_token is not None 6683 if self._match_text_seq("UNSIGNED"): 6684 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6685 if not unsigned_type_token: 6686 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6687 6688 type_token = unsigned_type_token or type_token 6689 6690 # NULLABLE without parentheses can be a column (Presto/Trino) 6691 if type_token == TokenType.NULLABLE and not expressions: 6692 self._retreat(index) 6693 return None 6694 6695 this = exp.DataType( 6696 this=exp.DType[type_token.name], 6697 expressions=expressions, 6698 nested=nested, 6699 ) 6700 6701 # Empty arrays/structs are allowed 6702 if values is not None: 6703 cls = exp.Struct if is_struct else exp.Array 6704 this = exp.cast(cls(expressions=values), this, copy=False) 6705 6706 elif expressions: 6707 this.set("expressions", expressions) 6708 6709 # https://materialize.com/docs/sql/types/list/#type-name 6710 while self._match(TokenType.LIST): 6711 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6712 6713 index = self._index 6714 6715 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6716 matched_array = self._match(TokenType.ARRAY) 6717 6718 while self._curr: 6719 datatype_token = self._prev.token_type 6720 matched_l_bracket = self._match(TokenType.L_BRACKET) 6721 6722 if (not matched_l_bracket and not matched_array) or ( 6723 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6724 ): 6725 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6726 # not to be confused with the fixed size array parsing 6727 break 6728 6729 matched_array = False 6730 values = self._parse_csv(self._parse_disjunction) or None 6731 if ( 6732 values 6733 and not schema 6734 and ( 6735 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6736 or datatype_token == TokenType.ARRAY 6737 or not self._match(TokenType.R_BRACKET, advance=False) 6738 ) 6739 ): 6740 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6741 # 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 6742 self._retreat(index) 6743 break 6744 6745 this = exp.DataType( 6746 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6747 ) 6748 self._match(TokenType.R_BRACKET) 6749 6750 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6751 converter = self.TYPE_CONVERTERS.get(this.this) 6752 if converter: 6753 this = converter(t.cast(exp.DataType, this)) 6754 6755 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6756 this.set("collate", self._parse_identifier() or self._parse_column()) 6757 6758 return this 6759 6760 def _parse_json_type_arg(self) -> exp.Expr | None: 6761 """Parse a single argument to ClickHouse's JSON type.""" 6762 6763 # SKIP col or SKIP REGEXP 'pattern' 6764 if self._match_text_seq("SKIP"): 6765 regexp = self._match(TokenType.RLIKE) 6766 arg = self._parse_column() 6767 if isinstance(arg, exp.Column): 6768 arg = arg.to_dot() 6769 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6770 6771 param_or_col = self._parse_column() 6772 if not isinstance(param_or_col, exp.Column): 6773 return None 6774 6775 # Parameter: name=value (e.g., max_dynamic_paths=2) 6776 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 6777 param = param_or_col.name 6778 value = self._parse_primary() 6779 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 6780 6781 # Column type hint: col_name Type 6782 col = param_or_col.to_dot() 6783 kind = self._parse_types(check_func=False, allow_identifiers=False) 6784 return self.expression(exp.ColumnDef(this=col, kind=kind)) 6785 6786 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 6787 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 6788 6789 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 6790 index = self._index 6791 6792 if ( 6793 self._curr 6794 and self._next 6795 and self._curr.token_type in self.TYPE_TOKENS 6796 and self._next.token_type in self.TYPE_TOKENS 6797 ): 6798 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 6799 # type token. Without this, the list will be parsed as a type and we'll eventually crash 6800 this = self._parse_id_var() 6801 else: 6802 this = ( 6803 self._parse_type(parse_interval=False, fallback_to_identifier=True) 6804 or self._parse_id_var() 6805 ) 6806 6807 self._match(TokenType.COLON) 6808 6809 if ( 6810 type_required 6811 and not isinstance(this, exp.DataType) 6812 and not self._match_set(self.TYPE_TOKENS, advance=False) 6813 ): 6814 self._retreat(index) 6815 return self._parse_types() 6816 6817 return self._parse_column_def(this) 6818 6819 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 6820 if not self._match_text_seq("AT", "TIME", "ZONE"): 6821 return this 6822 return self._parse_at_time_zone( 6823 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 6824 ) 6825 6826 def _parse_atom(self) -> exp.Expr | None: 6827 if ( 6828 self._curr.token_type in self.IDENTIFIER_TOKENS 6829 and (column := self._parse_column()) is not None 6830 ): 6831 return column 6832 6833 token = self._curr 6834 token_type = token.token_type 6835 6836 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 6837 return None 6838 6839 next_type = self._next.token_type 6840 6841 if ( 6842 next_type in self.COLUMN_OPERATORS 6843 or next_type in self.COLUMN_POSTFIX_TOKENS 6844 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 6845 ): 6846 return None 6847 6848 self._advance() 6849 return primary_parser(self, token) 6850 6851 def _parse_column(self) -> exp.Expr | None: 6852 column: exp.Expr | None = self._parse_column_parts_fast() 6853 if column is None: 6854 this = self._parse_column_reference() 6855 if not this: 6856 this = self._parse_bracket(this) 6857 column = self._parse_column_ops(this) if this else this 6858 6859 if column: 6860 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 6861 column.set("join_mark", self._match(TokenType.JOIN_MARKER)) 6862 if self.COLON_IS_VARIANT_EXTRACT: 6863 column = self._parse_colon_as_variant_extract(column) 6864 6865 return column 6866 6867 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 6868 """Fast path for simple column and dot references (a, a.b, ...). 6869 6870 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 6871 that nothing complex follows. If it does, retreats and returns None so 6872 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 6873 """ 6874 index = self._index 6875 parts: list[exp.Identifier] | None = None 6876 all_comments: list[str] | None = None 6877 6878 while self._match_set(self.IDENTIFIER_TOKENS): 6879 token = self._prev 6880 comments = self._prev_comments 6881 6882 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 6883 self._retreat(index) 6884 return None 6885 6886 has_dot = self._match(TokenType.DOT) 6887 curr_tt = self._curr.token_type 6888 6889 if not has_dot: 6890 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 6891 self._retreat(index) 6892 return None 6893 elif curr_tt not in self.IDENTIFIER_TOKENS: 6894 self._retreat(index) 6895 return None 6896 6897 if parts is None: 6898 parts = [] 6899 6900 if comments: 6901 if all_comments is None: 6902 all_comments = [] 6903 all_comments.extend(comments) 6904 self._prev_comments = [] 6905 6906 parts.append( 6907 self.expression( 6908 exp.Identifier( 6909 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 6910 ), 6911 token, 6912 ) 6913 ) 6914 6915 if not has_dot: 6916 break 6917 6918 if parts is None: 6919 return None 6920 6921 n = len(parts) 6922 6923 if n == 1: 6924 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 6925 elif n == 2: 6926 column = exp.Column(this=parts[1], table=parts[0]) 6927 elif n == 3: 6928 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 6929 else: 6930 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 6931 6932 for i in range(4, n): 6933 column = exp.Dot(this=column, expression=parts[i]) 6934 6935 if all_comments: 6936 column.add_comments(all_comments) 6937 6938 return column 6939 6940 def _parse_column_reference(self) -> exp.Expr | None: 6941 this = self._parse_field() 6942 if ( 6943 not this 6944 and self._match(TokenType.VALUES, advance=False) 6945 and self.VALUES_FOLLOWED_BY_PAREN 6946 and (not self._next or self._next.token_type != TokenType.L_PAREN) 6947 ): 6948 this = self._parse_id_var() 6949 6950 if isinstance(this, exp.Identifier): 6951 # We bubble up comments from the Identifier to the Column 6952 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 6953 6954 return this 6955 6956 def _build_json_extract( 6957 self, 6958 this: exp.Expr | None, 6959 path_parts: list[exp.JSONPathPart], 6960 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 6961 if len(path_parts) > 1: 6962 this = self.expression( 6963 exp.JSONExtract( 6964 this=this, 6965 expression=exp.JSONPath(expressions=path_parts), 6966 variant_extract=True, 6967 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 6968 ) 6969 ) 6970 path_parts = [exp.JSONPathRoot()] 6971 6972 return this, path_parts 6973 6974 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 6975 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 6976 6977 while self._match(TokenType.COLON): 6978 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 6979 this, path_parts = self._build_json_extract(this, path_parts) 6980 6981 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 6982 6983 if key: 6984 quoted = isinstance(key, exp.Identifier) and key.quoted 6985 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 6986 6987 while True: 6988 if self._match(TokenType.DOT): 6989 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 6990 6991 if next_key: 6992 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 6993 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 6994 elif self._match(TokenType.L_BRACKET): 6995 bracket_expr = self._parse_bracket_key_value() 6996 6997 if not self._match(TokenType.R_BRACKET): 6998 self.raise_error("Expected ]") 6999 7000 if bracket_expr: 7001 if bracket_expr.is_string: 7002 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7003 elif bracket_expr.is_star: 7004 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7005 elif bracket_expr.is_number: 7006 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7007 else: 7008 this, path_parts = self._build_json_extract(this, path_parts) 7009 7010 this = self.expression( 7011 exp.Bracket( 7012 this=this, expressions=[bracket_expr], json_access=True 7013 ), 7014 ) 7015 7016 elif self._match(TokenType.DCOLON): 7017 this, path_parts = self._build_json_extract(this, path_parts) 7018 7019 cast_type = self._parse_types() 7020 if cast_type: 7021 this = self.expression(exp.Cast(this=this, to=cast_type)) 7022 else: 7023 self.raise_error("Expected type after '::'") 7024 else: 7025 break 7026 7027 this, _ = self._build_json_extract(this, path_parts) 7028 7029 return this 7030 7031 def _parse_dcolon(self) -> exp.Expr | None: 7032 return self._parse_types() 7033 7034 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7035 while self._curr.token_type in self.BRACKETS: 7036 this = self._parse_bracket(this) 7037 7038 column_operators = self.COLUMN_OPERATORS 7039 cast_column_operators = self.CAST_COLUMN_OPERATORS 7040 while self._curr: 7041 op_token = self._curr.token_type 7042 7043 if op_token not in column_operators: 7044 break 7045 op = column_operators[op_token] 7046 self._advance() 7047 7048 if op_token in cast_column_operators: 7049 field = self._parse_dcolon() 7050 if not field: 7051 self.raise_error("Expected type") 7052 elif op and self._curr: 7053 field = self._parse_column_reference() or self._parse_bitwise() 7054 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7055 field = self._parse_column_ops(field) 7056 else: 7057 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7058 field = self._parse_field(any_token=True, anonymous_func=True) 7059 7060 # In t.true, t.null we should produce an Identifier node 7061 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7062 field = self.expression( 7063 exp.Identifier(this=self._prev.text), 7064 comments=field.comments, 7065 ) 7066 7067 # Function calls can be qualified, e.g., x.y.FOO() 7068 # This converts the final AST to a series of Dots leading to the function call 7069 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7070 if isinstance(field, (exp.Func, exp.Window)) and this: 7071 this = this.transform( 7072 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7073 ) 7074 7075 if op: 7076 this = op(self, this, field) 7077 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7078 this = self.expression( 7079 exp.Column( 7080 this=field, 7081 table=this.this, 7082 db=this.args.get("table"), 7083 catalog=this.args.get("db"), 7084 ), 7085 comments=this.comments, 7086 ) 7087 elif isinstance(field, exp.Window): 7088 # Move the exp.Dot's to the window's function 7089 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7090 field.set("this", window_func) 7091 this = field 7092 else: 7093 this = self.expression(exp.Dot(this=this, expression=field)) 7094 7095 if field and field.comments: 7096 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7097 7098 this = self._parse_bracket(this) 7099 7100 return this 7101 7102 def _parse_paren(self) -> exp.Expr | None: 7103 if not self._match(TokenType.L_PAREN): 7104 return None 7105 7106 comments = self._prev_comments 7107 query = self._parse_select() 7108 7109 if query: 7110 expressions = [query] 7111 else: 7112 expressions = self._parse_expressions() 7113 7114 this = seq_get(expressions, 0) 7115 7116 if not this and self._match(TokenType.R_PAREN, advance=False): 7117 this = self.expression(exp.Tuple()) 7118 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7119 this = self.expression(exp.Tuple(expressions=expressions)) 7120 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7121 this = self._parse_subquery(this=this, parse_alias=False) 7122 elif isinstance(this, (exp.Subquery, exp.Values)): 7123 this = self._parse_subquery( 7124 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7125 parse_alias=False, 7126 ) 7127 else: 7128 this = self.expression(exp.Paren(this=this)) 7129 7130 if this: 7131 this.add_comments(comments) 7132 7133 self._match_r_paren(expression=this) 7134 7135 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7136 return self._parse_window(this) 7137 7138 return this 7139 7140 def _parse_primary(self) -> exp.Expr | None: 7141 if self._match_set(self.PRIMARY_PARSERS): 7142 token_type = self._prev.token_type 7143 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7144 7145 if token_type == TokenType.STRING: 7146 expressions = [primary] 7147 while self._match(TokenType.STRING, advance=False): 7148 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7149 self.raise_error( 7150 "Adjacent string literals need to be separated by whitespace or comments" 7151 ) 7152 7153 self._advance() 7154 expressions.append(exp.Literal.string(self._prev.text)) 7155 7156 if len(expressions) > 1: 7157 return self.expression( 7158 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7159 ) 7160 7161 return primary 7162 7163 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7164 return exp.Literal.number(f"0.{self._prev.text}") 7165 7166 return self._parse_paren() 7167 7168 def _parse_field( 7169 self, 7170 any_token: bool = False, 7171 tokens: t.Collection[TokenType] | None = None, 7172 anonymous_func: bool = False, 7173 ) -> exp.Expr | None: 7174 after_dot = ( 7175 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7176 ) 7177 7178 if anonymous_func: 7179 field = ( 7180 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7181 or self._parse_primary() 7182 ) 7183 else: 7184 field = self._parse_primary() or self._parse_function( 7185 anonymous=anonymous_func, any_token=any_token 7186 ) 7187 7188 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7189 7190 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7191 name = field.name 7192 if self._is_connected() and self._parse_var(any_token=True): 7193 name += self._prev.text 7194 7195 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7196 7197 return field 7198 7199 def _parse_function( 7200 self, 7201 functions: dict[str, t.Callable] | None = None, 7202 anonymous: bool = False, 7203 optional_parens: bool = True, 7204 any_token: bool = False, 7205 ) -> exp.Expr | None: 7206 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7207 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7208 fn_syntax = False 7209 if ( 7210 self._match(TokenType.L_BRACE, advance=False) 7211 and self._next 7212 and self._next.text.upper() == "FN" 7213 ): 7214 self._advance(2) 7215 fn_syntax = True 7216 7217 func = self._parse_function_call( 7218 functions=functions, 7219 anonymous=anonymous, 7220 optional_parens=optional_parens, 7221 any_token=any_token, 7222 ) 7223 7224 if fn_syntax: 7225 self._match(TokenType.R_BRACE) 7226 7227 return func 7228 7229 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7230 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7231 7232 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7233 args = self._parse_function_args(alias=False) 7234 if not args: 7235 self.raise_error("Expected at least one argument") 7236 7237 # Wrapped so the connector keeps its precedence in the parent context 7238 return exp.Paren(this=connector(*args, copy=False)) 7239 7240 def _parse_function_call( 7241 self, 7242 functions: dict[str, t.Callable] | None = None, 7243 anonymous: bool = False, 7244 optional_parens: bool = True, 7245 any_token: bool = False, 7246 ) -> exp.Expr | None: 7247 if not self._curr: 7248 return None 7249 7250 comments = self._curr.comments 7251 prev = self._prev 7252 token = self._curr 7253 token_type = self._curr.token_type 7254 this: str | exp.Expr = self._curr.text 7255 upper = self._curr.text.upper() 7256 7257 after_dot = prev.token_type == TokenType.DOT 7258 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7259 if ( 7260 optional_parens 7261 and parser 7262 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7263 and not after_dot 7264 ): 7265 self._advance() 7266 return self._parse_window(parser(self)) 7267 7268 if self._next.token_type != TokenType.L_PAREN: 7269 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7270 self._advance() 7271 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7272 7273 return None 7274 7275 if any_token: 7276 if token_type in self.RESERVED_TOKENS: 7277 return None 7278 elif token_type not in self.FUNC_TOKENS: 7279 return None 7280 7281 self._advance(2) 7282 7283 parser = self.FUNCTION_PARSERS.get(upper) 7284 if parser and not anonymous: 7285 result = parser(self) 7286 else: 7287 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7288 7289 if subquery_predicate: 7290 expr = None 7291 if self._curr.token_type in self.SUBQUERY_TOKENS: 7292 expr = self._parse_select() 7293 self._match_r_paren() 7294 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7295 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7296 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7297 self._advance(-1) 7298 expr = self._parse_bitwise() 7299 7300 if expr: 7301 return self.expression(subquery_predicate(this=expr), comments=comments) 7302 7303 if functions is None: 7304 functions = self.FUNCTIONS 7305 7306 function = functions.get(upper) 7307 known_function = function and not anonymous 7308 7309 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7310 args = self._parse_function_args(alias) 7311 7312 post_func_comments = self._curr.comments if self._curr else None 7313 if known_function and post_func_comments: 7314 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7315 # call we'll construct it as exp.Anonymous, even if it's "known" 7316 if any( 7317 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7318 for comment in post_func_comments 7319 ): 7320 known_function = False 7321 7322 if alias and known_function: 7323 args = self._kv_to_prop_eq(args) 7324 7325 if known_function: 7326 func_builder = t.cast(t.Callable, function) 7327 7328 # mypyc compiled functions don't have __code__, so we use 7329 # try/except to check if func_builder accepts 'dialect'. 7330 try: 7331 func = func_builder(args) 7332 except TypeError: 7333 func = func_builder(args, dialect=self.dialect) 7334 7335 func = self.validate_expression(func, args) 7336 if self.dialect.PRESERVE_ORIGINAL_NAMES: 7337 func.meta["name"] = this 7338 7339 result = func 7340 else: 7341 if token_type == TokenType.IDENTIFIER: 7342 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7343 7344 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7345 7346 result = result.update_positions(token) 7347 7348 if isinstance(result, exp.Expr): 7349 result.add_comments(comments) 7350 7351 if parser: 7352 self._match(TokenType.R_PAREN, expression=result) 7353 else: 7354 self._match_r_paren(result) 7355 return self._parse_window(result) 7356 7357 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7358 return expression 7359 7360 def _kv_to_prop_eq( 7361 self, expressions: list[exp.Expr], parse_map: bool = False 7362 ) -> list[exp.Expr]: 7363 transformed = [] 7364 7365 for index, e in enumerate(expressions): 7366 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7367 if isinstance(e, exp.Alias): 7368 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7369 7370 if not isinstance(e, exp.PropertyEQ): 7371 e = self.expression( 7372 exp.PropertyEQ( 7373 this=e.this if parse_map else exp.to_identifier(e.this.name), 7374 expression=e.expression, 7375 ) 7376 ) 7377 7378 if isinstance(e.this, exp.Column): 7379 e.this.replace(e.this.this) 7380 else: 7381 e = self._to_prop_eq(e, index) 7382 7383 transformed.append(e) 7384 7385 return transformed 7386 7387 def _parse_function_properties(self) -> exp.Properties | None: 7388 # Skip the generic `key = value` fallback in _parse_property since this 7389 # runs post-AS where a function body like `name = expr` can be misread 7390 # as a property. 7391 properties = [] 7392 while True: 7393 if self._match_texts(self.PROPERTY_PARSERS): 7394 keyword = self._prev.text.upper() 7395 prop = self.PROPERTY_PARSERS[keyword](self) 7396 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7397 keyword = self._prev.text.upper() 7398 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7399 else: 7400 break 7401 if not prop: 7402 self.raise_error(f"Failed to parse property '{keyword}'") 7403 break 7404 for p in ensure_list(prop): 7405 properties.append(p) 7406 7407 return self.expression(exp.Properties(expressions=properties)) if properties else None 7408 7409 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7410 return self._parse_statement() 7411 7412 def _parse_function_parameter(self) -> exp.Expr | None: 7413 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7414 7415 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7416 this = self._parse_table_parts(schema=True) 7417 7418 if not self._match(TokenType.L_PAREN): 7419 return this 7420 7421 expressions = self._parse_csv(self._parse_function_parameter) 7422 self._match_r_paren() 7423 return self.expression( 7424 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7425 ) 7426 7427 def _parse_macro_overloads( 7428 self, 7429 this: exp.UserDefinedFunction, 7430 first_body: exp.Expr, 7431 first_is_table: bool = False, 7432 ) -> exp.MacroOverloads: 7433 overloads = [ 7434 self.expression( 7435 exp.MacroOverload( 7436 this=first_body, 7437 expressions=this.expressions or None, 7438 is_table=first_is_table, 7439 ) 7440 ) 7441 ] 7442 this.set("expressions", None) 7443 this.set("wrapped", False) 7444 7445 while self._match(TokenType.COMMA): 7446 if not self._match(TokenType.L_PAREN): 7447 break 7448 7449 params = self._parse_csv(self._parse_function_parameter) 7450 self._match_r_paren() 7451 7452 if not self._match(TokenType.ALIAS): 7453 break 7454 7455 is_table = self._match(TokenType.TABLE) 7456 body = self._parse_expression() 7457 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7458 overloads.append(self.expression(macro)) 7459 7460 return self.expression(exp.MacroOverloads(expressions=overloads)) 7461 7462 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7463 literal = self._parse_primary() 7464 if literal: 7465 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7466 7467 return self._identifier_expression(token) 7468 7469 def _parse_session_parameter(self) -> exp.SessionParameter: 7470 kind = None 7471 this = self._parse_id_var() or self._parse_primary() 7472 7473 if this and self._match(TokenType.DOT): 7474 kind = this.name 7475 this = self._parse_var() or self._parse_primary() 7476 7477 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7478 7479 def _parse_lambda_arg(self) -> exp.Expr | None: 7480 return self._parse_id_var() 7481 7482 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7483 next_token_type = self._next.token_type 7484 7485 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7486 if ( 7487 next_token_type in self.LAMBDA_ARG_TERMINATORS 7488 and (atom := self._parse_atom()) is not None 7489 ): 7490 return atom 7491 7492 index = self._index 7493 7494 if self._match(TokenType.L_PAREN): 7495 expressions = t.cast( 7496 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7497 ) 7498 7499 if not self._match(TokenType.R_PAREN): 7500 self._retreat(index) 7501 elif self._match_set(self.LAMBDAS): 7502 return self.LAMBDAS[self._prev.token_type](self, expressions) 7503 else: 7504 self._retreat(index) 7505 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7506 expressions = [self._parse_lambda_arg()] 7507 7508 if self._match_set(self.LAMBDAS): 7509 return self.LAMBDAS[self._prev.token_type](self, expressions) 7510 7511 self._retreat(index) 7512 7513 this: exp.Expr | None 7514 7515 if self._match(TokenType.DISTINCT): 7516 this = self.expression( 7517 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7518 ) 7519 else: 7520 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7521 this = self._parse_select_or_expression(alias=alias) 7522 7523 return self._parse_limit( 7524 self._parse_respect_or_ignore_nulls( 7525 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7526 ) 7527 ) 7528 7529 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7530 index = self._index 7531 if not self._match(TokenType.L_PAREN): 7532 return this 7533 7534 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7535 # expr can be of both types 7536 if self._match_set(self.SELECT_START_TOKENS): 7537 self._retreat(index) 7538 return this 7539 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7540 self._match_r_paren() 7541 return self.expression(exp.Schema(this=this, expressions=args)) 7542 7543 def _parse_field_def(self) -> exp.Expr | None: 7544 return self._parse_column_def(self._parse_field(any_token=True)) 7545 7546 def _parse_column_def( 7547 self, this: exp.Expr | None, computed_column: bool = True 7548 ) -> exp.Expr | None: 7549 # column defs are not really columns, they're identifiers 7550 if isinstance(this, exp.Column): 7551 this = this.this 7552 7553 if not computed_column: 7554 self._match(TokenType.ALIAS) 7555 7556 kind = self._parse_types(schema=True) 7557 7558 if self._match_text_seq("FOR", "ORDINALITY"): 7559 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7560 7561 constraints: list[exp.Expr] = [] 7562 7563 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7564 ("ALIAS", "MATERIALIZED") 7565 ): 7566 # Match storage before _parse_types so STORED is not treated as a data type 7567 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7568 persisted = self._prev.text.upper() == "MATERIALIZED" 7569 expression = self._parse_disjunction() 7570 if not persisted: 7571 if self._match_text_seq("PERSISTED"): 7572 persisted = True 7573 elif self._match_texts(("STORED", "VIRTUAL")): 7574 persisted = self._prev.text.upper() == "STORED" 7575 constraint_kind = exp.ComputedColumnConstraint( 7576 this=expression, 7577 persisted=persisted, 7578 data_type=exp.Var(this="AUTO") 7579 if self._match_text_seq("AUTO") 7580 else self._parse_types(), 7581 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7582 ) 7583 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7584 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7585 in_out_constraint = self.expression( 7586 exp.InOutColumnConstraint( 7587 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7588 ) 7589 ) 7590 constraints.append(in_out_constraint) 7591 kind = self._parse_types() 7592 elif ( 7593 kind 7594 and self._match(TokenType.ALIAS, advance=False) 7595 and ( 7596 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7597 or self._next.token_type == TokenType.L_PAREN 7598 ) 7599 ): 7600 self._advance() 7601 constraints.append( 7602 self.expression( 7603 exp.ColumnConstraint( 7604 kind=exp.ComputedColumnConstraint( 7605 this=self._parse_disjunction(), 7606 persisted=self._match_texts(("STORED", "VIRTUAL")) 7607 and self._prev.text.upper() == "STORED", 7608 ) 7609 ) 7610 ) 7611 ) 7612 7613 while True: 7614 constraint = self._parse_column_constraint() 7615 if not constraint: 7616 break 7617 constraints.append(constraint) 7618 7619 if not kind and not constraints: 7620 return this 7621 7622 position = None 7623 if self._match_texts(("FIRST", "AFTER")): 7624 pos = self._prev.text 7625 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7626 7627 return self.expression( 7628 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7629 ) 7630 7631 def _parse_auto_increment( 7632 self, 7633 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7634 start = None 7635 increment = None 7636 order = None 7637 7638 if self._match(TokenType.L_PAREN, advance=False): 7639 args = self._parse_wrapped_csv(self._parse_bitwise) 7640 start = seq_get(args, 0) 7641 increment = seq_get(args, 1) 7642 7643 # The remaining parts form an unordered bag and any of them can be omitted, in which 7644 # case the engine falls back to its own default, so they're parsed independently. 7645 while True: 7646 if self._match_text_seq("START"): 7647 start = self._parse_bitwise() 7648 elif self._match_text_seq("INCREMENT"): 7649 increment = self._parse_bitwise() 7650 elif self._match_text_seq("ORDER"): 7651 order = True 7652 elif self._match_text_seq("NOORDER"): 7653 order = False 7654 else: 7655 break 7656 7657 if start or increment or order is not None: 7658 return exp.GeneratedAsIdentityColumnConstraint( 7659 start=start, increment=increment, this=False, order=order 7660 ) 7661 7662 return exp.AutoIncrementColumnConstraint() 7663 7664 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7665 if not self._match(TokenType.L_PAREN, advance=False): 7666 return None 7667 7668 return self.expression( 7669 exp.CheckColumnConstraint( 7670 this=self._parse_wrapped(self._parse_assignment), 7671 enforced=self._match_text_seq("ENFORCED"), 7672 ) 7673 ) 7674 7675 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7676 if not self._match_text_seq("REFRESH"): 7677 self._retreat(self._index - 1) 7678 return None 7679 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7680 7681 def _parse_compress(self) -> exp.CompressColumnConstraint: 7682 if self._match(TokenType.L_PAREN, advance=False): 7683 return self.expression( 7684 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7685 ) 7686 7687 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7688 7689 def _parse_generated_as_identity( 7690 self, 7691 ) -> ( 7692 exp.GeneratedAsIdentityColumnConstraint 7693 | exp.ComputedColumnConstraint 7694 | exp.GeneratedAsRowColumnConstraint 7695 ): 7696 if self._match_text_seq("BY", "DEFAULT"): 7697 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7698 this = self.expression( 7699 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7700 ) 7701 else: 7702 self._match_text_seq("ALWAYS") 7703 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7704 7705 self._match(TokenType.ALIAS) 7706 7707 if self._match_text_seq("ROW"): 7708 start = self._match_text_seq("START") 7709 if not start: 7710 self._match(TokenType.END) 7711 hidden = self._match_text_seq("HIDDEN") 7712 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7713 7714 identity = self._match_text_seq("IDENTITY") 7715 7716 if self._match(TokenType.L_PAREN): 7717 if self._match_text_seq("START", "WITH"): 7718 this.set("start", self._parse_bitwise()) 7719 if self._match_text_seq("INCREMENT", "BY"): 7720 this.set("increment", self._parse_bitwise()) 7721 if self._match_text_seq("MINVALUE"): 7722 this.set("minvalue", self._parse_bitwise()) 7723 if self._match_text_seq("MAXVALUE"): 7724 this.set("maxvalue", self._parse_bitwise()) 7725 7726 if self._match_text_seq("CYCLE"): 7727 this.set("cycle", True) 7728 elif self._match_text_seq("NO", "CYCLE"): 7729 this.set("cycle", False) 7730 7731 if not identity: 7732 this.set("expression", self._parse_range()) 7733 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7734 args = self._parse_csv(self._parse_bitwise) 7735 this.set("start", seq_get(args, 0)) 7736 this.set("increment", seq_get(args, 1)) 7737 7738 self._match_r_paren() 7739 7740 return this 7741 7742 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7743 self._match_text_seq("LENGTH") 7744 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7745 7746 def _parse_not_constraint(self) -> exp.Expr | None: 7747 if self._match_text_seq("NULL"): 7748 return self.expression(exp.NotNullColumnConstraint()) 7749 if self._match_text_seq("CASESPECIFIC"): 7750 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7751 if self._match_text_seq("FOR", "REPLICATION"): 7752 return self.expression(exp.NotForReplicationColumnConstraint()) 7753 7754 # Unconsume the `NOT` token 7755 self._retreat(self._index - 1) 7756 return None 7757 7758 def _parse_column_constraint(self) -> exp.Expr | None: 7759 this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None 7760 7761 procedure_option_follows = ( 7762 self._match(TokenType.WITH, advance=False) 7763 and self._next 7764 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7765 ) 7766 7767 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 7768 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 7769 if not constraint: 7770 self._retreat(self._index - 1) 7771 return None 7772 7773 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 7774 7775 if self._match_text_seq("CHARACTER", "SET"): 7776 return self.expression( 7777 exp.ColumnConstraint( 7778 this=this, 7779 kind=self.expression( 7780 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 7781 ), 7782 ) 7783 ) 7784 7785 return this 7786 7787 def _parse_constraint(self) -> exp.Expr | None: 7788 if not self._match(TokenType.CONSTRAINT): 7789 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 7790 7791 return self.expression( 7792 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 7793 ) 7794 7795 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 7796 constraints = [] 7797 while True: 7798 constraint = self._parse_unnamed_constraint() or self._parse_function() 7799 if not constraint: 7800 break 7801 constraints.append(constraint) 7802 7803 return constraints 7804 7805 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 7806 index = self._index 7807 7808 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 7809 constraints or self.CONSTRAINT_PARSERS 7810 ): 7811 return None 7812 7813 constraint_key = self._prev.text.upper() 7814 if constraint_key not in self.CONSTRAINT_PARSERS: 7815 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 7816 7817 result = self.CONSTRAINT_PARSERS[constraint_key](self) 7818 if not result: 7819 self._retreat(index) 7820 7821 return result 7822 7823 def _parse_unique_key(self) -> exp.Expr | None: 7824 if ( 7825 self._curr 7826 and self._curr.token_type != TokenType.IDENTIFIER 7827 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 7828 ): 7829 return None 7830 return self._parse_id_var(any_token=False) 7831 7832 def _parse_unique(self) -> exp.UniqueColumnConstraint: 7833 self._match_texts(("KEY", "INDEX")) 7834 return self.expression( 7835 exp.UniqueColumnConstraint( 7836 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 7837 this=self._parse_schema(self._parse_unique_key()), 7838 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 7839 on_conflict=self._parse_on_conflict(), 7840 options=self._parse_key_constraint_options(), 7841 ) 7842 ) 7843 7844 def _parse_key_constraint_options(self) -> list[str]: 7845 options = [] 7846 while True: 7847 if not self._curr: 7848 break 7849 7850 if self._match(TokenType.ON): 7851 action = None 7852 on = self._advance_any() and self._prev.text 7853 7854 if self._match_text_seq("NO", "ACTION"): 7855 action = "NO ACTION" 7856 elif self._match_text_seq("CASCADE"): 7857 action = "CASCADE" 7858 elif self._match_text_seq("RESTRICT"): 7859 action = "RESTRICT" 7860 elif self._match_pair(TokenType.SET, TokenType.NULL): 7861 action = "SET NULL" 7862 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 7863 action = "SET DEFAULT" 7864 else: 7865 self.raise_error("Invalid key constraint") 7866 7867 options.append(f"ON {on} {action}") 7868 else: 7869 var = self._parse_var_from_options( 7870 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 7871 ) 7872 if not var: 7873 break 7874 options.append(var.name) 7875 7876 return options 7877 7878 def _parse_references(self, match: bool = True) -> exp.Reference | None: 7879 if match and not self._match(TokenType.REFERENCES): 7880 return None 7881 7882 expressions: list | None = None 7883 this = self._parse_table(schema=True) 7884 options = self._parse_key_constraint_options() 7885 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 7886 7887 def _parse_foreign_key(self) -> exp.ForeignKey: 7888 expressions = ( 7889 self._parse_wrapped_id_vars() 7890 if not self._match(TokenType.REFERENCES, advance=False) 7891 else None 7892 ) 7893 reference = self._parse_references() 7894 on_options = {} 7895 7896 while self._match(TokenType.ON): 7897 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 7898 self.raise_error("Expected DELETE or UPDATE") 7899 7900 kind = self._prev.text.lower() 7901 7902 if self._match_text_seq("NO", "ACTION"): 7903 action = "NO ACTION" 7904 elif self._match(TokenType.SET): 7905 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 7906 action = "SET " + self._prev.text.upper() 7907 else: 7908 self._advance() 7909 action = self._prev.text.upper() 7910 7911 on_options[kind] = action 7912 7913 return self.expression( 7914 exp.ForeignKey( 7915 expressions=expressions, 7916 reference=reference, 7917 options=self._parse_key_constraint_options(), 7918 **on_options, 7919 ) 7920 ) 7921 7922 def _parse_primary_key_part(self) -> exp.Expr | None: 7923 return self._parse_field() 7924 7925 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 7926 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 7927 self._retreat(self._index - 1) 7928 return None 7929 7930 id_vars = self._parse_wrapped_id_vars() 7931 return self.expression( 7932 exp.PeriodForSystemTimeConstraint( 7933 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 7934 ) 7935 ) 7936 7937 def _parse_primary_key( 7938 self, 7939 wrapped_optional: bool = False, 7940 in_props: bool = False, 7941 named_primary_key: bool = False, 7942 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 7943 desc = ( 7944 self._prev.token_type == TokenType.DESC 7945 if self._match_set((TokenType.ASC, TokenType.DESC)) 7946 else None 7947 ) 7948 7949 this = None 7950 if ( 7951 named_primary_key 7952 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 7953 and self._next 7954 and self._next.token_type == TokenType.L_PAREN 7955 ): 7956 this = self._parse_id_var() 7957 7958 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 7959 return self.expression( 7960 exp.PrimaryKeyColumnConstraint( 7961 desc=desc, options=self._parse_key_constraint_options() 7962 ) 7963 ) 7964 7965 expressions = self._parse_wrapped_csv( 7966 self._parse_primary_key_part, optional=wrapped_optional 7967 ) 7968 7969 return self.expression( 7970 exp.PrimaryKey( 7971 this=this, 7972 expressions=expressions, 7973 include=self._parse_index_params(), 7974 options=self._parse_key_constraint_options(), 7975 ) 7976 ) 7977 7978 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 7979 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 7980 7981 def _parse_odbc_datetime_literal(self) -> exp.Expr: 7982 """ 7983 Parses a datetime column in ODBC format. We parse the column into the corresponding 7984 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 7985 same as we did for `DATE('yyyy-mm-dd')`. 7986 7987 Reference: 7988 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 7989 """ 7990 self._match(TokenType.VAR) 7991 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 7992 expression = self.expression(exp_class(this=self._parse_string())) 7993 if not self._match(TokenType.R_BRACE): 7994 self.raise_error("Expected }") 7995 return expression 7996 7997 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 7998 if not self._match_set(self.BRACKETS): 7999 return this 8000 8001 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8002 map_token = seq_get(self._tokens, self._index - 2) 8003 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8004 else: 8005 parse_map = False 8006 8007 bracket_kind = self._prev.token_type 8008 if ( 8009 bracket_kind == TokenType.L_BRACE 8010 and self._curr 8011 and self._curr.token_type == TokenType.VAR 8012 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8013 ): 8014 return self._parse_odbc_datetime_literal() 8015 8016 expressions = self._parse_csv( 8017 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8018 ) 8019 8020 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8021 self.raise_error("Expected ]") 8022 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8023 self.raise_error("Expected }") 8024 8025 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8026 if bracket_kind == TokenType.L_BRACE: 8027 this = self.expression( 8028 exp.Struct( 8029 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8030 ) 8031 ) 8032 elif not this: 8033 this = build_array_constructor( 8034 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8035 ) 8036 else: 8037 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8038 if constructor_type: 8039 return build_array_constructor( 8040 constructor_type, 8041 args=expressions, 8042 bracket_kind=bracket_kind, 8043 dialect=self.dialect, 8044 ) 8045 8046 expressions = apply_index_offset( 8047 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8048 ) 8049 this = self.expression( 8050 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8051 ) 8052 8053 self._add_comments(this) 8054 return self._parse_bracket(this) 8055 8056 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8057 if not self._match(TokenType.COLON): 8058 return this 8059 8060 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8061 self._advance() 8062 end: exp.Expr | None = -exp.Literal.number("1") 8063 else: 8064 end = self._parse_assignment() 8065 step = self._parse_unary() if self._match(TokenType.COLON) else None 8066 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8067 8068 def _parse_case(self) -> exp.Expr | None: 8069 if self._match(TokenType.DOT, advance=False): 8070 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8071 self._retreat(self._index - 1) 8072 return None 8073 8074 ifs = [] 8075 default = None 8076 8077 comments = self._prev_comments 8078 expression = self._parse_disjunction() 8079 8080 while self._match(TokenType.WHEN): 8081 this = self._parse_disjunction() 8082 self._match(TokenType.THEN) 8083 then = self._parse_disjunction() 8084 ifs.append(self.expression(exp.If(this=this, true=then))) 8085 8086 if self._match(TokenType.ELSE): 8087 default = self._parse_disjunction() 8088 8089 if not self._match(TokenType.END): 8090 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8091 default = exp.column("interval") 8092 else: 8093 self.raise_error("Expected END after CASE", self._prev) 8094 8095 return self.expression( 8096 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8097 ) 8098 8099 def _parse_if(self) -> exp.Expr | None: 8100 if self._match(TokenType.L_PAREN): 8101 args = self._parse_csv( 8102 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8103 ) 8104 this = self.validate_expression(exp.If.from_arg_list(args), args) 8105 self._match_r_paren() 8106 else: 8107 index = self._index - 1 8108 8109 if self.NO_PAREN_IF_COMMANDS and index == 0: 8110 return self._parse_as_command(self._prev) 8111 8112 condition = self._parse_disjunction() 8113 8114 if not condition: 8115 self._retreat(index) 8116 return None 8117 8118 self._match(TokenType.THEN) 8119 true = self._parse_disjunction() 8120 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8121 self._match(TokenType.END) 8122 this = self.expression(exp.If(this=condition, true=true, false=false)) 8123 8124 return this 8125 8126 def _parse_next_value_for(self) -> exp.Expr | None: 8127 if not self._match_text_seq("VALUE", "FOR"): 8128 self._retreat(self._index - 1) 8129 return None 8130 8131 return self.expression( 8132 exp.NextValueFor( 8133 this=self._parse_column(), 8134 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8135 ) 8136 ) 8137 8138 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8139 this = self._parse_function() or self._parse_var_or_string(upper=True) 8140 8141 if self._match(TokenType.FROM): 8142 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8143 8144 if not self._match(TokenType.COMMA): 8145 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8146 8147 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8148 8149 def _parse_gap_fill(self) -> exp.GapFill: 8150 self._match(TokenType.TABLE) 8151 this = self._parse_table() 8152 8153 self._match(TokenType.COMMA) 8154 args = [this, *self._parse_csv(self._parse_lambda)] 8155 8156 gap_fill = exp.GapFill.from_arg_list(args) 8157 return self.validate_expression(gap_fill, args) 8158 8159 def _parse_char(self) -> exp.Chr: 8160 return self.expression( 8161 exp.Chr( 8162 expressions=self._parse_csv(self._parse_assignment), 8163 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8164 ) 8165 ) 8166 8167 def _parse_charset_name(self) -> exp.Expr | None: 8168 """ 8169 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8170 for specific name shapes override this. 8171 """ 8172 return self._parse_var( 8173 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8174 ) 8175 8176 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8177 this = self._parse_assignment() 8178 8179 if not self._match(TokenType.ALIAS): 8180 if self._match(TokenType.COMMA): 8181 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8182 8183 self.raise_error("Expected AS after CAST") 8184 8185 fmt = None 8186 to = self._parse_types(with_collation=True) 8187 8188 default = None 8189 if self._match(TokenType.DEFAULT): 8190 default = self._parse_bitwise() 8191 self._match_text_seq("ON", "CONVERSION", "ERROR") 8192 8193 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8194 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8195 fmt = self._parse_at_time_zone(fmt_string) 8196 8197 if not to: 8198 to = exp.DType.UNKNOWN.into_expr() 8199 if to.this in exp.DataType.TEMPORAL_TYPES: 8200 this = self.expression( 8201 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8202 this=this, 8203 format=exp.Literal.string( 8204 format_time( 8205 fmt_string.this if fmt_string else "", 8206 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8207 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8208 ) 8209 ), 8210 safe=safe, 8211 ) 8212 ) 8213 8214 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8215 this.set("zone", fmt.args["zone"]) 8216 return this 8217 elif not to: 8218 self.raise_error("Expected TYPE after CAST") 8219 elif isinstance(to, exp.Identifier): 8220 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8221 elif to.this == exp.DType.CHAR and ( 8222 self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET") 8223 ): 8224 to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string()) 8225 8226 return self.build_cast( 8227 strict=strict, 8228 this=this, 8229 to=to, 8230 format=fmt, 8231 safe=safe, 8232 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8233 default=default, 8234 ) 8235 8236 def _parse_string_agg(self) -> exp.GroupConcat: 8237 if self._match(TokenType.DISTINCT): 8238 args: list[exp.Expr | None] = [ 8239 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8240 ] 8241 if self._match(TokenType.COMMA): 8242 args.extend(self._parse_csv(self._parse_disjunction)) 8243 else: 8244 args = self._parse_csv(self._parse_disjunction) # type: ignore 8245 8246 if self._match_text_seq("ON", "OVERFLOW"): 8247 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8248 if self._match_text_seq("ERROR"): 8249 on_overflow: exp.Expr | None = exp.var("ERROR") 8250 else: 8251 self._match_text_seq("TRUNCATE") 8252 on_overflow = self.expression( 8253 exp.OverflowTruncateBehavior( 8254 this=self._parse_string(), 8255 with_count=( 8256 self._match_text_seq("WITH", "COUNT") 8257 or not self._match_text_seq("WITHOUT", "COUNT") 8258 ), 8259 ) 8260 ) 8261 else: 8262 on_overflow = None 8263 8264 index = self._index 8265 if not self._match(TokenType.R_PAREN) and args: 8266 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8267 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8268 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8269 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8270 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8271 8272 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8273 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8274 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8275 if not self._match_text_seq("WITHIN", "GROUP"): 8276 self._retreat(index) 8277 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8278 8279 # The corresponding match_r_paren will be called in parse_function (caller) 8280 self._match_l_paren() 8281 8282 return self.expression( 8283 exp.GroupConcat( 8284 this=self._parse_order(this=seq_get(args, 0)), 8285 separator=seq_get(args, 1), 8286 on_overflow=on_overflow, 8287 ) 8288 ) 8289 8290 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8291 this = self._parse_bitwise() 8292 8293 if self._match(TokenType.USING): 8294 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8295 elif self._match(TokenType.COMMA): 8296 to = self._parse_types() 8297 else: 8298 to = None 8299 8300 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8301 8302 def _parse_xml_element(self) -> exp.XMLElement: 8303 if self._match_text_seq("EVALNAME"): 8304 evalname = True 8305 this = self._parse_bitwise() 8306 else: 8307 evalname = None 8308 self._match_text_seq("NAME") 8309 this = self._parse_id_var() 8310 8311 return self.expression( 8312 exp.XMLElement( 8313 this=this, 8314 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8315 evalname=evalname, 8316 ) 8317 ) 8318 8319 def _parse_xml_table(self) -> exp.XMLTable: 8320 namespaces = None 8321 passing = None 8322 columns = None 8323 8324 if self._match_text_seq("XMLNAMESPACES", "("): 8325 namespaces = self._parse_xml_namespace() 8326 self._match_text_seq(")", ",") 8327 8328 this = self._parse_string() 8329 8330 if self._match_text_seq("PASSING"): 8331 # The BY VALUE keywords are optional and are provided for semantic clarity 8332 self._match_text_seq("BY", "VALUE") 8333 passing = self._parse_csv(self._parse_column) 8334 8335 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8336 8337 if self._match_text_seq("COLUMNS"): 8338 columns = self._parse_csv(self._parse_field_def) 8339 8340 return self.expression( 8341 exp.XMLTable( 8342 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8343 ) 8344 ) 8345 8346 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8347 namespaces = [] 8348 8349 while True: 8350 if self._match(TokenType.DEFAULT): 8351 uri = self._parse_string() 8352 else: 8353 uri = self._parse_alias(self._parse_string()) 8354 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8355 if not self._match(TokenType.COMMA): 8356 break 8357 8358 return namespaces 8359 8360 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8361 args = self._parse_csv(self._parse_disjunction) 8362 8363 if len(args) < 3: 8364 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8365 8366 return self.expression(exp.DecodeCase(expressions=args)) 8367 8368 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8369 self._match_text_seq("KEY") 8370 key = self._parse_column() 8371 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8372 self._match_text_seq("VALUE") 8373 value = self._parse_bitwise() 8374 8375 if not key and not value: 8376 return None 8377 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8378 8379 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8380 if not this or not self._match_text_seq("FORMAT", "JSON"): 8381 return this 8382 8383 return self.expression(exp.FormatJson(this=this)) 8384 8385 def _parse_on_condition(self) -> exp.OnCondition | None: 8386 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8387 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8388 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8389 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8390 else: 8391 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8392 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8393 8394 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8395 8396 if not empty and not error and not null: 8397 return None 8398 8399 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8400 8401 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8402 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8403 for value in values: 8404 if self._match_text_seq(value, "ON", on): 8405 return f"{value} ON {on}" 8406 8407 index = self._index 8408 if self._match(TokenType.DEFAULT): 8409 default_value = self._parse_bitwise() 8410 if self._match_text_seq("ON", on): 8411 return default_value 8412 8413 self._retreat(index) 8414 8415 return None 8416 8417 @t.overload 8418 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8419 8420 @t.overload 8421 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8422 8423 def _parse_json_object(self, agg=False): 8424 star = self._parse_star() 8425 expressions = ( 8426 [star] 8427 if star 8428 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8429 ) 8430 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8431 8432 unique_keys = None 8433 if self._match_text_seq("WITH", "UNIQUE"): 8434 unique_keys = True 8435 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8436 unique_keys = False 8437 8438 self._match_text_seq("KEYS") 8439 8440 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8441 self._parse_type() 8442 ) 8443 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8444 8445 return self.expression( 8446 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8447 expressions=expressions, 8448 null_handling=null_handling, 8449 unique_keys=unique_keys, 8450 return_type=return_type, 8451 encoding=encoding, 8452 ) 8453 ) 8454 8455 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8456 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8457 if not self._match_text_seq("NESTED"): 8458 this = self._parse_id_var() 8459 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8460 kind = self._parse_types(allow_identifiers=False) 8461 nested = None 8462 else: 8463 this = None 8464 ordinality = None 8465 kind = None 8466 nested = True 8467 8468 format_json = self._match_text_seq("FORMAT", "JSON") 8469 path = self._match_text_seq("PATH") and self._parse_string() 8470 nested_schema = nested and self._parse_json_schema() 8471 8472 return self.expression( 8473 exp.JSONColumnDef( 8474 this=this, 8475 kind=kind, 8476 path=path, 8477 nested_schema=nested_schema, 8478 ordinality=ordinality, 8479 format_json=format_json, 8480 ) 8481 ) 8482 8483 def _parse_json_schema(self) -> exp.JSONSchema: 8484 self._match_text_seq("COLUMNS") 8485 return self.expression( 8486 exp.JSONSchema( 8487 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8488 ) 8489 ) 8490 8491 def _parse_json_table(self) -> exp.JSONTable: 8492 this = self._parse_format_json(self._parse_bitwise()) 8493 path = self._match(TokenType.COMMA) and self._parse_string() 8494 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8495 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8496 schema = self._parse_json_schema() 8497 8498 return exp.JSONTable( 8499 this=this, 8500 schema=schema, 8501 path=path, 8502 error_handling=error_handling, 8503 empty_handling=empty_handling, 8504 ) 8505 8506 def _parse_match_against(self) -> exp.MatchAgainst: 8507 if self._match_text_seq("TABLE"): 8508 # parse SingleStore MATCH(TABLE ...) syntax 8509 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8510 expressions = [] 8511 table = self._parse_table() 8512 if table: 8513 expressions = [table] 8514 else: 8515 expressions = self._parse_csv(self._parse_column) 8516 8517 self._match_text_seq(")", "AGAINST", "(") 8518 8519 this = self._parse_string() 8520 8521 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8522 modifier = "IN NATURAL LANGUAGE MODE" 8523 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8524 modifier = f"{modifier} WITH QUERY EXPANSION" 8525 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8526 modifier = "IN BOOLEAN MODE" 8527 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8528 modifier = "WITH QUERY EXPANSION" 8529 else: 8530 modifier = None 8531 8532 return self.expression( 8533 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8534 ) 8535 8536 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8537 def _parse_open_json(self) -> exp.OpenJSON: 8538 this = self._parse_bitwise() 8539 path = self._match(TokenType.COMMA) and self._parse_string() 8540 8541 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8542 this = self._parse_field(any_token=True) 8543 kind = self._parse_types() 8544 path = self._parse_string() 8545 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8546 8547 return self.expression( 8548 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8549 ) 8550 8551 expressions = None 8552 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8553 self._match_l_paren() 8554 expressions = self._parse_csv(_parse_open_json_column_def) 8555 8556 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8557 8558 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8559 args = self._parse_csv(self._parse_bitwise) 8560 8561 if self._match(TokenType.IN): 8562 return self.expression( 8563 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8564 ) 8565 8566 if haystack_first: 8567 haystack = seq_get(args, 0) 8568 needle = seq_get(args, 1) 8569 else: 8570 haystack = seq_get(args, 1) 8571 needle = seq_get(args, 0) 8572 8573 return self.expression( 8574 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8575 ) 8576 8577 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8578 args = self._parse_csv(self._parse_table) 8579 return exp.JoinHint(this=func_name.upper(), expressions=args) 8580 8581 def _parse_substring(self) -> exp.Substring: 8582 # Postgres supports the form: substring(string [from int] [for int]) 8583 # (despite being undocumented, the reverse order also works) 8584 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8585 8586 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8587 8588 start, length = None, None 8589 8590 while self._curr: 8591 if self._match(TokenType.FROM): 8592 start = self._parse_bitwise() 8593 elif self._match(TokenType.FOR): 8594 if not start: 8595 start = exp.Literal.number(1) 8596 length = self._parse_bitwise() 8597 else: 8598 break 8599 8600 if start: 8601 args.append(start) 8602 if length: 8603 args.append(length) 8604 8605 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8606 8607 def _parse_trim(self) -> exp.Trim: 8608 # https://www.w3resource.com/sql/character-functions/trim.php 8609 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8610 8611 position = None 8612 collation = None 8613 expression = None 8614 8615 if self._match_texts(self.TRIM_TYPES): 8616 position = self._prev.text.upper() 8617 8618 this = self._parse_bitwise() 8619 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8620 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8621 expression = self._parse_bitwise() 8622 8623 if invert_order: 8624 this, expression = expression, this 8625 8626 if self._match(TokenType.COLLATE): 8627 collation = self._parse_bitwise() 8628 8629 return self.expression( 8630 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8631 ) 8632 8633 def _parse_window_clause(self) -> list[exp.Expr] | None: 8634 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8635 8636 def _parse_named_window(self) -> exp.Expr | None: 8637 return self._parse_window(self._parse_id_var(), alias=True) 8638 8639 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8640 if self._curr.token_type == TokenType.VAR: 8641 if self._match_text_seq("IGNORE", "NULLS"): 8642 return self.expression(exp.IgnoreNulls(this=this)) 8643 if self._match_text_seq("RESPECT", "NULLS"): 8644 return self.expression(exp.RespectNulls(this=this)) 8645 return this 8646 8647 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8648 if self._match(TokenType.HAVING): 8649 self._match_texts(("MAX", "MIN")) 8650 max = self._prev.text.upper() != "MIN" 8651 return self.expression( 8652 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8653 ) 8654 8655 return this 8656 8657 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8658 func = this 8659 comments = func.comments if isinstance(func, exp.Expr) else None 8660 8661 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8662 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8663 if self._match_text_seq("FROM", "FIRST"): 8664 this.set("from_first", True) 8665 elif self._match_text_seq("FROM", "LAST"): 8666 this.set("from_first", False) 8667 8668 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8669 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8670 if self._match_text_seq("WITHIN", "GROUP"): 8671 order = self._parse_wrapped(self._parse_order) 8672 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8673 8674 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8675 self._match(TokenType.WHERE) 8676 this = self.expression( 8677 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8678 ) 8679 self._match_r_paren() 8680 8681 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8682 # Some dialects choose to implement and some do not. 8683 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8684 8685 # There is some code above in _parse_lambda that handles 8686 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8687 8688 # The below changes handle 8689 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8690 8691 # Oracle allows both formats 8692 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8693 # and Snowflake chose to do the same for familiarity 8694 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8695 if isinstance(this, exp.AggFunc): 8696 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8697 8698 if ignore_respect and ignore_respect is not this: 8699 ignore_respect.replace(ignore_respect.this) 8700 this = self.expression(ignore_respect.__class__(this=this)) 8701 8702 this = self._parse_respect_or_ignore_nulls(this) 8703 8704 # bigquery select from window x AS (partition by ...) 8705 if alias: 8706 over = None 8707 self._match(TokenType.ALIAS) 8708 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8709 return this 8710 else: 8711 over = self._prev.text.upper() 8712 8713 if comments and isinstance(func, exp.Expr): 8714 func.pop_comments() 8715 8716 if not self._match(TokenType.L_PAREN): 8717 return self.expression( 8718 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8719 ) 8720 8721 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8722 8723 first: bool | None = True if self._match(TokenType.FIRST) else None 8724 if self._match_text_seq("LAST"): 8725 first = False 8726 8727 partition, order = self._parse_partition_and_order() 8728 kind = ( 8729 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8730 ) and self._prev.text 8731 8732 if kind: 8733 self._match(TokenType.BETWEEN) 8734 start = self._parse_window_spec() 8735 8736 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8737 exclude = ( 8738 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8739 if self._match_text_seq("EXCLUDE") 8740 else None 8741 ) 8742 8743 spec = self.expression( 8744 exp.WindowSpec( 8745 kind=kind, 8746 start=start["value"], 8747 start_side=start["side"], 8748 end=end.get("value"), 8749 end_side=end.get("side"), 8750 exclude=exclude, 8751 ) 8752 ) 8753 else: 8754 spec = None 8755 8756 self._match_r_paren() 8757 8758 window = self.expression( 8759 exp.Window( 8760 this=this, 8761 partition_by=partition, 8762 order=order, 8763 spec=spec, 8764 alias=window_alias, 8765 over=over, 8766 first=first, 8767 ), 8768 comments=comments, 8769 ) 8770 8771 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 8772 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 8773 return self._parse_window(window, alias=alias) 8774 8775 return window 8776 8777 def _parse_partition_and_order( 8778 self, 8779 ) -> tuple[list[exp.Expr], exp.Expr | None]: 8780 return self._parse_partition_by(), self._parse_order() 8781 8782 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 8783 self._match(TokenType.BETWEEN) 8784 8785 return { 8786 "value": ( 8787 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 8788 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 8789 or self._parse_bitwise() 8790 ), 8791 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 8792 } 8793 8794 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 8795 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 8796 # so this section tries to parse the clause version and if it fails, it treats the token 8797 # as an identifier (alias) 8798 if self._can_parse_limit_or_offset(): 8799 return this 8800 8801 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 8802 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 8803 if self._can_parse_named_window(): 8804 return this 8805 8806 any_token = self._match(TokenType.ALIAS) 8807 comments = self._prev_comments 8808 8809 if explicit and not any_token: 8810 return this 8811 8812 if self._match(TokenType.L_PAREN): 8813 aliases = self.expression( 8814 exp.Aliases( 8815 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 8816 ), 8817 comments=comments, 8818 ) 8819 self._match_r_paren(aliases) 8820 return aliases 8821 8822 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 8823 self.STRING_ALIASES and self._parse_string_as_identifier() 8824 ) 8825 8826 if alias: 8827 comments.extend(alias.pop_comments()) 8828 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 8829 column = this.this 8830 8831 # Moves the comment next to the alias in `expr /* comment */ AS alias` 8832 if not this.comments and column and column.comments: 8833 this.comments = column.pop_comments() 8834 8835 return this 8836 8837 def _parse_id_var( 8838 self, 8839 any_token: bool = True, 8840 tokens: t.Collection[TokenType] | None = None, 8841 ) -> exp.Expr | None: 8842 expression = self._parse_identifier() 8843 if not expression and ( 8844 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 8845 ): 8846 quoted = self._prev.token_type == TokenType.STRING 8847 expression = self._identifier_expression(quoted=quoted) 8848 8849 return expression 8850 8851 def _parse_string(self) -> exp.Expr | None: 8852 if self._match_set(self.STRING_PARSERS): 8853 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 8854 return self._parse_placeholder() 8855 8856 def _parse_string_as_identifier(self) -> exp.Identifier | None: 8857 if not self._match(TokenType.STRING): 8858 return None 8859 output = exp.to_identifier(self._prev.text, quoted=True) 8860 output.update_positions(self._prev) 8861 return output 8862 8863 def _parse_number(self) -> exp.Expr | None: 8864 if self._match_set(self.NUMERIC_PARSERS): 8865 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 8866 return self._parse_placeholder() 8867 8868 def _parse_identifier(self) -> exp.Expr | None: 8869 if self._match(TokenType.IDENTIFIER): 8870 return self._identifier_expression(quoted=True) 8871 return self._parse_placeholder() 8872 8873 def _parse_var( 8874 self, 8875 any_token: bool = False, 8876 tokens: t.Collection[TokenType] | None = None, 8877 upper: bool = False, 8878 ) -> exp.Expr | None: 8879 if ( 8880 (any_token and self._advance_any()) 8881 or self._match(TokenType.VAR) 8882 or (self._match_set(tokens) if tokens else False) 8883 ): 8884 return self.expression( 8885 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 8886 ) 8887 return self._parse_placeholder() 8888 8889 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 8890 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 8891 self._advance() 8892 return self._prev 8893 return None 8894 8895 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 8896 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 8897 8898 def _parse_primary_or_var(self) -> exp.Expr | None: 8899 return self._parse_primary() or self._parse_var(any_token=True) 8900 8901 def _parse_null(self) -> exp.Expr | None: 8902 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 8903 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 8904 return self._parse_placeholder() 8905 8906 def _parse_boolean(self) -> exp.Expr | None: 8907 if self._match(TokenType.TRUE): 8908 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 8909 if self._match(TokenType.FALSE): 8910 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 8911 return self._parse_placeholder() 8912 8913 def _parse_star(self) -> exp.Expr | None: 8914 if self._match(TokenType.STAR): 8915 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 8916 return self._parse_placeholder() 8917 8918 def _parse_parameter(self) -> exp.Parameter: 8919 this = self._parse_identifier() or self._parse_primary_or_var() 8920 return self.expression(exp.Parameter(this=this)) 8921 8922 def _parse_placeholder(self) -> exp.Expr | None: 8923 if self._match_set(self.PLACEHOLDER_PARSERS): 8924 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 8925 if placeholder: 8926 return placeholder 8927 self._advance(-1) 8928 return None 8929 8930 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 8931 if not self._match_texts(keywords): 8932 return None 8933 if self._match(TokenType.L_PAREN, advance=False): 8934 return self._parse_wrapped_csv(self._parse_expression) 8935 8936 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 8937 return [expression] if expression else None 8938 8939 def _parse_csv( 8940 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 8941 ) -> list[T]: 8942 parse_result = parse_method() 8943 items = [parse_result] if parse_result is not None else [] 8944 8945 while self._match(sep): 8946 if isinstance(parse_result, exp.Expr): 8947 self._add_comments(parse_result) 8948 parse_result = parse_method() 8949 if parse_result is not None: 8950 items.append(parse_result) 8951 8952 return items 8953 8954 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 8955 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 8956 8957 def _parse_wrapped_csv( 8958 self, 8959 parse_method: t.Callable[[], T | None], 8960 sep: TokenType = TokenType.COMMA, 8961 optional: bool = False, 8962 ) -> list[T]: 8963 return self._parse_wrapped( 8964 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 8965 ) 8966 8967 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 8968 wrapped = self._match(TokenType.L_PAREN) 8969 if not wrapped and not optional: 8970 self.raise_error("Expecting (") 8971 parse_result = parse_method() 8972 if wrapped: 8973 self._match_r_paren() 8974 return parse_result 8975 8976 def _parse_expressions(self) -> list[exp.Expr]: 8977 return self._parse_csv(self._parse_expression) 8978 8979 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 8980 return ( 8981 self._parse_set_operations( 8982 self._parse_alias(self._parse_assignment(), explicit=True) 8983 if alias 8984 else self._parse_assignment() 8985 ) 8986 or self._parse_select() 8987 ) 8988 8989 def _parse_ddl_select(self) -> exp.Expr | None: 8990 return self._parse_query_modifiers( 8991 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 8992 ) 8993 8994 def _parse_transaction(self) -> exp.Transaction | exp.Command: 8995 this = None 8996 if self._match_texts(self.TRANSACTION_KIND): 8997 this = self._prev.text 8998 8999 self._match_texts(("TRANSACTION", "WORK")) 9000 9001 modes = [] 9002 while True: 9003 mode = [] 9004 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 9005 mode.append(self._prev.text) 9006 9007 if mode: 9008 modes.append(" ".join(mode)) 9009 if not self._match(TokenType.COMMA): 9010 break 9011 9012 return self.expression(exp.Transaction(this=this, modes=modes)) 9013 9014 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 9015 chain = None 9016 savepoint = None 9017 is_rollback = self._prev.token_type == TokenType.ROLLBACK 9018 9019 self._match_texts(("TRANSACTION", "WORK")) 9020 9021 if self._match_text_seq("TO"): 9022 self._match_text_seq("SAVEPOINT") 9023 savepoint = self._parse_id_var() 9024 9025 if self._match(TokenType.AND): 9026 chain = not self._match_text_seq("NO") 9027 self._match_text_seq("CHAIN") 9028 9029 if is_rollback: 9030 return self.expression(exp.Rollback(savepoint=savepoint)) 9031 9032 return self.expression(exp.Commit(chain=chain)) 9033 9034 def _parse_refresh(self) -> exp.Refresh | exp.Command: 9035 if self._match_text_seq("EXTERNAL", "TABLE"): 9036 kind = "EXTERNAL TABLE" 9037 elif self._match(TokenType.TABLE): 9038 kind = "TABLE" 9039 elif self._match_text_seq("MATERIALIZED", "VIEW"): 9040 kind = "MATERIALIZED VIEW" 9041 else: 9042 kind = "" 9043 9044 this = self._parse_string() or self._parse_table() 9045 if not kind and not isinstance(this, exp.Literal): 9046 return self._parse_as_command(self._prev) 9047 9048 return self.expression(exp.Refresh(this=this, kind=kind)) 9049 9050 def _parse_column_def_with_exists(self): 9051 start = self._index 9052 self._match(TokenType.COLUMN) 9053 9054 exists_column = self._parse_exists(not_=True) 9055 expression = self._parse_field_def() 9056 9057 if not isinstance(expression, exp.ColumnDef): 9058 self._retreat(start) 9059 return None 9060 9061 expression.set("exists", exists_column) 9062 9063 return expression 9064 9065 def _parse_add_column(self) -> exp.ColumnDef | None: 9066 if not self._prev.text.upper() == "ADD": 9067 return None 9068 9069 return self._parse_column_def_with_exists() 9070 9071 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9072 drop = self._parse_drop() if self._match(TokenType.DROP) else None 9073 if drop and not isinstance(drop, exp.Command): 9074 drop.set("kind", drop.args.get("kind", "COLUMN")) 9075 return drop 9076 9077 def _parse_alter_drop_action(self) -> exp.Expr | None: 9078 return self._parse_drop_column() 9079 9080 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9081 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9082 return self.expression( 9083 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9084 ) 9085 9086 def _parse_alter_table_add(self) -> list[exp.Expr]: 9087 def _parse_add_alteration() -> exp.Expr | None: 9088 self._match_text_seq("ADD") 9089 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False): 9090 return self.expression( 9091 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9092 ) 9093 9094 column_def = self._parse_add_column() 9095 if isinstance(column_def, exp.ColumnDef): 9096 return column_def 9097 9098 exists = self._parse_exists(not_=True) 9099 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9100 return self.expression( 9101 exp.AddPartition( 9102 exists=exists, 9103 this=self._parse_field(any_token=True), 9104 location=self._match_text_seq("LOCATION", advance=False) 9105 and self._parse_property(), 9106 ) 9107 ) 9108 9109 return None 9110 9111 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9112 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9113 or self._match_text_seq("COLUMNS") 9114 ): 9115 schema = self._parse_schema() 9116 9117 return ( 9118 ensure_list(schema) 9119 if schema 9120 else self._parse_csv(self._parse_column_def_with_exists) 9121 ) 9122 9123 return self._parse_csv(_parse_add_alteration) 9124 9125 def _parse_alter_table_alter(self) -> exp.Expr | None: 9126 if self._match_texts(self.ALTER_ALTER_PARSERS): 9127 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9128 9129 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9130 # keyword after ALTER we default to parsing this statement 9131 self._match(TokenType.COLUMN) 9132 exists = self._parse_exists() 9133 column = self._parse_field(any_token=True) 9134 9135 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9136 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9137 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9138 return self.expression( 9139 exp.AlterColumn( 9140 this=column, default=self._parse_disjunction(), exists=exists or None 9141 ) 9142 ) 9143 if self._match(TokenType.COMMENT): 9144 return self.expression( 9145 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9146 ) 9147 if self._match_text_seq("DROP", "NOT", "NULL"): 9148 return self.expression( 9149 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9150 ) 9151 if self._match_text_seq("SET", "NOT", "NULL"): 9152 return self.expression( 9153 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9154 ) 9155 9156 if self._match_text_seq("SET", "VISIBLE"): 9157 return self.expression( 9158 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9159 ) 9160 if self._match_text_seq("SET", "INVISIBLE"): 9161 return self.expression( 9162 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9163 ) 9164 9165 self._match_text_seq("SET", "DATA") 9166 self._match_text_seq("TYPE") 9167 return self.expression( 9168 exp.AlterColumn( 9169 this=column, 9170 dtype=self._parse_types(), 9171 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9172 using=self._match(TokenType.USING) and self._parse_disjunction(), 9173 exists=exists or None, 9174 ) 9175 ) 9176 9177 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9178 if self._match_texts(("ALL", "EVEN", "AUTO")): 9179 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9180 9181 self._match_text_seq("KEY", "DISTKEY") 9182 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9183 9184 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9185 if compound: 9186 self._match_text_seq("SORTKEY") 9187 9188 if self._match(TokenType.L_PAREN, advance=False): 9189 return self.expression( 9190 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9191 ) 9192 9193 self._match_texts(("AUTO", "NONE")) 9194 return self.expression( 9195 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9196 ) 9197 9198 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9199 index = self._index - 1 9200 9201 partition_exists = self._parse_exists() 9202 if self._match(TokenType.PARTITION, advance=False): 9203 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9204 9205 self._retreat(index) 9206 return self._parse_csv(self._parse_alter_drop_action) 9207 9208 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9209 if self._match(TokenType.COLUMN) or ( 9210 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9211 ): 9212 exists = self._parse_exists() 9213 old_column = self._parse_column() 9214 to = self._match_text_seq("TO") 9215 new_column = self._parse_column() 9216 9217 if old_column is None or not to or new_column is None: 9218 return None 9219 9220 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9221 9222 self._match_text_seq("TO") 9223 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9224 9225 def _parse_alter_table_set(self) -> exp.AlterSet: 9226 alter_set = self.expression(exp.AlterSet()) 9227 9228 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9229 "TABLE", "PROPERTIES" 9230 ): 9231 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9232 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9233 alter_set.set("expressions", [self._parse_assignment()]) 9234 elif self._match_texts(("LOGGED", "UNLOGGED")): 9235 alter_set.set("option", exp.var(self._prev.text.upper())) 9236 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9237 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9238 elif self._match_text_seq("LOCATION"): 9239 alter_set.set("location", self._parse_field()) 9240 elif self._match_text_seq("ACCESS", "METHOD"): 9241 alter_set.set("access_method", self._parse_field()) 9242 elif self._match_text_seq("TABLESPACE"): 9243 alter_set.set("tablespace", self._parse_field()) 9244 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9245 alter_set.set("file_format", [self._parse_field()]) 9246 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9247 alter_set.set("file_format", self._parse_wrapped_options()) 9248 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9249 alter_set.set("copy_options", self._parse_wrapped_options()) 9250 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9251 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9252 else: 9253 if self._match_text_seq("SERDE"): 9254 alter_set.set("serde", self._parse_field()) 9255 9256 properties = self._parse_wrapped(self._parse_properties, optional=True) 9257 alter_set.set("expressions", [properties]) 9258 9259 return alter_set 9260 9261 def _parse_alter_session(self) -> exp.AlterSession: 9262 """Parse ALTER SESSION SET/UNSET statements.""" 9263 if self._match(TokenType.SET): 9264 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9265 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9266 9267 self._match_text_seq("UNSET") 9268 expressions = self._parse_csv( 9269 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9270 ) 9271 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9272 9273 def _parse_alter(self) -> exp.Alter | exp.Command: 9274 start = self._prev 9275 9276 iceberg = self._match_text_seq("ICEBERG") 9277 9278 alter_token = self._match_set(self.ALTERABLES) and self._prev 9279 if not alter_token: 9280 return self._parse_as_command(start) 9281 if iceberg and alter_token.token_type != TokenType.TABLE: 9282 return self._parse_as_command(start) 9283 9284 exists = self._parse_exists() 9285 only = self._match_text_seq("ONLY") 9286 9287 if alter_token.token_type == TokenType.SESSION: 9288 this = None 9289 check = None 9290 cluster = None 9291 else: 9292 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9293 check = self._match_text_seq("WITH", "CHECK") 9294 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9295 9296 if self._next: 9297 self._advance() 9298 9299 parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None 9300 if parser: 9301 actions = ensure_list(parser(self)) 9302 not_valid = self._match_text_seq("NOT", "VALID") 9303 options = self._parse_csv(self._parse_property) 9304 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9305 9306 if not self._curr and actions: 9307 return self.expression( 9308 exp.Alter( 9309 this=this, 9310 kind=alter_token.text.upper(), 9311 exists=exists, 9312 actions=actions, 9313 only=only, 9314 options=options, 9315 cluster=cluster, 9316 not_valid=not_valid, 9317 check=check, 9318 cascade=cascade, 9319 iceberg=iceberg, 9320 ) 9321 ) 9322 9323 return self._parse_as_command(start) 9324 9325 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9326 start = self._prev 9327 # https://duckdb.org/docs/sql/statements/analyze 9328 if not self._curr: 9329 return self.expression(exp.Analyze()) 9330 9331 options = [] 9332 while self._match_texts(self.ANALYZE_STYLES): 9333 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9334 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9335 else: 9336 options.append(self._prev.text.upper()) 9337 9338 tables: exp.Expr | list[exp.Expr] | None = None 9339 inner_expression: exp.Expr | None = None 9340 9341 kind = self._curr.text.upper() if self._curr else None 9342 9343 if self._match(TokenType.TABLE): 9344 tables = self._parse_csv(self._parse_table_parts) 9345 elif self._match(TokenType.INDEX): 9346 tables = self._parse_table_parts() 9347 elif self._match_text_seq("TABLES"): 9348 if self._match_set((TokenType.FROM, TokenType.IN)): 9349 kind = f"{kind} {self._prev.text.upper()}" 9350 tables = self._parse_table(schema=True, is_db_reference=True) 9351 elif self._match_text_seq("DATABASE"): 9352 tables = self._parse_table(schema=True, is_db_reference=True) 9353 elif self._match_text_seq("CLUSTER"): 9354 tables = self._parse_table() 9355 # Try matching inner expr keywords before fallback to parse table. 9356 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9357 kind = None 9358 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9359 else: 9360 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9361 kind = None 9362 tables = self._parse_csv(self._parse_table_parts) 9363 9364 partition = self._try_parse(self._parse_partition) 9365 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9366 return self._parse_as_command(start) 9367 9368 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9369 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9370 "WITH", "ASYNC", "MODE" 9371 ): 9372 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9373 else: 9374 mode = None 9375 9376 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9377 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9378 9379 properties = self._parse_properties() 9380 return self.expression( 9381 exp.Analyze( 9382 kind=kind, 9383 tables=ensure_list(tables), 9384 mode=mode, 9385 partition=partition, 9386 properties=properties, 9387 expression=inner_expression, 9388 options=options, 9389 ) 9390 ) 9391 9392 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9393 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9394 this = None 9395 kind = self._prev.text.upper() 9396 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9397 expressions = [] 9398 9399 if not self._match_text_seq("STATISTICS"): 9400 self.raise_error("Expecting token STATISTICS") 9401 9402 if self._match_text_seq("NOSCAN"): 9403 this = "NOSCAN" 9404 elif self._match(TokenType.FOR): 9405 if self._match_text_seq("ALL", "COLUMNS"): 9406 this = "FOR ALL COLUMNS" 9407 if self._match_text_seq("COLUMNS"): 9408 this = "FOR COLUMNS" 9409 expressions = self._parse_csv(self._parse_column_reference) 9410 elif self._match_text_seq("SAMPLE"): 9411 sample = self._parse_number() 9412 expressions = [ 9413 self.expression( 9414 exp.AnalyzeSample( 9415 sample=sample, 9416 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9417 ) 9418 ) 9419 ] 9420 9421 return self.expression( 9422 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9423 ) 9424 9425 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9426 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9427 kind = None 9428 this = None 9429 expression: exp.Expr | None = None 9430 if self._match_text_seq("REF", "UPDATE"): 9431 kind = "REF" 9432 this = "UPDATE" 9433 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9434 this = "UPDATE SET DANGLING TO NULL" 9435 elif self._match_text_seq("STRUCTURE"): 9436 kind = "STRUCTURE" 9437 if self._match_text_seq("CASCADE", "FAST"): 9438 this = "CASCADE FAST" 9439 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9440 ("ONLINE", "OFFLINE") 9441 ): 9442 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9443 expression = self._parse_into() 9444 9445 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9446 9447 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9448 this = self._prev.text.upper() 9449 if self._match_text_seq("COLUMNS"): 9450 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9451 return None 9452 9453 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9454 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9455 if self._match_text_seq("STATISTICS"): 9456 return self.expression(exp.AnalyzeDelete(kind=kind)) 9457 return None 9458 9459 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9460 if self._match_text_seq("CHAINED", "ROWS"): 9461 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9462 return None 9463 9464 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9465 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9466 this = self._prev.text.upper() 9467 expression: exp.Expr | None = None 9468 expressions = [] 9469 update_options = None 9470 9471 if self._match_text_seq("HISTOGRAM", "ON"): 9472 expressions = self._parse_csv(self._parse_column_reference) 9473 with_expressions = [] 9474 while self._match(TokenType.WITH): 9475 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9476 if self._match_texts(("SYNC", "ASYNC")): 9477 if self._match_text_seq("MODE", advance=False): 9478 with_expressions.append(f"{self._prev.text.upper()} MODE") 9479 self._advance() 9480 else: 9481 buckets = self._parse_number() 9482 if self._match_text_seq("BUCKETS"): 9483 with_expressions.append(f"{buckets} BUCKETS") 9484 if with_expressions: 9485 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9486 9487 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9488 TokenType.UPDATE, advance=False 9489 ): 9490 update_options = self._prev.text.upper() 9491 self._advance() 9492 elif self._match_text_seq("USING", "DATA"): 9493 expression = self.expression(exp.UsingData(this=self._parse_string())) 9494 9495 return self.expression( 9496 exp.AnalyzeHistogram( 9497 this=this, 9498 expressions=expressions, 9499 expression=expression, 9500 update_options=update_options, 9501 ) 9502 ) 9503 9504 def _parse_merge(self) -> exp.Merge: 9505 self._match(TokenType.INTO) 9506 target = self._parse_table() 9507 9508 if target and self._match(TokenType.ALIAS, advance=False): 9509 target.set("alias", self._parse_table_alias()) 9510 9511 self._match(TokenType.USING) 9512 using = self._parse_table() 9513 9514 return self.expression( 9515 exp.Merge( 9516 this=target, 9517 using=using, 9518 on=self._match(TokenType.ON) and self._parse_disjunction(), 9519 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9520 whens=self._parse_when_matched(), 9521 returning=self._parse_returning(), 9522 ) 9523 ) 9524 9525 def _parse_when_matched(self) -> exp.Whens: 9526 whens = [] 9527 9528 while self._match(TokenType.WHEN): 9529 matched = not self._match(TokenType.NOT) 9530 self._match_text_seq("MATCHED") 9531 source = ( 9532 False 9533 if self._match_text_seq("BY", "TARGET") 9534 else self._match_text_seq("BY", "SOURCE") 9535 ) 9536 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9537 9538 self._match(TokenType.THEN) 9539 9540 if self._match(TokenType.INSERT): 9541 this = self._parse_star() 9542 if this: 9543 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9544 else: 9545 then = self.expression( 9546 exp.Insert( 9547 this=exp.var("ROW") 9548 if self._match_text_seq("ROW") 9549 else self._parse_value(values=False), 9550 expression=self._match_text_seq("VALUES") and self._parse_value(), 9551 where=self._parse_where(), 9552 ) 9553 ) 9554 elif self._match(TokenType.UPDATE): 9555 expressions = self._parse_star() 9556 if expressions: 9557 then = self.expression(exp.Update(expressions=expressions)) 9558 else: 9559 then = self.expression( 9560 exp.Update( 9561 expressions=self._match(TokenType.SET) 9562 and self._parse_csv(self._parse_equality), 9563 where=self._parse_where(), 9564 ) 9565 ) 9566 elif self._match(TokenType.DELETE): 9567 then = self.expression(exp.Var(this=self._prev.text)) 9568 else: 9569 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9570 9571 whens.append( 9572 self.expression( 9573 exp.When(matched=matched, source=source, condition=condition, then=then) 9574 ) 9575 ) 9576 return self.expression(exp.Whens(expressions=whens)) 9577 9578 def _parse_show(self) -> exp.Expr | None: 9579 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9580 if parser: 9581 return parser(self) 9582 return self._parse_as_command(self._prev) 9583 9584 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9585 index = self._index 9586 9587 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9588 return self._parse_set_transaction(global_=kind == "GLOBAL") 9589 9590 left = self._parse_primary() or self._parse_column() 9591 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 9592 9593 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 9594 self._retreat(index) 9595 return None 9596 9597 right = self._parse_statement() or self._parse_id_var() 9598 if isinstance(right, (exp.Column, exp.Identifier)): 9599 right = exp.var(right.name) 9600 9601 this = self.expression(exp.EQ(this=left, expression=right)) 9602 return self.expression(exp.SetItem(this=this, kind=kind)) 9603 9604 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 9605 self._match_text_seq("TRANSACTION") 9606 characteristics = self._parse_csv( 9607 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 9608 ) 9609 return self.expression( 9610 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 9611 ) 9612 9613 def _parse_set_item(self) -> exp.Expr | None: 9614 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 9615 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 9616 9617 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 9618 index = self._index 9619 set_ = self.expression( 9620 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 9621 ) 9622 9623 if self._curr: 9624 self._retreat(index) 9625 return self._parse_as_command(self._prev) 9626 9627 return set_ 9628 9629 def _parse_var_from_options( 9630 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 9631 ) -> exp.Var | None: 9632 start = self._curr 9633 if not start: 9634 return None 9635 9636 option = start.text.upper() 9637 continuations = ( 9638 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 9639 ) 9640 9641 index = self._index 9642 self._advance() 9643 for keywords in continuations or []: 9644 if isinstance(keywords, str): 9645 keywords = (keywords,) 9646 9647 if self._match_text_seq(*keywords): 9648 option = f"{option} {' '.join(keywords)}" 9649 break 9650 else: 9651 if continuations or continuations is None: 9652 if raise_unmatched: 9653 self.raise_error(f"Unknown option {option}") 9654 9655 self._retreat(index) 9656 return None 9657 9658 return exp.var(option) 9659 9660 def _parse_as_command(self, start: Token) -> exp.Command: 9661 while self._curr: 9662 self._advance() 9663 text = self._find_sql(start, self._prev) 9664 size = len(start.text) 9665 self._warn_unsupported() 9666 return exp.Command(this=text[:size], expression=text[size:]) 9667 9668 def _parse_dict_property(self, this: str) -> exp.DictProperty: 9669 settings = [] 9670 9671 self._match_l_paren() 9672 kind = self._parse_id_var() 9673 9674 if self._match(TokenType.L_PAREN): 9675 while True: 9676 key = self._parse_id_var() 9677 value = self._parse_function() or self._parse_primary_or_var() 9678 if not key and value is None: 9679 break 9680 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 9681 self._match(TokenType.R_PAREN) 9682 9683 self._match_r_paren() 9684 9685 return self.expression( 9686 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 9687 ) 9688 9689 def _parse_dict_range(self, this: str) -> exp.DictRange: 9690 self._match_l_paren() 9691 has_min = self._match_text_seq("MIN") 9692 if has_min: 9693 min = self._parse_var() or self._parse_primary() 9694 self._match_text_seq("MAX") 9695 max = self._parse_var() or self._parse_primary() 9696 else: 9697 max = self._parse_var() or self._parse_primary() 9698 min = exp.Literal.number(0) 9699 self._match_r_paren() 9700 return self.expression(exp.DictRange(this=this, min=min, max=max)) 9701 9702 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 9703 index = self._index 9704 expression = self._parse_column() 9705 position = self._match(TokenType.COMMA) and self._parse_column() 9706 9707 if not self._match(TokenType.IN): 9708 self._retreat(index - 1) 9709 return None 9710 iterator = self._parse_column() 9711 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 9712 return self.expression( 9713 exp.Comprehension( 9714 this=this, 9715 expression=expression, 9716 position=position, 9717 iterator=iterator, 9718 condition=condition, 9719 ) 9720 ) 9721 9722 def _parse_heredoc(self) -> exp.Heredoc | None: 9723 if self._match(TokenType.HEREDOC_STRING): 9724 return self.expression(exp.Heredoc(this=self._prev.text)) 9725 9726 if not self._match_text_seq("$"): 9727 return None 9728 9729 tags = ["$"] 9730 tag_text = None 9731 9732 if self._is_connected(): 9733 self._advance() 9734 tags.append(self._prev.text.upper()) 9735 else: 9736 self.raise_error("No closing $ found") 9737 9738 if tags[-1] != "$": 9739 if self._is_connected() and self._match_text_seq("$"): 9740 tag_text = tags[-1] 9741 tags.append("$") 9742 else: 9743 self.raise_error("No closing $ found") 9744 9745 heredoc_start = self._curr 9746 9747 while self._curr: 9748 if self._match_text_seq(*tags, advance=False): 9749 this = self._find_sql(heredoc_start, self._prev) 9750 self._advance(len(tags)) 9751 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 9752 9753 self._advance() 9754 9755 self.raise_error(f"No closing {''.join(tags)} found") 9756 return None 9757 9758 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 9759 if not self._curr: 9760 return None 9761 9762 index = self._index 9763 this = [] 9764 while True: 9765 # The current token might be multiple words 9766 curr = self._curr.text.upper() 9767 key = curr.split(" ") 9768 this.append(curr) 9769 9770 self._advance() 9771 result, trie = in_trie(trie, key) 9772 if result == TrieResult.FAILED: 9773 break 9774 9775 if result == TrieResult.EXISTS: 9776 subparser = parsers[" ".join(this)] 9777 return subparser 9778 9779 self._retreat(index) 9780 return None 9781 9782 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 9783 if not self._match(TokenType.L_PAREN, expression=expression): 9784 self.raise_error("Expecting (") 9785 9786 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 9787 if not self._match(TokenType.R_PAREN, expression=expression): 9788 self.raise_error("Expecting )") 9789 9790 def _replace_lambda( 9791 self, node: exp.Expr | None, expressions: list[exp.Expr] 9792 ) -> exp.Expr | None: 9793 if not node: 9794 return node 9795 9796 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 9797 9798 for column in node.find_all(exp.Column): 9799 typ = lambda_types.get(column.parts[0].name) 9800 if typ is not None: 9801 dot_or_id = column.to_dot() if column.table else column.this 9802 9803 if typ: 9804 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ)) 9805 9806 parent = column.parent 9807 9808 while isinstance(parent, exp.Dot): 9809 if not isinstance(parent.parent, exp.Dot): 9810 parent.replace(dot_or_id) 9811 break 9812 parent = parent.parent 9813 else: 9814 if column is node: 9815 node = dot_or_id 9816 else: 9817 column.replace(dot_or_id) 9818 return node 9819 9820 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 9821 start = self._prev 9822 9823 # Not to be confused with TRUNCATE(number, decimals) function call 9824 if self._match(TokenType.L_PAREN): 9825 self._retreat(self._index - 2) 9826 return self._parse_function() 9827 9828 # Clickhouse supports TRUNCATE DATABASE as well 9829 is_database = self._match(TokenType.DATABASE) 9830 9831 self._match(TokenType.TABLE) 9832 9833 exists = self._parse_exists(not_=False) 9834 9835 expressions = self._parse_csv( 9836 lambda: self._parse_table(schema=True, is_db_reference=is_database) 9837 ) 9838 9839 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9840 9841 if self._match_text_seq("RESTART", "IDENTITY"): 9842 identity = "RESTART" 9843 elif self._match_text_seq("CONTINUE", "IDENTITY"): 9844 identity = "CONTINUE" 9845 else: 9846 identity = None 9847 9848 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 9849 option = self._prev.text 9850 else: 9851 option = None 9852 9853 partition = self._parse_partition() 9854 9855 # Fallback case 9856 if self._curr: 9857 return self._parse_as_command(start) 9858 9859 return self.expression( 9860 exp.TruncateTable( 9861 expressions=expressions, 9862 is_database=is_database, 9863 exists=exists, 9864 cluster=cluster, 9865 identity=identity, 9866 option=option, 9867 partition=partition, 9868 ) 9869 ) 9870 9871 def _parse_indexed_column(self) -> exp.Expr | None: 9872 return self._parse_ordered(self._parse_opclass) 9873 9874 def _parse_with_operator(self) -> exp.Expr | None: 9875 this = self._parse_indexed_column() 9876 9877 if not self._match(TokenType.WITH): 9878 return this 9879 9880 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 9881 9882 return self.expression(exp.WithOperator(this=this, op=op)) 9883 9884 def _parse_wrapped_options(self) -> list[exp.Expr]: 9885 self._match(TokenType.EQ) 9886 self._match(TokenType.L_PAREN) 9887 9888 opts: list[exp.Expr] = [] 9889 option: exp.Expr | list[exp.Expr] | None 9890 while self._curr and not self._match(TokenType.R_PAREN): 9891 if self._match_text_seq("FORMAT_NAME", "="): 9892 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 9893 option = self._parse_format_name() 9894 else: 9895 option = self._parse_property() 9896 9897 if option is None: 9898 self.raise_error("Unable to parse option") 9899 break 9900 9901 opts.extend(ensure_list(option)) 9902 9903 return opts 9904 9905 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 9906 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 9907 9908 options = [] 9909 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 9910 option = self._parse_var(any_token=True) 9911 prev = self._prev.text.upper() 9912 9913 # Different dialects might separate options and values by white space, "=" and "AS" 9914 self._match(TokenType.EQ) 9915 self._match(TokenType.ALIAS) 9916 9917 param = self.expression(exp.CopyParameter(this=option)) 9918 9919 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 9920 TokenType.L_PAREN, advance=False 9921 ): 9922 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 9923 param.set("expressions", self._parse_wrapped_options()) 9924 elif prev == "FILE_FORMAT": 9925 # T-SQL's external file format case 9926 param.set("expression", self._parse_field()) 9927 elif ( 9928 prev == "FORMAT" 9929 and self._prev.token_type == TokenType.ALIAS 9930 and self._match_texts(("AVRO", "JSON")) 9931 ): 9932 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 9933 param.set("expression", self._parse_field()) 9934 else: 9935 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 9936 9937 options.append(param) 9938 9939 if sep: 9940 self._match(sep) 9941 9942 return options 9943 9944 def _parse_credentials(self) -> exp.Credentials | None: 9945 expr = self.expression(exp.Credentials()) 9946 9947 if self._match_text_seq("STORAGE_INTEGRATION", "="): 9948 expr.set("storage", self._parse_field()) 9949 if self._match_text_seq("CREDENTIALS"): 9950 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 9951 creds = ( 9952 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 9953 ) 9954 expr.set("credentials", creds) 9955 if self._match_text_seq("ENCRYPTION"): 9956 expr.set("encryption", self._parse_wrapped_options()) 9957 if self._match_text_seq("IAM_ROLE"): 9958 expr.set( 9959 "iam_role", 9960 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 9961 ) 9962 if self._match_text_seq("REGION"): 9963 expr.set("region", self._parse_field()) 9964 9965 return expr 9966 9967 def _parse_file_location(self) -> exp.Expr | None: 9968 return self._parse_field() 9969 9970 def _parse_copy(self) -> exp.Copy | exp.Command: 9971 start = self._prev 9972 9973 self._match(TokenType.INTO) 9974 9975 this = ( 9976 self._parse_select(nested=True, parse_subquery_alias=False) 9977 if self._match(TokenType.L_PAREN, advance=False) 9978 else self._parse_table(schema=True) 9979 ) 9980 9981 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 9982 9983 files = self._parse_csv(self._parse_file_location) 9984 if self._match(TokenType.EQ, advance=False): 9985 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 9986 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 9987 # list via `_parse_wrapped(..)` below. 9988 self._advance(-1) 9989 files = [] 9990 9991 credentials = self._parse_credentials() 9992 9993 self._match_text_seq("WITH") 9994 9995 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 9996 9997 # Fallback case 9998 if self._curr: 9999 return self._parse_as_command(start) 10000 10001 return self.expression( 10002 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 10003 ) 10004 10005 def _parse_normalize(self) -> exp.Normalize: 10006 return self.expression( 10007 exp.Normalize( 10008 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 10009 ) 10010 ) 10011 10012 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 10013 args = self._parse_csv(lambda: self._parse_lambda()) 10014 10015 this = seq_get(args, 0) 10016 decimals = seq_get(args, 1) 10017 10018 return expr_type( 10019 this=this, 10020 decimals=decimals, 10021 to=self._parse_var() if self._match_text_seq("TO") else None, 10022 ) 10023 10024 def _parse_star_ops(self) -> exp.Expr | None: 10025 star_token = self._prev 10026 10027 if self._match_text_seq("COLUMNS", "(", advance=False): 10028 this = self._parse_function() 10029 if isinstance(this, exp.Columns): 10030 this.set("unpack", True) 10031 return this 10032 10033 index = self._index 10034 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 10035 if not ilike: 10036 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 10037 self._retreat(index) 10038 10039 return self.expression( 10040 exp.Star( 10041 ilike=ilike, 10042 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 10043 replace=self._parse_star_op("REPLACE"), 10044 rename=self._parse_star_op("RENAME"), 10045 ) 10046 ).update_positions(star_token) 10047 10048 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 10049 privilege_parts = [] 10050 10051 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 10052 # (end of privilege list) or L_PAREN (start of column list) are met 10053 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 10054 privilege_parts.append(self._curr.text.upper()) 10055 self._advance() 10056 10057 if not privilege_parts: 10058 self.raise_error("Expected privilege") 10059 return None 10060 10061 this = exp.var(" ".join(privilege_parts)) 10062 expressions = ( 10063 self._parse_wrapped_csv(self._parse_column) 10064 if self._match(TokenType.L_PAREN, advance=False) 10065 else None 10066 ) 10067 10068 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10069 10070 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10071 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10072 principal = self._parse_id_var() 10073 10074 if not principal: 10075 return None 10076 10077 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10078 10079 def _parse_grant_revoke_common( 10080 self, 10081 ) -> tuple[list | None, str | None, exp.Expr | None]: 10082 privileges = self._parse_csv(self._parse_grant_privilege) 10083 10084 self._match(TokenType.ON) 10085 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10086 10087 # Attempt to parse the securable e.g. MySQL allows names 10088 # such as "foo.*", "*.*" which are not easily parseable yet 10089 securable = self._try_parse(self._parse_table_parts) 10090 10091 return privileges, kind, securable 10092 10093 def _parse_grant(self) -> exp.Grant | exp.Command: 10094 start = self._prev 10095 10096 privileges, kind, securable = self._parse_grant_revoke_common() 10097 10098 if not securable or not self._match_text_seq("TO"): 10099 return self._parse_as_command(start) 10100 10101 principals = self._parse_csv(self._parse_grant_principal) 10102 10103 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10104 10105 if self._curr: 10106 return self._parse_as_command(start) 10107 10108 return self.expression( 10109 exp.Grant( 10110 privileges=privileges, 10111 kind=kind, 10112 securable=securable, 10113 principals=principals, 10114 grant_option=grant_option, 10115 ) 10116 ) 10117 10118 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10119 start = self._prev 10120 10121 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10122 10123 privileges, kind, securable = self._parse_grant_revoke_common() 10124 10125 if not securable or not self._match_text_seq("FROM"): 10126 return self._parse_as_command(start) 10127 10128 principals = self._parse_csv(self._parse_grant_principal) 10129 10130 cascade = None 10131 if self._match_texts(("CASCADE", "RESTRICT")): 10132 cascade = self._prev.text.upper() 10133 10134 if self._curr: 10135 return self._parse_as_command(start) 10136 10137 return self.expression( 10138 exp.Revoke( 10139 privileges=privileges, 10140 kind=kind, 10141 securable=securable, 10142 principals=principals, 10143 grant_option=grant_option, 10144 cascade=cascade, 10145 ) 10146 ) 10147 10148 def _parse_overlay(self) -> exp.Overlay: 10149 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10150 return ( 10151 self._parse_bitwise() 10152 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10153 else None 10154 ) 10155 10156 return self.expression( 10157 exp.Overlay( 10158 this=self._parse_bitwise(), 10159 expression=_parse_overlay_arg("PLACING"), 10160 from_=_parse_overlay_arg("FROM"), 10161 for_=_parse_overlay_arg("FOR"), 10162 ) 10163 ) 10164 10165 def _parse_format_name(self) -> exp.Property: 10166 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10167 # for FILE_FORMAT = <format_name> 10168 return self.expression( 10169 exp.Property( 10170 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10171 ) 10172 ) 10173 10174 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10175 is_distinct = self._match(TokenType.DISTINCT) 10176 if not is_distinct: 10177 self._match(TokenType.ALL) 10178 10179 args = [self._parse_lambda()] 10180 if self._match(TokenType.COMMA): 10181 args.extend(self._parse_function_args()) 10182 10183 target = seq_get(args, distinct_index) 10184 if is_distinct and target: 10185 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10186 10187 return func.from_arg_list(args) 10188 10189 def _identifier_expression( 10190 self, token: Token | None = None, quoted: bool | None = None 10191 ) -> exp.Identifier: 10192 token = token or self._prev 10193 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10194 10195 def _build_pipe_cte( 10196 self, 10197 query: exp.Query, 10198 expressions: list[exp.Expr], 10199 alias_cte: exp.TableAlias | None = None, 10200 ) -> exp.Select: 10201 new_cte: str | exp.TableAlias | None 10202 if alias_cte: 10203 new_cte = alias_cte 10204 else: 10205 self._pipe_cte_counter += 1 10206 new_cte = f"__tmp{self._pipe_cte_counter}" 10207 10208 with_ = query.args.get("with_") 10209 ctes = with_.pop() if with_ else None 10210 10211 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10212 if ctes: 10213 new_select.set("with_", ctes) 10214 10215 return new_select.with_(new_cte, as_=query, copy=False) 10216 10217 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10218 select = self._parse_select(consume_pipe=False) 10219 if not select: 10220 return query 10221 10222 return self._build_pipe_cte( 10223 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10224 ) 10225 10226 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10227 limit = self._parse_limit() 10228 offset = self._parse_offset() 10229 if limit: 10230 curr_limit = query.args.get("limit", limit) 10231 if curr_limit.expression.to_py() >= limit.expression.to_py(): 10232 query.limit(limit, copy=False) 10233 if offset: 10234 curr_offset = query.args.get("offset") 10235 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10236 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10237 10238 return query 10239 10240 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10241 this = self._parse_disjunction() 10242 if self._match_text_seq("GROUP", "AND", advance=False): 10243 return this 10244 10245 this = self._parse_alias(this) 10246 10247 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10248 return self._parse_ordered(lambda: this) 10249 10250 return this 10251 10252 def _parse_pipe_syntax_aggregate_group_order_by( 10253 self, query: exp.Select, group_by_exists: bool = True 10254 ) -> exp.Select: 10255 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10256 aggregates_or_groups, orders = [], [] 10257 for element in expr: 10258 if isinstance(element, exp.Ordered): 10259 this = element.this 10260 if isinstance(this, exp.Alias): 10261 element.set("this", this.args["alias"]) 10262 orders.append(element) 10263 else: 10264 this = element 10265 aggregates_or_groups.append(this) 10266 10267 if group_by_exists: 10268 query.select( 10269 *aggregates_or_groups, *query.expressions, append=False, copy=False 10270 ).group_by( 10271 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10272 copy=False, 10273 ) 10274 else: 10275 query.select(*aggregates_or_groups, append=False, copy=False) 10276 10277 if orders: 10278 return query.order_by(*orders, append=False, copy=False) 10279 10280 return query 10281 10282 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10283 self._match_text_seq("AGGREGATE") 10284 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10285 10286 if self._match(TokenType.GROUP_BY) or ( 10287 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10288 ): 10289 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10290 10291 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10292 10293 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10294 first_setop = self.parse_set_operation(this=query) 10295 if not first_setop: 10296 return None 10297 10298 def _parse_and_unwrap_query() -> exp.Expr | None: 10299 expr = self._parse_paren() 10300 return expr.assert_is(exp.Subquery).unnest() if expr else None 10301 10302 first_setop.this.pop() 10303 10304 setops = [ 10305 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10306 *self._parse_csv(_parse_and_unwrap_query), 10307 ] 10308 10309 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10310 with_ = query.args.get("with_") 10311 ctes = with_.pop() if with_ else None 10312 10313 if isinstance(first_setop, exp.Union): 10314 query = query.union(*setops, copy=False, **first_setop.args) 10315 elif isinstance(first_setop, exp.Except): 10316 query = query.except_(*setops, copy=False, **first_setop.args) 10317 else: 10318 query = query.intersect(*setops, copy=False, **first_setop.args) 10319 10320 query.set("with_", ctes) 10321 10322 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10323 10324 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10325 join = self._parse_join() 10326 if not join: 10327 return None 10328 10329 if isinstance(query, exp.Select): 10330 return query.join(join, copy=False) 10331 10332 return query 10333 10334 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10335 pivots = self._parse_pivots() 10336 if not pivots: 10337 return query 10338 10339 from_ = query.args.get("from_") 10340 if from_: 10341 from_.this.set("pivots", pivots) 10342 10343 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10344 10345 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10346 self._match_text_seq("EXTEND") 10347 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10348 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10349 10350 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10351 sample = self._parse_table_sample() 10352 10353 with_ = query.args.get("with_") 10354 if with_: 10355 with_.expressions[-1].this.set("sample", sample) 10356 else: 10357 query.set("sample", sample) 10358 10359 return query 10360 10361 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10362 if isinstance(query, exp.Subquery): 10363 query = exp.select("*").from_(query, copy=False) 10364 10365 if not query.args.get("from_"): 10366 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10367 10368 while self._match(TokenType.PIPE_GT): 10369 start_index = self._index 10370 start_text = self._curr.text.upper() 10371 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10372 if not parser: 10373 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10374 # keywords, making it tricky to disambiguate them without lookahead. The approach 10375 # here is to try and parse a set operation and if that fails, then try to parse a 10376 # join operator. If that fails as well, then the operator is not supported. 10377 parsed_query = self._parse_pipe_syntax_set_operator(query) 10378 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10379 if not parsed_query: 10380 self._retreat(start_index) 10381 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10382 break 10383 query = parsed_query 10384 else: 10385 query = parser(self, query) 10386 10387 return query 10388 10389 def _parse_declareitem(self) -> exp.DeclareItem | None: 10390 self._match_texts(("VAR", "VARIABLE")) 10391 10392 vars = self._parse_csv(self._parse_id_var) 10393 if not vars: 10394 return None 10395 10396 self._match(TokenType.ALIAS) 10397 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10398 default = ( 10399 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10400 ) and self._parse_bitwise() 10401 10402 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10403 10404 def _parse_declare(self) -> exp.Declare | exp.Command: 10405 start = self._prev 10406 replace = self._match_text_seq("OR", "REPLACE") 10407 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10408 10409 if not expressions or self._curr: 10410 return self._parse_as_command(start) 10411 10412 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10413 10414 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10415 exp_class = exp.Cast if strict else exp.TryCast 10416 10417 if exp_class == exp.TryCast: 10418 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10419 10420 return self.expression(exp_class(**kwargs)) 10421 10422 def _parse_json_value(self) -> exp.JSONValue: 10423 this = self._parse_bitwise() 10424 self._match(TokenType.COMMA) 10425 path = self._parse_bitwise() 10426 10427 returning = self._match(TokenType.RETURNING) and self._parse_type() 10428 10429 return self.expression( 10430 exp.JSONValue( 10431 this=this, 10432 path=self.dialect.to_json_path(path), 10433 returning=returning, 10434 on_condition=self._parse_on_condition(), 10435 ) 10436 ) 10437 10438 def _parse_group_concat(self) -> exp.Expr | None: 10439 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10440 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10441 concat_exprs = [ 10442 self.expression( 10443 exp.Concat( 10444 expressions=node.expressions, 10445 safe=True, 10446 coalesce=self.dialect.CONCAT_COALESCE, 10447 ) 10448 ) 10449 ] 10450 node.set("expressions", concat_exprs) 10451 return node 10452 if len(exprs) == 1: 10453 return exprs[0] 10454 return self.expression( 10455 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10456 ) 10457 10458 args = self._parse_csv(self._parse_lambda) 10459 10460 if args: 10461 order = args[-1] if isinstance(args[-1], exp.Order) else None 10462 10463 if order: 10464 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10465 # remove 'expr' from exp.Order and add it back to args 10466 args[-1] = order.this 10467 order.set("this", concat_exprs(order.this, args)) 10468 10469 this = order or concat_exprs(args[0], args) 10470 else: 10471 this = None 10472 10473 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10474 10475 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10476 10477 def _parse_initcap(self) -> exp.Initcap: 10478 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10479 10480 # attach dialect's default delimiters 10481 if expr.args.get("expression") is None: 10482 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10483 10484 return expr 10485 10486 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10487 while True: 10488 if not self._match(TokenType.L_PAREN): 10489 break 10490 10491 op = "" 10492 while self._curr and not self._match(TokenType.R_PAREN): 10493 op += self._curr.text 10494 self._advance() 10495 10496 comments = self._prev_comments 10497 this = self.expression( 10498 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10499 comments=comments, 10500 ) 10501 10502 if not self._match(TokenType.OPERATOR): 10503 break 10504 10505 return this
51def build_var_map(args: BuilderArgs) -> exp.StarMap | exp.VarMap: 52 if len(args) == 1 and args[0].is_star: 53 return exp.StarMap(this=args[0]) 54 55 keys: list[ExpOrStr] = [] 56 values: list[ExpOrStr] = [] 57 for i in range(0, len(args), 2): 58 keys.append(args[i]) 59 values.append(args[i + 1]) 60 61 return exp.VarMap(keys=exp.array(*keys, copy=False), values=exp.array(*values, copy=False))
69def binary_range_parser( 70 expr_type: Type[exp.Expr], reverse_args: bool = False 71) -> t.Callable[[Parser, exp.Expr | None], exp.Expr | None]: 72 def _parse_binary_range(self: Parser, this: exp.Expr | None) -> exp.Expr | None: 73 expression = self._parse_bitwise() 74 if reverse_args: 75 this, expression = expression, this 76 return self._parse_escape(self.expression(expr_type(this=this, expression=expression))) 77 78 return _parse_binary_range
81def build_logarithm(args: BuilderArgs, dialect: Dialect) -> exp.Func: 82 # Default argument order is base, expression 83 this = seq_get(args, 0) 84 expression = seq_get(args, 1) 85 86 if expression: 87 if not dialect.LOG_BASE_FIRST: 88 this, expression = expression, this 89 return exp.Log(this=this, expression=expression) 90 91 return (exp.Ln if dialect.parser_class.LOG_DEFAULTS_TO_LN else exp.Log)(this=this)
111def build_extract_json_with_path( 112 expr_type: Type[E], 113) -> t.Callable[[BuilderArgs, Dialect], E]: 114 def _builder(args: BuilderArgs, dialect: Dialect) -> E: 115 expression = expr_type( 116 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 117 ) 118 if len(args) > 2 and expr_type is exp.JSONExtract: 119 expression.set("expressions", args[2:]) 120 if expr_type is exp.JSONExtractScalar: 121 expression.set("scalar_only", dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY) 122 123 return expression 124 125 return _builder
128def build_mod(args: BuilderArgs) -> exp.Mod: 129 this = seq_get(args, 0) 130 expression = seq_get(args, 1) 131 132 # Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7 133 this = exp.Paren(this=this) if isinstance(this, exp.Binary) else this 134 expression = exp.Paren(this=expression) if isinstance(expression, exp.Binary) else expression 135 136 return exp.Mod(this=this, expression=expression)
148def build_array_constructor( 149 exp_class: Type[E], args: list[t.Any], bracket_kind: TokenType, dialect: Dialect 150) -> exp.Expr: 151 array_exp = exp_class(expressions=args) 152 153 if exp_class == exp.Array and dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS: 154 array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET) 155 156 return array_exp
159def build_convert_timezone( 160 args: BuilderArgs, default_source_tz: str | None = None 161) -> exp.ConvertTimezone | exp.Anonymous: 162 if len(args) == 2: 163 source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None 164 return exp.ConvertTimezone( 165 source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1) 166 ) 167 168 return exp.ConvertTimezone.from_arg_list(args)
171def build_trim(args: BuilderArgs, is_left: bool = True, reverse_args: bool = False) -> exp.Trim: 172 this, expression = seq_get(args, 0), seq_get(args, 1) 173 174 if expression and reverse_args: 175 this, expression = expression, this 176 177 return exp.Trim(this=this, expression=expression, position="LEADING" if is_left else "TRAILING")
194def build_array_append(args: BuilderArgs, dialect: Dialect) -> exp.ArrayAppend: 195 """ 196 Builds ArrayAppend with NULL propagation semantics based on the dialect configuration. 197 198 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 199 Others (DuckDB, PostgreSQL) create a new single-element array instead. 200 201 Args: 202 args: Function arguments [array, element] 203 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 204 205 Returns: 206 ArrayAppend expression with appropriate null_propagation flag 207 """ 208 return exp.ArrayAppend( 209 this=seq_get(args, 0), 210 expression=seq_get(args, 1), 211 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 212 )
Builds ArrayAppend with NULL propagation semantics based on the dialect configuration.
Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. Others (DuckDB, PostgreSQL) create a new single-element array instead.
Arguments:
- args: Function arguments [array, element]
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayAppend expression with appropriate null_propagation flag
215def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend: 216 """ 217 Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration. 218 219 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 220 Others (DuckDB, PostgreSQL) create a new single-element array instead. 221 222 Args: 223 args: Function arguments [array, element] 224 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 225 226 Returns: 227 ArrayPrepend expression with appropriate null_propagation flag 228 """ 229 return exp.ArrayPrepend( 230 this=seq_get(args, 0), 231 expression=seq_get(args, 1), 232 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 233 )
Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration.
Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. Others (DuckDB, PostgreSQL) create a new single-element array instead.
Arguments:
- args: Function arguments [array, element]
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayPrepend expression with appropriate null_propagation flag
236def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat: 237 """ 238 Builds ArrayConcat with NULL propagation semantics based on the dialect configuration. 239 240 Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. 241 Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation. 242 243 Args: 244 args: Function arguments [array1, array2, ...] (variadic) 245 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 246 247 Returns: 248 ArrayConcat expression with appropriate null_propagation flag 249 """ 250 return exp.ArrayConcat( 251 this=seq_get(args, 0), 252 expressions=args[1:], 253 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 254 )
Builds ArrayConcat with NULL propagation semantics based on the dialect configuration.
Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation.
Arguments:
- args: Function arguments [array1, array2, ...] (variadic)
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayConcat expression with appropriate null_propagation flag
257def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove: 258 """ 259 Builds ArrayRemove with NULL propagation semantics based on the dialect configuration. 260 261 Some dialects (Snowflake) return NULL when the removal value is NULL. 262 Others (DuckDB) may return empty array due to NULL comparison semantics. 263 264 Args: 265 args: Function arguments [array, value_to_remove] 266 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 267 268 Returns: 269 ArrayRemove expression with appropriate null_propagation flag 270 """ 271 return exp.ArrayRemove( 272 this=seq_get(args, 0), 273 expression=seq_get(args, 1), 274 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 275 )
Builds ArrayRemove with NULL propagation semantics based on the dialect configuration.
Some dialects (Snowflake) return NULL when the removal value is NULL. Others (DuckDB) may return empty array due to NULL comparison semantics.
Arguments:
- args: Function arguments [array, value_to_remove]
- dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:
ArrayRemove expression with appropriate null_propagation flag
306def build_json_extract_scalar( 307 self: Parser, this: exp.Expr, path: exp.Expr 308) -> exp.JSONExtractScalar: 309 return self.expression( 310 exp.JSONExtractScalar( 311 this=this, 312 expression=self.dialect.to_json_path(path), 313 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 314 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 315 ) 316 )
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"} 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 index = self._index 2925 2926 seq_props = self._parse_sequence_properties() 2927 if seq_props: 2928 return seq_props 2929 2930 self._retreat(index) 2931 return self._parse_key_value_property() 2932 2933 def _parse_key_value_property( 2934 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 2935 ) -> exp.Property | None: 2936 index = self._index 2937 key = self._parse_column() 2938 2939 if not self._match(TokenType.EQ): 2940 self._retreat(index) 2941 return None 2942 2943 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 2944 if isinstance(key, exp.Column): 2945 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 2946 2947 value = ( 2948 parse_value() 2949 if parse_value 2950 else self._parse_bitwise() or self._parse_var(any_token=True) 2951 ) 2952 2953 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 2954 if isinstance(value, exp.Column): 2955 value = exp.var(value.name) 2956 2957 return self.expression(exp.Property(this=key, value=value)) 2958 2959 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 2960 if self._match_text_seq("BY"): 2961 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 2962 2963 self._match(TokenType.ALIAS) 2964 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 2965 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 2966 2967 return self.expression( 2968 exp.FileFormatProperty( 2969 this=( 2970 self.expression( 2971 exp.InputOutputFormat( 2972 input_format=input_format, output_format=output_format 2973 ) 2974 ) 2975 if input_format or output_format 2976 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 2977 ), 2978 hive_format=True, 2979 ) 2980 ) 2981 2982 def _parse_unquoted_field(self) -> exp.Expr | None: 2983 field = self._parse_field() 2984 if isinstance(field, exp.Identifier) and not field.quoted: 2985 field = exp.var(field) 2986 2987 return field 2988 2989 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 2990 self._match(TokenType.EQ) 2991 self._match(TokenType.ALIAS) 2992 2993 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 2994 2995 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 2996 properties = [] 2997 while True: 2998 if before: 2999 prop = self._parse_property_before() 3000 else: 3001 prop = self._parse_property() 3002 if not prop: 3003 break 3004 for p in ensure_list(prop): 3005 properties.append(p) 3006 3007 if properties: 3008 return self.expression(exp.Properties(expressions=properties)) 3009 3010 return None 3011 3012 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3013 return self.expression( 3014 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3015 ) 3016 3017 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3018 return self.expression( 3019 exp.SqlSecurityProperty( 3020 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3021 ) 3022 ) 3023 3024 def _parse_settings_property(self) -> exp.SettingsProperty: 3025 return self.expression( 3026 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3027 ) 3028 3029 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3030 if not self._match_text_seq("ON", "NULL", "INPUT"): 3031 self._retreat(self._index - 1) 3032 return None 3033 3034 return self.expression(exp.CalledOnNullInputProperty()) 3035 3036 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3037 if self._index >= 2: 3038 pre_volatile_token = self._tokens[self._index - 2] 3039 else: 3040 pre_volatile_token = None 3041 3042 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3043 return exp.VolatileProperty() 3044 3045 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3046 3047 def _parse_retention_period(self) -> exp.Var: 3048 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3049 number = self._parse_number() 3050 number_str = f"{number} " if number else "" 3051 unit = self._parse_var(any_token=True) 3052 return exp.var(f"{number_str}{unit}") 3053 3054 def _parse_system_versioning_property( 3055 self, with_: bool = False 3056 ) -> exp.WithSystemVersioningProperty: 3057 self._match(TokenType.EQ) 3058 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3059 3060 if self._match_text_seq("OFF"): 3061 prop.set("on", False) 3062 return prop 3063 3064 self._match(TokenType.ON) 3065 if self._match(TokenType.L_PAREN): 3066 while self._curr and not self._match(TokenType.R_PAREN): 3067 if self._match_text_seq("HISTORY_TABLE", "="): 3068 prop.set("this", self._parse_table_parts()) 3069 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3070 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3071 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3072 prop.set("retention_period", self._parse_retention_period()) 3073 3074 self._match(TokenType.COMMA) 3075 3076 return prop 3077 3078 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3079 self._match(TokenType.EQ) 3080 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3081 prop = self.expression(exp.DataDeletionProperty(on=on)) 3082 3083 if self._match(TokenType.L_PAREN): 3084 while self._curr and not self._match(TokenType.R_PAREN): 3085 if self._match_text_seq("FILTER_COLUMN", "="): 3086 prop.set("filter_column", self._parse_column()) 3087 elif self._match_text_seq("RETENTION_PERIOD", "="): 3088 prop.set("retention_period", self._parse_retention_period()) 3089 3090 self._match(TokenType.COMMA) 3091 3092 return prop 3093 3094 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3095 kind = "HASH" 3096 expressions: list[exp.Expr] | None = None 3097 if self._match_text_seq("BY", "HASH"): 3098 expressions = self._parse_wrapped_csv(self._parse_id_var) 3099 elif self._match_text_seq("BY", "RANDOM"): 3100 kind = "RANDOM" 3101 3102 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3103 buckets: exp.Expr | None = None 3104 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3105 buckets = self._parse_number() 3106 3107 return self.expression( 3108 exp.DistributedByProperty( 3109 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3110 ) 3111 ) 3112 3113 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3114 self._match_text_seq("KEY") 3115 expressions = self._parse_wrapped_id_vars() 3116 return self.expression(expr_type(expressions=expressions)) 3117 3118 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3119 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3120 prop = self._parse_system_versioning_property(with_=True) 3121 self._match_r_paren() 3122 return prop 3123 3124 if self._match(TokenType.L_PAREN, advance=False): 3125 result: list[exp.Expr] = [] 3126 for i in self._parse_wrapped_properties(): 3127 result.extend(i) if isinstance(i, list) else result.append(i) 3128 return result 3129 3130 if self._match_text_seq("JOURNAL"): 3131 return self._parse_withjournaltable() 3132 3133 if self._match_texts(self.VIEW_ATTRIBUTES): 3134 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3135 3136 if self._match_text_seq("DATA"): 3137 return self._parse_withdata(no=False) 3138 elif self._match_text_seq("NO", "DATA"): 3139 return self._parse_withdata(no=True) 3140 3141 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3142 return self._parse_serde_properties(with_=True) 3143 3144 if self._match(TokenType.SCHEMA): 3145 return self.expression( 3146 exp.WithSchemaBindingProperty( 3147 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3148 ) 3149 ) 3150 3151 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3152 return self.expression( 3153 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3154 ) 3155 3156 if not self._next: 3157 return None 3158 3159 return self._parse_withisolatedloading() 3160 3161 def _parse_procedure_option(self) -> exp.Expr | None: 3162 if self._match_text_seq("EXECUTE", "AS"): 3163 return self.expression( 3164 exp.ExecuteAsProperty( 3165 this=self._parse_var_from_options( 3166 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3167 ) 3168 or self._parse_string() 3169 ) 3170 ) 3171 3172 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3173 3174 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3175 def _parse_definer(self) -> exp.DefinerProperty | None: 3176 self._match(TokenType.EQ) 3177 3178 user = self._parse_id_var() 3179 self._match(TokenType.PARAMETER) 3180 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3181 3182 if not user or not host: 3183 return None 3184 3185 return exp.DefinerProperty(this=f"{user}@{host}") 3186 3187 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3188 self._match(TokenType.TABLE) 3189 self._match(TokenType.EQ) 3190 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3191 3192 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3193 return self.expression(exp.LogProperty(no=no)) 3194 3195 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3196 return self.expression(exp.JournalProperty(**kwargs)) 3197 3198 def _parse_checksum(self) -> exp.ChecksumProperty: 3199 self._match(TokenType.EQ) 3200 3201 on = None 3202 if self._match(TokenType.ON): 3203 on = True 3204 elif self._match_text_seq("OFF"): 3205 on = False 3206 3207 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3208 3209 def _parse_cluster(self) -> exp.Cluster: 3210 self._match(TokenType.CLUSTER_BY) 3211 return self.expression( 3212 exp.Cluster( 3213 expressions=self._parse_csv(self._parse_column), 3214 ) 3215 ) 3216 3217 def _parse_cluster_property(self) -> exp.ClusterProperty: 3218 return self.expression( 3219 exp.ClusterProperty( 3220 expressions=self._parse_wrapped_csv(self._parse_column), 3221 ) 3222 ) 3223 3224 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3225 self._match_text_seq("BY") 3226 3227 self._match_l_paren() 3228 expressions = self._parse_csv(self._parse_column) 3229 self._match_r_paren() 3230 3231 if self._match_text_seq("SORTED", "BY"): 3232 self._match_l_paren() 3233 sorted_by = self._parse_csv(self._parse_ordered) 3234 self._match_r_paren() 3235 else: 3236 sorted_by = None 3237 3238 self._match(TokenType.INTO) 3239 buckets = self._parse_number() 3240 self._match_text_seq("BUCKETS") 3241 3242 return self.expression( 3243 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3244 ) 3245 3246 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3247 if not self._match_text_seq("GRANTS"): 3248 self._retreat(self._index - 1) 3249 return None 3250 3251 return self.expression(exp.CopyGrantsProperty()) 3252 3253 def _parse_freespace(self) -> exp.FreespaceProperty: 3254 self._match(TokenType.EQ) 3255 return self.expression( 3256 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3257 ) 3258 3259 def _parse_mergeblockratio( 3260 self, no: bool = False, default: bool = False 3261 ) -> exp.MergeBlockRatioProperty: 3262 if self._match(TokenType.EQ): 3263 return self.expression( 3264 exp.MergeBlockRatioProperty( 3265 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3266 ) 3267 ) 3268 3269 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3270 3271 def _parse_datablocksize( 3272 self, 3273 default: bool | None = None, 3274 minimum: bool | None = None, 3275 maximum: bool | None = None, 3276 ) -> exp.DataBlocksizeProperty: 3277 self._match(TokenType.EQ) 3278 size = self._parse_number() 3279 3280 units = None 3281 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3282 units = self._prev.text 3283 3284 return self.expression( 3285 exp.DataBlocksizeProperty( 3286 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3287 ) 3288 ) 3289 3290 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3291 self._match(TokenType.EQ) 3292 always = self._match_text_seq("ALWAYS") 3293 manual = self._match_text_seq("MANUAL") 3294 never = self._match_text_seq("NEVER") 3295 default = self._match_text_seq("DEFAULT") 3296 3297 autotemp = None 3298 if self._match_text_seq("AUTOTEMP"): 3299 autotemp = self._parse_schema() 3300 3301 return self.expression( 3302 exp.BlockCompressionProperty( 3303 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3304 ) 3305 ) 3306 3307 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3308 index = self._index 3309 no = self._match_text_seq("NO") 3310 concurrent = self._match_text_seq("CONCURRENT") 3311 3312 if not self._match_text_seq("ISOLATED", "LOADING"): 3313 self._retreat(index) 3314 return None 3315 3316 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3317 return self.expression( 3318 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3319 ) 3320 3321 def _parse_locking(self) -> exp.LockingProperty: 3322 if self._match(TokenType.TABLE): 3323 kind = "TABLE" 3324 elif self._match(TokenType.VIEW): 3325 kind = "VIEW" 3326 elif self._match(TokenType.ROW): 3327 kind = "ROW" 3328 elif self._match_text_seq("DATABASE"): 3329 kind = "DATABASE" 3330 else: 3331 kind = None 3332 3333 if kind in ("DATABASE", "TABLE", "VIEW"): 3334 this = self._parse_table_parts() 3335 else: 3336 this = None 3337 3338 if self._match(TokenType.FOR): 3339 for_or_in = "FOR" 3340 elif self._match(TokenType.IN): 3341 for_or_in = "IN" 3342 else: 3343 for_or_in = None 3344 3345 if self._match_text_seq("ACCESS"): 3346 lock_type = "ACCESS" 3347 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3348 lock_type = "EXCLUSIVE" 3349 elif self._match_text_seq("SHARE"): 3350 lock_type = "SHARE" 3351 elif self._match_text_seq("READ"): 3352 lock_type = "READ" 3353 elif self._match_text_seq("WRITE"): 3354 lock_type = "WRITE" 3355 elif self._match_text_seq("CHECKSUM"): 3356 lock_type = "CHECKSUM" 3357 else: 3358 lock_type = None 3359 3360 override = self._match_text_seq("OVERRIDE") 3361 3362 return self.expression( 3363 exp.LockingProperty( 3364 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3365 ) 3366 ) 3367 3368 def _parse_partition_by(self) -> list[exp.Expr]: 3369 if self._match(TokenType.PARTITION_BY): 3370 return self._parse_csv(self._parse_disjunction) 3371 return [] 3372 3373 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3374 def _parse_partition_bound_expr() -> exp.Expr | None: 3375 if self._match_text_seq("MINVALUE"): 3376 return exp.var("MINVALUE") 3377 if self._match_text_seq("MAXVALUE"): 3378 return exp.var("MAXVALUE") 3379 return self._parse_bitwise() 3380 3381 this: exp.Expr | list[exp.Expr] | None = None 3382 expression = None 3383 from_expressions = None 3384 to_expressions = None 3385 3386 if self._match(TokenType.IN): 3387 this = self._parse_wrapped_csv(self._parse_bitwise) 3388 elif self._match(TokenType.FROM): 3389 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3390 self._match_text_seq("TO") 3391 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3392 elif self._match_text_seq("WITH", "(", "MODULUS"): 3393 this = self._parse_number() 3394 self._match_text_seq(",", "REMAINDER") 3395 expression = self._parse_number() 3396 self._match_r_paren() 3397 else: 3398 self.raise_error("Failed to parse partition bound spec.") 3399 3400 return self.expression( 3401 exp.PartitionBoundSpec( 3402 this=this, 3403 expression=expression, 3404 from_expressions=from_expressions, 3405 to_expressions=to_expressions, 3406 ) 3407 ) 3408 3409 # https://www.postgresql.org/docs/current/sql-createtable.html 3410 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3411 if not self._match_text_seq("OF"): 3412 self._retreat(self._index - 1) 3413 return None 3414 3415 this = self._parse_table(schema=True) 3416 3417 if self._match(TokenType.DEFAULT): 3418 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3419 elif self._match_text_seq("FOR", "VALUES"): 3420 expression = self._parse_partition_bound_spec() 3421 else: 3422 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3423 3424 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3425 3426 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3427 self._match(TokenType.EQ) 3428 return self.expression( 3429 exp.PartitionedByProperty( 3430 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3431 ) 3432 ) 3433 3434 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3435 if self._match_text_seq("AND", "STATISTICS"): 3436 statistics = True 3437 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3438 statistics = False 3439 else: 3440 statistics = None 3441 3442 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3443 3444 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3445 if self._match_text_seq("SQL"): 3446 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3447 return None 3448 3449 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3450 if self._match_text_seq("SQL", "DATA"): 3451 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3452 return None 3453 3454 def _parse_no_property(self) -> exp.Expr | None: 3455 if self._match_text_seq("PRIMARY", "INDEX"): 3456 return exp.NoPrimaryIndexProperty() 3457 if self._match_text_seq("SQL"): 3458 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3459 return None 3460 3461 def _parse_on_property(self) -> exp.Expr | None: 3462 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3463 return exp.OnCommitProperty() 3464 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3465 return exp.OnCommitProperty(delete=True) 3466 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3467 3468 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3469 if self._match_text_seq("SQL", "DATA"): 3470 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3471 return None 3472 3473 def _parse_distkey(self) -> exp.DistKeyProperty: 3474 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3475 3476 def _parse_create_like(self) -> exp.LikeProperty | None: 3477 table = self._parse_table(schema=True) 3478 3479 options = [] 3480 while self._match_texts(("INCLUDING", "EXCLUDING")): 3481 this = self._prev.text.upper() 3482 3483 id_var = self._parse_id_var() 3484 if not id_var: 3485 return None 3486 3487 options.append( 3488 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3489 ) 3490 3491 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3492 3493 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3494 return self.expression( 3495 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3496 ) 3497 3498 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3499 self._match(TokenType.EQ) 3500 return self.expression( 3501 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3502 ) 3503 3504 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3505 self._match_text_seq("WITH", "CONNECTION") 3506 return self.expression( 3507 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3508 ) 3509 3510 def _parse_returns(self) -> exp.ReturnsProperty: 3511 value: exp.Expr | None 3512 null = None 3513 is_table = self._match(TokenType.TABLE) 3514 3515 if is_table: 3516 if self._match(TokenType.LT): 3517 value = self.expression( 3518 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3519 ) 3520 if not self._match(TokenType.GT): 3521 self.raise_error("Expecting >") 3522 else: 3523 value = self._parse_schema(exp.var("TABLE")) 3524 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3525 null = True 3526 value = None 3527 else: 3528 value = self._parse_types() 3529 3530 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3531 3532 def _parse_describe(self) -> exp.Describe: 3533 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3534 style: str | None = ( 3535 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3536 ) 3537 if self._match(TokenType.DOT): 3538 style = None 3539 self._retreat(self._index - 2) 3540 3541 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3542 3543 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3544 this = self._parse_statement() 3545 else: 3546 this = self._parse_table(schema=True) 3547 3548 properties = self._parse_properties() 3549 expressions = properties.expressions if properties else None 3550 partition = self._parse_partition() 3551 return self.expression( 3552 exp.Describe( 3553 this=this, 3554 style=style, 3555 kind=kind, 3556 expressions=expressions, 3557 partition=partition, 3558 format=format, 3559 as_json=self._match_text_seq("AS", "JSON"), 3560 ) 3561 ) 3562 3563 def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts: 3564 kind = self._prev.text.upper() 3565 expressions = [] 3566 3567 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3568 if self._match(TokenType.WHEN): 3569 expression = self._parse_disjunction() 3570 self._match(TokenType.THEN) 3571 else: 3572 expression = None 3573 3574 else_ = self._match(TokenType.ELSE) 3575 3576 if not self._match(TokenType.INTO): 3577 return None 3578 3579 return self.expression( 3580 exp.ConditionalInsert( 3581 this=self.expression( 3582 exp.Insert( 3583 this=self._parse_table(schema=True), 3584 expression=self._parse_derived_table_values(), 3585 ) 3586 ), 3587 expression=expression, 3588 else_=else_, 3589 ) 3590 ) 3591 3592 expression = parse_conditional_insert() 3593 while expression is not None: 3594 expressions.append(expression) 3595 expression = parse_conditional_insert() 3596 3597 return self.expression( 3598 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3599 comments=comments, 3600 ) 3601 3602 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3603 comments: list[str] = [] 3604 hint = self._parse_hint() 3605 overwrite = self._match(TokenType.OVERWRITE) 3606 ignore = self._match(TokenType.IGNORE) 3607 local = self._match_text_seq("LOCAL") 3608 alternative = None 3609 is_function = None 3610 3611 if self._match_text_seq("DIRECTORY"): 3612 this: exp.Expr | None = self.expression( 3613 exp.Directory( 3614 this=self._parse_var_or_string(), 3615 local=local, 3616 row_format=self._parse_row_format(match_row=True), 3617 ) 3618 ) 3619 else: 3620 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3621 comments += ensure_list(self._prev_comments) 3622 return self._parse_multitable_inserts(comments) 3623 3624 if self._match(TokenType.OR): 3625 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3626 3627 self._match(TokenType.INTO) 3628 comments += ensure_list(self._prev_comments) 3629 self._match(TokenType.TABLE) 3630 is_function = self._match(TokenType.FUNCTION) 3631 3632 this = self._parse_function() if is_function else self._parse_insert_table() 3633 3634 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3635 set_values = None 3636 if self._match(TokenType.SET): 3637 columns = [] 3638 values = [] 3639 3640 def _parse_set_assignment() -> exp.Expr | None: 3641 target = self._parse_column() 3642 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3643 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3644 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3645 else: 3646 value = self._parse_disjunction() 3647 3648 if value: 3649 columns.append(target.this) 3650 values.append(value) 3651 return value 3652 3653 self.raise_error("Expected column assignment in INSERT ... SET") 3654 return None 3655 3656 self._parse_csv(_parse_set_assignment) 3657 3658 this = self.expression(exp.Schema(this=this, expressions=columns)) 3659 set_values = self.expression( 3660 exp.Values( 3661 expressions=[exp.Tuple(expressions=values)], 3662 alias=self._parse_table_alias(), 3663 ) 3664 ) 3665 3666 returning = self._parse_returning() # TSQL allows RETURNING before source 3667 3668 stored = self._match_text_seq("STORED") and self._parse_stored() 3669 by_name = self._match_text_seq("BY", "NAME") 3670 exists = self._parse_exists() 3671 replace_where = None 3672 replace_using = None 3673 3674 if self._match(TokenType.REPLACE): 3675 if self._match(TokenType.WHERE): 3676 replace_where = self._parse_disjunction() 3677 elif self._match(TokenType.USING): 3678 replace_using = self._parse_using_identifiers() 3679 3680 return self.expression( 3681 exp.Insert( 3682 hint=hint, 3683 is_function=is_function, 3684 this=this, 3685 stored=stored, 3686 by_name=by_name, 3687 exists=exists, 3688 where=replace_where, 3689 using=replace_using, 3690 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3691 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3692 default=self._match_text_seq("DEFAULT", "VALUES"), 3693 expression=set_values 3694 or self._parse_derived_table_values() 3695 or self._parse_ddl_select(), 3696 conflict=self._parse_on_conflict(), 3697 returning=returning or self._parse_returning(), 3698 overwrite=overwrite, 3699 alternative=alternative, 3700 ignore=ignore, 3701 source=self._match(TokenType.TABLE) and self._parse_table(), 3702 ), 3703 comments=comments, 3704 ) 3705 3706 def _parse_insert_table(self) -> exp.Expr | None: 3707 this = self._parse_table(schema=True, parse_partition=True) 3708 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3709 this.set("alias", self._parse_table_alias()) 3710 return this 3711 3712 def _parse_kill(self) -> exp.Kill: 3713 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3714 3715 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3716 3717 def _parse_on_conflict(self) -> exp.OnConflict | None: 3718 conflict = self._match_text_seq("ON", "CONFLICT") 3719 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3720 3721 if not conflict and not duplicate: 3722 return None 3723 3724 conflict_keys = None 3725 constraint = None 3726 3727 if conflict: 3728 if self._match_text_seq("ON", "CONSTRAINT"): 3729 constraint = self._parse_id_var() 3730 elif self._match(TokenType.L_PAREN): 3731 conflict_keys = self._parse_csv(self._parse_indexed_column) 3732 self._match_r_paren() 3733 3734 index_predicate = self._parse_where() 3735 3736 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3737 if self._prev.token_type == TokenType.UPDATE: 3738 self._match(TokenType.SET) 3739 expressions = self._parse_csv(self._parse_equality) 3740 else: 3741 expressions = None 3742 3743 return self.expression( 3744 exp.OnConflict( 3745 duplicate=duplicate, 3746 expressions=expressions, 3747 action=action, 3748 conflict_keys=conflict_keys, 3749 index_predicate=index_predicate, 3750 constraint=constraint, 3751 where=self._parse_where(), 3752 ) 3753 ) 3754 3755 def _parse_returning(self) -> exp.Returning | None: 3756 if not self._match(TokenType.RETURNING): 3757 return None 3758 return self.expression( 3759 exp.Returning( 3760 expressions=self._parse_csv(self._parse_expression), 3761 into=self._match(TokenType.INTO) and self._parse_table_part(), 3762 ) 3763 ) 3764 3765 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3766 if not self._match(TokenType.FORMAT): 3767 return None 3768 return self._parse_row_format() 3769 3770 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3771 index = self._index 3772 with_ = with_ or self._match_text_seq("WITH") 3773 3774 if not self._match(TokenType.SERDE_PROPERTIES): 3775 self._retreat(index) 3776 return None 3777 return self.expression( 3778 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3779 ) 3780 3781 def _parse_row_format( 3782 self, match_row: bool = False 3783 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3784 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3785 return None 3786 3787 if self._match_text_seq("SERDE"): 3788 this = self._parse_string() 3789 3790 serde_properties = self._parse_serde_properties() 3791 3792 return self.expression( 3793 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3794 ) 3795 3796 self._match_text_seq("DELIMITED") 3797 3798 kwargs = {} 3799 3800 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3801 kwargs["fields"] = self._parse_string() 3802 if self._match_text_seq("ESCAPED", "BY"): 3803 kwargs["escaped"] = self._parse_string() 3804 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3805 kwargs["collection_items"] = self._parse_string() 3806 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3807 kwargs["map_keys"] = self._parse_string() 3808 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3809 kwargs["lines"] = self._parse_string() 3810 if self._match_text_seq("NULL", "DEFINED", "AS"): 3811 kwargs["null"] = self._parse_string() 3812 3813 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3814 3815 def _parse_load(self) -> exp.LoadData | exp.Command: 3816 if self._match_text_seq("DATA"): 3817 local = self._match_text_seq("LOCAL") 3818 self._match_text_seq("INPATH") 3819 inpath = self._parse_string() 3820 overwrite = self._match(TokenType.OVERWRITE) 3821 temp: bool | None = None 3822 if self._match(TokenType.INTO): 3823 temp = self._match(TokenType.TEMPORARY) 3824 self._match(TokenType.TABLE) 3825 3826 return self.expression( 3827 exp.LoadData( 3828 this=self._parse_table(schema=True), 3829 local=local, 3830 overwrite=overwrite, 3831 temp=temp, 3832 inpath=inpath, 3833 files=self._match_text_seq("FROM", "FILES") 3834 and exp.Properties(expressions=self._parse_wrapped_properties()), 3835 partition=self._parse_partition(), 3836 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3837 serde=self._match_text_seq("SERDE") and self._parse_string(), 3838 ) 3839 ) 3840 return self._parse_as_command(self._prev) 3841 3842 def _parse_delete(self) -> exp.Delete: 3843 hint = self._parse_hint() 3844 3845 # This handles MySQL's "Multiple-Table Syntax" 3846 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3847 tables = None 3848 if not self._match(TokenType.FROM, advance=False): 3849 tables = self._parse_csv(self._parse_table) or None 3850 3851 returning = self._parse_returning() 3852 3853 return self.expression( 3854 exp.Delete( 3855 hint=hint, 3856 tables=tables, 3857 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3858 using=self._match(TokenType.USING) 3859 and self._parse_csv(lambda: self._parse_table(joins=True)), 3860 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3861 where=self._parse_where(), 3862 returning=returning or self._parse_returning(), 3863 order=self._parse_order(), 3864 limit=self._parse_limit(), 3865 ) 3866 ) 3867 3868 def _parse_update(self) -> exp.Update: 3869 hint = self._parse_hint() 3870 kwargs: dict[str, object] = { 3871 "hint": hint, 3872 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3873 } 3874 while self._curr: 3875 if self._match(TokenType.SET): 3876 kwargs["expressions"] = self._parse_csv(self._parse_equality) 3877 elif self._match(TokenType.RETURNING, advance=False): 3878 kwargs["returning"] = self._parse_returning() 3879 elif self._match(TokenType.FROM, advance=False): 3880 from_ = self._parse_from(joins=True) 3881 table = from_.this if from_ else None 3882 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3883 table.set("joins", list(self._parse_joins()) or None) 3884 3885 kwargs["from_"] = from_ 3886 elif self._match(TokenType.WHERE, advance=False): 3887 kwargs["where"] = self._parse_where() 3888 elif self._match(TokenType.ORDER_BY, advance=False): 3889 kwargs["order"] = self._parse_order() 3890 elif self._match(TokenType.LIMIT, advance=False): 3891 kwargs["limit"] = self._parse_limit() 3892 else: 3893 break 3894 3895 return self.expression(exp.Update(**kwargs)) 3896 3897 def _parse_use(self) -> exp.Use: 3898 return self.expression( 3899 exp.Use( 3900 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 3901 this=self._parse_table(schema=False), 3902 ) 3903 ) 3904 3905 def _parse_uncache(self) -> exp.Uncache: 3906 if not self._match(TokenType.TABLE): 3907 self.raise_error("Expecting TABLE after UNCACHE") 3908 3909 return self.expression( 3910 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 3911 ) 3912 3913 def _parse_cache(self) -> exp.Cache: 3914 lazy = self._match_text_seq("LAZY") 3915 self._match(TokenType.TABLE) 3916 table = self._parse_table(schema=True) 3917 3918 options = [] 3919 if self._match_text_seq("OPTIONS"): 3920 self._match_l_paren() 3921 k = self._parse_string() 3922 self._match(TokenType.EQ) 3923 v = self._parse_string() 3924 options = [k, v] 3925 self._match_r_paren() 3926 3927 self._match(TokenType.ALIAS) 3928 return self.expression( 3929 exp.Cache( 3930 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 3931 ) 3932 ) 3933 3934 def _parse_partition(self) -> exp.Partition | None: 3935 if not self._match_texts(self.PARTITION_KEYWORDS): 3936 return None 3937 3938 return self.expression( 3939 exp.Partition( 3940 subpartition=self._prev.text.upper() == "SUBPARTITION", 3941 expressions=self._parse_wrapped_csv(self._parse_disjunction), 3942 ) 3943 ) 3944 3945 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 3946 def _parse_value_expression() -> exp.Expr | None: 3947 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3948 return exp.var(self._prev.text.upper()) 3949 return self._parse_expression() 3950 3951 if self._match(TokenType.L_PAREN): 3952 expressions = self._parse_csv(_parse_value_expression) 3953 self._match_r_paren() 3954 return self.expression(exp.Tuple(expressions=expressions)) 3955 3956 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 3957 expression = self._parse_expression() 3958 if expression: 3959 return self.expression(exp.Tuple(expressions=[expression])) 3960 return None 3961 3962 def _parse_projections( 3963 self, 3964 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 3965 return self._parse_expressions(), None 3966 3967 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 3968 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 3969 this: exp.Expr | None = self._parse_simplified_pivot( 3970 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 3971 ) 3972 elif self._match(TokenType.FROM): 3973 from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True) 3974 # Support parentheses for duckdb FROM-first syntax 3975 select = self._parse_select(from_=from_) 3976 if select: 3977 if not select.args.get("from_"): 3978 select.set("from_", from_) 3979 this = select 3980 else: 3981 this = exp.select("*").from_(t.cast(exp.From, from_)) 3982 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3983 else: 3984 this = ( 3985 self._parse_table(consume_pipe=True) 3986 if table 3987 else self._parse_select(nested=True, parse_set_operation=False) 3988 ) 3989 3990 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 3991 # in case a modifier (e.g. join) is following 3992 if table and isinstance(this, exp.Values) and this.alias: 3993 alias = this.args["alias"].pop() 3994 this = exp.Table(this=this, alias=alias) 3995 3996 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3997 3998 return this 3999 4000 def _parse_select( 4001 self, 4002 nested: bool = False, 4003 table: bool = False, 4004 parse_subquery_alias: bool = True, 4005 parse_set_operation: bool = True, 4006 consume_pipe: bool = True, 4007 from_: exp.From | None = None, 4008 ) -> exp.Expr | None: 4009 query = self._parse_select_query( 4010 nested=nested, 4011 table=table, 4012 parse_subquery_alias=parse_subquery_alias, 4013 parse_set_operation=parse_set_operation, 4014 ) 4015 4016 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4017 if not query and from_: 4018 query = exp.select("*").from_(from_) 4019 if isinstance(query, exp.Query): 4020 query = self._parse_pipe_syntax_query(query) 4021 query = query.subquery(copy=False) if query and table else query 4022 4023 return query 4024 4025 def _parse_select_query( 4026 self, 4027 nested: bool = False, 4028 table: bool = False, 4029 parse_subquery_alias: bool = True, 4030 parse_set_operation: bool = True, 4031 ) -> exp.Expr | None: 4032 cte = self._parse_with() 4033 4034 if cte: 4035 this = self._parse_statement() 4036 4037 if not this: 4038 self.raise_error("Failed to parse any statement following CTE") 4039 return cte 4040 4041 while isinstance(this, exp.Subquery) and this.is_wrapper: 4042 this = this.this 4043 4044 assert this is not None 4045 if "with_" in this.arg_types: 4046 if inner_cte := this.args.get("with_"): 4047 cte.set("expressions", cte.expressions + inner_cte.expressions) 4048 if inner_cte.args.get("recursive"): 4049 cte.set("recursive", True) 4050 this.set("with_", cte) 4051 else: 4052 self.raise_error(f"{this.key} does not support CTE") 4053 this = cte 4054 4055 return this 4056 4057 # duckdb supports leading with FROM x 4058 from_ = ( 4059 self._parse_from(joins=True, consume_pipe=True) 4060 if self._match(TokenType.FROM, advance=False) 4061 else None 4062 ) 4063 4064 if self._match(TokenType.SELECT): 4065 comments = self._prev_comments 4066 4067 hint = self._parse_hint() 4068 4069 if self._next and not self._next.token_type == TokenType.DOT: 4070 all_ = self._match(TokenType.ALL) 4071 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4072 else: 4073 all_, matched_distinct = None, False 4074 4075 kind = ( 4076 self._prev.text.upper() 4077 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4078 else None 4079 ) 4080 4081 distinct: exp.Expr | None = ( 4082 self.expression( 4083 exp.Distinct( 4084 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4085 ) 4086 ) 4087 if matched_distinct 4088 else None 4089 ) 4090 4091 operation_modifiers = [] 4092 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4093 operation_modifiers.append(exp.var(self._prev.text.upper())) 4094 4095 limit = self._parse_limit(top=True) 4096 4097 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4098 if limit and not matched_distinct and not all_: 4099 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4100 if matched_distinct: 4101 distinct = self.expression( 4102 exp.Distinct( 4103 on=self._parse_value(values=False) 4104 if self._match(TokenType.ON) 4105 else None 4106 ) 4107 ) 4108 else: 4109 all_ = self._match(TokenType.ALL) 4110 4111 if all_ and distinct: 4112 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4113 4114 projections, exclude = self._parse_projections() 4115 4116 this = self.expression( 4117 exp.Select( 4118 kind=kind, 4119 hint=hint, 4120 distinct=distinct, 4121 expressions=projections, 4122 limit=limit, 4123 exclude=exclude, 4124 operation_modifiers=operation_modifiers or None, 4125 ) 4126 ) 4127 this.comments = comments 4128 4129 into = self._parse_into() 4130 if into: 4131 this.set("into", into) 4132 4133 if not from_: 4134 from_ = self._parse_from() 4135 4136 if from_: 4137 this.set("from_", from_) 4138 4139 this = self._parse_query_modifiers(this) 4140 elif (table or nested) and self._match(TokenType.L_PAREN): 4141 comments = self._prev_comments 4142 this = self._parse_wrapped_select(table=table) 4143 4144 if this: 4145 this.add_comments(comments, prepend=True) 4146 4147 # We return early here so that the UNION isn't attached to the subquery by the 4148 # following call to _parse_set_operations, but instead becomes the parent node 4149 self._match_r_paren() 4150 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4151 elif self._match(TokenType.VALUES, advance=False): 4152 this = self._parse_derived_table_values() 4153 elif from_: 4154 this = exp.select("*").from_(from_.this, copy=False) 4155 this = self._parse_query_modifiers(this) 4156 elif self._match(TokenType.SUMMARIZE): 4157 table = self._match(TokenType.TABLE) 4158 this = self._parse_select() or self._parse_string() or self._parse_table() 4159 return self.expression(exp.Summarize(this=this, table=table)) 4160 elif self._match(TokenType.DESCRIBE): 4161 this = self._parse_describe() 4162 else: 4163 this = None 4164 4165 return self._parse_set_operations(this) if parse_set_operation else this 4166 4167 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4168 self._match_text_seq("SEARCH") 4169 4170 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4171 4172 if not kind: 4173 return None 4174 4175 self._match_text_seq("FIRST", "BY") 4176 4177 return self.expression( 4178 exp.RecursiveWithSearch( 4179 kind=kind, 4180 this=self._parse_id_var(), 4181 expression=self._match_text_seq("SET") and self._parse_id_var(), 4182 using=self._match_text_seq("USING") and self._parse_id_var(), 4183 ) 4184 ) 4185 4186 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4187 if not skip_with_token and not self._match(TokenType.WITH): 4188 return None 4189 4190 comments = self._prev_comments 4191 recursive = self._match(TokenType.RECURSIVE) 4192 4193 last_comments = None 4194 expressions = [] 4195 udfs = [] 4196 while True: 4197 cte = self._parse_cte() 4198 if cte: 4199 if isinstance(cte, exp.FunctionSpecification): 4200 udfs.append(cte) 4201 else: 4202 expressions.append(cte) 4203 4204 if last_comments: 4205 cte.add_comments(last_comments) 4206 4207 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4208 break 4209 else: 4210 self._match(TokenType.WITH) 4211 recursive = self._match(TokenType.RECURSIVE) or recursive 4212 4213 last_comments = self._prev_comments 4214 4215 return self.expression( 4216 exp.With( 4217 expressions=expressions, 4218 recursive=recursive or None, 4219 search=self._parse_recursive_with_search(), 4220 udfs=udfs or None, 4221 ), 4222 comments=comments, 4223 ) 4224 4225 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4226 index = self._index 4227 4228 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4229 if not alias or not alias.this: 4230 self.raise_error("Expected CTE to have alias") 4231 4232 key_expressions = ( 4233 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4234 ) 4235 4236 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4237 self._retreat(index) 4238 return None 4239 4240 comments = self._prev_comments 4241 4242 if self._match_text_seq("NOT", "MATERIALIZED"): 4243 materialized = False 4244 elif self._match_text_seq("MATERIALIZED"): 4245 materialized = True 4246 else: 4247 materialized = None 4248 4249 cte = self.expression( 4250 exp.CTE( 4251 this=self._parse_wrapped(self._parse_statement), 4252 alias=alias, 4253 materialized=materialized, 4254 key_expressions=key_expressions, 4255 ), 4256 comments=comments, 4257 ) 4258 4259 values = cte.this 4260 if isinstance(values, exp.Values): 4261 cte.set("this", self._values_to_select(values)) 4262 4263 return cte 4264 4265 def _values_to_select(self, values: exp.Values) -> exp.Select: 4266 if values.alias: 4267 return exp.select("*").from_(values) 4268 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4269 4270 def _parse_table_alias( 4271 self, alias_tokens: t.Collection[TokenType] | None = None 4272 ) -> exp.TableAlias | None: 4273 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4274 # so this section tries to parse the clause version and if it fails, it treats the token 4275 # as an identifier (alias) 4276 if self._can_parse_limit_or_offset(): 4277 return None 4278 4279 # START is never treated as an implicit alias when followed by WITH, since that 4280 # would swallow the beginning of a START WITH ... CONNECT BY clause 4281 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4282 return None 4283 4284 any_token = self._match(TokenType.ALIAS) 4285 alias = ( 4286 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4287 or self._parse_string_as_identifier() 4288 ) 4289 4290 index = self._index 4291 if self._match(TokenType.L_PAREN): 4292 columns = self._parse_csv(self._parse_function_parameter) 4293 self._match_r_paren() if columns else self._retreat(index) 4294 else: 4295 columns = None 4296 4297 if not alias and not columns: 4298 return None 4299 4300 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4301 4302 # We bubble up comments from the Identifier to the TableAlias 4303 if isinstance(alias, exp.Identifier): 4304 table_alias.add_comments(alias.pop_comments()) 4305 4306 return table_alias 4307 4308 def _parse_subquery( 4309 self, this: exp.Expr | None, parse_alias: bool = True 4310 ) -> exp.Subquery | None: 4311 if not this: 4312 return None 4313 4314 return self.expression( 4315 exp.Subquery( 4316 this=this, 4317 pivots=self._parse_pivots(), 4318 alias=self._parse_table_alias() if parse_alias else None, 4319 sample=self._parse_table_sample(), 4320 ) 4321 ) 4322 4323 def _implicit_unnests_to_explicit(self, this: E) -> E: 4324 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4325 4326 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4327 for i, join in enumerate(this.args.get("joins") or []): 4328 table = join.this 4329 normalized_table = table.copy() 4330 normalized_table.meta["maybe_column"] = True 4331 normalized_table = _norm(normalized_table, dialect=self.dialect) 4332 4333 if isinstance(table, exp.Table) and not join.args.get("on"): 4334 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4335 table_as_column = table.to_column() 4336 unnest = exp.Unnest(expressions=[table_as_column]) 4337 4338 # Table.to_column creates a parent Alias node that we want to convert to 4339 # a TableAlias and attach to the Unnest, so it matches the parser's output 4340 if isinstance(table.args.get("alias"), exp.TableAlias): 4341 table_as_column.replace(table_as_column.this) 4342 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4343 4344 table.replace(unnest) 4345 4346 refs.add(normalized_table.alias_or_name) 4347 4348 return this 4349 4350 @t.overload 4351 def _parse_query_modifiers(self, this: E) -> E: ... 4352 4353 @t.overload 4354 def _parse_query_modifiers(self, this: None) -> None: ... 4355 4356 def _parse_query_modifiers(self, this): 4357 if isinstance(this, self.MODIFIABLES): 4358 for join in self._parse_joins(): 4359 this.append("joins", join) 4360 for lateral in iter(self._parse_lateral, None): 4361 this.append("laterals", lateral) 4362 4363 while True: 4364 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4365 modifier_token = self._curr 4366 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4367 key, expression = parser(self) 4368 4369 if expression: 4370 if this.args.get(key): 4371 self.raise_error( 4372 f"Found multiple '{modifier_token.text.upper()}' clauses", 4373 token=modifier_token, 4374 ) 4375 4376 this.set(key, expression) 4377 if key == "limit": 4378 offset = expression.args.get("offset") 4379 expression.set("offset", None) 4380 4381 if offset: 4382 offset = exp.Offset(expression=offset) 4383 this.set("offset", offset) 4384 4385 limit_by_expressions = expression.expressions 4386 expression.set("expressions", None) 4387 offset.set("expressions", limit_by_expressions) 4388 continue 4389 4390 if self._curr.text.upper() == "START": 4391 modifier_token = self._curr 4392 connect = self._parse_connect() 4393 if connect: 4394 if this.args.get("connect"): 4395 self.raise_error( 4396 "Found multiple 'START WITH' clauses", token=modifier_token 4397 ) 4398 4399 this.set("connect", connect) 4400 continue 4401 break 4402 4403 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4404 this = self._implicit_unnests_to_explicit(this) 4405 4406 return this 4407 4408 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4409 start = self._curr 4410 while self._curr: 4411 self._advance() 4412 4413 end = self._tokens[self._index - 1] 4414 return exp.Hint(expressions=[self._find_sql(start, end)]) 4415 4416 def _parse_hint_function_call(self) -> exp.Expr | None: 4417 return self._parse_function_call() 4418 4419 def _parse_hint_body(self) -> exp.Hint | None: 4420 start_index = self._index 4421 should_fallback_to_string = False 4422 4423 hints = [] 4424 try: 4425 for hint in iter( 4426 lambda: self._parse_csv( 4427 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4428 ), 4429 [], 4430 ): 4431 hints.extend(hint) 4432 except ParseError: 4433 should_fallback_to_string = True 4434 4435 if should_fallback_to_string or self._curr: 4436 self._retreat(start_index) 4437 return self._parse_hint_fallback_to_string() 4438 4439 return self.expression(exp.Hint(expressions=hints)) 4440 4441 def _parse_hint(self) -> exp.Hint | None: 4442 if self._match(TokenType.HINT) and self._prev_comments: 4443 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4444 4445 return None 4446 4447 def _parse_into(self) -> exp.Into | None: 4448 if not self._match(TokenType.INTO): 4449 return None 4450 4451 temp = self._match(TokenType.TEMPORARY) 4452 unlogged = self._match_text_seq("UNLOGGED") 4453 self._match(TokenType.TABLE) 4454 4455 return self.expression( 4456 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4457 ) 4458 4459 def _parse_from( 4460 self, 4461 joins: bool = False, 4462 skip_from_token: bool = False, 4463 consume_pipe: bool = False, 4464 ) -> exp.From | None: 4465 if not skip_from_token and not self._match(TokenType.FROM): 4466 return None 4467 4468 comments = self._prev_comments 4469 return self.expression( 4470 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4471 comments=comments, 4472 ) 4473 4474 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4475 return self.expression( 4476 exp.MatchRecognizeMeasure( 4477 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4478 this=self._parse_expression(), 4479 ) 4480 ) 4481 4482 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4483 if not self._match(TokenType.MATCH_RECOGNIZE): 4484 return None 4485 4486 self._match_l_paren() 4487 4488 partition = self._parse_partition_by() 4489 order = self._parse_order() 4490 4491 measures = ( 4492 self._parse_csv(self._parse_match_recognize_measure) 4493 if self._match_text_seq("MEASURES") 4494 else None 4495 ) 4496 4497 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4498 rows = exp.var("ONE ROW PER MATCH") 4499 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4500 text = "ALL ROWS PER MATCH" 4501 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4502 text += " SHOW EMPTY MATCHES" 4503 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4504 text += " OMIT EMPTY MATCHES" 4505 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4506 text += " WITH UNMATCHED ROWS" 4507 rows = exp.var(text) 4508 else: 4509 rows = None 4510 4511 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4512 text = "AFTER MATCH SKIP" 4513 if self._match_text_seq("PAST", "LAST", "ROW"): 4514 text += " PAST LAST ROW" 4515 elif self._match_text_seq("TO", "NEXT", "ROW"): 4516 text += " TO NEXT ROW" 4517 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4518 direction = self._prev.text.upper() 4519 pattern_var = self._advance_any() 4520 if not pattern_var: 4521 self.raise_error( 4522 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4523 ) 4524 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4525 after = exp.var(text) 4526 else: 4527 after = None 4528 4529 if self._match_text_seq("PATTERN"): 4530 self._match_l_paren() 4531 4532 if not self._curr: 4533 self.raise_error("Expecting )", self._curr) 4534 4535 paren = 1 4536 start = self._curr 4537 4538 while self._curr and paren > 0: 4539 if self._curr.token_type == TokenType.L_PAREN: 4540 paren += 1 4541 if self._curr.token_type == TokenType.R_PAREN: 4542 paren -= 1 4543 4544 end = self._prev 4545 self._advance() 4546 4547 if paren > 0: 4548 self.raise_error("Expecting )", self._curr) 4549 4550 pattern = exp.var(self._find_sql(start, end)) 4551 else: 4552 pattern = None 4553 4554 define = ( 4555 self._parse_csv(self._parse_name_as_expression) 4556 if self._match_text_seq("DEFINE") 4557 else None 4558 ) 4559 4560 self._match_r_paren() 4561 4562 return self.expression( 4563 exp.MatchRecognize( 4564 partition_by=partition, 4565 order=order, 4566 measures=measures, 4567 rows=rows, 4568 after=after, 4569 pattern=pattern, 4570 define=define, 4571 alias=self._parse_table_alias(), 4572 ) 4573 ) 4574 4575 def _parse_lateral(self) -> exp.Lateral | None: 4576 cross_apply: bool | None = None 4577 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4578 cross_apply = True 4579 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4580 cross_apply = False 4581 4582 if cross_apply is not None: 4583 this = self._parse_select(table=True) 4584 view = None 4585 outer = None 4586 elif self._match(TokenType.LATERAL): 4587 this = self._parse_select(table=True) 4588 view = self._match(TokenType.VIEW) 4589 outer = self._match(TokenType.OUTER) 4590 else: 4591 return None 4592 4593 if not this: 4594 this = ( 4595 self._parse_unnest() 4596 or self._parse_function() 4597 or self._parse_id_var(any_token=False) 4598 ) 4599 4600 while self._match(TokenType.DOT): 4601 this = exp.Dot( 4602 this=this, 4603 expression=self._parse_function() or self._parse_id_var(any_token=False), 4604 ) 4605 4606 ordinality: bool | None = None 4607 4608 if view: 4609 table = self._parse_id_var(any_token=False) 4610 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4611 table_alias: exp.TableAlias | None = self.expression( 4612 exp.TableAlias(this=table, columns=columns) 4613 ) 4614 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4615 # We move the alias from the lateral's child node to the lateral itself 4616 table_alias = this.args["alias"].pop() 4617 else: 4618 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4619 table_alias = self._parse_table_alias() 4620 4621 return self.expression( 4622 exp.Lateral( 4623 this=this, 4624 view=view, 4625 outer=outer, 4626 alias=table_alias, 4627 cross_apply=cross_apply, 4628 ordinality=ordinality, 4629 ) 4630 ) 4631 4632 def _parse_stream(self) -> exp.Stream | None: 4633 index = self._index 4634 if self._match(TokenType.STREAM): 4635 if this := self._try_parse(self._parse_table): 4636 return self.expression(exp.Stream(this=this)) 4637 self._retreat(index) 4638 return None 4639 4640 def _parse_join_parts( 4641 self, 4642 ) -> tuple[Token | None, Token | None, Token | None]: 4643 return ( 4644 self._prev if self._match_set(self.JOIN_METHODS) else None, 4645 self._prev if self._match_set(self.JOIN_SIDES) else None, 4646 self._prev if self._match_set(self.JOIN_KINDS) else None, 4647 ) 4648 4649 def _parse_using_identifiers(self) -> list[exp.Expr]: 4650 def _parse_column_as_identifier() -> exp.Expr | None: 4651 this = self._parse_column() 4652 if isinstance(this, exp.Column): 4653 return this.this 4654 return this 4655 4656 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4657 4658 def _parse_join( 4659 self, 4660 skip_join_token: bool = False, 4661 parse_bracket: bool = False, 4662 alias_tokens: t.Collection[TokenType] | None = None, 4663 ) -> exp.Join | None: 4664 if self._match(TokenType.COMMA): 4665 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4666 cross_join = self.expression(exp.Join(this=table)) if table else None 4667 4668 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4669 cross_join.set("kind", "CROSS") 4670 4671 return cross_join 4672 4673 index = self._index 4674 method, side, kind = self._parse_join_parts() 4675 directed = self._match_text_seq("DIRECTED") 4676 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4677 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4678 join_comments = self._prev_comments 4679 4680 if not skip_join_token and not join: 4681 self._retreat(index) 4682 kind = None 4683 method = None 4684 side = None 4685 4686 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4687 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4688 4689 if not skip_join_token and not join and not outer_apply and not cross_apply: 4690 return None 4691 4692 kwargs: dict[str, t.Any] = { 4693 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4694 } 4695 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4696 kwargs["expressions"] = self._parse_csv( 4697 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4698 ) 4699 4700 if method: 4701 kwargs["method"] = method.text.upper() 4702 if side: 4703 kwargs["side"] = side.text.upper() 4704 if kind: 4705 kwargs["kind"] = kind.text.upper() 4706 if hint: 4707 kwargs["hint"] = hint 4708 4709 if self._match(TokenType.MATCH_CONDITION): 4710 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4711 4712 if self._match(TokenType.ON): 4713 kwargs["on"] = self._parse_disjunction() 4714 elif self._match(TokenType.USING): 4715 kwargs["using"] = self._parse_using_identifiers() 4716 elif ( 4717 not method 4718 and not (outer_apply or cross_apply) 4719 and not isinstance(kwargs["this"], exp.Unnest) 4720 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4721 ): 4722 index = self._index 4723 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4724 4725 if joins and self._match(TokenType.ON): 4726 kwargs["on"] = self._parse_disjunction() 4727 elif joins and self._match(TokenType.USING): 4728 kwargs["using"] = self._parse_using_identifiers() 4729 else: 4730 joins = None 4731 self._retreat(index) 4732 4733 kwargs["this"].set("joins", joins if joins else None) 4734 4735 kwargs["pivots"] = self._parse_pivots() 4736 4737 comments = [c for token in (method, side, kind) if token for c in token.comments] 4738 comments = (join_comments or []) + comments 4739 4740 if ( 4741 self.ADD_JOIN_ON_TRUE 4742 and not kwargs.get("on") 4743 and not kwargs.get("using") 4744 and not kwargs.get("method") 4745 and kwargs.get("kind") in (None, "INNER", "OUTER") 4746 ): 4747 kwargs["on"] = exp.true() 4748 4749 if directed: 4750 kwargs["directed"] = directed 4751 4752 return self.expression(exp.Join(**kwargs), comments=comments) 4753 4754 def _parse_opclass(self) -> exp.Expr | None: 4755 this = self._parse_disjunction() 4756 4757 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4758 return this 4759 4760 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4761 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4762 4763 return this 4764 4765 def _parse_index_params(self) -> exp.IndexParameters: 4766 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4767 4768 if self._match(TokenType.L_PAREN, advance=False): 4769 columns = self._parse_wrapped_csv(self._parse_with_operator) 4770 else: 4771 columns = None 4772 4773 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4774 partition_by = self._parse_partition_by() 4775 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4776 tablespace = ( 4777 self._parse_var(any_token=True) 4778 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4779 else None 4780 ) 4781 where = self._parse_where() 4782 4783 on = self._parse_field() if self._match(TokenType.ON) else None 4784 4785 return self.expression( 4786 exp.IndexParameters( 4787 using=using, 4788 columns=columns, 4789 include=include, 4790 partition_by=partition_by, 4791 where=where, 4792 with_storage=with_storage, 4793 tablespace=tablespace, 4794 on=on, 4795 ) 4796 ) 4797 4798 def _parse_index( 4799 self, index: exp.Expr | None = None, anonymous: bool = False 4800 ) -> exp.Index | None: 4801 if index or anonymous: 4802 unique = None 4803 primary = None 4804 amp = None 4805 4806 self._match(TokenType.ON) 4807 self._match(TokenType.TABLE) # hive 4808 table = self._parse_table_parts(schema=True) 4809 else: 4810 unique = self._match(TokenType.UNIQUE) 4811 primary = self._match_text_seq("PRIMARY") 4812 amp = self._match_text_seq("AMP") 4813 4814 if not self._match(TokenType.INDEX): 4815 return None 4816 4817 index = self._parse_id_var() 4818 table = None 4819 4820 params = self._parse_index_params() 4821 4822 return self.expression( 4823 exp.Index( 4824 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4825 ) 4826 ) 4827 4828 def _parse_table_hints(self) -> list[exp.Expr] | None: 4829 hints: list[exp.Expr] = [] 4830 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4831 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4832 hints.append( 4833 self.expression( 4834 exp.WithTableHint( 4835 expressions=self._parse_csv( 4836 lambda: self._parse_function() or self._parse_var(any_token=True) 4837 ) 4838 ) 4839 ) 4840 ) 4841 self._match_r_paren() 4842 else: 4843 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4844 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4845 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4846 4847 self._match_set((TokenType.INDEX, TokenType.KEY)) 4848 if self._match(TokenType.FOR): 4849 hint.set("target", self._advance_any() and self._prev.text.upper()) 4850 4851 hint.set("expressions", self._parse_wrapped_id_vars()) 4852 hints.append(hint) 4853 4854 return hints or None 4855 4856 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 4857 return ( 4858 (not schema and self._parse_function(optional_parens=False)) 4859 or self._parse_id_var(any_token=False) 4860 or self._parse_string_as_identifier() 4861 or self._parse_placeholder() 4862 ) 4863 4864 def _parse_table_parts_fast(self) -> exp.Table | None: 4865 index = self._index 4866 parts: list[exp.Identifier] | None = None 4867 all_comments: list[str] | None = None 4868 4869 while self._match_set(self.IDENTIFIER_TOKENS): 4870 token = self._prev 4871 comments = self._prev_comments 4872 4873 has_dot = self._match(TokenType.DOT) 4874 curr_tt = self._curr.token_type 4875 4876 if not has_dot: 4877 if curr_tt in self.TABLE_POSTFIX_TOKENS: 4878 self._retreat(index) 4879 return None 4880 elif curr_tt not in self.IDENTIFIER_TOKENS: 4881 self._retreat(index) 4882 return None 4883 4884 if parts is None: 4885 parts = [] 4886 4887 if comments: 4888 if all_comments is None: 4889 all_comments = [] 4890 all_comments.extend(comments) 4891 self._prev_comments = [] 4892 4893 parts.append( 4894 self.expression( 4895 exp.Identifier( 4896 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 4897 ), 4898 token, 4899 ) 4900 ) 4901 4902 if not has_dot: 4903 break 4904 4905 if parts is None: 4906 return None 4907 4908 n = len(parts) 4909 4910 if n == 1: 4911 table: exp.Table = exp.Table(this=parts[0]) 4912 elif n == 2: 4913 table = exp.Table(this=parts[1], db=parts[0]) 4914 elif n >= 3: 4915 this: exp.Identifier | exp.Dot = parts[2] 4916 for i in range(3, n): 4917 this = exp.Dot(this=this, expression=parts[i]) 4918 4919 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 4920 4921 if table is None: 4922 self._retreat(index) 4923 elif all_comments: 4924 table.add_comments(all_comments) 4925 return table 4926 4927 def _parse_table_parts( 4928 self, 4929 schema: bool = False, 4930 is_db_reference: bool = False, 4931 wildcard: bool = False, 4932 fast: bool = False, 4933 ) -> exp.Table | exp.Dot | None: 4934 if fast: 4935 return self._parse_table_parts_fast() 4936 4937 catalog: exp.Expr | str | None = None 4938 db: exp.Expr | str | None = None 4939 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 4940 4941 while self._match(TokenType.DOT): 4942 if catalog: 4943 # This allows nesting the table in arbitrarily many dot expressions if needed 4944 table = self.expression( 4945 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 4946 ) 4947 else: 4948 catalog = db 4949 db = table 4950 # "" used for tsql FROM a..b case 4951 table = self._parse_table_part(schema=schema) or "" 4952 4953 if ( 4954 wildcard 4955 and self._is_connected() 4956 and (isinstance(table, exp.Identifier) or not table) 4957 and self._match(TokenType.STAR) 4958 ): 4959 if isinstance(table, exp.Identifier): 4960 table.args["this"] += "*" 4961 else: 4962 table = exp.Identifier(this="*") 4963 4964 if is_db_reference: 4965 catalog = db 4966 db = table 4967 table = None 4968 4969 if not table and not is_db_reference: 4970 self.raise_error(f"Expected table name but got {self._curr}") 4971 if not db and is_db_reference: 4972 self.raise_error(f"Expected database name but got {self._curr}") 4973 4974 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 4975 4976 # Bubble up comments from identifier parts to the Table 4977 comments = [] 4978 for part in table.parts: 4979 if part_comments := part.pop_comments(): 4980 comments.extend(part_comments) 4981 if comments: 4982 table.add_comments(comments) 4983 4984 changes = self._parse_changes() 4985 if changes: 4986 table.set("changes", changes) 4987 4988 at_before = self._parse_historical_data() 4989 if at_before: 4990 table.set("when", at_before) 4991 4992 pivots = self._parse_pivots() 4993 if pivots: 4994 table.set("pivots", pivots) 4995 4996 return table 4997 4998 def _parse_table( 4999 self, 5000 schema: bool = False, 5001 joins: bool = False, 5002 alias_tokens: t.Collection[TokenType] | None = None, 5003 parse_bracket: bool = False, 5004 is_db_reference: bool = False, 5005 parse_partition: bool = False, 5006 consume_pipe: bool = False, 5007 ) -> exp.Expr | None: 5008 if not schema and not is_db_reference and not consume_pipe and not joins: 5009 index = self._index 5010 table = self._parse_table_parts(fast=True) 5011 5012 if table is not None: 5013 curr_tt = self._curr.token_type 5014 next_tt = self._next.token_type 5015 5016 fast_terminators = self.TABLE_TERMINATORS 5017 5018 # only return the table if we're sure there are no other operators 5019 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5020 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5021 return table 5022 5023 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5024 5025 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5026 if alias := self._parse_table_alias( 5027 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5028 ): 5029 table.set("alias", alias) 5030 5031 if self._curr.token_type in fast_terminators: 5032 return table 5033 5034 self._retreat(index) 5035 5036 if stream := self._parse_stream(): 5037 return stream 5038 5039 if lateral := self._parse_lateral(): 5040 return lateral 5041 5042 if unnest := self._parse_unnest(): 5043 return unnest 5044 5045 if values := self._parse_derived_table_values(): 5046 return values 5047 5048 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5049 if not subquery.args.get("pivots"): 5050 subquery.set("pivots", self._parse_pivots()) 5051 if joins: 5052 for join in self._parse_joins(): 5053 subquery.append("joins", join) 5054 return subquery 5055 5056 bracket = parse_bracket and self._parse_bracket(None) 5057 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5058 5059 rows_from_tables = ( 5060 self._parse_wrapped_csv(self._parse_table) 5061 if self._match_text_seq("ROWS", "FROM") 5062 else None 5063 ) 5064 rows_from = ( 5065 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5066 ) 5067 5068 only = self._match(TokenType.ONLY) 5069 5070 this = t.cast( 5071 exp.Expr, 5072 bracket 5073 or rows_from 5074 or self._parse_bracket( 5075 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5076 ), 5077 ) 5078 5079 if only: 5080 this.set("only", only) 5081 5082 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5083 self._match(TokenType.STAR) 5084 5085 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5086 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5087 this.set("partition", self._parse_partition()) 5088 5089 if schema: 5090 return self._parse_schema(this=this) 5091 5092 if self.dialect.ALIAS_POST_VERSION: 5093 this.set("version", self._parse_version()) 5094 5095 if self.dialect.ALIAS_POST_TABLESAMPLE: 5096 this.set("sample", self._parse_table_sample()) 5097 5098 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5099 if alias: 5100 this.set("alias", alias) 5101 5102 # DuckDB requires the time-travel clause to come after the alias, e.g. 5103 # SELECT * FROM t AS a AT (VERSION => 1) 5104 if isinstance(this, exp.Table) and not this.args.get("when"): 5105 this.set("when", self._parse_historical_data()) 5106 5107 if self._match(TokenType.INDEXED_BY): 5108 this.set("indexed", self._parse_table_parts()) 5109 elif self._match_text_seq("NOT", "INDEXED"): 5110 this.set("indexed", False) 5111 5112 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5113 return self.expression( 5114 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5115 ) 5116 5117 this.set("hints", self._parse_table_hints()) 5118 5119 if not this.args.get("pivots"): 5120 this.set("pivots", self._parse_pivots()) 5121 5122 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5123 this.set("sample", self._parse_table_sample()) 5124 5125 if not self.dialect.ALIAS_POST_VERSION: 5126 this.set("version", self._parse_version()) 5127 5128 if joins: 5129 for join in self._parse_joins(alias_tokens=alias_tokens): 5130 this.append("joins", join) 5131 5132 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5133 this.set("ordinality", True) 5134 this.set("alias", self._parse_table_alias()) 5135 5136 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5137 # hoist the table args onto the latter and return it instead 5138 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5139 table_from_rows = this.this 5140 for arg in exp.TableFromRows.arg_types: 5141 if arg != "this": 5142 table_from_rows.set(arg, this.args.get(arg)) 5143 5144 this = table_from_rows 5145 5146 return this 5147 5148 def _parse_version(self) -> exp.Version | None: 5149 for phrase, this in self.VERSION_PHRASES.items(): 5150 if self._match_text_seq(*phrase): 5151 break 5152 else: 5153 return None 5154 5155 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5156 kind = self._prev.text.upper() 5157 start = self._parse_bitwise() 5158 self._match_texts(("TO", "AND")) 5159 end = self._parse_bitwise() 5160 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5161 elif self._match_text_seq("CONTAINED", "IN"): 5162 kind = "CONTAINED IN" 5163 expression = self.expression( 5164 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5165 ) 5166 elif self._match(TokenType.ALL): 5167 kind = "ALL" 5168 expression = None 5169 else: 5170 self._match_text_seq("AS", "OF") 5171 kind = "AS OF" 5172 expression = self._parse_type() 5173 5174 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5175 5176 def _parse_historical_data(self) -> exp.HistoricalData | None: 5177 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5178 index = self._index 5179 historical_data = None 5180 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5181 this = self._prev.text.upper() 5182 kind = ( 5183 self._match(TokenType.L_PAREN) 5184 and self._match_texts(self.HISTORICAL_DATA_KIND) 5185 and self._prev.text.upper() 5186 ) 5187 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5188 5189 if expression: 5190 self._match_r_paren() 5191 historical_data = self.expression( 5192 exp.HistoricalData(this=this, kind=kind, expression=expression) 5193 ) 5194 else: 5195 self._retreat(index) 5196 5197 return historical_data 5198 5199 def _parse_changes(self) -> exp.Changes | None: 5200 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5201 return None 5202 5203 information = self._parse_var(any_token=True) 5204 self._match_r_paren() 5205 5206 return self.expression( 5207 exp.Changes( 5208 information=information, 5209 at_before=self._parse_historical_data(), 5210 end=self._parse_historical_data(), 5211 ) 5212 ) 5213 5214 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5215 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5216 return None 5217 5218 self._advance() 5219 5220 expressions = self._parse_wrapped_csv(self._parse_equality) 5221 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5222 5223 alias = self._parse_table_alias() if with_alias else None 5224 5225 if alias: 5226 if self.dialect.UNNEST_COLUMN_ONLY: 5227 if alias.args.get("columns"): 5228 self.raise_error("Unexpected extra column alias in unnest.") 5229 5230 alias.set("columns", [alias.this]) 5231 alias.set("this", None) 5232 5233 columns = alias.args.get("columns") or [] 5234 if offset and len(expressions) < len(columns): 5235 offset = columns.pop() 5236 5237 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5238 self._match(TokenType.ALIAS) 5239 offset = self._parse_id_var( 5240 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5241 ) or exp.to_identifier("offset") 5242 5243 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5244 5245 def _parse_derived_table_values(self) -> exp.Values | None: 5246 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5247 if not is_derived and not ( 5248 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5249 self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES") 5250 ): 5251 return None 5252 5253 expressions = self._parse_csv(self._parse_value) 5254 alias = self._parse_table_alias() 5255 5256 if is_derived: 5257 self._match_r_paren() 5258 5259 return self.expression( 5260 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5261 ) 5262 5263 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5264 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5265 as_modifier and self._match_text_seq("USING", "SAMPLE") 5266 ): 5267 return None 5268 5269 bucket_numerator = None 5270 bucket_denominator = None 5271 bucket_field = None 5272 percent = None 5273 size = None 5274 seed = None 5275 5276 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5277 matched_l_paren = self._match(TokenType.L_PAREN) 5278 5279 if self.TABLESAMPLE_CSV: 5280 num = None 5281 expressions = self._parse_csv(self._parse_primary) 5282 else: 5283 expressions = None 5284 num = ( 5285 self._parse_factor(parse_mod=False) 5286 if self._match(TokenType.NUMBER, advance=False) 5287 else self._parse_primary() or self._parse_placeholder() 5288 ) 5289 5290 if self._match_text_seq("BUCKET"): 5291 bucket_numerator = self._parse_number() 5292 self._match_text_seq("OUT", "OF") 5293 bucket_denominator = bucket_denominator = self._parse_number() 5294 self._match(TokenType.ON) 5295 bucket_field = self._parse_field() 5296 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5297 percent = num 5298 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5299 size = num 5300 else: 5301 percent = num 5302 5303 if matched_l_paren: 5304 self._match_r_paren() 5305 5306 if self._match(TokenType.L_PAREN): 5307 method = self._parse_var(upper=True) 5308 seed = self._match(TokenType.COMMA) and self._parse_number() 5309 self._match_r_paren() 5310 elif self._match_texts(("SEED", "REPEATABLE")): 5311 seed = self._parse_wrapped(self._parse_number) 5312 5313 if not method and self.DEFAULT_SAMPLING_METHOD: 5314 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5315 5316 return self.expression( 5317 exp.TableSample( 5318 expressions=expressions, 5319 method=method, 5320 bucket_numerator=bucket_numerator, 5321 bucket_denominator=bucket_denominator, 5322 bucket_field=bucket_field, 5323 percent=percent, 5324 size=size, 5325 seed=seed, 5326 ) 5327 ) 5328 5329 def _parse_pivots(self) -> list[exp.Pivot] | None: 5330 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5331 return None 5332 return list(iter(self._parse_pivot, None)) or None 5333 5334 def _parse_joins( 5335 self, alias_tokens: t.Collection[TokenType] | None = None 5336 ) -> t.Iterator[exp.Join]: 5337 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5338 5339 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5340 if not self._match(TokenType.INTO): 5341 return None 5342 5343 return self.expression( 5344 exp.UnpivotColumns( 5345 this=self._match_text_seq("NAME") and self._parse_column(), 5346 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5347 ) 5348 ) 5349 5350 # https://duckdb.org/docs/sql/statements/pivot 5351 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5352 def _parse_on() -> exp.Expr | None: 5353 this = self._parse_bitwise() 5354 5355 if self._match(TokenType.IN): 5356 # PIVOT ... ON col IN (row_val1, row_val2) 5357 return self._parse_in(this) 5358 if self._match(TokenType.ALIAS, advance=False): 5359 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5360 return self._parse_alias(this) 5361 5362 return this 5363 5364 this = self._parse_table() 5365 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5366 into = self._parse_unpivot_columns() 5367 using = self._match(TokenType.USING) and self._parse_csv( 5368 lambda: self._parse_alias(self._parse_column()) 5369 ) 5370 group = self._parse_group() 5371 5372 return self.expression( 5373 exp.Pivot( 5374 this=this, 5375 expressions=expressions, 5376 using=using, 5377 group=group, 5378 unpivot=is_unpivot, 5379 into=into, 5380 ) 5381 ) 5382 5383 def _parse_pivot_in(self) -> exp.In: 5384 def _parse_aliased_expression() -> exp.Expr | None: 5385 this = self._parse_select_or_expression() 5386 5387 self._match(TokenType.ALIAS) 5388 alias = self._parse_bitwise() 5389 if alias: 5390 if isinstance(alias, exp.Column) and not alias.db: 5391 alias = alias.this 5392 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5393 5394 return this 5395 5396 value = self._parse_column() 5397 5398 if not self._match(TokenType.IN): 5399 self.raise_error("Expecting IN") 5400 5401 if self._match(TokenType.L_PAREN): 5402 if self._match(TokenType.ANY): 5403 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5404 else: 5405 exprs = self._parse_csv(_parse_aliased_expression) 5406 self._match_r_paren() 5407 return self.expression(exp.In(this=value, expressions=exprs)) 5408 5409 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5410 5411 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5412 func = self._parse_function() 5413 if not func: 5414 if self._prev.token_type == TokenType.COMMA: 5415 return None 5416 self.raise_error("Expecting an aggregation function in PIVOT") 5417 5418 return self._parse_alias(func) 5419 5420 def _parse_pivot(self) -> exp.Pivot | None: 5421 index = self._index 5422 include_nulls = None 5423 5424 if self._match(TokenType.PIVOT): 5425 unpivot = False 5426 elif self._match(TokenType.UNPIVOT): 5427 unpivot = True 5428 5429 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5430 if self._match_text_seq("INCLUDE", "NULLS"): 5431 include_nulls = True 5432 elif self._match_text_seq("EXCLUDE", "NULLS"): 5433 include_nulls = False 5434 else: 5435 return None 5436 5437 expressions = [] 5438 5439 if not self._match(TokenType.L_PAREN): 5440 self._retreat(index) 5441 return None 5442 5443 if unpivot: 5444 expressions = self._parse_csv(self._parse_column) 5445 else: 5446 expressions = self._parse_csv(self._parse_pivot_aggregation) 5447 5448 if not expressions: 5449 self.raise_error("Failed to parse PIVOT's aggregation list") 5450 5451 if not self._match(TokenType.FOR): 5452 self.raise_error("Expecting FOR") 5453 5454 fields = [] 5455 while True: 5456 field = self._try_parse(self._parse_pivot_in) 5457 if not field: 5458 break 5459 fields.append(field) 5460 5461 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5462 self._parse_bitwise 5463 ) 5464 5465 group = self._parse_group() 5466 5467 self._match_r_paren() 5468 5469 pivot = self.expression( 5470 exp.Pivot( 5471 expressions=expressions, 5472 fields=fields, 5473 unpivot=unpivot, 5474 include_nulls=include_nulls, 5475 default_on_null=default_on_null, 5476 group=group, 5477 ) 5478 ) 5479 5480 if unpivot: 5481 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5482 for pivot_field in pivot.fields: 5483 if isinstance(pivot_field, exp.In): 5484 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5485 5486 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5487 5488 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5489 pivot.set("alias", self._parse_table_alias()) 5490 5491 if not unpivot: 5492 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5493 5494 columns: list[exp.Expr] = [] 5495 all_fields = [] 5496 for pivot_field in pivot.fields: 5497 pivot_field_expressions = pivot_field.expressions 5498 5499 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5500 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5501 continue 5502 5503 all_fields.append( 5504 [ 5505 # An explicit `<field> AS <alias>` names the output column directly, 5506 # so it wins over the dialect's string-identifying convention 5507 fld.sql() 5508 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5509 else fld.alias_or_name 5510 for fld in pivot_field_expressions 5511 ] 5512 ) 5513 5514 if all_fields: 5515 if names: 5516 all_fields.append(names) 5517 5518 # Generate all possible combinations of the pivot columns 5519 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5520 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5521 for fld_parts_tuple in itertools.product(*all_fields): 5522 fld_parts = list(fld_parts_tuple) 5523 5524 if names and self.PREFIXED_PIVOT_COLUMNS: 5525 # Move the "name" to the front of the list 5526 fld_parts.insert(0, fld_parts.pop(-1)) 5527 5528 columns.append(exp.to_identifier("_".join(fld_parts))) 5529 5530 pivot.set("columns", columns) 5531 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5532 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5533 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5534 5535 return pivot 5536 5537 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5538 return [agg.alias for agg in aggregations if agg.alias] 5539 5540 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5541 if not skip_where_token and not self._match(TokenType.PREWHERE): 5542 return None 5543 5544 comments = self._prev_comments 5545 return self.expression( 5546 exp.PreWhere(this=self._parse_disjunction()), 5547 comments=comments, 5548 ) 5549 5550 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5551 if not skip_where_token and not self._match(TokenType.WHERE): 5552 return None 5553 5554 comments = self._prev_comments 5555 return self.expression( 5556 exp.Where(this=self._parse_disjunction()), 5557 comments=comments, 5558 ) 5559 5560 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5561 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5562 return None 5563 comments = self._prev_comments 5564 5565 elements: dict[str, t.Any] = defaultdict(list) 5566 5567 if self._match(TokenType.ALL): 5568 elements["all"] = True 5569 elif self._match(TokenType.DISTINCT): 5570 elements["all"] = False 5571 5572 while True: 5573 index = self._index 5574 5575 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5576 # which are also valid identifiers 5577 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5578 break 5579 5580 elements["expressions"].extend( 5581 self._parse_csv( 5582 lambda: ( 5583 None 5584 if self._match_set((TokenType.CUBE, TokenType.ROLLUP), advance=False) 5585 else self._parse_disjunction() 5586 ) 5587 ) 5588 ) 5589 grouping_sets_as_group_by_element = ( 5590 not elements["expressions"] or self._prev.token_type == TokenType.COMMA 5591 ) 5592 5593 before_with_index = self._index 5594 with_prefix = self._match(TokenType.WITH) 5595 5596 if cube_or_rollup := self._parse_cube_or_rollup(with_prefix=with_prefix): 5597 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5598 elements[key].append(cube_or_rollup) 5599 elif grouping_sets := self._parse_grouping_sets(): 5600 elements["grouping_sets"].append(grouping_sets) 5601 elements["grouping_sets_as_group_by_element"] = grouping_sets_as_group_by_element 5602 if not grouping_sets_as_group_by_element: 5603 break 5604 elif self._match_text_seq("TOTALS"): 5605 elements["totals"] = True # type: ignore 5606 5607 if before_with_index <= self._index <= before_with_index + 1: 5608 self._retreat(before_with_index) 5609 break 5610 5611 if index == self._index: 5612 break 5613 5614 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5615 5616 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5617 if self._match(TokenType.CUBE): 5618 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5619 elif self._match(TokenType.ROLLUP): 5620 kind = exp.Rollup 5621 else: 5622 return None 5623 5624 return self.expression( 5625 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5626 ) 5627 5628 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5629 if self._match(TokenType.GROUPING_SETS): 5630 return self.expression( 5631 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5632 ) 5633 return None 5634 5635 def _parse_grouping_set(self) -> exp.Expr | None: 5636 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5637 5638 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5639 if not skip_having_token and not self._match(TokenType.HAVING): 5640 return None 5641 comments = self._prev_comments 5642 return self.expression( 5643 exp.Having(this=self._parse_disjunction()), 5644 comments=comments, 5645 ) 5646 5647 def _parse_qualify(self) -> exp.Qualify | None: 5648 if not self._match(TokenType.QUALIFY): 5649 return None 5650 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5651 5652 def _parse_connect_with_prior(self) -> exp.Expr | None: 5653 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5654 exp.Prior(this=self._parse_bitwise()) 5655 ) 5656 connect = self._parse_disjunction() 5657 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5658 return connect 5659 5660 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5661 if skip_start_token: 5662 start = None 5663 elif self._match_text_seq("START", "WITH"): 5664 start = self._parse_disjunction() 5665 else: 5666 return None 5667 5668 self._match(TokenType.CONNECT_BY) 5669 nocycle = self._match_text_seq("NOCYCLE") 5670 connect = self._parse_connect_with_prior() 5671 5672 if not start and self._match_text_seq("START", "WITH"): 5673 start = self._parse_disjunction() 5674 5675 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5676 5677 def _parse_name_as_expression(self) -> exp.Expr | None: 5678 this = self._parse_id_var(any_token=True) 5679 if self._match(TokenType.ALIAS): 5680 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5681 return this 5682 5683 def _parse_interpolate(self) -> list[exp.Expr] | None: 5684 if self._match_text_seq("INTERPOLATE"): 5685 return self._parse_wrapped_csv(self._parse_name_as_expression) 5686 return None 5687 5688 def _parse_order( 5689 self, this: exp.Expr | None = None, skip_order_token: bool = False 5690 ) -> exp.Expr | None: 5691 siblings = None 5692 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5693 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5694 return this 5695 5696 siblings = True 5697 5698 comments = self._prev_comments 5699 return self.expression( 5700 exp.Order( 5701 this=this, 5702 expressions=self._parse_csv(self._parse_ordered), 5703 siblings=siblings, 5704 ), 5705 comments=comments, 5706 ) 5707 5708 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5709 if not self._match(token): 5710 return None 5711 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5712 5713 def _parse_ordered( 5714 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5715 ) -> exp.Ordered | None: 5716 this = parse_method() if parse_method else self._parse_disjunction() 5717 if not this: 5718 return None 5719 5720 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5721 this = exp.var("ALL") 5722 5723 asc = self._match(TokenType.ASC) 5724 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5725 5726 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5727 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5728 5729 nulls_first = is_nulls_first or False 5730 explicitly_null_ordered = is_nulls_first or is_nulls_last 5731 5732 if ( 5733 not explicitly_null_ordered 5734 and ( 5735 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5736 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5737 ) 5738 and self.dialect.NULL_ORDERING != "nulls_are_last" 5739 ): 5740 nulls_first = True 5741 5742 if self._match_text_seq("WITH", "FILL"): 5743 with_fill = self.expression( 5744 exp.WithFill( 5745 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5746 to=self._match_text_seq("TO") and self._parse_bitwise(), 5747 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5748 interpolate=self._parse_interpolate(), 5749 ) 5750 ) 5751 else: 5752 with_fill = None 5753 5754 return self.expression( 5755 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5756 ) 5757 5758 def _parse_limit_options(self) -> exp.LimitOptions | None: 5759 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5760 rows = self._match_set((TokenType.ROW, TokenType.ROWS)) 5761 self._match_text_seq("ONLY") 5762 with_ties = self._match_text_seq("WITH", "TIES") 5763 5764 if not (percent or rows or with_ties): 5765 return None 5766 5767 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5768 5769 def _parse_limit( 5770 self, 5771 this: exp.Expr | None = None, 5772 top: bool = False, 5773 skip_limit_token: bool = False, 5774 ) -> exp.Expr | None: 5775 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5776 comments = self._prev_comments 5777 if top: 5778 limit_paren = self._match(TokenType.L_PAREN) 5779 expression = ( 5780 self._parse_term() or self._parse_select() 5781 if limit_paren 5782 else self._parse_number() 5783 ) 5784 5785 if limit_paren: 5786 self._match_r_paren() 5787 5788 else: 5789 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5790 return this 5791 5792 expression = self._parse_term(parse_mod=False) 5793 limit_options = self._parse_limit_options() 5794 5795 if self._match(TokenType.COMMA): 5796 offset = expression 5797 expression = self._parse_term() 5798 else: 5799 offset = None 5800 5801 limit_exp = self.expression( 5802 exp.Limit( 5803 this=this, 5804 expression=expression, 5805 offset=offset, 5806 limit_options=limit_options, 5807 expressions=self._parse_limit_by(), 5808 ), 5809 comments=comments, 5810 ) 5811 5812 return limit_exp 5813 5814 if self._match(TokenType.FETCH): 5815 direction = ( 5816 self._prev.text.upper() 5817 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5818 else "FIRST" 5819 ) 5820 5821 count = self._parse_field(tokens=self.FETCH_TOKENS) 5822 5823 return self.expression( 5824 exp.Fetch( 5825 direction=direction, count=count, limit_options=self._parse_limit_options() 5826 ) 5827 ) 5828 5829 return this 5830 5831 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5832 if not self._match(TokenType.OFFSET): 5833 return this 5834 5835 count = self._parse_term() 5836 self._match_set((TokenType.ROW, TokenType.ROWS)) 5837 5838 return self.expression( 5839 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5840 ) 5841 5842 def _can_parse_limit_or_offset(self) -> bool: 5843 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5844 return False 5845 5846 index = self._index 5847 result = bool( 5848 self._try_parse(self._parse_limit, retreat=True) 5849 or self._try_parse(self._parse_offset, retreat=True) 5850 ) 5851 self._retreat(index) 5852 5853 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 5854 if self._next.token_type == TokenType.MATCH_CONDITION: 5855 result = False 5856 5857 return result 5858 5859 def _can_parse_named_window(self) -> bool: 5860 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 5861 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 5862 if not self._match(TokenType.WINDOW, advance=False): 5863 return False 5864 5865 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 5866 if name is None or name.token_type not in self.ID_VAR_TOKENS: 5867 return False 5868 5869 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 5870 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 5871 return False 5872 5873 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 5874 return body is not None and body.token_type == TokenType.L_PAREN 5875 5876 def _parse_limit_by(self) -> list[exp.Expr] | None: 5877 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 5878 5879 def _parse_locks(self) -> list[exp.Lock]: 5880 locks = [] 5881 while True: 5882 update, key = None, None 5883 if self._match_text_seq("FOR", "UPDATE"): 5884 update = True 5885 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 5886 "LOCK", "IN", "SHARE", "MODE" 5887 ): 5888 update = False 5889 elif self._match_text_seq("FOR", "KEY", "SHARE"): 5890 update, key = False, True 5891 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 5892 update, key = True, True 5893 else: 5894 break 5895 5896 expressions = None 5897 if self._match_text_seq("OF"): 5898 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 5899 5900 wait: bool | exp.Expr | None = None 5901 if self._match_text_seq("NOWAIT"): 5902 wait = True 5903 elif self._match_text_seq("WAIT"): 5904 wait = self._parse_primary() 5905 elif self._match_text_seq("SKIP", "LOCKED"): 5906 wait = False 5907 5908 locks.append( 5909 self.expression( 5910 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 5911 ) 5912 ) 5913 5914 return locks 5915 5916 def parse_set_operation( 5917 self, this: exp.Expr | None, consume_pipe: bool = False 5918 ) -> exp.Expr | None: 5919 start = self._index 5920 _, side_token, kind_token = self._parse_join_parts() 5921 5922 side = side_token.text if side_token else None 5923 kind = kind_token.text if kind_token else None 5924 5925 if not self._match_set(self.SET_OPERATIONS): 5926 self._retreat(start) 5927 return None 5928 5929 token_type = self._prev.token_type 5930 5931 if token_type == TokenType.UNION: 5932 operation: type[exp.SetOperation] = exp.Union 5933 elif token_type == TokenType.EXCEPT: 5934 operation = exp.Except 5935 else: 5936 operation = exp.Intersect 5937 5938 comments = self._prev.comments 5939 5940 if self._match(TokenType.DISTINCT): 5941 distinct: bool | None = True 5942 elif self._match(TokenType.ALL): 5943 distinct = False 5944 else: 5945 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5946 if distinct is None: 5947 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5948 5949 by_name = ( 5950 self._match_text_seq("BY", "NAME") 5951 or self._match_text_seq("STRICT", "CORRESPONDING") 5952 or None 5953 ) 5954 if self._match_text_seq("CORRESPONDING"): 5955 by_name = True 5956 if not side and not kind: 5957 kind = "INNER" 5958 5959 on_column_list = None 5960 if by_name and self._match_texts(("ON", "BY")): 5961 on_column_list = self._parse_wrapped_csv(self._parse_column) 5962 5963 expression = self._parse_select( 5964 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5965 ) 5966 5967 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5968 # in _parse_cte and so that alias pushdown can reach into set operation branches 5969 if isinstance(this, exp.Values): 5970 this = self._values_to_select(this) 5971 if isinstance(expression, exp.Values): 5972 expression = self._values_to_select(expression) 5973 5974 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 5975 subquery = this.this 5976 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 5977 subquery.add_comments(this.pop_comments()) 5978 this = subquery 5979 5980 return self.expression( 5981 operation( 5982 this=this, 5983 distinct=distinct, 5984 by_name=by_name, 5985 expression=expression, 5986 side=side, 5987 kind=kind, 5988 on=on_column_list, 5989 ), 5990 comments=comments, 5991 ) 5992 5993 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 5994 while this: 5995 setop = self.parse_set_operation(this) 5996 if not setop: 5997 break 5998 this = setop 5999 6000 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6001 expression = this.expression 6002 6003 if expression: 6004 for arg in self.SET_OP_MODIFIERS: 6005 expr = expression.args.get(arg) 6006 if expr: 6007 this.set(arg, expr.pop()) 6008 6009 return this 6010 6011 def _parse_expression(self) -> exp.Expr | None: 6012 return self._parse_alias(self._parse_assignment()) 6013 6014 def _parse_assignment(self) -> exp.Expr | None: 6015 this = self._parse_disjunction() 6016 if not this and self._next.token_type in self.ASSIGNMENT: 6017 # This allows us to parse <non-identifier token> := <expr> 6018 this = exp.column( 6019 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6020 ) 6021 6022 while self._match_set(self.ASSIGNMENT): 6023 if isinstance(this, exp.Column) and len(this.parts) == 1: 6024 this = this.this 6025 6026 comments = self._prev_comments 6027 this = self.expression( 6028 self.ASSIGNMENT[self._prev.token_type]( 6029 this=this, expression=self._parse_assignment() 6030 ), 6031 comments=comments, 6032 ) 6033 6034 return this 6035 6036 def _parse_disjunction(self) -> exp.Expr | None: 6037 this = self._parse_conjunction() 6038 while self._match_set(self.DISJUNCTION): 6039 comments = self._prev_comments 6040 this = self.expression( 6041 self.DISJUNCTION[self._prev.token_type]( 6042 this=this, expression=self._parse_conjunction() 6043 ), 6044 comments=comments, 6045 ) 6046 return this 6047 6048 def _parse_conjunction(self) -> exp.Expr | None: 6049 this = self._parse_equality() 6050 while self._match_set(self.CONJUNCTION): 6051 comments = self._prev_comments 6052 this = self.expression( 6053 self.CONJUNCTION[self._prev.token_type]( 6054 this=this, expression=self._parse_equality() 6055 ), 6056 comments=comments, 6057 ) 6058 return this 6059 6060 def _parse_equality(self) -> exp.Expr | None: 6061 this = self._parse_comparison() 6062 while self._match_set(self.EQUALITY): 6063 comments = self._prev_comments 6064 this = self.expression( 6065 self.EQUALITY[self._prev.token_type]( 6066 this=this, expression=self._parse_comparison() 6067 ), 6068 comments=comments, 6069 ) 6070 return this 6071 6072 def _parse_comparison(self) -> exp.Expr | None: 6073 this = self._parse_range() 6074 while self._match_set(self.COMPARISON): 6075 comments = self._prev_comments 6076 this = self.expression( 6077 self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()), 6078 comments=comments, 6079 ) 6080 return this 6081 6082 def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6083 this = this or self._parse_bitwise() 6084 6085 while True: 6086 negate = self._match(TokenType.NOT) 6087 if self._match_set(self.RANGE_PARSERS): 6088 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6089 if not expression: 6090 return this 6091 6092 this = expression 6093 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6094 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6095 elif self._match(TokenType.NOTNULL): 6096 # Postgres supports ISNULL and NOTNULL for conditions. 6097 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6098 if self.dialect.NORMALIZE_NOT_NULL: 6099 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6100 this = self.expression(exp.Not(this=this)) 6101 else: 6102 this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True)) 6103 else: 6104 if negate: 6105 self._retreat(self._index - 1) 6106 break 6107 6108 if negate: 6109 this = self._negate_range(this) 6110 if self._curr and ( 6111 self._curr.token_type == TokenType.NOT 6112 or self._curr.token_type in self.RANGE_PARSERS 6113 ): 6114 this = self.expression(exp.Paren(this=this)) 6115 6116 return this 6117 6118 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6119 if not this: 6120 return this 6121 6122 expression = this.this if isinstance(this, exp.Escape) else this 6123 if isinstance(expression, (exp.Like, exp.ILike)): 6124 expression.set("negate", True) 6125 return this 6126 6127 return self.expression(exp.Not(this=this)) 6128 6129 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6130 index = self._index - 1 6131 negate = self._match(TokenType.NOT) 6132 6133 if self._match_text_seq("DISTINCT", "FROM"): 6134 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6135 return self.expression(klass(this=this, expression=self._parse_bitwise())) 6136 6137 if self._match(TokenType.JSON): 6138 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6139 6140 if self._match_text_seq("WITH"): 6141 _with = True 6142 elif self._match_text_seq("WITHOUT"): 6143 _with = False 6144 else: 6145 _with = None 6146 6147 unique = self._match(TokenType.UNIQUE) 6148 self._match_text_seq("KEYS") 6149 expression: exp.Expr | None = self.expression( 6150 exp.JSON(this=kind, with_=_with, unique=unique) 6151 ) 6152 else: 6153 expression = self._parse_null() or self._parse_bitwise() 6154 if not expression: 6155 self._retreat(index) 6156 return None 6157 6158 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6159 this = self.expression(exp.Is(this=this, expression=expression, negate=True)) 6160 else: 6161 this = self.expression(exp.Is(this=this, expression=expression)) 6162 this = self.expression(exp.Not(this=this)) if negate else this 6163 6164 return self._parse_column_ops(this) 6165 6166 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6167 unnest = self._parse_unnest(with_alias=False) 6168 if unnest: 6169 this = self.expression(exp.In(this=this, unnest=unnest)) 6170 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6171 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6172 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6173 6174 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6175 this = self.expression( 6176 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6177 ) 6178 else: 6179 this = self.expression(exp.In(this=this, expressions=expressions)) 6180 6181 if matched_l_paren: 6182 self._match_r_paren(this) 6183 elif not self._match(TokenType.R_BRACKET, expression=this): 6184 self.raise_error("Expecting ]") 6185 else: 6186 this = self.expression(exp.In(this=this, field=self._parse_column())) 6187 6188 return this 6189 6190 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6191 symmetric = None 6192 if self._match_text_seq("SYMMETRIC"): 6193 symmetric = True 6194 elif self._match_text_seq("ASYMMETRIC"): 6195 symmetric = False 6196 6197 low = self._parse_bitwise() 6198 self._match(TokenType.AND) 6199 high = self._parse_bitwise() 6200 6201 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6202 6203 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6204 if not self._match(TokenType.ESCAPE): 6205 return this 6206 return self.expression( 6207 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6208 ) 6209 6210 def _parse_interval_span( 6211 self, this: exp.Expr, parse_function_unit: bool = True 6212 ) -> exp.Interval: 6213 # handle day-time format interval span with omitted units: 6214 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6215 interval_span_units_omitted = None 6216 if ( 6217 this 6218 and this.is_string 6219 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6220 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6221 ): 6222 index = self._index 6223 6224 # Var "TO" Var 6225 first_unit = self._parse_var(any_token=True, upper=True) 6226 second_unit = None 6227 if first_unit and self._match_text_seq("TO"): 6228 second_unit = self._parse_var(any_token=True, upper=True) 6229 6230 interval_span_units_omitted = not (first_unit and second_unit) 6231 6232 self._retreat(index) 6233 6234 unit_index = self._index 6235 if interval_span_units_omitted: 6236 unit = None 6237 else: 6238 # Only attempt to parse a unit if the current token can actually be one, so that a 6239 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6240 is_unit = self._curr is not None and ( 6241 self._curr.token_type == TokenType.VAR 6242 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6243 ) 6244 unit = self._parse_function() if parse_function_unit and is_unit else None 6245 if not unit and is_unit: 6246 unit = self._parse_var(any_token=True, upper=True) 6247 6248 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6249 # each INTERVAL expression into this canonical form so it's easy to transpile 6250 if this and this.is_number: 6251 try: 6252 this = exp.Literal.string(this.to_py()) 6253 except ValueError: 6254 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6255 elif this and this.is_string: 6256 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6257 if parts and unit: 6258 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6259 unit = None 6260 self._retreat(unit_index) 6261 6262 if len(parts) == 1: 6263 this = exp.Literal.string(parts[0][0]) 6264 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6265 6266 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6267 unit = self.expression( 6268 exp.IntervalSpan( 6269 this=unit, 6270 expression=self._parse_function() 6271 or self._parse_var(any_token=True, upper=True), 6272 ) 6273 ) 6274 6275 return self.expression(exp.Interval(this=this, unit=unit)) 6276 6277 def _parse_interval( 6278 self, require_interval: bool = True, parse_function_unit: bool = True 6279 ) -> exp.Add | exp.Interval | None: 6280 index = self._index 6281 6282 if not self._match(TokenType.INTERVAL) and require_interval: 6283 return None 6284 6285 if self._match(TokenType.STRING, advance=False): 6286 this = self._parse_primary() 6287 else: 6288 this = self._parse_term() 6289 6290 if not this or ( 6291 isinstance(this, exp.Column) 6292 and not this.table 6293 and not this.this.quoted 6294 and self._curr 6295 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6296 ): 6297 self._retreat(index) 6298 return None 6299 6300 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6301 6302 index = self._index 6303 self._match(TokenType.PLUS) 6304 6305 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6306 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6307 return self.expression( 6308 exp.Add( 6309 this=interval, 6310 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6311 ) 6312 ) 6313 6314 self._retreat(index) 6315 return interval 6316 6317 def _parse_bitwise(self) -> exp.Expr | None: 6318 this = self._parse_term() 6319 6320 while True: 6321 if self._match_set(self.BITWISE): 6322 this = self.expression( 6323 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6324 ) 6325 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6326 this = self.expression( 6327 exp.DPipe( 6328 this=this, 6329 expression=self._parse_term(), 6330 safe=not self.dialect.STRICT_STRING_CONCAT, 6331 ) 6332 ) 6333 elif self._match(TokenType.DQMARK): 6334 this = self.expression( 6335 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6336 ) 6337 elif self._match_pair(TokenType.LT, TokenType.LT): 6338 this = self.expression( 6339 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6340 ) 6341 elif self._match_pair(TokenType.GT, TokenType.GT): 6342 this = self.expression( 6343 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6344 ) 6345 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6346 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6347 else: 6348 break 6349 6350 return this 6351 6352 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6353 this = self._parse_factor(parse_mod=parse_mod) 6354 6355 while self._match_set(self.TERM): 6356 klass = self.TERM[self._prev.token_type] 6357 comments = self._prev_comments 6358 expression = self._parse_factor(parse_mod=parse_mod) 6359 6360 this = self.expression(klass(this=this, expression=expression), comments=comments) 6361 6362 if isinstance(this, exp.Collate): 6363 self._normalize_collate(this) 6364 6365 return this 6366 6367 def _normalize_collate(self, collate: exp.Collate) -> None: 6368 expr = collate.expression 6369 6370 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6371 # fallback to Identifier / Var 6372 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6373 ident = expr.this 6374 if isinstance(ident, exp.Identifier): 6375 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6376 6377 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6378 parse_method = self._parse_factor_operand 6379 this = self._parse_at_time_zone(parse_method()) 6380 6381 while self._match_set(self.FACTOR, advance=False): 6382 if not parse_mod and self._curr.token_type == TokenType.MOD: 6383 break 6384 6385 self._advance() 6386 klass = self.FACTOR[self._prev.token_type] 6387 comments = self._prev_comments 6388 expression = parse_method() 6389 6390 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6391 self._retreat(self._index - 1) 6392 return this 6393 6394 this = self.expression(klass(this=this, expression=expression), comments=comments) 6395 6396 if isinstance(this, exp.Div): 6397 this.set("typed", self.dialect.TYPED_DIVISION) 6398 this.set("safe", self.dialect.SAFE_DIVISION) 6399 6400 return this 6401 6402 def _parse_factor_operand(self) -> exp.Expr | None: 6403 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6404 6405 def _parse_exponent(self) -> exp.Expr | None: 6406 this = self._parse_unary() 6407 while self._match_set(self.EXPONENT): 6408 comments = self._prev_comments 6409 this = self.expression( 6410 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6411 comments=comments, 6412 ) 6413 return this 6414 6415 def _parse_unary(self) -> exp.Expr | None: 6416 if self._match_set(self.UNARY_PARSERS): 6417 return self.UNARY_PARSERS[self._prev.token_type](self) 6418 return self._parse_type() 6419 6420 def _parse_type( 6421 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6422 ) -> exp.Expr | None: 6423 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6424 return atom 6425 6426 if interval := parse_interval and self._parse_interval(): 6427 return self._parse_column_ops(interval) 6428 6429 index = self._index 6430 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6431 6432 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6433 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6434 if isinstance(data_type, exp.Cast): 6435 # This constructor can contain ops directly after it, for instance struct unnesting: 6436 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6437 return self._parse_column_ops(data_type) 6438 6439 if data_type: 6440 index2 = self._index 6441 this = self._parse_primary() 6442 6443 if isinstance(this, exp.Literal): 6444 literal = this.name 6445 this = self._parse_column_ops(this) 6446 6447 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6448 if parser: 6449 return parser(self, this, data_type) 6450 6451 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6452 if data_type.is_type(exp.DType.TIMESTAMP): 6453 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6454 elif data_type.is_type(exp.DType.TIME): 6455 data_type = exp.DType.TIMETZ.into_expr() 6456 6457 return self.expression(exp.Cast(this=this, to=data_type)) 6458 6459 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6460 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6461 # 6462 # If the index difference here is greater than 1, that means the parser itself must have 6463 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6464 # 6465 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6466 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6467 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6468 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6469 # 6470 # In these cases, we don't really want to return the converted type, but instead retreat 6471 # and try to parse a Column or Identifier in the section below. 6472 if data_type.expressions and index2 - index > 1: 6473 self._retreat(index2) 6474 return self._parse_column_ops(data_type) 6475 6476 self._retreat(index) 6477 6478 if fallback_to_identifier: 6479 return self._parse_id_var() 6480 6481 return self._parse_column() 6482 6483 def _parse_type_size(self) -> exp.DataTypeParam | None: 6484 this = self._parse_type() 6485 if not this: 6486 return None 6487 6488 if isinstance(this, exp.Column) and not this.table: 6489 this = exp.var(this.name.upper()) 6490 6491 return self.expression( 6492 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6493 ) 6494 6495 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6496 type_name = identifier.name 6497 6498 while self._match(TokenType.DOT): 6499 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6500 6501 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6502 6503 def _parse_types( 6504 self, 6505 check_func: bool = False, 6506 schema: bool = False, 6507 allow_identifiers: bool = True, 6508 with_collation: bool = False, 6509 ) -> exp.Expr | None: 6510 index = self._index 6511 this: exp.Expr | None = None 6512 6513 if self._match_set(self.TYPE_TOKENS): 6514 type_token = self._prev.token_type 6515 else: 6516 type_token = None 6517 identifier = allow_identifiers and self._parse_id_var( 6518 any_token=False, tokens=(TokenType.VAR,) 6519 ) 6520 if isinstance(identifier, exp.Identifier): 6521 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6522 this = exp.DataType.build(identifier, udt=True) 6523 else: 6524 try: 6525 tokens = self.dialect.tokenize(identifier.name) 6526 except TokenError: 6527 tokens = None 6528 6529 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6530 if len(tokens) > 1: 6531 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6532 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6533 this = self._parse_user_defined_type(identifier) 6534 else: 6535 self._retreat(self._index - 1) 6536 return None 6537 else: 6538 return None 6539 6540 if type_token == TokenType.PSEUDO_TYPE: 6541 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6542 6543 if type_token == TokenType.OBJECT_IDENTIFIER: 6544 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6545 6546 # https://materialize.com/docs/sql/types/map/ 6547 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6548 key_type = self._parse_types( 6549 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6550 ) 6551 if not self._match(TokenType.FARROW): 6552 self._retreat(index) 6553 return None 6554 6555 value_type = self._parse_types( 6556 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6557 ) 6558 if not self._match(TokenType.R_BRACKET): 6559 self._retreat(index) 6560 return None 6561 6562 return exp.DataType( 6563 this=exp.DType.MAP, 6564 expressions=[key_type, value_type], 6565 nested=True, 6566 ) 6567 6568 nested = type_token in self.NESTED_TYPE_TOKENS 6569 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6570 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6571 expressions = None 6572 maybe_func = False 6573 6574 if self._match(TokenType.L_PAREN): 6575 if is_struct: 6576 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6577 elif nested: 6578 expressions = self._parse_csv( 6579 lambda: self._parse_types( 6580 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6581 ) 6582 ) 6583 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6584 this = expressions[0] 6585 this.set("nullable", True) 6586 self._match_r_paren() 6587 return this 6588 elif type_token in self.ENUM_TYPE_TOKENS: 6589 expressions = self._parse_csv(self._parse_equality) 6590 elif type_token == TokenType.JSON: 6591 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6592 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6593 expressions = self._parse_csv(self._parse_json_type_arg) 6594 elif is_aggregate: 6595 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6596 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6597 ) 6598 if not func_or_ident: 6599 return None 6600 expressions = [func_or_ident] 6601 if self._match(TokenType.COMMA): 6602 expressions.extend( 6603 self._parse_csv( 6604 lambda: self._parse_types( 6605 check_func=check_func, 6606 schema=schema, 6607 allow_identifiers=allow_identifiers, 6608 ) 6609 ) 6610 ) 6611 else: 6612 expressions = self._parse_csv(self._parse_type_size) 6613 6614 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6615 if type_token == TokenType.VECTOR and len(expressions) == 2: 6616 expressions = self._parse_vector_expressions(expressions) 6617 6618 if not self._match(TokenType.R_PAREN): 6619 self._retreat(index) 6620 return None 6621 6622 maybe_func = True 6623 6624 values: list[exp.Expr] | None = None 6625 6626 if nested and self._match(TokenType.LT): 6627 if is_struct: 6628 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6629 else: 6630 expressions = self._parse_csv( 6631 lambda: self._parse_types( 6632 check_func=check_func, 6633 schema=schema, 6634 allow_identifiers=allow_identifiers, 6635 with_collation=True, 6636 ) 6637 ) 6638 6639 if not self._match(TokenType.GT): 6640 self.raise_error("Expecting >") 6641 6642 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6643 values = self._parse_csv(self._parse_disjunction) 6644 if not values and is_struct: 6645 values = None 6646 self._retreat(self._index - 1) 6647 else: 6648 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6649 6650 if type_token in self.TIMESTAMPS: 6651 if self._match_text_seq("WITH", "TIME", "ZONE"): 6652 maybe_func = False 6653 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6654 this = exp.DataType(this=tz_type, expressions=expressions) 6655 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6656 maybe_func = False 6657 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6658 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6659 maybe_func = False 6660 elif type_token == TokenType.INTERVAL: 6661 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6662 unit = self._parse_var(upper=True) 6663 if self._match_text_seq("TO"): 6664 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6665 6666 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6667 else: 6668 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6669 elif type_token == TokenType.VOID: 6670 this = exp.DataType(this=exp.DType.NULL) 6671 6672 if maybe_func and check_func: 6673 index2 = self._index 6674 peek = self._parse_string() 6675 6676 if not peek: 6677 self._retreat(index) 6678 return None 6679 6680 self._retreat(index2) 6681 6682 if not this: 6683 assert type_token is not None 6684 if self._match_text_seq("UNSIGNED"): 6685 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6686 if not unsigned_type_token: 6687 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6688 6689 type_token = unsigned_type_token or type_token 6690 6691 # NULLABLE without parentheses can be a column (Presto/Trino) 6692 if type_token == TokenType.NULLABLE and not expressions: 6693 self._retreat(index) 6694 return None 6695 6696 this = exp.DataType( 6697 this=exp.DType[type_token.name], 6698 expressions=expressions, 6699 nested=nested, 6700 ) 6701 6702 # Empty arrays/structs are allowed 6703 if values is not None: 6704 cls = exp.Struct if is_struct else exp.Array 6705 this = exp.cast(cls(expressions=values), this, copy=False) 6706 6707 elif expressions: 6708 this.set("expressions", expressions) 6709 6710 # https://materialize.com/docs/sql/types/list/#type-name 6711 while self._match(TokenType.LIST): 6712 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6713 6714 index = self._index 6715 6716 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6717 matched_array = self._match(TokenType.ARRAY) 6718 6719 while self._curr: 6720 datatype_token = self._prev.token_type 6721 matched_l_bracket = self._match(TokenType.L_BRACKET) 6722 6723 if (not matched_l_bracket and not matched_array) or ( 6724 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6725 ): 6726 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6727 # not to be confused with the fixed size array parsing 6728 break 6729 6730 matched_array = False 6731 values = self._parse_csv(self._parse_disjunction) or None 6732 if ( 6733 values 6734 and not schema 6735 and ( 6736 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6737 or datatype_token == TokenType.ARRAY 6738 or not self._match(TokenType.R_BRACKET, advance=False) 6739 ) 6740 ): 6741 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6742 # 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 6743 self._retreat(index) 6744 break 6745 6746 this = exp.DataType( 6747 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6748 ) 6749 self._match(TokenType.R_BRACKET) 6750 6751 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6752 converter = self.TYPE_CONVERTERS.get(this.this) 6753 if converter: 6754 this = converter(t.cast(exp.DataType, this)) 6755 6756 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6757 this.set("collate", self._parse_identifier() or self._parse_column()) 6758 6759 return this 6760 6761 def _parse_json_type_arg(self) -> exp.Expr | None: 6762 """Parse a single argument to ClickHouse's JSON type.""" 6763 6764 # SKIP col or SKIP REGEXP 'pattern' 6765 if self._match_text_seq("SKIP"): 6766 regexp = self._match(TokenType.RLIKE) 6767 arg = self._parse_column() 6768 if isinstance(arg, exp.Column): 6769 arg = arg.to_dot() 6770 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6771 6772 param_or_col = self._parse_column() 6773 if not isinstance(param_or_col, exp.Column): 6774 return None 6775 6776 # Parameter: name=value (e.g., max_dynamic_paths=2) 6777 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 6778 param = param_or_col.name 6779 value = self._parse_primary() 6780 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 6781 6782 # Column type hint: col_name Type 6783 col = param_or_col.to_dot() 6784 kind = self._parse_types(check_func=False, allow_identifiers=False) 6785 return self.expression(exp.ColumnDef(this=col, kind=kind)) 6786 6787 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 6788 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 6789 6790 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 6791 index = self._index 6792 6793 if ( 6794 self._curr 6795 and self._next 6796 and self._curr.token_type in self.TYPE_TOKENS 6797 and self._next.token_type in self.TYPE_TOKENS 6798 ): 6799 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 6800 # type token. Without this, the list will be parsed as a type and we'll eventually crash 6801 this = self._parse_id_var() 6802 else: 6803 this = ( 6804 self._parse_type(parse_interval=False, fallback_to_identifier=True) 6805 or self._parse_id_var() 6806 ) 6807 6808 self._match(TokenType.COLON) 6809 6810 if ( 6811 type_required 6812 and not isinstance(this, exp.DataType) 6813 and not self._match_set(self.TYPE_TOKENS, advance=False) 6814 ): 6815 self._retreat(index) 6816 return self._parse_types() 6817 6818 return self._parse_column_def(this) 6819 6820 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 6821 if not self._match_text_seq("AT", "TIME", "ZONE"): 6822 return this 6823 return self._parse_at_time_zone( 6824 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 6825 ) 6826 6827 def _parse_atom(self) -> exp.Expr | None: 6828 if ( 6829 self._curr.token_type in self.IDENTIFIER_TOKENS 6830 and (column := self._parse_column()) is not None 6831 ): 6832 return column 6833 6834 token = self._curr 6835 token_type = token.token_type 6836 6837 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 6838 return None 6839 6840 next_type = self._next.token_type 6841 6842 if ( 6843 next_type in self.COLUMN_OPERATORS 6844 or next_type in self.COLUMN_POSTFIX_TOKENS 6845 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 6846 ): 6847 return None 6848 6849 self._advance() 6850 return primary_parser(self, token) 6851 6852 def _parse_column(self) -> exp.Expr | None: 6853 column: exp.Expr | None = self._parse_column_parts_fast() 6854 if column is None: 6855 this = self._parse_column_reference() 6856 if not this: 6857 this = self._parse_bracket(this) 6858 column = self._parse_column_ops(this) if this else this 6859 6860 if column: 6861 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 6862 column.set("join_mark", self._match(TokenType.JOIN_MARKER)) 6863 if self.COLON_IS_VARIANT_EXTRACT: 6864 column = self._parse_colon_as_variant_extract(column) 6865 6866 return column 6867 6868 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 6869 """Fast path for simple column and dot references (a, a.b, ...). 6870 6871 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 6872 that nothing complex follows. If it does, retreats and returns None so 6873 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 6874 """ 6875 index = self._index 6876 parts: list[exp.Identifier] | None = None 6877 all_comments: list[str] | None = None 6878 6879 while self._match_set(self.IDENTIFIER_TOKENS): 6880 token = self._prev 6881 comments = self._prev_comments 6882 6883 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 6884 self._retreat(index) 6885 return None 6886 6887 has_dot = self._match(TokenType.DOT) 6888 curr_tt = self._curr.token_type 6889 6890 if not has_dot: 6891 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 6892 self._retreat(index) 6893 return None 6894 elif curr_tt not in self.IDENTIFIER_TOKENS: 6895 self._retreat(index) 6896 return None 6897 6898 if parts is None: 6899 parts = [] 6900 6901 if comments: 6902 if all_comments is None: 6903 all_comments = [] 6904 all_comments.extend(comments) 6905 self._prev_comments = [] 6906 6907 parts.append( 6908 self.expression( 6909 exp.Identifier( 6910 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 6911 ), 6912 token, 6913 ) 6914 ) 6915 6916 if not has_dot: 6917 break 6918 6919 if parts is None: 6920 return None 6921 6922 n = len(parts) 6923 6924 if n == 1: 6925 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 6926 elif n == 2: 6927 column = exp.Column(this=parts[1], table=parts[0]) 6928 elif n == 3: 6929 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 6930 else: 6931 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 6932 6933 for i in range(4, n): 6934 column = exp.Dot(this=column, expression=parts[i]) 6935 6936 if all_comments: 6937 column.add_comments(all_comments) 6938 6939 return column 6940 6941 def _parse_column_reference(self) -> exp.Expr | None: 6942 this = self._parse_field() 6943 if ( 6944 not this 6945 and self._match(TokenType.VALUES, advance=False) 6946 and self.VALUES_FOLLOWED_BY_PAREN 6947 and (not self._next or self._next.token_type != TokenType.L_PAREN) 6948 ): 6949 this = self._parse_id_var() 6950 6951 if isinstance(this, exp.Identifier): 6952 # We bubble up comments from the Identifier to the Column 6953 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 6954 6955 return this 6956 6957 def _build_json_extract( 6958 self, 6959 this: exp.Expr | None, 6960 path_parts: list[exp.JSONPathPart], 6961 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 6962 if len(path_parts) > 1: 6963 this = self.expression( 6964 exp.JSONExtract( 6965 this=this, 6966 expression=exp.JSONPath(expressions=path_parts), 6967 variant_extract=True, 6968 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 6969 ) 6970 ) 6971 path_parts = [exp.JSONPathRoot()] 6972 6973 return this, path_parts 6974 6975 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 6976 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 6977 6978 while self._match(TokenType.COLON): 6979 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 6980 this, path_parts = self._build_json_extract(this, path_parts) 6981 6982 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 6983 6984 if key: 6985 quoted = isinstance(key, exp.Identifier) and key.quoted 6986 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 6987 6988 while True: 6989 if self._match(TokenType.DOT): 6990 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 6991 6992 if next_key: 6993 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 6994 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 6995 elif self._match(TokenType.L_BRACKET): 6996 bracket_expr = self._parse_bracket_key_value() 6997 6998 if not self._match(TokenType.R_BRACKET): 6999 self.raise_error("Expected ]") 7000 7001 if bracket_expr: 7002 if bracket_expr.is_string: 7003 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7004 elif bracket_expr.is_star: 7005 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7006 elif bracket_expr.is_number: 7007 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7008 else: 7009 this, path_parts = self._build_json_extract(this, path_parts) 7010 7011 this = self.expression( 7012 exp.Bracket( 7013 this=this, expressions=[bracket_expr], json_access=True 7014 ), 7015 ) 7016 7017 elif self._match(TokenType.DCOLON): 7018 this, path_parts = self._build_json_extract(this, path_parts) 7019 7020 cast_type = self._parse_types() 7021 if cast_type: 7022 this = self.expression(exp.Cast(this=this, to=cast_type)) 7023 else: 7024 self.raise_error("Expected type after '::'") 7025 else: 7026 break 7027 7028 this, _ = self._build_json_extract(this, path_parts) 7029 7030 return this 7031 7032 def _parse_dcolon(self) -> exp.Expr | None: 7033 return self._parse_types() 7034 7035 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7036 while self._curr.token_type in self.BRACKETS: 7037 this = self._parse_bracket(this) 7038 7039 column_operators = self.COLUMN_OPERATORS 7040 cast_column_operators = self.CAST_COLUMN_OPERATORS 7041 while self._curr: 7042 op_token = self._curr.token_type 7043 7044 if op_token not in column_operators: 7045 break 7046 op = column_operators[op_token] 7047 self._advance() 7048 7049 if op_token in cast_column_operators: 7050 field = self._parse_dcolon() 7051 if not field: 7052 self.raise_error("Expected type") 7053 elif op and self._curr: 7054 field = self._parse_column_reference() or self._parse_bitwise() 7055 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7056 field = self._parse_column_ops(field) 7057 else: 7058 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7059 field = self._parse_field(any_token=True, anonymous_func=True) 7060 7061 # In t.true, t.null we should produce an Identifier node 7062 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7063 field = self.expression( 7064 exp.Identifier(this=self._prev.text), 7065 comments=field.comments, 7066 ) 7067 7068 # Function calls can be qualified, e.g., x.y.FOO() 7069 # This converts the final AST to a series of Dots leading to the function call 7070 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7071 if isinstance(field, (exp.Func, exp.Window)) and this: 7072 this = this.transform( 7073 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7074 ) 7075 7076 if op: 7077 this = op(self, this, field) 7078 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7079 this = self.expression( 7080 exp.Column( 7081 this=field, 7082 table=this.this, 7083 db=this.args.get("table"), 7084 catalog=this.args.get("db"), 7085 ), 7086 comments=this.comments, 7087 ) 7088 elif isinstance(field, exp.Window): 7089 # Move the exp.Dot's to the window's function 7090 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7091 field.set("this", window_func) 7092 this = field 7093 else: 7094 this = self.expression(exp.Dot(this=this, expression=field)) 7095 7096 if field and field.comments: 7097 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7098 7099 this = self._parse_bracket(this) 7100 7101 return this 7102 7103 def _parse_paren(self) -> exp.Expr | None: 7104 if not self._match(TokenType.L_PAREN): 7105 return None 7106 7107 comments = self._prev_comments 7108 query = self._parse_select() 7109 7110 if query: 7111 expressions = [query] 7112 else: 7113 expressions = self._parse_expressions() 7114 7115 this = seq_get(expressions, 0) 7116 7117 if not this and self._match(TokenType.R_PAREN, advance=False): 7118 this = self.expression(exp.Tuple()) 7119 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7120 this = self.expression(exp.Tuple(expressions=expressions)) 7121 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7122 this = self._parse_subquery(this=this, parse_alias=False) 7123 elif isinstance(this, (exp.Subquery, exp.Values)): 7124 this = self._parse_subquery( 7125 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7126 parse_alias=False, 7127 ) 7128 else: 7129 this = self.expression(exp.Paren(this=this)) 7130 7131 if this: 7132 this.add_comments(comments) 7133 7134 self._match_r_paren(expression=this) 7135 7136 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7137 return self._parse_window(this) 7138 7139 return this 7140 7141 def _parse_primary(self) -> exp.Expr | None: 7142 if self._match_set(self.PRIMARY_PARSERS): 7143 token_type = self._prev.token_type 7144 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7145 7146 if token_type == TokenType.STRING: 7147 expressions = [primary] 7148 while self._match(TokenType.STRING, advance=False): 7149 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7150 self.raise_error( 7151 "Adjacent string literals need to be separated by whitespace or comments" 7152 ) 7153 7154 self._advance() 7155 expressions.append(exp.Literal.string(self._prev.text)) 7156 7157 if len(expressions) > 1: 7158 return self.expression( 7159 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7160 ) 7161 7162 return primary 7163 7164 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7165 return exp.Literal.number(f"0.{self._prev.text}") 7166 7167 return self._parse_paren() 7168 7169 def _parse_field( 7170 self, 7171 any_token: bool = False, 7172 tokens: t.Collection[TokenType] | None = None, 7173 anonymous_func: bool = False, 7174 ) -> exp.Expr | None: 7175 after_dot = ( 7176 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7177 ) 7178 7179 if anonymous_func: 7180 field = ( 7181 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7182 or self._parse_primary() 7183 ) 7184 else: 7185 field = self._parse_primary() or self._parse_function( 7186 anonymous=anonymous_func, any_token=any_token 7187 ) 7188 7189 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7190 7191 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7192 name = field.name 7193 if self._is_connected() and self._parse_var(any_token=True): 7194 name += self._prev.text 7195 7196 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7197 7198 return field 7199 7200 def _parse_function( 7201 self, 7202 functions: dict[str, t.Callable] | None = None, 7203 anonymous: bool = False, 7204 optional_parens: bool = True, 7205 any_token: bool = False, 7206 ) -> exp.Expr | None: 7207 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7208 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7209 fn_syntax = False 7210 if ( 7211 self._match(TokenType.L_BRACE, advance=False) 7212 and self._next 7213 and self._next.text.upper() == "FN" 7214 ): 7215 self._advance(2) 7216 fn_syntax = True 7217 7218 func = self._parse_function_call( 7219 functions=functions, 7220 anonymous=anonymous, 7221 optional_parens=optional_parens, 7222 any_token=any_token, 7223 ) 7224 7225 if fn_syntax: 7226 self._match(TokenType.R_BRACE) 7227 7228 return func 7229 7230 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7231 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7232 7233 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7234 args = self._parse_function_args(alias=False) 7235 if not args: 7236 self.raise_error("Expected at least one argument") 7237 7238 # Wrapped so the connector keeps its precedence in the parent context 7239 return exp.Paren(this=connector(*args, copy=False)) 7240 7241 def _parse_function_call( 7242 self, 7243 functions: dict[str, t.Callable] | None = None, 7244 anonymous: bool = False, 7245 optional_parens: bool = True, 7246 any_token: bool = False, 7247 ) -> exp.Expr | None: 7248 if not self._curr: 7249 return None 7250 7251 comments = self._curr.comments 7252 prev = self._prev 7253 token = self._curr 7254 token_type = self._curr.token_type 7255 this: str | exp.Expr = self._curr.text 7256 upper = self._curr.text.upper() 7257 7258 after_dot = prev.token_type == TokenType.DOT 7259 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7260 if ( 7261 optional_parens 7262 and parser 7263 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7264 and not after_dot 7265 ): 7266 self._advance() 7267 return self._parse_window(parser(self)) 7268 7269 if self._next.token_type != TokenType.L_PAREN: 7270 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7271 self._advance() 7272 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7273 7274 return None 7275 7276 if any_token: 7277 if token_type in self.RESERVED_TOKENS: 7278 return None 7279 elif token_type not in self.FUNC_TOKENS: 7280 return None 7281 7282 self._advance(2) 7283 7284 parser = self.FUNCTION_PARSERS.get(upper) 7285 if parser and not anonymous: 7286 result = parser(self) 7287 else: 7288 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7289 7290 if subquery_predicate: 7291 expr = None 7292 if self._curr.token_type in self.SUBQUERY_TOKENS: 7293 expr = self._parse_select() 7294 self._match_r_paren() 7295 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7296 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7297 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7298 self._advance(-1) 7299 expr = self._parse_bitwise() 7300 7301 if expr: 7302 return self.expression(subquery_predicate(this=expr), comments=comments) 7303 7304 if functions is None: 7305 functions = self.FUNCTIONS 7306 7307 function = functions.get(upper) 7308 known_function = function and not anonymous 7309 7310 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7311 args = self._parse_function_args(alias) 7312 7313 post_func_comments = self._curr.comments if self._curr else None 7314 if known_function and post_func_comments: 7315 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7316 # call we'll construct it as exp.Anonymous, even if it's "known" 7317 if any( 7318 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7319 for comment in post_func_comments 7320 ): 7321 known_function = False 7322 7323 if alias and known_function: 7324 args = self._kv_to_prop_eq(args) 7325 7326 if known_function: 7327 func_builder = t.cast(t.Callable, function) 7328 7329 # mypyc compiled functions don't have __code__, so we use 7330 # try/except to check if func_builder accepts 'dialect'. 7331 try: 7332 func = func_builder(args) 7333 except TypeError: 7334 func = func_builder(args, dialect=self.dialect) 7335 7336 func = self.validate_expression(func, args) 7337 if self.dialect.PRESERVE_ORIGINAL_NAMES: 7338 func.meta["name"] = this 7339 7340 result = func 7341 else: 7342 if token_type == TokenType.IDENTIFIER: 7343 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7344 7345 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7346 7347 result = result.update_positions(token) 7348 7349 if isinstance(result, exp.Expr): 7350 result.add_comments(comments) 7351 7352 if parser: 7353 self._match(TokenType.R_PAREN, expression=result) 7354 else: 7355 self._match_r_paren(result) 7356 return self._parse_window(result) 7357 7358 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7359 return expression 7360 7361 def _kv_to_prop_eq( 7362 self, expressions: list[exp.Expr], parse_map: bool = False 7363 ) -> list[exp.Expr]: 7364 transformed = [] 7365 7366 for index, e in enumerate(expressions): 7367 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7368 if isinstance(e, exp.Alias): 7369 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7370 7371 if not isinstance(e, exp.PropertyEQ): 7372 e = self.expression( 7373 exp.PropertyEQ( 7374 this=e.this if parse_map else exp.to_identifier(e.this.name), 7375 expression=e.expression, 7376 ) 7377 ) 7378 7379 if isinstance(e.this, exp.Column): 7380 e.this.replace(e.this.this) 7381 else: 7382 e = self._to_prop_eq(e, index) 7383 7384 transformed.append(e) 7385 7386 return transformed 7387 7388 def _parse_function_properties(self) -> exp.Properties | None: 7389 # Skip the generic `key = value` fallback in _parse_property since this 7390 # runs post-AS where a function body like `name = expr` can be misread 7391 # as a property. 7392 properties = [] 7393 while True: 7394 if self._match_texts(self.PROPERTY_PARSERS): 7395 keyword = self._prev.text.upper() 7396 prop = self.PROPERTY_PARSERS[keyword](self) 7397 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7398 keyword = self._prev.text.upper() 7399 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7400 else: 7401 break 7402 if not prop: 7403 self.raise_error(f"Failed to parse property '{keyword}'") 7404 break 7405 for p in ensure_list(prop): 7406 properties.append(p) 7407 7408 return self.expression(exp.Properties(expressions=properties)) if properties else None 7409 7410 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7411 return self._parse_statement() 7412 7413 def _parse_function_parameter(self) -> exp.Expr | None: 7414 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7415 7416 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7417 this = self._parse_table_parts(schema=True) 7418 7419 if not self._match(TokenType.L_PAREN): 7420 return this 7421 7422 expressions = self._parse_csv(self._parse_function_parameter) 7423 self._match_r_paren() 7424 return self.expression( 7425 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7426 ) 7427 7428 def _parse_macro_overloads( 7429 self, 7430 this: exp.UserDefinedFunction, 7431 first_body: exp.Expr, 7432 first_is_table: bool = False, 7433 ) -> exp.MacroOverloads: 7434 overloads = [ 7435 self.expression( 7436 exp.MacroOverload( 7437 this=first_body, 7438 expressions=this.expressions or None, 7439 is_table=first_is_table, 7440 ) 7441 ) 7442 ] 7443 this.set("expressions", None) 7444 this.set("wrapped", False) 7445 7446 while self._match(TokenType.COMMA): 7447 if not self._match(TokenType.L_PAREN): 7448 break 7449 7450 params = self._parse_csv(self._parse_function_parameter) 7451 self._match_r_paren() 7452 7453 if not self._match(TokenType.ALIAS): 7454 break 7455 7456 is_table = self._match(TokenType.TABLE) 7457 body = self._parse_expression() 7458 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7459 overloads.append(self.expression(macro)) 7460 7461 return self.expression(exp.MacroOverloads(expressions=overloads)) 7462 7463 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7464 literal = self._parse_primary() 7465 if literal: 7466 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7467 7468 return self._identifier_expression(token) 7469 7470 def _parse_session_parameter(self) -> exp.SessionParameter: 7471 kind = None 7472 this = self._parse_id_var() or self._parse_primary() 7473 7474 if this and self._match(TokenType.DOT): 7475 kind = this.name 7476 this = self._parse_var() or self._parse_primary() 7477 7478 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7479 7480 def _parse_lambda_arg(self) -> exp.Expr | None: 7481 return self._parse_id_var() 7482 7483 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7484 next_token_type = self._next.token_type 7485 7486 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7487 if ( 7488 next_token_type in self.LAMBDA_ARG_TERMINATORS 7489 and (atom := self._parse_atom()) is not None 7490 ): 7491 return atom 7492 7493 index = self._index 7494 7495 if self._match(TokenType.L_PAREN): 7496 expressions = t.cast( 7497 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7498 ) 7499 7500 if not self._match(TokenType.R_PAREN): 7501 self._retreat(index) 7502 elif self._match_set(self.LAMBDAS): 7503 return self.LAMBDAS[self._prev.token_type](self, expressions) 7504 else: 7505 self._retreat(index) 7506 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7507 expressions = [self._parse_lambda_arg()] 7508 7509 if self._match_set(self.LAMBDAS): 7510 return self.LAMBDAS[self._prev.token_type](self, expressions) 7511 7512 self._retreat(index) 7513 7514 this: exp.Expr | None 7515 7516 if self._match(TokenType.DISTINCT): 7517 this = self.expression( 7518 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7519 ) 7520 else: 7521 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7522 this = self._parse_select_or_expression(alias=alias) 7523 7524 return self._parse_limit( 7525 self._parse_respect_or_ignore_nulls( 7526 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7527 ) 7528 ) 7529 7530 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7531 index = self._index 7532 if not self._match(TokenType.L_PAREN): 7533 return this 7534 7535 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7536 # expr can be of both types 7537 if self._match_set(self.SELECT_START_TOKENS): 7538 self._retreat(index) 7539 return this 7540 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7541 self._match_r_paren() 7542 return self.expression(exp.Schema(this=this, expressions=args)) 7543 7544 def _parse_field_def(self) -> exp.Expr | None: 7545 return self._parse_column_def(self._parse_field(any_token=True)) 7546 7547 def _parse_column_def( 7548 self, this: exp.Expr | None, computed_column: bool = True 7549 ) -> exp.Expr | None: 7550 # column defs are not really columns, they're identifiers 7551 if isinstance(this, exp.Column): 7552 this = this.this 7553 7554 if not computed_column: 7555 self._match(TokenType.ALIAS) 7556 7557 kind = self._parse_types(schema=True) 7558 7559 if self._match_text_seq("FOR", "ORDINALITY"): 7560 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7561 7562 constraints: list[exp.Expr] = [] 7563 7564 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7565 ("ALIAS", "MATERIALIZED") 7566 ): 7567 # Match storage before _parse_types so STORED is not treated as a data type 7568 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7569 persisted = self._prev.text.upper() == "MATERIALIZED" 7570 expression = self._parse_disjunction() 7571 if not persisted: 7572 if self._match_text_seq("PERSISTED"): 7573 persisted = True 7574 elif self._match_texts(("STORED", "VIRTUAL")): 7575 persisted = self._prev.text.upper() == "STORED" 7576 constraint_kind = exp.ComputedColumnConstraint( 7577 this=expression, 7578 persisted=persisted, 7579 data_type=exp.Var(this="AUTO") 7580 if self._match_text_seq("AUTO") 7581 else self._parse_types(), 7582 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7583 ) 7584 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7585 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7586 in_out_constraint = self.expression( 7587 exp.InOutColumnConstraint( 7588 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7589 ) 7590 ) 7591 constraints.append(in_out_constraint) 7592 kind = self._parse_types() 7593 elif ( 7594 kind 7595 and self._match(TokenType.ALIAS, advance=False) 7596 and ( 7597 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7598 or self._next.token_type == TokenType.L_PAREN 7599 ) 7600 ): 7601 self._advance() 7602 constraints.append( 7603 self.expression( 7604 exp.ColumnConstraint( 7605 kind=exp.ComputedColumnConstraint( 7606 this=self._parse_disjunction(), 7607 persisted=self._match_texts(("STORED", "VIRTUAL")) 7608 and self._prev.text.upper() == "STORED", 7609 ) 7610 ) 7611 ) 7612 ) 7613 7614 while True: 7615 constraint = self._parse_column_constraint() 7616 if not constraint: 7617 break 7618 constraints.append(constraint) 7619 7620 if not kind and not constraints: 7621 return this 7622 7623 position = None 7624 if self._match_texts(("FIRST", "AFTER")): 7625 pos = self._prev.text 7626 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7627 7628 return self.expression( 7629 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7630 ) 7631 7632 def _parse_auto_increment( 7633 self, 7634 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7635 start = None 7636 increment = None 7637 order = None 7638 7639 if self._match(TokenType.L_PAREN, advance=False): 7640 args = self._parse_wrapped_csv(self._parse_bitwise) 7641 start = seq_get(args, 0) 7642 increment = seq_get(args, 1) 7643 7644 # The remaining parts form an unordered bag and any of them can be omitted, in which 7645 # case the engine falls back to its own default, so they're parsed independently. 7646 while True: 7647 if self._match_text_seq("START"): 7648 start = self._parse_bitwise() 7649 elif self._match_text_seq("INCREMENT"): 7650 increment = self._parse_bitwise() 7651 elif self._match_text_seq("ORDER"): 7652 order = True 7653 elif self._match_text_seq("NOORDER"): 7654 order = False 7655 else: 7656 break 7657 7658 if start or increment or order is not None: 7659 return exp.GeneratedAsIdentityColumnConstraint( 7660 start=start, increment=increment, this=False, order=order 7661 ) 7662 7663 return exp.AutoIncrementColumnConstraint() 7664 7665 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7666 if not self._match(TokenType.L_PAREN, advance=False): 7667 return None 7668 7669 return self.expression( 7670 exp.CheckColumnConstraint( 7671 this=self._parse_wrapped(self._parse_assignment), 7672 enforced=self._match_text_seq("ENFORCED"), 7673 ) 7674 ) 7675 7676 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7677 if not self._match_text_seq("REFRESH"): 7678 self._retreat(self._index - 1) 7679 return None 7680 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7681 7682 def _parse_compress(self) -> exp.CompressColumnConstraint: 7683 if self._match(TokenType.L_PAREN, advance=False): 7684 return self.expression( 7685 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7686 ) 7687 7688 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7689 7690 def _parse_generated_as_identity( 7691 self, 7692 ) -> ( 7693 exp.GeneratedAsIdentityColumnConstraint 7694 | exp.ComputedColumnConstraint 7695 | exp.GeneratedAsRowColumnConstraint 7696 ): 7697 if self._match_text_seq("BY", "DEFAULT"): 7698 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7699 this = self.expression( 7700 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7701 ) 7702 else: 7703 self._match_text_seq("ALWAYS") 7704 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7705 7706 self._match(TokenType.ALIAS) 7707 7708 if self._match_text_seq("ROW"): 7709 start = self._match_text_seq("START") 7710 if not start: 7711 self._match(TokenType.END) 7712 hidden = self._match_text_seq("HIDDEN") 7713 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7714 7715 identity = self._match_text_seq("IDENTITY") 7716 7717 if self._match(TokenType.L_PAREN): 7718 if self._match_text_seq("START", "WITH"): 7719 this.set("start", self._parse_bitwise()) 7720 if self._match_text_seq("INCREMENT", "BY"): 7721 this.set("increment", self._parse_bitwise()) 7722 if self._match_text_seq("MINVALUE"): 7723 this.set("minvalue", self._parse_bitwise()) 7724 if self._match_text_seq("MAXVALUE"): 7725 this.set("maxvalue", self._parse_bitwise()) 7726 7727 if self._match_text_seq("CYCLE"): 7728 this.set("cycle", True) 7729 elif self._match_text_seq("NO", "CYCLE"): 7730 this.set("cycle", False) 7731 7732 if not identity: 7733 this.set("expression", self._parse_range()) 7734 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7735 args = self._parse_csv(self._parse_bitwise) 7736 this.set("start", seq_get(args, 0)) 7737 this.set("increment", seq_get(args, 1)) 7738 7739 self._match_r_paren() 7740 7741 return this 7742 7743 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7744 self._match_text_seq("LENGTH") 7745 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7746 7747 def _parse_not_constraint(self) -> exp.Expr | None: 7748 if self._match_text_seq("NULL"): 7749 return self.expression(exp.NotNullColumnConstraint()) 7750 if self._match_text_seq("CASESPECIFIC"): 7751 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7752 if self._match_text_seq("FOR", "REPLICATION"): 7753 return self.expression(exp.NotForReplicationColumnConstraint()) 7754 7755 # Unconsume the `NOT` token 7756 self._retreat(self._index - 1) 7757 return None 7758 7759 def _parse_column_constraint(self) -> exp.Expr | None: 7760 this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None 7761 7762 procedure_option_follows = ( 7763 self._match(TokenType.WITH, advance=False) 7764 and self._next 7765 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7766 ) 7767 7768 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 7769 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 7770 if not constraint: 7771 self._retreat(self._index - 1) 7772 return None 7773 7774 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 7775 7776 if self._match_text_seq("CHARACTER", "SET"): 7777 return self.expression( 7778 exp.ColumnConstraint( 7779 this=this, 7780 kind=self.expression( 7781 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 7782 ), 7783 ) 7784 ) 7785 7786 return this 7787 7788 def _parse_constraint(self) -> exp.Expr | None: 7789 if not self._match(TokenType.CONSTRAINT): 7790 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 7791 7792 return self.expression( 7793 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 7794 ) 7795 7796 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 7797 constraints = [] 7798 while True: 7799 constraint = self._parse_unnamed_constraint() or self._parse_function() 7800 if not constraint: 7801 break 7802 constraints.append(constraint) 7803 7804 return constraints 7805 7806 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 7807 index = self._index 7808 7809 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 7810 constraints or self.CONSTRAINT_PARSERS 7811 ): 7812 return None 7813 7814 constraint_key = self._prev.text.upper() 7815 if constraint_key not in self.CONSTRAINT_PARSERS: 7816 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 7817 7818 result = self.CONSTRAINT_PARSERS[constraint_key](self) 7819 if not result: 7820 self._retreat(index) 7821 7822 return result 7823 7824 def _parse_unique_key(self) -> exp.Expr | None: 7825 if ( 7826 self._curr 7827 and self._curr.token_type != TokenType.IDENTIFIER 7828 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 7829 ): 7830 return None 7831 return self._parse_id_var(any_token=False) 7832 7833 def _parse_unique(self) -> exp.UniqueColumnConstraint: 7834 self._match_texts(("KEY", "INDEX")) 7835 return self.expression( 7836 exp.UniqueColumnConstraint( 7837 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 7838 this=self._parse_schema(self._parse_unique_key()), 7839 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 7840 on_conflict=self._parse_on_conflict(), 7841 options=self._parse_key_constraint_options(), 7842 ) 7843 ) 7844 7845 def _parse_key_constraint_options(self) -> list[str]: 7846 options = [] 7847 while True: 7848 if not self._curr: 7849 break 7850 7851 if self._match(TokenType.ON): 7852 action = None 7853 on = self._advance_any() and self._prev.text 7854 7855 if self._match_text_seq("NO", "ACTION"): 7856 action = "NO ACTION" 7857 elif self._match_text_seq("CASCADE"): 7858 action = "CASCADE" 7859 elif self._match_text_seq("RESTRICT"): 7860 action = "RESTRICT" 7861 elif self._match_pair(TokenType.SET, TokenType.NULL): 7862 action = "SET NULL" 7863 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 7864 action = "SET DEFAULT" 7865 else: 7866 self.raise_error("Invalid key constraint") 7867 7868 options.append(f"ON {on} {action}") 7869 else: 7870 var = self._parse_var_from_options( 7871 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 7872 ) 7873 if not var: 7874 break 7875 options.append(var.name) 7876 7877 return options 7878 7879 def _parse_references(self, match: bool = True) -> exp.Reference | None: 7880 if match and not self._match(TokenType.REFERENCES): 7881 return None 7882 7883 expressions: list | None = None 7884 this = self._parse_table(schema=True) 7885 options = self._parse_key_constraint_options() 7886 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 7887 7888 def _parse_foreign_key(self) -> exp.ForeignKey: 7889 expressions = ( 7890 self._parse_wrapped_id_vars() 7891 if not self._match(TokenType.REFERENCES, advance=False) 7892 else None 7893 ) 7894 reference = self._parse_references() 7895 on_options = {} 7896 7897 while self._match(TokenType.ON): 7898 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 7899 self.raise_error("Expected DELETE or UPDATE") 7900 7901 kind = self._prev.text.lower() 7902 7903 if self._match_text_seq("NO", "ACTION"): 7904 action = "NO ACTION" 7905 elif self._match(TokenType.SET): 7906 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 7907 action = "SET " + self._prev.text.upper() 7908 else: 7909 self._advance() 7910 action = self._prev.text.upper() 7911 7912 on_options[kind] = action 7913 7914 return self.expression( 7915 exp.ForeignKey( 7916 expressions=expressions, 7917 reference=reference, 7918 options=self._parse_key_constraint_options(), 7919 **on_options, 7920 ) 7921 ) 7922 7923 def _parse_primary_key_part(self) -> exp.Expr | None: 7924 return self._parse_field() 7925 7926 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 7927 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 7928 self._retreat(self._index - 1) 7929 return None 7930 7931 id_vars = self._parse_wrapped_id_vars() 7932 return self.expression( 7933 exp.PeriodForSystemTimeConstraint( 7934 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 7935 ) 7936 ) 7937 7938 def _parse_primary_key( 7939 self, 7940 wrapped_optional: bool = False, 7941 in_props: bool = False, 7942 named_primary_key: bool = False, 7943 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 7944 desc = ( 7945 self._prev.token_type == TokenType.DESC 7946 if self._match_set((TokenType.ASC, TokenType.DESC)) 7947 else None 7948 ) 7949 7950 this = None 7951 if ( 7952 named_primary_key 7953 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 7954 and self._next 7955 and self._next.token_type == TokenType.L_PAREN 7956 ): 7957 this = self._parse_id_var() 7958 7959 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 7960 return self.expression( 7961 exp.PrimaryKeyColumnConstraint( 7962 desc=desc, options=self._parse_key_constraint_options() 7963 ) 7964 ) 7965 7966 expressions = self._parse_wrapped_csv( 7967 self._parse_primary_key_part, optional=wrapped_optional 7968 ) 7969 7970 return self.expression( 7971 exp.PrimaryKey( 7972 this=this, 7973 expressions=expressions, 7974 include=self._parse_index_params(), 7975 options=self._parse_key_constraint_options(), 7976 ) 7977 ) 7978 7979 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 7980 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 7981 7982 def _parse_odbc_datetime_literal(self) -> exp.Expr: 7983 """ 7984 Parses a datetime column in ODBC format. We parse the column into the corresponding 7985 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 7986 same as we did for `DATE('yyyy-mm-dd')`. 7987 7988 Reference: 7989 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 7990 """ 7991 self._match(TokenType.VAR) 7992 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 7993 expression = self.expression(exp_class(this=self._parse_string())) 7994 if not self._match(TokenType.R_BRACE): 7995 self.raise_error("Expected }") 7996 return expression 7997 7998 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 7999 if not self._match_set(self.BRACKETS): 8000 return this 8001 8002 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8003 map_token = seq_get(self._tokens, self._index - 2) 8004 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8005 else: 8006 parse_map = False 8007 8008 bracket_kind = self._prev.token_type 8009 if ( 8010 bracket_kind == TokenType.L_BRACE 8011 and self._curr 8012 and self._curr.token_type == TokenType.VAR 8013 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8014 ): 8015 return self._parse_odbc_datetime_literal() 8016 8017 expressions = self._parse_csv( 8018 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8019 ) 8020 8021 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8022 self.raise_error("Expected ]") 8023 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8024 self.raise_error("Expected }") 8025 8026 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8027 if bracket_kind == TokenType.L_BRACE: 8028 this = self.expression( 8029 exp.Struct( 8030 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8031 ) 8032 ) 8033 elif not this: 8034 this = build_array_constructor( 8035 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8036 ) 8037 else: 8038 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8039 if constructor_type: 8040 return build_array_constructor( 8041 constructor_type, 8042 args=expressions, 8043 bracket_kind=bracket_kind, 8044 dialect=self.dialect, 8045 ) 8046 8047 expressions = apply_index_offset( 8048 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8049 ) 8050 this = self.expression( 8051 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8052 ) 8053 8054 self._add_comments(this) 8055 return self._parse_bracket(this) 8056 8057 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8058 if not self._match(TokenType.COLON): 8059 return this 8060 8061 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8062 self._advance() 8063 end: exp.Expr | None = -exp.Literal.number("1") 8064 else: 8065 end = self._parse_assignment() 8066 step = self._parse_unary() if self._match(TokenType.COLON) else None 8067 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8068 8069 def _parse_case(self) -> exp.Expr | None: 8070 if self._match(TokenType.DOT, advance=False): 8071 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8072 self._retreat(self._index - 1) 8073 return None 8074 8075 ifs = [] 8076 default = None 8077 8078 comments = self._prev_comments 8079 expression = self._parse_disjunction() 8080 8081 while self._match(TokenType.WHEN): 8082 this = self._parse_disjunction() 8083 self._match(TokenType.THEN) 8084 then = self._parse_disjunction() 8085 ifs.append(self.expression(exp.If(this=this, true=then))) 8086 8087 if self._match(TokenType.ELSE): 8088 default = self._parse_disjunction() 8089 8090 if not self._match(TokenType.END): 8091 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8092 default = exp.column("interval") 8093 else: 8094 self.raise_error("Expected END after CASE", self._prev) 8095 8096 return self.expression( 8097 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8098 ) 8099 8100 def _parse_if(self) -> exp.Expr | None: 8101 if self._match(TokenType.L_PAREN): 8102 args = self._parse_csv( 8103 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8104 ) 8105 this = self.validate_expression(exp.If.from_arg_list(args), args) 8106 self._match_r_paren() 8107 else: 8108 index = self._index - 1 8109 8110 if self.NO_PAREN_IF_COMMANDS and index == 0: 8111 return self._parse_as_command(self._prev) 8112 8113 condition = self._parse_disjunction() 8114 8115 if not condition: 8116 self._retreat(index) 8117 return None 8118 8119 self._match(TokenType.THEN) 8120 true = self._parse_disjunction() 8121 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8122 self._match(TokenType.END) 8123 this = self.expression(exp.If(this=condition, true=true, false=false)) 8124 8125 return this 8126 8127 def _parse_next_value_for(self) -> exp.Expr | None: 8128 if not self._match_text_seq("VALUE", "FOR"): 8129 self._retreat(self._index - 1) 8130 return None 8131 8132 return self.expression( 8133 exp.NextValueFor( 8134 this=self._parse_column(), 8135 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8136 ) 8137 ) 8138 8139 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8140 this = self._parse_function() or self._parse_var_or_string(upper=True) 8141 8142 if self._match(TokenType.FROM): 8143 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8144 8145 if not self._match(TokenType.COMMA): 8146 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8147 8148 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8149 8150 def _parse_gap_fill(self) -> exp.GapFill: 8151 self._match(TokenType.TABLE) 8152 this = self._parse_table() 8153 8154 self._match(TokenType.COMMA) 8155 args = [this, *self._parse_csv(self._parse_lambda)] 8156 8157 gap_fill = exp.GapFill.from_arg_list(args) 8158 return self.validate_expression(gap_fill, args) 8159 8160 def _parse_char(self) -> exp.Chr: 8161 return self.expression( 8162 exp.Chr( 8163 expressions=self._parse_csv(self._parse_assignment), 8164 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8165 ) 8166 ) 8167 8168 def _parse_charset_name(self) -> exp.Expr | None: 8169 """ 8170 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8171 for specific name shapes override this. 8172 """ 8173 return self._parse_var( 8174 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8175 ) 8176 8177 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8178 this = self._parse_assignment() 8179 8180 if not self._match(TokenType.ALIAS): 8181 if self._match(TokenType.COMMA): 8182 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8183 8184 self.raise_error("Expected AS after CAST") 8185 8186 fmt = None 8187 to = self._parse_types(with_collation=True) 8188 8189 default = None 8190 if self._match(TokenType.DEFAULT): 8191 default = self._parse_bitwise() 8192 self._match_text_seq("ON", "CONVERSION", "ERROR") 8193 8194 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8195 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8196 fmt = self._parse_at_time_zone(fmt_string) 8197 8198 if not to: 8199 to = exp.DType.UNKNOWN.into_expr() 8200 if to.this in exp.DataType.TEMPORAL_TYPES: 8201 this = self.expression( 8202 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8203 this=this, 8204 format=exp.Literal.string( 8205 format_time( 8206 fmt_string.this if fmt_string else "", 8207 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8208 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8209 ) 8210 ), 8211 safe=safe, 8212 ) 8213 ) 8214 8215 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8216 this.set("zone", fmt.args["zone"]) 8217 return this 8218 elif not to: 8219 self.raise_error("Expected TYPE after CAST") 8220 elif isinstance(to, exp.Identifier): 8221 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8222 elif to.this == exp.DType.CHAR and ( 8223 self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET") 8224 ): 8225 to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string()) 8226 8227 return self.build_cast( 8228 strict=strict, 8229 this=this, 8230 to=to, 8231 format=fmt, 8232 safe=safe, 8233 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8234 default=default, 8235 ) 8236 8237 def _parse_string_agg(self) -> exp.GroupConcat: 8238 if self._match(TokenType.DISTINCT): 8239 args: list[exp.Expr | None] = [ 8240 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8241 ] 8242 if self._match(TokenType.COMMA): 8243 args.extend(self._parse_csv(self._parse_disjunction)) 8244 else: 8245 args = self._parse_csv(self._parse_disjunction) # type: ignore 8246 8247 if self._match_text_seq("ON", "OVERFLOW"): 8248 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8249 if self._match_text_seq("ERROR"): 8250 on_overflow: exp.Expr | None = exp.var("ERROR") 8251 else: 8252 self._match_text_seq("TRUNCATE") 8253 on_overflow = self.expression( 8254 exp.OverflowTruncateBehavior( 8255 this=self._parse_string(), 8256 with_count=( 8257 self._match_text_seq("WITH", "COUNT") 8258 or not self._match_text_seq("WITHOUT", "COUNT") 8259 ), 8260 ) 8261 ) 8262 else: 8263 on_overflow = None 8264 8265 index = self._index 8266 if not self._match(TokenType.R_PAREN) and args: 8267 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8268 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8269 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8270 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8271 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8272 8273 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8274 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8275 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8276 if not self._match_text_seq("WITHIN", "GROUP"): 8277 self._retreat(index) 8278 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8279 8280 # The corresponding match_r_paren will be called in parse_function (caller) 8281 self._match_l_paren() 8282 8283 return self.expression( 8284 exp.GroupConcat( 8285 this=self._parse_order(this=seq_get(args, 0)), 8286 separator=seq_get(args, 1), 8287 on_overflow=on_overflow, 8288 ) 8289 ) 8290 8291 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8292 this = self._parse_bitwise() 8293 8294 if self._match(TokenType.USING): 8295 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8296 elif self._match(TokenType.COMMA): 8297 to = self._parse_types() 8298 else: 8299 to = None 8300 8301 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8302 8303 def _parse_xml_element(self) -> exp.XMLElement: 8304 if self._match_text_seq("EVALNAME"): 8305 evalname = True 8306 this = self._parse_bitwise() 8307 else: 8308 evalname = None 8309 self._match_text_seq("NAME") 8310 this = self._parse_id_var() 8311 8312 return self.expression( 8313 exp.XMLElement( 8314 this=this, 8315 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8316 evalname=evalname, 8317 ) 8318 ) 8319 8320 def _parse_xml_table(self) -> exp.XMLTable: 8321 namespaces = None 8322 passing = None 8323 columns = None 8324 8325 if self._match_text_seq("XMLNAMESPACES", "("): 8326 namespaces = self._parse_xml_namespace() 8327 self._match_text_seq(")", ",") 8328 8329 this = self._parse_string() 8330 8331 if self._match_text_seq("PASSING"): 8332 # The BY VALUE keywords are optional and are provided for semantic clarity 8333 self._match_text_seq("BY", "VALUE") 8334 passing = self._parse_csv(self._parse_column) 8335 8336 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8337 8338 if self._match_text_seq("COLUMNS"): 8339 columns = self._parse_csv(self._parse_field_def) 8340 8341 return self.expression( 8342 exp.XMLTable( 8343 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8344 ) 8345 ) 8346 8347 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8348 namespaces = [] 8349 8350 while True: 8351 if self._match(TokenType.DEFAULT): 8352 uri = self._parse_string() 8353 else: 8354 uri = self._parse_alias(self._parse_string()) 8355 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8356 if not self._match(TokenType.COMMA): 8357 break 8358 8359 return namespaces 8360 8361 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8362 args = self._parse_csv(self._parse_disjunction) 8363 8364 if len(args) < 3: 8365 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8366 8367 return self.expression(exp.DecodeCase(expressions=args)) 8368 8369 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8370 self._match_text_seq("KEY") 8371 key = self._parse_column() 8372 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8373 self._match_text_seq("VALUE") 8374 value = self._parse_bitwise() 8375 8376 if not key and not value: 8377 return None 8378 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8379 8380 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8381 if not this or not self._match_text_seq("FORMAT", "JSON"): 8382 return this 8383 8384 return self.expression(exp.FormatJson(this=this)) 8385 8386 def _parse_on_condition(self) -> exp.OnCondition | None: 8387 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8388 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8389 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8390 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8391 else: 8392 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8393 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8394 8395 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8396 8397 if not empty and not error and not null: 8398 return None 8399 8400 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8401 8402 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8403 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8404 for value in values: 8405 if self._match_text_seq(value, "ON", on): 8406 return f"{value} ON {on}" 8407 8408 index = self._index 8409 if self._match(TokenType.DEFAULT): 8410 default_value = self._parse_bitwise() 8411 if self._match_text_seq("ON", on): 8412 return default_value 8413 8414 self._retreat(index) 8415 8416 return None 8417 8418 @t.overload 8419 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8420 8421 @t.overload 8422 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8423 8424 def _parse_json_object(self, agg=False): 8425 star = self._parse_star() 8426 expressions = ( 8427 [star] 8428 if star 8429 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8430 ) 8431 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8432 8433 unique_keys = None 8434 if self._match_text_seq("WITH", "UNIQUE"): 8435 unique_keys = True 8436 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8437 unique_keys = False 8438 8439 self._match_text_seq("KEYS") 8440 8441 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8442 self._parse_type() 8443 ) 8444 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8445 8446 return self.expression( 8447 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8448 expressions=expressions, 8449 null_handling=null_handling, 8450 unique_keys=unique_keys, 8451 return_type=return_type, 8452 encoding=encoding, 8453 ) 8454 ) 8455 8456 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8457 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8458 if not self._match_text_seq("NESTED"): 8459 this = self._parse_id_var() 8460 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8461 kind = self._parse_types(allow_identifiers=False) 8462 nested = None 8463 else: 8464 this = None 8465 ordinality = None 8466 kind = None 8467 nested = True 8468 8469 format_json = self._match_text_seq("FORMAT", "JSON") 8470 path = self._match_text_seq("PATH") and self._parse_string() 8471 nested_schema = nested and self._parse_json_schema() 8472 8473 return self.expression( 8474 exp.JSONColumnDef( 8475 this=this, 8476 kind=kind, 8477 path=path, 8478 nested_schema=nested_schema, 8479 ordinality=ordinality, 8480 format_json=format_json, 8481 ) 8482 ) 8483 8484 def _parse_json_schema(self) -> exp.JSONSchema: 8485 self._match_text_seq("COLUMNS") 8486 return self.expression( 8487 exp.JSONSchema( 8488 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8489 ) 8490 ) 8491 8492 def _parse_json_table(self) -> exp.JSONTable: 8493 this = self._parse_format_json(self._parse_bitwise()) 8494 path = self._match(TokenType.COMMA) and self._parse_string() 8495 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8496 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8497 schema = self._parse_json_schema() 8498 8499 return exp.JSONTable( 8500 this=this, 8501 schema=schema, 8502 path=path, 8503 error_handling=error_handling, 8504 empty_handling=empty_handling, 8505 ) 8506 8507 def _parse_match_against(self) -> exp.MatchAgainst: 8508 if self._match_text_seq("TABLE"): 8509 # parse SingleStore MATCH(TABLE ...) syntax 8510 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8511 expressions = [] 8512 table = self._parse_table() 8513 if table: 8514 expressions = [table] 8515 else: 8516 expressions = self._parse_csv(self._parse_column) 8517 8518 self._match_text_seq(")", "AGAINST", "(") 8519 8520 this = self._parse_string() 8521 8522 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8523 modifier = "IN NATURAL LANGUAGE MODE" 8524 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8525 modifier = f"{modifier} WITH QUERY EXPANSION" 8526 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8527 modifier = "IN BOOLEAN MODE" 8528 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8529 modifier = "WITH QUERY EXPANSION" 8530 else: 8531 modifier = None 8532 8533 return self.expression( 8534 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8535 ) 8536 8537 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8538 def _parse_open_json(self) -> exp.OpenJSON: 8539 this = self._parse_bitwise() 8540 path = self._match(TokenType.COMMA) and self._parse_string() 8541 8542 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8543 this = self._parse_field(any_token=True) 8544 kind = self._parse_types() 8545 path = self._parse_string() 8546 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8547 8548 return self.expression( 8549 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8550 ) 8551 8552 expressions = None 8553 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8554 self._match_l_paren() 8555 expressions = self._parse_csv(_parse_open_json_column_def) 8556 8557 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8558 8559 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8560 args = self._parse_csv(self._parse_bitwise) 8561 8562 if self._match(TokenType.IN): 8563 return self.expression( 8564 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8565 ) 8566 8567 if haystack_first: 8568 haystack = seq_get(args, 0) 8569 needle = seq_get(args, 1) 8570 else: 8571 haystack = seq_get(args, 1) 8572 needle = seq_get(args, 0) 8573 8574 return self.expression( 8575 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8576 ) 8577 8578 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8579 args = self._parse_csv(self._parse_table) 8580 return exp.JoinHint(this=func_name.upper(), expressions=args) 8581 8582 def _parse_substring(self) -> exp.Substring: 8583 # Postgres supports the form: substring(string [from int] [for int]) 8584 # (despite being undocumented, the reverse order also works) 8585 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8586 8587 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8588 8589 start, length = None, None 8590 8591 while self._curr: 8592 if self._match(TokenType.FROM): 8593 start = self._parse_bitwise() 8594 elif self._match(TokenType.FOR): 8595 if not start: 8596 start = exp.Literal.number(1) 8597 length = self._parse_bitwise() 8598 else: 8599 break 8600 8601 if start: 8602 args.append(start) 8603 if length: 8604 args.append(length) 8605 8606 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8607 8608 def _parse_trim(self) -> exp.Trim: 8609 # https://www.w3resource.com/sql/character-functions/trim.php 8610 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8611 8612 position = None 8613 collation = None 8614 expression = None 8615 8616 if self._match_texts(self.TRIM_TYPES): 8617 position = self._prev.text.upper() 8618 8619 this = self._parse_bitwise() 8620 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8621 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8622 expression = self._parse_bitwise() 8623 8624 if invert_order: 8625 this, expression = expression, this 8626 8627 if self._match(TokenType.COLLATE): 8628 collation = self._parse_bitwise() 8629 8630 return self.expression( 8631 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8632 ) 8633 8634 def _parse_window_clause(self) -> list[exp.Expr] | None: 8635 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8636 8637 def _parse_named_window(self) -> exp.Expr | None: 8638 return self._parse_window(self._parse_id_var(), alias=True) 8639 8640 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8641 if self._curr.token_type == TokenType.VAR: 8642 if self._match_text_seq("IGNORE", "NULLS"): 8643 return self.expression(exp.IgnoreNulls(this=this)) 8644 if self._match_text_seq("RESPECT", "NULLS"): 8645 return self.expression(exp.RespectNulls(this=this)) 8646 return this 8647 8648 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8649 if self._match(TokenType.HAVING): 8650 self._match_texts(("MAX", "MIN")) 8651 max = self._prev.text.upper() != "MIN" 8652 return self.expression( 8653 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8654 ) 8655 8656 return this 8657 8658 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8659 func = this 8660 comments = func.comments if isinstance(func, exp.Expr) else None 8661 8662 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8663 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8664 if self._match_text_seq("FROM", "FIRST"): 8665 this.set("from_first", True) 8666 elif self._match_text_seq("FROM", "LAST"): 8667 this.set("from_first", False) 8668 8669 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8670 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8671 if self._match_text_seq("WITHIN", "GROUP"): 8672 order = self._parse_wrapped(self._parse_order) 8673 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8674 8675 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8676 self._match(TokenType.WHERE) 8677 this = self.expression( 8678 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8679 ) 8680 self._match_r_paren() 8681 8682 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8683 # Some dialects choose to implement and some do not. 8684 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8685 8686 # There is some code above in _parse_lambda that handles 8687 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8688 8689 # The below changes handle 8690 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8691 8692 # Oracle allows both formats 8693 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8694 # and Snowflake chose to do the same for familiarity 8695 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8696 if isinstance(this, exp.AggFunc): 8697 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8698 8699 if ignore_respect and ignore_respect is not this: 8700 ignore_respect.replace(ignore_respect.this) 8701 this = self.expression(ignore_respect.__class__(this=this)) 8702 8703 this = self._parse_respect_or_ignore_nulls(this) 8704 8705 # bigquery select from window x AS (partition by ...) 8706 if alias: 8707 over = None 8708 self._match(TokenType.ALIAS) 8709 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8710 return this 8711 else: 8712 over = self._prev.text.upper() 8713 8714 if comments and isinstance(func, exp.Expr): 8715 func.pop_comments() 8716 8717 if not self._match(TokenType.L_PAREN): 8718 return self.expression( 8719 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8720 ) 8721 8722 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8723 8724 first: bool | None = True if self._match(TokenType.FIRST) else None 8725 if self._match_text_seq("LAST"): 8726 first = False 8727 8728 partition, order = self._parse_partition_and_order() 8729 kind = ( 8730 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8731 ) and self._prev.text 8732 8733 if kind: 8734 self._match(TokenType.BETWEEN) 8735 start = self._parse_window_spec() 8736 8737 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8738 exclude = ( 8739 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8740 if self._match_text_seq("EXCLUDE") 8741 else None 8742 ) 8743 8744 spec = self.expression( 8745 exp.WindowSpec( 8746 kind=kind, 8747 start=start["value"], 8748 start_side=start["side"], 8749 end=end.get("value"), 8750 end_side=end.get("side"), 8751 exclude=exclude, 8752 ) 8753 ) 8754 else: 8755 spec = None 8756 8757 self._match_r_paren() 8758 8759 window = self.expression( 8760 exp.Window( 8761 this=this, 8762 partition_by=partition, 8763 order=order, 8764 spec=spec, 8765 alias=window_alias, 8766 over=over, 8767 first=first, 8768 ), 8769 comments=comments, 8770 ) 8771 8772 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 8773 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 8774 return self._parse_window(window, alias=alias) 8775 8776 return window 8777 8778 def _parse_partition_and_order( 8779 self, 8780 ) -> tuple[list[exp.Expr], exp.Expr | None]: 8781 return self._parse_partition_by(), self._parse_order() 8782 8783 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 8784 self._match(TokenType.BETWEEN) 8785 8786 return { 8787 "value": ( 8788 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 8789 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 8790 or self._parse_bitwise() 8791 ), 8792 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 8793 } 8794 8795 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 8796 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 8797 # so this section tries to parse the clause version and if it fails, it treats the token 8798 # as an identifier (alias) 8799 if self._can_parse_limit_or_offset(): 8800 return this 8801 8802 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 8803 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 8804 if self._can_parse_named_window(): 8805 return this 8806 8807 any_token = self._match(TokenType.ALIAS) 8808 comments = self._prev_comments 8809 8810 if explicit and not any_token: 8811 return this 8812 8813 if self._match(TokenType.L_PAREN): 8814 aliases = self.expression( 8815 exp.Aliases( 8816 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 8817 ), 8818 comments=comments, 8819 ) 8820 self._match_r_paren(aliases) 8821 return aliases 8822 8823 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 8824 self.STRING_ALIASES and self._parse_string_as_identifier() 8825 ) 8826 8827 if alias: 8828 comments.extend(alias.pop_comments()) 8829 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 8830 column = this.this 8831 8832 # Moves the comment next to the alias in `expr /* comment */ AS alias` 8833 if not this.comments and column and column.comments: 8834 this.comments = column.pop_comments() 8835 8836 return this 8837 8838 def _parse_id_var( 8839 self, 8840 any_token: bool = True, 8841 tokens: t.Collection[TokenType] | None = None, 8842 ) -> exp.Expr | None: 8843 expression = self._parse_identifier() 8844 if not expression and ( 8845 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 8846 ): 8847 quoted = self._prev.token_type == TokenType.STRING 8848 expression = self._identifier_expression(quoted=quoted) 8849 8850 return expression 8851 8852 def _parse_string(self) -> exp.Expr | None: 8853 if self._match_set(self.STRING_PARSERS): 8854 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 8855 return self._parse_placeholder() 8856 8857 def _parse_string_as_identifier(self) -> exp.Identifier | None: 8858 if not self._match(TokenType.STRING): 8859 return None 8860 output = exp.to_identifier(self._prev.text, quoted=True) 8861 output.update_positions(self._prev) 8862 return output 8863 8864 def _parse_number(self) -> exp.Expr | None: 8865 if self._match_set(self.NUMERIC_PARSERS): 8866 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 8867 return self._parse_placeholder() 8868 8869 def _parse_identifier(self) -> exp.Expr | None: 8870 if self._match(TokenType.IDENTIFIER): 8871 return self._identifier_expression(quoted=True) 8872 return self._parse_placeholder() 8873 8874 def _parse_var( 8875 self, 8876 any_token: bool = False, 8877 tokens: t.Collection[TokenType] | None = None, 8878 upper: bool = False, 8879 ) -> exp.Expr | None: 8880 if ( 8881 (any_token and self._advance_any()) 8882 or self._match(TokenType.VAR) 8883 or (self._match_set(tokens) if tokens else False) 8884 ): 8885 return self.expression( 8886 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 8887 ) 8888 return self._parse_placeholder() 8889 8890 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 8891 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 8892 self._advance() 8893 return self._prev 8894 return None 8895 8896 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 8897 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 8898 8899 def _parse_primary_or_var(self) -> exp.Expr | None: 8900 return self._parse_primary() or self._parse_var(any_token=True) 8901 8902 def _parse_null(self) -> exp.Expr | None: 8903 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 8904 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 8905 return self._parse_placeholder() 8906 8907 def _parse_boolean(self) -> exp.Expr | None: 8908 if self._match(TokenType.TRUE): 8909 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 8910 if self._match(TokenType.FALSE): 8911 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 8912 return self._parse_placeholder() 8913 8914 def _parse_star(self) -> exp.Expr | None: 8915 if self._match(TokenType.STAR): 8916 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 8917 return self._parse_placeholder() 8918 8919 def _parse_parameter(self) -> exp.Parameter: 8920 this = self._parse_identifier() or self._parse_primary_or_var() 8921 return self.expression(exp.Parameter(this=this)) 8922 8923 def _parse_placeholder(self) -> exp.Expr | None: 8924 if self._match_set(self.PLACEHOLDER_PARSERS): 8925 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 8926 if placeholder: 8927 return placeholder 8928 self._advance(-1) 8929 return None 8930 8931 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 8932 if not self._match_texts(keywords): 8933 return None 8934 if self._match(TokenType.L_PAREN, advance=False): 8935 return self._parse_wrapped_csv(self._parse_expression) 8936 8937 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 8938 return [expression] if expression else None 8939 8940 def _parse_csv( 8941 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 8942 ) -> list[T]: 8943 parse_result = parse_method() 8944 items = [parse_result] if parse_result is not None else [] 8945 8946 while self._match(sep): 8947 if isinstance(parse_result, exp.Expr): 8948 self._add_comments(parse_result) 8949 parse_result = parse_method() 8950 if parse_result is not None: 8951 items.append(parse_result) 8952 8953 return items 8954 8955 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 8956 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 8957 8958 def _parse_wrapped_csv( 8959 self, 8960 parse_method: t.Callable[[], T | None], 8961 sep: TokenType = TokenType.COMMA, 8962 optional: bool = False, 8963 ) -> list[T]: 8964 return self._parse_wrapped( 8965 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 8966 ) 8967 8968 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 8969 wrapped = self._match(TokenType.L_PAREN) 8970 if not wrapped and not optional: 8971 self.raise_error("Expecting (") 8972 parse_result = parse_method() 8973 if wrapped: 8974 self._match_r_paren() 8975 return parse_result 8976 8977 def _parse_expressions(self) -> list[exp.Expr]: 8978 return self._parse_csv(self._parse_expression) 8979 8980 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 8981 return ( 8982 self._parse_set_operations( 8983 self._parse_alias(self._parse_assignment(), explicit=True) 8984 if alias 8985 else self._parse_assignment() 8986 ) 8987 or self._parse_select() 8988 ) 8989 8990 def _parse_ddl_select(self) -> exp.Expr | None: 8991 return self._parse_query_modifiers( 8992 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 8993 ) 8994 8995 def _parse_transaction(self) -> exp.Transaction | exp.Command: 8996 this = None 8997 if self._match_texts(self.TRANSACTION_KIND): 8998 this = self._prev.text 8999 9000 self._match_texts(("TRANSACTION", "WORK")) 9001 9002 modes = [] 9003 while True: 9004 mode = [] 9005 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 9006 mode.append(self._prev.text) 9007 9008 if mode: 9009 modes.append(" ".join(mode)) 9010 if not self._match(TokenType.COMMA): 9011 break 9012 9013 return self.expression(exp.Transaction(this=this, modes=modes)) 9014 9015 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 9016 chain = None 9017 savepoint = None 9018 is_rollback = self._prev.token_type == TokenType.ROLLBACK 9019 9020 self._match_texts(("TRANSACTION", "WORK")) 9021 9022 if self._match_text_seq("TO"): 9023 self._match_text_seq("SAVEPOINT") 9024 savepoint = self._parse_id_var() 9025 9026 if self._match(TokenType.AND): 9027 chain = not self._match_text_seq("NO") 9028 self._match_text_seq("CHAIN") 9029 9030 if is_rollback: 9031 return self.expression(exp.Rollback(savepoint=savepoint)) 9032 9033 return self.expression(exp.Commit(chain=chain)) 9034 9035 def _parse_refresh(self) -> exp.Refresh | exp.Command: 9036 if self._match_text_seq("EXTERNAL", "TABLE"): 9037 kind = "EXTERNAL TABLE" 9038 elif self._match(TokenType.TABLE): 9039 kind = "TABLE" 9040 elif self._match_text_seq("MATERIALIZED", "VIEW"): 9041 kind = "MATERIALIZED VIEW" 9042 else: 9043 kind = "" 9044 9045 this = self._parse_string() or self._parse_table() 9046 if not kind and not isinstance(this, exp.Literal): 9047 return self._parse_as_command(self._prev) 9048 9049 return self.expression(exp.Refresh(this=this, kind=kind)) 9050 9051 def _parse_column_def_with_exists(self): 9052 start = self._index 9053 self._match(TokenType.COLUMN) 9054 9055 exists_column = self._parse_exists(not_=True) 9056 expression = self._parse_field_def() 9057 9058 if not isinstance(expression, exp.ColumnDef): 9059 self._retreat(start) 9060 return None 9061 9062 expression.set("exists", exists_column) 9063 9064 return expression 9065 9066 def _parse_add_column(self) -> exp.ColumnDef | None: 9067 if not self._prev.text.upper() == "ADD": 9068 return None 9069 9070 return self._parse_column_def_with_exists() 9071 9072 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9073 drop = self._parse_drop() if self._match(TokenType.DROP) else None 9074 if drop and not isinstance(drop, exp.Command): 9075 drop.set("kind", drop.args.get("kind", "COLUMN")) 9076 return drop 9077 9078 def _parse_alter_drop_action(self) -> exp.Expr | None: 9079 return self._parse_drop_column() 9080 9081 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9082 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9083 return self.expression( 9084 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9085 ) 9086 9087 def _parse_alter_table_add(self) -> list[exp.Expr]: 9088 def _parse_add_alteration() -> exp.Expr | None: 9089 self._match_text_seq("ADD") 9090 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False): 9091 return self.expression( 9092 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9093 ) 9094 9095 column_def = self._parse_add_column() 9096 if isinstance(column_def, exp.ColumnDef): 9097 return column_def 9098 9099 exists = self._parse_exists(not_=True) 9100 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9101 return self.expression( 9102 exp.AddPartition( 9103 exists=exists, 9104 this=self._parse_field(any_token=True), 9105 location=self._match_text_seq("LOCATION", advance=False) 9106 and self._parse_property(), 9107 ) 9108 ) 9109 9110 return None 9111 9112 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9113 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9114 or self._match_text_seq("COLUMNS") 9115 ): 9116 schema = self._parse_schema() 9117 9118 return ( 9119 ensure_list(schema) 9120 if schema 9121 else self._parse_csv(self._parse_column_def_with_exists) 9122 ) 9123 9124 return self._parse_csv(_parse_add_alteration) 9125 9126 def _parse_alter_table_alter(self) -> exp.Expr | None: 9127 if self._match_texts(self.ALTER_ALTER_PARSERS): 9128 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9129 9130 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9131 # keyword after ALTER we default to parsing this statement 9132 self._match(TokenType.COLUMN) 9133 exists = self._parse_exists() 9134 column = self._parse_field(any_token=True) 9135 9136 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9137 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9138 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9139 return self.expression( 9140 exp.AlterColumn( 9141 this=column, default=self._parse_disjunction(), exists=exists or None 9142 ) 9143 ) 9144 if self._match(TokenType.COMMENT): 9145 return self.expression( 9146 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9147 ) 9148 if self._match_text_seq("DROP", "NOT", "NULL"): 9149 return self.expression( 9150 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9151 ) 9152 if self._match_text_seq("SET", "NOT", "NULL"): 9153 return self.expression( 9154 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9155 ) 9156 9157 if self._match_text_seq("SET", "VISIBLE"): 9158 return self.expression( 9159 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9160 ) 9161 if self._match_text_seq("SET", "INVISIBLE"): 9162 return self.expression( 9163 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9164 ) 9165 9166 self._match_text_seq("SET", "DATA") 9167 self._match_text_seq("TYPE") 9168 return self.expression( 9169 exp.AlterColumn( 9170 this=column, 9171 dtype=self._parse_types(), 9172 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9173 using=self._match(TokenType.USING) and self._parse_disjunction(), 9174 exists=exists or None, 9175 ) 9176 ) 9177 9178 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9179 if self._match_texts(("ALL", "EVEN", "AUTO")): 9180 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9181 9182 self._match_text_seq("KEY", "DISTKEY") 9183 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9184 9185 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9186 if compound: 9187 self._match_text_seq("SORTKEY") 9188 9189 if self._match(TokenType.L_PAREN, advance=False): 9190 return self.expression( 9191 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9192 ) 9193 9194 self._match_texts(("AUTO", "NONE")) 9195 return self.expression( 9196 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9197 ) 9198 9199 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9200 index = self._index - 1 9201 9202 partition_exists = self._parse_exists() 9203 if self._match(TokenType.PARTITION, advance=False): 9204 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9205 9206 self._retreat(index) 9207 return self._parse_csv(self._parse_alter_drop_action) 9208 9209 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9210 if self._match(TokenType.COLUMN) or ( 9211 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9212 ): 9213 exists = self._parse_exists() 9214 old_column = self._parse_column() 9215 to = self._match_text_seq("TO") 9216 new_column = self._parse_column() 9217 9218 if old_column is None or not to or new_column is None: 9219 return None 9220 9221 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9222 9223 self._match_text_seq("TO") 9224 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9225 9226 def _parse_alter_table_set(self) -> exp.AlterSet: 9227 alter_set = self.expression(exp.AlterSet()) 9228 9229 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9230 "TABLE", "PROPERTIES" 9231 ): 9232 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9233 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9234 alter_set.set("expressions", [self._parse_assignment()]) 9235 elif self._match_texts(("LOGGED", "UNLOGGED")): 9236 alter_set.set("option", exp.var(self._prev.text.upper())) 9237 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9238 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9239 elif self._match_text_seq("LOCATION"): 9240 alter_set.set("location", self._parse_field()) 9241 elif self._match_text_seq("ACCESS", "METHOD"): 9242 alter_set.set("access_method", self._parse_field()) 9243 elif self._match_text_seq("TABLESPACE"): 9244 alter_set.set("tablespace", self._parse_field()) 9245 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9246 alter_set.set("file_format", [self._parse_field()]) 9247 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9248 alter_set.set("file_format", self._parse_wrapped_options()) 9249 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9250 alter_set.set("copy_options", self._parse_wrapped_options()) 9251 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9252 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9253 else: 9254 if self._match_text_seq("SERDE"): 9255 alter_set.set("serde", self._parse_field()) 9256 9257 properties = self._parse_wrapped(self._parse_properties, optional=True) 9258 alter_set.set("expressions", [properties]) 9259 9260 return alter_set 9261 9262 def _parse_alter_session(self) -> exp.AlterSession: 9263 """Parse ALTER SESSION SET/UNSET statements.""" 9264 if self._match(TokenType.SET): 9265 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9266 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9267 9268 self._match_text_seq("UNSET") 9269 expressions = self._parse_csv( 9270 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9271 ) 9272 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9273 9274 def _parse_alter(self) -> exp.Alter | exp.Command: 9275 start = self._prev 9276 9277 iceberg = self._match_text_seq("ICEBERG") 9278 9279 alter_token = self._match_set(self.ALTERABLES) and self._prev 9280 if not alter_token: 9281 return self._parse_as_command(start) 9282 if iceberg and alter_token.token_type != TokenType.TABLE: 9283 return self._parse_as_command(start) 9284 9285 exists = self._parse_exists() 9286 only = self._match_text_seq("ONLY") 9287 9288 if alter_token.token_type == TokenType.SESSION: 9289 this = None 9290 check = None 9291 cluster = None 9292 else: 9293 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9294 check = self._match_text_seq("WITH", "CHECK") 9295 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9296 9297 if self._next: 9298 self._advance() 9299 9300 parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None 9301 if parser: 9302 actions = ensure_list(parser(self)) 9303 not_valid = self._match_text_seq("NOT", "VALID") 9304 options = self._parse_csv(self._parse_property) 9305 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9306 9307 if not self._curr and actions: 9308 return self.expression( 9309 exp.Alter( 9310 this=this, 9311 kind=alter_token.text.upper(), 9312 exists=exists, 9313 actions=actions, 9314 only=only, 9315 options=options, 9316 cluster=cluster, 9317 not_valid=not_valid, 9318 check=check, 9319 cascade=cascade, 9320 iceberg=iceberg, 9321 ) 9322 ) 9323 9324 return self._parse_as_command(start) 9325 9326 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9327 start = self._prev 9328 # https://duckdb.org/docs/sql/statements/analyze 9329 if not self._curr: 9330 return self.expression(exp.Analyze()) 9331 9332 options = [] 9333 while self._match_texts(self.ANALYZE_STYLES): 9334 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9335 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9336 else: 9337 options.append(self._prev.text.upper()) 9338 9339 tables: exp.Expr | list[exp.Expr] | None = None 9340 inner_expression: exp.Expr | None = None 9341 9342 kind = self._curr.text.upper() if self._curr else None 9343 9344 if self._match(TokenType.TABLE): 9345 tables = self._parse_csv(self._parse_table_parts) 9346 elif self._match(TokenType.INDEX): 9347 tables = self._parse_table_parts() 9348 elif self._match_text_seq("TABLES"): 9349 if self._match_set((TokenType.FROM, TokenType.IN)): 9350 kind = f"{kind} {self._prev.text.upper()}" 9351 tables = self._parse_table(schema=True, is_db_reference=True) 9352 elif self._match_text_seq("DATABASE"): 9353 tables = self._parse_table(schema=True, is_db_reference=True) 9354 elif self._match_text_seq("CLUSTER"): 9355 tables = self._parse_table() 9356 # Try matching inner expr keywords before fallback to parse table. 9357 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9358 kind = None 9359 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9360 else: 9361 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9362 kind = None 9363 tables = self._parse_csv(self._parse_table_parts) 9364 9365 partition = self._try_parse(self._parse_partition) 9366 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9367 return self._parse_as_command(start) 9368 9369 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9370 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9371 "WITH", "ASYNC", "MODE" 9372 ): 9373 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9374 else: 9375 mode = None 9376 9377 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9378 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9379 9380 properties = self._parse_properties() 9381 return self.expression( 9382 exp.Analyze( 9383 kind=kind, 9384 tables=ensure_list(tables), 9385 mode=mode, 9386 partition=partition, 9387 properties=properties, 9388 expression=inner_expression, 9389 options=options, 9390 ) 9391 ) 9392 9393 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9394 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9395 this = None 9396 kind = self._prev.text.upper() 9397 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9398 expressions = [] 9399 9400 if not self._match_text_seq("STATISTICS"): 9401 self.raise_error("Expecting token STATISTICS") 9402 9403 if self._match_text_seq("NOSCAN"): 9404 this = "NOSCAN" 9405 elif self._match(TokenType.FOR): 9406 if self._match_text_seq("ALL", "COLUMNS"): 9407 this = "FOR ALL COLUMNS" 9408 if self._match_text_seq("COLUMNS"): 9409 this = "FOR COLUMNS" 9410 expressions = self._parse_csv(self._parse_column_reference) 9411 elif self._match_text_seq("SAMPLE"): 9412 sample = self._parse_number() 9413 expressions = [ 9414 self.expression( 9415 exp.AnalyzeSample( 9416 sample=sample, 9417 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9418 ) 9419 ) 9420 ] 9421 9422 return self.expression( 9423 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9424 ) 9425 9426 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9427 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9428 kind = None 9429 this = None 9430 expression: exp.Expr | None = None 9431 if self._match_text_seq("REF", "UPDATE"): 9432 kind = "REF" 9433 this = "UPDATE" 9434 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9435 this = "UPDATE SET DANGLING TO NULL" 9436 elif self._match_text_seq("STRUCTURE"): 9437 kind = "STRUCTURE" 9438 if self._match_text_seq("CASCADE", "FAST"): 9439 this = "CASCADE FAST" 9440 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9441 ("ONLINE", "OFFLINE") 9442 ): 9443 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9444 expression = self._parse_into() 9445 9446 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9447 9448 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9449 this = self._prev.text.upper() 9450 if self._match_text_seq("COLUMNS"): 9451 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9452 return None 9453 9454 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9455 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9456 if self._match_text_seq("STATISTICS"): 9457 return self.expression(exp.AnalyzeDelete(kind=kind)) 9458 return None 9459 9460 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9461 if self._match_text_seq("CHAINED", "ROWS"): 9462 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9463 return None 9464 9465 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9466 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9467 this = self._prev.text.upper() 9468 expression: exp.Expr | None = None 9469 expressions = [] 9470 update_options = None 9471 9472 if self._match_text_seq("HISTOGRAM", "ON"): 9473 expressions = self._parse_csv(self._parse_column_reference) 9474 with_expressions = [] 9475 while self._match(TokenType.WITH): 9476 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9477 if self._match_texts(("SYNC", "ASYNC")): 9478 if self._match_text_seq("MODE", advance=False): 9479 with_expressions.append(f"{self._prev.text.upper()} MODE") 9480 self._advance() 9481 else: 9482 buckets = self._parse_number() 9483 if self._match_text_seq("BUCKETS"): 9484 with_expressions.append(f"{buckets} BUCKETS") 9485 if with_expressions: 9486 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9487 9488 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9489 TokenType.UPDATE, advance=False 9490 ): 9491 update_options = self._prev.text.upper() 9492 self._advance() 9493 elif self._match_text_seq("USING", "DATA"): 9494 expression = self.expression(exp.UsingData(this=self._parse_string())) 9495 9496 return self.expression( 9497 exp.AnalyzeHistogram( 9498 this=this, 9499 expressions=expressions, 9500 expression=expression, 9501 update_options=update_options, 9502 ) 9503 ) 9504 9505 def _parse_merge(self) -> exp.Merge: 9506 self._match(TokenType.INTO) 9507 target = self._parse_table() 9508 9509 if target and self._match(TokenType.ALIAS, advance=False): 9510 target.set("alias", self._parse_table_alias()) 9511 9512 self._match(TokenType.USING) 9513 using = self._parse_table() 9514 9515 return self.expression( 9516 exp.Merge( 9517 this=target, 9518 using=using, 9519 on=self._match(TokenType.ON) and self._parse_disjunction(), 9520 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9521 whens=self._parse_when_matched(), 9522 returning=self._parse_returning(), 9523 ) 9524 ) 9525 9526 def _parse_when_matched(self) -> exp.Whens: 9527 whens = [] 9528 9529 while self._match(TokenType.WHEN): 9530 matched = not self._match(TokenType.NOT) 9531 self._match_text_seq("MATCHED") 9532 source = ( 9533 False 9534 if self._match_text_seq("BY", "TARGET") 9535 else self._match_text_seq("BY", "SOURCE") 9536 ) 9537 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9538 9539 self._match(TokenType.THEN) 9540 9541 if self._match(TokenType.INSERT): 9542 this = self._parse_star() 9543 if this: 9544 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9545 else: 9546 then = self.expression( 9547 exp.Insert( 9548 this=exp.var("ROW") 9549 if self._match_text_seq("ROW") 9550 else self._parse_value(values=False), 9551 expression=self._match_text_seq("VALUES") and self._parse_value(), 9552 where=self._parse_where(), 9553 ) 9554 ) 9555 elif self._match(TokenType.UPDATE): 9556 expressions = self._parse_star() 9557 if expressions: 9558 then = self.expression(exp.Update(expressions=expressions)) 9559 else: 9560 then = self.expression( 9561 exp.Update( 9562 expressions=self._match(TokenType.SET) 9563 and self._parse_csv(self._parse_equality), 9564 where=self._parse_where(), 9565 ) 9566 ) 9567 elif self._match(TokenType.DELETE): 9568 then = self.expression(exp.Var(this=self._prev.text)) 9569 else: 9570 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9571 9572 whens.append( 9573 self.expression( 9574 exp.When(matched=matched, source=source, condition=condition, then=then) 9575 ) 9576 ) 9577 return self.expression(exp.Whens(expressions=whens)) 9578 9579 def _parse_show(self) -> exp.Expr | None: 9580 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9581 if parser: 9582 return parser(self) 9583 return self._parse_as_command(self._prev) 9584 9585 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9586 index = self._index 9587 9588 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9589 return self._parse_set_transaction(global_=kind == "GLOBAL") 9590 9591 left = self._parse_primary() or self._parse_column() 9592 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 9593 9594 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 9595 self._retreat(index) 9596 return None 9597 9598 right = self._parse_statement() or self._parse_id_var() 9599 if isinstance(right, (exp.Column, exp.Identifier)): 9600 right = exp.var(right.name) 9601 9602 this = self.expression(exp.EQ(this=left, expression=right)) 9603 return self.expression(exp.SetItem(this=this, kind=kind)) 9604 9605 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 9606 self._match_text_seq("TRANSACTION") 9607 characteristics = self._parse_csv( 9608 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 9609 ) 9610 return self.expression( 9611 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 9612 ) 9613 9614 def _parse_set_item(self) -> exp.Expr | None: 9615 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 9616 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 9617 9618 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 9619 index = self._index 9620 set_ = self.expression( 9621 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 9622 ) 9623 9624 if self._curr: 9625 self._retreat(index) 9626 return self._parse_as_command(self._prev) 9627 9628 return set_ 9629 9630 def _parse_var_from_options( 9631 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 9632 ) -> exp.Var | None: 9633 start = self._curr 9634 if not start: 9635 return None 9636 9637 option = start.text.upper() 9638 continuations = ( 9639 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 9640 ) 9641 9642 index = self._index 9643 self._advance() 9644 for keywords in continuations or []: 9645 if isinstance(keywords, str): 9646 keywords = (keywords,) 9647 9648 if self._match_text_seq(*keywords): 9649 option = f"{option} {' '.join(keywords)}" 9650 break 9651 else: 9652 if continuations or continuations is None: 9653 if raise_unmatched: 9654 self.raise_error(f"Unknown option {option}") 9655 9656 self._retreat(index) 9657 return None 9658 9659 return exp.var(option) 9660 9661 def _parse_as_command(self, start: Token) -> exp.Command: 9662 while self._curr: 9663 self._advance() 9664 text = self._find_sql(start, self._prev) 9665 size = len(start.text) 9666 self._warn_unsupported() 9667 return exp.Command(this=text[:size], expression=text[size:]) 9668 9669 def _parse_dict_property(self, this: str) -> exp.DictProperty: 9670 settings = [] 9671 9672 self._match_l_paren() 9673 kind = self._parse_id_var() 9674 9675 if self._match(TokenType.L_PAREN): 9676 while True: 9677 key = self._parse_id_var() 9678 value = self._parse_function() or self._parse_primary_or_var() 9679 if not key and value is None: 9680 break 9681 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 9682 self._match(TokenType.R_PAREN) 9683 9684 self._match_r_paren() 9685 9686 return self.expression( 9687 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 9688 ) 9689 9690 def _parse_dict_range(self, this: str) -> exp.DictRange: 9691 self._match_l_paren() 9692 has_min = self._match_text_seq("MIN") 9693 if has_min: 9694 min = self._parse_var() or self._parse_primary() 9695 self._match_text_seq("MAX") 9696 max = self._parse_var() or self._parse_primary() 9697 else: 9698 max = self._parse_var() or self._parse_primary() 9699 min = exp.Literal.number(0) 9700 self._match_r_paren() 9701 return self.expression(exp.DictRange(this=this, min=min, max=max)) 9702 9703 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 9704 index = self._index 9705 expression = self._parse_column() 9706 position = self._match(TokenType.COMMA) and self._parse_column() 9707 9708 if not self._match(TokenType.IN): 9709 self._retreat(index - 1) 9710 return None 9711 iterator = self._parse_column() 9712 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 9713 return self.expression( 9714 exp.Comprehension( 9715 this=this, 9716 expression=expression, 9717 position=position, 9718 iterator=iterator, 9719 condition=condition, 9720 ) 9721 ) 9722 9723 def _parse_heredoc(self) -> exp.Heredoc | None: 9724 if self._match(TokenType.HEREDOC_STRING): 9725 return self.expression(exp.Heredoc(this=self._prev.text)) 9726 9727 if not self._match_text_seq("$"): 9728 return None 9729 9730 tags = ["$"] 9731 tag_text = None 9732 9733 if self._is_connected(): 9734 self._advance() 9735 tags.append(self._prev.text.upper()) 9736 else: 9737 self.raise_error("No closing $ found") 9738 9739 if tags[-1] != "$": 9740 if self._is_connected() and self._match_text_seq("$"): 9741 tag_text = tags[-1] 9742 tags.append("$") 9743 else: 9744 self.raise_error("No closing $ found") 9745 9746 heredoc_start = self._curr 9747 9748 while self._curr: 9749 if self._match_text_seq(*tags, advance=False): 9750 this = self._find_sql(heredoc_start, self._prev) 9751 self._advance(len(tags)) 9752 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 9753 9754 self._advance() 9755 9756 self.raise_error(f"No closing {''.join(tags)} found") 9757 return None 9758 9759 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 9760 if not self._curr: 9761 return None 9762 9763 index = self._index 9764 this = [] 9765 while True: 9766 # The current token might be multiple words 9767 curr = self._curr.text.upper() 9768 key = curr.split(" ") 9769 this.append(curr) 9770 9771 self._advance() 9772 result, trie = in_trie(trie, key) 9773 if result == TrieResult.FAILED: 9774 break 9775 9776 if result == TrieResult.EXISTS: 9777 subparser = parsers[" ".join(this)] 9778 return subparser 9779 9780 self._retreat(index) 9781 return None 9782 9783 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 9784 if not self._match(TokenType.L_PAREN, expression=expression): 9785 self.raise_error("Expecting (") 9786 9787 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 9788 if not self._match(TokenType.R_PAREN, expression=expression): 9789 self.raise_error("Expecting )") 9790 9791 def _replace_lambda( 9792 self, node: exp.Expr | None, expressions: list[exp.Expr] 9793 ) -> exp.Expr | None: 9794 if not node: 9795 return node 9796 9797 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 9798 9799 for column in node.find_all(exp.Column): 9800 typ = lambda_types.get(column.parts[0].name) 9801 if typ is not None: 9802 dot_or_id = column.to_dot() if column.table else column.this 9803 9804 if typ: 9805 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ)) 9806 9807 parent = column.parent 9808 9809 while isinstance(parent, exp.Dot): 9810 if not isinstance(parent.parent, exp.Dot): 9811 parent.replace(dot_or_id) 9812 break 9813 parent = parent.parent 9814 else: 9815 if column is node: 9816 node = dot_or_id 9817 else: 9818 column.replace(dot_or_id) 9819 return node 9820 9821 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 9822 start = self._prev 9823 9824 # Not to be confused with TRUNCATE(number, decimals) function call 9825 if self._match(TokenType.L_PAREN): 9826 self._retreat(self._index - 2) 9827 return self._parse_function() 9828 9829 # Clickhouse supports TRUNCATE DATABASE as well 9830 is_database = self._match(TokenType.DATABASE) 9831 9832 self._match(TokenType.TABLE) 9833 9834 exists = self._parse_exists(not_=False) 9835 9836 expressions = self._parse_csv( 9837 lambda: self._parse_table(schema=True, is_db_reference=is_database) 9838 ) 9839 9840 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9841 9842 if self._match_text_seq("RESTART", "IDENTITY"): 9843 identity = "RESTART" 9844 elif self._match_text_seq("CONTINUE", "IDENTITY"): 9845 identity = "CONTINUE" 9846 else: 9847 identity = None 9848 9849 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 9850 option = self._prev.text 9851 else: 9852 option = None 9853 9854 partition = self._parse_partition() 9855 9856 # Fallback case 9857 if self._curr: 9858 return self._parse_as_command(start) 9859 9860 return self.expression( 9861 exp.TruncateTable( 9862 expressions=expressions, 9863 is_database=is_database, 9864 exists=exists, 9865 cluster=cluster, 9866 identity=identity, 9867 option=option, 9868 partition=partition, 9869 ) 9870 ) 9871 9872 def _parse_indexed_column(self) -> exp.Expr | None: 9873 return self._parse_ordered(self._parse_opclass) 9874 9875 def _parse_with_operator(self) -> exp.Expr | None: 9876 this = self._parse_indexed_column() 9877 9878 if not self._match(TokenType.WITH): 9879 return this 9880 9881 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 9882 9883 return self.expression(exp.WithOperator(this=this, op=op)) 9884 9885 def _parse_wrapped_options(self) -> list[exp.Expr]: 9886 self._match(TokenType.EQ) 9887 self._match(TokenType.L_PAREN) 9888 9889 opts: list[exp.Expr] = [] 9890 option: exp.Expr | list[exp.Expr] | None 9891 while self._curr and not self._match(TokenType.R_PAREN): 9892 if self._match_text_seq("FORMAT_NAME", "="): 9893 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 9894 option = self._parse_format_name() 9895 else: 9896 option = self._parse_property() 9897 9898 if option is None: 9899 self.raise_error("Unable to parse option") 9900 break 9901 9902 opts.extend(ensure_list(option)) 9903 9904 return opts 9905 9906 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 9907 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 9908 9909 options = [] 9910 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 9911 option = self._parse_var(any_token=True) 9912 prev = self._prev.text.upper() 9913 9914 # Different dialects might separate options and values by white space, "=" and "AS" 9915 self._match(TokenType.EQ) 9916 self._match(TokenType.ALIAS) 9917 9918 param = self.expression(exp.CopyParameter(this=option)) 9919 9920 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 9921 TokenType.L_PAREN, advance=False 9922 ): 9923 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 9924 param.set("expressions", self._parse_wrapped_options()) 9925 elif prev == "FILE_FORMAT": 9926 # T-SQL's external file format case 9927 param.set("expression", self._parse_field()) 9928 elif ( 9929 prev == "FORMAT" 9930 and self._prev.token_type == TokenType.ALIAS 9931 and self._match_texts(("AVRO", "JSON")) 9932 ): 9933 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 9934 param.set("expression", self._parse_field()) 9935 else: 9936 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 9937 9938 options.append(param) 9939 9940 if sep: 9941 self._match(sep) 9942 9943 return options 9944 9945 def _parse_credentials(self) -> exp.Credentials | None: 9946 expr = self.expression(exp.Credentials()) 9947 9948 if self._match_text_seq("STORAGE_INTEGRATION", "="): 9949 expr.set("storage", self._parse_field()) 9950 if self._match_text_seq("CREDENTIALS"): 9951 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 9952 creds = ( 9953 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 9954 ) 9955 expr.set("credentials", creds) 9956 if self._match_text_seq("ENCRYPTION"): 9957 expr.set("encryption", self._parse_wrapped_options()) 9958 if self._match_text_seq("IAM_ROLE"): 9959 expr.set( 9960 "iam_role", 9961 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 9962 ) 9963 if self._match_text_seq("REGION"): 9964 expr.set("region", self._parse_field()) 9965 9966 return expr 9967 9968 def _parse_file_location(self) -> exp.Expr | None: 9969 return self._parse_field() 9970 9971 def _parse_copy(self) -> exp.Copy | exp.Command: 9972 start = self._prev 9973 9974 self._match(TokenType.INTO) 9975 9976 this = ( 9977 self._parse_select(nested=True, parse_subquery_alias=False) 9978 if self._match(TokenType.L_PAREN, advance=False) 9979 else self._parse_table(schema=True) 9980 ) 9981 9982 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 9983 9984 files = self._parse_csv(self._parse_file_location) 9985 if self._match(TokenType.EQ, advance=False): 9986 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 9987 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 9988 # list via `_parse_wrapped(..)` below. 9989 self._advance(-1) 9990 files = [] 9991 9992 credentials = self._parse_credentials() 9993 9994 self._match_text_seq("WITH") 9995 9996 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 9997 9998 # Fallback case 9999 if self._curr: 10000 return self._parse_as_command(start) 10001 10002 return self.expression( 10003 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 10004 ) 10005 10006 def _parse_normalize(self) -> exp.Normalize: 10007 return self.expression( 10008 exp.Normalize( 10009 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 10010 ) 10011 ) 10012 10013 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 10014 args = self._parse_csv(lambda: self._parse_lambda()) 10015 10016 this = seq_get(args, 0) 10017 decimals = seq_get(args, 1) 10018 10019 return expr_type( 10020 this=this, 10021 decimals=decimals, 10022 to=self._parse_var() if self._match_text_seq("TO") else None, 10023 ) 10024 10025 def _parse_star_ops(self) -> exp.Expr | None: 10026 star_token = self._prev 10027 10028 if self._match_text_seq("COLUMNS", "(", advance=False): 10029 this = self._parse_function() 10030 if isinstance(this, exp.Columns): 10031 this.set("unpack", True) 10032 return this 10033 10034 index = self._index 10035 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 10036 if not ilike: 10037 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 10038 self._retreat(index) 10039 10040 return self.expression( 10041 exp.Star( 10042 ilike=ilike, 10043 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 10044 replace=self._parse_star_op("REPLACE"), 10045 rename=self._parse_star_op("RENAME"), 10046 ) 10047 ).update_positions(star_token) 10048 10049 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 10050 privilege_parts = [] 10051 10052 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 10053 # (end of privilege list) or L_PAREN (start of column list) are met 10054 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 10055 privilege_parts.append(self._curr.text.upper()) 10056 self._advance() 10057 10058 if not privilege_parts: 10059 self.raise_error("Expected privilege") 10060 return None 10061 10062 this = exp.var(" ".join(privilege_parts)) 10063 expressions = ( 10064 self._parse_wrapped_csv(self._parse_column) 10065 if self._match(TokenType.L_PAREN, advance=False) 10066 else None 10067 ) 10068 10069 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10070 10071 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10072 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10073 principal = self._parse_id_var() 10074 10075 if not principal: 10076 return None 10077 10078 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10079 10080 def _parse_grant_revoke_common( 10081 self, 10082 ) -> tuple[list | None, str | None, exp.Expr | None]: 10083 privileges = self._parse_csv(self._parse_grant_privilege) 10084 10085 self._match(TokenType.ON) 10086 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10087 10088 # Attempt to parse the securable e.g. MySQL allows names 10089 # such as "foo.*", "*.*" which are not easily parseable yet 10090 securable = self._try_parse(self._parse_table_parts) 10091 10092 return privileges, kind, securable 10093 10094 def _parse_grant(self) -> exp.Grant | exp.Command: 10095 start = self._prev 10096 10097 privileges, kind, securable = self._parse_grant_revoke_common() 10098 10099 if not securable or not self._match_text_seq("TO"): 10100 return self._parse_as_command(start) 10101 10102 principals = self._parse_csv(self._parse_grant_principal) 10103 10104 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10105 10106 if self._curr: 10107 return self._parse_as_command(start) 10108 10109 return self.expression( 10110 exp.Grant( 10111 privileges=privileges, 10112 kind=kind, 10113 securable=securable, 10114 principals=principals, 10115 grant_option=grant_option, 10116 ) 10117 ) 10118 10119 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10120 start = self._prev 10121 10122 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10123 10124 privileges, kind, securable = self._parse_grant_revoke_common() 10125 10126 if not securable or not self._match_text_seq("FROM"): 10127 return self._parse_as_command(start) 10128 10129 principals = self._parse_csv(self._parse_grant_principal) 10130 10131 cascade = None 10132 if self._match_texts(("CASCADE", "RESTRICT")): 10133 cascade = self._prev.text.upper() 10134 10135 if self._curr: 10136 return self._parse_as_command(start) 10137 10138 return self.expression( 10139 exp.Revoke( 10140 privileges=privileges, 10141 kind=kind, 10142 securable=securable, 10143 principals=principals, 10144 grant_option=grant_option, 10145 cascade=cascade, 10146 ) 10147 ) 10148 10149 def _parse_overlay(self) -> exp.Overlay: 10150 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10151 return ( 10152 self._parse_bitwise() 10153 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10154 else None 10155 ) 10156 10157 return self.expression( 10158 exp.Overlay( 10159 this=self._parse_bitwise(), 10160 expression=_parse_overlay_arg("PLACING"), 10161 from_=_parse_overlay_arg("FROM"), 10162 for_=_parse_overlay_arg("FOR"), 10163 ) 10164 ) 10165 10166 def _parse_format_name(self) -> exp.Property: 10167 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10168 # for FILE_FORMAT = <format_name> 10169 return self.expression( 10170 exp.Property( 10171 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10172 ) 10173 ) 10174 10175 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10176 is_distinct = self._match(TokenType.DISTINCT) 10177 if not is_distinct: 10178 self._match(TokenType.ALL) 10179 10180 args = [self._parse_lambda()] 10181 if self._match(TokenType.COMMA): 10182 args.extend(self._parse_function_args()) 10183 10184 target = seq_get(args, distinct_index) 10185 if is_distinct and target: 10186 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10187 10188 return func.from_arg_list(args) 10189 10190 def _identifier_expression( 10191 self, token: Token | None = None, quoted: bool | None = None 10192 ) -> exp.Identifier: 10193 token = token or self._prev 10194 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10195 10196 def _build_pipe_cte( 10197 self, 10198 query: exp.Query, 10199 expressions: list[exp.Expr], 10200 alias_cte: exp.TableAlias | None = None, 10201 ) -> exp.Select: 10202 new_cte: str | exp.TableAlias | None 10203 if alias_cte: 10204 new_cte = alias_cte 10205 else: 10206 self._pipe_cte_counter += 1 10207 new_cte = f"__tmp{self._pipe_cte_counter}" 10208 10209 with_ = query.args.get("with_") 10210 ctes = with_.pop() if with_ else None 10211 10212 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10213 if ctes: 10214 new_select.set("with_", ctes) 10215 10216 return new_select.with_(new_cte, as_=query, copy=False) 10217 10218 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10219 select = self._parse_select(consume_pipe=False) 10220 if not select: 10221 return query 10222 10223 return self._build_pipe_cte( 10224 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10225 ) 10226 10227 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10228 limit = self._parse_limit() 10229 offset = self._parse_offset() 10230 if limit: 10231 curr_limit = query.args.get("limit", limit) 10232 if curr_limit.expression.to_py() >= limit.expression.to_py(): 10233 query.limit(limit, copy=False) 10234 if offset: 10235 curr_offset = query.args.get("offset") 10236 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10237 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10238 10239 return query 10240 10241 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10242 this = self._parse_disjunction() 10243 if self._match_text_seq("GROUP", "AND", advance=False): 10244 return this 10245 10246 this = self._parse_alias(this) 10247 10248 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10249 return self._parse_ordered(lambda: this) 10250 10251 return this 10252 10253 def _parse_pipe_syntax_aggregate_group_order_by( 10254 self, query: exp.Select, group_by_exists: bool = True 10255 ) -> exp.Select: 10256 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10257 aggregates_or_groups, orders = [], [] 10258 for element in expr: 10259 if isinstance(element, exp.Ordered): 10260 this = element.this 10261 if isinstance(this, exp.Alias): 10262 element.set("this", this.args["alias"]) 10263 orders.append(element) 10264 else: 10265 this = element 10266 aggregates_or_groups.append(this) 10267 10268 if group_by_exists: 10269 query.select( 10270 *aggregates_or_groups, *query.expressions, append=False, copy=False 10271 ).group_by( 10272 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10273 copy=False, 10274 ) 10275 else: 10276 query.select(*aggregates_or_groups, append=False, copy=False) 10277 10278 if orders: 10279 return query.order_by(*orders, append=False, copy=False) 10280 10281 return query 10282 10283 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10284 self._match_text_seq("AGGREGATE") 10285 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10286 10287 if self._match(TokenType.GROUP_BY) or ( 10288 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10289 ): 10290 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10291 10292 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10293 10294 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10295 first_setop = self.parse_set_operation(this=query) 10296 if not first_setop: 10297 return None 10298 10299 def _parse_and_unwrap_query() -> exp.Expr | None: 10300 expr = self._parse_paren() 10301 return expr.assert_is(exp.Subquery).unnest() if expr else None 10302 10303 first_setop.this.pop() 10304 10305 setops = [ 10306 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10307 *self._parse_csv(_parse_and_unwrap_query), 10308 ] 10309 10310 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10311 with_ = query.args.get("with_") 10312 ctes = with_.pop() if with_ else None 10313 10314 if isinstance(first_setop, exp.Union): 10315 query = query.union(*setops, copy=False, **first_setop.args) 10316 elif isinstance(first_setop, exp.Except): 10317 query = query.except_(*setops, copy=False, **first_setop.args) 10318 else: 10319 query = query.intersect(*setops, copy=False, **first_setop.args) 10320 10321 query.set("with_", ctes) 10322 10323 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10324 10325 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10326 join = self._parse_join() 10327 if not join: 10328 return None 10329 10330 if isinstance(query, exp.Select): 10331 return query.join(join, copy=False) 10332 10333 return query 10334 10335 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10336 pivots = self._parse_pivots() 10337 if not pivots: 10338 return query 10339 10340 from_ = query.args.get("from_") 10341 if from_: 10342 from_.this.set("pivots", pivots) 10343 10344 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10345 10346 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10347 self._match_text_seq("EXTEND") 10348 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10349 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10350 10351 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10352 sample = self._parse_table_sample() 10353 10354 with_ = query.args.get("with_") 10355 if with_: 10356 with_.expressions[-1].this.set("sample", sample) 10357 else: 10358 query.set("sample", sample) 10359 10360 return query 10361 10362 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10363 if isinstance(query, exp.Subquery): 10364 query = exp.select("*").from_(query, copy=False) 10365 10366 if not query.args.get("from_"): 10367 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10368 10369 while self._match(TokenType.PIPE_GT): 10370 start_index = self._index 10371 start_text = self._curr.text.upper() 10372 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10373 if not parser: 10374 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10375 # keywords, making it tricky to disambiguate them without lookahead. The approach 10376 # here is to try and parse a set operation and if that fails, then try to parse a 10377 # join operator. If that fails as well, then the operator is not supported. 10378 parsed_query = self._parse_pipe_syntax_set_operator(query) 10379 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10380 if not parsed_query: 10381 self._retreat(start_index) 10382 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10383 break 10384 query = parsed_query 10385 else: 10386 query = parser(self, query) 10387 10388 return query 10389 10390 def _parse_declareitem(self) -> exp.DeclareItem | None: 10391 self._match_texts(("VAR", "VARIABLE")) 10392 10393 vars = self._parse_csv(self._parse_id_var) 10394 if not vars: 10395 return None 10396 10397 self._match(TokenType.ALIAS) 10398 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10399 default = ( 10400 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10401 ) and self._parse_bitwise() 10402 10403 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10404 10405 def _parse_declare(self) -> exp.Declare | exp.Command: 10406 start = self._prev 10407 replace = self._match_text_seq("OR", "REPLACE") 10408 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10409 10410 if not expressions or self._curr: 10411 return self._parse_as_command(start) 10412 10413 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10414 10415 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10416 exp_class = exp.Cast if strict else exp.TryCast 10417 10418 if exp_class == exp.TryCast: 10419 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10420 10421 return self.expression(exp_class(**kwargs)) 10422 10423 def _parse_json_value(self) -> exp.JSONValue: 10424 this = self._parse_bitwise() 10425 self._match(TokenType.COMMA) 10426 path = self._parse_bitwise() 10427 10428 returning = self._match(TokenType.RETURNING) and self._parse_type() 10429 10430 return self.expression( 10431 exp.JSONValue( 10432 this=this, 10433 path=self.dialect.to_json_path(path), 10434 returning=returning, 10435 on_condition=self._parse_on_condition(), 10436 ) 10437 ) 10438 10439 def _parse_group_concat(self) -> exp.Expr | None: 10440 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10441 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10442 concat_exprs = [ 10443 self.expression( 10444 exp.Concat( 10445 expressions=node.expressions, 10446 safe=True, 10447 coalesce=self.dialect.CONCAT_COALESCE, 10448 ) 10449 ) 10450 ] 10451 node.set("expressions", concat_exprs) 10452 return node 10453 if len(exprs) == 1: 10454 return exprs[0] 10455 return self.expression( 10456 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10457 ) 10458 10459 args = self._parse_csv(self._parse_lambda) 10460 10461 if args: 10462 order = args[-1] if isinstance(args[-1], exp.Order) else None 10463 10464 if order: 10465 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10466 # remove 'expr' from exp.Order and add it back to args 10467 args[-1] = order.this 10468 order.set("this", concat_exprs(order.this, args)) 10469 10470 this = order or concat_exprs(args[0], args) 10471 else: 10472 this = None 10473 10474 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10475 10476 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10477 10478 def _parse_initcap(self) -> exp.Initcap: 10479 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10480 10481 # attach dialect's default delimiters 10482 if expr.args.get("expression") is None: 10483 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10484 10485 return expr 10486 10487 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10488 while True: 10489 if not self._match(TokenType.L_PAREN): 10490 break 10491 10492 op = "" 10493 while self._curr and not self._match(TokenType.R_PAREN): 10494 op += self._curr.text 10495 self._advance() 10496 10497 comments = self._prev_comments 10498 this = self.expression( 10499 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10500 comments=comments, 10501 ) 10502 10503 if not self._match(TokenType.OPERATOR): 10504 break 10505 10506 return this
Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.
Arguments:
- error_level: The desired error level. Default: ErrorLevel.IMMEDIATE
- error_message_context: The amount of context to capture from a query string when displaying the error message (in number of characters). Default: 100
- max_errors: Maximum number of error messages to include in a raised ParseError. This is only relevant if error_level is ErrorLevel.RAISE. Default: 3
- max_nodes: Maximum number of AST nodes to prevent memory exhaustion. Set to -1 (default) to disable the check.
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
5916 def parse_set_operation( 5917 self, this: exp.Expr | None, consume_pipe: bool = False 5918 ) -> exp.Expr | None: 5919 start = self._index 5920 _, side_token, kind_token = self._parse_join_parts() 5921 5922 side = side_token.text if side_token else None 5923 kind = kind_token.text if kind_token else None 5924 5925 if not self._match_set(self.SET_OPERATIONS): 5926 self._retreat(start) 5927 return None 5928 5929 token_type = self._prev.token_type 5930 5931 if token_type == TokenType.UNION: 5932 operation: type[exp.SetOperation] = exp.Union 5933 elif token_type == TokenType.EXCEPT: 5934 operation = exp.Except 5935 else: 5936 operation = exp.Intersect 5937 5938 comments = self._prev.comments 5939 5940 if self._match(TokenType.DISTINCT): 5941 distinct: bool | None = True 5942 elif self._match(TokenType.ALL): 5943 distinct = False 5944 else: 5945 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5946 if distinct is None: 5947 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5948 5949 by_name = ( 5950 self._match_text_seq("BY", "NAME") 5951 or self._match_text_seq("STRICT", "CORRESPONDING") 5952 or None 5953 ) 5954 if self._match_text_seq("CORRESPONDING"): 5955 by_name = True 5956 if not side and not kind: 5957 kind = "INNER" 5958 5959 on_column_list = None 5960 if by_name and self._match_texts(("ON", "BY")): 5961 on_column_list = self._parse_wrapped_csv(self._parse_column) 5962 5963 expression = self._parse_select( 5964 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5965 ) 5966 5967 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5968 # in _parse_cte and so that alias pushdown can reach into set operation branches 5969 if isinstance(this, exp.Values): 5970 this = self._values_to_select(this) 5971 if isinstance(expression, exp.Values): 5972 expression = self._values_to_select(expression) 5973 5974 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 5975 subquery = this.this 5976 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 5977 subquery.add_comments(this.pop_comments()) 5978 this = subquery 5979 5980 return self.expression( 5981 operation( 5982 this=this, 5983 distinct=distinct, 5984 by_name=by_name, 5985 expression=expression, 5986 side=side, 5987 kind=kind, 5988 on=on_column_list, 5989 ), 5990 comments=comments, 5991 )