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( 1637 exp.ParseJSON(this=this, is_literal=True) 1638 ), 1639 } 1640 1641 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1642 1643 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1644 1645 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1646 1647 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1648 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1649 "ISOLATION": ( 1650 ("LEVEL", "REPEATABLE", "READ"), 1651 ("LEVEL", "READ", "COMMITTED"), 1652 ("LEVEL", "READ", "UNCOMITTED"), 1653 ("LEVEL", "SERIALIZABLE"), 1654 ), 1655 "READ": ("WRITE", "ONLY"), 1656 } 1657 1658 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1659 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1660 "DO": ("NOTHING", "UPDATE"), 1661 } 1662 1663 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1664 "INSTEAD": (("OF",),), 1665 "BEFORE": tuple(), 1666 "AFTER": tuple(), 1667 } 1668 1669 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1670 "NOT": (("DEFERRABLE",),), 1671 "DEFERRABLE": tuple(), 1672 } 1673 1674 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1675 "SCALE": ("EXTEND", "NOEXTEND"), 1676 "SHARD": ("EXTEND", "NOEXTEND"), 1677 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1678 **dict.fromkeys( 1679 ( 1680 "SESSION", 1681 "GLOBAL", 1682 "KEEP", 1683 "NOKEEP", 1684 "ORDER", 1685 "NOORDER", 1686 "NOCACHE", 1687 "CYCLE", 1688 "NOCYCLE", 1689 "NOMINVALUE", 1690 "NOMAXVALUE", 1691 "NOSCALE", 1692 "NOSHARD", 1693 ), 1694 tuple(), 1695 ), 1696 } 1697 1698 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1699 1700 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1701 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1702 ) 1703 1704 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1705 1706 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1707 "TYPE": ("EVOLUTION",), 1708 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1709 } 1710 1711 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1712 1713 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1714 ("CALLER", "SELF", "OWNER"), tuple() 1715 ) 1716 1717 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1718 "NOT": ("ENFORCED",), 1719 "MATCH": ( 1720 "FULL", 1721 "PARTIAL", 1722 "SIMPLE", 1723 ), 1724 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1725 "USING": ( 1726 "BTREE", 1727 "HASH", 1728 ), 1729 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1730 } 1731 1732 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1733 "NO": ("OTHERS",), 1734 "CURRENT": ("ROW",), 1735 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1736 } 1737 1738 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1739 1740 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1741 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1742 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1743 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1744 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1745 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1746 ("FOR", "VERSION"): "VERSION", 1747 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1748 ("VERSION", "AS", "OF"): "VERSION", 1749 } 1750 1751 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1752 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1753 1754 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1755 1756 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1757 1758 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1759 1760 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1761 1762 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1763 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1764 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1765 1766 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1767 1768 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1769 1770 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1771 TokenType.CONSTRAINT, 1772 TokenType.FOREIGN_KEY, 1773 TokenType.INDEX, 1774 TokenType.KEY, 1775 TokenType.PRIMARY_KEY, 1776 TokenType.UNIQUE, 1777 } 1778 1779 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1780 1781 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1782 1783 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1784 1785 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1786 "FILE_FORMAT", 1787 "COPY_OPTIONS", 1788 "FORMAT_OPTIONS", 1789 "CREDENTIAL", 1790 } 1791 1792 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1793 1794 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1795 1796 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1797 1798 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1799 1800 # The style options for the DESCRIBE statement 1801 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1802 1803 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1804 1805 # The style options for the ANALYZE statement 1806 ANALYZE_STYLES: t.ClassVar = { 1807 "BUFFER_USAGE_LIMIT", 1808 "FULL", 1809 "LOCAL", 1810 "NO_WRITE_TO_BINLOG", 1811 "SAMPLE", 1812 "SKIP_LOCKED", 1813 "VERBOSE", 1814 } 1815 1816 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1817 "ALL": lambda self: self._parse_analyze_columns(), 1818 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1819 "DELETE": lambda self: self._parse_analyze_delete(), 1820 "DROP": lambda self: self._parse_analyze_histogram(), 1821 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1822 "LIST": lambda self: self._parse_analyze_list(), 1823 "PREDICATE": lambda self: self._parse_analyze_columns(), 1824 "UPDATE": lambda self: self._parse_analyze_histogram(), 1825 "VALIDATE": lambda self: self._parse_analyze_validate(), 1826 } 1827 1828 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1829 1830 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1831 1832 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1833 1834 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1835 1836 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1837 1838 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1839 1840 STRICT_CAST: t.ClassVar = True 1841 1842 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1843 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1844 # Whether an UNPIVOT outputs its value column(s) before the name column 1845 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1846 # Controls when an aggregation's name is included in a pivoted column's name: 1847 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1848 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1849 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1850 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1851 1852 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1853 1854 # Whether the table sample clause expects CSV syntax 1855 TABLESAMPLE_CSV: t.ClassVar = False 1856 1857 # The default method used for table sampling 1858 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1859 1860 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1861 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1862 1863 # Whether the TRIM function expects the characters to trim as its first argument 1864 TRIM_PATTERN_FIRST: t.ClassVar = False 1865 1866 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1867 STRING_ALIASES: t.ClassVar = False 1868 1869 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1870 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1871 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"} 1872 1873 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1874 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1875 1876 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1877 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1878 1879 # Whether the `:` operator is used to extract a value from a VARIANT column 1880 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1881 1882 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1883 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1884 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1885 1886 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1887 # If this is True and '(' is not found, the keyword will be treated as an identifier 1888 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1889 1890 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1891 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1892 1893 # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery) 1894 SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False 1895 1896 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1897 INTERVAL_SPANS: t.ClassVar = True 1898 1899 # Whether a PARTITION clause can follow a table reference 1900 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1901 1902 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1903 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1904 1905 # Whether the 'AS' keyword is optional in the CTE definition syntax 1906 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1907 1908 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1909 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1910 1911 # Whether Alter statements are allowed to contain Partition specifications 1912 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1913 1914 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1915 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1916 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1917 # as BigQuery, where all joins have the same precedence. 1918 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1919 1920 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1921 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1922 1923 # Whether map literals support arbitrary expressions as keys. 1924 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1925 # When False, keys are typically restricted to identifiers. 1926 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1927 1928 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1929 # is true for Snowflake but not for BigQuery which can also process strings 1930 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1931 1932 # Dialects like Databricks support JOINS without join criteria 1933 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 1934 ADD_JOIN_ON_TRUE: t.ClassVar = False 1935 1936 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 1937 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 1938 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 1939 1940 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 1941 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 1942 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 1943 1944 # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause, 1945 # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake) 1946 SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False 1947 1948 # Type names that denote a different type when they're quoted, so quoting has to be 1949 # preserved instead of resolving them into the built-in type of the same name. These 1950 # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR 1951 QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set() 1952 1953 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 1954 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 1955 1956 def __init__( 1957 self, 1958 error_level: ErrorLevel | None = None, 1959 error_message_context: int = 100, 1960 max_errors: int = 3, 1961 max_nodes: int = -1, 1962 dialect: DialectType = None, 1963 ): 1964 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 1965 self.error_message_context: int = error_message_context 1966 self.max_errors: int = max_errors 1967 self.max_nodes: int = max_nodes 1968 self.dialect: t.Any = _resolve_dialect(dialect) 1969 self.sql: str = "" 1970 self.errors: list[ParseError] = [] 1971 self._tokens: list[Token] = [] 1972 self._tokens_size: i64 = 0 1973 self._index: i64 = 0 1974 self._curr: Token = SENTINEL_NONE 1975 self._next: Token = SENTINEL_NONE 1976 self._prev: Token = SENTINEL_NONE 1977 self._prev_comments: list[str] = [] 1978 self._pipe_cte_counter: int = 0 1979 self._chunks: list[list[Token]] = [] 1980 self._chunk_index: i64 = 0 1981 self._node_count: int = 0 1982 1983 def reset(self) -> None: 1984 self.sql = "" 1985 self.errors = [] 1986 self._tokens = [] 1987 self._tokens_size = 0 1988 self._index = 0 1989 self._curr = SENTINEL_NONE 1990 self._next = SENTINEL_NONE 1991 self._prev = SENTINEL_NONE 1992 self._prev_comments = [] 1993 self._pipe_cte_counter = 0 1994 self._chunks = [] 1995 self._chunk_index = 0 1996 self._node_count = 0 1997 1998 def _advance(self, times: i64 = 1) -> None: 1999 index = self._index + times 2000 self._index = index 2001 tokens = self._tokens 2002 size = self._tokens_size 2003 self._curr = tokens[index] if index < size else SENTINEL_NONE 2004 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 2005 2006 if index > 0: 2007 prev = tokens[index - 1] 2008 self._prev = prev 2009 self._prev_comments = prev.comments 2010 else: 2011 self._prev = SENTINEL_NONE 2012 self._prev_comments = [] 2013 2014 def _advance_chunk(self) -> None: 2015 self._index = -1 2016 self._tokens = self._chunks[self._chunk_index] 2017 self._tokens_size = i64(len(self._tokens)) 2018 self._chunk_index += 1 2019 self._advance() 2020 2021 def _retreat(self, index: i64) -> None: 2022 if index != self._index: 2023 self._advance(index - self._index) 2024 2025 def _add_comments(self, expression: exp.Expr | None) -> None: 2026 if expression and self._prev_comments: 2027 expression.add_comments(self._prev_comments) 2028 self._prev_comments = [] 2029 2030 def _match( 2031 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2032 ) -> bool: 2033 if self._curr.token_type == token_type: 2034 if advance: 2035 self._advance() 2036 self._add_comments(expression) 2037 return True 2038 return False 2039 2040 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2041 if self._curr.token_type in types: 2042 if advance: 2043 self._advance() 2044 return True 2045 return False 2046 2047 def _match_pair( 2048 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2049 ) -> bool: 2050 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2051 if advance: 2052 self._advance(2) 2053 return True 2054 return False 2055 2056 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2057 if ( 2058 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2059 and self._curr.text.upper() in texts 2060 ): 2061 if advance: 2062 self._advance() 2063 return True 2064 return False 2065 2066 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2067 index = self._index 2068 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2069 for text in texts: 2070 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2071 self._advance() 2072 else: 2073 self._retreat(index) 2074 return False 2075 2076 if not advance: 2077 self._retreat(index) 2078 2079 return True 2080 2081 def _is_connected(self) -> bool: 2082 prev = self._prev 2083 curr = self._curr 2084 return bool(prev and curr and prev.end + 1 == curr.start) 2085 2086 def _find_sql(self, start: Token, end: Token) -> str: 2087 return self.sql[start.start : end.end + 1] 2088 2089 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2090 token = token or self._curr or self._prev or Token.string("") 2091 formatted_sql, start_context, highlight, end_context = highlight_sql( 2092 sql=self.sql, 2093 positions=[(token.start, token.end)], 2094 context_length=self.error_message_context, 2095 ) 2096 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2097 2098 error = ParseError.new( 2099 formatted_message, 2100 description=message, 2101 line=token.line, 2102 col=token.col, 2103 start_context=start_context, 2104 highlight=highlight, 2105 end_context=end_context, 2106 ) 2107 2108 if self.error_level == ErrorLevel.IMMEDIATE: 2109 raise error 2110 2111 self.errors.append(error) 2112 2113 def validate_expression(self, expression: E, args: list | None = None) -> E: 2114 if self.max_nodes > -1: 2115 self._node_count += 1 2116 if self._node_count > self.max_nodes: 2117 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2118 if self.error_level != ErrorLevel.IGNORE: 2119 for error_message in expression.error_messages(args): 2120 self.raise_error(error_message) 2121 return expression 2122 2123 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2124 index = self._index 2125 error_level = self.error_level 2126 this: T | None = None 2127 2128 self.error_level = ErrorLevel.IMMEDIATE 2129 try: 2130 this = parse_method() 2131 except ParseError: 2132 this = None 2133 finally: 2134 if not this or retreat: 2135 self._retreat(index) 2136 self.error_level = error_level 2137 2138 return this 2139 2140 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2141 """ 2142 Parses a list of tokens and returns a list of syntax trees, one tree 2143 per parsed SQL statement. 2144 2145 Args: 2146 raw_tokens: The list of tokens. 2147 sql: The original SQL string. 2148 2149 Returns: 2150 The list of the produced syntax trees. 2151 """ 2152 return self._parse( 2153 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2154 ) 2155 2156 def parse_into( 2157 self, 2158 expression_types: exp.IntoType, 2159 raw_tokens: list[Token], 2160 sql: str | None = None, 2161 ) -> list[exp.Expr | None]: 2162 """ 2163 Parses a list of tokens into a given Expr type. If a collection of Expr 2164 types is given instead, this method will try to parse the token list into each one 2165 of them, stopping at the first for which the parsing succeeds. 2166 2167 Args: 2168 expression_types: The expression type(s) to try and parse the token list into. 2169 raw_tokens: The list of tokens. 2170 sql: The original SQL string, used to produce helpful debug messages. 2171 2172 Returns: 2173 The target Expr. 2174 """ 2175 errors = [] 2176 for expression_type in ensure_list(expression_types): 2177 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2178 if not parser: 2179 raise TypeError(f"No parser registered for {expression_type}") 2180 2181 try: 2182 return self._parse(parser, raw_tokens, sql) 2183 except ParseError as e: 2184 e.errors[0]["into_expression"] = expression_type 2185 errors.append(e) 2186 2187 raise ParseError( 2188 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2189 errors=merge_errors(errors), 2190 ) from errors[-1] 2191 2192 def check_errors(self) -> None: 2193 """Logs or raises any found errors, depending on the chosen error level setting.""" 2194 if self.error_level == ErrorLevel.WARN: 2195 for error in self.errors: 2196 logger.error(str(error)) 2197 elif self.error_level == ErrorLevel.RAISE and self.errors: 2198 raise ParseError( 2199 concat_messages(self.errors, self.max_errors), 2200 errors=merge_errors(self.errors), 2201 ) 2202 2203 def expression( 2204 self, 2205 instance: E, 2206 token: Token | None = None, 2207 comments: list[str] | None = None, 2208 ) -> E: 2209 if token: 2210 instance.update_positions(token) 2211 instance.add_comments(comments) if comments else self._add_comments(instance) 2212 if not instance.is_primitive: 2213 instance = self.validate_expression(instance) 2214 return instance 2215 2216 def _parse_batch_statements( 2217 self, 2218 parse_method: t.Callable[[Parser], exp.Expr | None], 2219 sep_first_statement: bool = True, 2220 ) -> list[exp.Expr | None]: 2221 expressions = [] 2222 2223 # Chunkification binds if/while statements with the first statement of the body 2224 if sep_first_statement: 2225 self._match(TokenType.BEGIN) 2226 expressions.append(parse_method(self)) 2227 2228 chunks_length = len(self._chunks) 2229 while self._chunk_index < chunks_length: 2230 self._advance_chunk() 2231 2232 if self._match(TokenType.ELSE, advance=False): 2233 return expressions 2234 2235 if expressions and not self._next and self._match(TokenType.END): 2236 expressions.append(exp.EndStatement()) 2237 continue 2238 2239 expressions.append(parse_method(self)) 2240 2241 if self._index < self._tokens_size: 2242 self.raise_error("Invalid expression / Unexpected token") 2243 2244 self.check_errors() 2245 2246 return expressions 2247 2248 def _parse( 2249 self, 2250 parse_method: t.Callable[[Parser], exp.Expr | None], 2251 raw_tokens: list[Token], 2252 sql: str | None = None, 2253 ) -> list[exp.Expr | None]: 2254 self.reset() 2255 self.sql = sql or "" 2256 2257 total = len(raw_tokens) 2258 chunks: list[list[Token]] = [[]] 2259 2260 for i, token in enumerate(raw_tokens): 2261 if token.token_type == TokenType.SEMICOLON: 2262 if token.comments: 2263 chunks.append([token]) 2264 2265 if i < total - 1: 2266 chunks.append([]) 2267 else: 2268 chunks[-1].append(token) 2269 2270 self._chunks = chunks 2271 2272 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2273 2274 def _warn_unsupported(self) -> None: 2275 if self._tokens_size <= 1: 2276 return 2277 2278 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2279 # interested in emitting a warning for the one being currently processed. 2280 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2281 2282 logger.warning( 2283 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2284 ) 2285 2286 def _parse_command(self) -> exp.Command: 2287 self._warn_unsupported() 2288 comments = self._prev_comments 2289 return self.expression( 2290 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2291 comments=comments, 2292 ) 2293 2294 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2295 start = self._prev 2296 exists = self._parse_exists() if allow_exists else None 2297 2298 self._match(TokenType.ON) 2299 2300 materialized = self._match_text_seq("MATERIALIZED") 2301 kind = self._match_set(self.CREATABLES) and self._prev 2302 if not kind: 2303 return self._parse_as_command(start) 2304 2305 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2306 this = self._parse_user_defined_function(kind=kind.token_type) 2307 elif kind.token_type == TokenType.TABLE: 2308 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2309 elif kind.token_type == TokenType.COLUMN: 2310 this = self._parse_column() 2311 else: 2312 this = self._parse_table_parts(schema=True) 2313 2314 self._match(TokenType.IS) 2315 2316 return self.expression( 2317 exp.Comment( 2318 this=this, 2319 kind=kind.text, 2320 expression=self._parse_string(), 2321 exists=exists, 2322 materialized=materialized, 2323 ) 2324 ) 2325 2326 def _parse_to_table( 2327 self, 2328 ) -> exp.ToTableProperty: 2329 table = self._parse_table_parts(schema=True) 2330 return self.expression(exp.ToTableProperty(this=table)) 2331 2332 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2333 def _parse_ttl(self) -> exp.Expr: 2334 def _parse_ttl_action() -> exp.Expr | None: 2335 this = self._parse_bitwise() 2336 2337 if self._match_text_seq("DELETE"): 2338 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2339 if self._match_text_seq("RECOMPRESS"): 2340 return self.expression( 2341 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2342 ) 2343 if self._match_text_seq("TO", "DISK"): 2344 return self.expression( 2345 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2346 ) 2347 if self._match_text_seq("TO", "VOLUME"): 2348 return self.expression( 2349 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2350 ) 2351 2352 return this 2353 2354 expressions = self._parse_csv(_parse_ttl_action) 2355 where = self._parse_where() 2356 group = self._parse_group() 2357 2358 aggregates = None 2359 if group and self._match(TokenType.SET): 2360 aggregates = self._parse_csv(self._parse_set_item) 2361 2362 return self.expression( 2363 exp.MergeTreeTTL( 2364 expressions=expressions, where=where, group=group, aggregates=aggregates 2365 ) 2366 ) 2367 2368 def _parse_condition(self) -> exp.Expr | None: 2369 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2370 2371 def _parse_block(self) -> exp.Block: 2372 return self.expression( 2373 exp.Block( 2374 expressions=self._parse_batch_statements( 2375 parse_method=lambda self: self._parse_statement() 2376 ) 2377 ) 2378 ) 2379 2380 def _parse_whileblock(self) -> exp.WhileBlock: 2381 return self.expression( 2382 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2383 ) 2384 2385 def _parse_statement(self) -> exp.Expr | None: 2386 if not self._curr: 2387 return None 2388 2389 if self._match_set(self.STATEMENT_PARSERS): 2390 comments = self._prev_comments 2391 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2392 stmt.add_comments(comments, prepend=True) 2393 return stmt 2394 2395 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2396 return self._parse_command() 2397 2398 if self._match_text_seq("WHILE"): 2399 return self._parse_whileblock() 2400 2401 expression = self._parse_expression() 2402 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2403 2404 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2405 expression = self._parse_pipe_syntax_query(expression) 2406 2407 return self._parse_query_modifiers(expression) 2408 2409 def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command: 2410 start = self._prev 2411 temporary = self._match(TokenType.TEMPORARY) 2412 materialized = self._match_text_seq("MATERIALIZED") 2413 iceberg = self._match_text_seq("ICEBERG") 2414 2415 kind = self._match_set(self.CREATABLES) and self._prev.text.upper() 2416 if not kind or (iceberg and kind and kind != "TABLE"): 2417 return self._parse_as_command(start) 2418 2419 concurrently = self._match_text_seq("CONCURRENTLY") 2420 if_exists = exists or self._parse_exists() 2421 2422 tables: exp.Expr | list[exp.Expr] | None 2423 if kind == "COLUMN": 2424 tables = self._parse_column() 2425 elif kind in ("TABLE", "VIEW"): 2426 tables = self._parse_csv(lambda: self._parse_table_parts(schema=True)) 2427 else: 2428 tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2429 2430 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 2431 2432 if self._match(TokenType.L_PAREN, advance=False): 2433 expressions = self._parse_wrapped_csv(self._parse_types) 2434 else: 2435 expressions = None 2436 2437 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2438 2439 return self.expression( 2440 exp.Drop( 2441 exists=if_exists, 2442 tables=ensure_list(tables), 2443 expressions=expressions, 2444 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2445 temporary=temporary, 2446 materialized=materialized, 2447 cascade=cascade_or_restrict == "CASCADE", 2448 restrict=cascade_or_restrict == "RESTRICT", 2449 constraints=self._match_text_seq("CONSTRAINTS"), 2450 purge=self._match_text_seq("PURGE"), 2451 cluster=cluster, 2452 concurrently=concurrently, 2453 sync=self._match_text_seq("SYNC"), 2454 iceberg=iceberg, 2455 force=self._match_text_seq("FORCE"), 2456 ) 2457 ) 2458 2459 def _parse_exists(self, not_: bool = False) -> bool | None: 2460 return ( 2461 self._match_text_seq("IF") 2462 and (not not_ or self._match(TokenType.NOT)) 2463 and self._match(TokenType.EXISTS) 2464 ) 2465 2466 def _parse_create(self) -> exp.Create | exp.Command: 2467 # Note: this can't be None because we've matched a statement parser 2468 start = self._prev 2469 2470 replace = ( 2471 start.token_type == TokenType.REPLACE 2472 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2473 or self._match_pair(TokenType.OR, TokenType.ALTER) 2474 ) 2475 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2476 2477 unique = self._match(TokenType.UNIQUE) 2478 2479 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2480 clustered = True 2481 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2482 "COLUMNSTORE" 2483 ): 2484 clustered = False 2485 else: 2486 clustered = None 2487 2488 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2489 self._advance() 2490 2491 properties = None 2492 create_token = self._match_set(self.CREATABLES) and self._prev 2493 2494 if not create_token: 2495 # exp.Properties.Location.POST_CREATE 2496 properties = self._parse_properties() 2497 create_token = self._match_set(self.CREATABLES) and self._prev 2498 2499 if not properties or not create_token: 2500 return self._parse_as_command(start) 2501 2502 create_token_type = t.cast(Token, create_token).token_type 2503 2504 concurrently = self._match_text_seq("CONCURRENTLY") 2505 exists = self._parse_exists(not_=True) 2506 this = None 2507 expression: exp.Expr | None = None 2508 indexes = None 2509 no_schema_binding = None 2510 begin = None 2511 clone = None 2512 2513 def extend_props(temp_props: exp.Properties | None) -> None: 2514 nonlocal properties 2515 if properties and temp_props: 2516 properties.expressions.extend(temp_props.expressions) 2517 elif temp_props: 2518 properties = temp_props 2519 2520 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2521 this = self._parse_user_defined_function(kind=create_token_type) 2522 2523 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2524 extend_props(self._parse_properties()) 2525 2526 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2527 2528 if ( 2529 not expression 2530 and create_token_type == TokenType.FUNCTION 2531 and isinstance(this, exp.UserDefinedFunction) 2532 and this.args.get("wrapped") 2533 ): 2534 pre_table_index = self._index 2535 is_table = self._match(TokenType.TABLE) 2536 2537 expression = self._parse_expression() 2538 overload_mode = bool( 2539 expression 2540 and self._curr.token_type == TokenType.COMMA 2541 and self._next.token_type == TokenType.L_PAREN 2542 ) 2543 if not overload_mode: 2544 self._retreat(pre_table_index) 2545 is_table = False 2546 expression = None 2547 else: 2548 is_table = False 2549 overload_mode = False 2550 2551 extend_props(self._parse_function_properties()) 2552 2553 if not expression: 2554 if self._match(TokenType.COMMAND): 2555 expression = self._parse_as_command(self._prev) 2556 else: 2557 begin = self._match(TokenType.BEGIN) 2558 return_ = self._match_text_seq("RETURN") 2559 2560 if self._match(TokenType.STRING, advance=False): 2561 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2562 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2563 expression = self._parse_string() 2564 extend_props(self._parse_properties()) 2565 else: 2566 expression = ( 2567 self._parse_user_defined_function_expression() 2568 if create_token_type == TokenType.FUNCTION 2569 else self._parse_block() 2570 ) 2571 2572 if return_: 2573 expression = self.expression(exp.Return(this=expression)) 2574 2575 if overload_mode and expression: 2576 expression = self._parse_macro_overloads( 2577 t.cast(exp.UserDefinedFunction, this), expression, is_table 2578 ) 2579 elif create_token_type == TokenType.INDEX: 2580 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2581 if not self._match(TokenType.ON): 2582 index = self._parse_id_var() 2583 anonymous = False 2584 else: 2585 index = None 2586 anonymous = True 2587 2588 this = self._parse_index(index=index, anonymous=anonymous) 2589 elif ( 2590 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2591 ) or create_token_type == TokenType.TRIGGER: 2592 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2593 create_token = self._prev 2594 2595 trigger_name = self._parse_id_var() 2596 if not trigger_name: 2597 return self._parse_as_command(start) 2598 2599 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2600 timing = timing_var.this if timing_var else None 2601 if not timing: 2602 return self._parse_as_command(start) 2603 2604 events = self._parse_trigger_events() 2605 if not self._match(TokenType.ON): 2606 self.raise_error("Expected ON in trigger definition") 2607 2608 table = self._parse_table_parts() 2609 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2610 deferrable, initially = self._parse_trigger_deferrable() 2611 referencing = self._parse_trigger_referencing() 2612 for_each = self._parse_trigger_for_each() 2613 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2614 self._parse_disjunction, optional=True 2615 ) 2616 execute = self._parse_trigger_execute() 2617 2618 if execute is None: 2619 return self._parse_as_command(start) 2620 2621 trigger_props = self.expression( 2622 exp.TriggerProperties( 2623 table=table, 2624 timing=timing, 2625 events=events, 2626 execute=execute, 2627 constraint=is_constraint, 2628 referenced_table=referenced_table, 2629 deferrable=deferrable, 2630 initially=initially, 2631 referencing=referencing, 2632 for_each=for_each, 2633 when=when, 2634 ) 2635 ) 2636 2637 this = trigger_name 2638 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2639 elif create_token_type == TokenType.TYPE: 2640 this = self._parse_table_parts(schema=True) 2641 if not this or not self._match(TokenType.ALIAS): 2642 return self._parse_as_command(start) 2643 2644 if self._match(TokenType.ENUM): 2645 expression = exp.DataType( 2646 this=exp.DType.ENUM, 2647 expressions=self._parse_wrapped_csv(self._parse_string), 2648 ) 2649 elif self._match(TokenType.L_PAREN, advance=False): 2650 expression = self._parse_schema() 2651 else: 2652 return self._parse_as_command(start) 2653 elif create_token_type in self.DB_CREATABLES: 2654 table_parts = self._parse_table_parts( 2655 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2656 ) 2657 2658 # exp.Properties.Location.POST_NAME 2659 self._match(TokenType.COMMA) 2660 extend_props(self._parse_properties(before=True)) 2661 2662 this = self._parse_schema(this=table_parts) 2663 2664 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2665 extend_props(self._parse_properties()) 2666 2667 has_alias = self._match(TokenType.ALIAS) 2668 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2669 # exp.Properties.Location.POST_ALIAS 2670 extend_props(self._parse_properties()) 2671 2672 if create_token_type == TokenType.SEQUENCE: 2673 expression = self._parse_types() 2674 props = self._parse_properties() 2675 if props: 2676 sequence_props = exp.SequenceProperties() 2677 options = [] 2678 for prop in props: 2679 if isinstance(prop, exp.SequenceProperties): 2680 for arg, value in prop.args.items(): 2681 if arg == "options": 2682 options.extend(value) 2683 else: 2684 sequence_props.set(arg, value) 2685 prop.pop() 2686 2687 if options: 2688 sequence_props.set("options", options) 2689 2690 props.append("expressions", sequence_props) 2691 extend_props(props) 2692 else: 2693 expression = self._parse_ddl_select() 2694 2695 # Some dialects also support using a table as an alias instead of a SELECT. 2696 # Here we fallback to this as an alternative. 2697 if not expression and has_alias: 2698 expression = self._try_parse(self._parse_table_parts) 2699 2700 if create_token_type == TokenType.TABLE: 2701 # exp.Properties.Location.POST_EXPRESSION 2702 extend_props(self._parse_properties()) 2703 2704 indexes = [] 2705 while True: 2706 index = self._parse_index() 2707 2708 # exp.Properties.Location.POST_INDEX 2709 extend_props(self._parse_properties()) 2710 if not index: 2711 break 2712 else: 2713 self._match(TokenType.COMMA) 2714 indexes.append(index) 2715 elif create_token_type == TokenType.VIEW: 2716 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2717 no_schema_binding = True 2718 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2719 extend_props(self._parse_properties()) 2720 2721 shallow = self._match_text_seq("SHALLOW") 2722 2723 if self._match_texts(self.CLONE_KEYWORDS): 2724 copy = self._prev.text.lower() == "copy" 2725 clone = self.expression( 2726 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2727 ) 2728 2729 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2730 return self._parse_as_command(start) 2731 2732 create_kind_text = create_token.text.upper() 2733 return self.expression( 2734 exp.Create( 2735 this=this, 2736 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2737 replace=replace, 2738 refresh=refresh, 2739 unique=unique, 2740 expression=expression, 2741 exists=exists, 2742 properties=properties, 2743 indexes=indexes, 2744 no_schema_binding=no_schema_binding, 2745 begin=begin, 2746 clone=clone, 2747 concurrently=concurrently, 2748 clustered=clustered, 2749 ) 2750 ) 2751 2752 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2753 seq = exp.SequenceProperties() 2754 2755 options = [] 2756 index = self._index 2757 2758 while self._curr: 2759 self._match(TokenType.COMMA) 2760 if self._match_text_seq("INCREMENT"): 2761 self._match_text_seq("BY") 2762 self._match_text_seq("=") 2763 seq.set("increment", self._parse_term()) 2764 elif self._match_text_seq("MINVALUE"): 2765 seq.set("minvalue", self._parse_term()) 2766 elif self._match_text_seq("MAXVALUE"): 2767 seq.set("maxvalue", self._parse_term()) 2768 elif self._match_text_seq("START"): 2769 self._match_text_seq("WITH") 2770 self._match_text_seq("=") 2771 seq.set("start", self._parse_term()) 2772 elif self._match_text_seq("CACHE"): 2773 # T-SQL allows empty CACHE which is initialized dynamically 2774 seq.set("cache", self._parse_number() or True) 2775 elif self._match_text_seq("OWNED", "BY"): 2776 # "OWNED BY NONE" is the default 2777 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2778 else: 2779 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2780 if opt: 2781 options.append(opt) 2782 else: 2783 break 2784 2785 seq.set("options", options if options else None) 2786 return None if self._index == index else seq 2787 2788 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2789 events = [] 2790 2791 while True: 2792 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2793 2794 if not event_type: 2795 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2796 2797 columns = ( 2798 self._parse_csv(self._parse_column) 2799 if event_type == "UPDATE" and self._match_text_seq("OF") 2800 else None 2801 ) 2802 2803 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2804 2805 if not self._match(TokenType.OR): 2806 break 2807 2808 return events 2809 2810 def _parse_trigger_deferrable( 2811 self, 2812 ) -> tuple[str | None, str | None]: 2813 deferrable_var = self._parse_var_from_options( 2814 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2815 ) 2816 deferrable = deferrable_var.this if deferrable_var else None 2817 2818 initially = None 2819 if deferrable and self._match_text_seq("INITIALLY"): 2820 initially = ( 2821 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2822 ) 2823 2824 return deferrable, initially 2825 2826 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2827 if not self._match_text_seq(keyword): 2828 return None 2829 if not self._match_text_seq("TABLE"): 2830 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2831 self._match_text_seq("AS") 2832 return self._parse_id_var() 2833 2834 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2835 if not self._match_text_seq("REFERENCING"): 2836 return None 2837 2838 old_alias = None 2839 new_alias = None 2840 2841 while True: 2842 if alias := self._parse_trigger_referencing_clause("OLD"): 2843 if old_alias is not None: 2844 self.raise_error("Duplicate OLD clause in REFERENCING") 2845 old_alias = alias 2846 elif alias := self._parse_trigger_referencing_clause("NEW"): 2847 if new_alias is not None: 2848 self.raise_error("Duplicate NEW clause in REFERENCING") 2849 new_alias = alias 2850 else: 2851 break 2852 2853 if old_alias is None and new_alias is None: 2854 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2855 2856 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2857 2858 def _parse_trigger_for_each(self) -> str | None: 2859 if not self._match_text_seq("FOR", "EACH"): 2860 return None 2861 2862 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2863 2864 def _parse_trigger_execute(self) -> exp.TriggerExecute | None: 2865 if not self._match(TokenType.EXECUTE): 2866 return None 2867 2868 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2869 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2870 2871 func_call = self._parse_column() 2872 return self.expression(exp.TriggerExecute(this=func_call)) 2873 2874 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2875 # only used for teradata currently 2876 self._match(TokenType.COMMA) 2877 2878 kwargs = { 2879 "no": self._match_text_seq("NO"), 2880 "dual": self._match_text_seq("DUAL"), 2881 "before": self._match_text_seq("BEFORE"), 2882 "default": self._match_text_seq("DEFAULT"), 2883 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2884 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2885 "after": self._match_text_seq("AFTER"), 2886 "minimum": self._match_texts(("MIN", "MINIMUM")), 2887 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2888 } 2889 2890 if self._match_texts(self.PROPERTY_PARSERS): 2891 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2892 try: 2893 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2894 except TypeError: 2895 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2896 2897 if self._match_text_seq("CHARACTER", "SET"): 2898 return self._parse_character_set(default=bool(kwargs["default"])) 2899 2900 return None 2901 2902 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2903 return self._parse_wrapped_csv(self._parse_property) 2904 2905 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2906 if self._match_texts(self.PROPERTY_PARSERS): 2907 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2908 2909 if self._match_text_seq("CHARACTER", "SET"): 2910 return self._parse_character_set() 2911 2912 if self._match(TokenType.DEFAULT): 2913 if self._match_texts(self.PROPERTY_PARSERS): 2914 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 2915 2916 if self._match_text_seq("CHARACTER", "SET"): 2917 return self._parse_character_set(default=True) 2918 2919 if self._match_text_seq("COMPOUND", "SORTKEY"): 2920 return self._parse_sortkey(compound=True) 2921 2922 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 2923 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 2924 2925 if self._match_text_seq("NOT", "DETERMINISTIC"): 2926 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 2927 2928 index = self._index 2929 2930 seq_props = self._parse_sequence_properties() 2931 if seq_props: 2932 return seq_props 2933 2934 self._retreat(index) 2935 return self._parse_key_value_property() 2936 2937 def _parse_key_value_property( 2938 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 2939 ) -> exp.Property | None: 2940 index = self._index 2941 key = self._parse_column() 2942 2943 if not self._match(TokenType.EQ): 2944 self._retreat(index) 2945 return None 2946 2947 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 2948 if isinstance(key, exp.Column): 2949 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 2950 2951 value = ( 2952 parse_value() 2953 if parse_value 2954 else self._parse_bitwise() or self._parse_var(any_token=True) 2955 ) 2956 2957 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 2958 if isinstance(value, exp.Column): 2959 value = exp.var(value.name) 2960 2961 return self.expression(exp.Property(this=key, value=value)) 2962 2963 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 2964 if self._match_text_seq("BY"): 2965 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 2966 2967 self._match(TokenType.ALIAS) 2968 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 2969 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 2970 2971 return self.expression( 2972 exp.FileFormatProperty( 2973 this=( 2974 self.expression( 2975 exp.InputOutputFormat( 2976 input_format=input_format, output_format=output_format 2977 ) 2978 ) 2979 if input_format or output_format 2980 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 2981 ), 2982 hive_format=True, 2983 ) 2984 ) 2985 2986 def _parse_unquoted_field(self) -> exp.Expr | None: 2987 field = self._parse_field() 2988 if isinstance(field, exp.Identifier) and not field.quoted: 2989 field = exp.var(field) 2990 2991 return field 2992 2993 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 2994 self._match(TokenType.EQ) 2995 self._match(TokenType.ALIAS) 2996 2997 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 2998 2999 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 3000 properties = [] 3001 while True: 3002 if before: 3003 prop = self._parse_property_before() 3004 else: 3005 prop = self._parse_property() 3006 if not prop: 3007 break 3008 for p in ensure_list(prop): 3009 properties.append(p) 3010 3011 if properties: 3012 return self.expression(exp.Properties(expressions=properties)) 3013 3014 return None 3015 3016 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3017 return self.expression( 3018 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3019 ) 3020 3021 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3022 return self.expression( 3023 exp.SqlSecurityProperty( 3024 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3025 ) 3026 ) 3027 3028 def _parse_settings_property(self) -> exp.SettingsProperty: 3029 return self.expression( 3030 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3031 ) 3032 3033 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3034 if not self._match_text_seq("ON", "NULL", "INPUT"): 3035 self._retreat(self._index - 1) 3036 return None 3037 3038 return self.expression(exp.CalledOnNullInputProperty()) 3039 3040 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3041 if self._index >= 2: 3042 pre_volatile_token = self._tokens[self._index - 2] 3043 else: 3044 pre_volatile_token = None 3045 3046 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3047 return exp.VolatileProperty() 3048 3049 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3050 3051 def _parse_retention_period(self) -> exp.Var: 3052 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3053 number = self._parse_number() 3054 number_str = f"{number} " if number else "" 3055 unit = self._parse_var(any_token=True) 3056 return exp.var(f"{number_str}{unit}") 3057 3058 def _parse_system_versioning_property( 3059 self, with_: bool = False 3060 ) -> exp.WithSystemVersioningProperty: 3061 self._match(TokenType.EQ) 3062 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3063 3064 if self._match_text_seq("OFF"): 3065 prop.set("on", False) 3066 return prop 3067 3068 self._match(TokenType.ON) 3069 if self._match(TokenType.L_PAREN): 3070 while self._curr and not self._match(TokenType.R_PAREN): 3071 if self._match_text_seq("HISTORY_TABLE", "="): 3072 prop.set("this", self._parse_table_parts()) 3073 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3074 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3075 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3076 prop.set("retention_period", self._parse_retention_period()) 3077 3078 self._match(TokenType.COMMA) 3079 3080 return prop 3081 3082 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3083 self._match(TokenType.EQ) 3084 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3085 prop = self.expression(exp.DataDeletionProperty(on=on)) 3086 3087 if self._match(TokenType.L_PAREN): 3088 while self._curr and not self._match(TokenType.R_PAREN): 3089 if self._match_text_seq("FILTER_COLUMN", "="): 3090 prop.set("filter_column", self._parse_column()) 3091 elif self._match_text_seq("RETENTION_PERIOD", "="): 3092 prop.set("retention_period", self._parse_retention_period()) 3093 3094 self._match(TokenType.COMMA) 3095 3096 return prop 3097 3098 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3099 kind = "HASH" 3100 expressions: list[exp.Expr] | None = None 3101 if self._match_text_seq("BY", "HASH"): 3102 expressions = self._parse_wrapped_csv(self._parse_id_var) 3103 elif self._match_text_seq("BY", "RANDOM"): 3104 kind = "RANDOM" 3105 3106 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3107 buckets: exp.Expr | None = None 3108 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3109 buckets = self._parse_number() 3110 3111 return self.expression( 3112 exp.DistributedByProperty( 3113 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3114 ) 3115 ) 3116 3117 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3118 self._match_text_seq("KEY") 3119 expressions = self._parse_wrapped_id_vars() 3120 return self.expression(expr_type(expressions=expressions)) 3121 3122 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3123 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3124 prop = self._parse_system_versioning_property(with_=True) 3125 self._match_r_paren() 3126 return prop 3127 3128 if self._match(TokenType.L_PAREN, advance=False): 3129 result: list[exp.Expr] = [] 3130 for i in self._parse_wrapped_properties(): 3131 result.extend(i) if isinstance(i, list) else result.append(i) 3132 return result 3133 3134 if self._match_text_seq("JOURNAL"): 3135 return self._parse_withjournaltable() 3136 3137 if self._match_texts(self.VIEW_ATTRIBUTES): 3138 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3139 3140 if self._match_text_seq("DATA"): 3141 return self._parse_withdata(no=False) 3142 elif self._match_text_seq("NO", "DATA"): 3143 return self._parse_withdata(no=True) 3144 3145 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3146 return self._parse_serde_properties(with_=True) 3147 3148 if self._match(TokenType.SCHEMA): 3149 return self.expression( 3150 exp.WithSchemaBindingProperty( 3151 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3152 ) 3153 ) 3154 3155 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3156 return self.expression( 3157 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3158 ) 3159 3160 if not self._next: 3161 return None 3162 3163 return self._parse_withisolatedloading() 3164 3165 def _parse_procedure_option(self) -> exp.Expr | None: 3166 if self._match_text_seq("EXECUTE", "AS"): 3167 return self.expression( 3168 exp.ExecuteAsProperty( 3169 this=self._parse_var_from_options( 3170 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3171 ) 3172 or self._parse_string() 3173 ) 3174 ) 3175 3176 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3177 3178 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3179 def _parse_definer(self) -> exp.DefinerProperty | None: 3180 self._match(TokenType.EQ) 3181 3182 user = self._parse_id_var() 3183 self._match(TokenType.PARAMETER) 3184 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3185 3186 if not user or not host: 3187 return None 3188 3189 return exp.DefinerProperty(this=f"{user}@{host}") 3190 3191 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3192 self._match(TokenType.TABLE) 3193 self._match(TokenType.EQ) 3194 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3195 3196 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3197 return self.expression(exp.LogProperty(no=no)) 3198 3199 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3200 return self.expression(exp.JournalProperty(**kwargs)) 3201 3202 def _parse_checksum(self) -> exp.ChecksumProperty: 3203 self._match(TokenType.EQ) 3204 3205 on = None 3206 if self._match(TokenType.ON): 3207 on = True 3208 elif self._match_text_seq("OFF"): 3209 on = False 3210 3211 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3212 3213 def _parse_cluster(self) -> exp.Cluster: 3214 self._match(TokenType.CLUSTER_BY) 3215 return self.expression( 3216 exp.Cluster( 3217 expressions=self._parse_csv(self._parse_column), 3218 ) 3219 ) 3220 3221 def _parse_cluster_property(self) -> exp.ClusterProperty: 3222 return self.expression( 3223 exp.ClusterProperty( 3224 expressions=self._parse_wrapped_csv(self._parse_column), 3225 ) 3226 ) 3227 3228 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3229 self._match_text_seq("BY") 3230 3231 self._match_l_paren() 3232 expressions = self._parse_csv(self._parse_column) 3233 self._match_r_paren() 3234 3235 if self._match_text_seq("SORTED", "BY"): 3236 self._match_l_paren() 3237 sorted_by = self._parse_csv(self._parse_ordered) 3238 self._match_r_paren() 3239 else: 3240 sorted_by = None 3241 3242 self._match(TokenType.INTO) 3243 buckets = self._parse_number() 3244 self._match_text_seq("BUCKETS") 3245 3246 return self.expression( 3247 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3248 ) 3249 3250 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3251 if not self._match_text_seq("GRANTS"): 3252 self._retreat(self._index - 1) 3253 return None 3254 3255 return self.expression(exp.CopyGrantsProperty()) 3256 3257 def _parse_freespace(self) -> exp.FreespaceProperty: 3258 self._match(TokenType.EQ) 3259 return self.expression( 3260 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3261 ) 3262 3263 def _parse_mergeblockratio( 3264 self, no: bool = False, default: bool = False 3265 ) -> exp.MergeBlockRatioProperty: 3266 if self._match(TokenType.EQ): 3267 return self.expression( 3268 exp.MergeBlockRatioProperty( 3269 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3270 ) 3271 ) 3272 3273 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3274 3275 def _parse_datablocksize( 3276 self, 3277 default: bool | None = None, 3278 minimum: bool | None = None, 3279 maximum: bool | None = None, 3280 ) -> exp.DataBlocksizeProperty: 3281 self._match(TokenType.EQ) 3282 size = self._parse_number() 3283 3284 units = None 3285 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3286 units = self._prev.text 3287 3288 return self.expression( 3289 exp.DataBlocksizeProperty( 3290 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3291 ) 3292 ) 3293 3294 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3295 self._match(TokenType.EQ) 3296 always = self._match_text_seq("ALWAYS") 3297 manual = self._match_text_seq("MANUAL") 3298 never = self._match_text_seq("NEVER") 3299 default = self._match_text_seq("DEFAULT") 3300 3301 autotemp = None 3302 if self._match_text_seq("AUTOTEMP"): 3303 autotemp = self._parse_schema() 3304 3305 return self.expression( 3306 exp.BlockCompressionProperty( 3307 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3308 ) 3309 ) 3310 3311 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3312 index = self._index 3313 no = self._match_text_seq("NO") 3314 concurrent = self._match_text_seq("CONCURRENT") 3315 3316 if not self._match_text_seq("ISOLATED", "LOADING"): 3317 self._retreat(index) 3318 return None 3319 3320 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3321 return self.expression( 3322 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3323 ) 3324 3325 def _parse_locking(self) -> exp.LockingProperty: 3326 if self._match(TokenType.TABLE): 3327 kind = "TABLE" 3328 elif self._match(TokenType.VIEW): 3329 kind = "VIEW" 3330 elif self._match(TokenType.ROW): 3331 kind = "ROW" 3332 elif self._match_text_seq("DATABASE"): 3333 kind = "DATABASE" 3334 else: 3335 kind = None 3336 3337 if kind in ("DATABASE", "TABLE", "VIEW"): 3338 this = self._parse_table_parts() 3339 else: 3340 this = None 3341 3342 if self._match(TokenType.FOR): 3343 for_or_in = "FOR" 3344 elif self._match(TokenType.IN): 3345 for_or_in = "IN" 3346 else: 3347 for_or_in = None 3348 3349 if self._match_text_seq("ACCESS"): 3350 lock_type = "ACCESS" 3351 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3352 lock_type = "EXCLUSIVE" 3353 elif self._match_text_seq("SHARE"): 3354 lock_type = "SHARE" 3355 elif self._match_text_seq("READ"): 3356 lock_type = "READ" 3357 elif self._match_text_seq("WRITE"): 3358 lock_type = "WRITE" 3359 elif self._match_text_seq("CHECKSUM"): 3360 lock_type = "CHECKSUM" 3361 else: 3362 lock_type = None 3363 3364 override = self._match_text_seq("OVERRIDE") 3365 3366 return self.expression( 3367 exp.LockingProperty( 3368 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3369 ) 3370 ) 3371 3372 def _parse_partition_by(self) -> list[exp.Expr]: 3373 if self._match(TokenType.PARTITION_BY): 3374 return self._parse_csv(self._parse_disjunction) 3375 return [] 3376 3377 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3378 def _parse_partition_bound_expr() -> exp.Expr | None: 3379 if self._match_text_seq("MINVALUE"): 3380 return exp.var("MINVALUE") 3381 if self._match_text_seq("MAXVALUE"): 3382 return exp.var("MAXVALUE") 3383 return self._parse_bitwise() 3384 3385 this: exp.Expr | list[exp.Expr] | None = None 3386 expression = None 3387 from_expressions = None 3388 to_expressions = None 3389 3390 if self._match(TokenType.IN): 3391 this = self._parse_wrapped_csv(self._parse_bitwise) 3392 elif self._match(TokenType.FROM): 3393 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3394 self._match_text_seq("TO") 3395 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3396 elif self._match_text_seq("WITH", "(", "MODULUS"): 3397 this = self._parse_number() 3398 self._match_text_seq(",", "REMAINDER") 3399 expression = self._parse_number() 3400 self._match_r_paren() 3401 else: 3402 self.raise_error("Failed to parse partition bound spec.") 3403 3404 return self.expression( 3405 exp.PartitionBoundSpec( 3406 this=this, 3407 expression=expression, 3408 from_expressions=from_expressions, 3409 to_expressions=to_expressions, 3410 ) 3411 ) 3412 3413 # https://www.postgresql.org/docs/current/sql-createtable.html 3414 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3415 if not self._match_text_seq("OF"): 3416 self._retreat(self._index - 1) 3417 return None 3418 3419 this = self._parse_table(schema=True) 3420 3421 if self._match(TokenType.DEFAULT): 3422 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3423 elif self._match_text_seq("FOR", "VALUES"): 3424 expression = self._parse_partition_bound_spec() 3425 else: 3426 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3427 3428 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3429 3430 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3431 self._match(TokenType.EQ) 3432 return self.expression( 3433 exp.PartitionedByProperty( 3434 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3435 ) 3436 ) 3437 3438 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3439 if self._match_text_seq("AND", "STATISTICS"): 3440 statistics = True 3441 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3442 statistics = False 3443 else: 3444 statistics = None 3445 3446 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3447 3448 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3449 if self._match_text_seq("SQL"): 3450 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3451 return None 3452 3453 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3454 if self._match_text_seq("SQL", "DATA"): 3455 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3456 return None 3457 3458 def _parse_no_property(self) -> exp.Expr | None: 3459 if self._match_text_seq("PRIMARY", "INDEX"): 3460 return exp.NoPrimaryIndexProperty() 3461 if self._match_text_seq("SQL"): 3462 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3463 return None 3464 3465 def _parse_on_property(self) -> exp.Expr | None: 3466 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3467 return exp.OnCommitProperty() 3468 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3469 return exp.OnCommitProperty(delete=True) 3470 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3471 3472 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3473 if self._match_text_seq("SQL", "DATA"): 3474 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3475 return None 3476 3477 def _parse_distkey(self) -> exp.DistKeyProperty: 3478 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3479 3480 def _parse_create_like(self) -> exp.LikeProperty | None: 3481 table = self._parse_table(schema=True) 3482 3483 options = [] 3484 while self._match_texts(("INCLUDING", "EXCLUDING")): 3485 this = self._prev.text.upper() 3486 3487 id_var = self._parse_id_var() 3488 if not id_var: 3489 return None 3490 3491 options.append( 3492 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3493 ) 3494 3495 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3496 3497 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3498 return self.expression( 3499 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3500 ) 3501 3502 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3503 self._match(TokenType.EQ) 3504 return self.expression( 3505 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3506 ) 3507 3508 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3509 self._match_text_seq("WITH", "CONNECTION") 3510 return self.expression( 3511 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3512 ) 3513 3514 def _parse_returns(self) -> exp.ReturnsProperty: 3515 value: exp.Expr | None 3516 null = None 3517 is_table = self._match(TokenType.TABLE) 3518 3519 if is_table: 3520 if self._match(TokenType.LT): 3521 value = self.expression( 3522 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3523 ) 3524 if not self._match(TokenType.GT): 3525 self.raise_error("Expecting >") 3526 else: 3527 value = self._parse_schema(exp.var("TABLE")) 3528 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3529 null = True 3530 value = None 3531 else: 3532 value = self._parse_types() 3533 3534 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3535 3536 def _parse_describe(self) -> exp.Describe: 3537 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3538 style: str | None = ( 3539 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3540 ) 3541 if self._match(TokenType.DOT): 3542 style = None 3543 self._retreat(self._index - 2) 3544 3545 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3546 3547 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3548 this = self._parse_statement() 3549 else: 3550 this = self._parse_table(schema=True) 3551 3552 properties = self._parse_properties() 3553 expressions = properties.expressions if properties else None 3554 partition = self._parse_partition() 3555 return self.expression( 3556 exp.Describe( 3557 this=this, 3558 style=style, 3559 kind=kind, 3560 expressions=expressions, 3561 partition=partition, 3562 format=format, 3563 as_json=self._match_text_seq("AS", "JSON"), 3564 ) 3565 ) 3566 3567 def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts: 3568 kind = self._prev.text.upper() 3569 expressions = [] 3570 3571 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3572 if self._match(TokenType.WHEN): 3573 expression = self._parse_disjunction() 3574 self._match(TokenType.THEN) 3575 else: 3576 expression = None 3577 3578 else_ = self._match(TokenType.ELSE) 3579 3580 if not self._match(TokenType.INTO): 3581 return None 3582 3583 return self.expression( 3584 exp.ConditionalInsert( 3585 this=self.expression( 3586 exp.Insert( 3587 this=self._parse_table(schema=True), 3588 expression=self._parse_derived_table_values(), 3589 ) 3590 ), 3591 expression=expression, 3592 else_=else_, 3593 ) 3594 ) 3595 3596 expression = parse_conditional_insert() 3597 while expression is not None: 3598 expressions.append(expression) 3599 expression = parse_conditional_insert() 3600 3601 return self.expression( 3602 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3603 comments=comments, 3604 ) 3605 3606 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3607 comments: list[str] = [] 3608 hint = self._parse_hint() 3609 overwrite = self._match(TokenType.OVERWRITE) 3610 ignore = self._match(TokenType.IGNORE) 3611 local = self._match_text_seq("LOCAL") 3612 alternative = None 3613 is_function = None 3614 3615 if self._match_text_seq("DIRECTORY"): 3616 this: exp.Expr | None = self.expression( 3617 exp.Directory( 3618 this=self._parse_var_or_string(), 3619 local=local, 3620 row_format=self._parse_row_format(match_row=True), 3621 ) 3622 ) 3623 else: 3624 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3625 comments += ensure_list(self._prev_comments) 3626 return self._parse_multitable_inserts(comments) 3627 3628 if self._match(TokenType.OR): 3629 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3630 3631 self._match(TokenType.INTO) 3632 comments += ensure_list(self._prev_comments) 3633 self._match(TokenType.TABLE) 3634 is_function = self._match(TokenType.FUNCTION) 3635 3636 this = self._parse_function() if is_function else self._parse_insert_table() 3637 3638 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3639 set_values = None 3640 if self._match(TokenType.SET): 3641 columns = [] 3642 values = [] 3643 3644 def _parse_set_assignment() -> exp.Expr | None: 3645 target = self._parse_column() 3646 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3647 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3648 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3649 else: 3650 value = self._parse_disjunction() 3651 3652 if value: 3653 columns.append(target.this) 3654 values.append(value) 3655 return value 3656 3657 self.raise_error("Expected column assignment in INSERT ... SET") 3658 return None 3659 3660 self._parse_csv(_parse_set_assignment) 3661 3662 this = self.expression(exp.Schema(this=this, expressions=columns)) 3663 set_values = self.expression( 3664 exp.Values( 3665 expressions=[exp.Tuple(expressions=values)], 3666 alias=self._parse_table_alias(), 3667 ) 3668 ) 3669 3670 returning = self._parse_returning() # TSQL allows RETURNING before source 3671 3672 stored = self._match_text_seq("STORED") and self._parse_stored() 3673 by_name = self._match_text_seq("BY", "NAME") 3674 exists = self._parse_exists() 3675 replace_where = None 3676 replace_using = None 3677 3678 if self._match(TokenType.REPLACE): 3679 if self._match(TokenType.WHERE): 3680 replace_where = self._parse_disjunction() 3681 elif self._match(TokenType.USING): 3682 replace_using = self._parse_using_identifiers() 3683 3684 return self.expression( 3685 exp.Insert( 3686 hint=hint, 3687 is_function=is_function, 3688 this=this, 3689 stored=stored, 3690 by_name=by_name, 3691 exists=exists, 3692 where=replace_where, 3693 using=replace_using, 3694 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3695 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3696 default=self._match_text_seq("DEFAULT", "VALUES"), 3697 expression=set_values 3698 or self._parse_derived_table_values(allow_value_synonym=True) 3699 or self._parse_ddl_select(), 3700 conflict=self._parse_on_conflict(), 3701 returning=returning or self._parse_returning(), 3702 overwrite=overwrite, 3703 alternative=alternative, 3704 ignore=ignore, 3705 source=self._match(TokenType.TABLE) and self._parse_table(), 3706 ), 3707 comments=comments, 3708 ) 3709 3710 def _parse_insert_table(self) -> exp.Expr | None: 3711 this = self._parse_table(schema=True, parse_partition=True) 3712 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3713 this.set("alias", self._parse_table_alias()) 3714 return this 3715 3716 def _parse_kill(self) -> exp.Kill: 3717 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3718 3719 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3720 3721 def _parse_on_conflict(self) -> exp.OnConflict | None: 3722 conflict = self._match_text_seq("ON", "CONFLICT") 3723 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3724 3725 if not conflict and not duplicate: 3726 return None 3727 3728 conflict_keys = None 3729 constraint = None 3730 3731 if conflict: 3732 if self._match_text_seq("ON", "CONSTRAINT"): 3733 constraint = self._parse_id_var() 3734 elif self._match(TokenType.L_PAREN): 3735 conflict_keys = self._parse_csv(self._parse_indexed_column) 3736 self._match_r_paren() 3737 3738 index_predicate = self._parse_where() 3739 3740 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3741 if self._prev.token_type == TokenType.UPDATE: 3742 self._match(TokenType.SET) 3743 expressions = self._parse_csv(self._parse_update_assignment) 3744 else: 3745 expressions = None 3746 3747 return self.expression( 3748 exp.OnConflict( 3749 duplicate=duplicate, 3750 expressions=expressions, 3751 action=action, 3752 conflict_keys=conflict_keys, 3753 index_predicate=index_predicate, 3754 constraint=constraint, 3755 where=self._parse_where(), 3756 ) 3757 ) 3758 3759 def _parse_returning(self) -> exp.Returning | None: 3760 if not self._match(TokenType.RETURNING): 3761 return None 3762 return self.expression( 3763 exp.Returning( 3764 expressions=self._parse_csv(self._parse_expression), 3765 into=self._match(TokenType.INTO) and self._parse_table_part(), 3766 ) 3767 ) 3768 3769 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3770 if not self._match(TokenType.FORMAT): 3771 return None 3772 return self._parse_row_format() 3773 3774 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3775 index = self._index 3776 with_ = with_ or self._match_text_seq("WITH") 3777 3778 if not self._match(TokenType.SERDE_PROPERTIES): 3779 self._retreat(index) 3780 return None 3781 return self.expression( 3782 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3783 ) 3784 3785 def _parse_row_format( 3786 self, match_row: bool = False 3787 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3788 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3789 return None 3790 3791 if self._match_text_seq("SERDE"): 3792 this = self._parse_string() 3793 3794 serde_properties = self._parse_serde_properties() 3795 3796 return self.expression( 3797 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3798 ) 3799 3800 self._match_text_seq("DELIMITED") 3801 3802 kwargs = {} 3803 3804 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3805 kwargs["fields"] = self._parse_string() 3806 if self._match_text_seq("ESCAPED", "BY"): 3807 kwargs["escaped"] = self._parse_string() 3808 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3809 kwargs["collection_items"] = self._parse_string() 3810 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3811 kwargs["map_keys"] = self._parse_string() 3812 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3813 kwargs["lines"] = self._parse_string() 3814 if self._match_text_seq("NULL", "DEFINED", "AS"): 3815 kwargs["null"] = self._parse_string() 3816 3817 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3818 3819 def _parse_load(self) -> exp.LoadData | exp.Command: 3820 if self._match_text_seq("DATA"): 3821 local = self._match_text_seq("LOCAL") 3822 self._match_text_seq("INPATH") 3823 inpath = self._parse_string() 3824 overwrite = self._match(TokenType.OVERWRITE) 3825 temp: bool | None = None 3826 if self._match(TokenType.INTO): 3827 temp = self._match(TokenType.TEMPORARY) 3828 self._match(TokenType.TABLE) 3829 3830 return self.expression( 3831 exp.LoadData( 3832 this=self._parse_table(schema=True), 3833 local=local, 3834 overwrite=overwrite, 3835 temp=temp, 3836 inpath=inpath, 3837 files=self._match_text_seq("FROM", "FILES") 3838 and exp.Properties(expressions=self._parse_wrapped_properties()), 3839 partition=self._parse_partition(), 3840 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3841 serde=self._match_text_seq("SERDE") and self._parse_string(), 3842 ) 3843 ) 3844 return self._parse_as_command(self._prev) 3845 3846 def _parse_delete(self) -> exp.Delete: 3847 hint = self._parse_hint() 3848 3849 # This handles MySQL's "Multiple-Table Syntax" 3850 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3851 tables = None 3852 if not self._match(TokenType.FROM, advance=False): 3853 tables = self._parse_csv(self._parse_table) or None 3854 3855 returning = self._parse_returning() 3856 3857 return self.expression( 3858 exp.Delete( 3859 hint=hint, 3860 tables=tables, 3861 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3862 using=self._match(TokenType.USING) 3863 and self._parse_csv(lambda: self._parse_table(joins=True)), 3864 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3865 where=self._parse_where(), 3866 returning=returning or self._parse_returning(), 3867 order=self._parse_order(), 3868 limit=self._parse_limit(), 3869 ) 3870 ) 3871 3872 def _parse_update_assignment(self) -> exp.Expr | None: 3873 this = self._parse_comparison() 3874 if self._match(TokenType.EQ): 3875 comments = self._prev_comments 3876 this = self.expression( 3877 exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments 3878 ) 3879 return this 3880 3881 def _parse_update(self) -> exp.Update: 3882 hint = self._parse_hint() 3883 kwargs: dict[str, object] = { 3884 "hint": hint, 3885 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3886 } 3887 while self._curr: 3888 if self._match(TokenType.SET): 3889 kwargs["expressions"] = self._parse_csv(self._parse_update_assignment) 3890 elif self._match(TokenType.RETURNING, advance=False): 3891 kwargs["returning"] = self._parse_returning() 3892 elif self._match(TokenType.FROM, advance=False): 3893 from_ = self._parse_from(joins=True) 3894 table = from_.this if from_ else None 3895 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3896 table.set("joins", list(self._parse_joins()) or None) 3897 3898 kwargs["from_"] = from_ 3899 elif self._match(TokenType.WHERE, advance=False): 3900 kwargs["where"] = self._parse_where() 3901 elif self._match(TokenType.ORDER_BY, advance=False): 3902 kwargs["order"] = self._parse_order() 3903 elif self._match(TokenType.LIMIT, advance=False): 3904 kwargs["limit"] = self._parse_limit() 3905 else: 3906 break 3907 3908 return self.expression(exp.Update(**kwargs)) 3909 3910 def _parse_use(self) -> exp.Use: 3911 return self.expression( 3912 exp.Use( 3913 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 3914 this=self._parse_table(schema=False), 3915 ) 3916 ) 3917 3918 def _parse_uncache(self) -> exp.Uncache: 3919 if not self._match(TokenType.TABLE): 3920 self.raise_error("Expecting TABLE after UNCACHE") 3921 3922 return self.expression( 3923 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 3924 ) 3925 3926 def _parse_cache(self) -> exp.Cache: 3927 lazy = self._match_text_seq("LAZY") 3928 self._match(TokenType.TABLE) 3929 table = self._parse_table(schema=True) 3930 3931 options = [] 3932 if self._match_text_seq("OPTIONS"): 3933 self._match_l_paren() 3934 k = self._parse_string() 3935 self._match(TokenType.EQ) 3936 v = self._parse_string() 3937 options = [k, v] 3938 self._match_r_paren() 3939 3940 self._match(TokenType.ALIAS) 3941 return self.expression( 3942 exp.Cache( 3943 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 3944 ) 3945 ) 3946 3947 def _parse_partition(self) -> exp.Partition | None: 3948 if not self._match_texts(self.PARTITION_KEYWORDS): 3949 return None 3950 3951 return self.expression( 3952 exp.Partition( 3953 subpartition=self._prev.text.upper() == "SUBPARTITION", 3954 expressions=self._parse_wrapped_csv(self._parse_disjunction), 3955 ) 3956 ) 3957 3958 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 3959 def _parse_value_expression() -> exp.Expr | None: 3960 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3961 return exp.var(self._prev.text.upper()) 3962 return self._parse_expression() 3963 3964 if self._match(TokenType.L_PAREN): 3965 expressions = self._parse_csv(_parse_value_expression) 3966 self._match_r_paren() 3967 return self.expression(exp.Tuple(expressions=expressions)) 3968 3969 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 3970 expression = self._parse_expression() 3971 if expression: 3972 return self.expression(exp.Tuple(expressions=[expression])) 3973 return None 3974 3975 def _parse_projections( 3976 self, 3977 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 3978 return self._parse_expressions(), None 3979 3980 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 3981 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 3982 this: exp.Expr | None = self._parse_simplified_pivot( 3983 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 3984 ) 3985 elif self._match(TokenType.FROM): 3986 from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True) 3987 # Support parentheses for duckdb FROM-first syntax 3988 select = self._parse_select(from_=from_) 3989 if select: 3990 if not select.args.get("from_"): 3991 select.set("from_", from_) 3992 this = select 3993 else: 3994 this = exp.select("*").from_(t.cast(exp.From, from_)) 3995 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3996 else: 3997 this = ( 3998 self._parse_table(consume_pipe=True) 3999 if table 4000 else self._parse_select(nested=True, parse_set_operation=False) 4001 ) 4002 4003 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 4004 # in case a modifier (e.g. join) is following 4005 if table and isinstance(this, exp.Values) and this.alias: 4006 alias = this.args["alias"].pop() 4007 this = exp.Table(this=this, alias=alias) 4008 4009 this = self._parse_query_modifiers(self._parse_set_operations(this)) 4010 4011 return this 4012 4013 def _parse_select( 4014 self, 4015 nested: bool = False, 4016 table: bool = False, 4017 parse_subquery_alias: bool = True, 4018 parse_set_operation: bool = True, 4019 consume_pipe: bool = True, 4020 from_: exp.From | None = None, 4021 ) -> exp.Expr | None: 4022 query = self._parse_select_query( 4023 nested=nested, 4024 table=table, 4025 parse_subquery_alias=parse_subquery_alias, 4026 parse_set_operation=parse_set_operation, 4027 ) 4028 4029 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4030 if not query and from_: 4031 query = exp.select("*").from_(from_) 4032 if isinstance(query, exp.Query): 4033 query = self._parse_pipe_syntax_query(query) 4034 query = query.subquery(copy=False) if query and table else query 4035 4036 return query 4037 4038 def _parse_select_query( 4039 self, 4040 nested: bool = False, 4041 table: bool = False, 4042 parse_subquery_alias: bool = True, 4043 parse_set_operation: bool = True, 4044 ) -> exp.Expr | None: 4045 cte = self._parse_with() 4046 4047 if cte: 4048 this = self._parse_statement() 4049 4050 if not this: 4051 self.raise_error("Failed to parse any statement following CTE") 4052 return cte 4053 4054 while isinstance(this, exp.Subquery) and this.is_wrapper: 4055 this = this.this 4056 4057 assert this is not None 4058 if "with_" in this.arg_types: 4059 if inner_cte := this.args.get("with_"): 4060 cte.set("expressions", cte.expressions + inner_cte.expressions) 4061 if inner_cte.args.get("recursive"): 4062 cte.set("recursive", True) 4063 this.set("with_", cte) 4064 else: 4065 self.raise_error(f"{this.key} does not support CTE") 4066 this = cte 4067 4068 return this 4069 4070 # duckdb supports leading with FROM x 4071 from_ = ( 4072 self._parse_from(joins=True, consume_pipe=True) 4073 if self._match(TokenType.FROM, advance=False) 4074 else None 4075 ) 4076 4077 if self._match(TokenType.SELECT): 4078 comments = self._prev_comments 4079 4080 hint = self._parse_hint() 4081 4082 if self._next and not self._next.token_type == TokenType.DOT: 4083 all_ = self._match(TokenType.ALL) 4084 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4085 else: 4086 all_, matched_distinct = None, False 4087 4088 kind = ( 4089 self._prev.text.upper() 4090 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4091 else None 4092 ) 4093 4094 distinct: exp.Expr | None = ( 4095 self.expression( 4096 exp.Distinct( 4097 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4098 ) 4099 ) 4100 if matched_distinct 4101 else None 4102 ) 4103 4104 operation_modifiers = [] 4105 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4106 operation_modifiers.append(exp.var(self._prev.text.upper())) 4107 4108 limit = self._parse_limit(top=True) 4109 4110 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4111 if limit and not matched_distinct and not all_: 4112 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4113 if matched_distinct: 4114 distinct = self.expression( 4115 exp.Distinct( 4116 on=self._parse_value(values=False) 4117 if self._match(TokenType.ON) 4118 else None 4119 ) 4120 ) 4121 else: 4122 all_ = self._match(TokenType.ALL) 4123 4124 if all_ and distinct: 4125 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4126 4127 projections, exclude = self._parse_projections() 4128 4129 this = self.expression( 4130 exp.Select( 4131 kind=kind, 4132 hint=hint, 4133 distinct=distinct, 4134 expressions=projections, 4135 limit=limit, 4136 exclude=exclude, 4137 operation_modifiers=operation_modifiers or None, 4138 ) 4139 ) 4140 this.comments = comments 4141 4142 into = self._parse_into() 4143 if into: 4144 this.set("into", into) 4145 4146 if not from_: 4147 from_ = self._parse_from() 4148 4149 if from_: 4150 this.set("from_", from_) 4151 4152 this = self._parse_query_modifiers(this) 4153 elif (table or nested) and self._match(TokenType.L_PAREN): 4154 comments = self._prev_comments 4155 this = self._parse_wrapped_select(table=table) 4156 4157 if this: 4158 this.add_comments(comments, prepend=True) 4159 4160 # We return early here so that the UNION isn't attached to the subquery by the 4161 # following call to _parse_set_operations, but instead becomes the parent node 4162 self._match_r_paren() 4163 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4164 elif self._match(TokenType.VALUES, advance=False): 4165 this = self._parse_derived_table_values() 4166 elif from_: 4167 this = exp.select("*").from_(from_.this, copy=False) 4168 this = self._parse_query_modifiers(this) 4169 elif self._match(TokenType.SUMMARIZE): 4170 table = self._match(TokenType.TABLE) 4171 this = self._parse_select() or self._parse_string() or self._parse_table() 4172 return self.expression(exp.Summarize(this=this, table=table)) 4173 elif self._match(TokenType.DESCRIBE): 4174 this = self._parse_describe() 4175 else: 4176 this = None 4177 4178 return self._parse_set_operations(this) if parse_set_operation else this 4179 4180 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4181 self._match_text_seq("SEARCH") 4182 4183 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4184 4185 if not kind: 4186 return None 4187 4188 self._match_text_seq("FIRST", "BY") 4189 4190 return self.expression( 4191 exp.RecursiveWithSearch( 4192 kind=kind, 4193 this=self._parse_id_var(), 4194 expression=self._match_text_seq("SET") and self._parse_id_var(), 4195 using=self._match_text_seq("USING") and self._parse_id_var(), 4196 ) 4197 ) 4198 4199 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4200 if not skip_with_token and not self._match(TokenType.WITH): 4201 return None 4202 4203 comments = self._prev_comments 4204 recursive = self._match(TokenType.RECURSIVE) 4205 4206 last_comments = None 4207 expressions = [] 4208 udfs = [] 4209 while True: 4210 cte = self._parse_cte() 4211 if cte: 4212 if isinstance(cte, exp.FunctionSpecification): 4213 udfs.append(cte) 4214 else: 4215 expressions.append(cte) 4216 4217 if last_comments: 4218 cte.add_comments(last_comments) 4219 4220 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4221 break 4222 else: 4223 self._match(TokenType.WITH) 4224 recursive = self._match(TokenType.RECURSIVE) or recursive 4225 4226 last_comments = self._prev_comments 4227 4228 return self.expression( 4229 exp.With( 4230 expressions=expressions, 4231 recursive=recursive or None, 4232 search=self._parse_recursive_with_search(), 4233 udfs=udfs or None, 4234 ), 4235 comments=comments, 4236 ) 4237 4238 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4239 index = self._index 4240 4241 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4242 if not alias or not alias.this: 4243 self.raise_error("Expected CTE to have alias") 4244 4245 key_expressions = ( 4246 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4247 ) 4248 4249 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4250 self._retreat(index) 4251 return None 4252 4253 comments = self._prev_comments 4254 4255 if self._match_text_seq("NOT", "MATERIALIZED"): 4256 materialized = False 4257 elif self._match_text_seq("MATERIALIZED"): 4258 materialized = True 4259 else: 4260 materialized = None 4261 4262 cte = self.expression( 4263 exp.CTE( 4264 this=self._parse_wrapped(self._parse_statement), 4265 alias=alias, 4266 materialized=materialized, 4267 key_expressions=key_expressions, 4268 ), 4269 comments=comments, 4270 ) 4271 4272 values = cte.this 4273 if isinstance(values, exp.Values): 4274 cte.set("this", self._values_to_select(values)) 4275 4276 return cte 4277 4278 def _values_to_select(self, values: exp.Values) -> exp.Select: 4279 if values.alias: 4280 return exp.select("*").from_(values) 4281 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4282 4283 def _parse_table_alias( 4284 self, alias_tokens: t.Collection[TokenType] | None = None 4285 ) -> exp.TableAlias | None: 4286 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4287 # so this section tries to parse the clause version and if it fails, it treats the token 4288 # as an identifier (alias) 4289 if self._can_parse_limit_or_offset(): 4290 return None 4291 4292 # START is never treated as an implicit alias when followed by WITH, since that 4293 # would swallow the beginning of a START WITH ... CONNECT BY clause 4294 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4295 return None 4296 4297 any_token = self._match(TokenType.ALIAS) 4298 alias = ( 4299 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4300 or self._parse_string_as_identifier() 4301 ) 4302 4303 index = self._index 4304 if self._match(TokenType.L_PAREN): 4305 columns = self._parse_csv(self._parse_function_parameter) 4306 self._match_r_paren() if columns else self._retreat(index) 4307 else: 4308 columns = None 4309 4310 if not alias and not columns: 4311 return None 4312 4313 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4314 4315 # We bubble up comments from the Identifier to the TableAlias 4316 if isinstance(alias, exp.Identifier): 4317 table_alias.add_comments(alias.pop_comments()) 4318 4319 return table_alias 4320 4321 def _parse_subquery( 4322 self, this: exp.Expr | None, parse_alias: bool = True 4323 ) -> exp.Subquery | None: 4324 if not this: 4325 return None 4326 4327 return self.expression( 4328 exp.Subquery( 4329 this=this, 4330 pivots=self._parse_pivots(), 4331 alias=self._parse_table_alias() if parse_alias else None, 4332 sample=self._parse_table_sample(), 4333 ) 4334 ) 4335 4336 def _implicit_unnests_to_explicit(self, this: E) -> E: 4337 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4338 4339 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4340 for i, join in enumerate(this.args.get("joins") or []): 4341 table = join.this 4342 normalized_table = table.copy() 4343 normalized_table.meta["maybe_column"] = True 4344 normalized_table = _norm(normalized_table, dialect=self.dialect) 4345 4346 if isinstance(table, exp.Table) and not join.args.get("on"): 4347 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4348 table_as_column = table.to_column() 4349 unnest = exp.Unnest(expressions=[table_as_column]) 4350 4351 # Table.to_column creates a parent Alias node that we want to convert to 4352 # a TableAlias and attach to the Unnest, so it matches the parser's output 4353 if isinstance(table.args.get("alias"), exp.TableAlias): 4354 table_as_column.replace(table_as_column.this) 4355 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4356 4357 table.replace(unnest) 4358 4359 refs.add(normalized_table.alias_or_name) 4360 4361 return this 4362 4363 @t.overload 4364 def _parse_query_modifiers(self, this: E) -> E: ... 4365 4366 @t.overload 4367 def _parse_query_modifiers(self, this: None) -> None: ... 4368 4369 def _parse_query_modifiers(self, this): 4370 if isinstance(this, self.MODIFIABLES): 4371 for join in self._parse_joins(): 4372 this.append("joins", join) 4373 for lateral in iter(self._parse_lateral, None): 4374 this.append("laterals", lateral) 4375 4376 while True: 4377 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4378 modifier_token = self._curr 4379 4380 # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result 4381 # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union 4382 if ( 4383 modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH) 4384 and (limit := this.args.get("limit")) 4385 and limit.meta.get("top") 4386 ): 4387 break 4388 4389 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4390 key, expression = parser(self) 4391 4392 if expression: 4393 if this.args.get(key): 4394 self.raise_error( 4395 f"Found multiple '{modifier_token.text.upper()}' clauses", 4396 token=modifier_token, 4397 ) 4398 4399 this.set(key, expression) 4400 if key == "limit": 4401 offset = expression.args.get("offset") 4402 expression.set("offset", None) 4403 4404 if offset: 4405 if this.args.get("offset"): 4406 self.raise_error( 4407 "Found multiple 'OFFSET' clauses", token=modifier_token 4408 ) 4409 4410 offset = exp.Offset(expression=offset) 4411 this.set("offset", offset) 4412 4413 limit_by_expressions = expression.expressions 4414 expression.set("expressions", None) 4415 offset.set("expressions", limit_by_expressions) 4416 continue 4417 4418 if self._curr.text.upper() == "START": 4419 modifier_token = self._curr 4420 connect = self._parse_connect() 4421 if connect: 4422 if this.args.get("connect"): 4423 self.raise_error( 4424 "Found multiple 'START WITH' clauses", token=modifier_token 4425 ) 4426 4427 this.set("connect", connect) 4428 continue 4429 break 4430 4431 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4432 this = self._implicit_unnests_to_explicit(this) 4433 4434 return this 4435 4436 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4437 start = self._curr 4438 while self._curr: 4439 self._advance() 4440 4441 end = self._tokens[self._index - 1] 4442 return exp.Hint(expressions=[self._find_sql(start, end)]) 4443 4444 def _parse_hint_function_call(self) -> exp.Expr | None: 4445 return self._parse_function_call() 4446 4447 def _parse_hint_body(self) -> exp.Hint | None: 4448 start_index = self._index 4449 should_fallback_to_string = False 4450 4451 hints = [] 4452 try: 4453 for hint in iter( 4454 lambda: self._parse_csv( 4455 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4456 ), 4457 [], 4458 ): 4459 hints.extend(hint) 4460 except ParseError: 4461 should_fallback_to_string = True 4462 4463 if should_fallback_to_string or self._curr: 4464 self._retreat(start_index) 4465 return self._parse_hint_fallback_to_string() 4466 4467 return self.expression(exp.Hint(expressions=hints)) 4468 4469 def _parse_hint(self) -> exp.Hint | None: 4470 if self._match(TokenType.HINT) and self._prev_comments: 4471 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4472 4473 return None 4474 4475 def _parse_into(self) -> exp.Into | None: 4476 if not self._match(TokenType.INTO): 4477 return None 4478 4479 temp = self._match(TokenType.TEMPORARY) 4480 unlogged = self._match_text_seq("UNLOGGED") 4481 self._match(TokenType.TABLE) 4482 4483 return self.expression( 4484 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4485 ) 4486 4487 def _parse_from( 4488 self, 4489 joins: bool = False, 4490 skip_from_token: bool = False, 4491 consume_pipe: bool = False, 4492 ) -> exp.From | None: 4493 if not skip_from_token and not self._match(TokenType.FROM): 4494 return None 4495 4496 comments = self._prev_comments 4497 return self.expression( 4498 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4499 comments=comments, 4500 ) 4501 4502 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4503 return self.expression( 4504 exp.MatchRecognizeMeasure( 4505 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4506 this=self._parse_expression(), 4507 ) 4508 ) 4509 4510 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4511 if not self._match(TokenType.MATCH_RECOGNIZE): 4512 return None 4513 4514 self._match_l_paren() 4515 4516 partition = self._parse_partition_by() 4517 order = self._parse_order() 4518 4519 measures = ( 4520 self._parse_csv(self._parse_match_recognize_measure) 4521 if self._match_text_seq("MEASURES") 4522 else None 4523 ) 4524 4525 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4526 rows = exp.var("ONE ROW PER MATCH") 4527 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4528 text = "ALL ROWS PER MATCH" 4529 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4530 text += " SHOW EMPTY MATCHES" 4531 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4532 text += " OMIT EMPTY MATCHES" 4533 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4534 text += " WITH UNMATCHED ROWS" 4535 rows = exp.var(text) 4536 else: 4537 rows = None 4538 4539 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4540 text = "AFTER MATCH SKIP" 4541 if self._match_text_seq("PAST", "LAST", "ROW"): 4542 text += " PAST LAST ROW" 4543 elif self._match_text_seq("TO", "NEXT", "ROW"): 4544 text += " TO NEXT ROW" 4545 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4546 direction = self._prev.text.upper() 4547 pattern_var = self._advance_any() 4548 if not pattern_var: 4549 self.raise_error( 4550 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4551 ) 4552 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4553 after = exp.var(text) 4554 else: 4555 after = None 4556 4557 if self._match_text_seq("PATTERN"): 4558 self._match_l_paren() 4559 4560 if not self._curr: 4561 self.raise_error("Expecting )", self._curr) 4562 4563 paren = 1 4564 start = self._curr 4565 4566 while self._curr and paren > 0: 4567 if self._curr.token_type == TokenType.L_PAREN: 4568 paren += 1 4569 if self._curr.token_type == TokenType.R_PAREN: 4570 paren -= 1 4571 4572 end = self._prev 4573 self._advance() 4574 4575 if paren > 0: 4576 self.raise_error("Expecting )", self._curr) 4577 4578 pattern = exp.var(self._find_sql(start, end)) 4579 else: 4580 pattern = None 4581 4582 define = ( 4583 self._parse_csv(self._parse_name_as_expression) 4584 if self._match_text_seq("DEFINE") 4585 else None 4586 ) 4587 4588 self._match_r_paren() 4589 4590 return self.expression( 4591 exp.MatchRecognize( 4592 partition_by=partition, 4593 order=order, 4594 measures=measures, 4595 rows=rows, 4596 after=after, 4597 pattern=pattern, 4598 define=define, 4599 alias=self._parse_table_alias(), 4600 ) 4601 ) 4602 4603 def _parse_lateral(self) -> exp.Lateral | None: 4604 cross_apply: bool | None = None 4605 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4606 cross_apply = True 4607 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4608 cross_apply = False 4609 4610 if cross_apply is not None: 4611 this = self._parse_select(table=True) 4612 view = None 4613 outer = None 4614 elif self._match(TokenType.LATERAL): 4615 this = self._parse_select(table=True) 4616 view = self._match(TokenType.VIEW) 4617 outer = self._match(TokenType.OUTER) 4618 else: 4619 return None 4620 4621 if not this: 4622 this = ( 4623 self._parse_unnest() 4624 or self._parse_function() 4625 or self._parse_id_var(any_token=False) 4626 ) 4627 4628 while self._match(TokenType.DOT): 4629 this = exp.Dot( 4630 this=this, 4631 expression=self._parse_function() or self._parse_id_var(any_token=False), 4632 ) 4633 4634 ordinality: bool | None = None 4635 4636 if view: 4637 table = self._parse_id_var(any_token=False) 4638 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4639 table_alias: exp.TableAlias | None = self.expression( 4640 exp.TableAlias(this=table, columns=columns) 4641 ) 4642 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4643 # We move the alias from the lateral's child node to the lateral itself 4644 table_alias = this.args["alias"].pop() 4645 else: 4646 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4647 table_alias = self._parse_table_alias() 4648 4649 return self.expression( 4650 exp.Lateral( 4651 this=this, 4652 view=view, 4653 outer=outer, 4654 alias=table_alias, 4655 cross_apply=cross_apply, 4656 ordinality=ordinality, 4657 ) 4658 ) 4659 4660 def _parse_stream(self) -> exp.Stream | None: 4661 index = self._index 4662 if self._match(TokenType.STREAM): 4663 if this := self._try_parse(self._parse_table): 4664 return self.expression(exp.Stream(this=this)) 4665 self._retreat(index) 4666 return None 4667 4668 def _parse_join_parts( 4669 self, 4670 ) -> tuple[Token | None, Token | None, Token | None]: 4671 return ( 4672 self._prev if self._match_set(self.JOIN_METHODS) else None, 4673 self._prev if self._match_set(self.JOIN_SIDES) else None, 4674 self._prev if self._match_set(self.JOIN_KINDS) else None, 4675 ) 4676 4677 def _parse_using_identifiers(self) -> list[exp.Expr]: 4678 def _parse_column_as_identifier() -> exp.Expr | None: 4679 this = self._parse_column() 4680 if isinstance(this, exp.Column): 4681 return this.this 4682 return this 4683 4684 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4685 4686 def _parse_join( 4687 self, 4688 skip_join_token: bool = False, 4689 parse_bracket: bool = False, 4690 alias_tokens: t.Collection[TokenType] | None = None, 4691 ) -> exp.Join | None: 4692 if self._match(TokenType.COMMA): 4693 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4694 cross_join = self.expression(exp.Join(this=table)) if table else None 4695 4696 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4697 cross_join.set("kind", "CROSS") 4698 4699 return cross_join 4700 4701 index = self._index 4702 method, side, kind = self._parse_join_parts() 4703 directed = self._match_text_seq("DIRECTED") 4704 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4705 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4706 join_comments = self._prev_comments 4707 4708 if not skip_join_token and not join: 4709 self._retreat(index) 4710 kind = None 4711 method = None 4712 side = None 4713 4714 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4715 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4716 4717 if not skip_join_token and not join and not outer_apply and not cross_apply: 4718 return None 4719 4720 kwargs: dict[str, t.Any] = { 4721 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4722 } 4723 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4724 kwargs["expressions"] = self._parse_csv( 4725 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4726 ) 4727 4728 if method: 4729 kwargs["method"] = method.text.upper() 4730 if side: 4731 kwargs["side"] = side.text.upper() 4732 if kind: 4733 kwargs["kind"] = kind.text.upper() 4734 if hint: 4735 kwargs["hint"] = hint 4736 4737 if self._match(TokenType.MATCH_CONDITION): 4738 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4739 4740 if self._match(TokenType.ON): 4741 kwargs["on"] = self._parse_disjunction() 4742 elif self._match(TokenType.USING): 4743 kwargs["using"] = self._parse_using_identifiers() 4744 elif ( 4745 not method 4746 and not (outer_apply or cross_apply) 4747 and not isinstance(kwargs["this"], exp.Unnest) 4748 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4749 ): 4750 index = self._index 4751 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4752 4753 if joins and self._match(TokenType.ON): 4754 kwargs["on"] = self._parse_disjunction() 4755 elif joins and self._match(TokenType.USING): 4756 kwargs["using"] = self._parse_using_identifiers() 4757 else: 4758 joins = None 4759 self._retreat(index) 4760 4761 kwargs["this"].set("joins", joins if joins else None) 4762 4763 kwargs["pivots"] = self._parse_pivots() 4764 4765 comments = [c for token in (method, side, kind) if token for c in token.comments] 4766 comments = (join_comments or []) + comments 4767 4768 if ( 4769 self.ADD_JOIN_ON_TRUE 4770 and not kwargs.get("on") 4771 and not kwargs.get("using") 4772 and not kwargs.get("method") 4773 and kwargs.get("kind") in (None, "INNER", "OUTER") 4774 ): 4775 kwargs["on"] = exp.true() 4776 4777 if directed: 4778 kwargs["directed"] = directed 4779 4780 return self.expression(exp.Join(**kwargs), comments=comments) 4781 4782 def _parse_opclass(self) -> exp.Expr | None: 4783 this = self._parse_disjunction() 4784 4785 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4786 return this 4787 4788 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4789 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4790 4791 return this 4792 4793 def _parse_index_params(self) -> exp.IndexParameters: 4794 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4795 4796 if self._match(TokenType.L_PAREN, advance=False): 4797 columns = self._parse_wrapped_csv(self._parse_with_operator) 4798 else: 4799 columns = None 4800 4801 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4802 partition_by = self._parse_partition_by() 4803 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4804 tablespace = ( 4805 self._parse_var(any_token=True) 4806 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4807 else None 4808 ) 4809 where = self._parse_where() 4810 4811 on = self._parse_field() if self._match(TokenType.ON) else None 4812 4813 return self.expression( 4814 exp.IndexParameters( 4815 using=using, 4816 columns=columns, 4817 include=include, 4818 partition_by=partition_by, 4819 where=where, 4820 with_storage=with_storage, 4821 tablespace=tablespace, 4822 on=on, 4823 ) 4824 ) 4825 4826 def _parse_index( 4827 self, index: exp.Expr | None = None, anonymous: bool = False 4828 ) -> exp.Index | None: 4829 if index or anonymous: 4830 unique = None 4831 primary = None 4832 amp = None 4833 4834 self._match(TokenType.ON) 4835 self._match(TokenType.TABLE) # hive 4836 table = self._parse_table_parts(schema=True) 4837 else: 4838 unique = self._match(TokenType.UNIQUE) 4839 primary = self._match_text_seq("PRIMARY") 4840 amp = self._match_text_seq("AMP") 4841 4842 if not self._match(TokenType.INDEX): 4843 return None 4844 4845 index = self._parse_id_var() 4846 table = None 4847 4848 params = self._parse_index_params() 4849 4850 return self.expression( 4851 exp.Index( 4852 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4853 ) 4854 ) 4855 4856 def _parse_table_hints(self) -> list[exp.Expr] | None: 4857 hints: list[exp.Expr] = [] 4858 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4859 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4860 hints.append( 4861 self.expression( 4862 exp.WithTableHint( 4863 expressions=self._parse_csv( 4864 lambda: self._parse_function() or self._parse_var(any_token=True) 4865 ) 4866 ) 4867 ) 4868 ) 4869 self._match_r_paren() 4870 else: 4871 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4872 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4873 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4874 4875 self._match_set((TokenType.INDEX, TokenType.KEY)) 4876 if self._match(TokenType.FOR): 4877 hint.set("target", self._advance_any() and self._prev.text.upper()) 4878 4879 hint.set("expressions", self._parse_wrapped_id_vars()) 4880 hints.append(hint) 4881 4882 return hints or None 4883 4884 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 4885 return ( 4886 (not schema and self._parse_function(optional_parens=False)) 4887 or self._parse_id_var(any_token=False) 4888 or self._parse_string_as_identifier() 4889 or self._parse_placeholder() 4890 ) 4891 4892 def _parse_table_parts_fast(self) -> exp.Table | None: 4893 index = self._index 4894 parts: list[exp.Identifier] | None = None 4895 all_comments: list[str] | None = None 4896 4897 while self._match_set(self.IDENTIFIER_TOKENS): 4898 token = self._prev 4899 comments = self._prev_comments 4900 4901 has_dot = self._match(TokenType.DOT) 4902 curr_tt = self._curr.token_type 4903 4904 if not has_dot: 4905 if curr_tt in self.TABLE_POSTFIX_TOKENS: 4906 self._retreat(index) 4907 return None 4908 elif curr_tt not in self.IDENTIFIER_TOKENS: 4909 self._retreat(index) 4910 return None 4911 4912 if parts is None: 4913 parts = [] 4914 4915 if comments: 4916 if all_comments is None: 4917 all_comments = [] 4918 all_comments.extend(comments) 4919 self._prev_comments = [] 4920 4921 parts.append( 4922 self.expression( 4923 exp.Identifier( 4924 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 4925 ), 4926 token, 4927 ) 4928 ) 4929 4930 if not has_dot: 4931 break 4932 4933 if parts is None: 4934 return None 4935 4936 n = len(parts) 4937 4938 if n == 1: 4939 table: exp.Table = exp.Table(this=parts[0]) 4940 elif n == 2: 4941 table = exp.Table(this=parts[1], db=parts[0]) 4942 elif n >= 3: 4943 this: exp.Identifier | exp.Dot = parts[2] 4944 for i in range(3, n): 4945 this = exp.Dot(this=this, expression=parts[i]) 4946 4947 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 4948 4949 if table is None: 4950 self._retreat(index) 4951 elif all_comments: 4952 table.add_comments(all_comments) 4953 return table 4954 4955 def _parse_table_parts( 4956 self, 4957 schema: bool = False, 4958 is_db_reference: bool = False, 4959 wildcard: bool = False, 4960 fast: bool = False, 4961 ) -> exp.Table | exp.Dot | None: 4962 if fast: 4963 return self._parse_table_parts_fast() 4964 4965 catalog: exp.Expr | str | None = None 4966 db: exp.Expr | str | None = None 4967 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 4968 4969 while self._match(TokenType.DOT): 4970 if catalog: 4971 # This allows nesting the table in arbitrarily many dot expressions if needed 4972 table = self.expression( 4973 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 4974 ) 4975 else: 4976 catalog = db 4977 db = table 4978 # "" used for tsql FROM a..b case 4979 table = self._parse_table_part(schema=schema) or "" 4980 4981 if ( 4982 wildcard 4983 and self._is_connected() 4984 and (isinstance(table, exp.Identifier) or not table) 4985 and self._match(TokenType.STAR) 4986 ): 4987 if isinstance(table, exp.Identifier): 4988 table.args["this"] += "*" 4989 else: 4990 table = exp.Identifier(this="*") 4991 4992 if is_db_reference: 4993 catalog = db 4994 db = table 4995 table = None 4996 4997 if not table and not is_db_reference: 4998 self.raise_error(f"Expected table name but got {self._curr}") 4999 if not db and is_db_reference: 5000 self.raise_error(f"Expected database name but got {self._curr}") 5001 5002 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 5003 5004 # Bubble up comments from identifier parts to the Table 5005 comments = [] 5006 for part in table.parts: 5007 if part_comments := part.pop_comments(): 5008 comments.extend(part_comments) 5009 if comments: 5010 table.add_comments(comments) 5011 5012 changes = self._parse_changes() 5013 if changes: 5014 table.set("changes", changes) 5015 5016 at_before = self._parse_historical_data() 5017 if at_before: 5018 table.set("when", at_before) 5019 5020 pivots = self._parse_pivots() 5021 if pivots: 5022 table.set("pivots", pivots) 5023 5024 return table 5025 5026 def _parse_table( 5027 self, 5028 schema: bool = False, 5029 joins: bool = False, 5030 alias_tokens: t.Collection[TokenType] | None = None, 5031 parse_bracket: bool = False, 5032 is_db_reference: bool = False, 5033 parse_partition: bool = False, 5034 consume_pipe: bool = False, 5035 ) -> exp.Expr | None: 5036 if not schema and not is_db_reference and not consume_pipe and not joins: 5037 index = self._index 5038 table = self._parse_table_parts(fast=True) 5039 5040 if table is not None: 5041 curr_tt = self._curr.token_type 5042 next_tt = self._next.token_type 5043 5044 fast_terminators = self.TABLE_TERMINATORS 5045 5046 # only return the table if we're sure there are no other operators 5047 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5048 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5049 return table 5050 5051 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5052 5053 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5054 if alias := self._parse_table_alias( 5055 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5056 ): 5057 table.set("alias", alias) 5058 5059 if self._curr.token_type in fast_terminators: 5060 return table 5061 5062 self._retreat(index) 5063 5064 if stream := self._parse_stream(): 5065 return stream 5066 5067 if lateral := self._parse_lateral(): 5068 return lateral 5069 5070 if unnest := self._parse_unnest(): 5071 return unnest 5072 5073 if values := self._parse_derived_table_values(): 5074 return values 5075 5076 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5077 if not subquery.args.get("pivots"): 5078 subquery.set("pivots", self._parse_pivots()) 5079 if joins: 5080 for join in self._parse_joins(): 5081 subquery.append("joins", join) 5082 return subquery 5083 5084 bracket = parse_bracket and self._parse_bracket(None) 5085 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5086 5087 rows_from_tables = ( 5088 self._parse_wrapped_csv(self._parse_table) 5089 if self._match_text_seq("ROWS", "FROM") 5090 else None 5091 ) 5092 rows_from = ( 5093 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5094 ) 5095 5096 only = self._match(TokenType.ONLY) 5097 5098 this = t.cast( 5099 exp.Expr, 5100 bracket 5101 or rows_from 5102 or self._parse_bracket( 5103 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5104 ), 5105 ) 5106 5107 if only: 5108 this.set("only", only) 5109 5110 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5111 self._match(TokenType.STAR) 5112 5113 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5114 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5115 this.set("partition", self._parse_partition()) 5116 5117 if schema: 5118 return self._parse_schema(this=this) 5119 5120 if self.dialect.ALIAS_POST_VERSION: 5121 this.set("version", self._parse_version()) 5122 5123 if self.dialect.ALIAS_POST_TABLESAMPLE: 5124 this.set("sample", self._parse_table_sample()) 5125 5126 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5127 if alias: 5128 this.set("alias", alias) 5129 5130 # DuckDB requires the time-travel clause to come after the alias, e.g. 5131 # SELECT * FROM t AS a AT (VERSION => 1) 5132 if isinstance(this, exp.Table) and not this.args.get("when"): 5133 this.set("when", self._parse_historical_data()) 5134 5135 if self._match(TokenType.INDEXED_BY): 5136 this.set("indexed", self._parse_table_parts()) 5137 elif self._match_text_seq("NOT", "INDEXED"): 5138 this.set("indexed", False) 5139 5140 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5141 return self.expression( 5142 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5143 ) 5144 5145 this.set("hints", self._parse_table_hints()) 5146 5147 if not this.args.get("pivots"): 5148 this.set("pivots", self._parse_pivots()) 5149 5150 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5151 this.set("sample", self._parse_table_sample()) 5152 5153 if not self.dialect.ALIAS_POST_VERSION: 5154 this.set("version", self._parse_version()) 5155 5156 if joins: 5157 for join in self._parse_joins(alias_tokens=alias_tokens): 5158 this.append("joins", join) 5159 5160 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5161 this.set("ordinality", True) 5162 this.set("alias", self._parse_table_alias()) 5163 5164 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5165 # hoist the table args onto the latter and return it instead 5166 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5167 table_from_rows = this.this 5168 for arg in exp.TableFromRows.arg_types: 5169 if arg != "this": 5170 table_from_rows.set(arg, this.args.get(arg)) 5171 5172 this = table_from_rows 5173 5174 return this 5175 5176 def _parse_version(self) -> exp.Version | None: 5177 for phrase, this in self.VERSION_PHRASES.items(): 5178 if self._match_text_seq(*phrase): 5179 break 5180 else: 5181 return None 5182 5183 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5184 kind = self._prev.text.upper() 5185 start = self._parse_bitwise() 5186 self._match_texts(("TO", "AND")) 5187 end = self._parse_bitwise() 5188 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5189 elif self._match_text_seq("CONTAINED", "IN"): 5190 kind = "CONTAINED IN" 5191 expression = self.expression( 5192 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5193 ) 5194 elif self._match(TokenType.ALL): 5195 kind = "ALL" 5196 expression = None 5197 else: 5198 self._match_text_seq("AS", "OF") 5199 kind = "AS OF" 5200 expression = self._parse_type() 5201 5202 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5203 5204 def _parse_historical_data(self) -> exp.HistoricalData | None: 5205 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5206 index = self._index 5207 historical_data = None 5208 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5209 this = self._prev.text.upper() 5210 kind = ( 5211 self._match(TokenType.L_PAREN) 5212 and self._match_texts(self.HISTORICAL_DATA_KIND) 5213 and self._prev.text.upper() 5214 ) 5215 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5216 5217 if expression: 5218 self._match_r_paren() 5219 historical_data = self.expression( 5220 exp.HistoricalData(this=this, kind=kind, expression=expression) 5221 ) 5222 else: 5223 self._retreat(index) 5224 5225 return historical_data 5226 5227 def _parse_changes(self) -> exp.Changes | None: 5228 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5229 return None 5230 5231 information = self._parse_var(any_token=True) 5232 self._match_r_paren() 5233 5234 return self.expression( 5235 exp.Changes( 5236 information=information, 5237 at_before=self._parse_historical_data(), 5238 end=self._parse_historical_data(), 5239 ) 5240 ) 5241 5242 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5243 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5244 return None 5245 5246 self._advance() 5247 5248 expressions = self._parse_wrapped_csv(self._parse_equality) 5249 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5250 5251 alias = self._parse_table_alias() if with_alias else None 5252 5253 if alias: 5254 if self.dialect.UNNEST_COLUMN_ONLY: 5255 if alias.args.get("columns"): 5256 self.raise_error("Unexpected extra column alias in unnest.") 5257 5258 alias.set("columns", [alias.this]) 5259 alias.set("this", None) 5260 5261 columns = alias.args.get("columns") or [] 5262 if offset and len(expressions) < len(columns): 5263 offset = columns.pop() 5264 5265 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5266 self._match(TokenType.ALIAS) 5267 offset = self._parse_id_var( 5268 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5269 ) or exp.to_identifier("offset") 5270 5271 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5272 5273 def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None: 5274 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5275 if not is_derived and not ( 5276 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5277 self._match_text_seq("VALUES") 5278 or self._match_text_seq("FORMAT", "VALUES") 5279 # MySQL accepts VALUE as a synonym for VALUES in INSERT statements. 5280 or (allow_value_synonym and self._match_text_seq("VALUE")) 5281 ): 5282 return None 5283 5284 expressions = self._parse_csv(self._parse_value) 5285 alias = self._parse_table_alias() 5286 5287 if is_derived: 5288 self._match_r_paren() 5289 5290 return self.expression( 5291 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5292 ) 5293 5294 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5295 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5296 as_modifier and self._match_text_seq("USING", "SAMPLE") 5297 ): 5298 return None 5299 5300 bucket_numerator = None 5301 bucket_denominator = None 5302 bucket_field = None 5303 percent = None 5304 size = None 5305 seed = None 5306 5307 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5308 matched_l_paren = self._match(TokenType.L_PAREN) 5309 5310 if self.TABLESAMPLE_CSV: 5311 num = None 5312 expressions = self._parse_csv(self._parse_primary) 5313 else: 5314 expressions = None 5315 num = ( 5316 self._parse_factor(parse_mod=False) 5317 if self._match(TokenType.NUMBER, advance=False) 5318 else self._parse_primary() or self._parse_placeholder() 5319 ) 5320 5321 if self._match_text_seq("BUCKET"): 5322 bucket_numerator = self._parse_number() 5323 self._match_text_seq("OUT", "OF") 5324 bucket_denominator = bucket_denominator = self._parse_number() 5325 self._match(TokenType.ON) 5326 bucket_field = self._parse_field() 5327 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5328 percent = num 5329 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5330 size = num 5331 else: 5332 percent = num 5333 5334 if matched_l_paren: 5335 self._match_r_paren() 5336 5337 if self._match(TokenType.L_PAREN): 5338 method = self._parse_var(upper=True) 5339 seed = self._match(TokenType.COMMA) and self._parse_number() 5340 self._match_r_paren() 5341 elif self._match_texts(("SEED", "REPEATABLE")): 5342 seed = self._parse_wrapped(self._parse_number) 5343 5344 if not method and self.DEFAULT_SAMPLING_METHOD: 5345 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5346 5347 return self.expression( 5348 exp.TableSample( 5349 expressions=expressions, 5350 method=method, 5351 bucket_numerator=bucket_numerator, 5352 bucket_denominator=bucket_denominator, 5353 bucket_field=bucket_field, 5354 percent=percent, 5355 size=size, 5356 seed=seed, 5357 ) 5358 ) 5359 5360 def _parse_pivots(self) -> list[exp.Pivot] | None: 5361 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5362 return None 5363 return list(iter(self._parse_pivot, None)) or None 5364 5365 def _parse_joins( 5366 self, alias_tokens: t.Collection[TokenType] | None = None 5367 ) -> t.Iterator[exp.Join]: 5368 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5369 5370 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5371 if not self._match(TokenType.INTO): 5372 return None 5373 5374 return self.expression( 5375 exp.UnpivotColumns( 5376 this=self._match_text_seq("NAME") and self._parse_column(), 5377 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5378 ) 5379 ) 5380 5381 # https://duckdb.org/docs/sql/statements/pivot 5382 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5383 def _parse_on() -> exp.Expr | None: 5384 this = self._parse_bitwise() 5385 5386 if self._match(TokenType.IN): 5387 # PIVOT ... ON col IN (row_val1, row_val2) 5388 return self._parse_in(this) 5389 if self._match(TokenType.ALIAS, advance=False): 5390 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5391 return self._parse_alias(this) 5392 5393 return this 5394 5395 this = self._parse_table() 5396 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5397 into = self._parse_unpivot_columns() 5398 using = self._match(TokenType.USING) and self._parse_csv( 5399 lambda: self._parse_alias(self._parse_column()) 5400 ) 5401 group = self._parse_group() 5402 5403 return self.expression( 5404 exp.Pivot( 5405 this=this, 5406 expressions=expressions, 5407 using=using, 5408 group=group, 5409 unpivot=is_unpivot, 5410 into=into, 5411 ) 5412 ) 5413 5414 def _parse_pivot_in(self) -> exp.In: 5415 def _parse_aliased_expression() -> exp.Expr | None: 5416 this = self._parse_select_or_expression() 5417 5418 self._match(TokenType.ALIAS) 5419 alias = self._parse_bitwise() 5420 if alias: 5421 if isinstance(alias, exp.Column) and not alias.db: 5422 alias = alias.this 5423 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5424 5425 return this 5426 5427 value = self._parse_column() 5428 5429 if not self._match(TokenType.IN): 5430 self.raise_error("Expecting IN") 5431 5432 if self._match(TokenType.L_PAREN): 5433 if self._match(TokenType.ANY): 5434 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5435 else: 5436 exprs = self._parse_csv(_parse_aliased_expression) 5437 self._match_r_paren() 5438 return self.expression(exp.In(this=value, expressions=exprs)) 5439 5440 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5441 5442 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5443 func = self._parse_function() 5444 if not func: 5445 if self._prev.token_type == TokenType.COMMA: 5446 return None 5447 self.raise_error("Expecting an aggregation function in PIVOT") 5448 5449 return self._parse_alias(func) 5450 5451 def _parse_pivot(self) -> exp.Pivot | None: 5452 index = self._index 5453 include_nulls = None 5454 5455 if self._match(TokenType.PIVOT): 5456 unpivot = False 5457 elif self._match(TokenType.UNPIVOT): 5458 unpivot = True 5459 5460 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5461 if self._match_text_seq("INCLUDE", "NULLS"): 5462 include_nulls = True 5463 elif self._match_text_seq("EXCLUDE", "NULLS"): 5464 include_nulls = False 5465 else: 5466 return None 5467 5468 expressions = [] 5469 5470 if not self._match(TokenType.L_PAREN): 5471 self._retreat(index) 5472 return None 5473 5474 if unpivot: 5475 expressions = self._parse_csv(self._parse_column) 5476 else: 5477 expressions = self._parse_csv(self._parse_pivot_aggregation) 5478 5479 if not expressions: 5480 self.raise_error("Failed to parse PIVOT's aggregation list") 5481 5482 if not self._match(TokenType.FOR): 5483 self.raise_error("Expecting FOR") 5484 5485 fields = [] 5486 while True: 5487 field = self._try_parse(self._parse_pivot_in) 5488 if not field: 5489 break 5490 fields.append(field) 5491 5492 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5493 self._parse_bitwise 5494 ) 5495 5496 group = self._parse_group() 5497 5498 self._match_r_paren() 5499 5500 pivot = self.expression( 5501 exp.Pivot( 5502 expressions=expressions, 5503 fields=fields, 5504 unpivot=unpivot, 5505 include_nulls=include_nulls, 5506 default_on_null=default_on_null, 5507 group=group, 5508 ) 5509 ) 5510 5511 if unpivot: 5512 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5513 for pivot_field in pivot.fields: 5514 if isinstance(pivot_field, exp.In): 5515 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5516 5517 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5518 5519 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5520 pivot.set("alias", self._parse_table_alias()) 5521 5522 if not unpivot: 5523 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5524 5525 columns: list[exp.Expr] = [] 5526 all_fields = [] 5527 for pivot_field in pivot.fields: 5528 pivot_field_expressions = pivot_field.expressions 5529 5530 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5531 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5532 continue 5533 5534 all_fields.append( 5535 [ 5536 # An explicit `<field> AS <alias>` names the output column directly, 5537 # so it wins over the dialect's string-identifying convention 5538 fld.sql() 5539 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5540 else fld.alias_or_name 5541 for fld in pivot_field_expressions 5542 ] 5543 ) 5544 5545 if all_fields: 5546 if names: 5547 all_fields.append(names) 5548 5549 # Generate all possible combinations of the pivot columns 5550 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5551 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5552 for fld_parts_tuple in itertools.product(*all_fields): 5553 fld_parts = list(fld_parts_tuple) 5554 5555 if names and self.PREFIXED_PIVOT_COLUMNS: 5556 # Move the "name" to the front of the list 5557 fld_parts.insert(0, fld_parts.pop(-1)) 5558 5559 columns.append(exp.to_identifier("_".join(fld_parts))) 5560 5561 pivot.set("columns", columns) 5562 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5563 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5564 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5565 5566 return pivot 5567 5568 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5569 return [agg.alias for agg in aggregations if agg.alias] 5570 5571 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5572 if not skip_where_token and not self._match(TokenType.PREWHERE): 5573 return None 5574 5575 comments = self._prev_comments 5576 return self.expression( 5577 exp.PreWhere(this=self._parse_disjunction()), 5578 comments=comments, 5579 ) 5580 5581 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5582 if not skip_where_token and not self._match(TokenType.WHERE): 5583 return None 5584 5585 comments = self._prev_comments 5586 return self.expression( 5587 exp.Where(this=self._parse_disjunction()), 5588 comments=comments, 5589 ) 5590 5591 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5592 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5593 return None 5594 comments = self._prev_comments 5595 5596 elements: dict[str, t.Any] = defaultdict(list) 5597 5598 if self._match(TokenType.ALL): 5599 elements["all"] = True 5600 elif self._match(TokenType.DISTINCT): 5601 elements["all"] = False 5602 5603 while True: 5604 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5605 # which are also valid identifiers 5606 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5607 break 5608 5609 elements["expressions"].extend( 5610 self._parse_csv( 5611 lambda: ( 5612 self._parse_grouping_sets() 5613 or self._parse_cube_or_rollup() 5614 or self._parse_disjunction() 5615 ) 5616 ) 5617 ) 5618 5619 before_with_index = self._index 5620 5621 if self._match(TokenType.WITH) and ( 5622 cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True) 5623 ): 5624 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5625 elements[key].append(cube_or_rollup) 5626 elif grouping_sets := self._parse_grouping_sets(): 5627 # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...) 5628 elements["grouping_sets"].append(grouping_sets) 5629 break 5630 elif self._match_text_seq("TOTALS"): 5631 elements["totals"] = True # type: ignore 5632 5633 if before_with_index <= self._index <= before_with_index + 1: 5634 self._retreat(before_with_index) 5635 break 5636 5637 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5638 5639 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5640 if self._match(TokenType.CUBE): 5641 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5642 elif self._match(TokenType.ROLLUP): 5643 kind = exp.Rollup 5644 else: 5645 return None 5646 5647 return self.expression( 5648 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5649 ) 5650 5651 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5652 if self._match(TokenType.GROUPING_SETS): 5653 return self.expression( 5654 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5655 ) 5656 return None 5657 5658 def _parse_grouping_set(self) -> exp.Expr | None: 5659 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5660 5661 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5662 if not skip_having_token and not self._match(TokenType.HAVING): 5663 return None 5664 comments = self._prev_comments 5665 return self.expression( 5666 exp.Having(this=self._parse_disjunction()), 5667 comments=comments, 5668 ) 5669 5670 def _parse_qualify(self) -> exp.Qualify | None: 5671 if not self._match(TokenType.QUALIFY): 5672 return None 5673 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5674 5675 def _parse_connect_with_prior(self) -> exp.Expr | None: 5676 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5677 exp.Prior(this=self._parse_bitwise()) 5678 ) 5679 connect = self._parse_disjunction() 5680 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5681 return connect 5682 5683 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5684 if skip_start_token: 5685 start = None 5686 elif self._match_text_seq("START", "WITH"): 5687 start = self._parse_disjunction() 5688 else: 5689 return None 5690 5691 self._match(TokenType.CONNECT_BY) 5692 nocycle = self._match_text_seq("NOCYCLE") 5693 connect = self._parse_connect_with_prior() 5694 5695 if not start and self._match_text_seq("START", "WITH"): 5696 start = self._parse_disjunction() 5697 5698 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5699 5700 def _parse_name_as_expression(self) -> exp.Expr | None: 5701 this = self._parse_id_var(any_token=True) 5702 if self._match(TokenType.ALIAS): 5703 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5704 return this 5705 5706 def _parse_interpolate(self) -> list[exp.Expr] | None: 5707 if self._match_text_seq("INTERPOLATE"): 5708 return self._parse_wrapped_csv(self._parse_name_as_expression) 5709 return None 5710 5711 def _parse_order( 5712 self, this: exp.Expr | None = None, skip_order_token: bool = False 5713 ) -> exp.Expr | None: 5714 siblings = None 5715 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5716 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5717 return this 5718 5719 siblings = True 5720 5721 comments = self._prev_comments 5722 return self.expression( 5723 exp.Order( 5724 this=this, 5725 expressions=self._parse_csv(self._parse_ordered), 5726 siblings=siblings, 5727 ), 5728 comments=comments, 5729 ) 5730 5731 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5732 if not self._match(token): 5733 return None 5734 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5735 5736 def _parse_ordered( 5737 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5738 ) -> exp.Ordered | None: 5739 this = parse_method() if parse_method else self._parse_disjunction() 5740 if not this: 5741 return None 5742 5743 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5744 this = exp.var("ALL") 5745 5746 asc = self._match(TokenType.ASC) 5747 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5748 5749 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5750 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5751 5752 nulls_first = is_nulls_first or False 5753 explicitly_null_ordered = is_nulls_first or is_nulls_last 5754 5755 if ( 5756 not explicitly_null_ordered 5757 and ( 5758 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5759 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5760 ) 5761 and self.dialect.NULL_ORDERING != "nulls_are_last" 5762 ): 5763 nulls_first = True 5764 5765 if self._match_text_seq("WITH", "FILL"): 5766 with_fill = self.expression( 5767 exp.WithFill( 5768 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5769 to=self._match_text_seq("TO") and self._parse_bitwise(), 5770 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5771 interpolate=self._parse_interpolate(), 5772 ) 5773 ) 5774 else: 5775 with_fill = None 5776 5777 return self.expression( 5778 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5779 ) 5780 5781 def _parse_limit_options(self) -> exp.LimitOptions | None: 5782 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5783 rows = self._match_texts(("ROW", "ROWS")) 5784 self._match_text_seq("ONLY") 5785 with_ties = self._match_text_seq("WITH", "TIES") 5786 5787 if not (percent or rows or with_ties): 5788 return None 5789 5790 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5791 5792 def _parse_limit( 5793 self, 5794 this: exp.Expr | None = None, 5795 top: bool = False, 5796 skip_limit_token: bool = False, 5797 ) -> exp.Expr | None: 5798 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5799 comments = self._prev_comments 5800 if top: 5801 limit_paren = self._match(TokenType.L_PAREN) 5802 expression = ( 5803 self._parse_term() or self._parse_select() 5804 if limit_paren 5805 else self._parse_number() 5806 ) 5807 5808 if limit_paren: 5809 self._match_r_paren() 5810 5811 else: 5812 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5813 return this 5814 5815 expression = self._parse_term(parse_mod=False) 5816 limit_options = self._parse_limit_options() 5817 5818 if self._match(TokenType.COMMA): 5819 offset = expression 5820 expression = self._parse_term() 5821 else: 5822 offset = None 5823 5824 limit_exp = self.expression( 5825 exp.Limit( 5826 this=this, 5827 expression=expression, 5828 offset=offset, 5829 limit_options=limit_options, 5830 expressions=self._parse_limit_by(), 5831 ), 5832 comments=comments, 5833 ) 5834 5835 if top: 5836 limit_exp.meta["top"] = True 5837 5838 return limit_exp 5839 5840 if self._match(TokenType.FETCH): 5841 direction = ( 5842 self._prev.text.upper() 5843 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5844 else "FIRST" 5845 ) 5846 5847 count = ( 5848 None 5849 if self._match_texts(("ROW", "ROWS"), advance=False) 5850 else self._parse_field(tokens=self.FETCH_TOKENS) 5851 ) 5852 5853 return self.expression( 5854 exp.Fetch( 5855 direction=direction, count=count, limit_options=self._parse_limit_options() 5856 ) 5857 ) 5858 5859 return this 5860 5861 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5862 if not self._match(TokenType.OFFSET): 5863 return this 5864 5865 count = self._parse_term() 5866 self._match_texts(("ROW", "ROWS")) 5867 5868 return self.expression( 5869 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5870 ) 5871 5872 def _can_parse_limit_or_offset(self) -> bool: 5873 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5874 return False 5875 5876 index = self._index 5877 result = bool( 5878 self._try_parse(self._parse_limit, retreat=True) 5879 or self._try_parse(self._parse_offset, retreat=True) 5880 ) 5881 self._retreat(index) 5882 5883 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 5884 if self._next.token_type == TokenType.MATCH_CONDITION: 5885 result = False 5886 5887 return result 5888 5889 def _can_parse_named_window(self) -> bool: 5890 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 5891 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 5892 if not self._match(TokenType.WINDOW, advance=False): 5893 return False 5894 5895 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 5896 if name is None or name.token_type not in self.ID_VAR_TOKENS: 5897 return False 5898 5899 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 5900 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 5901 return False 5902 5903 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 5904 return body is not None and body.token_type == TokenType.L_PAREN 5905 5906 def _parse_limit_by(self) -> list[exp.Expr] | None: 5907 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 5908 5909 def _parse_locks(self) -> list[exp.Lock]: 5910 locks = [] 5911 while True: 5912 update, key = None, None 5913 if self._match_text_seq("FOR", "UPDATE"): 5914 update = True 5915 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 5916 "LOCK", "IN", "SHARE", "MODE" 5917 ): 5918 update = False 5919 elif self._match_text_seq("FOR", "KEY", "SHARE"): 5920 update, key = False, True 5921 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 5922 update, key = True, True 5923 else: 5924 break 5925 5926 expressions = None 5927 if self._match_text_seq("OF"): 5928 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 5929 5930 wait: bool | exp.Expr | None = None 5931 if self._match_text_seq("NOWAIT"): 5932 wait = True 5933 elif self._match_text_seq("WAIT"): 5934 wait = self._parse_primary() 5935 elif self._match_text_seq("SKIP", "LOCKED"): 5936 wait = False 5937 5938 locks.append( 5939 self.expression( 5940 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 5941 ) 5942 ) 5943 5944 return locks 5945 5946 def parse_set_operation( 5947 self, this: exp.Expr | None, consume_pipe: bool = False 5948 ) -> exp.Expr | None: 5949 start = self._index 5950 _, side_token, kind_token = self._parse_join_parts() 5951 5952 side = side_token.text if side_token else None 5953 kind = kind_token.text if kind_token else None 5954 5955 if not self._match_set(self.SET_OPERATIONS): 5956 self._retreat(start) 5957 return None 5958 5959 token_type = self._prev.token_type 5960 5961 if token_type == TokenType.UNION: 5962 operation: type[exp.SetOperation] = exp.Union 5963 elif token_type == TokenType.EXCEPT: 5964 operation = exp.Except 5965 else: 5966 operation = exp.Intersect 5967 5968 comments = self._prev.comments 5969 5970 if self._match(TokenType.DISTINCT): 5971 distinct: bool | None = True 5972 elif self._match(TokenType.ALL): 5973 distinct = False 5974 else: 5975 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5976 if distinct is None: 5977 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5978 5979 by_name = ( 5980 self._match_text_seq("BY", "NAME") 5981 or self._match_text_seq("STRICT", "CORRESPONDING") 5982 or None 5983 ) 5984 if self._match_text_seq("CORRESPONDING"): 5985 by_name = True 5986 if not side and not kind: 5987 kind = "INNER" 5988 5989 on_column_list = None 5990 if by_name and self._match_texts(("ON", "BY")): 5991 on_column_list = self._parse_wrapped_csv(self._parse_column) 5992 5993 expression = self._parse_select( 5994 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5995 ) 5996 5997 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5998 # in _parse_cte and so that alias pushdown can reach into set operation branches 5999 if isinstance(this, exp.Values): 6000 this = self._values_to_select(this) 6001 if isinstance(expression, exp.Values): 6002 expression = self._values_to_select(expression) 6003 6004 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 6005 subquery = this.this 6006 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 6007 subquery.add_comments(this.pop_comments()) 6008 this = subquery 6009 6010 return self.expression( 6011 operation( 6012 this=this, 6013 distinct=distinct, 6014 by_name=by_name, 6015 expression=expression, 6016 side=side, 6017 kind=kind, 6018 on=on_column_list, 6019 ), 6020 comments=comments, 6021 ) 6022 6023 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 6024 while this: 6025 setop = self.parse_set_operation(this) 6026 if not setop: 6027 break 6028 this = setop 6029 6030 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6031 expression = this.expression 6032 6033 if expression: 6034 for arg in self.SET_OP_MODIFIERS: 6035 expr = expression.args.get(arg) 6036 if expr and not (arg == "limit" and expr.meta.get("top")): 6037 expression.set(arg, None) 6038 this.set(arg, expr) 6039 6040 # A trailing LIMIT/FETCH can coexist with TOP on the final operand. 6041 if self._curr.token_type in (TokenType.LIMIT, TokenType.FETCH): 6042 this = self._parse_query_modifiers(this) 6043 6044 return this 6045 6046 def _parse_expression(self) -> exp.Expr | None: 6047 return self._parse_alias(self._parse_assignment()) 6048 6049 def _parse_assignment(self) -> exp.Expr | None: 6050 this = self._parse_disjunction() 6051 if not this and self._next.token_type in self.ASSIGNMENT: 6052 # This allows us to parse <non-identifier token> := <expr> 6053 this = exp.column( 6054 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6055 ) 6056 6057 while self._match_set(self.ASSIGNMENT): 6058 if isinstance(this, exp.Column) and len(this.parts) == 1: 6059 this = this.this 6060 6061 comments = self._prev_comments 6062 this = self.expression( 6063 self.ASSIGNMENT[self._prev.token_type]( 6064 this=this, expression=self._parse_assignment() 6065 ), 6066 comments=comments, 6067 ) 6068 6069 return this 6070 6071 def _parse_disjunction(self) -> exp.Expr | None: 6072 this = self._parse_conjunction() 6073 while self._match_set(self.DISJUNCTION): 6074 comments = self._prev_comments 6075 this = self.expression( 6076 self.DISJUNCTION[self._prev.token_type]( 6077 this=this, expression=self._parse_conjunction() 6078 ), 6079 comments=comments, 6080 ) 6081 return this 6082 6083 def _parse_conjunction(self) -> exp.Expr | None: 6084 this = self._parse_equality() 6085 while self._match_set(self.CONJUNCTION): 6086 comments = self._prev_comments 6087 this = self.expression( 6088 self.CONJUNCTION[self._prev.token_type]( 6089 this=this, expression=self._parse_equality() 6090 ), 6091 comments=comments, 6092 ) 6093 return this 6094 6095 def _parse_equality(self) -> exp.Expr | None: 6096 this = self._parse_comparison() 6097 while self._match_set(self.EQUALITY): 6098 comments = self._prev_comments 6099 this = self.expression( 6100 self.EQUALITY[self._prev.token_type]( 6101 this=this, expression=self._parse_comparison() 6102 ), 6103 comments=comments, 6104 ) 6105 return this 6106 6107 def _parse_comparison(self) -> exp.Expr | None: 6108 this = self._parse_range() 6109 while self._match_set(self.COMPARISON): 6110 comments = self._prev_comments 6111 this = self.expression( 6112 self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()), 6113 comments=comments, 6114 ) 6115 return this 6116 6117 def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6118 this = this or self._parse_bitwise() 6119 6120 while True: 6121 negate = self._match(TokenType.NOT) 6122 if self._match_set(self.RANGE_PARSERS): 6123 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6124 if not expression: 6125 return this 6126 6127 this = expression 6128 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6129 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6130 elif self._match(TokenType.NOTNULL): 6131 # Postgres supports ISNULL and NOTNULL for conditions. 6132 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6133 if self.dialect.NORMALIZE_NOT_NULL: 6134 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6135 this = self.expression(exp.Not(this=this)) 6136 else: 6137 this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True)) 6138 else: 6139 if negate: 6140 self._retreat(self._index - 1) 6141 break 6142 6143 if negate: 6144 this = self._negate_range(this) 6145 if self._curr and ( 6146 self._curr.token_type == TokenType.NOT 6147 or self._curr.token_type in self.RANGE_PARSERS 6148 ): 6149 this = self.expression(exp.Paren(this=this)) 6150 6151 return this 6152 6153 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6154 if not this: 6155 return this 6156 6157 expression = this.this if isinstance(this, exp.Escape) else this 6158 if isinstance(expression, (exp.Like, exp.ILike)): 6159 expression.set("negate", True) 6160 return this 6161 6162 return self.expression(exp.Not(this=this)) 6163 6164 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6165 index = self._index - 1 6166 negate = self._match(TokenType.NOT) 6167 6168 if self._match_text_seq("DISTINCT", "FROM"): 6169 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6170 return self.expression(klass(this=this, expression=self._parse_bitwise())) 6171 6172 if self._match(TokenType.JSON): 6173 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6174 6175 if self._match_text_seq("WITH"): 6176 _with = True 6177 elif self._match_text_seq("WITHOUT"): 6178 _with = False 6179 else: 6180 _with = None 6181 6182 unique = self._match(TokenType.UNIQUE) 6183 self._match_text_seq("KEYS") 6184 expression: exp.Expr | None = self.expression( 6185 exp.JSON(this=kind, with_=_with, unique=unique) 6186 ) 6187 else: 6188 expression = self._parse_null() or self._parse_bitwise() 6189 if not expression: 6190 self._retreat(index) 6191 return None 6192 6193 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6194 this = self.expression(exp.Is(this=this, expression=expression, negate=True)) 6195 else: 6196 this = self.expression(exp.Is(this=this, expression=expression)) 6197 this = self.expression(exp.Not(this=this)) if negate else this 6198 6199 return self._parse_column_ops(this) 6200 6201 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6202 unnest = self._parse_unnest(with_alias=False) 6203 if unnest: 6204 this = self.expression(exp.In(this=this, unnest=unnest)) 6205 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6206 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6207 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6208 6209 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6210 this = self.expression( 6211 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6212 ) 6213 else: 6214 this = self.expression(exp.In(this=this, expressions=expressions)) 6215 6216 if matched_l_paren: 6217 self._match_r_paren(this) 6218 elif not self._match(TokenType.R_BRACKET, expression=this): 6219 self.raise_error("Expecting ]") 6220 else: 6221 this = self.expression(exp.In(this=this, field=self._parse_column())) 6222 6223 return this 6224 6225 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6226 symmetric = None 6227 if self._match_text_seq("SYMMETRIC"): 6228 symmetric = True 6229 elif self._match_text_seq("ASYMMETRIC"): 6230 symmetric = False 6231 6232 low = self._parse_bitwise() 6233 self._match(TokenType.AND) 6234 high = self._parse_bitwise() 6235 6236 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6237 6238 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6239 if not self._match(TokenType.ESCAPE): 6240 return this 6241 return self.expression( 6242 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6243 ) 6244 6245 def _parse_interval_span( 6246 self, this: exp.Expr, parse_function_unit: bool = True 6247 ) -> exp.Interval: 6248 # handle day-time format interval span with omitted units: 6249 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6250 interval_span_units_omitted = None 6251 if ( 6252 this 6253 and this.is_string 6254 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6255 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6256 ): 6257 index = self._index 6258 6259 # Var "TO" Var 6260 first_unit = self._parse_var(any_token=True, upper=True) 6261 second_unit = None 6262 if first_unit and self._match_text_seq("TO"): 6263 second_unit = self._parse_var(any_token=True, upper=True) 6264 6265 interval_span_units_omitted = not (first_unit and second_unit) 6266 6267 self._retreat(index) 6268 6269 unit_index = self._index 6270 if interval_span_units_omitted: 6271 unit = None 6272 else: 6273 # Only attempt to parse a unit if the current token can actually be one, so that a 6274 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6275 is_unit = self._curr is not None and ( 6276 self._curr.token_type == TokenType.VAR 6277 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6278 ) 6279 unit = self._parse_function() if parse_function_unit and is_unit else None 6280 if not unit and is_unit: 6281 unit = self._parse_var(any_token=True, upper=True) 6282 6283 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6284 # each INTERVAL expression into this canonical form so it's easy to transpile 6285 if this and this.is_number: 6286 try: 6287 this = exp.Literal.string(this.to_py()) 6288 except ValueError: 6289 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6290 elif this and this.is_string: 6291 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6292 if parts and unit: 6293 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6294 unit = None 6295 self._retreat(unit_index) 6296 6297 if len(parts) == 1: 6298 this = exp.Literal.string(parts[0][0]) 6299 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6300 6301 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6302 unit = self.expression( 6303 exp.IntervalSpan( 6304 this=unit, 6305 expression=self._parse_function() 6306 or self._parse_var(any_token=True, upper=True), 6307 ) 6308 ) 6309 6310 return self.expression(exp.Interval(this=this, unit=unit)) 6311 6312 def _parse_interval( 6313 self, require_interval: bool = True, parse_function_unit: bool = True 6314 ) -> exp.Add | exp.Interval | None: 6315 index = self._index 6316 6317 if not self._match(TokenType.INTERVAL) and require_interval: 6318 return None 6319 6320 if self._match(TokenType.STRING, advance=False): 6321 this = self._parse_primary() 6322 else: 6323 this = self._parse_term() 6324 6325 if not this or ( 6326 isinstance(this, exp.Column) 6327 and not this.table 6328 and not this.this.quoted 6329 and self._curr 6330 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6331 ): 6332 self._retreat(index) 6333 return None 6334 6335 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6336 6337 index = self._index 6338 self._match(TokenType.PLUS) 6339 6340 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6341 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6342 return self.expression( 6343 exp.Add( 6344 this=interval, 6345 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6346 ) 6347 ) 6348 6349 self._retreat(index) 6350 return interval 6351 6352 def _parse_bitwise(self) -> exp.Expr | None: 6353 this = self._parse_term() 6354 6355 while True: 6356 if self._match_set(self.BITWISE): 6357 this = self.expression( 6358 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6359 ) 6360 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6361 this = self.expression( 6362 exp.DPipe( 6363 this=this, 6364 expression=self._parse_term(), 6365 safe=not self.dialect.STRICT_STRING_CONCAT, 6366 ) 6367 ) 6368 elif self._match(TokenType.DQMARK): 6369 this = self.expression( 6370 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6371 ) 6372 elif self._match_pair(TokenType.LT, TokenType.LT): 6373 this = self.expression( 6374 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6375 ) 6376 elif self._match_pair(TokenType.GT, TokenType.GT): 6377 this = self.expression( 6378 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6379 ) 6380 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6381 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6382 else: 6383 break 6384 6385 return this 6386 6387 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6388 this = self._parse_factor(parse_mod=parse_mod) 6389 6390 while self._match_set(self.TERM): 6391 klass = self.TERM[self._prev.token_type] 6392 comments = self._prev_comments 6393 expression = self._parse_factor(parse_mod=parse_mod) 6394 6395 this = self.expression(klass(this=this, expression=expression), comments=comments) 6396 6397 if isinstance(this, exp.Collate): 6398 self._normalize_collate(this) 6399 6400 return this 6401 6402 def _normalize_collate(self, collate: exp.Collate) -> None: 6403 expr = collate.expression 6404 6405 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6406 # fallback to Identifier / Var 6407 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6408 ident = expr.this 6409 if isinstance(ident, exp.Identifier): 6410 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6411 6412 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6413 parse_method = self._parse_factor_operand 6414 this = self._parse_at_time_zone(parse_method()) 6415 6416 while self._match_set(self.FACTOR, advance=False): 6417 if not parse_mod and self._curr.token_type == TokenType.MOD: 6418 break 6419 6420 self._advance() 6421 klass = self.FACTOR[self._prev.token_type] 6422 comments = self._prev_comments 6423 expression = parse_method() 6424 6425 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6426 self._retreat(self._index - 1) 6427 return this 6428 6429 this = self.expression(klass(this=this, expression=expression), comments=comments) 6430 6431 if isinstance(this, exp.Div): 6432 this.set("typed", self.dialect.TYPED_DIVISION) 6433 this.set("safe", self.dialect.SAFE_DIVISION) 6434 6435 return this 6436 6437 def _parse_factor_operand(self) -> exp.Expr | None: 6438 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6439 6440 def _parse_exponent(self) -> exp.Expr | None: 6441 this = self._parse_unary() 6442 while self._match_set(self.EXPONENT): 6443 comments = self._prev_comments 6444 this = self.expression( 6445 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6446 comments=comments, 6447 ) 6448 return this 6449 6450 def _parse_unary(self) -> exp.Expr | None: 6451 if self._match_set(self.UNARY_PARSERS): 6452 return self.UNARY_PARSERS[self._prev.token_type](self) 6453 return self._parse_type() 6454 6455 def _parse_type( 6456 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6457 ) -> exp.Expr | None: 6458 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6459 return atom 6460 6461 if interval := parse_interval and self._parse_interval(): 6462 return self._parse_column_ops(interval) 6463 6464 index = self._index 6465 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6466 6467 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6468 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6469 if isinstance(data_type, exp.Cast): 6470 # This constructor can contain ops directly after it, for instance struct unnesting: 6471 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6472 return self._parse_column_ops(data_type) 6473 6474 if data_type: 6475 index2 = self._index 6476 this = self._parse_primary() 6477 6478 if isinstance(this, exp.Literal): 6479 literal = this.name 6480 this = self._parse_column_ops(this) 6481 6482 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6483 if parser: 6484 return parser(self, this, data_type) 6485 6486 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6487 if data_type.is_type(exp.DType.TIMESTAMP): 6488 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6489 elif data_type.is_type(exp.DType.TIME): 6490 data_type = exp.DType.TIMETZ.into_expr() 6491 6492 return self.expression(exp.Cast(this=this, to=data_type)) 6493 6494 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6495 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6496 # 6497 # If the index difference here is greater than 1, that means the parser itself must have 6498 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6499 # 6500 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6501 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6502 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6503 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6504 # 6505 # In these cases, we don't really want to return the converted type, but instead retreat 6506 # and try to parse a Column or Identifier in the section below. 6507 if data_type.expressions and index2 - index > 1: 6508 self._retreat(index2) 6509 return self._parse_column_ops(data_type) 6510 6511 self._retreat(index) 6512 6513 if fallback_to_identifier: 6514 return self._parse_id_var() 6515 6516 return self._parse_column() 6517 6518 def _parse_type_size(self) -> exp.DataTypeParam | None: 6519 this = self._parse_type() 6520 if not this: 6521 return None 6522 6523 if isinstance(this, exp.Column) and not this.table: 6524 this = exp.var(this.name.upper()) 6525 6526 return self.expression( 6527 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6528 ) 6529 6530 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6531 type_name = identifier.name 6532 6533 while self._match(TokenType.DOT): 6534 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6535 6536 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6537 6538 def _parse_types( 6539 self, 6540 check_func: bool = False, 6541 schema: bool = False, 6542 allow_identifiers: bool = True, 6543 with_collation: bool = False, 6544 ) -> exp.Expr | None: 6545 index = self._index 6546 this: exp.Expr | None = None 6547 6548 if self._match_set(self.TYPE_TOKENS): 6549 type_token = self._prev.token_type 6550 else: 6551 type_token = None 6552 identifier = allow_identifiers and self._parse_id_var( 6553 any_token=False, tokens=(TokenType.VAR,) 6554 ) 6555 if isinstance(identifier, exp.Identifier): 6556 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6557 this = exp.DataType.build(identifier, udt=True) 6558 else: 6559 try: 6560 tokens = self.dialect.tokenize(identifier.name) 6561 except TokenError: 6562 tokens = None 6563 6564 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6565 if len(tokens) > 1: 6566 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6567 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6568 this = self._parse_user_defined_type(identifier) 6569 else: 6570 self._retreat(self._index - 1) 6571 return None 6572 else: 6573 return None 6574 6575 if type_token == TokenType.PSEUDO_TYPE: 6576 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6577 6578 if type_token == TokenType.OBJECT_IDENTIFIER: 6579 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6580 6581 # https://materialize.com/docs/sql/types/map/ 6582 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6583 key_type = self._parse_types( 6584 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6585 ) 6586 if not self._match(TokenType.FARROW): 6587 self._retreat(index) 6588 return None 6589 6590 value_type = self._parse_types( 6591 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6592 ) 6593 if not self._match(TokenType.R_BRACKET): 6594 self._retreat(index) 6595 return None 6596 6597 return exp.DataType( 6598 this=exp.DType.MAP, 6599 expressions=[key_type, value_type], 6600 nested=True, 6601 ) 6602 6603 nested = type_token in self.NESTED_TYPE_TOKENS 6604 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6605 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6606 expressions = None 6607 maybe_func = False 6608 6609 if self._match(TokenType.L_PAREN): 6610 if is_struct: 6611 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6612 elif nested: 6613 expressions = self._parse_csv( 6614 lambda: self._parse_types( 6615 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6616 ) 6617 ) 6618 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6619 this = expressions[0] 6620 this.set("nullable", True) 6621 self._match_r_paren() 6622 return this 6623 elif type_token in self.ENUM_TYPE_TOKENS: 6624 expressions = self._parse_csv(self._parse_equality) 6625 elif type_token == TokenType.JSON: 6626 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6627 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6628 expressions = self._parse_csv(self._parse_json_type_arg) 6629 elif is_aggregate: 6630 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6631 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6632 ) 6633 if not func_or_ident: 6634 return None 6635 expressions = [func_or_ident] 6636 if self._match(TokenType.COMMA): 6637 expressions.extend( 6638 self._parse_csv( 6639 lambda: self._parse_types( 6640 check_func=check_func, 6641 schema=schema, 6642 allow_identifiers=allow_identifiers, 6643 ) 6644 ) 6645 ) 6646 else: 6647 expressions = self._parse_csv(self._parse_type_size) 6648 6649 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6650 if type_token == TokenType.VECTOR and len(expressions) == 2: 6651 expressions = self._parse_vector_expressions(expressions) 6652 6653 if not self._match(TokenType.R_PAREN): 6654 self._retreat(index) 6655 return None 6656 6657 maybe_func = True 6658 6659 values: list[exp.Expr] | None = None 6660 6661 if nested and self._match(TokenType.LT): 6662 if is_struct: 6663 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6664 else: 6665 expressions = self._parse_csv( 6666 lambda: self._parse_types( 6667 check_func=check_func, 6668 schema=schema, 6669 allow_identifiers=allow_identifiers, 6670 with_collation=True, 6671 ) 6672 ) 6673 6674 if not self._match(TokenType.GT): 6675 self.raise_error("Expecting >") 6676 6677 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6678 values = self._parse_csv(self._parse_disjunction) 6679 if not values and is_struct: 6680 values = None 6681 self._retreat(self._index - 1) 6682 else: 6683 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6684 6685 if type_token in self.TIMESTAMPS: 6686 if self._match_text_seq("WITH", "TIME", "ZONE"): 6687 maybe_func = False 6688 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6689 this = exp.DataType(this=tz_type, expressions=expressions) 6690 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6691 maybe_func = False 6692 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6693 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6694 maybe_func = False 6695 elif type_token == TokenType.INTERVAL: 6696 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6697 unit = self._parse_var(upper=True) 6698 if self._match_text_seq("TO"): 6699 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6700 6701 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6702 else: 6703 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6704 elif type_token == TokenType.VOID: 6705 this = exp.DataType(this=exp.DType.NULL) 6706 6707 if maybe_func and check_func: 6708 index2 = self._index 6709 peek = self._parse_string() 6710 6711 if not peek: 6712 self._retreat(index) 6713 return None 6714 6715 self._retreat(index2) 6716 6717 if not this: 6718 assert type_token is not None 6719 if self._match_text_seq("UNSIGNED"): 6720 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6721 if not unsigned_type_token: 6722 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6723 6724 type_token = unsigned_type_token or type_token 6725 6726 # NULLABLE without parentheses can be a column (Presto/Trino) 6727 if type_token == TokenType.NULLABLE and not expressions: 6728 self._retreat(index) 6729 return None 6730 6731 this = exp.DataType( 6732 this=exp.DType[type_token.name], 6733 expressions=expressions, 6734 nested=nested, 6735 ) 6736 6737 # Empty arrays/structs are allowed 6738 if values is not None: 6739 cls = exp.Struct if is_struct else exp.Array 6740 this = exp.cast(cls(expressions=values), this, copy=False) 6741 6742 elif expressions: 6743 this.set("expressions", expressions) 6744 6745 # https://materialize.com/docs/sql/types/list/#type-name 6746 while self._match(TokenType.LIST): 6747 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6748 6749 index = self._index 6750 6751 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6752 matched_array = self._match(TokenType.ARRAY) 6753 6754 while self._curr: 6755 datatype_token = self._prev.token_type 6756 matched_l_bracket = self._match(TokenType.L_BRACKET) 6757 6758 if (not matched_l_bracket and not matched_array) or ( 6759 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6760 ): 6761 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6762 # not to be confused with the fixed size array parsing 6763 break 6764 6765 matched_array = False 6766 values = self._parse_csv(self._parse_disjunction) or None 6767 if ( 6768 values 6769 and not schema 6770 and ( 6771 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6772 or datatype_token == TokenType.ARRAY 6773 or not self._match(TokenType.R_BRACKET, advance=False) 6774 ) 6775 ): 6776 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6777 # 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 6778 self._retreat(index) 6779 break 6780 6781 this = exp.DataType( 6782 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6783 ) 6784 self._match(TokenType.R_BRACKET) 6785 6786 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6787 converter = self.TYPE_CONVERTERS.get(this.this) 6788 if converter: 6789 this = converter(t.cast(exp.DataType, this)) 6790 6791 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6792 this.set("collate", self._parse_identifier() or self._parse_column()) 6793 6794 return this 6795 6796 def _parse_json_type_arg(self) -> exp.Expr | None: 6797 """Parse a single argument to ClickHouse's JSON type.""" 6798 6799 # SKIP col or SKIP REGEXP 'pattern' 6800 if self._match_text_seq("SKIP"): 6801 regexp = self._match(TokenType.RLIKE) 6802 arg = self._parse_column() 6803 if isinstance(arg, exp.Column): 6804 arg = arg.to_dot() 6805 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6806 6807 param_or_col = self._parse_column() 6808 if not isinstance(param_or_col, exp.Column): 6809 return None 6810 6811 # Parameter: name=value (e.g., max_dynamic_paths=2) 6812 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 6813 param = param_or_col.name 6814 value = self._parse_primary() 6815 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 6816 6817 # Column type hint: col_name Type 6818 col = param_or_col.to_dot() 6819 kind = self._parse_types(check_func=False, allow_identifiers=False) 6820 return self.expression(exp.ColumnDef(this=col, kind=kind)) 6821 6822 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 6823 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 6824 6825 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 6826 index = self._index 6827 6828 if ( 6829 self._curr 6830 and self._next 6831 and self._curr.token_type in self.TYPE_TOKENS 6832 and self._next.token_type in self.TYPE_TOKENS 6833 ): 6834 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 6835 # type token. Without this, the list will be parsed as a type and we'll eventually crash 6836 this = self._parse_id_var() 6837 else: 6838 this = ( 6839 self._parse_type(parse_interval=False, fallback_to_identifier=True) 6840 or self._parse_id_var() 6841 ) 6842 6843 self._match(TokenType.COLON) 6844 6845 if ( 6846 type_required 6847 and not isinstance(this, exp.DataType) 6848 and not self._match_set(self.TYPE_TOKENS, advance=False) 6849 ): 6850 self._retreat(index) 6851 return self._parse_types() 6852 6853 return self._parse_column_def(this) 6854 6855 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 6856 if not self._match_text_seq("AT", "TIME", "ZONE"): 6857 return this 6858 return self._parse_at_time_zone( 6859 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 6860 ) 6861 6862 def _parse_atom(self) -> exp.Expr | None: 6863 if ( 6864 self._curr.token_type in self.IDENTIFIER_TOKENS 6865 and (column := self._parse_column()) is not None 6866 ): 6867 return column 6868 6869 token = self._curr 6870 token_type = token.token_type 6871 6872 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 6873 return None 6874 6875 next_type = self._next.token_type 6876 6877 if ( 6878 next_type in self.COLUMN_OPERATORS 6879 or next_type in self.COLUMN_POSTFIX_TOKENS 6880 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 6881 ): 6882 return None 6883 6884 self._advance() 6885 return primary_parser(self, token) 6886 6887 def _parse_column(self) -> exp.Expr | None: 6888 column: exp.Expr | None = self._parse_column_parts_fast() 6889 if column is None: 6890 this = self._parse_column_reference() 6891 if not this: 6892 this = self._parse_bracket(this) 6893 column = self._parse_column_ops(this) if this else this 6894 6895 if column: 6896 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 6897 column.set("join_mark", self._match(TokenType.JOIN_MARKER)) 6898 if self.COLON_IS_VARIANT_EXTRACT: 6899 column = self._parse_colon_as_variant_extract(column) 6900 6901 return column 6902 6903 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 6904 """Fast path for simple column and dot references (a, a.b, ...). 6905 6906 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 6907 that nothing complex follows. If it does, retreats and returns None so 6908 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 6909 """ 6910 index = self._index 6911 parts: list[exp.Identifier] | None = None 6912 all_comments: list[str] | None = None 6913 6914 while self._match_set(self.IDENTIFIER_TOKENS): 6915 token = self._prev 6916 comments = self._prev_comments 6917 6918 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 6919 self._retreat(index) 6920 return None 6921 6922 has_dot = self._match(TokenType.DOT) 6923 curr_tt = self._curr.token_type 6924 6925 if not has_dot: 6926 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 6927 self._retreat(index) 6928 return None 6929 elif curr_tt not in self.IDENTIFIER_TOKENS: 6930 self._retreat(index) 6931 return None 6932 6933 if parts is None: 6934 parts = [] 6935 6936 if comments: 6937 if all_comments is None: 6938 all_comments = [] 6939 all_comments.extend(comments) 6940 self._prev_comments = [] 6941 6942 parts.append( 6943 self.expression( 6944 exp.Identifier( 6945 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 6946 ), 6947 token, 6948 ) 6949 ) 6950 6951 if not has_dot: 6952 break 6953 6954 if parts is None: 6955 return None 6956 6957 n = len(parts) 6958 6959 if n == 1: 6960 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 6961 elif n == 2: 6962 column = exp.Column(this=parts[1], table=parts[0]) 6963 elif n == 3: 6964 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 6965 else: 6966 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 6967 6968 for i in range(4, n): 6969 column = exp.Dot(this=column, expression=parts[i]) 6970 6971 if all_comments: 6972 column.add_comments(all_comments) 6973 6974 return column 6975 6976 def _parse_column_reference(self) -> exp.Expr | None: 6977 this = self._parse_field() 6978 if ( 6979 not this 6980 and self._match(TokenType.VALUES, advance=False) 6981 and self.VALUES_FOLLOWED_BY_PAREN 6982 and (not self._next or self._next.token_type != TokenType.L_PAREN) 6983 ): 6984 this = self._parse_id_var() 6985 6986 if isinstance(this, exp.Identifier): 6987 # We bubble up comments from the Identifier to the Column 6988 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 6989 6990 return this 6991 6992 def _build_json_extract( 6993 self, 6994 this: exp.Expr | None, 6995 path_parts: list[exp.JSONPathPart], 6996 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 6997 if len(path_parts) > 1: 6998 this = self.expression( 6999 exp.JSONExtract( 7000 this=this, 7001 expression=exp.JSONPath(expressions=path_parts), 7002 variant_extract=True, 7003 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 7004 ) 7005 ) 7006 path_parts = [exp.JSONPathRoot()] 7007 7008 return this, path_parts 7009 7010 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 7011 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 7012 7013 while self._match(TokenType.COLON): 7014 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 7015 this, path_parts = self._build_json_extract(this, path_parts) 7016 7017 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7018 7019 if key: 7020 quoted = isinstance(key, exp.Identifier) and key.quoted 7021 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 7022 7023 while True: 7024 if self._match(TokenType.DOT): 7025 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7026 7027 if next_key: 7028 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 7029 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 7030 elif self._match(TokenType.L_BRACKET): 7031 bracket_expr = self._parse_bracket_key_value() 7032 7033 if not self._match(TokenType.R_BRACKET): 7034 self.raise_error("Expected ]") 7035 7036 if bracket_expr: 7037 if bracket_expr.is_string: 7038 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7039 elif bracket_expr.is_star: 7040 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7041 elif bracket_expr.is_number: 7042 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7043 else: 7044 this, path_parts = self._build_json_extract(this, path_parts) 7045 7046 this = self.expression( 7047 exp.Bracket( 7048 this=this, expressions=[bracket_expr], json_access=True 7049 ), 7050 ) 7051 7052 elif self._match(TokenType.DCOLON): 7053 this, path_parts = self._build_json_extract(this, path_parts) 7054 7055 cast_type = self._parse_types() 7056 if cast_type: 7057 this = self.expression(exp.Cast(this=this, to=cast_type)) 7058 else: 7059 self.raise_error("Expected type after '::'") 7060 else: 7061 break 7062 7063 this, _ = self._build_json_extract(this, path_parts) 7064 7065 return this 7066 7067 def _parse_dcolon(self) -> exp.Expr | None: 7068 return self._parse_types() 7069 7070 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7071 while self._curr.token_type in self.BRACKETS: 7072 this = self._parse_bracket(this) 7073 7074 column_operators = self.COLUMN_OPERATORS 7075 cast_column_operators = self.CAST_COLUMN_OPERATORS 7076 while self._curr: 7077 op_token = self._curr.token_type 7078 7079 if op_token not in column_operators: 7080 break 7081 op = column_operators[op_token] 7082 self._advance() 7083 7084 if op_token in cast_column_operators: 7085 field = self._parse_dcolon() 7086 if not field: 7087 self.raise_error("Expected type") 7088 elif op and self._curr: 7089 field = self._parse_column_reference() or self._parse_bitwise() 7090 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7091 field = self._parse_column_ops(field) 7092 else: 7093 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7094 field = self._parse_field(any_token=True, anonymous_func=True) 7095 7096 # In t.true, t.null we should produce an Identifier node 7097 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7098 field = self.expression( 7099 exp.Identifier(this=self._prev.text), 7100 comments=field.comments, 7101 ) 7102 7103 # Function calls can be qualified, e.g., x.y.FOO() 7104 # This converts the final AST to a series of Dots leading to the function call 7105 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7106 if isinstance(field, (exp.Func, exp.Window)) and this: 7107 this = this.transform( 7108 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7109 ) 7110 7111 if op: 7112 this = op(self, this, field) 7113 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7114 this = self.expression( 7115 exp.Column( 7116 this=field, 7117 table=this.this, 7118 db=this.args.get("table"), 7119 catalog=this.args.get("db"), 7120 ), 7121 comments=this.comments, 7122 ) 7123 elif isinstance(field, exp.Window): 7124 # Move the exp.Dot's to the window's function 7125 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7126 field.set("this", window_func) 7127 this = field 7128 else: 7129 this = self.expression(exp.Dot(this=this, expression=field)) 7130 7131 if field and field.comments: 7132 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7133 7134 this = self._parse_bracket(this) 7135 7136 return this 7137 7138 def _parse_paren(self) -> exp.Expr | None: 7139 if not self._match(TokenType.L_PAREN): 7140 return None 7141 7142 comments = self._prev_comments 7143 query = self._parse_select() 7144 7145 if query: 7146 expressions = [query] 7147 else: 7148 expressions = self._parse_expressions() 7149 7150 this = seq_get(expressions, 0) 7151 7152 if not this and self._match(TokenType.R_PAREN, advance=False): 7153 this = self.expression(exp.Tuple()) 7154 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7155 this = self.expression(exp.Tuple(expressions=expressions)) 7156 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7157 this = self._parse_subquery(this=this, parse_alias=False) 7158 elif isinstance(this, (exp.Subquery, exp.Values)): 7159 this = self._parse_subquery( 7160 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7161 parse_alias=False, 7162 ) 7163 else: 7164 this = self.expression(exp.Paren(this=this)) 7165 7166 if this: 7167 this.add_comments(comments) 7168 7169 self._match_r_paren(expression=this) 7170 7171 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7172 return self._parse_window(this) 7173 7174 return this 7175 7176 def _parse_primary(self) -> exp.Expr | None: 7177 if self._match_set(self.PRIMARY_PARSERS): 7178 token_type = self._prev.token_type 7179 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7180 7181 if token_type == TokenType.STRING: 7182 expressions = [primary] 7183 while self._match(TokenType.STRING, advance=False): 7184 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7185 self.raise_error( 7186 "Adjacent string literals need to be separated by whitespace or comments" 7187 ) 7188 7189 self._advance() 7190 expressions.append(exp.Literal.string(self._prev.text)) 7191 7192 if len(expressions) > 1: 7193 return self.expression( 7194 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7195 ) 7196 7197 return primary 7198 7199 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7200 return exp.Literal.number(f"0.{self._prev.text}") 7201 7202 return self._parse_paren() 7203 7204 def _parse_field( 7205 self, 7206 any_token: bool = False, 7207 tokens: t.Collection[TokenType] | None = None, 7208 anonymous_func: bool = False, 7209 ) -> exp.Expr | None: 7210 after_dot = ( 7211 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7212 ) 7213 7214 if anonymous_func: 7215 field = ( 7216 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7217 or self._parse_primary() 7218 ) 7219 else: 7220 field = self._parse_primary() or self._parse_function( 7221 anonymous=anonymous_func, any_token=any_token 7222 ) 7223 7224 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7225 7226 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7227 name = field.name 7228 if self._is_connected() and self._parse_var(any_token=True): 7229 name += self._prev.text 7230 7231 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7232 7233 return field 7234 7235 def _parse_function( 7236 self, 7237 functions: dict[str, t.Callable] | None = None, 7238 anonymous: bool = False, 7239 optional_parens: bool = True, 7240 any_token: bool = False, 7241 ) -> exp.Expr | None: 7242 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7243 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7244 fn_syntax = False 7245 if ( 7246 self._match(TokenType.L_BRACE, advance=False) 7247 and self._next 7248 and self._next.text.upper() == "FN" 7249 ): 7250 self._advance(2) 7251 fn_syntax = True 7252 7253 func = self._parse_function_call( 7254 functions=functions, 7255 anonymous=anonymous, 7256 optional_parens=optional_parens, 7257 any_token=any_token, 7258 ) 7259 7260 if fn_syntax: 7261 self._match(TokenType.R_BRACE) 7262 7263 return func 7264 7265 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7266 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7267 7268 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7269 args = self._parse_function_args(alias=False) 7270 if not args: 7271 self.raise_error("Expected at least one argument") 7272 7273 # Wrapped so the connector keeps its precedence in the parent context 7274 return exp.Paren(this=connector(*args, copy=False)) 7275 7276 def _parse_function_call( 7277 self, 7278 functions: dict[str, t.Callable] | None = None, 7279 anonymous: bool = False, 7280 optional_parens: bool = True, 7281 any_token: bool = False, 7282 ) -> exp.Expr | None: 7283 if not self._curr: 7284 return None 7285 7286 comments = self._curr.comments 7287 prev = self._prev 7288 token = self._curr 7289 token_type = self._curr.token_type 7290 this: str | exp.Expr = self._curr.text 7291 upper = self._curr.text.upper() 7292 7293 after_dot = prev.token_type == TokenType.DOT 7294 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7295 if ( 7296 optional_parens 7297 and parser 7298 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7299 and not after_dot 7300 ): 7301 self._advance() 7302 return self._parse_window(parser(self)) 7303 7304 if self._next.token_type != TokenType.L_PAREN: 7305 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7306 self._advance() 7307 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7308 7309 return None 7310 7311 if any_token: 7312 if token_type in self.RESERVED_TOKENS: 7313 return None 7314 elif token_type not in self.FUNC_TOKENS: 7315 return None 7316 7317 self._advance(2) 7318 7319 parser = self.FUNCTION_PARSERS.get(upper) 7320 if parser and not anonymous: 7321 result = parser(self) 7322 else: 7323 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7324 7325 if subquery_predicate: 7326 expr = None 7327 if self._curr.token_type in self.SUBQUERY_TOKENS: 7328 expr = self._parse_select() 7329 self._match_r_paren() 7330 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7331 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7332 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7333 self._advance(-1) 7334 expr = self._parse_bitwise() 7335 7336 if expr: 7337 return self.expression(subquery_predicate(this=expr), comments=comments) 7338 7339 if functions is None: 7340 functions = self.FUNCTIONS 7341 7342 function = functions.get(upper) 7343 known_function = function and not anonymous 7344 7345 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7346 args = self._parse_function_args(alias) 7347 7348 post_func_comments = self._curr.comments if self._curr else None 7349 if known_function and post_func_comments: 7350 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7351 # call we'll construct it as exp.Anonymous, even if it's "known" 7352 if any( 7353 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7354 for comment in post_func_comments 7355 ): 7356 known_function = False 7357 7358 if alias and known_function: 7359 args = self._kv_to_prop_eq(args) 7360 7361 if known_function: 7362 func_builder = t.cast(t.Callable, function) 7363 7364 # mypyc compiled functions don't have __code__, so we use 7365 # try/except to check if func_builder accepts 'dialect'. 7366 try: 7367 func = func_builder(args) 7368 except TypeError: 7369 func = func_builder(args, dialect=self.dialect) 7370 7371 func = self.validate_expression(func, args) 7372 if self.dialect.PRESERVE_ORIGINAL_NAMES: 7373 func.meta["name"] = this 7374 7375 result = func 7376 else: 7377 if token_type == TokenType.IDENTIFIER: 7378 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7379 7380 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7381 7382 result = result.update_positions(token) 7383 7384 if isinstance(result, exp.Expr): 7385 result.add_comments(comments) 7386 7387 if parser: 7388 self._match(TokenType.R_PAREN, expression=result) 7389 else: 7390 self._match_r_paren(result) 7391 return self._parse_window(result) 7392 7393 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7394 return expression 7395 7396 def _kv_to_prop_eq( 7397 self, expressions: list[exp.Expr], parse_map: bool = False 7398 ) -> list[exp.Expr]: 7399 transformed = [] 7400 7401 for index, e in enumerate(expressions): 7402 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7403 if isinstance(e, exp.Alias): 7404 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7405 7406 if not isinstance(e, exp.PropertyEQ): 7407 e = self.expression( 7408 exp.PropertyEQ( 7409 this=e.this if parse_map else exp.to_identifier(e.this.name), 7410 expression=e.expression, 7411 ) 7412 ) 7413 7414 if isinstance(e.this, exp.Column): 7415 e.this.replace(e.this.this) 7416 else: 7417 e = self._to_prop_eq(e, index) 7418 7419 transformed.append(e) 7420 7421 return transformed 7422 7423 def _parse_function_properties(self) -> exp.Properties | None: 7424 # Skip the generic `key = value` fallback in _parse_property since this 7425 # runs post-AS where a function body like `name = expr` can be misread 7426 # as a property. 7427 properties = [] 7428 while True: 7429 if self._match_texts(self.PROPERTY_PARSERS): 7430 keyword = self._prev.text.upper() 7431 prop = self.PROPERTY_PARSERS[keyword](self) 7432 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7433 keyword = self._prev.text.upper() 7434 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7435 else: 7436 break 7437 if not prop: 7438 self.raise_error(f"Failed to parse property '{keyword}'") 7439 break 7440 for p in ensure_list(prop): 7441 properties.append(p) 7442 7443 return self.expression(exp.Properties(expressions=properties)) if properties else None 7444 7445 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7446 return self._parse_statement() 7447 7448 def _parse_function_parameter(self) -> exp.Expr | None: 7449 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7450 7451 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7452 this = self._parse_table_parts(schema=True) 7453 7454 if not self._match(TokenType.L_PAREN): 7455 return this 7456 7457 expressions = self._parse_csv(self._parse_function_parameter) 7458 self._match_r_paren() 7459 return self.expression( 7460 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7461 ) 7462 7463 def _parse_macro_overloads( 7464 self, 7465 this: exp.UserDefinedFunction, 7466 first_body: exp.Expr, 7467 first_is_table: bool = False, 7468 ) -> exp.MacroOverloads: 7469 overloads = [ 7470 self.expression( 7471 exp.MacroOverload( 7472 this=first_body, 7473 expressions=this.expressions or None, 7474 is_table=first_is_table, 7475 ) 7476 ) 7477 ] 7478 this.set("expressions", None) 7479 this.set("wrapped", False) 7480 7481 while self._match(TokenType.COMMA): 7482 if not self._match(TokenType.L_PAREN): 7483 break 7484 7485 params = self._parse_csv(self._parse_function_parameter) 7486 self._match_r_paren() 7487 7488 if not self._match(TokenType.ALIAS): 7489 break 7490 7491 is_table = self._match(TokenType.TABLE) 7492 body = self._parse_expression() 7493 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7494 overloads.append(self.expression(macro)) 7495 7496 return self.expression(exp.MacroOverloads(expressions=overloads)) 7497 7498 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7499 literal = self._parse_primary() 7500 if literal: 7501 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7502 7503 return self._identifier_expression(token) 7504 7505 def _parse_session_parameter(self) -> exp.SessionParameter: 7506 kind = None 7507 this = self._parse_id_var() or self._parse_primary() 7508 7509 if this and self._match(TokenType.DOT): 7510 kind = this.name 7511 this = self._parse_var() or self._parse_primary() 7512 7513 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7514 7515 def _parse_lambda_arg(self) -> exp.Expr | None: 7516 return self._parse_id_var() 7517 7518 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7519 next_token_type = self._next.token_type 7520 7521 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7522 if ( 7523 next_token_type in self.LAMBDA_ARG_TERMINATORS 7524 and (atom := self._parse_atom()) is not None 7525 ): 7526 return atom 7527 7528 index = self._index 7529 7530 if self._match(TokenType.L_PAREN): 7531 expressions = t.cast( 7532 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7533 ) 7534 7535 if not self._match(TokenType.R_PAREN): 7536 self._retreat(index) 7537 elif self._match_set(self.LAMBDAS): 7538 return self.LAMBDAS[self._prev.token_type](self, expressions) 7539 else: 7540 self._retreat(index) 7541 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7542 expressions = [self._parse_lambda_arg()] 7543 7544 if self._match_set(self.LAMBDAS): 7545 return self.LAMBDAS[self._prev.token_type](self, expressions) 7546 7547 self._retreat(index) 7548 7549 this: exp.Expr | None 7550 7551 if self._match(TokenType.DISTINCT): 7552 this = self.expression( 7553 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7554 ) 7555 else: 7556 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7557 this = self._parse_select_or_expression(alias=alias) 7558 7559 return self._parse_limit( 7560 self._parse_respect_or_ignore_nulls( 7561 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7562 ) 7563 ) 7564 7565 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7566 index = self._index 7567 if not self._match(TokenType.L_PAREN): 7568 return this 7569 7570 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7571 # expr can be of both types 7572 if self._match_set(self.SELECT_START_TOKENS): 7573 self._retreat(index) 7574 return this 7575 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7576 self._match_r_paren() 7577 return self.expression(exp.Schema(this=this, expressions=args)) 7578 7579 def _parse_field_def(self) -> exp.Expr | None: 7580 return self._parse_column_def(self._parse_field(any_token=True)) 7581 7582 def _parse_column_def( 7583 self, this: exp.Expr | None, computed_column: bool = True 7584 ) -> exp.Expr | None: 7585 # column defs are not really columns, they're identifiers 7586 if isinstance(this, exp.Column): 7587 this = this.this 7588 7589 if not computed_column: 7590 self._match(TokenType.ALIAS) 7591 7592 kind = self._parse_types(schema=True) 7593 7594 if self._match_text_seq("FOR", "ORDINALITY"): 7595 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7596 7597 constraints: list[exp.Expr] = [] 7598 7599 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7600 ("ALIAS", "MATERIALIZED") 7601 ): 7602 # Match storage before _parse_types so STORED is not treated as a data type 7603 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7604 persisted = self._prev.text.upper() == "MATERIALIZED" 7605 expression = self._parse_disjunction() 7606 if not persisted: 7607 if self._match_text_seq("PERSISTED"): 7608 persisted = True 7609 elif self._match_texts(("STORED", "VIRTUAL")): 7610 persisted = self._prev.text.upper() == "STORED" 7611 constraint_kind = exp.ComputedColumnConstraint( 7612 this=expression, 7613 persisted=persisted, 7614 data_type=exp.Var(this="AUTO") 7615 if self._match_text_seq("AUTO") 7616 else self._parse_types(), 7617 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7618 ) 7619 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7620 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7621 in_out_constraint = self.expression( 7622 exp.InOutColumnConstraint( 7623 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7624 ) 7625 ) 7626 constraints.append(in_out_constraint) 7627 kind = self._parse_types() 7628 elif ( 7629 kind 7630 and self._match(TokenType.ALIAS, advance=False) 7631 and ( 7632 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7633 or self._next.token_type == TokenType.L_PAREN 7634 ) 7635 ): 7636 self._advance() 7637 constraints.append( 7638 self.expression( 7639 exp.ColumnConstraint( 7640 kind=exp.ComputedColumnConstraint( 7641 this=self._parse_disjunction(), 7642 persisted=self._match_texts(("STORED", "VIRTUAL")) 7643 and self._prev.text.upper() == "STORED", 7644 ) 7645 ) 7646 ) 7647 ) 7648 7649 while True: 7650 constraint = self._parse_column_constraint() 7651 if not constraint: 7652 break 7653 constraints.append(constraint) 7654 7655 if not kind and not constraints: 7656 return this 7657 7658 position = None 7659 if self._match_texts(("FIRST", "AFTER")): 7660 pos = self._prev.text 7661 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7662 7663 return self.expression( 7664 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7665 ) 7666 7667 def _parse_auto_increment( 7668 self, 7669 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7670 start = None 7671 increment = None 7672 order = None 7673 7674 if self._match(TokenType.L_PAREN, advance=False): 7675 args = self._parse_wrapped_csv(self._parse_bitwise) 7676 start = seq_get(args, 0) 7677 increment = seq_get(args, 1) 7678 7679 # The remaining parts form an unordered bag and any of them can be omitted, in which 7680 # case the engine falls back to its own default, so they're parsed independently. 7681 while True: 7682 if self._match_text_seq("START"): 7683 start = self._parse_bitwise() 7684 elif self._match_text_seq("INCREMENT"): 7685 increment = self._parse_bitwise() 7686 elif self._match_text_seq("ORDER"): 7687 order = True 7688 elif self._match_text_seq("NOORDER"): 7689 order = False 7690 else: 7691 break 7692 7693 if start or increment or order is not None: 7694 return exp.GeneratedAsIdentityColumnConstraint( 7695 start=start, increment=increment, this=False, order=order 7696 ) 7697 7698 return exp.AutoIncrementColumnConstraint() 7699 7700 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7701 if not self._match(TokenType.L_PAREN, advance=False): 7702 return None 7703 7704 return self.expression( 7705 exp.CheckColumnConstraint( 7706 this=self._parse_wrapped(self._parse_assignment), 7707 enforced=self._match_text_seq("ENFORCED"), 7708 ) 7709 ) 7710 7711 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7712 if not self._match_text_seq("REFRESH"): 7713 self._retreat(self._index - 1) 7714 return None 7715 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7716 7717 def _parse_compress(self) -> exp.CompressColumnConstraint: 7718 if self._match(TokenType.L_PAREN, advance=False): 7719 return self.expression( 7720 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7721 ) 7722 7723 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7724 7725 def _parse_generated_as_identity( 7726 self, 7727 ) -> ( 7728 exp.GeneratedAsIdentityColumnConstraint 7729 | exp.ComputedColumnConstraint 7730 | exp.GeneratedAsRowColumnConstraint 7731 ): 7732 if self._match_text_seq("BY", "DEFAULT"): 7733 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7734 this = self.expression( 7735 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7736 ) 7737 else: 7738 self._match_text_seq("ALWAYS") 7739 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7740 7741 self._match(TokenType.ALIAS) 7742 7743 if self._match_text_seq("ROW"): 7744 start = self._match_text_seq("START") 7745 if not start: 7746 self._match(TokenType.END) 7747 hidden = self._match_text_seq("HIDDEN") 7748 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7749 7750 identity = self._match_text_seq("IDENTITY") 7751 7752 if self._match(TokenType.L_PAREN): 7753 if self._match_text_seq("START", "WITH"): 7754 this.set("start", self._parse_bitwise()) 7755 if self._match_text_seq("INCREMENT", "BY"): 7756 this.set("increment", self._parse_bitwise()) 7757 if self._match_text_seq("MINVALUE"): 7758 this.set("minvalue", self._parse_bitwise()) 7759 if self._match_text_seq("MAXVALUE"): 7760 this.set("maxvalue", self._parse_bitwise()) 7761 7762 if self._match_text_seq("CYCLE"): 7763 this.set("cycle", True) 7764 elif self._match_text_seq("NO", "CYCLE"): 7765 this.set("cycle", False) 7766 7767 if not identity: 7768 this.set("expression", self._parse_range()) 7769 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7770 args = self._parse_csv(self._parse_bitwise) 7771 this.set("start", seq_get(args, 0)) 7772 this.set("increment", seq_get(args, 1)) 7773 7774 self._match_r_paren() 7775 7776 return this 7777 7778 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7779 self._match_text_seq("LENGTH") 7780 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7781 7782 def _parse_not_constraint(self) -> exp.Expr | None: 7783 if self._match_text_seq("NULL"): 7784 return self.expression(exp.NotNullColumnConstraint()) 7785 if self._match_text_seq("CASESPECIFIC"): 7786 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7787 if self._match_text_seq("FOR", "REPLICATION"): 7788 return self.expression(exp.NotForReplicationColumnConstraint()) 7789 7790 # Unconsume the `NOT` token 7791 self._retreat(self._index - 1) 7792 return None 7793 7794 def _parse_column_constraint(self) -> exp.Expr | None: 7795 this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None 7796 7797 procedure_option_follows = ( 7798 self._match(TokenType.WITH, advance=False) 7799 and self._next 7800 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7801 ) 7802 7803 index = self._index 7804 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 7805 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 7806 if not constraint: 7807 self._retreat(index) 7808 return None 7809 7810 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 7811 7812 if self._match_text_seq("CHARACTER", "SET"): 7813 return self.expression( 7814 exp.ColumnConstraint( 7815 this=this, 7816 kind=self.expression( 7817 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 7818 ), 7819 ) 7820 ) 7821 7822 return this 7823 7824 def _parse_constraint(self) -> exp.Expr | None: 7825 if not self._match(TokenType.CONSTRAINT): 7826 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 7827 7828 return self.expression( 7829 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 7830 ) 7831 7832 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 7833 constraints = [] 7834 while True: 7835 constraint = self._parse_unnamed_constraint() or self._parse_function() 7836 if not constraint: 7837 break 7838 constraints.append(constraint) 7839 7840 return constraints 7841 7842 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 7843 index = self._index 7844 7845 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 7846 constraints or self.CONSTRAINT_PARSERS 7847 ): 7848 return None 7849 7850 constraint_key = self._prev.text.upper() 7851 if constraint_key not in self.CONSTRAINT_PARSERS: 7852 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 7853 7854 result = self.CONSTRAINT_PARSERS[constraint_key](self) 7855 if not result: 7856 self._retreat(index) 7857 7858 return result 7859 7860 def _parse_unique_key(self) -> exp.Expr | None: 7861 if ( 7862 self._curr 7863 and self._curr.token_type != TokenType.IDENTIFIER 7864 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 7865 ): 7866 return None 7867 return self._parse_id_var(any_token=False) 7868 7869 def _parse_unique(self) -> exp.UniqueColumnConstraint: 7870 self._match_texts(("KEY", "INDEX")) 7871 return self.expression( 7872 exp.UniqueColumnConstraint( 7873 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 7874 this=self._parse_schema(self._parse_unique_key()), 7875 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 7876 on_conflict=self._parse_on_conflict(), 7877 options=self._parse_key_constraint_options(), 7878 ) 7879 ) 7880 7881 def _parse_key_constraint_options(self) -> list[str]: 7882 options = [] 7883 while True: 7884 if not self._curr: 7885 break 7886 7887 if self._match(TokenType.ON): 7888 action = None 7889 on = self._advance_any() and self._prev.text 7890 7891 if self._match_text_seq("NO", "ACTION"): 7892 action = "NO ACTION" 7893 elif self._match_text_seq("CASCADE"): 7894 action = "CASCADE" 7895 elif self._match_text_seq("RESTRICT"): 7896 action = "RESTRICT" 7897 elif self._match_pair(TokenType.SET, TokenType.NULL): 7898 action = "SET NULL" 7899 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 7900 action = "SET DEFAULT" 7901 else: 7902 self.raise_error("Invalid key constraint") 7903 7904 options.append(f"ON {on} {action}") 7905 else: 7906 var = self._parse_var_from_options( 7907 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 7908 ) 7909 if not var: 7910 break 7911 options.append(var.name) 7912 7913 return options 7914 7915 def _parse_references(self, match: bool = True) -> exp.Reference | None: 7916 if match and not self._match(TokenType.REFERENCES): 7917 return None 7918 7919 expressions: list | None = None 7920 this = self._parse_table(schema=True) 7921 options = self._parse_key_constraint_options() 7922 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 7923 7924 def _parse_foreign_key(self) -> exp.ForeignKey: 7925 expressions = ( 7926 self._parse_wrapped_id_vars() 7927 if not self._match(TokenType.REFERENCES, advance=False) 7928 else None 7929 ) 7930 reference = self._parse_references() 7931 on_options = {} 7932 7933 while self._match(TokenType.ON): 7934 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 7935 self.raise_error("Expected DELETE or UPDATE") 7936 7937 kind = self._prev.text.lower() 7938 7939 if self._match_text_seq("NO", "ACTION"): 7940 action = "NO ACTION" 7941 elif self._match(TokenType.SET): 7942 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 7943 action = "SET " + self._prev.text.upper() 7944 else: 7945 self._advance() 7946 action = self._prev.text.upper() 7947 7948 on_options[kind] = action 7949 7950 return self.expression( 7951 exp.ForeignKey( 7952 expressions=expressions, 7953 reference=reference, 7954 options=self._parse_key_constraint_options(), 7955 **on_options, 7956 ) 7957 ) 7958 7959 def _parse_primary_key_part(self) -> exp.Expr | None: 7960 return self._parse_field() 7961 7962 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 7963 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 7964 self._retreat(self._index - 1) 7965 return None 7966 7967 id_vars = self._parse_wrapped_id_vars() 7968 return self.expression( 7969 exp.PeriodForSystemTimeConstraint( 7970 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 7971 ) 7972 ) 7973 7974 def _parse_primary_key( 7975 self, 7976 wrapped_optional: bool = False, 7977 in_props: bool = False, 7978 named_primary_key: bool = False, 7979 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 7980 desc = ( 7981 self._prev.token_type == TokenType.DESC 7982 if self._match_set((TokenType.ASC, TokenType.DESC)) 7983 else None 7984 ) 7985 7986 this = None 7987 if ( 7988 named_primary_key 7989 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 7990 and self._next 7991 and self._next.token_type == TokenType.L_PAREN 7992 ): 7993 this = self._parse_id_var() 7994 7995 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 7996 return self.expression( 7997 exp.PrimaryKeyColumnConstraint( 7998 desc=desc, options=self._parse_key_constraint_options() 7999 ) 8000 ) 8001 8002 expressions = self._parse_wrapped_csv( 8003 self._parse_primary_key_part, optional=wrapped_optional 8004 ) 8005 8006 return self.expression( 8007 exp.PrimaryKey( 8008 this=this, 8009 expressions=expressions, 8010 include=self._parse_index_params(), 8011 options=self._parse_key_constraint_options(), 8012 ) 8013 ) 8014 8015 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 8016 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 8017 8018 def _parse_odbc_datetime_literal(self) -> exp.Expr: 8019 """ 8020 Parses a datetime column in ODBC format. We parse the column into the corresponding 8021 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 8022 same as we did for `DATE('yyyy-mm-dd')`. 8023 8024 Reference: 8025 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 8026 """ 8027 self._match(TokenType.VAR) 8028 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 8029 expression = self.expression(exp_class(this=self._parse_string())) 8030 if not self._match(TokenType.R_BRACE): 8031 self.raise_error("Expected }") 8032 return expression 8033 8034 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 8035 if not self._match_set(self.BRACKETS): 8036 return this 8037 8038 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8039 map_token = seq_get(self._tokens, self._index - 2) 8040 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8041 else: 8042 parse_map = False 8043 8044 bracket_kind = self._prev.token_type 8045 if ( 8046 bracket_kind == TokenType.L_BRACE 8047 and self._curr 8048 and self._curr.token_type == TokenType.VAR 8049 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8050 ): 8051 return self._parse_odbc_datetime_literal() 8052 8053 expressions = self._parse_csv( 8054 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8055 ) 8056 8057 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8058 self.raise_error("Expected ]") 8059 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8060 self.raise_error("Expected }") 8061 8062 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8063 if bracket_kind == TokenType.L_BRACE: 8064 this = self.expression( 8065 exp.Struct( 8066 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8067 ) 8068 ) 8069 elif not this: 8070 this = build_array_constructor( 8071 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8072 ) 8073 else: 8074 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8075 if constructor_type: 8076 return build_array_constructor( 8077 constructor_type, 8078 args=expressions, 8079 bracket_kind=bracket_kind, 8080 dialect=self.dialect, 8081 ) 8082 8083 expressions = apply_index_offset( 8084 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8085 ) 8086 this = self.expression( 8087 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8088 ) 8089 8090 self._add_comments(this) 8091 return self._parse_bracket(this) 8092 8093 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8094 if not self._match(TokenType.COLON): 8095 return this 8096 8097 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8098 self._advance() 8099 end: exp.Expr | None = -exp.Literal.number("1") 8100 else: 8101 end = self._parse_assignment() 8102 step = self._parse_unary() if self._match(TokenType.COLON) else None 8103 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8104 8105 def _parse_case(self) -> exp.Expr | None: 8106 if self._match(TokenType.DOT, advance=False): 8107 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8108 self._retreat(self._index - 1) 8109 return None 8110 8111 ifs = [] 8112 default = None 8113 8114 comments = self._prev_comments 8115 expression = self._parse_disjunction() 8116 8117 while self._match(TokenType.WHEN): 8118 this = self._parse_disjunction() 8119 self._match(TokenType.THEN) 8120 then = self._parse_disjunction() 8121 ifs.append(self.expression(exp.If(this=this, true=then))) 8122 8123 if self._match(TokenType.ELSE): 8124 default = self._parse_disjunction() 8125 8126 if not self._match(TokenType.END): 8127 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8128 default = exp.column("interval") 8129 else: 8130 self.raise_error("Expected END after CASE", self._prev) 8131 8132 return self.expression( 8133 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8134 ) 8135 8136 def _parse_if(self) -> exp.Expr | None: 8137 if self._match(TokenType.L_PAREN): 8138 args = self._parse_csv( 8139 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8140 ) 8141 this = self.validate_expression(exp.If.from_arg_list(args), args) 8142 self._match_r_paren() 8143 else: 8144 index = self._index - 1 8145 8146 if self.NO_PAREN_IF_COMMANDS and index == 0: 8147 return self._parse_as_command(self._prev) 8148 8149 condition = self._parse_disjunction() 8150 8151 if not condition: 8152 self._retreat(index) 8153 return None 8154 8155 self._match(TokenType.THEN) 8156 true = self._parse_disjunction() 8157 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8158 self._match(TokenType.END) 8159 this = self.expression(exp.If(this=condition, true=true, false=false)) 8160 8161 return this 8162 8163 def _parse_next_value_for(self) -> exp.Expr | None: 8164 if not self._match_text_seq("VALUE", "FOR"): 8165 self._retreat(self._index - 1) 8166 return None 8167 8168 return self.expression( 8169 exp.NextValueFor( 8170 this=self._parse_column(), 8171 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8172 ) 8173 ) 8174 8175 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8176 this = self._parse_function() or self._parse_var_or_string(upper=True) 8177 8178 if self._match(TokenType.FROM): 8179 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8180 8181 if not self._match(TokenType.COMMA): 8182 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8183 8184 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8185 8186 def _parse_gap_fill(self) -> exp.GapFill: 8187 self._match(TokenType.TABLE) 8188 this = self._parse_table() 8189 8190 self._match(TokenType.COMMA) 8191 args = [this, *self._parse_csv(self._parse_lambda)] 8192 8193 gap_fill = exp.GapFill.from_arg_list(args) 8194 return self.validate_expression(gap_fill, args) 8195 8196 def _parse_char(self) -> exp.Chr: 8197 return self.expression( 8198 exp.Chr( 8199 expressions=self._parse_csv(self._parse_assignment), 8200 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8201 ) 8202 ) 8203 8204 def _parse_charset_name(self) -> exp.Expr | None: 8205 """ 8206 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8207 for specific name shapes override this. 8208 """ 8209 return self._parse_var( 8210 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8211 ) 8212 8213 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8214 this = self._parse_assignment() 8215 8216 if not self._match(TokenType.ALIAS): 8217 if self._match(TokenType.COMMA): 8218 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8219 8220 self.raise_error("Expected AS after CAST") 8221 8222 fmt = None 8223 to = self._parse_types(with_collation=True) 8224 8225 default = None 8226 if self._match(TokenType.DEFAULT): 8227 default = self._parse_bitwise() 8228 self._match_text_seq("ON", "CONVERSION", "ERROR") 8229 8230 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8231 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8232 fmt = self._parse_at_time_zone(fmt_string) 8233 8234 if not to: 8235 to = exp.DType.UNKNOWN.into_expr() 8236 if to.this in exp.DataType.TEMPORAL_TYPES: 8237 this = self.expression( 8238 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8239 this=this, 8240 format=exp.Literal.string( 8241 format_time( 8242 fmt_string.this if fmt_string else "", 8243 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8244 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8245 ) 8246 ), 8247 safe=safe, 8248 ) 8249 ) 8250 8251 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8252 this.set("zone", fmt.args["zone"]) 8253 return this 8254 elif not to: 8255 self.raise_error("Expected TYPE after CAST") 8256 elif isinstance(to, exp.Identifier): 8257 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8258 elif to.this == exp.DType.CHAR and ( 8259 self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET") 8260 ): 8261 to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string()) 8262 8263 return self.build_cast( 8264 strict=strict, 8265 this=this, 8266 to=to, 8267 format=fmt, 8268 safe=safe, 8269 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8270 default=default, 8271 ) 8272 8273 def _parse_string_agg(self) -> exp.GroupConcat: 8274 if self._match(TokenType.DISTINCT): 8275 args: list[exp.Expr | None] = [ 8276 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8277 ] 8278 if self._match(TokenType.COMMA): 8279 args.extend(self._parse_csv(self._parse_disjunction)) 8280 else: 8281 args = self._parse_csv(self._parse_disjunction) # type: ignore 8282 8283 if self._match_text_seq("ON", "OVERFLOW"): 8284 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8285 if self._match_text_seq("ERROR"): 8286 on_overflow: exp.Expr | None = exp.var("ERROR") 8287 else: 8288 self._match_text_seq("TRUNCATE") 8289 on_overflow = self.expression( 8290 exp.OverflowTruncateBehavior( 8291 this=self._parse_string(), 8292 with_count=( 8293 self._match_text_seq("WITH", "COUNT") 8294 or not self._match_text_seq("WITHOUT", "COUNT") 8295 ), 8296 ) 8297 ) 8298 else: 8299 on_overflow = None 8300 8301 index = self._index 8302 if not self._match(TokenType.R_PAREN) and args: 8303 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8304 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8305 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8306 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8307 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8308 8309 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8310 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8311 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8312 if not self._match_text_seq("WITHIN", "GROUP"): 8313 self._retreat(index) 8314 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8315 8316 # The corresponding match_r_paren will be called in parse_function (caller) 8317 self._match_l_paren() 8318 8319 return self.expression( 8320 exp.GroupConcat( 8321 this=self._parse_order(this=seq_get(args, 0)), 8322 separator=seq_get(args, 1), 8323 on_overflow=on_overflow, 8324 ) 8325 ) 8326 8327 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8328 this = self._parse_bitwise() 8329 8330 if self._match(TokenType.USING): 8331 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8332 elif self._match(TokenType.COMMA): 8333 to = self._parse_types() 8334 else: 8335 to = None 8336 8337 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8338 8339 def _parse_xml_element(self) -> exp.XMLElement: 8340 if self._match_text_seq("EVALNAME"): 8341 evalname = True 8342 this = self._parse_bitwise() 8343 else: 8344 evalname = None 8345 self._match_text_seq("NAME") 8346 this = self._parse_id_var() 8347 8348 return self.expression( 8349 exp.XMLElement( 8350 this=this, 8351 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8352 evalname=evalname, 8353 ) 8354 ) 8355 8356 def _parse_xml_table(self) -> exp.XMLTable: 8357 namespaces = None 8358 passing = None 8359 columns = None 8360 8361 if self._match_text_seq("XMLNAMESPACES", "("): 8362 namespaces = self._parse_xml_namespace() 8363 self._match_text_seq(")", ",") 8364 8365 this = self._parse_string() 8366 8367 if self._match_text_seq("PASSING"): 8368 # The BY VALUE keywords are optional and are provided for semantic clarity 8369 self._match_text_seq("BY", "VALUE") 8370 passing = self._parse_csv(self._parse_column) 8371 8372 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8373 8374 if self._match_text_seq("COLUMNS"): 8375 columns = self._parse_csv(self._parse_field_def) 8376 8377 return self.expression( 8378 exp.XMLTable( 8379 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8380 ) 8381 ) 8382 8383 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8384 namespaces = [] 8385 8386 while True: 8387 if self._match(TokenType.DEFAULT): 8388 uri = self._parse_string() 8389 else: 8390 uri = self._parse_alias(self._parse_string()) 8391 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8392 if not self._match(TokenType.COMMA): 8393 break 8394 8395 return namespaces 8396 8397 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8398 args = self._parse_csv(self._parse_disjunction) 8399 8400 if len(args) < 3: 8401 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8402 8403 return self.expression(exp.DecodeCase(expressions=args)) 8404 8405 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8406 self._match_text_seq("KEY") 8407 key = self._parse_column() 8408 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8409 self._match_text_seq("VALUE") 8410 value = self._parse_bitwise() 8411 8412 if not key and not value: 8413 return None 8414 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8415 8416 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8417 if not this or not self._match_text_seq("FORMAT", "JSON"): 8418 return this 8419 8420 return self.expression(exp.FormatJson(this=this)) 8421 8422 def _parse_on_condition(self) -> exp.OnCondition | None: 8423 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8424 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8425 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8426 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8427 else: 8428 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8429 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8430 8431 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8432 8433 if not empty and not error and not null: 8434 return None 8435 8436 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8437 8438 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8439 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8440 for value in values: 8441 if self._match_text_seq(value, "ON", on): 8442 return f"{value} ON {on}" 8443 8444 index = self._index 8445 if self._match(TokenType.DEFAULT): 8446 default_value = self._parse_bitwise() 8447 if self._match_text_seq("ON", on): 8448 return default_value 8449 8450 self._retreat(index) 8451 8452 return None 8453 8454 @t.overload 8455 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8456 8457 @t.overload 8458 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8459 8460 def _parse_json_object(self, agg=False): 8461 star = self._parse_star() 8462 expressions = ( 8463 [star] 8464 if star 8465 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8466 ) 8467 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8468 8469 unique_keys = None 8470 if self._match_text_seq("WITH", "UNIQUE"): 8471 unique_keys = True 8472 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8473 unique_keys = False 8474 8475 self._match_text_seq("KEYS") 8476 8477 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8478 self._parse_type() 8479 ) 8480 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8481 8482 return self.expression( 8483 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8484 expressions=expressions, 8485 null_handling=null_handling, 8486 unique_keys=unique_keys, 8487 return_type=return_type, 8488 encoding=encoding, 8489 ) 8490 ) 8491 8492 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8493 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8494 if not self._match_text_seq("NESTED"): 8495 this = self._parse_id_var() 8496 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8497 kind = self._parse_types(allow_identifiers=False) 8498 nested = None 8499 else: 8500 this = None 8501 ordinality = None 8502 kind = None 8503 nested = True 8504 8505 format_json = self._match_text_seq("FORMAT", "JSON") 8506 path = self._match_text_seq("PATH") and self._parse_string() 8507 nested_schema = nested and self._parse_json_schema() 8508 8509 return self.expression( 8510 exp.JSONColumnDef( 8511 this=this, 8512 kind=kind, 8513 path=path, 8514 nested_schema=nested_schema, 8515 ordinality=ordinality, 8516 format_json=format_json, 8517 ) 8518 ) 8519 8520 def _parse_json_schema(self) -> exp.JSONSchema: 8521 self._match_text_seq("COLUMNS") 8522 return self.expression( 8523 exp.JSONSchema( 8524 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8525 ) 8526 ) 8527 8528 def _parse_json_table(self) -> exp.JSONTable: 8529 this = self._parse_format_json(self._parse_bitwise()) 8530 path = self._match(TokenType.COMMA) and self._parse_string() 8531 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8532 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8533 schema = self._parse_json_schema() 8534 8535 return exp.JSONTable( 8536 this=this, 8537 schema=schema, 8538 path=path, 8539 error_handling=error_handling, 8540 empty_handling=empty_handling, 8541 ) 8542 8543 def _parse_match_against(self) -> exp.MatchAgainst: 8544 if self._match_text_seq("TABLE"): 8545 # parse SingleStore MATCH(TABLE ...) syntax 8546 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8547 expressions = [] 8548 table = self._parse_table() 8549 if table: 8550 expressions = [table] 8551 else: 8552 expressions = self._parse_csv(self._parse_column) 8553 8554 self._match_text_seq(")", "AGAINST", "(") 8555 8556 this = self._parse_string() 8557 8558 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8559 modifier = "IN NATURAL LANGUAGE MODE" 8560 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8561 modifier = f"{modifier} WITH QUERY EXPANSION" 8562 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8563 modifier = "IN BOOLEAN MODE" 8564 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8565 modifier = "WITH QUERY EXPANSION" 8566 else: 8567 modifier = None 8568 8569 return self.expression( 8570 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8571 ) 8572 8573 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8574 def _parse_open_json(self) -> exp.OpenJSON: 8575 this = self._parse_bitwise() 8576 path = self._match(TokenType.COMMA) and self._parse_string() 8577 8578 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8579 this = self._parse_field(any_token=True) 8580 kind = self._parse_types() 8581 path = self._parse_string() 8582 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8583 8584 return self.expression( 8585 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8586 ) 8587 8588 expressions = None 8589 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8590 self._match_l_paren() 8591 expressions = self._parse_csv(_parse_open_json_column_def) 8592 8593 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8594 8595 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8596 args = self._parse_csv(self._parse_bitwise) 8597 8598 if self._match(TokenType.IN): 8599 return self.expression( 8600 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8601 ) 8602 8603 if haystack_first: 8604 haystack = seq_get(args, 0) 8605 needle = seq_get(args, 1) 8606 else: 8607 haystack = seq_get(args, 1) 8608 needle = seq_get(args, 0) 8609 8610 return self.expression( 8611 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8612 ) 8613 8614 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8615 args = self._parse_csv(self._parse_table) 8616 return exp.JoinHint(this=func_name.upper(), expressions=args) 8617 8618 def _parse_substring(self) -> exp.Substring: 8619 # Postgres supports the form: substring(string [from int] [for int]) 8620 # (despite being undocumented, the reverse order also works) 8621 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8622 8623 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8624 8625 start, length = None, None 8626 8627 while self._curr: 8628 if self._match(TokenType.FROM): 8629 start = self._parse_bitwise() 8630 elif self._match(TokenType.FOR): 8631 if not start: 8632 start = exp.Literal.number(1) 8633 length = self._parse_bitwise() 8634 else: 8635 break 8636 8637 if start: 8638 args.append(start) 8639 if length: 8640 args.append(length) 8641 8642 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8643 8644 def _parse_trim(self) -> exp.Trim: 8645 # https://www.w3resource.com/sql/character-functions/trim.php 8646 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8647 8648 position = None 8649 collation = None 8650 expression = None 8651 8652 if self._match_texts(self.TRIM_TYPES): 8653 position = self._prev.text.upper() 8654 8655 this = self._parse_bitwise() 8656 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8657 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8658 expression = self._parse_bitwise() 8659 8660 if invert_order: 8661 this, expression = expression, this 8662 8663 if self._match(TokenType.COLLATE): 8664 collation = self._parse_bitwise() 8665 8666 return self.expression( 8667 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8668 ) 8669 8670 def _parse_window_clause(self) -> list[exp.Expr] | None: 8671 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8672 8673 def _parse_named_window(self) -> exp.Expr | None: 8674 return self._parse_window(self._parse_id_var(), alias=True) 8675 8676 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8677 if self._curr.token_type == TokenType.VAR: 8678 if self._match_text_seq("IGNORE", "NULLS"): 8679 return self.expression(exp.IgnoreNulls(this=this)) 8680 if self._match_text_seq("RESPECT", "NULLS"): 8681 return self.expression(exp.RespectNulls(this=this)) 8682 return this 8683 8684 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8685 if self._match(TokenType.HAVING): 8686 self._match_texts(("MAX", "MIN")) 8687 max = self._prev.text.upper() != "MIN" 8688 return self.expression( 8689 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8690 ) 8691 8692 return this 8693 8694 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8695 func = this 8696 comments = func.comments if isinstance(func, exp.Expr) else None 8697 8698 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8699 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8700 if self._match_text_seq("FROM", "FIRST"): 8701 this.set("from_first", True) 8702 elif self._match_text_seq("FROM", "LAST"): 8703 this.set("from_first", False) 8704 8705 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8706 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8707 if self._match_text_seq("WITHIN", "GROUP"): 8708 order = self._parse_wrapped(self._parse_order) 8709 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8710 8711 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8712 self._match(TokenType.WHERE) 8713 this = self.expression( 8714 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8715 ) 8716 self._match_r_paren() 8717 8718 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8719 # Some dialects choose to implement and some do not. 8720 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8721 8722 # There is some code above in _parse_lambda that handles 8723 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8724 8725 # The below changes handle 8726 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8727 8728 # Oracle allows both formats 8729 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8730 # and Snowflake chose to do the same for familiarity 8731 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8732 if isinstance(this, exp.AggFunc): 8733 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8734 8735 if ignore_respect and ignore_respect is not this: 8736 ignore_respect.replace(ignore_respect.this) 8737 this = self.expression(ignore_respect.__class__(this=this)) 8738 8739 this = self._parse_respect_or_ignore_nulls(this) 8740 8741 # bigquery select from window x AS (partition by ...) 8742 if alias: 8743 over = None 8744 self._match(TokenType.ALIAS) 8745 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8746 return this 8747 else: 8748 over = self._prev.text.upper() 8749 8750 if comments and isinstance(func, exp.Expr): 8751 func.pop_comments() 8752 8753 if not self._match(TokenType.L_PAREN): 8754 return self.expression( 8755 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8756 ) 8757 8758 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8759 8760 first: bool | None = True if self._match(TokenType.FIRST) else None 8761 if self._match_text_seq("LAST"): 8762 first = False 8763 8764 partition, order = self._parse_partition_and_order() 8765 kind = ( 8766 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8767 ) and self._prev.text 8768 8769 if kind: 8770 self._match(TokenType.BETWEEN) 8771 start = self._parse_window_spec() 8772 8773 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8774 exclude = ( 8775 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8776 if self._match_text_seq("EXCLUDE") 8777 else None 8778 ) 8779 8780 spec = self.expression( 8781 exp.WindowSpec( 8782 kind=kind, 8783 start=start["value"], 8784 start_side=start["side"], 8785 end=end.get("value"), 8786 end_side=end.get("side"), 8787 exclude=exclude, 8788 ) 8789 ) 8790 else: 8791 spec = None 8792 8793 self._match_r_paren() 8794 8795 window = self.expression( 8796 exp.Window( 8797 this=this, 8798 partition_by=partition, 8799 order=order, 8800 spec=spec, 8801 alias=window_alias, 8802 over=over, 8803 first=first, 8804 ), 8805 comments=comments, 8806 ) 8807 8808 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 8809 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 8810 return self._parse_window(window, alias=alias) 8811 8812 return window 8813 8814 def _parse_partition_and_order( 8815 self, 8816 ) -> tuple[list[exp.Expr], exp.Expr | None]: 8817 return self._parse_partition_by(), self._parse_order() 8818 8819 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 8820 self._match(TokenType.BETWEEN) 8821 8822 return { 8823 "value": ( 8824 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 8825 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 8826 or self._parse_bitwise() 8827 ), 8828 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 8829 } 8830 8831 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 8832 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 8833 # so this section tries to parse the clause version and if it fails, it treats the token 8834 # as an identifier (alias) 8835 if self._can_parse_limit_or_offset(): 8836 return this 8837 8838 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 8839 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 8840 if self._can_parse_named_window(): 8841 return this 8842 8843 any_token = self._match(TokenType.ALIAS) 8844 comments = self._prev_comments 8845 8846 if explicit and not any_token: 8847 return this 8848 8849 if self._match(TokenType.L_PAREN): 8850 aliases = self.expression( 8851 exp.Aliases( 8852 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 8853 ), 8854 comments=comments, 8855 ) 8856 self._match_r_paren(aliases) 8857 return aliases 8858 8859 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 8860 self.STRING_ALIASES and self._parse_string_as_identifier() 8861 ) 8862 8863 if alias: 8864 comments.extend(alias.pop_comments()) 8865 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 8866 column = this.this 8867 8868 # Moves the comment next to the alias in `expr /* comment */ AS alias` 8869 if not this.comments and column and column.comments: 8870 this.comments = column.pop_comments() 8871 8872 return this 8873 8874 def _parse_id_var( 8875 self, 8876 any_token: bool = True, 8877 tokens: t.Collection[TokenType] | None = None, 8878 ) -> exp.Expr | None: 8879 expression = self._parse_identifier() 8880 if not expression and ( 8881 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 8882 ): 8883 quoted = self._prev.token_type == TokenType.STRING 8884 expression = self._identifier_expression(quoted=quoted) 8885 8886 return expression 8887 8888 def _parse_string(self) -> exp.Expr | None: 8889 if self._match_set(self.STRING_PARSERS): 8890 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 8891 return self._parse_placeholder() 8892 8893 def _parse_string_as_identifier(self) -> exp.Identifier | None: 8894 if not self._match(TokenType.STRING): 8895 return None 8896 output = exp.to_identifier(self._prev.text, quoted=True) 8897 output.update_positions(self._prev) 8898 return output 8899 8900 def _parse_number(self) -> exp.Expr | None: 8901 if self._match_set(self.NUMERIC_PARSERS): 8902 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 8903 return self._parse_placeholder() 8904 8905 def _parse_identifier(self) -> exp.Expr | None: 8906 if self._match(TokenType.IDENTIFIER): 8907 return self._identifier_expression(quoted=True) 8908 return self._parse_placeholder() 8909 8910 def _parse_var( 8911 self, 8912 any_token: bool = False, 8913 tokens: t.Collection[TokenType] | None = None, 8914 upper: bool = False, 8915 ) -> exp.Expr | None: 8916 if ( 8917 (any_token and self._advance_any()) 8918 or self._match(TokenType.VAR) 8919 or (self._match_set(tokens) if tokens else False) 8920 ): 8921 return self.expression( 8922 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 8923 ) 8924 return self._parse_placeholder() 8925 8926 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 8927 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 8928 self._advance() 8929 return self._prev 8930 return None 8931 8932 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 8933 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 8934 8935 def _parse_primary_or_var(self) -> exp.Expr | None: 8936 return self._parse_primary() or self._parse_var(any_token=True) 8937 8938 def _parse_null(self) -> exp.Expr | None: 8939 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 8940 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 8941 return self._parse_placeholder() 8942 8943 def _parse_boolean(self) -> exp.Expr | None: 8944 if self._match(TokenType.TRUE): 8945 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 8946 if self._match(TokenType.FALSE): 8947 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 8948 return self._parse_placeholder() 8949 8950 def _parse_star(self) -> exp.Expr | None: 8951 if self._match(TokenType.STAR): 8952 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 8953 return self._parse_placeholder() 8954 8955 def _parse_parameter(self) -> exp.Parameter: 8956 this = self._parse_identifier() or self._parse_primary_or_var() 8957 return self.expression(exp.Parameter(this=this)) 8958 8959 def _parse_placeholder(self) -> exp.Expr | None: 8960 if self._match_set(self.PLACEHOLDER_PARSERS): 8961 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 8962 if placeholder: 8963 return placeholder 8964 self._advance(-1) 8965 return None 8966 8967 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 8968 if not self._match_texts(keywords): 8969 return None 8970 if self._match(TokenType.L_PAREN, advance=False): 8971 return self._parse_wrapped_csv(self._parse_expression) 8972 8973 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 8974 return [expression] if expression else None 8975 8976 def _parse_csv( 8977 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 8978 ) -> list[T]: 8979 parse_result = parse_method() 8980 items = [parse_result] if parse_result is not None else [] 8981 8982 while self._match(sep): 8983 if isinstance(parse_result, exp.Expr): 8984 self._add_comments(parse_result) 8985 parse_result = parse_method() 8986 if parse_result is not None: 8987 items.append(parse_result) 8988 8989 return items 8990 8991 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 8992 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 8993 8994 def _parse_wrapped_csv( 8995 self, 8996 parse_method: t.Callable[[], T | None], 8997 sep: TokenType = TokenType.COMMA, 8998 optional: bool = False, 8999 ) -> list[T]: 9000 return self._parse_wrapped( 9001 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 9002 ) 9003 9004 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 9005 wrapped = self._match(TokenType.L_PAREN) 9006 if not wrapped and not optional: 9007 self.raise_error("Expecting (") 9008 parse_result = parse_method() 9009 if wrapped: 9010 self._match_r_paren() 9011 return parse_result 9012 9013 def _parse_expressions(self) -> list[exp.Expr]: 9014 return self._parse_csv(self._parse_expression) 9015 9016 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 9017 return ( 9018 self._parse_set_operations( 9019 self._parse_alias(self._parse_assignment(), explicit=True) 9020 if alias 9021 else self._parse_assignment() 9022 ) 9023 or self._parse_select() 9024 ) 9025 9026 def _parse_ddl_select(self) -> exp.Expr | None: 9027 return self._parse_query_modifiers( 9028 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 9029 ) 9030 9031 def _parse_transaction(self) -> exp.Transaction | exp.Command: 9032 this = None 9033 if self._match_texts(self.TRANSACTION_KIND): 9034 this = self._prev.text 9035 9036 self._match_texts(("TRANSACTION", "WORK")) 9037 9038 modes = [] 9039 while True: 9040 mode = [] 9041 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 9042 mode.append(self._prev.text) 9043 9044 if mode: 9045 modes.append(" ".join(mode)) 9046 if not self._match(TokenType.COMMA): 9047 break 9048 9049 return self.expression(exp.Transaction(this=this, modes=modes)) 9050 9051 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 9052 chain = None 9053 savepoint = None 9054 is_rollback = self._prev.token_type == TokenType.ROLLBACK 9055 9056 self._match_texts(("TRANSACTION", "WORK")) 9057 9058 if self._match_text_seq("TO"): 9059 self._match_text_seq("SAVEPOINT") 9060 savepoint = self._parse_id_var() 9061 9062 if self._match(TokenType.AND): 9063 chain = not self._match_text_seq("NO") 9064 self._match_text_seq("CHAIN") 9065 9066 if is_rollback: 9067 return self.expression(exp.Rollback(savepoint=savepoint)) 9068 9069 return self.expression(exp.Commit(chain=chain)) 9070 9071 def _parse_refresh(self) -> exp.Refresh | exp.Command: 9072 if self._match_text_seq("EXTERNAL", "TABLE"): 9073 kind = "EXTERNAL TABLE" 9074 elif self._match(TokenType.TABLE): 9075 kind = "TABLE" 9076 elif self._match_text_seq("MATERIALIZED", "VIEW"): 9077 kind = "MATERIALIZED VIEW" 9078 else: 9079 kind = "" 9080 9081 this = self._parse_string() or self._parse_table() 9082 if not kind and not isinstance(this, exp.Literal): 9083 return self._parse_as_command(self._prev) 9084 9085 return self.expression(exp.Refresh(this=this, kind=kind)) 9086 9087 def _parse_column_def_with_exists(self): 9088 start = self._index 9089 self._match(TokenType.COLUMN) 9090 9091 exists_column = self._parse_exists(not_=True) 9092 expression = self._parse_field_def() 9093 9094 if not isinstance(expression, exp.ColumnDef): 9095 self._retreat(start) 9096 return None 9097 9098 expression.set("exists", exists_column) 9099 9100 return expression 9101 9102 def _parse_add_column(self) -> exp.ColumnDef | None: 9103 if not self._prev.text.upper() == "ADD": 9104 return None 9105 9106 return self._parse_column_def_with_exists() 9107 9108 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9109 drop = self._parse_drop() if self._match(TokenType.DROP) else None 9110 if drop and not isinstance(drop, exp.Command): 9111 drop.set("kind", drop.args.get("kind", "COLUMN")) 9112 return drop 9113 9114 def _parse_alter_drop_action(self) -> exp.Expr | None: 9115 return self._parse_drop_column() 9116 9117 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9118 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9119 return self.expression( 9120 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9121 ) 9122 9123 def _parse_alter_table_add(self) -> list[exp.Expr]: 9124 def _parse_add_alteration() -> exp.Expr | None: 9125 self._match_text_seq("ADD") 9126 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False): 9127 return self.expression( 9128 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9129 ) 9130 9131 column_def = self._parse_add_column() 9132 if isinstance(column_def, exp.ColumnDef): 9133 return column_def 9134 9135 exists = self._parse_exists(not_=True) 9136 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9137 return self.expression( 9138 exp.AddPartition( 9139 exists=exists, 9140 this=self._parse_field(any_token=True), 9141 location=self._match_text_seq("LOCATION", advance=False) 9142 and self._parse_property(), 9143 ) 9144 ) 9145 9146 return None 9147 9148 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9149 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9150 or self._match_text_seq("COLUMNS") 9151 ): 9152 schema = self._parse_schema() 9153 9154 return ( 9155 ensure_list(schema) 9156 if schema 9157 else self._parse_csv(self._parse_column_def_with_exists) 9158 ) 9159 9160 return self._parse_csv(_parse_add_alteration) 9161 9162 def _parse_alter_table_alter(self) -> exp.Expr | None: 9163 if self._match_texts(self.ALTER_ALTER_PARSERS): 9164 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9165 9166 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9167 # keyword after ALTER we default to parsing this statement 9168 self._match(TokenType.COLUMN) 9169 exists = self._parse_exists() 9170 column = self._parse_field(any_token=True) 9171 9172 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9173 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9174 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9175 return self.expression( 9176 exp.AlterColumn( 9177 this=column, default=self._parse_disjunction(), exists=exists or None 9178 ) 9179 ) 9180 if self._match(TokenType.COMMENT): 9181 return self.expression( 9182 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9183 ) 9184 if self._match_text_seq("DROP", "NOT", "NULL"): 9185 return self.expression( 9186 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9187 ) 9188 if self._match_text_seq("SET", "NOT", "NULL"): 9189 return self.expression( 9190 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9191 ) 9192 9193 if self._match_text_seq("SET", "VISIBLE"): 9194 return self.expression( 9195 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9196 ) 9197 if self._match_text_seq("SET", "INVISIBLE"): 9198 return self.expression( 9199 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9200 ) 9201 9202 self._match_text_seq("SET", "DATA") 9203 self._match_text_seq("TYPE") 9204 return self.expression( 9205 exp.AlterColumn( 9206 this=column, 9207 dtype=self._parse_types(), 9208 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9209 using=self._match(TokenType.USING) and self._parse_disjunction(), 9210 exists=exists or None, 9211 ) 9212 ) 9213 9214 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9215 if self._match_texts(("ALL", "EVEN", "AUTO")): 9216 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9217 9218 self._match_text_seq("KEY", "DISTKEY") 9219 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9220 9221 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9222 if compound: 9223 self._match_text_seq("SORTKEY") 9224 9225 if self._match(TokenType.L_PAREN, advance=False): 9226 return self.expression( 9227 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9228 ) 9229 9230 self._match_texts(("AUTO", "NONE")) 9231 return self.expression( 9232 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9233 ) 9234 9235 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9236 index = self._index - 1 9237 9238 partition_exists = self._parse_exists() 9239 if self._match(TokenType.PARTITION, advance=False): 9240 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9241 9242 self._retreat(index) 9243 return self._parse_csv(self._parse_alter_drop_action) 9244 9245 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9246 if self._match(TokenType.COLUMN) or ( 9247 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9248 ): 9249 exists = self._parse_exists() 9250 old_column = self._parse_column() 9251 to = self._match_text_seq("TO") 9252 new_column = self._parse_column() 9253 9254 if old_column is None or not to or new_column is None: 9255 return None 9256 9257 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9258 9259 self._match_text_seq("TO") 9260 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9261 9262 def _parse_alter_table_set(self) -> exp.AlterSet: 9263 alter_set = self.expression(exp.AlterSet()) 9264 9265 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9266 "TABLE", "PROPERTIES" 9267 ): 9268 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9269 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9270 alter_set.set("expressions", [self._parse_assignment()]) 9271 elif self._match_texts(("LOGGED", "UNLOGGED")): 9272 alter_set.set("option", exp.var(self._prev.text.upper())) 9273 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9274 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9275 elif self._match_text_seq("LOCATION"): 9276 alter_set.set("location", self._parse_field()) 9277 elif self._match_text_seq("ACCESS", "METHOD"): 9278 alter_set.set("access_method", self._parse_field()) 9279 elif self._match_text_seq("TABLESPACE"): 9280 alter_set.set("tablespace", self._parse_field()) 9281 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9282 alter_set.set("file_format", [self._parse_field()]) 9283 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9284 alter_set.set("file_format", self._parse_wrapped_options()) 9285 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9286 alter_set.set("copy_options", self._parse_wrapped_options()) 9287 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9288 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9289 else: 9290 if self._match_text_seq("SERDE"): 9291 alter_set.set("serde", self._parse_field()) 9292 9293 properties = self._parse_wrapped(self._parse_properties, optional=True) 9294 alter_set.set("expressions", [properties]) 9295 9296 return alter_set 9297 9298 def _parse_alter_session(self) -> exp.AlterSession: 9299 """Parse ALTER SESSION SET/UNSET statements.""" 9300 if self._match(TokenType.SET): 9301 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9302 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9303 9304 self._match_text_seq("UNSET") 9305 expressions = self._parse_csv( 9306 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9307 ) 9308 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9309 9310 def _parse_alter(self) -> exp.Alter | exp.Command: 9311 start = self._prev 9312 9313 iceberg = self._match_text_seq("ICEBERG") 9314 9315 alter_token = self._match_set(self.ALTERABLES) and self._prev 9316 if not alter_token: 9317 return self._parse_as_command(start) 9318 if iceberg and alter_token.token_type != TokenType.TABLE: 9319 return self._parse_as_command(start) 9320 9321 exists = self._parse_exists() 9322 only = self._match_text_seq("ONLY") 9323 9324 if alter_token.token_type == TokenType.SESSION: 9325 this = None 9326 check = None 9327 cluster = None 9328 else: 9329 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9330 check = self._match_text_seq("WITH", "CHECK") 9331 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9332 9333 if self._next: 9334 self._advance() 9335 9336 parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None 9337 if parser: 9338 actions = ensure_list(parser(self)) 9339 not_valid = self._match_text_seq("NOT", "VALID") 9340 options = self._parse_csv(self._parse_property) 9341 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9342 9343 if not self._curr and actions: 9344 return self.expression( 9345 exp.Alter( 9346 this=this, 9347 kind=alter_token.text.upper(), 9348 exists=exists, 9349 actions=actions, 9350 only=only, 9351 options=options, 9352 cluster=cluster, 9353 not_valid=not_valid, 9354 check=check, 9355 cascade=cascade, 9356 iceberg=iceberg, 9357 ) 9358 ) 9359 9360 return self._parse_as_command(start) 9361 9362 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9363 start = self._prev 9364 # https://duckdb.org/docs/sql/statements/analyze 9365 if not self._curr: 9366 return self.expression(exp.Analyze()) 9367 9368 options = [] 9369 while self._match_texts(self.ANALYZE_STYLES): 9370 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9371 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9372 else: 9373 options.append(self._prev.text.upper()) 9374 9375 tables: exp.Expr | list[exp.Expr] | None = None 9376 inner_expression: exp.Expr | None = None 9377 9378 kind = self._curr.text.upper() if self._curr else None 9379 9380 if self._match(TokenType.TABLE): 9381 tables = self._parse_csv(self._parse_table_parts) 9382 elif self._match(TokenType.INDEX): 9383 tables = self._parse_table_parts() 9384 elif self._match_text_seq("TABLES"): 9385 if self._match_set((TokenType.FROM, TokenType.IN)): 9386 kind = f"{kind} {self._prev.text.upper()}" 9387 tables = self._parse_table(schema=True, is_db_reference=True) 9388 elif self._match_text_seq("DATABASE"): 9389 tables = self._parse_table(schema=True, is_db_reference=True) 9390 elif self._match_text_seq("CLUSTER"): 9391 tables = self._parse_table() 9392 # Try matching inner expr keywords before fallback to parse table. 9393 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9394 kind = None 9395 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9396 else: 9397 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9398 kind = None 9399 tables = self._parse_csv(self._parse_table_parts) 9400 9401 partition = self._try_parse(self._parse_partition) 9402 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9403 return self._parse_as_command(start) 9404 9405 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9406 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9407 "WITH", "ASYNC", "MODE" 9408 ): 9409 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9410 else: 9411 mode = None 9412 9413 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9414 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9415 9416 properties = self._parse_properties() 9417 return self.expression( 9418 exp.Analyze( 9419 kind=kind, 9420 tables=ensure_list(tables), 9421 mode=mode, 9422 partition=partition, 9423 properties=properties, 9424 expression=inner_expression, 9425 options=options, 9426 ) 9427 ) 9428 9429 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9430 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9431 this = None 9432 kind = self._prev.text.upper() 9433 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9434 expressions = [] 9435 9436 if not self._match_text_seq("STATISTICS"): 9437 self.raise_error("Expecting token STATISTICS") 9438 9439 if self._match_text_seq("NOSCAN"): 9440 this = "NOSCAN" 9441 elif self._match(TokenType.FOR): 9442 if self._match_text_seq("ALL", "COLUMNS"): 9443 this = "FOR ALL COLUMNS" 9444 if self._match_text_seq("COLUMNS"): 9445 this = "FOR COLUMNS" 9446 expressions = self._parse_csv(self._parse_column_reference) 9447 elif self._match_text_seq("SAMPLE"): 9448 sample = self._parse_number() 9449 expressions = [ 9450 self.expression( 9451 exp.AnalyzeSample( 9452 sample=sample, 9453 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9454 ) 9455 ) 9456 ] 9457 9458 return self.expression( 9459 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9460 ) 9461 9462 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9463 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9464 kind = None 9465 this = None 9466 expression: exp.Expr | None = None 9467 if self._match_text_seq("REF", "UPDATE"): 9468 kind = "REF" 9469 this = "UPDATE" 9470 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9471 this = "UPDATE SET DANGLING TO NULL" 9472 elif self._match_text_seq("STRUCTURE"): 9473 kind = "STRUCTURE" 9474 if self._match_text_seq("CASCADE", "FAST"): 9475 this = "CASCADE FAST" 9476 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9477 ("ONLINE", "OFFLINE") 9478 ): 9479 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9480 expression = self._parse_into() 9481 9482 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9483 9484 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9485 this = self._prev.text.upper() 9486 if self._match_text_seq("COLUMNS"): 9487 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9488 return None 9489 9490 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9491 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9492 if self._match_text_seq("STATISTICS"): 9493 return self.expression(exp.AnalyzeDelete(kind=kind)) 9494 return None 9495 9496 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9497 if self._match_text_seq("CHAINED", "ROWS"): 9498 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9499 return None 9500 9501 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9502 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9503 this = self._prev.text.upper() 9504 expression: exp.Expr | None = None 9505 expressions = [] 9506 update_options = None 9507 9508 if self._match_text_seq("HISTOGRAM", "ON"): 9509 expressions = self._parse_csv(self._parse_column_reference) 9510 with_expressions = [] 9511 while self._match(TokenType.WITH): 9512 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9513 if self._match_texts(("SYNC", "ASYNC")): 9514 if self._match_text_seq("MODE", advance=False): 9515 with_expressions.append(f"{self._prev.text.upper()} MODE") 9516 self._advance() 9517 else: 9518 buckets = self._parse_number() 9519 if self._match_text_seq("BUCKETS"): 9520 with_expressions.append(f"{buckets} BUCKETS") 9521 if with_expressions: 9522 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9523 9524 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9525 TokenType.UPDATE, advance=False 9526 ): 9527 update_options = self._prev.text.upper() 9528 self._advance() 9529 elif self._match_text_seq("USING", "DATA"): 9530 expression = self.expression(exp.UsingData(this=self._parse_string())) 9531 9532 return self.expression( 9533 exp.AnalyzeHistogram( 9534 this=this, 9535 expressions=expressions, 9536 expression=expression, 9537 update_options=update_options, 9538 ) 9539 ) 9540 9541 def _parse_merge(self) -> exp.Merge: 9542 self._match(TokenType.INTO) 9543 target = self._parse_table() 9544 9545 if target and self._match(TokenType.ALIAS, advance=False): 9546 target.set("alias", self._parse_table_alias()) 9547 9548 self._match(TokenType.USING) 9549 using = self._parse_table() 9550 9551 return self.expression( 9552 exp.Merge( 9553 this=target, 9554 using=using, 9555 on=self._match(TokenType.ON) and self._parse_disjunction(), 9556 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9557 whens=self._parse_when_matched(), 9558 returning=self._parse_returning(), 9559 ) 9560 ) 9561 9562 def _parse_when_matched(self) -> exp.Whens: 9563 whens = [] 9564 9565 while self._match(TokenType.WHEN): 9566 matched = not self._match(TokenType.NOT) 9567 self._match_text_seq("MATCHED") 9568 source = ( 9569 False 9570 if self._match_text_seq("BY", "TARGET") 9571 else self._match_text_seq("BY", "SOURCE") 9572 ) 9573 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9574 9575 self._match(TokenType.THEN) 9576 9577 if self._match(TokenType.INSERT): 9578 this = self._parse_star() 9579 if this: 9580 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9581 else: 9582 then = self.expression( 9583 exp.Insert( 9584 this=exp.var("ROW") 9585 if self._match_text_seq("ROW") 9586 else self._parse_value(values=False), 9587 expression=self._match_text_seq("VALUES") and self._parse_value(), 9588 where=self._parse_where(), 9589 ) 9590 ) 9591 elif self._match(TokenType.UPDATE): 9592 expressions = self._parse_star() 9593 if expressions: 9594 then = self.expression(exp.Update(expressions=expressions)) 9595 else: 9596 then = self.expression( 9597 exp.Update( 9598 expressions=self._match(TokenType.SET) 9599 and self._parse_csv(self._parse_update_assignment), 9600 where=self._parse_where(), 9601 ) 9602 ) 9603 elif self._match(TokenType.DELETE): 9604 then = self.expression(exp.Var(this=self._prev.text)) 9605 else: 9606 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9607 9608 whens.append( 9609 self.expression( 9610 exp.When(matched=matched, source=source, condition=condition, then=then) 9611 ) 9612 ) 9613 return self.expression(exp.Whens(expressions=whens)) 9614 9615 def _parse_show(self) -> exp.Expr | None: 9616 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9617 if parser: 9618 return parser(self) 9619 return self._parse_as_command(self._prev) 9620 9621 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9622 index = self._index 9623 9624 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9625 return self._parse_set_transaction(global_=kind == "GLOBAL") 9626 9627 left = self._parse_primary() or self._parse_column() 9628 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 9629 9630 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 9631 self._retreat(index) 9632 return None 9633 9634 right = self._parse_statement() or self._parse_id_var() 9635 if isinstance(right, (exp.Column, exp.Identifier)): 9636 right = exp.var(right.name) 9637 9638 this = self.expression(exp.EQ(this=left, expression=right)) 9639 return self.expression(exp.SetItem(this=this, kind=kind)) 9640 9641 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 9642 self._match_text_seq("TRANSACTION") 9643 characteristics = self._parse_csv( 9644 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 9645 ) 9646 return self.expression( 9647 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 9648 ) 9649 9650 def _parse_set_item(self) -> exp.Expr | None: 9651 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 9652 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 9653 9654 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 9655 index = self._index 9656 set_ = self.expression( 9657 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 9658 ) 9659 9660 if self._curr: 9661 self._retreat(index) 9662 return self._parse_as_command(self._prev) 9663 9664 return set_ 9665 9666 def _parse_var_from_options( 9667 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 9668 ) -> exp.Var | None: 9669 start = self._curr 9670 if not start: 9671 return None 9672 9673 option = start.text.upper() 9674 continuations = ( 9675 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 9676 ) 9677 9678 index = self._index 9679 self._advance() 9680 for keywords in continuations or []: 9681 if isinstance(keywords, str): 9682 keywords = (keywords,) 9683 9684 if self._match_text_seq(*keywords): 9685 option = f"{option} {' '.join(keywords)}" 9686 break 9687 else: 9688 if continuations or continuations is None: 9689 if raise_unmatched: 9690 self.raise_error(f"Unknown option {option}") 9691 9692 self._retreat(index) 9693 return None 9694 9695 return exp.var(option) 9696 9697 def _parse_as_command(self, start: Token) -> exp.Command: 9698 while self._curr: 9699 self._advance() 9700 text = self._find_sql(start, self._prev) 9701 size = len(start.text) 9702 self._warn_unsupported() 9703 return exp.Command(this=text[:size], expression=text[size:]) 9704 9705 def _parse_dict_property(self, this: str) -> exp.DictProperty: 9706 settings = [] 9707 9708 self._match_l_paren() 9709 kind = self._parse_id_var() 9710 9711 if self._match(TokenType.L_PAREN): 9712 while True: 9713 key = self._parse_id_var() 9714 value = self._parse_function() or self._parse_primary_or_var() 9715 if not key and value is None: 9716 break 9717 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 9718 self._match(TokenType.R_PAREN) 9719 9720 self._match_r_paren() 9721 9722 return self.expression( 9723 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 9724 ) 9725 9726 def _parse_dict_range(self, this: str) -> exp.DictRange: 9727 self._match_l_paren() 9728 has_min = self._match_text_seq("MIN") 9729 if has_min: 9730 min = self._parse_var() or self._parse_primary() 9731 self._match_text_seq("MAX") 9732 max = self._parse_var() or self._parse_primary() 9733 else: 9734 max = self._parse_var() or self._parse_primary() 9735 min = exp.Literal.number(0) 9736 self._match_r_paren() 9737 return self.expression(exp.DictRange(this=this, min=min, max=max)) 9738 9739 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 9740 index = self._index 9741 expression = self._parse_column() 9742 position = self._match(TokenType.COMMA) and self._parse_column() 9743 9744 if not self._match(TokenType.IN): 9745 self._retreat(index - 1) 9746 return None 9747 iterator = self._parse_column() 9748 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 9749 return self.expression( 9750 exp.Comprehension( 9751 this=this, 9752 expression=expression, 9753 position=position, 9754 iterator=iterator, 9755 condition=condition, 9756 ) 9757 ) 9758 9759 def _parse_heredoc(self) -> exp.Heredoc | None: 9760 if self._match(TokenType.HEREDOC_STRING): 9761 return self.expression(exp.Heredoc(this=self._prev.text)) 9762 9763 if not self._match_text_seq("$"): 9764 return None 9765 9766 tags = ["$"] 9767 tag_text = None 9768 9769 if self._is_connected(): 9770 self._advance() 9771 tags.append(self._prev.text.upper()) 9772 else: 9773 self.raise_error("No closing $ found") 9774 9775 if tags[-1] != "$": 9776 if self._is_connected() and self._match_text_seq("$"): 9777 tag_text = tags[-1] 9778 tags.append("$") 9779 else: 9780 self.raise_error("No closing $ found") 9781 9782 heredoc_start = self._curr 9783 9784 while self._curr: 9785 if self._match_text_seq(*tags, advance=False): 9786 this = self._find_sql(heredoc_start, self._prev) 9787 self._advance(len(tags)) 9788 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 9789 9790 self._advance() 9791 9792 self.raise_error(f"No closing {''.join(tags)} found") 9793 return None 9794 9795 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 9796 if not self._curr: 9797 return None 9798 9799 index = self._index 9800 this = [] 9801 while True: 9802 # The current token might be multiple words 9803 curr = self._curr.text.upper() 9804 key = curr.split(" ") 9805 this.append(curr) 9806 9807 self._advance() 9808 result, trie = in_trie(trie, key) 9809 if result == TrieResult.FAILED: 9810 break 9811 9812 if result == TrieResult.EXISTS: 9813 subparser = parsers[" ".join(this)] 9814 return subparser 9815 9816 self._retreat(index) 9817 return None 9818 9819 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 9820 if not self._match(TokenType.L_PAREN, expression=expression): 9821 self.raise_error("Expecting (") 9822 9823 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 9824 if not self._match(TokenType.R_PAREN, expression=expression): 9825 self.raise_error("Expecting )") 9826 9827 def _replace_lambda( 9828 self, node: exp.Expr | None, expressions: list[exp.Expr] 9829 ) -> exp.Expr | None: 9830 if not node: 9831 return node 9832 9833 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 9834 9835 for column in node.find_all(exp.Column): 9836 typ = lambda_types.get(column.parts[0].name) 9837 if typ is not None: 9838 dot_or_id = column.to_dot() if column.table else column.this 9839 9840 if typ: 9841 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ)) 9842 9843 parent = column.parent 9844 9845 while isinstance(parent, exp.Dot): 9846 if not isinstance(parent.parent, exp.Dot): 9847 parent.replace(dot_or_id) 9848 break 9849 parent = parent.parent 9850 else: 9851 if column is node: 9852 node = dot_or_id 9853 else: 9854 column.replace(dot_or_id) 9855 return node 9856 9857 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 9858 start = self._prev 9859 9860 # Not to be confused with TRUNCATE(number, decimals) function call 9861 if self._match(TokenType.L_PAREN): 9862 self._retreat(self._index - 2) 9863 return self._parse_function() 9864 9865 # Clickhouse supports TRUNCATE DATABASE as well 9866 is_database = self._match(TokenType.DATABASE) 9867 9868 self._match(TokenType.TABLE) 9869 9870 exists = self._parse_exists(not_=False) 9871 9872 expressions = self._parse_csv( 9873 lambda: self._parse_table(schema=True, is_db_reference=is_database) 9874 ) 9875 9876 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9877 9878 if self._match_text_seq("RESTART", "IDENTITY"): 9879 identity = "RESTART" 9880 elif self._match_text_seq("CONTINUE", "IDENTITY"): 9881 identity = "CONTINUE" 9882 else: 9883 identity = None 9884 9885 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 9886 option = self._prev.text 9887 else: 9888 option = None 9889 9890 partition = self._parse_partition() 9891 9892 # Fallback case 9893 if self._curr: 9894 return self._parse_as_command(start) 9895 9896 return self.expression( 9897 exp.TruncateTable( 9898 expressions=expressions, 9899 is_database=is_database, 9900 exists=exists, 9901 cluster=cluster, 9902 identity=identity, 9903 option=option, 9904 partition=partition, 9905 ) 9906 ) 9907 9908 def _parse_indexed_column(self) -> exp.Expr | None: 9909 return self._parse_ordered(self._parse_opclass) 9910 9911 def _parse_with_operator(self) -> exp.Expr | None: 9912 this = self._parse_indexed_column() 9913 9914 if not self._match(TokenType.WITH): 9915 return this 9916 9917 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 9918 9919 return self.expression(exp.WithOperator(this=this, op=op)) 9920 9921 def _parse_wrapped_options(self) -> list[exp.Expr]: 9922 self._match(TokenType.EQ) 9923 self._match(TokenType.L_PAREN) 9924 9925 opts: list[exp.Expr] = [] 9926 option: exp.Expr | list[exp.Expr] | None 9927 while self._curr and not self._match(TokenType.R_PAREN): 9928 if self._match_text_seq("FORMAT_NAME", "="): 9929 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 9930 option = self._parse_format_name() 9931 else: 9932 option = self._parse_property() 9933 9934 if option is None: 9935 self.raise_error("Unable to parse option") 9936 break 9937 9938 opts.extend(ensure_list(option)) 9939 9940 return opts 9941 9942 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 9943 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 9944 9945 options = [] 9946 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 9947 option = self._parse_var(any_token=True) 9948 prev = self._prev.text.upper() 9949 9950 # Different dialects might separate options and values by white space, "=" and "AS" 9951 self._match(TokenType.EQ) 9952 self._match(TokenType.ALIAS) 9953 9954 param = self.expression(exp.CopyParameter(this=option)) 9955 9956 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 9957 TokenType.L_PAREN, advance=False 9958 ): 9959 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 9960 param.set("expressions", self._parse_wrapped_options()) 9961 elif prev == "FILE_FORMAT": 9962 # T-SQL's external file format case 9963 param.set("expression", self._parse_field()) 9964 elif ( 9965 prev == "FORMAT" 9966 and self._prev.token_type == TokenType.ALIAS 9967 and self._match_texts(("AVRO", "JSON")) 9968 ): 9969 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 9970 param.set("expression", self._parse_field()) 9971 else: 9972 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 9973 9974 options.append(param) 9975 9976 if sep: 9977 self._match(sep) 9978 9979 return options 9980 9981 def _parse_credentials(self) -> exp.Credentials | None: 9982 expr = self.expression(exp.Credentials()) 9983 9984 if self._match_text_seq("STORAGE_INTEGRATION", "="): 9985 expr.set("storage", self._parse_field()) 9986 if self._match_text_seq("CREDENTIALS"): 9987 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 9988 creds = ( 9989 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 9990 ) 9991 expr.set("credentials", creds) 9992 if self._match_text_seq("ENCRYPTION"): 9993 expr.set("encryption", self._parse_wrapped_options()) 9994 if self._match_text_seq("IAM_ROLE"): 9995 expr.set( 9996 "iam_role", 9997 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 9998 ) 9999 if self._match_text_seq("REGION"): 10000 expr.set("region", self._parse_field()) 10001 10002 return expr 10003 10004 def _parse_file_location(self) -> exp.Expr | None: 10005 return self._parse_field() 10006 10007 def _parse_copy(self) -> exp.Copy | exp.Command: 10008 start = self._prev 10009 10010 self._match(TokenType.INTO) 10011 10012 this = ( 10013 self._parse_select(nested=True, parse_subquery_alias=False) 10014 if self._match(TokenType.L_PAREN, advance=False) 10015 else self._parse_table(schema=True) 10016 ) 10017 10018 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 10019 10020 files = self._parse_csv(self._parse_file_location) 10021 if self._match(TokenType.EQ, advance=False): 10022 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 10023 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 10024 # list via `_parse_wrapped(..)` below. 10025 self._advance(-1) 10026 files = [] 10027 10028 credentials = self._parse_credentials() 10029 10030 self._match_text_seq("WITH") 10031 10032 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 10033 10034 # Fallback case 10035 if self._curr: 10036 return self._parse_as_command(start) 10037 10038 return self.expression( 10039 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 10040 ) 10041 10042 def _parse_normalize(self) -> exp.Normalize: 10043 return self.expression( 10044 exp.Normalize( 10045 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 10046 ) 10047 ) 10048 10049 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 10050 args = self._parse_csv(lambda: self._parse_lambda()) 10051 10052 this = seq_get(args, 0) 10053 decimals = seq_get(args, 1) 10054 10055 return expr_type( 10056 this=this, 10057 decimals=decimals, 10058 to=self._parse_var() if self._match_text_seq("TO") else None, 10059 ) 10060 10061 def _parse_star_ops(self) -> exp.Expr | None: 10062 star_token = self._prev 10063 10064 if self._match_text_seq("COLUMNS", "(", advance=False): 10065 this = self._parse_function() 10066 if isinstance(this, exp.Columns): 10067 this.set("unpack", True) 10068 return this 10069 10070 index = self._index 10071 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 10072 if not ilike: 10073 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 10074 self._retreat(index) 10075 10076 return self.expression( 10077 exp.Star( 10078 ilike=ilike, 10079 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 10080 replace=self._parse_star_op("REPLACE"), 10081 rename=self._parse_star_op("RENAME"), 10082 ) 10083 ).update_positions(star_token) 10084 10085 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 10086 privilege_parts = [] 10087 10088 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 10089 # (end of privilege list) or L_PAREN (start of column list) are met 10090 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 10091 privilege_parts.append(self._curr.text.upper()) 10092 self._advance() 10093 10094 if not privilege_parts: 10095 self.raise_error("Expected privilege") 10096 return None 10097 10098 this = exp.var(" ".join(privilege_parts)) 10099 expressions = ( 10100 self._parse_wrapped_csv(self._parse_column) 10101 if self._match(TokenType.L_PAREN, advance=False) 10102 else None 10103 ) 10104 10105 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10106 10107 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10108 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10109 principal = self._parse_id_var() 10110 10111 if not principal: 10112 return None 10113 10114 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10115 10116 def _parse_grant_revoke_common( 10117 self, 10118 ) -> tuple[list | None, str | None, exp.Expr | None]: 10119 privileges = self._parse_csv(self._parse_grant_privilege) 10120 10121 self._match(TokenType.ON) 10122 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10123 10124 # Attempt to parse the securable e.g. MySQL allows names 10125 # such as "foo.*", "*.*" which are not easily parseable yet 10126 securable = self._try_parse(self._parse_table_parts) 10127 10128 return privileges, kind, securable 10129 10130 def _parse_grant(self) -> exp.Grant | exp.Command: 10131 start = self._prev 10132 10133 privileges, kind, securable = self._parse_grant_revoke_common() 10134 10135 if not securable or not self._match_text_seq("TO"): 10136 return self._parse_as_command(start) 10137 10138 principals = self._parse_csv(self._parse_grant_principal) 10139 10140 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10141 10142 if self._curr: 10143 return self._parse_as_command(start) 10144 10145 return self.expression( 10146 exp.Grant( 10147 privileges=privileges, 10148 kind=kind, 10149 securable=securable, 10150 principals=principals, 10151 grant_option=grant_option, 10152 ) 10153 ) 10154 10155 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10156 start = self._prev 10157 10158 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10159 10160 privileges, kind, securable = self._parse_grant_revoke_common() 10161 10162 if not securable or not self._match_text_seq("FROM"): 10163 return self._parse_as_command(start) 10164 10165 principals = self._parse_csv(self._parse_grant_principal) 10166 10167 cascade = None 10168 if self._match_texts(("CASCADE", "RESTRICT")): 10169 cascade = self._prev.text.upper() 10170 10171 if self._curr: 10172 return self._parse_as_command(start) 10173 10174 return self.expression( 10175 exp.Revoke( 10176 privileges=privileges, 10177 kind=kind, 10178 securable=securable, 10179 principals=principals, 10180 grant_option=grant_option, 10181 cascade=cascade, 10182 ) 10183 ) 10184 10185 def _parse_overlay(self) -> exp.Overlay: 10186 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10187 return ( 10188 self._parse_bitwise() 10189 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10190 else None 10191 ) 10192 10193 return self.expression( 10194 exp.Overlay( 10195 this=self._parse_bitwise(), 10196 expression=_parse_overlay_arg("PLACING"), 10197 from_=_parse_overlay_arg("FROM"), 10198 for_=_parse_overlay_arg("FOR"), 10199 ) 10200 ) 10201 10202 def _parse_format_name(self) -> exp.Property: 10203 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10204 # for FILE_FORMAT = <format_name> 10205 return self.expression( 10206 exp.Property( 10207 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10208 ) 10209 ) 10210 10211 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10212 is_distinct = self._match(TokenType.DISTINCT) 10213 if not is_distinct: 10214 self._match(TokenType.ALL) 10215 10216 args = [self._parse_lambda()] 10217 if self._match(TokenType.COMMA): 10218 args.extend(self._parse_function_args()) 10219 10220 target = seq_get(args, distinct_index) 10221 if is_distinct and target: 10222 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10223 10224 return func.from_arg_list(args) 10225 10226 def _identifier_expression( 10227 self, token: Token | None = None, quoted: bool | None = None 10228 ) -> exp.Identifier: 10229 token = token or self._prev 10230 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10231 10232 def _build_pipe_cte( 10233 self, 10234 query: exp.Query, 10235 expressions: list[exp.Expr], 10236 alias_cte: exp.TableAlias | None = None, 10237 ) -> exp.Select: 10238 new_cte: str | exp.TableAlias | None 10239 if alias_cte: 10240 new_cte = alias_cte 10241 else: 10242 self._pipe_cte_counter += 1 10243 new_cte = f"__tmp{self._pipe_cte_counter}" 10244 10245 with_ = query.args.get("with_") 10246 ctes = with_.pop() if with_ else None 10247 10248 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10249 if ctes: 10250 new_select.set("with_", ctes) 10251 10252 return new_select.with_(new_cte, as_=query, copy=False) 10253 10254 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10255 select = self._parse_select(consume_pipe=False) 10256 if not select: 10257 return query 10258 10259 return self._build_pipe_cte( 10260 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10261 ) 10262 10263 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10264 limit = self._parse_limit() 10265 offset = self._parse_offset() 10266 if limit: 10267 curr_limit = query.args.get("limit", limit) 10268 if curr_limit.expression.to_py() >= limit.expression.to_py(): 10269 query.limit(limit, copy=False) 10270 if offset: 10271 curr_offset = query.args.get("offset") 10272 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10273 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10274 10275 return query 10276 10277 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10278 this = self._parse_disjunction() 10279 if self._match_text_seq("GROUP", "AND", advance=False): 10280 return this 10281 10282 this = self._parse_alias(this) 10283 10284 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10285 return self._parse_ordered(lambda: this) 10286 10287 return this 10288 10289 def _parse_pipe_syntax_aggregate_group_order_by( 10290 self, query: exp.Select, group_by_exists: bool = True 10291 ) -> exp.Select: 10292 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10293 aggregates_or_groups, orders = [], [] 10294 for element in expr: 10295 if isinstance(element, exp.Ordered): 10296 this = element.this 10297 if isinstance(this, exp.Alias): 10298 element.set("this", this.args["alias"]) 10299 orders.append(element) 10300 else: 10301 this = element 10302 aggregates_or_groups.append(this) 10303 10304 if group_by_exists: 10305 query.select( 10306 *aggregates_or_groups, *query.expressions, append=False, copy=False 10307 ).group_by( 10308 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10309 copy=False, 10310 ) 10311 else: 10312 query.select(*aggregates_or_groups, append=False, copy=False) 10313 10314 if orders: 10315 return query.order_by(*orders, append=False, copy=False) 10316 10317 return query 10318 10319 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10320 self._match_text_seq("AGGREGATE") 10321 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10322 10323 if self._match(TokenType.GROUP_BY) or ( 10324 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10325 ): 10326 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10327 10328 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10329 10330 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10331 first_setop = self.parse_set_operation(this=query) 10332 if not first_setop: 10333 return None 10334 10335 def _parse_and_unwrap_query() -> exp.Expr | None: 10336 expr = self._parse_paren() 10337 return expr.assert_is(exp.Subquery).unnest() if expr else None 10338 10339 first_setop.this.pop() 10340 10341 setops = [ 10342 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10343 *self._parse_csv(_parse_and_unwrap_query), 10344 ] 10345 10346 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10347 with_ = query.args.get("with_") 10348 ctes = with_.pop() if with_ else None 10349 10350 if isinstance(first_setop, exp.Union): 10351 query = query.union(*setops, copy=False, **first_setop.args) 10352 elif isinstance(first_setop, exp.Except): 10353 query = query.except_(*setops, copy=False, **first_setop.args) 10354 else: 10355 query = query.intersect(*setops, copy=False, **first_setop.args) 10356 10357 query.set("with_", ctes) 10358 10359 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10360 10361 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10362 join = self._parse_join() 10363 if not join: 10364 return None 10365 10366 if isinstance(query, exp.Select): 10367 return query.join(join, copy=False) 10368 10369 return query 10370 10371 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10372 pivots = self._parse_pivots() 10373 if not pivots: 10374 return query 10375 10376 from_ = query.args.get("from_") 10377 if from_: 10378 from_.this.set("pivots", pivots) 10379 10380 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10381 10382 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10383 self._match_text_seq("EXTEND") 10384 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10385 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10386 10387 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10388 sample = self._parse_table_sample() 10389 10390 with_ = query.args.get("with_") 10391 if with_: 10392 with_.expressions[-1].this.set("sample", sample) 10393 else: 10394 query.set("sample", sample) 10395 10396 return query 10397 10398 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10399 if isinstance(query, exp.Subquery): 10400 query = exp.select("*").from_(query, copy=False) 10401 10402 if not query.args.get("from_"): 10403 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10404 10405 while self._match(TokenType.PIPE_GT): 10406 start_index = self._index 10407 start_text = self._curr.text.upper() 10408 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10409 if not parser: 10410 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10411 # keywords, making it tricky to disambiguate them without lookahead. The approach 10412 # here is to try and parse a set operation and if that fails, then try to parse a 10413 # join operator. If that fails as well, then the operator is not supported. 10414 parsed_query = self._parse_pipe_syntax_set_operator(query) 10415 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10416 if not parsed_query: 10417 self._retreat(start_index) 10418 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10419 break 10420 query = parsed_query 10421 else: 10422 query = parser(self, query) 10423 10424 return query 10425 10426 def _parse_declareitem(self) -> exp.DeclareItem | None: 10427 self._match_texts(("VAR", "VARIABLE")) 10428 10429 vars = self._parse_csv(self._parse_id_var) 10430 if not vars: 10431 return None 10432 10433 self._match(TokenType.ALIAS) 10434 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10435 default = ( 10436 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10437 ) and self._parse_bitwise() 10438 10439 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10440 10441 def _parse_declare(self) -> exp.Declare | exp.Command: 10442 start = self._prev 10443 replace = self._match_text_seq("OR", "REPLACE") 10444 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10445 10446 if not expressions or self._curr: 10447 return self._parse_as_command(start) 10448 10449 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10450 10451 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10452 exp_class = exp.Cast if strict else exp.TryCast 10453 10454 if exp_class == exp.TryCast: 10455 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10456 10457 return self.expression(exp_class(**kwargs)) 10458 10459 def _parse_json_value(self) -> exp.JSONValue: 10460 this = self._parse_bitwise() 10461 self._match(TokenType.COMMA) 10462 path = self._parse_bitwise() 10463 10464 returning = self._match(TokenType.RETURNING) and self._parse_type() 10465 10466 return self.expression( 10467 exp.JSONValue( 10468 this=this, 10469 path=self.dialect.to_json_path(path), 10470 returning=returning, 10471 on_condition=self._parse_on_condition(), 10472 ) 10473 ) 10474 10475 def _parse_group_concat(self) -> exp.Expr | None: 10476 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10477 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10478 concat_exprs = [ 10479 self.expression( 10480 exp.Concat( 10481 expressions=node.expressions, 10482 safe=True, 10483 coalesce=self.dialect.CONCAT_COALESCE, 10484 ) 10485 ) 10486 ] 10487 node.set("expressions", concat_exprs) 10488 return node 10489 if len(exprs) == 1: 10490 return exprs[0] 10491 return self.expression( 10492 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10493 ) 10494 10495 args = self._parse_csv(self._parse_lambda) 10496 10497 if args: 10498 order = args[-1] if isinstance(args[-1], exp.Order) else None 10499 10500 if order: 10501 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10502 # remove 'expr' from exp.Order and add it back to args 10503 args[-1] = order.this 10504 order.set("this", concat_exprs(order.this, args)) 10505 10506 this = order or concat_exprs(args[0], args) 10507 else: 10508 this = None 10509 10510 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10511 10512 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10513 10514 def _parse_initcap(self) -> exp.Initcap: 10515 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10516 10517 # attach dialect's default delimiters 10518 if expr.args.get("expression") is None: 10519 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10520 10521 return expr 10522 10523 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10524 if not self._match(TokenType.L_PAREN): 10525 self._retreat(self._index - 1) 10526 return None 10527 10528 op = "" 10529 while self._curr and not self._match(TokenType.R_PAREN): 10530 op += self._curr.text 10531 self._advance() 10532 10533 comments = self._prev_comments 10534 return self.expression( 10535 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10536 comments=comments, 10537 )
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( 1638 exp.ParseJSON(this=this, is_literal=True) 1639 ), 1640 } 1641 1642 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1643 1644 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1645 1646 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1647 1648 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1649 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1650 "ISOLATION": ( 1651 ("LEVEL", "REPEATABLE", "READ"), 1652 ("LEVEL", "READ", "COMMITTED"), 1653 ("LEVEL", "READ", "UNCOMITTED"), 1654 ("LEVEL", "SERIALIZABLE"), 1655 ), 1656 "READ": ("WRITE", "ONLY"), 1657 } 1658 1659 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1660 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1661 "DO": ("NOTHING", "UPDATE"), 1662 } 1663 1664 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1665 "INSTEAD": (("OF",),), 1666 "BEFORE": tuple(), 1667 "AFTER": tuple(), 1668 } 1669 1670 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1671 "NOT": (("DEFERRABLE",),), 1672 "DEFERRABLE": tuple(), 1673 } 1674 1675 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1676 "SCALE": ("EXTEND", "NOEXTEND"), 1677 "SHARD": ("EXTEND", "NOEXTEND"), 1678 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1679 **dict.fromkeys( 1680 ( 1681 "SESSION", 1682 "GLOBAL", 1683 "KEEP", 1684 "NOKEEP", 1685 "ORDER", 1686 "NOORDER", 1687 "NOCACHE", 1688 "CYCLE", 1689 "NOCYCLE", 1690 "NOMINVALUE", 1691 "NOMAXVALUE", 1692 "NOSCALE", 1693 "NOSHARD", 1694 ), 1695 tuple(), 1696 ), 1697 } 1698 1699 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1700 1701 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1702 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1703 ) 1704 1705 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1706 1707 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1708 "TYPE": ("EVOLUTION",), 1709 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1710 } 1711 1712 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1713 1714 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1715 ("CALLER", "SELF", "OWNER"), tuple() 1716 ) 1717 1718 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1719 "NOT": ("ENFORCED",), 1720 "MATCH": ( 1721 "FULL", 1722 "PARTIAL", 1723 "SIMPLE", 1724 ), 1725 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1726 "USING": ( 1727 "BTREE", 1728 "HASH", 1729 ), 1730 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1731 } 1732 1733 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1734 "NO": ("OTHERS",), 1735 "CURRENT": ("ROW",), 1736 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1737 } 1738 1739 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1740 1741 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1742 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1743 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1744 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1745 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1746 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1747 ("FOR", "VERSION"): "VERSION", 1748 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1749 ("VERSION", "AS", "OF"): "VERSION", 1750 } 1751 1752 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1753 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1754 1755 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1756 1757 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1758 1759 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1760 1761 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1762 1763 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1764 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1765 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1766 1767 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1768 1769 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1770 1771 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1772 TokenType.CONSTRAINT, 1773 TokenType.FOREIGN_KEY, 1774 TokenType.INDEX, 1775 TokenType.KEY, 1776 TokenType.PRIMARY_KEY, 1777 TokenType.UNIQUE, 1778 } 1779 1780 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1781 1782 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1783 1784 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1785 1786 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1787 "FILE_FORMAT", 1788 "COPY_OPTIONS", 1789 "FORMAT_OPTIONS", 1790 "CREDENTIAL", 1791 } 1792 1793 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1794 1795 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1796 1797 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1798 1799 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1800 1801 # The style options for the DESCRIBE statement 1802 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1803 1804 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1805 1806 # The style options for the ANALYZE statement 1807 ANALYZE_STYLES: t.ClassVar = { 1808 "BUFFER_USAGE_LIMIT", 1809 "FULL", 1810 "LOCAL", 1811 "NO_WRITE_TO_BINLOG", 1812 "SAMPLE", 1813 "SKIP_LOCKED", 1814 "VERBOSE", 1815 } 1816 1817 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1818 "ALL": lambda self: self._parse_analyze_columns(), 1819 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1820 "DELETE": lambda self: self._parse_analyze_delete(), 1821 "DROP": lambda self: self._parse_analyze_histogram(), 1822 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1823 "LIST": lambda self: self._parse_analyze_list(), 1824 "PREDICATE": lambda self: self._parse_analyze_columns(), 1825 "UPDATE": lambda self: self._parse_analyze_histogram(), 1826 "VALIDATE": lambda self: self._parse_analyze_validate(), 1827 } 1828 1829 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1830 1831 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1832 1833 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1834 1835 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1836 1837 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1838 1839 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1840 1841 STRICT_CAST: t.ClassVar = True 1842 1843 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1844 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1845 # Whether an UNPIVOT outputs its value column(s) before the name column 1846 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1847 # Controls when an aggregation's name is included in a pivoted column's name: 1848 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1849 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1850 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1851 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1852 1853 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1854 1855 # Whether the table sample clause expects CSV syntax 1856 TABLESAMPLE_CSV: t.ClassVar = False 1857 1858 # The default method used for table sampling 1859 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1860 1861 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1862 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1863 1864 # Whether the TRIM function expects the characters to trim as its first argument 1865 TRIM_PATTERN_FIRST: t.ClassVar = False 1866 1867 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1868 STRING_ALIASES: t.ClassVar = False 1869 1870 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1871 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1872 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"} 1873 1874 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1875 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1876 1877 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1878 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1879 1880 # Whether the `:` operator is used to extract a value from a VARIANT column 1881 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1882 1883 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1884 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1885 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1886 1887 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1888 # If this is True and '(' is not found, the keyword will be treated as an identifier 1889 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1890 1891 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1892 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1893 1894 # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery) 1895 SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False 1896 1897 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1898 INTERVAL_SPANS: t.ClassVar = True 1899 1900 # Whether a PARTITION clause can follow a table reference 1901 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1902 1903 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1904 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1905 1906 # Whether the 'AS' keyword is optional in the CTE definition syntax 1907 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1908 1909 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1910 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1911 1912 # Whether Alter statements are allowed to contain Partition specifications 1913 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1914 1915 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1916 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1917 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1918 # as BigQuery, where all joins have the same precedence. 1919 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1920 1921 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1922 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1923 1924 # Whether map literals support arbitrary expressions as keys. 1925 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1926 # When False, keys are typically restricted to identifiers. 1927 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1928 1929 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1930 # is true for Snowflake but not for BigQuery which can also process strings 1931 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1932 1933 # Dialects like Databricks support JOINS without join criteria 1934 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 1935 ADD_JOIN_ON_TRUE: t.ClassVar = False 1936 1937 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 1938 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 1939 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 1940 1941 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 1942 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 1943 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 1944 1945 # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause, 1946 # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake) 1947 SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False 1948 1949 # Type names that denote a different type when they're quoted, so quoting has to be 1950 # preserved instead of resolving them into the built-in type of the same name. These 1951 # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR 1952 QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set() 1953 1954 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 1955 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 1956 1957 def __init__( 1958 self, 1959 error_level: ErrorLevel | None = None, 1960 error_message_context: int = 100, 1961 max_errors: int = 3, 1962 max_nodes: int = -1, 1963 dialect: DialectType = None, 1964 ): 1965 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 1966 self.error_message_context: int = error_message_context 1967 self.max_errors: int = max_errors 1968 self.max_nodes: int = max_nodes 1969 self.dialect: t.Any = _resolve_dialect(dialect) 1970 self.sql: str = "" 1971 self.errors: list[ParseError] = [] 1972 self._tokens: list[Token] = [] 1973 self._tokens_size: i64 = 0 1974 self._index: i64 = 0 1975 self._curr: Token = SENTINEL_NONE 1976 self._next: Token = SENTINEL_NONE 1977 self._prev: Token = SENTINEL_NONE 1978 self._prev_comments: list[str] = [] 1979 self._pipe_cte_counter: int = 0 1980 self._chunks: list[list[Token]] = [] 1981 self._chunk_index: i64 = 0 1982 self._node_count: int = 0 1983 1984 def reset(self) -> None: 1985 self.sql = "" 1986 self.errors = [] 1987 self._tokens = [] 1988 self._tokens_size = 0 1989 self._index = 0 1990 self._curr = SENTINEL_NONE 1991 self._next = SENTINEL_NONE 1992 self._prev = SENTINEL_NONE 1993 self._prev_comments = [] 1994 self._pipe_cte_counter = 0 1995 self._chunks = [] 1996 self._chunk_index = 0 1997 self._node_count = 0 1998 1999 def _advance(self, times: i64 = 1) -> None: 2000 index = self._index + times 2001 self._index = index 2002 tokens = self._tokens 2003 size = self._tokens_size 2004 self._curr = tokens[index] if index < size else SENTINEL_NONE 2005 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 2006 2007 if index > 0: 2008 prev = tokens[index - 1] 2009 self._prev = prev 2010 self._prev_comments = prev.comments 2011 else: 2012 self._prev = SENTINEL_NONE 2013 self._prev_comments = [] 2014 2015 def _advance_chunk(self) -> None: 2016 self._index = -1 2017 self._tokens = self._chunks[self._chunk_index] 2018 self._tokens_size = i64(len(self._tokens)) 2019 self._chunk_index += 1 2020 self._advance() 2021 2022 def _retreat(self, index: i64) -> None: 2023 if index != self._index: 2024 self._advance(index - self._index) 2025 2026 def _add_comments(self, expression: exp.Expr | None) -> None: 2027 if expression and self._prev_comments: 2028 expression.add_comments(self._prev_comments) 2029 self._prev_comments = [] 2030 2031 def _match( 2032 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2033 ) -> bool: 2034 if self._curr.token_type == token_type: 2035 if advance: 2036 self._advance() 2037 self._add_comments(expression) 2038 return True 2039 return False 2040 2041 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2042 if self._curr.token_type in types: 2043 if advance: 2044 self._advance() 2045 return True 2046 return False 2047 2048 def _match_pair( 2049 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2050 ) -> bool: 2051 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2052 if advance: 2053 self._advance(2) 2054 return True 2055 return False 2056 2057 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2058 if ( 2059 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2060 and self._curr.text.upper() in texts 2061 ): 2062 if advance: 2063 self._advance() 2064 return True 2065 return False 2066 2067 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2068 index = self._index 2069 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2070 for text in texts: 2071 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2072 self._advance() 2073 else: 2074 self._retreat(index) 2075 return False 2076 2077 if not advance: 2078 self._retreat(index) 2079 2080 return True 2081 2082 def _is_connected(self) -> bool: 2083 prev = self._prev 2084 curr = self._curr 2085 return bool(prev and curr and prev.end + 1 == curr.start) 2086 2087 def _find_sql(self, start: Token, end: Token) -> str: 2088 return self.sql[start.start : end.end + 1] 2089 2090 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2091 token = token or self._curr or self._prev or Token.string("") 2092 formatted_sql, start_context, highlight, end_context = highlight_sql( 2093 sql=self.sql, 2094 positions=[(token.start, token.end)], 2095 context_length=self.error_message_context, 2096 ) 2097 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2098 2099 error = ParseError.new( 2100 formatted_message, 2101 description=message, 2102 line=token.line, 2103 col=token.col, 2104 start_context=start_context, 2105 highlight=highlight, 2106 end_context=end_context, 2107 ) 2108 2109 if self.error_level == ErrorLevel.IMMEDIATE: 2110 raise error 2111 2112 self.errors.append(error) 2113 2114 def validate_expression(self, expression: E, args: list | None = None) -> E: 2115 if self.max_nodes > -1: 2116 self._node_count += 1 2117 if self._node_count > self.max_nodes: 2118 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2119 if self.error_level != ErrorLevel.IGNORE: 2120 for error_message in expression.error_messages(args): 2121 self.raise_error(error_message) 2122 return expression 2123 2124 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2125 index = self._index 2126 error_level = self.error_level 2127 this: T | None = None 2128 2129 self.error_level = ErrorLevel.IMMEDIATE 2130 try: 2131 this = parse_method() 2132 except ParseError: 2133 this = None 2134 finally: 2135 if not this or retreat: 2136 self._retreat(index) 2137 self.error_level = error_level 2138 2139 return this 2140 2141 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2142 """ 2143 Parses a list of tokens and returns a list of syntax trees, one tree 2144 per parsed SQL statement. 2145 2146 Args: 2147 raw_tokens: The list of tokens. 2148 sql: The original SQL string. 2149 2150 Returns: 2151 The list of the produced syntax trees. 2152 """ 2153 return self._parse( 2154 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2155 ) 2156 2157 def parse_into( 2158 self, 2159 expression_types: exp.IntoType, 2160 raw_tokens: list[Token], 2161 sql: str | None = None, 2162 ) -> list[exp.Expr | None]: 2163 """ 2164 Parses a list of tokens into a given Expr type. If a collection of Expr 2165 types is given instead, this method will try to parse the token list into each one 2166 of them, stopping at the first for which the parsing succeeds. 2167 2168 Args: 2169 expression_types: The expression type(s) to try and parse the token list into. 2170 raw_tokens: The list of tokens. 2171 sql: The original SQL string, used to produce helpful debug messages. 2172 2173 Returns: 2174 The target Expr. 2175 """ 2176 errors = [] 2177 for expression_type in ensure_list(expression_types): 2178 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2179 if not parser: 2180 raise TypeError(f"No parser registered for {expression_type}") 2181 2182 try: 2183 return self._parse(parser, raw_tokens, sql) 2184 except ParseError as e: 2185 e.errors[0]["into_expression"] = expression_type 2186 errors.append(e) 2187 2188 raise ParseError( 2189 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2190 errors=merge_errors(errors), 2191 ) from errors[-1] 2192 2193 def check_errors(self) -> None: 2194 """Logs or raises any found errors, depending on the chosen error level setting.""" 2195 if self.error_level == ErrorLevel.WARN: 2196 for error in self.errors: 2197 logger.error(str(error)) 2198 elif self.error_level == ErrorLevel.RAISE and self.errors: 2199 raise ParseError( 2200 concat_messages(self.errors, self.max_errors), 2201 errors=merge_errors(self.errors), 2202 ) 2203 2204 def expression( 2205 self, 2206 instance: E, 2207 token: Token | None = None, 2208 comments: list[str] | None = None, 2209 ) -> E: 2210 if token: 2211 instance.update_positions(token) 2212 instance.add_comments(comments) if comments else self._add_comments(instance) 2213 if not instance.is_primitive: 2214 instance = self.validate_expression(instance) 2215 return instance 2216 2217 def _parse_batch_statements( 2218 self, 2219 parse_method: t.Callable[[Parser], exp.Expr | None], 2220 sep_first_statement: bool = True, 2221 ) -> list[exp.Expr | None]: 2222 expressions = [] 2223 2224 # Chunkification binds if/while statements with the first statement of the body 2225 if sep_first_statement: 2226 self._match(TokenType.BEGIN) 2227 expressions.append(parse_method(self)) 2228 2229 chunks_length = len(self._chunks) 2230 while self._chunk_index < chunks_length: 2231 self._advance_chunk() 2232 2233 if self._match(TokenType.ELSE, advance=False): 2234 return expressions 2235 2236 if expressions and not self._next and self._match(TokenType.END): 2237 expressions.append(exp.EndStatement()) 2238 continue 2239 2240 expressions.append(parse_method(self)) 2241 2242 if self._index < self._tokens_size: 2243 self.raise_error("Invalid expression / Unexpected token") 2244 2245 self.check_errors() 2246 2247 return expressions 2248 2249 def _parse( 2250 self, 2251 parse_method: t.Callable[[Parser], exp.Expr | None], 2252 raw_tokens: list[Token], 2253 sql: str | None = None, 2254 ) -> list[exp.Expr | None]: 2255 self.reset() 2256 self.sql = sql or "" 2257 2258 total = len(raw_tokens) 2259 chunks: list[list[Token]] = [[]] 2260 2261 for i, token in enumerate(raw_tokens): 2262 if token.token_type == TokenType.SEMICOLON: 2263 if token.comments: 2264 chunks.append([token]) 2265 2266 if i < total - 1: 2267 chunks.append([]) 2268 else: 2269 chunks[-1].append(token) 2270 2271 self._chunks = chunks 2272 2273 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2274 2275 def _warn_unsupported(self) -> None: 2276 if self._tokens_size <= 1: 2277 return 2278 2279 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2280 # interested in emitting a warning for the one being currently processed. 2281 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2282 2283 logger.warning( 2284 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2285 ) 2286 2287 def _parse_command(self) -> exp.Command: 2288 self._warn_unsupported() 2289 comments = self._prev_comments 2290 return self.expression( 2291 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2292 comments=comments, 2293 ) 2294 2295 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2296 start = self._prev 2297 exists = self._parse_exists() if allow_exists else None 2298 2299 self._match(TokenType.ON) 2300 2301 materialized = self._match_text_seq("MATERIALIZED") 2302 kind = self._match_set(self.CREATABLES) and self._prev 2303 if not kind: 2304 return self._parse_as_command(start) 2305 2306 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2307 this = self._parse_user_defined_function(kind=kind.token_type) 2308 elif kind.token_type == TokenType.TABLE: 2309 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2310 elif kind.token_type == TokenType.COLUMN: 2311 this = self._parse_column() 2312 else: 2313 this = self._parse_table_parts(schema=True) 2314 2315 self._match(TokenType.IS) 2316 2317 return self.expression( 2318 exp.Comment( 2319 this=this, 2320 kind=kind.text, 2321 expression=self._parse_string(), 2322 exists=exists, 2323 materialized=materialized, 2324 ) 2325 ) 2326 2327 def _parse_to_table( 2328 self, 2329 ) -> exp.ToTableProperty: 2330 table = self._parse_table_parts(schema=True) 2331 return self.expression(exp.ToTableProperty(this=table)) 2332 2333 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2334 def _parse_ttl(self) -> exp.Expr: 2335 def _parse_ttl_action() -> exp.Expr | None: 2336 this = self._parse_bitwise() 2337 2338 if self._match_text_seq("DELETE"): 2339 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2340 if self._match_text_seq("RECOMPRESS"): 2341 return self.expression( 2342 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2343 ) 2344 if self._match_text_seq("TO", "DISK"): 2345 return self.expression( 2346 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2347 ) 2348 if self._match_text_seq("TO", "VOLUME"): 2349 return self.expression( 2350 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2351 ) 2352 2353 return this 2354 2355 expressions = self._parse_csv(_parse_ttl_action) 2356 where = self._parse_where() 2357 group = self._parse_group() 2358 2359 aggregates = None 2360 if group and self._match(TokenType.SET): 2361 aggregates = self._parse_csv(self._parse_set_item) 2362 2363 return self.expression( 2364 exp.MergeTreeTTL( 2365 expressions=expressions, where=where, group=group, aggregates=aggregates 2366 ) 2367 ) 2368 2369 def _parse_condition(self) -> exp.Expr | None: 2370 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2371 2372 def _parse_block(self) -> exp.Block: 2373 return self.expression( 2374 exp.Block( 2375 expressions=self._parse_batch_statements( 2376 parse_method=lambda self: self._parse_statement() 2377 ) 2378 ) 2379 ) 2380 2381 def _parse_whileblock(self) -> exp.WhileBlock: 2382 return self.expression( 2383 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2384 ) 2385 2386 def _parse_statement(self) -> exp.Expr | None: 2387 if not self._curr: 2388 return None 2389 2390 if self._match_set(self.STATEMENT_PARSERS): 2391 comments = self._prev_comments 2392 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2393 stmt.add_comments(comments, prepend=True) 2394 return stmt 2395 2396 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2397 return self._parse_command() 2398 2399 if self._match_text_seq("WHILE"): 2400 return self._parse_whileblock() 2401 2402 expression = self._parse_expression() 2403 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2404 2405 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2406 expression = self._parse_pipe_syntax_query(expression) 2407 2408 return self._parse_query_modifiers(expression) 2409 2410 def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command: 2411 start = self._prev 2412 temporary = self._match(TokenType.TEMPORARY) 2413 materialized = self._match_text_seq("MATERIALIZED") 2414 iceberg = self._match_text_seq("ICEBERG") 2415 2416 kind = self._match_set(self.CREATABLES) and self._prev.text.upper() 2417 if not kind or (iceberg and kind and kind != "TABLE"): 2418 return self._parse_as_command(start) 2419 2420 concurrently = self._match_text_seq("CONCURRENTLY") 2421 if_exists = exists or self._parse_exists() 2422 2423 tables: exp.Expr | list[exp.Expr] | None 2424 if kind == "COLUMN": 2425 tables = self._parse_column() 2426 elif kind in ("TABLE", "VIEW"): 2427 tables = self._parse_csv(lambda: self._parse_table_parts(schema=True)) 2428 else: 2429 tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2430 2431 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 2432 2433 if self._match(TokenType.L_PAREN, advance=False): 2434 expressions = self._parse_wrapped_csv(self._parse_types) 2435 else: 2436 expressions = None 2437 2438 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2439 2440 return self.expression( 2441 exp.Drop( 2442 exists=if_exists, 2443 tables=ensure_list(tables), 2444 expressions=expressions, 2445 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2446 temporary=temporary, 2447 materialized=materialized, 2448 cascade=cascade_or_restrict == "CASCADE", 2449 restrict=cascade_or_restrict == "RESTRICT", 2450 constraints=self._match_text_seq("CONSTRAINTS"), 2451 purge=self._match_text_seq("PURGE"), 2452 cluster=cluster, 2453 concurrently=concurrently, 2454 sync=self._match_text_seq("SYNC"), 2455 iceberg=iceberg, 2456 force=self._match_text_seq("FORCE"), 2457 ) 2458 ) 2459 2460 def _parse_exists(self, not_: bool = False) -> bool | None: 2461 return ( 2462 self._match_text_seq("IF") 2463 and (not not_ or self._match(TokenType.NOT)) 2464 and self._match(TokenType.EXISTS) 2465 ) 2466 2467 def _parse_create(self) -> exp.Create | exp.Command: 2468 # Note: this can't be None because we've matched a statement parser 2469 start = self._prev 2470 2471 replace = ( 2472 start.token_type == TokenType.REPLACE 2473 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2474 or self._match_pair(TokenType.OR, TokenType.ALTER) 2475 ) 2476 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2477 2478 unique = self._match(TokenType.UNIQUE) 2479 2480 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2481 clustered = True 2482 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2483 "COLUMNSTORE" 2484 ): 2485 clustered = False 2486 else: 2487 clustered = None 2488 2489 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2490 self._advance() 2491 2492 properties = None 2493 create_token = self._match_set(self.CREATABLES) and self._prev 2494 2495 if not create_token: 2496 # exp.Properties.Location.POST_CREATE 2497 properties = self._parse_properties() 2498 create_token = self._match_set(self.CREATABLES) and self._prev 2499 2500 if not properties or not create_token: 2501 return self._parse_as_command(start) 2502 2503 create_token_type = t.cast(Token, create_token).token_type 2504 2505 concurrently = self._match_text_seq("CONCURRENTLY") 2506 exists = self._parse_exists(not_=True) 2507 this = None 2508 expression: exp.Expr | None = None 2509 indexes = None 2510 no_schema_binding = None 2511 begin = None 2512 clone = None 2513 2514 def extend_props(temp_props: exp.Properties | None) -> None: 2515 nonlocal properties 2516 if properties and temp_props: 2517 properties.expressions.extend(temp_props.expressions) 2518 elif temp_props: 2519 properties = temp_props 2520 2521 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2522 this = self._parse_user_defined_function(kind=create_token_type) 2523 2524 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2525 extend_props(self._parse_properties()) 2526 2527 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2528 2529 if ( 2530 not expression 2531 and create_token_type == TokenType.FUNCTION 2532 and isinstance(this, exp.UserDefinedFunction) 2533 and this.args.get("wrapped") 2534 ): 2535 pre_table_index = self._index 2536 is_table = self._match(TokenType.TABLE) 2537 2538 expression = self._parse_expression() 2539 overload_mode = bool( 2540 expression 2541 and self._curr.token_type == TokenType.COMMA 2542 and self._next.token_type == TokenType.L_PAREN 2543 ) 2544 if not overload_mode: 2545 self._retreat(pre_table_index) 2546 is_table = False 2547 expression = None 2548 else: 2549 is_table = False 2550 overload_mode = False 2551 2552 extend_props(self._parse_function_properties()) 2553 2554 if not expression: 2555 if self._match(TokenType.COMMAND): 2556 expression = self._parse_as_command(self._prev) 2557 else: 2558 begin = self._match(TokenType.BEGIN) 2559 return_ = self._match_text_seq("RETURN") 2560 2561 if self._match(TokenType.STRING, advance=False): 2562 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2563 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2564 expression = self._parse_string() 2565 extend_props(self._parse_properties()) 2566 else: 2567 expression = ( 2568 self._parse_user_defined_function_expression() 2569 if create_token_type == TokenType.FUNCTION 2570 else self._parse_block() 2571 ) 2572 2573 if return_: 2574 expression = self.expression(exp.Return(this=expression)) 2575 2576 if overload_mode and expression: 2577 expression = self._parse_macro_overloads( 2578 t.cast(exp.UserDefinedFunction, this), expression, is_table 2579 ) 2580 elif create_token_type == TokenType.INDEX: 2581 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2582 if not self._match(TokenType.ON): 2583 index = self._parse_id_var() 2584 anonymous = False 2585 else: 2586 index = None 2587 anonymous = True 2588 2589 this = self._parse_index(index=index, anonymous=anonymous) 2590 elif ( 2591 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2592 ) or create_token_type == TokenType.TRIGGER: 2593 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2594 create_token = self._prev 2595 2596 trigger_name = self._parse_id_var() 2597 if not trigger_name: 2598 return self._parse_as_command(start) 2599 2600 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2601 timing = timing_var.this if timing_var else None 2602 if not timing: 2603 return self._parse_as_command(start) 2604 2605 events = self._parse_trigger_events() 2606 if not self._match(TokenType.ON): 2607 self.raise_error("Expected ON in trigger definition") 2608 2609 table = self._parse_table_parts() 2610 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2611 deferrable, initially = self._parse_trigger_deferrable() 2612 referencing = self._parse_trigger_referencing() 2613 for_each = self._parse_trigger_for_each() 2614 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2615 self._parse_disjunction, optional=True 2616 ) 2617 execute = self._parse_trigger_execute() 2618 2619 if execute is None: 2620 return self._parse_as_command(start) 2621 2622 trigger_props = self.expression( 2623 exp.TriggerProperties( 2624 table=table, 2625 timing=timing, 2626 events=events, 2627 execute=execute, 2628 constraint=is_constraint, 2629 referenced_table=referenced_table, 2630 deferrable=deferrable, 2631 initially=initially, 2632 referencing=referencing, 2633 for_each=for_each, 2634 when=when, 2635 ) 2636 ) 2637 2638 this = trigger_name 2639 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2640 elif create_token_type == TokenType.TYPE: 2641 this = self._parse_table_parts(schema=True) 2642 if not this or not self._match(TokenType.ALIAS): 2643 return self._parse_as_command(start) 2644 2645 if self._match(TokenType.ENUM): 2646 expression = exp.DataType( 2647 this=exp.DType.ENUM, 2648 expressions=self._parse_wrapped_csv(self._parse_string), 2649 ) 2650 elif self._match(TokenType.L_PAREN, advance=False): 2651 expression = self._parse_schema() 2652 else: 2653 return self._parse_as_command(start) 2654 elif create_token_type in self.DB_CREATABLES: 2655 table_parts = self._parse_table_parts( 2656 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2657 ) 2658 2659 # exp.Properties.Location.POST_NAME 2660 self._match(TokenType.COMMA) 2661 extend_props(self._parse_properties(before=True)) 2662 2663 this = self._parse_schema(this=table_parts) 2664 2665 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2666 extend_props(self._parse_properties()) 2667 2668 has_alias = self._match(TokenType.ALIAS) 2669 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2670 # exp.Properties.Location.POST_ALIAS 2671 extend_props(self._parse_properties()) 2672 2673 if create_token_type == TokenType.SEQUENCE: 2674 expression = self._parse_types() 2675 props = self._parse_properties() 2676 if props: 2677 sequence_props = exp.SequenceProperties() 2678 options = [] 2679 for prop in props: 2680 if isinstance(prop, exp.SequenceProperties): 2681 for arg, value in prop.args.items(): 2682 if arg == "options": 2683 options.extend(value) 2684 else: 2685 sequence_props.set(arg, value) 2686 prop.pop() 2687 2688 if options: 2689 sequence_props.set("options", options) 2690 2691 props.append("expressions", sequence_props) 2692 extend_props(props) 2693 else: 2694 expression = self._parse_ddl_select() 2695 2696 # Some dialects also support using a table as an alias instead of a SELECT. 2697 # Here we fallback to this as an alternative. 2698 if not expression and has_alias: 2699 expression = self._try_parse(self._parse_table_parts) 2700 2701 if create_token_type == TokenType.TABLE: 2702 # exp.Properties.Location.POST_EXPRESSION 2703 extend_props(self._parse_properties()) 2704 2705 indexes = [] 2706 while True: 2707 index = self._parse_index() 2708 2709 # exp.Properties.Location.POST_INDEX 2710 extend_props(self._parse_properties()) 2711 if not index: 2712 break 2713 else: 2714 self._match(TokenType.COMMA) 2715 indexes.append(index) 2716 elif create_token_type == TokenType.VIEW: 2717 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2718 no_schema_binding = True 2719 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2720 extend_props(self._parse_properties()) 2721 2722 shallow = self._match_text_seq("SHALLOW") 2723 2724 if self._match_texts(self.CLONE_KEYWORDS): 2725 copy = self._prev.text.lower() == "copy" 2726 clone = self.expression( 2727 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2728 ) 2729 2730 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2731 return self._parse_as_command(start) 2732 2733 create_kind_text = create_token.text.upper() 2734 return self.expression( 2735 exp.Create( 2736 this=this, 2737 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2738 replace=replace, 2739 refresh=refresh, 2740 unique=unique, 2741 expression=expression, 2742 exists=exists, 2743 properties=properties, 2744 indexes=indexes, 2745 no_schema_binding=no_schema_binding, 2746 begin=begin, 2747 clone=clone, 2748 concurrently=concurrently, 2749 clustered=clustered, 2750 ) 2751 ) 2752 2753 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2754 seq = exp.SequenceProperties() 2755 2756 options = [] 2757 index = self._index 2758 2759 while self._curr: 2760 self._match(TokenType.COMMA) 2761 if self._match_text_seq("INCREMENT"): 2762 self._match_text_seq("BY") 2763 self._match_text_seq("=") 2764 seq.set("increment", self._parse_term()) 2765 elif self._match_text_seq("MINVALUE"): 2766 seq.set("minvalue", self._parse_term()) 2767 elif self._match_text_seq("MAXVALUE"): 2768 seq.set("maxvalue", self._parse_term()) 2769 elif self._match_text_seq("START"): 2770 self._match_text_seq("WITH") 2771 self._match_text_seq("=") 2772 seq.set("start", self._parse_term()) 2773 elif self._match_text_seq("CACHE"): 2774 # T-SQL allows empty CACHE which is initialized dynamically 2775 seq.set("cache", self._parse_number() or True) 2776 elif self._match_text_seq("OWNED", "BY"): 2777 # "OWNED BY NONE" is the default 2778 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2779 else: 2780 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2781 if opt: 2782 options.append(opt) 2783 else: 2784 break 2785 2786 seq.set("options", options if options else None) 2787 return None if self._index == index else seq 2788 2789 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2790 events = [] 2791 2792 while True: 2793 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2794 2795 if not event_type: 2796 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2797 2798 columns = ( 2799 self._parse_csv(self._parse_column) 2800 if event_type == "UPDATE" and self._match_text_seq("OF") 2801 else None 2802 ) 2803 2804 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2805 2806 if not self._match(TokenType.OR): 2807 break 2808 2809 return events 2810 2811 def _parse_trigger_deferrable( 2812 self, 2813 ) -> tuple[str | None, str | None]: 2814 deferrable_var = self._parse_var_from_options( 2815 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2816 ) 2817 deferrable = deferrable_var.this if deferrable_var else None 2818 2819 initially = None 2820 if deferrable and self._match_text_seq("INITIALLY"): 2821 initially = ( 2822 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2823 ) 2824 2825 return deferrable, initially 2826 2827 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2828 if not self._match_text_seq(keyword): 2829 return None 2830 if not self._match_text_seq("TABLE"): 2831 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2832 self._match_text_seq("AS") 2833 return self._parse_id_var() 2834 2835 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2836 if not self._match_text_seq("REFERENCING"): 2837 return None 2838 2839 old_alias = None 2840 new_alias = None 2841 2842 while True: 2843 if alias := self._parse_trigger_referencing_clause("OLD"): 2844 if old_alias is not None: 2845 self.raise_error("Duplicate OLD clause in REFERENCING") 2846 old_alias = alias 2847 elif alias := self._parse_trigger_referencing_clause("NEW"): 2848 if new_alias is not None: 2849 self.raise_error("Duplicate NEW clause in REFERENCING") 2850 new_alias = alias 2851 else: 2852 break 2853 2854 if old_alias is None and new_alias is None: 2855 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2856 2857 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2858 2859 def _parse_trigger_for_each(self) -> str | None: 2860 if not self._match_text_seq("FOR", "EACH"): 2861 return None 2862 2863 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2864 2865 def _parse_trigger_execute(self) -> exp.TriggerExecute | None: 2866 if not self._match(TokenType.EXECUTE): 2867 return None 2868 2869 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2870 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2871 2872 func_call = self._parse_column() 2873 return self.expression(exp.TriggerExecute(this=func_call)) 2874 2875 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2876 # only used for teradata currently 2877 self._match(TokenType.COMMA) 2878 2879 kwargs = { 2880 "no": self._match_text_seq("NO"), 2881 "dual": self._match_text_seq("DUAL"), 2882 "before": self._match_text_seq("BEFORE"), 2883 "default": self._match_text_seq("DEFAULT"), 2884 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2885 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2886 "after": self._match_text_seq("AFTER"), 2887 "minimum": self._match_texts(("MIN", "MINIMUM")), 2888 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2889 } 2890 2891 if self._match_texts(self.PROPERTY_PARSERS): 2892 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2893 try: 2894 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2895 except TypeError: 2896 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2897 2898 if self._match_text_seq("CHARACTER", "SET"): 2899 return self._parse_character_set(default=bool(kwargs["default"])) 2900 2901 return None 2902 2903 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2904 return self._parse_wrapped_csv(self._parse_property) 2905 2906 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2907 if self._match_texts(self.PROPERTY_PARSERS): 2908 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2909 2910 if self._match_text_seq("CHARACTER", "SET"): 2911 return self._parse_character_set() 2912 2913 if self._match(TokenType.DEFAULT): 2914 if self._match_texts(self.PROPERTY_PARSERS): 2915 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 2916 2917 if self._match_text_seq("CHARACTER", "SET"): 2918 return self._parse_character_set(default=True) 2919 2920 if self._match_text_seq("COMPOUND", "SORTKEY"): 2921 return self._parse_sortkey(compound=True) 2922 2923 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 2924 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 2925 2926 if self._match_text_seq("NOT", "DETERMINISTIC"): 2927 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 2928 2929 index = self._index 2930 2931 seq_props = self._parse_sequence_properties() 2932 if seq_props: 2933 return seq_props 2934 2935 self._retreat(index) 2936 return self._parse_key_value_property() 2937 2938 def _parse_key_value_property( 2939 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 2940 ) -> exp.Property | None: 2941 index = self._index 2942 key = self._parse_column() 2943 2944 if not self._match(TokenType.EQ): 2945 self._retreat(index) 2946 return None 2947 2948 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 2949 if isinstance(key, exp.Column): 2950 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 2951 2952 value = ( 2953 parse_value() 2954 if parse_value 2955 else self._parse_bitwise() or self._parse_var(any_token=True) 2956 ) 2957 2958 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 2959 if isinstance(value, exp.Column): 2960 value = exp.var(value.name) 2961 2962 return self.expression(exp.Property(this=key, value=value)) 2963 2964 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 2965 if self._match_text_seq("BY"): 2966 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 2967 2968 self._match(TokenType.ALIAS) 2969 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 2970 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 2971 2972 return self.expression( 2973 exp.FileFormatProperty( 2974 this=( 2975 self.expression( 2976 exp.InputOutputFormat( 2977 input_format=input_format, output_format=output_format 2978 ) 2979 ) 2980 if input_format or output_format 2981 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 2982 ), 2983 hive_format=True, 2984 ) 2985 ) 2986 2987 def _parse_unquoted_field(self) -> exp.Expr | None: 2988 field = self._parse_field() 2989 if isinstance(field, exp.Identifier) and not field.quoted: 2990 field = exp.var(field) 2991 2992 return field 2993 2994 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 2995 self._match(TokenType.EQ) 2996 self._match(TokenType.ALIAS) 2997 2998 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 2999 3000 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 3001 properties = [] 3002 while True: 3003 if before: 3004 prop = self._parse_property_before() 3005 else: 3006 prop = self._parse_property() 3007 if not prop: 3008 break 3009 for p in ensure_list(prop): 3010 properties.append(p) 3011 3012 if properties: 3013 return self.expression(exp.Properties(expressions=properties)) 3014 3015 return None 3016 3017 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3018 return self.expression( 3019 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3020 ) 3021 3022 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3023 return self.expression( 3024 exp.SqlSecurityProperty( 3025 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3026 ) 3027 ) 3028 3029 def _parse_settings_property(self) -> exp.SettingsProperty: 3030 return self.expression( 3031 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3032 ) 3033 3034 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3035 if not self._match_text_seq("ON", "NULL", "INPUT"): 3036 self._retreat(self._index - 1) 3037 return None 3038 3039 return self.expression(exp.CalledOnNullInputProperty()) 3040 3041 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3042 if self._index >= 2: 3043 pre_volatile_token = self._tokens[self._index - 2] 3044 else: 3045 pre_volatile_token = None 3046 3047 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3048 return exp.VolatileProperty() 3049 3050 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3051 3052 def _parse_retention_period(self) -> exp.Var: 3053 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3054 number = self._parse_number() 3055 number_str = f"{number} " if number else "" 3056 unit = self._parse_var(any_token=True) 3057 return exp.var(f"{number_str}{unit}") 3058 3059 def _parse_system_versioning_property( 3060 self, with_: bool = False 3061 ) -> exp.WithSystemVersioningProperty: 3062 self._match(TokenType.EQ) 3063 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3064 3065 if self._match_text_seq("OFF"): 3066 prop.set("on", False) 3067 return prop 3068 3069 self._match(TokenType.ON) 3070 if self._match(TokenType.L_PAREN): 3071 while self._curr and not self._match(TokenType.R_PAREN): 3072 if self._match_text_seq("HISTORY_TABLE", "="): 3073 prop.set("this", self._parse_table_parts()) 3074 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3075 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3076 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3077 prop.set("retention_period", self._parse_retention_period()) 3078 3079 self._match(TokenType.COMMA) 3080 3081 return prop 3082 3083 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3084 self._match(TokenType.EQ) 3085 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3086 prop = self.expression(exp.DataDeletionProperty(on=on)) 3087 3088 if self._match(TokenType.L_PAREN): 3089 while self._curr and not self._match(TokenType.R_PAREN): 3090 if self._match_text_seq("FILTER_COLUMN", "="): 3091 prop.set("filter_column", self._parse_column()) 3092 elif self._match_text_seq("RETENTION_PERIOD", "="): 3093 prop.set("retention_period", self._parse_retention_period()) 3094 3095 self._match(TokenType.COMMA) 3096 3097 return prop 3098 3099 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3100 kind = "HASH" 3101 expressions: list[exp.Expr] | None = None 3102 if self._match_text_seq("BY", "HASH"): 3103 expressions = self._parse_wrapped_csv(self._parse_id_var) 3104 elif self._match_text_seq("BY", "RANDOM"): 3105 kind = "RANDOM" 3106 3107 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3108 buckets: exp.Expr | None = None 3109 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3110 buckets = self._parse_number() 3111 3112 return self.expression( 3113 exp.DistributedByProperty( 3114 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3115 ) 3116 ) 3117 3118 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3119 self._match_text_seq("KEY") 3120 expressions = self._parse_wrapped_id_vars() 3121 return self.expression(expr_type(expressions=expressions)) 3122 3123 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3124 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3125 prop = self._parse_system_versioning_property(with_=True) 3126 self._match_r_paren() 3127 return prop 3128 3129 if self._match(TokenType.L_PAREN, advance=False): 3130 result: list[exp.Expr] = [] 3131 for i in self._parse_wrapped_properties(): 3132 result.extend(i) if isinstance(i, list) else result.append(i) 3133 return result 3134 3135 if self._match_text_seq("JOURNAL"): 3136 return self._parse_withjournaltable() 3137 3138 if self._match_texts(self.VIEW_ATTRIBUTES): 3139 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3140 3141 if self._match_text_seq("DATA"): 3142 return self._parse_withdata(no=False) 3143 elif self._match_text_seq("NO", "DATA"): 3144 return self._parse_withdata(no=True) 3145 3146 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3147 return self._parse_serde_properties(with_=True) 3148 3149 if self._match(TokenType.SCHEMA): 3150 return self.expression( 3151 exp.WithSchemaBindingProperty( 3152 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3153 ) 3154 ) 3155 3156 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3157 return self.expression( 3158 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3159 ) 3160 3161 if not self._next: 3162 return None 3163 3164 return self._parse_withisolatedloading() 3165 3166 def _parse_procedure_option(self) -> exp.Expr | None: 3167 if self._match_text_seq("EXECUTE", "AS"): 3168 return self.expression( 3169 exp.ExecuteAsProperty( 3170 this=self._parse_var_from_options( 3171 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3172 ) 3173 or self._parse_string() 3174 ) 3175 ) 3176 3177 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3178 3179 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3180 def _parse_definer(self) -> exp.DefinerProperty | None: 3181 self._match(TokenType.EQ) 3182 3183 user = self._parse_id_var() 3184 self._match(TokenType.PARAMETER) 3185 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3186 3187 if not user or not host: 3188 return None 3189 3190 return exp.DefinerProperty(this=f"{user}@{host}") 3191 3192 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3193 self._match(TokenType.TABLE) 3194 self._match(TokenType.EQ) 3195 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3196 3197 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3198 return self.expression(exp.LogProperty(no=no)) 3199 3200 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3201 return self.expression(exp.JournalProperty(**kwargs)) 3202 3203 def _parse_checksum(self) -> exp.ChecksumProperty: 3204 self._match(TokenType.EQ) 3205 3206 on = None 3207 if self._match(TokenType.ON): 3208 on = True 3209 elif self._match_text_seq("OFF"): 3210 on = False 3211 3212 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3213 3214 def _parse_cluster(self) -> exp.Cluster: 3215 self._match(TokenType.CLUSTER_BY) 3216 return self.expression( 3217 exp.Cluster( 3218 expressions=self._parse_csv(self._parse_column), 3219 ) 3220 ) 3221 3222 def _parse_cluster_property(self) -> exp.ClusterProperty: 3223 return self.expression( 3224 exp.ClusterProperty( 3225 expressions=self._parse_wrapped_csv(self._parse_column), 3226 ) 3227 ) 3228 3229 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3230 self._match_text_seq("BY") 3231 3232 self._match_l_paren() 3233 expressions = self._parse_csv(self._parse_column) 3234 self._match_r_paren() 3235 3236 if self._match_text_seq("SORTED", "BY"): 3237 self._match_l_paren() 3238 sorted_by = self._parse_csv(self._parse_ordered) 3239 self._match_r_paren() 3240 else: 3241 sorted_by = None 3242 3243 self._match(TokenType.INTO) 3244 buckets = self._parse_number() 3245 self._match_text_seq("BUCKETS") 3246 3247 return self.expression( 3248 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3249 ) 3250 3251 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3252 if not self._match_text_seq("GRANTS"): 3253 self._retreat(self._index - 1) 3254 return None 3255 3256 return self.expression(exp.CopyGrantsProperty()) 3257 3258 def _parse_freespace(self) -> exp.FreespaceProperty: 3259 self._match(TokenType.EQ) 3260 return self.expression( 3261 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3262 ) 3263 3264 def _parse_mergeblockratio( 3265 self, no: bool = False, default: bool = False 3266 ) -> exp.MergeBlockRatioProperty: 3267 if self._match(TokenType.EQ): 3268 return self.expression( 3269 exp.MergeBlockRatioProperty( 3270 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3271 ) 3272 ) 3273 3274 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3275 3276 def _parse_datablocksize( 3277 self, 3278 default: bool | None = None, 3279 minimum: bool | None = None, 3280 maximum: bool | None = None, 3281 ) -> exp.DataBlocksizeProperty: 3282 self._match(TokenType.EQ) 3283 size = self._parse_number() 3284 3285 units = None 3286 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3287 units = self._prev.text 3288 3289 return self.expression( 3290 exp.DataBlocksizeProperty( 3291 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3292 ) 3293 ) 3294 3295 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3296 self._match(TokenType.EQ) 3297 always = self._match_text_seq("ALWAYS") 3298 manual = self._match_text_seq("MANUAL") 3299 never = self._match_text_seq("NEVER") 3300 default = self._match_text_seq("DEFAULT") 3301 3302 autotemp = None 3303 if self._match_text_seq("AUTOTEMP"): 3304 autotemp = self._parse_schema() 3305 3306 return self.expression( 3307 exp.BlockCompressionProperty( 3308 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3309 ) 3310 ) 3311 3312 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3313 index = self._index 3314 no = self._match_text_seq("NO") 3315 concurrent = self._match_text_seq("CONCURRENT") 3316 3317 if not self._match_text_seq("ISOLATED", "LOADING"): 3318 self._retreat(index) 3319 return None 3320 3321 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3322 return self.expression( 3323 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3324 ) 3325 3326 def _parse_locking(self) -> exp.LockingProperty: 3327 if self._match(TokenType.TABLE): 3328 kind = "TABLE" 3329 elif self._match(TokenType.VIEW): 3330 kind = "VIEW" 3331 elif self._match(TokenType.ROW): 3332 kind = "ROW" 3333 elif self._match_text_seq("DATABASE"): 3334 kind = "DATABASE" 3335 else: 3336 kind = None 3337 3338 if kind in ("DATABASE", "TABLE", "VIEW"): 3339 this = self._parse_table_parts() 3340 else: 3341 this = None 3342 3343 if self._match(TokenType.FOR): 3344 for_or_in = "FOR" 3345 elif self._match(TokenType.IN): 3346 for_or_in = "IN" 3347 else: 3348 for_or_in = None 3349 3350 if self._match_text_seq("ACCESS"): 3351 lock_type = "ACCESS" 3352 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3353 lock_type = "EXCLUSIVE" 3354 elif self._match_text_seq("SHARE"): 3355 lock_type = "SHARE" 3356 elif self._match_text_seq("READ"): 3357 lock_type = "READ" 3358 elif self._match_text_seq("WRITE"): 3359 lock_type = "WRITE" 3360 elif self._match_text_seq("CHECKSUM"): 3361 lock_type = "CHECKSUM" 3362 else: 3363 lock_type = None 3364 3365 override = self._match_text_seq("OVERRIDE") 3366 3367 return self.expression( 3368 exp.LockingProperty( 3369 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3370 ) 3371 ) 3372 3373 def _parse_partition_by(self) -> list[exp.Expr]: 3374 if self._match(TokenType.PARTITION_BY): 3375 return self._parse_csv(self._parse_disjunction) 3376 return [] 3377 3378 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3379 def _parse_partition_bound_expr() -> exp.Expr | None: 3380 if self._match_text_seq("MINVALUE"): 3381 return exp.var("MINVALUE") 3382 if self._match_text_seq("MAXVALUE"): 3383 return exp.var("MAXVALUE") 3384 return self._parse_bitwise() 3385 3386 this: exp.Expr | list[exp.Expr] | None = None 3387 expression = None 3388 from_expressions = None 3389 to_expressions = None 3390 3391 if self._match(TokenType.IN): 3392 this = self._parse_wrapped_csv(self._parse_bitwise) 3393 elif self._match(TokenType.FROM): 3394 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3395 self._match_text_seq("TO") 3396 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3397 elif self._match_text_seq("WITH", "(", "MODULUS"): 3398 this = self._parse_number() 3399 self._match_text_seq(",", "REMAINDER") 3400 expression = self._parse_number() 3401 self._match_r_paren() 3402 else: 3403 self.raise_error("Failed to parse partition bound spec.") 3404 3405 return self.expression( 3406 exp.PartitionBoundSpec( 3407 this=this, 3408 expression=expression, 3409 from_expressions=from_expressions, 3410 to_expressions=to_expressions, 3411 ) 3412 ) 3413 3414 # https://www.postgresql.org/docs/current/sql-createtable.html 3415 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3416 if not self._match_text_seq("OF"): 3417 self._retreat(self._index - 1) 3418 return None 3419 3420 this = self._parse_table(schema=True) 3421 3422 if self._match(TokenType.DEFAULT): 3423 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3424 elif self._match_text_seq("FOR", "VALUES"): 3425 expression = self._parse_partition_bound_spec() 3426 else: 3427 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3428 3429 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3430 3431 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3432 self._match(TokenType.EQ) 3433 return self.expression( 3434 exp.PartitionedByProperty( 3435 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3436 ) 3437 ) 3438 3439 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3440 if self._match_text_seq("AND", "STATISTICS"): 3441 statistics = True 3442 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3443 statistics = False 3444 else: 3445 statistics = None 3446 3447 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3448 3449 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3450 if self._match_text_seq("SQL"): 3451 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3452 return None 3453 3454 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3455 if self._match_text_seq("SQL", "DATA"): 3456 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3457 return None 3458 3459 def _parse_no_property(self) -> exp.Expr | None: 3460 if self._match_text_seq("PRIMARY", "INDEX"): 3461 return exp.NoPrimaryIndexProperty() 3462 if self._match_text_seq("SQL"): 3463 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3464 return None 3465 3466 def _parse_on_property(self) -> exp.Expr | None: 3467 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3468 return exp.OnCommitProperty() 3469 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3470 return exp.OnCommitProperty(delete=True) 3471 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3472 3473 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3474 if self._match_text_seq("SQL", "DATA"): 3475 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3476 return None 3477 3478 def _parse_distkey(self) -> exp.DistKeyProperty: 3479 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3480 3481 def _parse_create_like(self) -> exp.LikeProperty | None: 3482 table = self._parse_table(schema=True) 3483 3484 options = [] 3485 while self._match_texts(("INCLUDING", "EXCLUDING")): 3486 this = self._prev.text.upper() 3487 3488 id_var = self._parse_id_var() 3489 if not id_var: 3490 return None 3491 3492 options.append( 3493 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3494 ) 3495 3496 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3497 3498 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3499 return self.expression( 3500 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3501 ) 3502 3503 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3504 self._match(TokenType.EQ) 3505 return self.expression( 3506 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3507 ) 3508 3509 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3510 self._match_text_seq("WITH", "CONNECTION") 3511 return self.expression( 3512 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3513 ) 3514 3515 def _parse_returns(self) -> exp.ReturnsProperty: 3516 value: exp.Expr | None 3517 null = None 3518 is_table = self._match(TokenType.TABLE) 3519 3520 if is_table: 3521 if self._match(TokenType.LT): 3522 value = self.expression( 3523 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3524 ) 3525 if not self._match(TokenType.GT): 3526 self.raise_error("Expecting >") 3527 else: 3528 value = self._parse_schema(exp.var("TABLE")) 3529 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3530 null = True 3531 value = None 3532 else: 3533 value = self._parse_types() 3534 3535 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3536 3537 def _parse_describe(self) -> exp.Describe: 3538 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3539 style: str | None = ( 3540 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3541 ) 3542 if self._match(TokenType.DOT): 3543 style = None 3544 self._retreat(self._index - 2) 3545 3546 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3547 3548 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3549 this = self._parse_statement() 3550 else: 3551 this = self._parse_table(schema=True) 3552 3553 properties = self._parse_properties() 3554 expressions = properties.expressions if properties else None 3555 partition = self._parse_partition() 3556 return self.expression( 3557 exp.Describe( 3558 this=this, 3559 style=style, 3560 kind=kind, 3561 expressions=expressions, 3562 partition=partition, 3563 format=format, 3564 as_json=self._match_text_seq("AS", "JSON"), 3565 ) 3566 ) 3567 3568 def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts: 3569 kind = self._prev.text.upper() 3570 expressions = [] 3571 3572 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3573 if self._match(TokenType.WHEN): 3574 expression = self._parse_disjunction() 3575 self._match(TokenType.THEN) 3576 else: 3577 expression = None 3578 3579 else_ = self._match(TokenType.ELSE) 3580 3581 if not self._match(TokenType.INTO): 3582 return None 3583 3584 return self.expression( 3585 exp.ConditionalInsert( 3586 this=self.expression( 3587 exp.Insert( 3588 this=self._parse_table(schema=True), 3589 expression=self._parse_derived_table_values(), 3590 ) 3591 ), 3592 expression=expression, 3593 else_=else_, 3594 ) 3595 ) 3596 3597 expression = parse_conditional_insert() 3598 while expression is not None: 3599 expressions.append(expression) 3600 expression = parse_conditional_insert() 3601 3602 return self.expression( 3603 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3604 comments=comments, 3605 ) 3606 3607 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3608 comments: list[str] = [] 3609 hint = self._parse_hint() 3610 overwrite = self._match(TokenType.OVERWRITE) 3611 ignore = self._match(TokenType.IGNORE) 3612 local = self._match_text_seq("LOCAL") 3613 alternative = None 3614 is_function = None 3615 3616 if self._match_text_seq("DIRECTORY"): 3617 this: exp.Expr | None = self.expression( 3618 exp.Directory( 3619 this=self._parse_var_or_string(), 3620 local=local, 3621 row_format=self._parse_row_format(match_row=True), 3622 ) 3623 ) 3624 else: 3625 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3626 comments += ensure_list(self._prev_comments) 3627 return self._parse_multitable_inserts(comments) 3628 3629 if self._match(TokenType.OR): 3630 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3631 3632 self._match(TokenType.INTO) 3633 comments += ensure_list(self._prev_comments) 3634 self._match(TokenType.TABLE) 3635 is_function = self._match(TokenType.FUNCTION) 3636 3637 this = self._parse_function() if is_function else self._parse_insert_table() 3638 3639 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3640 set_values = None 3641 if self._match(TokenType.SET): 3642 columns = [] 3643 values = [] 3644 3645 def _parse_set_assignment() -> exp.Expr | None: 3646 target = self._parse_column() 3647 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3648 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3649 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3650 else: 3651 value = self._parse_disjunction() 3652 3653 if value: 3654 columns.append(target.this) 3655 values.append(value) 3656 return value 3657 3658 self.raise_error("Expected column assignment in INSERT ... SET") 3659 return None 3660 3661 self._parse_csv(_parse_set_assignment) 3662 3663 this = self.expression(exp.Schema(this=this, expressions=columns)) 3664 set_values = self.expression( 3665 exp.Values( 3666 expressions=[exp.Tuple(expressions=values)], 3667 alias=self._parse_table_alias(), 3668 ) 3669 ) 3670 3671 returning = self._parse_returning() # TSQL allows RETURNING before source 3672 3673 stored = self._match_text_seq("STORED") and self._parse_stored() 3674 by_name = self._match_text_seq("BY", "NAME") 3675 exists = self._parse_exists() 3676 replace_where = None 3677 replace_using = None 3678 3679 if self._match(TokenType.REPLACE): 3680 if self._match(TokenType.WHERE): 3681 replace_where = self._parse_disjunction() 3682 elif self._match(TokenType.USING): 3683 replace_using = self._parse_using_identifiers() 3684 3685 return self.expression( 3686 exp.Insert( 3687 hint=hint, 3688 is_function=is_function, 3689 this=this, 3690 stored=stored, 3691 by_name=by_name, 3692 exists=exists, 3693 where=replace_where, 3694 using=replace_using, 3695 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3696 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3697 default=self._match_text_seq("DEFAULT", "VALUES"), 3698 expression=set_values 3699 or self._parse_derived_table_values(allow_value_synonym=True) 3700 or self._parse_ddl_select(), 3701 conflict=self._parse_on_conflict(), 3702 returning=returning or self._parse_returning(), 3703 overwrite=overwrite, 3704 alternative=alternative, 3705 ignore=ignore, 3706 source=self._match(TokenType.TABLE) and self._parse_table(), 3707 ), 3708 comments=comments, 3709 ) 3710 3711 def _parse_insert_table(self) -> exp.Expr | None: 3712 this = self._parse_table(schema=True, parse_partition=True) 3713 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3714 this.set("alias", self._parse_table_alias()) 3715 return this 3716 3717 def _parse_kill(self) -> exp.Kill: 3718 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3719 3720 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3721 3722 def _parse_on_conflict(self) -> exp.OnConflict | None: 3723 conflict = self._match_text_seq("ON", "CONFLICT") 3724 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3725 3726 if not conflict and not duplicate: 3727 return None 3728 3729 conflict_keys = None 3730 constraint = None 3731 3732 if conflict: 3733 if self._match_text_seq("ON", "CONSTRAINT"): 3734 constraint = self._parse_id_var() 3735 elif self._match(TokenType.L_PAREN): 3736 conflict_keys = self._parse_csv(self._parse_indexed_column) 3737 self._match_r_paren() 3738 3739 index_predicate = self._parse_where() 3740 3741 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3742 if self._prev.token_type == TokenType.UPDATE: 3743 self._match(TokenType.SET) 3744 expressions = self._parse_csv(self._parse_update_assignment) 3745 else: 3746 expressions = None 3747 3748 return self.expression( 3749 exp.OnConflict( 3750 duplicate=duplicate, 3751 expressions=expressions, 3752 action=action, 3753 conflict_keys=conflict_keys, 3754 index_predicate=index_predicate, 3755 constraint=constraint, 3756 where=self._parse_where(), 3757 ) 3758 ) 3759 3760 def _parse_returning(self) -> exp.Returning | None: 3761 if not self._match(TokenType.RETURNING): 3762 return None 3763 return self.expression( 3764 exp.Returning( 3765 expressions=self._parse_csv(self._parse_expression), 3766 into=self._match(TokenType.INTO) and self._parse_table_part(), 3767 ) 3768 ) 3769 3770 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3771 if not self._match(TokenType.FORMAT): 3772 return None 3773 return self._parse_row_format() 3774 3775 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3776 index = self._index 3777 with_ = with_ or self._match_text_seq("WITH") 3778 3779 if not self._match(TokenType.SERDE_PROPERTIES): 3780 self._retreat(index) 3781 return None 3782 return self.expression( 3783 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3784 ) 3785 3786 def _parse_row_format( 3787 self, match_row: bool = False 3788 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3789 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3790 return None 3791 3792 if self._match_text_seq("SERDE"): 3793 this = self._parse_string() 3794 3795 serde_properties = self._parse_serde_properties() 3796 3797 return self.expression( 3798 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3799 ) 3800 3801 self._match_text_seq("DELIMITED") 3802 3803 kwargs = {} 3804 3805 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3806 kwargs["fields"] = self._parse_string() 3807 if self._match_text_seq("ESCAPED", "BY"): 3808 kwargs["escaped"] = self._parse_string() 3809 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3810 kwargs["collection_items"] = self._parse_string() 3811 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3812 kwargs["map_keys"] = self._parse_string() 3813 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3814 kwargs["lines"] = self._parse_string() 3815 if self._match_text_seq("NULL", "DEFINED", "AS"): 3816 kwargs["null"] = self._parse_string() 3817 3818 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3819 3820 def _parse_load(self) -> exp.LoadData | exp.Command: 3821 if self._match_text_seq("DATA"): 3822 local = self._match_text_seq("LOCAL") 3823 self._match_text_seq("INPATH") 3824 inpath = self._parse_string() 3825 overwrite = self._match(TokenType.OVERWRITE) 3826 temp: bool | None = None 3827 if self._match(TokenType.INTO): 3828 temp = self._match(TokenType.TEMPORARY) 3829 self._match(TokenType.TABLE) 3830 3831 return self.expression( 3832 exp.LoadData( 3833 this=self._parse_table(schema=True), 3834 local=local, 3835 overwrite=overwrite, 3836 temp=temp, 3837 inpath=inpath, 3838 files=self._match_text_seq("FROM", "FILES") 3839 and exp.Properties(expressions=self._parse_wrapped_properties()), 3840 partition=self._parse_partition(), 3841 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3842 serde=self._match_text_seq("SERDE") and self._parse_string(), 3843 ) 3844 ) 3845 return self._parse_as_command(self._prev) 3846 3847 def _parse_delete(self) -> exp.Delete: 3848 hint = self._parse_hint() 3849 3850 # This handles MySQL's "Multiple-Table Syntax" 3851 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3852 tables = None 3853 if not self._match(TokenType.FROM, advance=False): 3854 tables = self._parse_csv(self._parse_table) or None 3855 3856 returning = self._parse_returning() 3857 3858 return self.expression( 3859 exp.Delete( 3860 hint=hint, 3861 tables=tables, 3862 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3863 using=self._match(TokenType.USING) 3864 and self._parse_csv(lambda: self._parse_table(joins=True)), 3865 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3866 where=self._parse_where(), 3867 returning=returning or self._parse_returning(), 3868 order=self._parse_order(), 3869 limit=self._parse_limit(), 3870 ) 3871 ) 3872 3873 def _parse_update_assignment(self) -> exp.Expr | None: 3874 this = self._parse_comparison() 3875 if self._match(TokenType.EQ): 3876 comments = self._prev_comments 3877 this = self.expression( 3878 exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments 3879 ) 3880 return this 3881 3882 def _parse_update(self) -> exp.Update: 3883 hint = self._parse_hint() 3884 kwargs: dict[str, object] = { 3885 "hint": hint, 3886 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3887 } 3888 while self._curr: 3889 if self._match(TokenType.SET): 3890 kwargs["expressions"] = self._parse_csv(self._parse_update_assignment) 3891 elif self._match(TokenType.RETURNING, advance=False): 3892 kwargs["returning"] = self._parse_returning() 3893 elif self._match(TokenType.FROM, advance=False): 3894 from_ = self._parse_from(joins=True) 3895 table = from_.this if from_ else None 3896 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3897 table.set("joins", list(self._parse_joins()) or None) 3898 3899 kwargs["from_"] = from_ 3900 elif self._match(TokenType.WHERE, advance=False): 3901 kwargs["where"] = self._parse_where() 3902 elif self._match(TokenType.ORDER_BY, advance=False): 3903 kwargs["order"] = self._parse_order() 3904 elif self._match(TokenType.LIMIT, advance=False): 3905 kwargs["limit"] = self._parse_limit() 3906 else: 3907 break 3908 3909 return self.expression(exp.Update(**kwargs)) 3910 3911 def _parse_use(self) -> exp.Use: 3912 return self.expression( 3913 exp.Use( 3914 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 3915 this=self._parse_table(schema=False), 3916 ) 3917 ) 3918 3919 def _parse_uncache(self) -> exp.Uncache: 3920 if not self._match(TokenType.TABLE): 3921 self.raise_error("Expecting TABLE after UNCACHE") 3922 3923 return self.expression( 3924 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 3925 ) 3926 3927 def _parse_cache(self) -> exp.Cache: 3928 lazy = self._match_text_seq("LAZY") 3929 self._match(TokenType.TABLE) 3930 table = self._parse_table(schema=True) 3931 3932 options = [] 3933 if self._match_text_seq("OPTIONS"): 3934 self._match_l_paren() 3935 k = self._parse_string() 3936 self._match(TokenType.EQ) 3937 v = self._parse_string() 3938 options = [k, v] 3939 self._match_r_paren() 3940 3941 self._match(TokenType.ALIAS) 3942 return self.expression( 3943 exp.Cache( 3944 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 3945 ) 3946 ) 3947 3948 def _parse_partition(self) -> exp.Partition | None: 3949 if not self._match_texts(self.PARTITION_KEYWORDS): 3950 return None 3951 3952 return self.expression( 3953 exp.Partition( 3954 subpartition=self._prev.text.upper() == "SUBPARTITION", 3955 expressions=self._parse_wrapped_csv(self._parse_disjunction), 3956 ) 3957 ) 3958 3959 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 3960 def _parse_value_expression() -> exp.Expr | None: 3961 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3962 return exp.var(self._prev.text.upper()) 3963 return self._parse_expression() 3964 3965 if self._match(TokenType.L_PAREN): 3966 expressions = self._parse_csv(_parse_value_expression) 3967 self._match_r_paren() 3968 return self.expression(exp.Tuple(expressions=expressions)) 3969 3970 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 3971 expression = self._parse_expression() 3972 if expression: 3973 return self.expression(exp.Tuple(expressions=[expression])) 3974 return None 3975 3976 def _parse_projections( 3977 self, 3978 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 3979 return self._parse_expressions(), None 3980 3981 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 3982 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 3983 this: exp.Expr | None = self._parse_simplified_pivot( 3984 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 3985 ) 3986 elif self._match(TokenType.FROM): 3987 from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True) 3988 # Support parentheses for duckdb FROM-first syntax 3989 select = self._parse_select(from_=from_) 3990 if select: 3991 if not select.args.get("from_"): 3992 select.set("from_", from_) 3993 this = select 3994 else: 3995 this = exp.select("*").from_(t.cast(exp.From, from_)) 3996 this = self._parse_query_modifiers(self._parse_set_operations(this)) 3997 else: 3998 this = ( 3999 self._parse_table(consume_pipe=True) 4000 if table 4001 else self._parse_select(nested=True, parse_set_operation=False) 4002 ) 4003 4004 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 4005 # in case a modifier (e.g. join) is following 4006 if table and isinstance(this, exp.Values) and this.alias: 4007 alias = this.args["alias"].pop() 4008 this = exp.Table(this=this, alias=alias) 4009 4010 this = self._parse_query_modifiers(self._parse_set_operations(this)) 4011 4012 return this 4013 4014 def _parse_select( 4015 self, 4016 nested: bool = False, 4017 table: bool = False, 4018 parse_subquery_alias: bool = True, 4019 parse_set_operation: bool = True, 4020 consume_pipe: bool = True, 4021 from_: exp.From | None = None, 4022 ) -> exp.Expr | None: 4023 query = self._parse_select_query( 4024 nested=nested, 4025 table=table, 4026 parse_subquery_alias=parse_subquery_alias, 4027 parse_set_operation=parse_set_operation, 4028 ) 4029 4030 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4031 if not query and from_: 4032 query = exp.select("*").from_(from_) 4033 if isinstance(query, exp.Query): 4034 query = self._parse_pipe_syntax_query(query) 4035 query = query.subquery(copy=False) if query and table else query 4036 4037 return query 4038 4039 def _parse_select_query( 4040 self, 4041 nested: bool = False, 4042 table: bool = False, 4043 parse_subquery_alias: bool = True, 4044 parse_set_operation: bool = True, 4045 ) -> exp.Expr | None: 4046 cte = self._parse_with() 4047 4048 if cte: 4049 this = self._parse_statement() 4050 4051 if not this: 4052 self.raise_error("Failed to parse any statement following CTE") 4053 return cte 4054 4055 while isinstance(this, exp.Subquery) and this.is_wrapper: 4056 this = this.this 4057 4058 assert this is not None 4059 if "with_" in this.arg_types: 4060 if inner_cte := this.args.get("with_"): 4061 cte.set("expressions", cte.expressions + inner_cte.expressions) 4062 if inner_cte.args.get("recursive"): 4063 cte.set("recursive", True) 4064 this.set("with_", cte) 4065 else: 4066 self.raise_error(f"{this.key} does not support CTE") 4067 this = cte 4068 4069 return this 4070 4071 # duckdb supports leading with FROM x 4072 from_ = ( 4073 self._parse_from(joins=True, consume_pipe=True) 4074 if self._match(TokenType.FROM, advance=False) 4075 else None 4076 ) 4077 4078 if self._match(TokenType.SELECT): 4079 comments = self._prev_comments 4080 4081 hint = self._parse_hint() 4082 4083 if self._next and not self._next.token_type == TokenType.DOT: 4084 all_ = self._match(TokenType.ALL) 4085 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4086 else: 4087 all_, matched_distinct = None, False 4088 4089 kind = ( 4090 self._prev.text.upper() 4091 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4092 else None 4093 ) 4094 4095 distinct: exp.Expr | None = ( 4096 self.expression( 4097 exp.Distinct( 4098 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4099 ) 4100 ) 4101 if matched_distinct 4102 else None 4103 ) 4104 4105 operation_modifiers = [] 4106 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4107 operation_modifiers.append(exp.var(self._prev.text.upper())) 4108 4109 limit = self._parse_limit(top=True) 4110 4111 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4112 if limit and not matched_distinct and not all_: 4113 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4114 if matched_distinct: 4115 distinct = self.expression( 4116 exp.Distinct( 4117 on=self._parse_value(values=False) 4118 if self._match(TokenType.ON) 4119 else None 4120 ) 4121 ) 4122 else: 4123 all_ = self._match(TokenType.ALL) 4124 4125 if all_ and distinct: 4126 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4127 4128 projections, exclude = self._parse_projections() 4129 4130 this = self.expression( 4131 exp.Select( 4132 kind=kind, 4133 hint=hint, 4134 distinct=distinct, 4135 expressions=projections, 4136 limit=limit, 4137 exclude=exclude, 4138 operation_modifiers=operation_modifiers or None, 4139 ) 4140 ) 4141 this.comments = comments 4142 4143 into = self._parse_into() 4144 if into: 4145 this.set("into", into) 4146 4147 if not from_: 4148 from_ = self._parse_from() 4149 4150 if from_: 4151 this.set("from_", from_) 4152 4153 this = self._parse_query_modifiers(this) 4154 elif (table or nested) and self._match(TokenType.L_PAREN): 4155 comments = self._prev_comments 4156 this = self._parse_wrapped_select(table=table) 4157 4158 if this: 4159 this.add_comments(comments, prepend=True) 4160 4161 # We return early here so that the UNION isn't attached to the subquery by the 4162 # following call to _parse_set_operations, but instead becomes the parent node 4163 self._match_r_paren() 4164 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4165 elif self._match(TokenType.VALUES, advance=False): 4166 this = self._parse_derived_table_values() 4167 elif from_: 4168 this = exp.select("*").from_(from_.this, copy=False) 4169 this = self._parse_query_modifiers(this) 4170 elif self._match(TokenType.SUMMARIZE): 4171 table = self._match(TokenType.TABLE) 4172 this = self._parse_select() or self._parse_string() or self._parse_table() 4173 return self.expression(exp.Summarize(this=this, table=table)) 4174 elif self._match(TokenType.DESCRIBE): 4175 this = self._parse_describe() 4176 else: 4177 this = None 4178 4179 return self._parse_set_operations(this) if parse_set_operation else this 4180 4181 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4182 self._match_text_seq("SEARCH") 4183 4184 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4185 4186 if not kind: 4187 return None 4188 4189 self._match_text_seq("FIRST", "BY") 4190 4191 return self.expression( 4192 exp.RecursiveWithSearch( 4193 kind=kind, 4194 this=self._parse_id_var(), 4195 expression=self._match_text_seq("SET") and self._parse_id_var(), 4196 using=self._match_text_seq("USING") and self._parse_id_var(), 4197 ) 4198 ) 4199 4200 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4201 if not skip_with_token and not self._match(TokenType.WITH): 4202 return None 4203 4204 comments = self._prev_comments 4205 recursive = self._match(TokenType.RECURSIVE) 4206 4207 last_comments = None 4208 expressions = [] 4209 udfs = [] 4210 while True: 4211 cte = self._parse_cte() 4212 if cte: 4213 if isinstance(cte, exp.FunctionSpecification): 4214 udfs.append(cte) 4215 else: 4216 expressions.append(cte) 4217 4218 if last_comments: 4219 cte.add_comments(last_comments) 4220 4221 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4222 break 4223 else: 4224 self._match(TokenType.WITH) 4225 recursive = self._match(TokenType.RECURSIVE) or recursive 4226 4227 last_comments = self._prev_comments 4228 4229 return self.expression( 4230 exp.With( 4231 expressions=expressions, 4232 recursive=recursive or None, 4233 search=self._parse_recursive_with_search(), 4234 udfs=udfs or None, 4235 ), 4236 comments=comments, 4237 ) 4238 4239 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4240 index = self._index 4241 4242 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4243 if not alias or not alias.this: 4244 self.raise_error("Expected CTE to have alias") 4245 4246 key_expressions = ( 4247 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4248 ) 4249 4250 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4251 self._retreat(index) 4252 return None 4253 4254 comments = self._prev_comments 4255 4256 if self._match_text_seq("NOT", "MATERIALIZED"): 4257 materialized = False 4258 elif self._match_text_seq("MATERIALIZED"): 4259 materialized = True 4260 else: 4261 materialized = None 4262 4263 cte = self.expression( 4264 exp.CTE( 4265 this=self._parse_wrapped(self._parse_statement), 4266 alias=alias, 4267 materialized=materialized, 4268 key_expressions=key_expressions, 4269 ), 4270 comments=comments, 4271 ) 4272 4273 values = cte.this 4274 if isinstance(values, exp.Values): 4275 cte.set("this", self._values_to_select(values)) 4276 4277 return cte 4278 4279 def _values_to_select(self, values: exp.Values) -> exp.Select: 4280 if values.alias: 4281 return exp.select("*").from_(values) 4282 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4283 4284 def _parse_table_alias( 4285 self, alias_tokens: t.Collection[TokenType] | None = None 4286 ) -> exp.TableAlias | None: 4287 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4288 # so this section tries to parse the clause version and if it fails, it treats the token 4289 # as an identifier (alias) 4290 if self._can_parse_limit_or_offset(): 4291 return None 4292 4293 # START is never treated as an implicit alias when followed by WITH, since that 4294 # would swallow the beginning of a START WITH ... CONNECT BY clause 4295 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4296 return None 4297 4298 any_token = self._match(TokenType.ALIAS) 4299 alias = ( 4300 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4301 or self._parse_string_as_identifier() 4302 ) 4303 4304 index = self._index 4305 if self._match(TokenType.L_PAREN): 4306 columns = self._parse_csv(self._parse_function_parameter) 4307 self._match_r_paren() if columns else self._retreat(index) 4308 else: 4309 columns = None 4310 4311 if not alias and not columns: 4312 return None 4313 4314 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4315 4316 # We bubble up comments from the Identifier to the TableAlias 4317 if isinstance(alias, exp.Identifier): 4318 table_alias.add_comments(alias.pop_comments()) 4319 4320 return table_alias 4321 4322 def _parse_subquery( 4323 self, this: exp.Expr | None, parse_alias: bool = True 4324 ) -> exp.Subquery | None: 4325 if not this: 4326 return None 4327 4328 return self.expression( 4329 exp.Subquery( 4330 this=this, 4331 pivots=self._parse_pivots(), 4332 alias=self._parse_table_alias() if parse_alias else None, 4333 sample=self._parse_table_sample(), 4334 ) 4335 ) 4336 4337 def _implicit_unnests_to_explicit(self, this: E) -> E: 4338 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4339 4340 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4341 for i, join in enumerate(this.args.get("joins") or []): 4342 table = join.this 4343 normalized_table = table.copy() 4344 normalized_table.meta["maybe_column"] = True 4345 normalized_table = _norm(normalized_table, dialect=self.dialect) 4346 4347 if isinstance(table, exp.Table) and not join.args.get("on"): 4348 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4349 table_as_column = table.to_column() 4350 unnest = exp.Unnest(expressions=[table_as_column]) 4351 4352 # Table.to_column creates a parent Alias node that we want to convert to 4353 # a TableAlias and attach to the Unnest, so it matches the parser's output 4354 if isinstance(table.args.get("alias"), exp.TableAlias): 4355 table_as_column.replace(table_as_column.this) 4356 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4357 4358 table.replace(unnest) 4359 4360 refs.add(normalized_table.alias_or_name) 4361 4362 return this 4363 4364 @t.overload 4365 def _parse_query_modifiers(self, this: E) -> E: ... 4366 4367 @t.overload 4368 def _parse_query_modifiers(self, this: None) -> None: ... 4369 4370 def _parse_query_modifiers(self, this): 4371 if isinstance(this, self.MODIFIABLES): 4372 for join in self._parse_joins(): 4373 this.append("joins", join) 4374 for lateral in iter(self._parse_lateral, None): 4375 this.append("laterals", lateral) 4376 4377 while True: 4378 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4379 modifier_token = self._curr 4380 4381 # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result 4382 # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union 4383 if ( 4384 modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH) 4385 and (limit := this.args.get("limit")) 4386 and limit.meta.get("top") 4387 ): 4388 break 4389 4390 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4391 key, expression = parser(self) 4392 4393 if expression: 4394 if this.args.get(key): 4395 self.raise_error( 4396 f"Found multiple '{modifier_token.text.upper()}' clauses", 4397 token=modifier_token, 4398 ) 4399 4400 this.set(key, expression) 4401 if key == "limit": 4402 offset = expression.args.get("offset") 4403 expression.set("offset", None) 4404 4405 if offset: 4406 if this.args.get("offset"): 4407 self.raise_error( 4408 "Found multiple 'OFFSET' clauses", token=modifier_token 4409 ) 4410 4411 offset = exp.Offset(expression=offset) 4412 this.set("offset", offset) 4413 4414 limit_by_expressions = expression.expressions 4415 expression.set("expressions", None) 4416 offset.set("expressions", limit_by_expressions) 4417 continue 4418 4419 if self._curr.text.upper() == "START": 4420 modifier_token = self._curr 4421 connect = self._parse_connect() 4422 if connect: 4423 if this.args.get("connect"): 4424 self.raise_error( 4425 "Found multiple 'START WITH' clauses", token=modifier_token 4426 ) 4427 4428 this.set("connect", connect) 4429 continue 4430 break 4431 4432 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4433 this = self._implicit_unnests_to_explicit(this) 4434 4435 return this 4436 4437 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4438 start = self._curr 4439 while self._curr: 4440 self._advance() 4441 4442 end = self._tokens[self._index - 1] 4443 return exp.Hint(expressions=[self._find_sql(start, end)]) 4444 4445 def _parse_hint_function_call(self) -> exp.Expr | None: 4446 return self._parse_function_call() 4447 4448 def _parse_hint_body(self) -> exp.Hint | None: 4449 start_index = self._index 4450 should_fallback_to_string = False 4451 4452 hints = [] 4453 try: 4454 for hint in iter( 4455 lambda: self._parse_csv( 4456 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4457 ), 4458 [], 4459 ): 4460 hints.extend(hint) 4461 except ParseError: 4462 should_fallback_to_string = True 4463 4464 if should_fallback_to_string or self._curr: 4465 self._retreat(start_index) 4466 return self._parse_hint_fallback_to_string() 4467 4468 return self.expression(exp.Hint(expressions=hints)) 4469 4470 def _parse_hint(self) -> exp.Hint | None: 4471 if self._match(TokenType.HINT) and self._prev_comments: 4472 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4473 4474 return None 4475 4476 def _parse_into(self) -> exp.Into | None: 4477 if not self._match(TokenType.INTO): 4478 return None 4479 4480 temp = self._match(TokenType.TEMPORARY) 4481 unlogged = self._match_text_seq("UNLOGGED") 4482 self._match(TokenType.TABLE) 4483 4484 return self.expression( 4485 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4486 ) 4487 4488 def _parse_from( 4489 self, 4490 joins: bool = False, 4491 skip_from_token: bool = False, 4492 consume_pipe: bool = False, 4493 ) -> exp.From | None: 4494 if not skip_from_token and not self._match(TokenType.FROM): 4495 return None 4496 4497 comments = self._prev_comments 4498 return self.expression( 4499 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4500 comments=comments, 4501 ) 4502 4503 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4504 return self.expression( 4505 exp.MatchRecognizeMeasure( 4506 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4507 this=self._parse_expression(), 4508 ) 4509 ) 4510 4511 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4512 if not self._match(TokenType.MATCH_RECOGNIZE): 4513 return None 4514 4515 self._match_l_paren() 4516 4517 partition = self._parse_partition_by() 4518 order = self._parse_order() 4519 4520 measures = ( 4521 self._parse_csv(self._parse_match_recognize_measure) 4522 if self._match_text_seq("MEASURES") 4523 else None 4524 ) 4525 4526 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4527 rows = exp.var("ONE ROW PER MATCH") 4528 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4529 text = "ALL ROWS PER MATCH" 4530 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4531 text += " SHOW EMPTY MATCHES" 4532 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4533 text += " OMIT EMPTY MATCHES" 4534 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4535 text += " WITH UNMATCHED ROWS" 4536 rows = exp.var(text) 4537 else: 4538 rows = None 4539 4540 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4541 text = "AFTER MATCH SKIP" 4542 if self._match_text_seq("PAST", "LAST", "ROW"): 4543 text += " PAST LAST ROW" 4544 elif self._match_text_seq("TO", "NEXT", "ROW"): 4545 text += " TO NEXT ROW" 4546 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4547 direction = self._prev.text.upper() 4548 pattern_var = self._advance_any() 4549 if not pattern_var: 4550 self.raise_error( 4551 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4552 ) 4553 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4554 after = exp.var(text) 4555 else: 4556 after = None 4557 4558 if self._match_text_seq("PATTERN"): 4559 self._match_l_paren() 4560 4561 if not self._curr: 4562 self.raise_error("Expecting )", self._curr) 4563 4564 paren = 1 4565 start = self._curr 4566 4567 while self._curr and paren > 0: 4568 if self._curr.token_type == TokenType.L_PAREN: 4569 paren += 1 4570 if self._curr.token_type == TokenType.R_PAREN: 4571 paren -= 1 4572 4573 end = self._prev 4574 self._advance() 4575 4576 if paren > 0: 4577 self.raise_error("Expecting )", self._curr) 4578 4579 pattern = exp.var(self._find_sql(start, end)) 4580 else: 4581 pattern = None 4582 4583 define = ( 4584 self._parse_csv(self._parse_name_as_expression) 4585 if self._match_text_seq("DEFINE") 4586 else None 4587 ) 4588 4589 self._match_r_paren() 4590 4591 return self.expression( 4592 exp.MatchRecognize( 4593 partition_by=partition, 4594 order=order, 4595 measures=measures, 4596 rows=rows, 4597 after=after, 4598 pattern=pattern, 4599 define=define, 4600 alias=self._parse_table_alias(), 4601 ) 4602 ) 4603 4604 def _parse_lateral(self) -> exp.Lateral | None: 4605 cross_apply: bool | None = None 4606 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4607 cross_apply = True 4608 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4609 cross_apply = False 4610 4611 if cross_apply is not None: 4612 this = self._parse_select(table=True) 4613 view = None 4614 outer = None 4615 elif self._match(TokenType.LATERAL): 4616 this = self._parse_select(table=True) 4617 view = self._match(TokenType.VIEW) 4618 outer = self._match(TokenType.OUTER) 4619 else: 4620 return None 4621 4622 if not this: 4623 this = ( 4624 self._parse_unnest() 4625 or self._parse_function() 4626 or self._parse_id_var(any_token=False) 4627 ) 4628 4629 while self._match(TokenType.DOT): 4630 this = exp.Dot( 4631 this=this, 4632 expression=self._parse_function() or self._parse_id_var(any_token=False), 4633 ) 4634 4635 ordinality: bool | None = None 4636 4637 if view: 4638 table = self._parse_id_var(any_token=False) 4639 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4640 table_alias: exp.TableAlias | None = self.expression( 4641 exp.TableAlias(this=table, columns=columns) 4642 ) 4643 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4644 # We move the alias from the lateral's child node to the lateral itself 4645 table_alias = this.args["alias"].pop() 4646 else: 4647 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4648 table_alias = self._parse_table_alias() 4649 4650 return self.expression( 4651 exp.Lateral( 4652 this=this, 4653 view=view, 4654 outer=outer, 4655 alias=table_alias, 4656 cross_apply=cross_apply, 4657 ordinality=ordinality, 4658 ) 4659 ) 4660 4661 def _parse_stream(self) -> exp.Stream | None: 4662 index = self._index 4663 if self._match(TokenType.STREAM): 4664 if this := self._try_parse(self._parse_table): 4665 return self.expression(exp.Stream(this=this)) 4666 self._retreat(index) 4667 return None 4668 4669 def _parse_join_parts( 4670 self, 4671 ) -> tuple[Token | None, Token | None, Token | None]: 4672 return ( 4673 self._prev if self._match_set(self.JOIN_METHODS) else None, 4674 self._prev if self._match_set(self.JOIN_SIDES) else None, 4675 self._prev if self._match_set(self.JOIN_KINDS) else None, 4676 ) 4677 4678 def _parse_using_identifiers(self) -> list[exp.Expr]: 4679 def _parse_column_as_identifier() -> exp.Expr | None: 4680 this = self._parse_column() 4681 if isinstance(this, exp.Column): 4682 return this.this 4683 return this 4684 4685 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4686 4687 def _parse_join( 4688 self, 4689 skip_join_token: bool = False, 4690 parse_bracket: bool = False, 4691 alias_tokens: t.Collection[TokenType] | None = None, 4692 ) -> exp.Join | None: 4693 if self._match(TokenType.COMMA): 4694 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4695 cross_join = self.expression(exp.Join(this=table)) if table else None 4696 4697 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4698 cross_join.set("kind", "CROSS") 4699 4700 return cross_join 4701 4702 index = self._index 4703 method, side, kind = self._parse_join_parts() 4704 directed = self._match_text_seq("DIRECTED") 4705 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4706 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4707 join_comments = self._prev_comments 4708 4709 if not skip_join_token and not join: 4710 self._retreat(index) 4711 kind = None 4712 method = None 4713 side = None 4714 4715 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4716 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4717 4718 if not skip_join_token and not join and not outer_apply and not cross_apply: 4719 return None 4720 4721 kwargs: dict[str, t.Any] = { 4722 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4723 } 4724 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4725 kwargs["expressions"] = self._parse_csv( 4726 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4727 ) 4728 4729 if method: 4730 kwargs["method"] = method.text.upper() 4731 if side: 4732 kwargs["side"] = side.text.upper() 4733 if kind: 4734 kwargs["kind"] = kind.text.upper() 4735 if hint: 4736 kwargs["hint"] = hint 4737 4738 if self._match(TokenType.MATCH_CONDITION): 4739 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4740 4741 if self._match(TokenType.ON): 4742 kwargs["on"] = self._parse_disjunction() 4743 elif self._match(TokenType.USING): 4744 kwargs["using"] = self._parse_using_identifiers() 4745 elif ( 4746 not method 4747 and not (outer_apply or cross_apply) 4748 and not isinstance(kwargs["this"], exp.Unnest) 4749 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4750 ): 4751 index = self._index 4752 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4753 4754 if joins and self._match(TokenType.ON): 4755 kwargs["on"] = self._parse_disjunction() 4756 elif joins and self._match(TokenType.USING): 4757 kwargs["using"] = self._parse_using_identifiers() 4758 else: 4759 joins = None 4760 self._retreat(index) 4761 4762 kwargs["this"].set("joins", joins if joins else None) 4763 4764 kwargs["pivots"] = self._parse_pivots() 4765 4766 comments = [c for token in (method, side, kind) if token for c in token.comments] 4767 comments = (join_comments or []) + comments 4768 4769 if ( 4770 self.ADD_JOIN_ON_TRUE 4771 and not kwargs.get("on") 4772 and not kwargs.get("using") 4773 and not kwargs.get("method") 4774 and kwargs.get("kind") in (None, "INNER", "OUTER") 4775 ): 4776 kwargs["on"] = exp.true() 4777 4778 if directed: 4779 kwargs["directed"] = directed 4780 4781 return self.expression(exp.Join(**kwargs), comments=comments) 4782 4783 def _parse_opclass(self) -> exp.Expr | None: 4784 this = self._parse_disjunction() 4785 4786 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4787 return this 4788 4789 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4790 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4791 4792 return this 4793 4794 def _parse_index_params(self) -> exp.IndexParameters: 4795 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4796 4797 if self._match(TokenType.L_PAREN, advance=False): 4798 columns = self._parse_wrapped_csv(self._parse_with_operator) 4799 else: 4800 columns = None 4801 4802 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4803 partition_by = self._parse_partition_by() 4804 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4805 tablespace = ( 4806 self._parse_var(any_token=True) 4807 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4808 else None 4809 ) 4810 where = self._parse_where() 4811 4812 on = self._parse_field() if self._match(TokenType.ON) else None 4813 4814 return self.expression( 4815 exp.IndexParameters( 4816 using=using, 4817 columns=columns, 4818 include=include, 4819 partition_by=partition_by, 4820 where=where, 4821 with_storage=with_storage, 4822 tablespace=tablespace, 4823 on=on, 4824 ) 4825 ) 4826 4827 def _parse_index( 4828 self, index: exp.Expr | None = None, anonymous: bool = False 4829 ) -> exp.Index | None: 4830 if index or anonymous: 4831 unique = None 4832 primary = None 4833 amp = None 4834 4835 self._match(TokenType.ON) 4836 self._match(TokenType.TABLE) # hive 4837 table = self._parse_table_parts(schema=True) 4838 else: 4839 unique = self._match(TokenType.UNIQUE) 4840 primary = self._match_text_seq("PRIMARY") 4841 amp = self._match_text_seq("AMP") 4842 4843 if not self._match(TokenType.INDEX): 4844 return None 4845 4846 index = self._parse_id_var() 4847 table = None 4848 4849 params = self._parse_index_params() 4850 4851 return self.expression( 4852 exp.Index( 4853 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4854 ) 4855 ) 4856 4857 def _parse_table_hints(self) -> list[exp.Expr] | None: 4858 hints: list[exp.Expr] = [] 4859 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4860 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4861 hints.append( 4862 self.expression( 4863 exp.WithTableHint( 4864 expressions=self._parse_csv( 4865 lambda: self._parse_function() or self._parse_var(any_token=True) 4866 ) 4867 ) 4868 ) 4869 ) 4870 self._match_r_paren() 4871 else: 4872 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4873 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4874 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4875 4876 self._match_set((TokenType.INDEX, TokenType.KEY)) 4877 if self._match(TokenType.FOR): 4878 hint.set("target", self._advance_any() and self._prev.text.upper()) 4879 4880 hint.set("expressions", self._parse_wrapped_id_vars()) 4881 hints.append(hint) 4882 4883 return hints or None 4884 4885 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 4886 return ( 4887 (not schema and self._parse_function(optional_parens=False)) 4888 or self._parse_id_var(any_token=False) 4889 or self._parse_string_as_identifier() 4890 or self._parse_placeholder() 4891 ) 4892 4893 def _parse_table_parts_fast(self) -> exp.Table | None: 4894 index = self._index 4895 parts: list[exp.Identifier] | None = None 4896 all_comments: list[str] | None = None 4897 4898 while self._match_set(self.IDENTIFIER_TOKENS): 4899 token = self._prev 4900 comments = self._prev_comments 4901 4902 has_dot = self._match(TokenType.DOT) 4903 curr_tt = self._curr.token_type 4904 4905 if not has_dot: 4906 if curr_tt in self.TABLE_POSTFIX_TOKENS: 4907 self._retreat(index) 4908 return None 4909 elif curr_tt not in self.IDENTIFIER_TOKENS: 4910 self._retreat(index) 4911 return None 4912 4913 if parts is None: 4914 parts = [] 4915 4916 if comments: 4917 if all_comments is None: 4918 all_comments = [] 4919 all_comments.extend(comments) 4920 self._prev_comments = [] 4921 4922 parts.append( 4923 self.expression( 4924 exp.Identifier( 4925 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 4926 ), 4927 token, 4928 ) 4929 ) 4930 4931 if not has_dot: 4932 break 4933 4934 if parts is None: 4935 return None 4936 4937 n = len(parts) 4938 4939 if n == 1: 4940 table: exp.Table = exp.Table(this=parts[0]) 4941 elif n == 2: 4942 table = exp.Table(this=parts[1], db=parts[0]) 4943 elif n >= 3: 4944 this: exp.Identifier | exp.Dot = parts[2] 4945 for i in range(3, n): 4946 this = exp.Dot(this=this, expression=parts[i]) 4947 4948 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 4949 4950 if table is None: 4951 self._retreat(index) 4952 elif all_comments: 4953 table.add_comments(all_comments) 4954 return table 4955 4956 def _parse_table_parts( 4957 self, 4958 schema: bool = False, 4959 is_db_reference: bool = False, 4960 wildcard: bool = False, 4961 fast: bool = False, 4962 ) -> exp.Table | exp.Dot | None: 4963 if fast: 4964 return self._parse_table_parts_fast() 4965 4966 catalog: exp.Expr | str | None = None 4967 db: exp.Expr | str | None = None 4968 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 4969 4970 while self._match(TokenType.DOT): 4971 if catalog: 4972 # This allows nesting the table in arbitrarily many dot expressions if needed 4973 table = self.expression( 4974 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 4975 ) 4976 else: 4977 catalog = db 4978 db = table 4979 # "" used for tsql FROM a..b case 4980 table = self._parse_table_part(schema=schema) or "" 4981 4982 if ( 4983 wildcard 4984 and self._is_connected() 4985 and (isinstance(table, exp.Identifier) or not table) 4986 and self._match(TokenType.STAR) 4987 ): 4988 if isinstance(table, exp.Identifier): 4989 table.args["this"] += "*" 4990 else: 4991 table = exp.Identifier(this="*") 4992 4993 if is_db_reference: 4994 catalog = db 4995 db = table 4996 table = None 4997 4998 if not table and not is_db_reference: 4999 self.raise_error(f"Expected table name but got {self._curr}") 5000 if not db and is_db_reference: 5001 self.raise_error(f"Expected database name but got {self._curr}") 5002 5003 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 5004 5005 # Bubble up comments from identifier parts to the Table 5006 comments = [] 5007 for part in table.parts: 5008 if part_comments := part.pop_comments(): 5009 comments.extend(part_comments) 5010 if comments: 5011 table.add_comments(comments) 5012 5013 changes = self._parse_changes() 5014 if changes: 5015 table.set("changes", changes) 5016 5017 at_before = self._parse_historical_data() 5018 if at_before: 5019 table.set("when", at_before) 5020 5021 pivots = self._parse_pivots() 5022 if pivots: 5023 table.set("pivots", pivots) 5024 5025 return table 5026 5027 def _parse_table( 5028 self, 5029 schema: bool = False, 5030 joins: bool = False, 5031 alias_tokens: t.Collection[TokenType] | None = None, 5032 parse_bracket: bool = False, 5033 is_db_reference: bool = False, 5034 parse_partition: bool = False, 5035 consume_pipe: bool = False, 5036 ) -> exp.Expr | None: 5037 if not schema and not is_db_reference and not consume_pipe and not joins: 5038 index = self._index 5039 table = self._parse_table_parts(fast=True) 5040 5041 if table is not None: 5042 curr_tt = self._curr.token_type 5043 next_tt = self._next.token_type 5044 5045 fast_terminators = self.TABLE_TERMINATORS 5046 5047 # only return the table if we're sure there are no other operators 5048 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5049 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5050 return table 5051 5052 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5053 5054 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5055 if alias := self._parse_table_alias( 5056 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5057 ): 5058 table.set("alias", alias) 5059 5060 if self._curr.token_type in fast_terminators: 5061 return table 5062 5063 self._retreat(index) 5064 5065 if stream := self._parse_stream(): 5066 return stream 5067 5068 if lateral := self._parse_lateral(): 5069 return lateral 5070 5071 if unnest := self._parse_unnest(): 5072 return unnest 5073 5074 if values := self._parse_derived_table_values(): 5075 return values 5076 5077 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5078 if not subquery.args.get("pivots"): 5079 subquery.set("pivots", self._parse_pivots()) 5080 if joins: 5081 for join in self._parse_joins(): 5082 subquery.append("joins", join) 5083 return subquery 5084 5085 bracket = parse_bracket and self._parse_bracket(None) 5086 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5087 5088 rows_from_tables = ( 5089 self._parse_wrapped_csv(self._parse_table) 5090 if self._match_text_seq("ROWS", "FROM") 5091 else None 5092 ) 5093 rows_from = ( 5094 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5095 ) 5096 5097 only = self._match(TokenType.ONLY) 5098 5099 this = t.cast( 5100 exp.Expr, 5101 bracket 5102 or rows_from 5103 or self._parse_bracket( 5104 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5105 ), 5106 ) 5107 5108 if only: 5109 this.set("only", only) 5110 5111 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5112 self._match(TokenType.STAR) 5113 5114 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5115 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5116 this.set("partition", self._parse_partition()) 5117 5118 if schema: 5119 return self._parse_schema(this=this) 5120 5121 if self.dialect.ALIAS_POST_VERSION: 5122 this.set("version", self._parse_version()) 5123 5124 if self.dialect.ALIAS_POST_TABLESAMPLE: 5125 this.set("sample", self._parse_table_sample()) 5126 5127 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5128 if alias: 5129 this.set("alias", alias) 5130 5131 # DuckDB requires the time-travel clause to come after the alias, e.g. 5132 # SELECT * FROM t AS a AT (VERSION => 1) 5133 if isinstance(this, exp.Table) and not this.args.get("when"): 5134 this.set("when", self._parse_historical_data()) 5135 5136 if self._match(TokenType.INDEXED_BY): 5137 this.set("indexed", self._parse_table_parts()) 5138 elif self._match_text_seq("NOT", "INDEXED"): 5139 this.set("indexed", False) 5140 5141 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5142 return self.expression( 5143 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5144 ) 5145 5146 this.set("hints", self._parse_table_hints()) 5147 5148 if not this.args.get("pivots"): 5149 this.set("pivots", self._parse_pivots()) 5150 5151 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5152 this.set("sample", self._parse_table_sample()) 5153 5154 if not self.dialect.ALIAS_POST_VERSION: 5155 this.set("version", self._parse_version()) 5156 5157 if joins: 5158 for join in self._parse_joins(alias_tokens=alias_tokens): 5159 this.append("joins", join) 5160 5161 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5162 this.set("ordinality", True) 5163 this.set("alias", self._parse_table_alias()) 5164 5165 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5166 # hoist the table args onto the latter and return it instead 5167 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5168 table_from_rows = this.this 5169 for arg in exp.TableFromRows.arg_types: 5170 if arg != "this": 5171 table_from_rows.set(arg, this.args.get(arg)) 5172 5173 this = table_from_rows 5174 5175 return this 5176 5177 def _parse_version(self) -> exp.Version | None: 5178 for phrase, this in self.VERSION_PHRASES.items(): 5179 if self._match_text_seq(*phrase): 5180 break 5181 else: 5182 return None 5183 5184 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5185 kind = self._prev.text.upper() 5186 start = self._parse_bitwise() 5187 self._match_texts(("TO", "AND")) 5188 end = self._parse_bitwise() 5189 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5190 elif self._match_text_seq("CONTAINED", "IN"): 5191 kind = "CONTAINED IN" 5192 expression = self.expression( 5193 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5194 ) 5195 elif self._match(TokenType.ALL): 5196 kind = "ALL" 5197 expression = None 5198 else: 5199 self._match_text_seq("AS", "OF") 5200 kind = "AS OF" 5201 expression = self._parse_type() 5202 5203 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5204 5205 def _parse_historical_data(self) -> exp.HistoricalData | None: 5206 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5207 index = self._index 5208 historical_data = None 5209 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5210 this = self._prev.text.upper() 5211 kind = ( 5212 self._match(TokenType.L_PAREN) 5213 and self._match_texts(self.HISTORICAL_DATA_KIND) 5214 and self._prev.text.upper() 5215 ) 5216 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5217 5218 if expression: 5219 self._match_r_paren() 5220 historical_data = self.expression( 5221 exp.HistoricalData(this=this, kind=kind, expression=expression) 5222 ) 5223 else: 5224 self._retreat(index) 5225 5226 return historical_data 5227 5228 def _parse_changes(self) -> exp.Changes | None: 5229 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5230 return None 5231 5232 information = self._parse_var(any_token=True) 5233 self._match_r_paren() 5234 5235 return self.expression( 5236 exp.Changes( 5237 information=information, 5238 at_before=self._parse_historical_data(), 5239 end=self._parse_historical_data(), 5240 ) 5241 ) 5242 5243 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5244 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5245 return None 5246 5247 self._advance() 5248 5249 expressions = self._parse_wrapped_csv(self._parse_equality) 5250 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5251 5252 alias = self._parse_table_alias() if with_alias else None 5253 5254 if alias: 5255 if self.dialect.UNNEST_COLUMN_ONLY: 5256 if alias.args.get("columns"): 5257 self.raise_error("Unexpected extra column alias in unnest.") 5258 5259 alias.set("columns", [alias.this]) 5260 alias.set("this", None) 5261 5262 columns = alias.args.get("columns") or [] 5263 if offset and len(expressions) < len(columns): 5264 offset = columns.pop() 5265 5266 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5267 self._match(TokenType.ALIAS) 5268 offset = self._parse_id_var( 5269 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5270 ) or exp.to_identifier("offset") 5271 5272 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5273 5274 def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None: 5275 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5276 if not is_derived and not ( 5277 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5278 self._match_text_seq("VALUES") 5279 or self._match_text_seq("FORMAT", "VALUES") 5280 # MySQL accepts VALUE as a synonym for VALUES in INSERT statements. 5281 or (allow_value_synonym and self._match_text_seq("VALUE")) 5282 ): 5283 return None 5284 5285 expressions = self._parse_csv(self._parse_value) 5286 alias = self._parse_table_alias() 5287 5288 if is_derived: 5289 self._match_r_paren() 5290 5291 return self.expression( 5292 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5293 ) 5294 5295 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5296 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5297 as_modifier and self._match_text_seq("USING", "SAMPLE") 5298 ): 5299 return None 5300 5301 bucket_numerator = None 5302 bucket_denominator = None 5303 bucket_field = None 5304 percent = None 5305 size = None 5306 seed = None 5307 5308 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5309 matched_l_paren = self._match(TokenType.L_PAREN) 5310 5311 if self.TABLESAMPLE_CSV: 5312 num = None 5313 expressions = self._parse_csv(self._parse_primary) 5314 else: 5315 expressions = None 5316 num = ( 5317 self._parse_factor(parse_mod=False) 5318 if self._match(TokenType.NUMBER, advance=False) 5319 else self._parse_primary() or self._parse_placeholder() 5320 ) 5321 5322 if self._match_text_seq("BUCKET"): 5323 bucket_numerator = self._parse_number() 5324 self._match_text_seq("OUT", "OF") 5325 bucket_denominator = bucket_denominator = self._parse_number() 5326 self._match(TokenType.ON) 5327 bucket_field = self._parse_field() 5328 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5329 percent = num 5330 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5331 size = num 5332 else: 5333 percent = num 5334 5335 if matched_l_paren: 5336 self._match_r_paren() 5337 5338 if self._match(TokenType.L_PAREN): 5339 method = self._parse_var(upper=True) 5340 seed = self._match(TokenType.COMMA) and self._parse_number() 5341 self._match_r_paren() 5342 elif self._match_texts(("SEED", "REPEATABLE")): 5343 seed = self._parse_wrapped(self._parse_number) 5344 5345 if not method and self.DEFAULT_SAMPLING_METHOD: 5346 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5347 5348 return self.expression( 5349 exp.TableSample( 5350 expressions=expressions, 5351 method=method, 5352 bucket_numerator=bucket_numerator, 5353 bucket_denominator=bucket_denominator, 5354 bucket_field=bucket_field, 5355 percent=percent, 5356 size=size, 5357 seed=seed, 5358 ) 5359 ) 5360 5361 def _parse_pivots(self) -> list[exp.Pivot] | None: 5362 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5363 return None 5364 return list(iter(self._parse_pivot, None)) or None 5365 5366 def _parse_joins( 5367 self, alias_tokens: t.Collection[TokenType] | None = None 5368 ) -> t.Iterator[exp.Join]: 5369 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5370 5371 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5372 if not self._match(TokenType.INTO): 5373 return None 5374 5375 return self.expression( 5376 exp.UnpivotColumns( 5377 this=self._match_text_seq("NAME") and self._parse_column(), 5378 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5379 ) 5380 ) 5381 5382 # https://duckdb.org/docs/sql/statements/pivot 5383 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5384 def _parse_on() -> exp.Expr | None: 5385 this = self._parse_bitwise() 5386 5387 if self._match(TokenType.IN): 5388 # PIVOT ... ON col IN (row_val1, row_val2) 5389 return self._parse_in(this) 5390 if self._match(TokenType.ALIAS, advance=False): 5391 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5392 return self._parse_alias(this) 5393 5394 return this 5395 5396 this = self._parse_table() 5397 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5398 into = self._parse_unpivot_columns() 5399 using = self._match(TokenType.USING) and self._parse_csv( 5400 lambda: self._parse_alias(self._parse_column()) 5401 ) 5402 group = self._parse_group() 5403 5404 return self.expression( 5405 exp.Pivot( 5406 this=this, 5407 expressions=expressions, 5408 using=using, 5409 group=group, 5410 unpivot=is_unpivot, 5411 into=into, 5412 ) 5413 ) 5414 5415 def _parse_pivot_in(self) -> exp.In: 5416 def _parse_aliased_expression() -> exp.Expr | None: 5417 this = self._parse_select_or_expression() 5418 5419 self._match(TokenType.ALIAS) 5420 alias = self._parse_bitwise() 5421 if alias: 5422 if isinstance(alias, exp.Column) and not alias.db: 5423 alias = alias.this 5424 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5425 5426 return this 5427 5428 value = self._parse_column() 5429 5430 if not self._match(TokenType.IN): 5431 self.raise_error("Expecting IN") 5432 5433 if self._match(TokenType.L_PAREN): 5434 if self._match(TokenType.ANY): 5435 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5436 else: 5437 exprs = self._parse_csv(_parse_aliased_expression) 5438 self._match_r_paren() 5439 return self.expression(exp.In(this=value, expressions=exprs)) 5440 5441 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5442 5443 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5444 func = self._parse_function() 5445 if not func: 5446 if self._prev.token_type == TokenType.COMMA: 5447 return None 5448 self.raise_error("Expecting an aggregation function in PIVOT") 5449 5450 return self._parse_alias(func) 5451 5452 def _parse_pivot(self) -> exp.Pivot | None: 5453 index = self._index 5454 include_nulls = None 5455 5456 if self._match(TokenType.PIVOT): 5457 unpivot = False 5458 elif self._match(TokenType.UNPIVOT): 5459 unpivot = True 5460 5461 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5462 if self._match_text_seq("INCLUDE", "NULLS"): 5463 include_nulls = True 5464 elif self._match_text_seq("EXCLUDE", "NULLS"): 5465 include_nulls = False 5466 else: 5467 return None 5468 5469 expressions = [] 5470 5471 if not self._match(TokenType.L_PAREN): 5472 self._retreat(index) 5473 return None 5474 5475 if unpivot: 5476 expressions = self._parse_csv(self._parse_column) 5477 else: 5478 expressions = self._parse_csv(self._parse_pivot_aggregation) 5479 5480 if not expressions: 5481 self.raise_error("Failed to parse PIVOT's aggregation list") 5482 5483 if not self._match(TokenType.FOR): 5484 self.raise_error("Expecting FOR") 5485 5486 fields = [] 5487 while True: 5488 field = self._try_parse(self._parse_pivot_in) 5489 if not field: 5490 break 5491 fields.append(field) 5492 5493 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5494 self._parse_bitwise 5495 ) 5496 5497 group = self._parse_group() 5498 5499 self._match_r_paren() 5500 5501 pivot = self.expression( 5502 exp.Pivot( 5503 expressions=expressions, 5504 fields=fields, 5505 unpivot=unpivot, 5506 include_nulls=include_nulls, 5507 default_on_null=default_on_null, 5508 group=group, 5509 ) 5510 ) 5511 5512 if unpivot: 5513 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5514 for pivot_field in pivot.fields: 5515 if isinstance(pivot_field, exp.In): 5516 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5517 5518 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5519 5520 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5521 pivot.set("alias", self._parse_table_alias()) 5522 5523 if not unpivot: 5524 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5525 5526 columns: list[exp.Expr] = [] 5527 all_fields = [] 5528 for pivot_field in pivot.fields: 5529 pivot_field_expressions = pivot_field.expressions 5530 5531 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5532 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5533 continue 5534 5535 all_fields.append( 5536 [ 5537 # An explicit `<field> AS <alias>` names the output column directly, 5538 # so it wins over the dialect's string-identifying convention 5539 fld.sql() 5540 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5541 else fld.alias_or_name 5542 for fld in pivot_field_expressions 5543 ] 5544 ) 5545 5546 if all_fields: 5547 if names: 5548 all_fields.append(names) 5549 5550 # Generate all possible combinations of the pivot columns 5551 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5552 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5553 for fld_parts_tuple in itertools.product(*all_fields): 5554 fld_parts = list(fld_parts_tuple) 5555 5556 if names and self.PREFIXED_PIVOT_COLUMNS: 5557 # Move the "name" to the front of the list 5558 fld_parts.insert(0, fld_parts.pop(-1)) 5559 5560 columns.append(exp.to_identifier("_".join(fld_parts))) 5561 5562 pivot.set("columns", columns) 5563 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5564 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5565 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5566 5567 return pivot 5568 5569 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5570 return [agg.alias for agg in aggregations if agg.alias] 5571 5572 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5573 if not skip_where_token and not self._match(TokenType.PREWHERE): 5574 return None 5575 5576 comments = self._prev_comments 5577 return self.expression( 5578 exp.PreWhere(this=self._parse_disjunction()), 5579 comments=comments, 5580 ) 5581 5582 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5583 if not skip_where_token and not self._match(TokenType.WHERE): 5584 return None 5585 5586 comments = self._prev_comments 5587 return self.expression( 5588 exp.Where(this=self._parse_disjunction()), 5589 comments=comments, 5590 ) 5591 5592 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5593 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5594 return None 5595 comments = self._prev_comments 5596 5597 elements: dict[str, t.Any] = defaultdict(list) 5598 5599 if self._match(TokenType.ALL): 5600 elements["all"] = True 5601 elif self._match(TokenType.DISTINCT): 5602 elements["all"] = False 5603 5604 while True: 5605 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5606 # which are also valid identifiers 5607 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5608 break 5609 5610 elements["expressions"].extend( 5611 self._parse_csv( 5612 lambda: ( 5613 self._parse_grouping_sets() 5614 or self._parse_cube_or_rollup() 5615 or self._parse_disjunction() 5616 ) 5617 ) 5618 ) 5619 5620 before_with_index = self._index 5621 5622 if self._match(TokenType.WITH) and ( 5623 cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True) 5624 ): 5625 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5626 elements[key].append(cube_or_rollup) 5627 elif grouping_sets := self._parse_grouping_sets(): 5628 # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...) 5629 elements["grouping_sets"].append(grouping_sets) 5630 break 5631 elif self._match_text_seq("TOTALS"): 5632 elements["totals"] = True # type: ignore 5633 5634 if before_with_index <= self._index <= before_with_index + 1: 5635 self._retreat(before_with_index) 5636 break 5637 5638 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5639 5640 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5641 if self._match(TokenType.CUBE): 5642 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5643 elif self._match(TokenType.ROLLUP): 5644 kind = exp.Rollup 5645 else: 5646 return None 5647 5648 return self.expression( 5649 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5650 ) 5651 5652 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5653 if self._match(TokenType.GROUPING_SETS): 5654 return self.expression( 5655 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5656 ) 5657 return None 5658 5659 def _parse_grouping_set(self) -> exp.Expr | None: 5660 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5661 5662 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5663 if not skip_having_token and not self._match(TokenType.HAVING): 5664 return None 5665 comments = self._prev_comments 5666 return self.expression( 5667 exp.Having(this=self._parse_disjunction()), 5668 comments=comments, 5669 ) 5670 5671 def _parse_qualify(self) -> exp.Qualify | None: 5672 if not self._match(TokenType.QUALIFY): 5673 return None 5674 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5675 5676 def _parse_connect_with_prior(self) -> exp.Expr | None: 5677 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5678 exp.Prior(this=self._parse_bitwise()) 5679 ) 5680 connect = self._parse_disjunction() 5681 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5682 return connect 5683 5684 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5685 if skip_start_token: 5686 start = None 5687 elif self._match_text_seq("START", "WITH"): 5688 start = self._parse_disjunction() 5689 else: 5690 return None 5691 5692 self._match(TokenType.CONNECT_BY) 5693 nocycle = self._match_text_seq("NOCYCLE") 5694 connect = self._parse_connect_with_prior() 5695 5696 if not start and self._match_text_seq("START", "WITH"): 5697 start = self._parse_disjunction() 5698 5699 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5700 5701 def _parse_name_as_expression(self) -> exp.Expr | None: 5702 this = self._parse_id_var(any_token=True) 5703 if self._match(TokenType.ALIAS): 5704 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5705 return this 5706 5707 def _parse_interpolate(self) -> list[exp.Expr] | None: 5708 if self._match_text_seq("INTERPOLATE"): 5709 return self._parse_wrapped_csv(self._parse_name_as_expression) 5710 return None 5711 5712 def _parse_order( 5713 self, this: exp.Expr | None = None, skip_order_token: bool = False 5714 ) -> exp.Expr | None: 5715 siblings = None 5716 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5717 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5718 return this 5719 5720 siblings = True 5721 5722 comments = self._prev_comments 5723 return self.expression( 5724 exp.Order( 5725 this=this, 5726 expressions=self._parse_csv(self._parse_ordered), 5727 siblings=siblings, 5728 ), 5729 comments=comments, 5730 ) 5731 5732 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5733 if not self._match(token): 5734 return None 5735 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5736 5737 def _parse_ordered( 5738 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5739 ) -> exp.Ordered | None: 5740 this = parse_method() if parse_method else self._parse_disjunction() 5741 if not this: 5742 return None 5743 5744 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5745 this = exp.var("ALL") 5746 5747 asc = self._match(TokenType.ASC) 5748 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5749 5750 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5751 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5752 5753 nulls_first = is_nulls_first or False 5754 explicitly_null_ordered = is_nulls_first or is_nulls_last 5755 5756 if ( 5757 not explicitly_null_ordered 5758 and ( 5759 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5760 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5761 ) 5762 and self.dialect.NULL_ORDERING != "nulls_are_last" 5763 ): 5764 nulls_first = True 5765 5766 if self._match_text_seq("WITH", "FILL"): 5767 with_fill = self.expression( 5768 exp.WithFill( 5769 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5770 to=self._match_text_seq("TO") and self._parse_bitwise(), 5771 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5772 interpolate=self._parse_interpolate(), 5773 ) 5774 ) 5775 else: 5776 with_fill = None 5777 5778 return self.expression( 5779 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5780 ) 5781 5782 def _parse_limit_options(self) -> exp.LimitOptions | None: 5783 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5784 rows = self._match_texts(("ROW", "ROWS")) 5785 self._match_text_seq("ONLY") 5786 with_ties = self._match_text_seq("WITH", "TIES") 5787 5788 if not (percent or rows or with_ties): 5789 return None 5790 5791 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5792 5793 def _parse_limit( 5794 self, 5795 this: exp.Expr | None = None, 5796 top: bool = False, 5797 skip_limit_token: bool = False, 5798 ) -> exp.Expr | None: 5799 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5800 comments = self._prev_comments 5801 if top: 5802 limit_paren = self._match(TokenType.L_PAREN) 5803 expression = ( 5804 self._parse_term() or self._parse_select() 5805 if limit_paren 5806 else self._parse_number() 5807 ) 5808 5809 if limit_paren: 5810 self._match_r_paren() 5811 5812 else: 5813 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5814 return this 5815 5816 expression = self._parse_term(parse_mod=False) 5817 limit_options = self._parse_limit_options() 5818 5819 if self._match(TokenType.COMMA): 5820 offset = expression 5821 expression = self._parse_term() 5822 else: 5823 offset = None 5824 5825 limit_exp = self.expression( 5826 exp.Limit( 5827 this=this, 5828 expression=expression, 5829 offset=offset, 5830 limit_options=limit_options, 5831 expressions=self._parse_limit_by(), 5832 ), 5833 comments=comments, 5834 ) 5835 5836 if top: 5837 limit_exp.meta["top"] = True 5838 5839 return limit_exp 5840 5841 if self._match(TokenType.FETCH): 5842 direction = ( 5843 self._prev.text.upper() 5844 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5845 else "FIRST" 5846 ) 5847 5848 count = ( 5849 None 5850 if self._match_texts(("ROW", "ROWS"), advance=False) 5851 else self._parse_field(tokens=self.FETCH_TOKENS) 5852 ) 5853 5854 return self.expression( 5855 exp.Fetch( 5856 direction=direction, count=count, limit_options=self._parse_limit_options() 5857 ) 5858 ) 5859 5860 return this 5861 5862 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5863 if not self._match(TokenType.OFFSET): 5864 return this 5865 5866 count = self._parse_term() 5867 self._match_texts(("ROW", "ROWS")) 5868 5869 return self.expression( 5870 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5871 ) 5872 5873 def _can_parse_limit_or_offset(self) -> bool: 5874 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5875 return False 5876 5877 index = self._index 5878 result = bool( 5879 self._try_parse(self._parse_limit, retreat=True) 5880 or self._try_parse(self._parse_offset, retreat=True) 5881 ) 5882 self._retreat(index) 5883 5884 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 5885 if self._next.token_type == TokenType.MATCH_CONDITION: 5886 result = False 5887 5888 return result 5889 5890 def _can_parse_named_window(self) -> bool: 5891 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 5892 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 5893 if not self._match(TokenType.WINDOW, advance=False): 5894 return False 5895 5896 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 5897 if name is None or name.token_type not in self.ID_VAR_TOKENS: 5898 return False 5899 5900 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 5901 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 5902 return False 5903 5904 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 5905 return body is not None and body.token_type == TokenType.L_PAREN 5906 5907 def _parse_limit_by(self) -> list[exp.Expr] | None: 5908 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 5909 5910 def _parse_locks(self) -> list[exp.Lock]: 5911 locks = [] 5912 while True: 5913 update, key = None, None 5914 if self._match_text_seq("FOR", "UPDATE"): 5915 update = True 5916 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 5917 "LOCK", "IN", "SHARE", "MODE" 5918 ): 5919 update = False 5920 elif self._match_text_seq("FOR", "KEY", "SHARE"): 5921 update, key = False, True 5922 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 5923 update, key = True, True 5924 else: 5925 break 5926 5927 expressions = None 5928 if self._match_text_seq("OF"): 5929 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 5930 5931 wait: bool | exp.Expr | None = None 5932 if self._match_text_seq("NOWAIT"): 5933 wait = True 5934 elif self._match_text_seq("WAIT"): 5935 wait = self._parse_primary() 5936 elif self._match_text_seq("SKIP", "LOCKED"): 5937 wait = False 5938 5939 locks.append( 5940 self.expression( 5941 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 5942 ) 5943 ) 5944 5945 return locks 5946 5947 def parse_set_operation( 5948 self, this: exp.Expr | None, consume_pipe: bool = False 5949 ) -> exp.Expr | None: 5950 start = self._index 5951 _, side_token, kind_token = self._parse_join_parts() 5952 5953 side = side_token.text if side_token else None 5954 kind = kind_token.text if kind_token else None 5955 5956 if not self._match_set(self.SET_OPERATIONS): 5957 self._retreat(start) 5958 return None 5959 5960 token_type = self._prev.token_type 5961 5962 if token_type == TokenType.UNION: 5963 operation: type[exp.SetOperation] = exp.Union 5964 elif token_type == TokenType.EXCEPT: 5965 operation = exp.Except 5966 else: 5967 operation = exp.Intersect 5968 5969 comments = self._prev.comments 5970 5971 if self._match(TokenType.DISTINCT): 5972 distinct: bool | None = True 5973 elif self._match(TokenType.ALL): 5974 distinct = False 5975 else: 5976 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5977 if distinct is None: 5978 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5979 5980 by_name = ( 5981 self._match_text_seq("BY", "NAME") 5982 or self._match_text_seq("STRICT", "CORRESPONDING") 5983 or None 5984 ) 5985 if self._match_text_seq("CORRESPONDING"): 5986 by_name = True 5987 if not side and not kind: 5988 kind = "INNER" 5989 5990 on_column_list = None 5991 if by_name and self._match_texts(("ON", "BY")): 5992 on_column_list = self._parse_wrapped_csv(self._parse_column) 5993 5994 expression = self._parse_select( 5995 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5996 ) 5997 5998 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5999 # in _parse_cte and so that alias pushdown can reach into set operation branches 6000 if isinstance(this, exp.Values): 6001 this = self._values_to_select(this) 6002 if isinstance(expression, exp.Values): 6003 expression = self._values_to_select(expression) 6004 6005 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 6006 subquery = this.this 6007 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 6008 subquery.add_comments(this.pop_comments()) 6009 this = subquery 6010 6011 return self.expression( 6012 operation( 6013 this=this, 6014 distinct=distinct, 6015 by_name=by_name, 6016 expression=expression, 6017 side=side, 6018 kind=kind, 6019 on=on_column_list, 6020 ), 6021 comments=comments, 6022 ) 6023 6024 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 6025 while this: 6026 setop = self.parse_set_operation(this) 6027 if not setop: 6028 break 6029 this = setop 6030 6031 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6032 expression = this.expression 6033 6034 if expression: 6035 for arg in self.SET_OP_MODIFIERS: 6036 expr = expression.args.get(arg) 6037 if expr and not (arg == "limit" and expr.meta.get("top")): 6038 expression.set(arg, None) 6039 this.set(arg, expr) 6040 6041 # A trailing LIMIT/FETCH can coexist with TOP on the final operand. 6042 if self._curr.token_type in (TokenType.LIMIT, TokenType.FETCH): 6043 this = self._parse_query_modifiers(this) 6044 6045 return this 6046 6047 def _parse_expression(self) -> exp.Expr | None: 6048 return self._parse_alias(self._parse_assignment()) 6049 6050 def _parse_assignment(self) -> exp.Expr | None: 6051 this = self._parse_disjunction() 6052 if not this and self._next.token_type in self.ASSIGNMENT: 6053 # This allows us to parse <non-identifier token> := <expr> 6054 this = exp.column( 6055 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6056 ) 6057 6058 while self._match_set(self.ASSIGNMENT): 6059 if isinstance(this, exp.Column) and len(this.parts) == 1: 6060 this = this.this 6061 6062 comments = self._prev_comments 6063 this = self.expression( 6064 self.ASSIGNMENT[self._prev.token_type]( 6065 this=this, expression=self._parse_assignment() 6066 ), 6067 comments=comments, 6068 ) 6069 6070 return this 6071 6072 def _parse_disjunction(self) -> exp.Expr | None: 6073 this = self._parse_conjunction() 6074 while self._match_set(self.DISJUNCTION): 6075 comments = self._prev_comments 6076 this = self.expression( 6077 self.DISJUNCTION[self._prev.token_type]( 6078 this=this, expression=self._parse_conjunction() 6079 ), 6080 comments=comments, 6081 ) 6082 return this 6083 6084 def _parse_conjunction(self) -> exp.Expr | None: 6085 this = self._parse_equality() 6086 while self._match_set(self.CONJUNCTION): 6087 comments = self._prev_comments 6088 this = self.expression( 6089 self.CONJUNCTION[self._prev.token_type]( 6090 this=this, expression=self._parse_equality() 6091 ), 6092 comments=comments, 6093 ) 6094 return this 6095 6096 def _parse_equality(self) -> exp.Expr | None: 6097 this = self._parse_comparison() 6098 while self._match_set(self.EQUALITY): 6099 comments = self._prev_comments 6100 this = self.expression( 6101 self.EQUALITY[self._prev.token_type]( 6102 this=this, expression=self._parse_comparison() 6103 ), 6104 comments=comments, 6105 ) 6106 return this 6107 6108 def _parse_comparison(self) -> exp.Expr | None: 6109 this = self._parse_range() 6110 while self._match_set(self.COMPARISON): 6111 comments = self._prev_comments 6112 this = self.expression( 6113 self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()), 6114 comments=comments, 6115 ) 6116 return this 6117 6118 def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6119 this = this or self._parse_bitwise() 6120 6121 while True: 6122 negate = self._match(TokenType.NOT) 6123 if self._match_set(self.RANGE_PARSERS): 6124 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6125 if not expression: 6126 return this 6127 6128 this = expression 6129 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6130 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6131 elif self._match(TokenType.NOTNULL): 6132 # Postgres supports ISNULL and NOTNULL for conditions. 6133 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6134 if self.dialect.NORMALIZE_NOT_NULL: 6135 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6136 this = self.expression(exp.Not(this=this)) 6137 else: 6138 this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True)) 6139 else: 6140 if negate: 6141 self._retreat(self._index - 1) 6142 break 6143 6144 if negate: 6145 this = self._negate_range(this) 6146 if self._curr and ( 6147 self._curr.token_type == TokenType.NOT 6148 or self._curr.token_type in self.RANGE_PARSERS 6149 ): 6150 this = self.expression(exp.Paren(this=this)) 6151 6152 return this 6153 6154 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6155 if not this: 6156 return this 6157 6158 expression = this.this if isinstance(this, exp.Escape) else this 6159 if isinstance(expression, (exp.Like, exp.ILike)): 6160 expression.set("negate", True) 6161 return this 6162 6163 return self.expression(exp.Not(this=this)) 6164 6165 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6166 index = self._index - 1 6167 negate = self._match(TokenType.NOT) 6168 6169 if self._match_text_seq("DISTINCT", "FROM"): 6170 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6171 return self.expression(klass(this=this, expression=self._parse_bitwise())) 6172 6173 if self._match(TokenType.JSON): 6174 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6175 6176 if self._match_text_seq("WITH"): 6177 _with = True 6178 elif self._match_text_seq("WITHOUT"): 6179 _with = False 6180 else: 6181 _with = None 6182 6183 unique = self._match(TokenType.UNIQUE) 6184 self._match_text_seq("KEYS") 6185 expression: exp.Expr | None = self.expression( 6186 exp.JSON(this=kind, with_=_with, unique=unique) 6187 ) 6188 else: 6189 expression = self._parse_null() or self._parse_bitwise() 6190 if not expression: 6191 self._retreat(index) 6192 return None 6193 6194 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6195 this = self.expression(exp.Is(this=this, expression=expression, negate=True)) 6196 else: 6197 this = self.expression(exp.Is(this=this, expression=expression)) 6198 this = self.expression(exp.Not(this=this)) if negate else this 6199 6200 return self._parse_column_ops(this) 6201 6202 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6203 unnest = self._parse_unnest(with_alias=False) 6204 if unnest: 6205 this = self.expression(exp.In(this=this, unnest=unnest)) 6206 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6207 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6208 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6209 6210 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6211 this = self.expression( 6212 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6213 ) 6214 else: 6215 this = self.expression(exp.In(this=this, expressions=expressions)) 6216 6217 if matched_l_paren: 6218 self._match_r_paren(this) 6219 elif not self._match(TokenType.R_BRACKET, expression=this): 6220 self.raise_error("Expecting ]") 6221 else: 6222 this = self.expression(exp.In(this=this, field=self._parse_column())) 6223 6224 return this 6225 6226 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6227 symmetric = None 6228 if self._match_text_seq("SYMMETRIC"): 6229 symmetric = True 6230 elif self._match_text_seq("ASYMMETRIC"): 6231 symmetric = False 6232 6233 low = self._parse_bitwise() 6234 self._match(TokenType.AND) 6235 high = self._parse_bitwise() 6236 6237 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6238 6239 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6240 if not self._match(TokenType.ESCAPE): 6241 return this 6242 return self.expression( 6243 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6244 ) 6245 6246 def _parse_interval_span( 6247 self, this: exp.Expr, parse_function_unit: bool = True 6248 ) -> exp.Interval: 6249 # handle day-time format interval span with omitted units: 6250 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6251 interval_span_units_omitted = None 6252 if ( 6253 this 6254 and this.is_string 6255 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6256 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6257 ): 6258 index = self._index 6259 6260 # Var "TO" Var 6261 first_unit = self._parse_var(any_token=True, upper=True) 6262 second_unit = None 6263 if first_unit and self._match_text_seq("TO"): 6264 second_unit = self._parse_var(any_token=True, upper=True) 6265 6266 interval_span_units_omitted = not (first_unit and second_unit) 6267 6268 self._retreat(index) 6269 6270 unit_index = self._index 6271 if interval_span_units_omitted: 6272 unit = None 6273 else: 6274 # Only attempt to parse a unit if the current token can actually be one, so that a 6275 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6276 is_unit = self._curr is not None and ( 6277 self._curr.token_type == TokenType.VAR 6278 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6279 ) 6280 unit = self._parse_function() if parse_function_unit and is_unit else None 6281 if not unit and is_unit: 6282 unit = self._parse_var(any_token=True, upper=True) 6283 6284 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6285 # each INTERVAL expression into this canonical form so it's easy to transpile 6286 if this and this.is_number: 6287 try: 6288 this = exp.Literal.string(this.to_py()) 6289 except ValueError: 6290 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6291 elif this and this.is_string: 6292 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6293 if parts and unit: 6294 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6295 unit = None 6296 self._retreat(unit_index) 6297 6298 if len(parts) == 1: 6299 this = exp.Literal.string(parts[0][0]) 6300 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6301 6302 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6303 unit = self.expression( 6304 exp.IntervalSpan( 6305 this=unit, 6306 expression=self._parse_function() 6307 or self._parse_var(any_token=True, upper=True), 6308 ) 6309 ) 6310 6311 return self.expression(exp.Interval(this=this, unit=unit)) 6312 6313 def _parse_interval( 6314 self, require_interval: bool = True, parse_function_unit: bool = True 6315 ) -> exp.Add | exp.Interval | None: 6316 index = self._index 6317 6318 if not self._match(TokenType.INTERVAL) and require_interval: 6319 return None 6320 6321 if self._match(TokenType.STRING, advance=False): 6322 this = self._parse_primary() 6323 else: 6324 this = self._parse_term() 6325 6326 if not this or ( 6327 isinstance(this, exp.Column) 6328 and not this.table 6329 and not this.this.quoted 6330 and self._curr 6331 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6332 ): 6333 self._retreat(index) 6334 return None 6335 6336 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6337 6338 index = self._index 6339 self._match(TokenType.PLUS) 6340 6341 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6342 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6343 return self.expression( 6344 exp.Add( 6345 this=interval, 6346 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6347 ) 6348 ) 6349 6350 self._retreat(index) 6351 return interval 6352 6353 def _parse_bitwise(self) -> exp.Expr | None: 6354 this = self._parse_term() 6355 6356 while True: 6357 if self._match_set(self.BITWISE): 6358 this = self.expression( 6359 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6360 ) 6361 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6362 this = self.expression( 6363 exp.DPipe( 6364 this=this, 6365 expression=self._parse_term(), 6366 safe=not self.dialect.STRICT_STRING_CONCAT, 6367 ) 6368 ) 6369 elif self._match(TokenType.DQMARK): 6370 this = self.expression( 6371 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6372 ) 6373 elif self._match_pair(TokenType.LT, TokenType.LT): 6374 this = self.expression( 6375 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6376 ) 6377 elif self._match_pair(TokenType.GT, TokenType.GT): 6378 this = self.expression( 6379 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6380 ) 6381 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6382 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6383 else: 6384 break 6385 6386 return this 6387 6388 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6389 this = self._parse_factor(parse_mod=parse_mod) 6390 6391 while self._match_set(self.TERM): 6392 klass = self.TERM[self._prev.token_type] 6393 comments = self._prev_comments 6394 expression = self._parse_factor(parse_mod=parse_mod) 6395 6396 this = self.expression(klass(this=this, expression=expression), comments=comments) 6397 6398 if isinstance(this, exp.Collate): 6399 self._normalize_collate(this) 6400 6401 return this 6402 6403 def _normalize_collate(self, collate: exp.Collate) -> None: 6404 expr = collate.expression 6405 6406 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6407 # fallback to Identifier / Var 6408 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6409 ident = expr.this 6410 if isinstance(ident, exp.Identifier): 6411 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6412 6413 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6414 parse_method = self._parse_factor_operand 6415 this = self._parse_at_time_zone(parse_method()) 6416 6417 while self._match_set(self.FACTOR, advance=False): 6418 if not parse_mod and self._curr.token_type == TokenType.MOD: 6419 break 6420 6421 self._advance() 6422 klass = self.FACTOR[self._prev.token_type] 6423 comments = self._prev_comments 6424 expression = parse_method() 6425 6426 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6427 self._retreat(self._index - 1) 6428 return this 6429 6430 this = self.expression(klass(this=this, expression=expression), comments=comments) 6431 6432 if isinstance(this, exp.Div): 6433 this.set("typed", self.dialect.TYPED_DIVISION) 6434 this.set("safe", self.dialect.SAFE_DIVISION) 6435 6436 return this 6437 6438 def _parse_factor_operand(self) -> exp.Expr | None: 6439 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6440 6441 def _parse_exponent(self) -> exp.Expr | None: 6442 this = self._parse_unary() 6443 while self._match_set(self.EXPONENT): 6444 comments = self._prev_comments 6445 this = self.expression( 6446 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6447 comments=comments, 6448 ) 6449 return this 6450 6451 def _parse_unary(self) -> exp.Expr | None: 6452 if self._match_set(self.UNARY_PARSERS): 6453 return self.UNARY_PARSERS[self._prev.token_type](self) 6454 return self._parse_type() 6455 6456 def _parse_type( 6457 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6458 ) -> exp.Expr | None: 6459 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6460 return atom 6461 6462 if interval := parse_interval and self._parse_interval(): 6463 return self._parse_column_ops(interval) 6464 6465 index = self._index 6466 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6467 6468 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6469 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6470 if isinstance(data_type, exp.Cast): 6471 # This constructor can contain ops directly after it, for instance struct unnesting: 6472 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6473 return self._parse_column_ops(data_type) 6474 6475 if data_type: 6476 index2 = self._index 6477 this = self._parse_primary() 6478 6479 if isinstance(this, exp.Literal): 6480 literal = this.name 6481 this = self._parse_column_ops(this) 6482 6483 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6484 if parser: 6485 return parser(self, this, data_type) 6486 6487 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6488 if data_type.is_type(exp.DType.TIMESTAMP): 6489 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6490 elif data_type.is_type(exp.DType.TIME): 6491 data_type = exp.DType.TIMETZ.into_expr() 6492 6493 return self.expression(exp.Cast(this=this, to=data_type)) 6494 6495 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6496 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6497 # 6498 # If the index difference here is greater than 1, that means the parser itself must have 6499 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6500 # 6501 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6502 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6503 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6504 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6505 # 6506 # In these cases, we don't really want to return the converted type, but instead retreat 6507 # and try to parse a Column or Identifier in the section below. 6508 if data_type.expressions and index2 - index > 1: 6509 self._retreat(index2) 6510 return self._parse_column_ops(data_type) 6511 6512 self._retreat(index) 6513 6514 if fallback_to_identifier: 6515 return self._parse_id_var() 6516 6517 return self._parse_column() 6518 6519 def _parse_type_size(self) -> exp.DataTypeParam | None: 6520 this = self._parse_type() 6521 if not this: 6522 return None 6523 6524 if isinstance(this, exp.Column) and not this.table: 6525 this = exp.var(this.name.upper()) 6526 6527 return self.expression( 6528 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6529 ) 6530 6531 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6532 type_name = identifier.name 6533 6534 while self._match(TokenType.DOT): 6535 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6536 6537 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6538 6539 def _parse_types( 6540 self, 6541 check_func: bool = False, 6542 schema: bool = False, 6543 allow_identifiers: bool = True, 6544 with_collation: bool = False, 6545 ) -> exp.Expr | None: 6546 index = self._index 6547 this: exp.Expr | None = None 6548 6549 if self._match_set(self.TYPE_TOKENS): 6550 type_token = self._prev.token_type 6551 else: 6552 type_token = None 6553 identifier = allow_identifiers and self._parse_id_var( 6554 any_token=False, tokens=(TokenType.VAR,) 6555 ) 6556 if isinstance(identifier, exp.Identifier): 6557 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6558 this = exp.DataType.build(identifier, udt=True) 6559 else: 6560 try: 6561 tokens = self.dialect.tokenize(identifier.name) 6562 except TokenError: 6563 tokens = None 6564 6565 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6566 if len(tokens) > 1: 6567 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6568 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6569 this = self._parse_user_defined_type(identifier) 6570 else: 6571 self._retreat(self._index - 1) 6572 return None 6573 else: 6574 return None 6575 6576 if type_token == TokenType.PSEUDO_TYPE: 6577 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6578 6579 if type_token == TokenType.OBJECT_IDENTIFIER: 6580 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6581 6582 # https://materialize.com/docs/sql/types/map/ 6583 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6584 key_type = self._parse_types( 6585 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6586 ) 6587 if not self._match(TokenType.FARROW): 6588 self._retreat(index) 6589 return None 6590 6591 value_type = self._parse_types( 6592 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6593 ) 6594 if not self._match(TokenType.R_BRACKET): 6595 self._retreat(index) 6596 return None 6597 6598 return exp.DataType( 6599 this=exp.DType.MAP, 6600 expressions=[key_type, value_type], 6601 nested=True, 6602 ) 6603 6604 nested = type_token in self.NESTED_TYPE_TOKENS 6605 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6606 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6607 expressions = None 6608 maybe_func = False 6609 6610 if self._match(TokenType.L_PAREN): 6611 if is_struct: 6612 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6613 elif nested: 6614 expressions = self._parse_csv( 6615 lambda: self._parse_types( 6616 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6617 ) 6618 ) 6619 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6620 this = expressions[0] 6621 this.set("nullable", True) 6622 self._match_r_paren() 6623 return this 6624 elif type_token in self.ENUM_TYPE_TOKENS: 6625 expressions = self._parse_csv(self._parse_equality) 6626 elif type_token == TokenType.JSON: 6627 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6628 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6629 expressions = self._parse_csv(self._parse_json_type_arg) 6630 elif is_aggregate: 6631 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6632 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6633 ) 6634 if not func_or_ident: 6635 return None 6636 expressions = [func_or_ident] 6637 if self._match(TokenType.COMMA): 6638 expressions.extend( 6639 self._parse_csv( 6640 lambda: self._parse_types( 6641 check_func=check_func, 6642 schema=schema, 6643 allow_identifiers=allow_identifiers, 6644 ) 6645 ) 6646 ) 6647 else: 6648 expressions = self._parse_csv(self._parse_type_size) 6649 6650 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6651 if type_token == TokenType.VECTOR and len(expressions) == 2: 6652 expressions = self._parse_vector_expressions(expressions) 6653 6654 if not self._match(TokenType.R_PAREN): 6655 self._retreat(index) 6656 return None 6657 6658 maybe_func = True 6659 6660 values: list[exp.Expr] | None = None 6661 6662 if nested and self._match(TokenType.LT): 6663 if is_struct: 6664 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6665 else: 6666 expressions = self._parse_csv( 6667 lambda: self._parse_types( 6668 check_func=check_func, 6669 schema=schema, 6670 allow_identifiers=allow_identifiers, 6671 with_collation=True, 6672 ) 6673 ) 6674 6675 if not self._match(TokenType.GT): 6676 self.raise_error("Expecting >") 6677 6678 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6679 values = self._parse_csv(self._parse_disjunction) 6680 if not values and is_struct: 6681 values = None 6682 self._retreat(self._index - 1) 6683 else: 6684 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6685 6686 if type_token in self.TIMESTAMPS: 6687 if self._match_text_seq("WITH", "TIME", "ZONE"): 6688 maybe_func = False 6689 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6690 this = exp.DataType(this=tz_type, expressions=expressions) 6691 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6692 maybe_func = False 6693 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6694 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6695 maybe_func = False 6696 elif type_token == TokenType.INTERVAL: 6697 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6698 unit = self._parse_var(upper=True) 6699 if self._match_text_seq("TO"): 6700 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6701 6702 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6703 else: 6704 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6705 elif type_token == TokenType.VOID: 6706 this = exp.DataType(this=exp.DType.NULL) 6707 6708 if maybe_func and check_func: 6709 index2 = self._index 6710 peek = self._parse_string() 6711 6712 if not peek: 6713 self._retreat(index) 6714 return None 6715 6716 self._retreat(index2) 6717 6718 if not this: 6719 assert type_token is not None 6720 if self._match_text_seq("UNSIGNED"): 6721 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6722 if not unsigned_type_token: 6723 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6724 6725 type_token = unsigned_type_token or type_token 6726 6727 # NULLABLE without parentheses can be a column (Presto/Trino) 6728 if type_token == TokenType.NULLABLE and not expressions: 6729 self._retreat(index) 6730 return None 6731 6732 this = exp.DataType( 6733 this=exp.DType[type_token.name], 6734 expressions=expressions, 6735 nested=nested, 6736 ) 6737 6738 # Empty arrays/structs are allowed 6739 if values is not None: 6740 cls = exp.Struct if is_struct else exp.Array 6741 this = exp.cast(cls(expressions=values), this, copy=False) 6742 6743 elif expressions: 6744 this.set("expressions", expressions) 6745 6746 # https://materialize.com/docs/sql/types/list/#type-name 6747 while self._match(TokenType.LIST): 6748 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6749 6750 index = self._index 6751 6752 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6753 matched_array = self._match(TokenType.ARRAY) 6754 6755 while self._curr: 6756 datatype_token = self._prev.token_type 6757 matched_l_bracket = self._match(TokenType.L_BRACKET) 6758 6759 if (not matched_l_bracket and not matched_array) or ( 6760 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6761 ): 6762 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6763 # not to be confused with the fixed size array parsing 6764 break 6765 6766 matched_array = False 6767 values = self._parse_csv(self._parse_disjunction) or None 6768 if ( 6769 values 6770 and not schema 6771 and ( 6772 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6773 or datatype_token == TokenType.ARRAY 6774 or not self._match(TokenType.R_BRACKET, advance=False) 6775 ) 6776 ): 6777 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6778 # 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 6779 self._retreat(index) 6780 break 6781 6782 this = exp.DataType( 6783 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6784 ) 6785 self._match(TokenType.R_BRACKET) 6786 6787 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6788 converter = self.TYPE_CONVERTERS.get(this.this) 6789 if converter: 6790 this = converter(t.cast(exp.DataType, this)) 6791 6792 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6793 this.set("collate", self._parse_identifier() or self._parse_column()) 6794 6795 return this 6796 6797 def _parse_json_type_arg(self) -> exp.Expr | None: 6798 """Parse a single argument to ClickHouse's JSON type.""" 6799 6800 # SKIP col or SKIP REGEXP 'pattern' 6801 if self._match_text_seq("SKIP"): 6802 regexp = self._match(TokenType.RLIKE) 6803 arg = self._parse_column() 6804 if isinstance(arg, exp.Column): 6805 arg = arg.to_dot() 6806 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6807 6808 param_or_col = self._parse_column() 6809 if not isinstance(param_or_col, exp.Column): 6810 return None 6811 6812 # Parameter: name=value (e.g., max_dynamic_paths=2) 6813 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 6814 param = param_or_col.name 6815 value = self._parse_primary() 6816 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 6817 6818 # Column type hint: col_name Type 6819 col = param_or_col.to_dot() 6820 kind = self._parse_types(check_func=False, allow_identifiers=False) 6821 return self.expression(exp.ColumnDef(this=col, kind=kind)) 6822 6823 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 6824 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 6825 6826 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 6827 index = self._index 6828 6829 if ( 6830 self._curr 6831 and self._next 6832 and self._curr.token_type in self.TYPE_TOKENS 6833 and self._next.token_type in self.TYPE_TOKENS 6834 ): 6835 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 6836 # type token. Without this, the list will be parsed as a type and we'll eventually crash 6837 this = self._parse_id_var() 6838 else: 6839 this = ( 6840 self._parse_type(parse_interval=False, fallback_to_identifier=True) 6841 or self._parse_id_var() 6842 ) 6843 6844 self._match(TokenType.COLON) 6845 6846 if ( 6847 type_required 6848 and not isinstance(this, exp.DataType) 6849 and not self._match_set(self.TYPE_TOKENS, advance=False) 6850 ): 6851 self._retreat(index) 6852 return self._parse_types() 6853 6854 return self._parse_column_def(this) 6855 6856 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 6857 if not self._match_text_seq("AT", "TIME", "ZONE"): 6858 return this 6859 return self._parse_at_time_zone( 6860 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 6861 ) 6862 6863 def _parse_atom(self) -> exp.Expr | None: 6864 if ( 6865 self._curr.token_type in self.IDENTIFIER_TOKENS 6866 and (column := self._parse_column()) is not None 6867 ): 6868 return column 6869 6870 token = self._curr 6871 token_type = token.token_type 6872 6873 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 6874 return None 6875 6876 next_type = self._next.token_type 6877 6878 if ( 6879 next_type in self.COLUMN_OPERATORS 6880 or next_type in self.COLUMN_POSTFIX_TOKENS 6881 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 6882 ): 6883 return None 6884 6885 self._advance() 6886 return primary_parser(self, token) 6887 6888 def _parse_column(self) -> exp.Expr | None: 6889 column: exp.Expr | None = self._parse_column_parts_fast() 6890 if column is None: 6891 this = self._parse_column_reference() 6892 if not this: 6893 this = self._parse_bracket(this) 6894 column = self._parse_column_ops(this) if this else this 6895 6896 if column: 6897 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 6898 column.set("join_mark", self._match(TokenType.JOIN_MARKER)) 6899 if self.COLON_IS_VARIANT_EXTRACT: 6900 column = self._parse_colon_as_variant_extract(column) 6901 6902 return column 6903 6904 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 6905 """Fast path for simple column and dot references (a, a.b, ...). 6906 6907 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 6908 that nothing complex follows. If it does, retreats and returns None so 6909 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 6910 """ 6911 index = self._index 6912 parts: list[exp.Identifier] | None = None 6913 all_comments: list[str] | None = None 6914 6915 while self._match_set(self.IDENTIFIER_TOKENS): 6916 token = self._prev 6917 comments = self._prev_comments 6918 6919 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 6920 self._retreat(index) 6921 return None 6922 6923 has_dot = self._match(TokenType.DOT) 6924 curr_tt = self._curr.token_type 6925 6926 if not has_dot: 6927 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 6928 self._retreat(index) 6929 return None 6930 elif curr_tt not in self.IDENTIFIER_TOKENS: 6931 self._retreat(index) 6932 return None 6933 6934 if parts is None: 6935 parts = [] 6936 6937 if comments: 6938 if all_comments is None: 6939 all_comments = [] 6940 all_comments.extend(comments) 6941 self._prev_comments = [] 6942 6943 parts.append( 6944 self.expression( 6945 exp.Identifier( 6946 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 6947 ), 6948 token, 6949 ) 6950 ) 6951 6952 if not has_dot: 6953 break 6954 6955 if parts is None: 6956 return None 6957 6958 n = len(parts) 6959 6960 if n == 1: 6961 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 6962 elif n == 2: 6963 column = exp.Column(this=parts[1], table=parts[0]) 6964 elif n == 3: 6965 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 6966 else: 6967 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 6968 6969 for i in range(4, n): 6970 column = exp.Dot(this=column, expression=parts[i]) 6971 6972 if all_comments: 6973 column.add_comments(all_comments) 6974 6975 return column 6976 6977 def _parse_column_reference(self) -> exp.Expr | None: 6978 this = self._parse_field() 6979 if ( 6980 not this 6981 and self._match(TokenType.VALUES, advance=False) 6982 and self.VALUES_FOLLOWED_BY_PAREN 6983 and (not self._next or self._next.token_type != TokenType.L_PAREN) 6984 ): 6985 this = self._parse_id_var() 6986 6987 if isinstance(this, exp.Identifier): 6988 # We bubble up comments from the Identifier to the Column 6989 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 6990 6991 return this 6992 6993 def _build_json_extract( 6994 self, 6995 this: exp.Expr | None, 6996 path_parts: list[exp.JSONPathPart], 6997 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 6998 if len(path_parts) > 1: 6999 this = self.expression( 7000 exp.JSONExtract( 7001 this=this, 7002 expression=exp.JSONPath(expressions=path_parts), 7003 variant_extract=True, 7004 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 7005 ) 7006 ) 7007 path_parts = [exp.JSONPathRoot()] 7008 7009 return this, path_parts 7010 7011 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 7012 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 7013 7014 while self._match(TokenType.COLON): 7015 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 7016 this, path_parts = self._build_json_extract(this, path_parts) 7017 7018 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7019 7020 if key: 7021 quoted = isinstance(key, exp.Identifier) and key.quoted 7022 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 7023 7024 while True: 7025 if self._match(TokenType.DOT): 7026 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7027 7028 if next_key: 7029 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 7030 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 7031 elif self._match(TokenType.L_BRACKET): 7032 bracket_expr = self._parse_bracket_key_value() 7033 7034 if not self._match(TokenType.R_BRACKET): 7035 self.raise_error("Expected ]") 7036 7037 if bracket_expr: 7038 if bracket_expr.is_string: 7039 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7040 elif bracket_expr.is_star: 7041 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7042 elif bracket_expr.is_number: 7043 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7044 else: 7045 this, path_parts = self._build_json_extract(this, path_parts) 7046 7047 this = self.expression( 7048 exp.Bracket( 7049 this=this, expressions=[bracket_expr], json_access=True 7050 ), 7051 ) 7052 7053 elif self._match(TokenType.DCOLON): 7054 this, path_parts = self._build_json_extract(this, path_parts) 7055 7056 cast_type = self._parse_types() 7057 if cast_type: 7058 this = self.expression(exp.Cast(this=this, to=cast_type)) 7059 else: 7060 self.raise_error("Expected type after '::'") 7061 else: 7062 break 7063 7064 this, _ = self._build_json_extract(this, path_parts) 7065 7066 return this 7067 7068 def _parse_dcolon(self) -> exp.Expr | None: 7069 return self._parse_types() 7070 7071 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7072 while self._curr.token_type in self.BRACKETS: 7073 this = self._parse_bracket(this) 7074 7075 column_operators = self.COLUMN_OPERATORS 7076 cast_column_operators = self.CAST_COLUMN_OPERATORS 7077 while self._curr: 7078 op_token = self._curr.token_type 7079 7080 if op_token not in column_operators: 7081 break 7082 op = column_operators[op_token] 7083 self._advance() 7084 7085 if op_token in cast_column_operators: 7086 field = self._parse_dcolon() 7087 if not field: 7088 self.raise_error("Expected type") 7089 elif op and self._curr: 7090 field = self._parse_column_reference() or self._parse_bitwise() 7091 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7092 field = self._parse_column_ops(field) 7093 else: 7094 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7095 field = self._parse_field(any_token=True, anonymous_func=True) 7096 7097 # In t.true, t.null we should produce an Identifier node 7098 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7099 field = self.expression( 7100 exp.Identifier(this=self._prev.text), 7101 comments=field.comments, 7102 ) 7103 7104 # Function calls can be qualified, e.g., x.y.FOO() 7105 # This converts the final AST to a series of Dots leading to the function call 7106 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7107 if isinstance(field, (exp.Func, exp.Window)) and this: 7108 this = this.transform( 7109 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7110 ) 7111 7112 if op: 7113 this = op(self, this, field) 7114 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7115 this = self.expression( 7116 exp.Column( 7117 this=field, 7118 table=this.this, 7119 db=this.args.get("table"), 7120 catalog=this.args.get("db"), 7121 ), 7122 comments=this.comments, 7123 ) 7124 elif isinstance(field, exp.Window): 7125 # Move the exp.Dot's to the window's function 7126 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7127 field.set("this", window_func) 7128 this = field 7129 else: 7130 this = self.expression(exp.Dot(this=this, expression=field)) 7131 7132 if field and field.comments: 7133 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7134 7135 this = self._parse_bracket(this) 7136 7137 return this 7138 7139 def _parse_paren(self) -> exp.Expr | None: 7140 if not self._match(TokenType.L_PAREN): 7141 return None 7142 7143 comments = self._prev_comments 7144 query = self._parse_select() 7145 7146 if query: 7147 expressions = [query] 7148 else: 7149 expressions = self._parse_expressions() 7150 7151 this = seq_get(expressions, 0) 7152 7153 if not this and self._match(TokenType.R_PAREN, advance=False): 7154 this = self.expression(exp.Tuple()) 7155 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7156 this = self.expression(exp.Tuple(expressions=expressions)) 7157 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7158 this = self._parse_subquery(this=this, parse_alias=False) 7159 elif isinstance(this, (exp.Subquery, exp.Values)): 7160 this = self._parse_subquery( 7161 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7162 parse_alias=False, 7163 ) 7164 else: 7165 this = self.expression(exp.Paren(this=this)) 7166 7167 if this: 7168 this.add_comments(comments) 7169 7170 self._match_r_paren(expression=this) 7171 7172 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7173 return self._parse_window(this) 7174 7175 return this 7176 7177 def _parse_primary(self) -> exp.Expr | None: 7178 if self._match_set(self.PRIMARY_PARSERS): 7179 token_type = self._prev.token_type 7180 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7181 7182 if token_type == TokenType.STRING: 7183 expressions = [primary] 7184 while self._match(TokenType.STRING, advance=False): 7185 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7186 self.raise_error( 7187 "Adjacent string literals need to be separated by whitespace or comments" 7188 ) 7189 7190 self._advance() 7191 expressions.append(exp.Literal.string(self._prev.text)) 7192 7193 if len(expressions) > 1: 7194 return self.expression( 7195 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7196 ) 7197 7198 return primary 7199 7200 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7201 return exp.Literal.number(f"0.{self._prev.text}") 7202 7203 return self._parse_paren() 7204 7205 def _parse_field( 7206 self, 7207 any_token: bool = False, 7208 tokens: t.Collection[TokenType] | None = None, 7209 anonymous_func: bool = False, 7210 ) -> exp.Expr | None: 7211 after_dot = ( 7212 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7213 ) 7214 7215 if anonymous_func: 7216 field = ( 7217 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7218 or self._parse_primary() 7219 ) 7220 else: 7221 field = self._parse_primary() or self._parse_function( 7222 anonymous=anonymous_func, any_token=any_token 7223 ) 7224 7225 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7226 7227 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7228 name = field.name 7229 if self._is_connected() and self._parse_var(any_token=True): 7230 name += self._prev.text 7231 7232 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7233 7234 return field 7235 7236 def _parse_function( 7237 self, 7238 functions: dict[str, t.Callable] | None = None, 7239 anonymous: bool = False, 7240 optional_parens: bool = True, 7241 any_token: bool = False, 7242 ) -> exp.Expr | None: 7243 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7244 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7245 fn_syntax = False 7246 if ( 7247 self._match(TokenType.L_BRACE, advance=False) 7248 and self._next 7249 and self._next.text.upper() == "FN" 7250 ): 7251 self._advance(2) 7252 fn_syntax = True 7253 7254 func = self._parse_function_call( 7255 functions=functions, 7256 anonymous=anonymous, 7257 optional_parens=optional_parens, 7258 any_token=any_token, 7259 ) 7260 7261 if fn_syntax: 7262 self._match(TokenType.R_BRACE) 7263 7264 return func 7265 7266 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7267 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7268 7269 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7270 args = self._parse_function_args(alias=False) 7271 if not args: 7272 self.raise_error("Expected at least one argument") 7273 7274 # Wrapped so the connector keeps its precedence in the parent context 7275 return exp.Paren(this=connector(*args, copy=False)) 7276 7277 def _parse_function_call( 7278 self, 7279 functions: dict[str, t.Callable] | None = None, 7280 anonymous: bool = False, 7281 optional_parens: bool = True, 7282 any_token: bool = False, 7283 ) -> exp.Expr | None: 7284 if not self._curr: 7285 return None 7286 7287 comments = self._curr.comments 7288 prev = self._prev 7289 token = self._curr 7290 token_type = self._curr.token_type 7291 this: str | exp.Expr = self._curr.text 7292 upper = self._curr.text.upper() 7293 7294 after_dot = prev.token_type == TokenType.DOT 7295 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7296 if ( 7297 optional_parens 7298 and parser 7299 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7300 and not after_dot 7301 ): 7302 self._advance() 7303 return self._parse_window(parser(self)) 7304 7305 if self._next.token_type != TokenType.L_PAREN: 7306 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7307 self._advance() 7308 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7309 7310 return None 7311 7312 if any_token: 7313 if token_type in self.RESERVED_TOKENS: 7314 return None 7315 elif token_type not in self.FUNC_TOKENS: 7316 return None 7317 7318 self._advance(2) 7319 7320 parser = self.FUNCTION_PARSERS.get(upper) 7321 if parser and not anonymous: 7322 result = parser(self) 7323 else: 7324 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7325 7326 if subquery_predicate: 7327 expr = None 7328 if self._curr.token_type in self.SUBQUERY_TOKENS: 7329 expr = self._parse_select() 7330 self._match_r_paren() 7331 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7332 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7333 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7334 self._advance(-1) 7335 expr = self._parse_bitwise() 7336 7337 if expr: 7338 return self.expression(subquery_predicate(this=expr), comments=comments) 7339 7340 if functions is None: 7341 functions = self.FUNCTIONS 7342 7343 function = functions.get(upper) 7344 known_function = function and not anonymous 7345 7346 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7347 args = self._parse_function_args(alias) 7348 7349 post_func_comments = self._curr.comments if self._curr else None 7350 if known_function and post_func_comments: 7351 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7352 # call we'll construct it as exp.Anonymous, even if it's "known" 7353 if any( 7354 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7355 for comment in post_func_comments 7356 ): 7357 known_function = False 7358 7359 if alias and known_function: 7360 args = self._kv_to_prop_eq(args) 7361 7362 if known_function: 7363 func_builder = t.cast(t.Callable, function) 7364 7365 # mypyc compiled functions don't have __code__, so we use 7366 # try/except to check if func_builder accepts 'dialect'. 7367 try: 7368 func = func_builder(args) 7369 except TypeError: 7370 func = func_builder(args, dialect=self.dialect) 7371 7372 func = self.validate_expression(func, args) 7373 if self.dialect.PRESERVE_ORIGINAL_NAMES: 7374 func.meta["name"] = this 7375 7376 result = func 7377 else: 7378 if token_type == TokenType.IDENTIFIER: 7379 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7380 7381 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7382 7383 result = result.update_positions(token) 7384 7385 if isinstance(result, exp.Expr): 7386 result.add_comments(comments) 7387 7388 if parser: 7389 self._match(TokenType.R_PAREN, expression=result) 7390 else: 7391 self._match_r_paren(result) 7392 return self._parse_window(result) 7393 7394 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7395 return expression 7396 7397 def _kv_to_prop_eq( 7398 self, expressions: list[exp.Expr], parse_map: bool = False 7399 ) -> list[exp.Expr]: 7400 transformed = [] 7401 7402 for index, e in enumerate(expressions): 7403 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7404 if isinstance(e, exp.Alias): 7405 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7406 7407 if not isinstance(e, exp.PropertyEQ): 7408 e = self.expression( 7409 exp.PropertyEQ( 7410 this=e.this if parse_map else exp.to_identifier(e.this.name), 7411 expression=e.expression, 7412 ) 7413 ) 7414 7415 if isinstance(e.this, exp.Column): 7416 e.this.replace(e.this.this) 7417 else: 7418 e = self._to_prop_eq(e, index) 7419 7420 transformed.append(e) 7421 7422 return transformed 7423 7424 def _parse_function_properties(self) -> exp.Properties | None: 7425 # Skip the generic `key = value` fallback in _parse_property since this 7426 # runs post-AS where a function body like `name = expr` can be misread 7427 # as a property. 7428 properties = [] 7429 while True: 7430 if self._match_texts(self.PROPERTY_PARSERS): 7431 keyword = self._prev.text.upper() 7432 prop = self.PROPERTY_PARSERS[keyword](self) 7433 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7434 keyword = self._prev.text.upper() 7435 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7436 else: 7437 break 7438 if not prop: 7439 self.raise_error(f"Failed to parse property '{keyword}'") 7440 break 7441 for p in ensure_list(prop): 7442 properties.append(p) 7443 7444 return self.expression(exp.Properties(expressions=properties)) if properties else None 7445 7446 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7447 return self._parse_statement() 7448 7449 def _parse_function_parameter(self) -> exp.Expr | None: 7450 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7451 7452 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7453 this = self._parse_table_parts(schema=True) 7454 7455 if not self._match(TokenType.L_PAREN): 7456 return this 7457 7458 expressions = self._parse_csv(self._parse_function_parameter) 7459 self._match_r_paren() 7460 return self.expression( 7461 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7462 ) 7463 7464 def _parse_macro_overloads( 7465 self, 7466 this: exp.UserDefinedFunction, 7467 first_body: exp.Expr, 7468 first_is_table: bool = False, 7469 ) -> exp.MacroOverloads: 7470 overloads = [ 7471 self.expression( 7472 exp.MacroOverload( 7473 this=first_body, 7474 expressions=this.expressions or None, 7475 is_table=first_is_table, 7476 ) 7477 ) 7478 ] 7479 this.set("expressions", None) 7480 this.set("wrapped", False) 7481 7482 while self._match(TokenType.COMMA): 7483 if not self._match(TokenType.L_PAREN): 7484 break 7485 7486 params = self._parse_csv(self._parse_function_parameter) 7487 self._match_r_paren() 7488 7489 if not self._match(TokenType.ALIAS): 7490 break 7491 7492 is_table = self._match(TokenType.TABLE) 7493 body = self._parse_expression() 7494 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7495 overloads.append(self.expression(macro)) 7496 7497 return self.expression(exp.MacroOverloads(expressions=overloads)) 7498 7499 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7500 literal = self._parse_primary() 7501 if literal: 7502 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7503 7504 return self._identifier_expression(token) 7505 7506 def _parse_session_parameter(self) -> exp.SessionParameter: 7507 kind = None 7508 this = self._parse_id_var() or self._parse_primary() 7509 7510 if this and self._match(TokenType.DOT): 7511 kind = this.name 7512 this = self._parse_var() or self._parse_primary() 7513 7514 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7515 7516 def _parse_lambda_arg(self) -> exp.Expr | None: 7517 return self._parse_id_var() 7518 7519 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7520 next_token_type = self._next.token_type 7521 7522 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7523 if ( 7524 next_token_type in self.LAMBDA_ARG_TERMINATORS 7525 and (atom := self._parse_atom()) is not None 7526 ): 7527 return atom 7528 7529 index = self._index 7530 7531 if self._match(TokenType.L_PAREN): 7532 expressions = t.cast( 7533 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7534 ) 7535 7536 if not self._match(TokenType.R_PAREN): 7537 self._retreat(index) 7538 elif self._match_set(self.LAMBDAS): 7539 return self.LAMBDAS[self._prev.token_type](self, expressions) 7540 else: 7541 self._retreat(index) 7542 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7543 expressions = [self._parse_lambda_arg()] 7544 7545 if self._match_set(self.LAMBDAS): 7546 return self.LAMBDAS[self._prev.token_type](self, expressions) 7547 7548 self._retreat(index) 7549 7550 this: exp.Expr | None 7551 7552 if self._match(TokenType.DISTINCT): 7553 this = self.expression( 7554 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7555 ) 7556 else: 7557 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7558 this = self._parse_select_or_expression(alias=alias) 7559 7560 return self._parse_limit( 7561 self._parse_respect_or_ignore_nulls( 7562 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7563 ) 7564 ) 7565 7566 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7567 index = self._index 7568 if not self._match(TokenType.L_PAREN): 7569 return this 7570 7571 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7572 # expr can be of both types 7573 if self._match_set(self.SELECT_START_TOKENS): 7574 self._retreat(index) 7575 return this 7576 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7577 self._match_r_paren() 7578 return self.expression(exp.Schema(this=this, expressions=args)) 7579 7580 def _parse_field_def(self) -> exp.Expr | None: 7581 return self._parse_column_def(self._parse_field(any_token=True)) 7582 7583 def _parse_column_def( 7584 self, this: exp.Expr | None, computed_column: bool = True 7585 ) -> exp.Expr | None: 7586 # column defs are not really columns, they're identifiers 7587 if isinstance(this, exp.Column): 7588 this = this.this 7589 7590 if not computed_column: 7591 self._match(TokenType.ALIAS) 7592 7593 kind = self._parse_types(schema=True) 7594 7595 if self._match_text_seq("FOR", "ORDINALITY"): 7596 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7597 7598 constraints: list[exp.Expr] = [] 7599 7600 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7601 ("ALIAS", "MATERIALIZED") 7602 ): 7603 # Match storage before _parse_types so STORED is not treated as a data type 7604 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7605 persisted = self._prev.text.upper() == "MATERIALIZED" 7606 expression = self._parse_disjunction() 7607 if not persisted: 7608 if self._match_text_seq("PERSISTED"): 7609 persisted = True 7610 elif self._match_texts(("STORED", "VIRTUAL")): 7611 persisted = self._prev.text.upper() == "STORED" 7612 constraint_kind = exp.ComputedColumnConstraint( 7613 this=expression, 7614 persisted=persisted, 7615 data_type=exp.Var(this="AUTO") 7616 if self._match_text_seq("AUTO") 7617 else self._parse_types(), 7618 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7619 ) 7620 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7621 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7622 in_out_constraint = self.expression( 7623 exp.InOutColumnConstraint( 7624 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7625 ) 7626 ) 7627 constraints.append(in_out_constraint) 7628 kind = self._parse_types() 7629 elif ( 7630 kind 7631 and self._match(TokenType.ALIAS, advance=False) 7632 and ( 7633 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7634 or self._next.token_type == TokenType.L_PAREN 7635 ) 7636 ): 7637 self._advance() 7638 constraints.append( 7639 self.expression( 7640 exp.ColumnConstraint( 7641 kind=exp.ComputedColumnConstraint( 7642 this=self._parse_disjunction(), 7643 persisted=self._match_texts(("STORED", "VIRTUAL")) 7644 and self._prev.text.upper() == "STORED", 7645 ) 7646 ) 7647 ) 7648 ) 7649 7650 while True: 7651 constraint = self._parse_column_constraint() 7652 if not constraint: 7653 break 7654 constraints.append(constraint) 7655 7656 if not kind and not constraints: 7657 return this 7658 7659 position = None 7660 if self._match_texts(("FIRST", "AFTER")): 7661 pos = self._prev.text 7662 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7663 7664 return self.expression( 7665 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7666 ) 7667 7668 def _parse_auto_increment( 7669 self, 7670 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7671 start = None 7672 increment = None 7673 order = None 7674 7675 if self._match(TokenType.L_PAREN, advance=False): 7676 args = self._parse_wrapped_csv(self._parse_bitwise) 7677 start = seq_get(args, 0) 7678 increment = seq_get(args, 1) 7679 7680 # The remaining parts form an unordered bag and any of them can be omitted, in which 7681 # case the engine falls back to its own default, so they're parsed independently. 7682 while True: 7683 if self._match_text_seq("START"): 7684 start = self._parse_bitwise() 7685 elif self._match_text_seq("INCREMENT"): 7686 increment = self._parse_bitwise() 7687 elif self._match_text_seq("ORDER"): 7688 order = True 7689 elif self._match_text_seq("NOORDER"): 7690 order = False 7691 else: 7692 break 7693 7694 if start or increment or order is not None: 7695 return exp.GeneratedAsIdentityColumnConstraint( 7696 start=start, increment=increment, this=False, order=order 7697 ) 7698 7699 return exp.AutoIncrementColumnConstraint() 7700 7701 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7702 if not self._match(TokenType.L_PAREN, advance=False): 7703 return None 7704 7705 return self.expression( 7706 exp.CheckColumnConstraint( 7707 this=self._parse_wrapped(self._parse_assignment), 7708 enforced=self._match_text_seq("ENFORCED"), 7709 ) 7710 ) 7711 7712 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7713 if not self._match_text_seq("REFRESH"): 7714 self._retreat(self._index - 1) 7715 return None 7716 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7717 7718 def _parse_compress(self) -> exp.CompressColumnConstraint: 7719 if self._match(TokenType.L_PAREN, advance=False): 7720 return self.expression( 7721 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7722 ) 7723 7724 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7725 7726 def _parse_generated_as_identity( 7727 self, 7728 ) -> ( 7729 exp.GeneratedAsIdentityColumnConstraint 7730 | exp.ComputedColumnConstraint 7731 | exp.GeneratedAsRowColumnConstraint 7732 ): 7733 if self._match_text_seq("BY", "DEFAULT"): 7734 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7735 this = self.expression( 7736 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7737 ) 7738 else: 7739 self._match_text_seq("ALWAYS") 7740 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7741 7742 self._match(TokenType.ALIAS) 7743 7744 if self._match_text_seq("ROW"): 7745 start = self._match_text_seq("START") 7746 if not start: 7747 self._match(TokenType.END) 7748 hidden = self._match_text_seq("HIDDEN") 7749 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7750 7751 identity = self._match_text_seq("IDENTITY") 7752 7753 if self._match(TokenType.L_PAREN): 7754 if self._match_text_seq("START", "WITH"): 7755 this.set("start", self._parse_bitwise()) 7756 if self._match_text_seq("INCREMENT", "BY"): 7757 this.set("increment", self._parse_bitwise()) 7758 if self._match_text_seq("MINVALUE"): 7759 this.set("minvalue", self._parse_bitwise()) 7760 if self._match_text_seq("MAXVALUE"): 7761 this.set("maxvalue", self._parse_bitwise()) 7762 7763 if self._match_text_seq("CYCLE"): 7764 this.set("cycle", True) 7765 elif self._match_text_seq("NO", "CYCLE"): 7766 this.set("cycle", False) 7767 7768 if not identity: 7769 this.set("expression", self._parse_range()) 7770 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7771 args = self._parse_csv(self._parse_bitwise) 7772 this.set("start", seq_get(args, 0)) 7773 this.set("increment", seq_get(args, 1)) 7774 7775 self._match_r_paren() 7776 7777 return this 7778 7779 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7780 self._match_text_seq("LENGTH") 7781 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7782 7783 def _parse_not_constraint(self) -> exp.Expr | None: 7784 if self._match_text_seq("NULL"): 7785 return self.expression(exp.NotNullColumnConstraint()) 7786 if self._match_text_seq("CASESPECIFIC"): 7787 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7788 if self._match_text_seq("FOR", "REPLICATION"): 7789 return self.expression(exp.NotForReplicationColumnConstraint()) 7790 7791 # Unconsume the `NOT` token 7792 self._retreat(self._index - 1) 7793 return None 7794 7795 def _parse_column_constraint(self) -> exp.Expr | None: 7796 this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None 7797 7798 procedure_option_follows = ( 7799 self._match(TokenType.WITH, advance=False) 7800 and self._next 7801 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7802 ) 7803 7804 index = self._index 7805 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 7806 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 7807 if not constraint: 7808 self._retreat(index) 7809 return None 7810 7811 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 7812 7813 if self._match_text_seq("CHARACTER", "SET"): 7814 return self.expression( 7815 exp.ColumnConstraint( 7816 this=this, 7817 kind=self.expression( 7818 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 7819 ), 7820 ) 7821 ) 7822 7823 return this 7824 7825 def _parse_constraint(self) -> exp.Expr | None: 7826 if not self._match(TokenType.CONSTRAINT): 7827 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 7828 7829 return self.expression( 7830 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 7831 ) 7832 7833 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 7834 constraints = [] 7835 while True: 7836 constraint = self._parse_unnamed_constraint() or self._parse_function() 7837 if not constraint: 7838 break 7839 constraints.append(constraint) 7840 7841 return constraints 7842 7843 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 7844 index = self._index 7845 7846 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 7847 constraints or self.CONSTRAINT_PARSERS 7848 ): 7849 return None 7850 7851 constraint_key = self._prev.text.upper() 7852 if constraint_key not in self.CONSTRAINT_PARSERS: 7853 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 7854 7855 result = self.CONSTRAINT_PARSERS[constraint_key](self) 7856 if not result: 7857 self._retreat(index) 7858 7859 return result 7860 7861 def _parse_unique_key(self) -> exp.Expr | None: 7862 if ( 7863 self._curr 7864 and self._curr.token_type != TokenType.IDENTIFIER 7865 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 7866 ): 7867 return None 7868 return self._parse_id_var(any_token=False) 7869 7870 def _parse_unique(self) -> exp.UniqueColumnConstraint: 7871 self._match_texts(("KEY", "INDEX")) 7872 return self.expression( 7873 exp.UniqueColumnConstraint( 7874 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 7875 this=self._parse_schema(self._parse_unique_key()), 7876 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 7877 on_conflict=self._parse_on_conflict(), 7878 options=self._parse_key_constraint_options(), 7879 ) 7880 ) 7881 7882 def _parse_key_constraint_options(self) -> list[str]: 7883 options = [] 7884 while True: 7885 if not self._curr: 7886 break 7887 7888 if self._match(TokenType.ON): 7889 action = None 7890 on = self._advance_any() and self._prev.text 7891 7892 if self._match_text_seq("NO", "ACTION"): 7893 action = "NO ACTION" 7894 elif self._match_text_seq("CASCADE"): 7895 action = "CASCADE" 7896 elif self._match_text_seq("RESTRICT"): 7897 action = "RESTRICT" 7898 elif self._match_pair(TokenType.SET, TokenType.NULL): 7899 action = "SET NULL" 7900 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 7901 action = "SET DEFAULT" 7902 else: 7903 self.raise_error("Invalid key constraint") 7904 7905 options.append(f"ON {on} {action}") 7906 else: 7907 var = self._parse_var_from_options( 7908 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 7909 ) 7910 if not var: 7911 break 7912 options.append(var.name) 7913 7914 return options 7915 7916 def _parse_references(self, match: bool = True) -> exp.Reference | None: 7917 if match and not self._match(TokenType.REFERENCES): 7918 return None 7919 7920 expressions: list | None = None 7921 this = self._parse_table(schema=True) 7922 options = self._parse_key_constraint_options() 7923 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 7924 7925 def _parse_foreign_key(self) -> exp.ForeignKey: 7926 expressions = ( 7927 self._parse_wrapped_id_vars() 7928 if not self._match(TokenType.REFERENCES, advance=False) 7929 else None 7930 ) 7931 reference = self._parse_references() 7932 on_options = {} 7933 7934 while self._match(TokenType.ON): 7935 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 7936 self.raise_error("Expected DELETE or UPDATE") 7937 7938 kind = self._prev.text.lower() 7939 7940 if self._match_text_seq("NO", "ACTION"): 7941 action = "NO ACTION" 7942 elif self._match(TokenType.SET): 7943 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 7944 action = "SET " + self._prev.text.upper() 7945 else: 7946 self._advance() 7947 action = self._prev.text.upper() 7948 7949 on_options[kind] = action 7950 7951 return self.expression( 7952 exp.ForeignKey( 7953 expressions=expressions, 7954 reference=reference, 7955 options=self._parse_key_constraint_options(), 7956 **on_options, 7957 ) 7958 ) 7959 7960 def _parse_primary_key_part(self) -> exp.Expr | None: 7961 return self._parse_field() 7962 7963 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 7964 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 7965 self._retreat(self._index - 1) 7966 return None 7967 7968 id_vars = self._parse_wrapped_id_vars() 7969 return self.expression( 7970 exp.PeriodForSystemTimeConstraint( 7971 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 7972 ) 7973 ) 7974 7975 def _parse_primary_key( 7976 self, 7977 wrapped_optional: bool = False, 7978 in_props: bool = False, 7979 named_primary_key: bool = False, 7980 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 7981 desc = ( 7982 self._prev.token_type == TokenType.DESC 7983 if self._match_set((TokenType.ASC, TokenType.DESC)) 7984 else None 7985 ) 7986 7987 this = None 7988 if ( 7989 named_primary_key 7990 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 7991 and self._next 7992 and self._next.token_type == TokenType.L_PAREN 7993 ): 7994 this = self._parse_id_var() 7995 7996 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 7997 return self.expression( 7998 exp.PrimaryKeyColumnConstraint( 7999 desc=desc, options=self._parse_key_constraint_options() 8000 ) 8001 ) 8002 8003 expressions = self._parse_wrapped_csv( 8004 self._parse_primary_key_part, optional=wrapped_optional 8005 ) 8006 8007 return self.expression( 8008 exp.PrimaryKey( 8009 this=this, 8010 expressions=expressions, 8011 include=self._parse_index_params(), 8012 options=self._parse_key_constraint_options(), 8013 ) 8014 ) 8015 8016 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 8017 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 8018 8019 def _parse_odbc_datetime_literal(self) -> exp.Expr: 8020 """ 8021 Parses a datetime column in ODBC format. We parse the column into the corresponding 8022 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 8023 same as we did for `DATE('yyyy-mm-dd')`. 8024 8025 Reference: 8026 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 8027 """ 8028 self._match(TokenType.VAR) 8029 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 8030 expression = self.expression(exp_class(this=self._parse_string())) 8031 if not self._match(TokenType.R_BRACE): 8032 self.raise_error("Expected }") 8033 return expression 8034 8035 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 8036 if not self._match_set(self.BRACKETS): 8037 return this 8038 8039 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8040 map_token = seq_get(self._tokens, self._index - 2) 8041 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8042 else: 8043 parse_map = False 8044 8045 bracket_kind = self._prev.token_type 8046 if ( 8047 bracket_kind == TokenType.L_BRACE 8048 and self._curr 8049 and self._curr.token_type == TokenType.VAR 8050 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8051 ): 8052 return self._parse_odbc_datetime_literal() 8053 8054 expressions = self._parse_csv( 8055 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8056 ) 8057 8058 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8059 self.raise_error("Expected ]") 8060 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8061 self.raise_error("Expected }") 8062 8063 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8064 if bracket_kind == TokenType.L_BRACE: 8065 this = self.expression( 8066 exp.Struct( 8067 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8068 ) 8069 ) 8070 elif not this: 8071 this = build_array_constructor( 8072 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8073 ) 8074 else: 8075 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8076 if constructor_type: 8077 return build_array_constructor( 8078 constructor_type, 8079 args=expressions, 8080 bracket_kind=bracket_kind, 8081 dialect=self.dialect, 8082 ) 8083 8084 expressions = apply_index_offset( 8085 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8086 ) 8087 this = self.expression( 8088 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8089 ) 8090 8091 self._add_comments(this) 8092 return self._parse_bracket(this) 8093 8094 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8095 if not self._match(TokenType.COLON): 8096 return this 8097 8098 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8099 self._advance() 8100 end: exp.Expr | None = -exp.Literal.number("1") 8101 else: 8102 end = self._parse_assignment() 8103 step = self._parse_unary() if self._match(TokenType.COLON) else None 8104 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8105 8106 def _parse_case(self) -> exp.Expr | None: 8107 if self._match(TokenType.DOT, advance=False): 8108 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8109 self._retreat(self._index - 1) 8110 return None 8111 8112 ifs = [] 8113 default = None 8114 8115 comments = self._prev_comments 8116 expression = self._parse_disjunction() 8117 8118 while self._match(TokenType.WHEN): 8119 this = self._parse_disjunction() 8120 self._match(TokenType.THEN) 8121 then = self._parse_disjunction() 8122 ifs.append(self.expression(exp.If(this=this, true=then))) 8123 8124 if self._match(TokenType.ELSE): 8125 default = self._parse_disjunction() 8126 8127 if not self._match(TokenType.END): 8128 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8129 default = exp.column("interval") 8130 else: 8131 self.raise_error("Expected END after CASE", self._prev) 8132 8133 return self.expression( 8134 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8135 ) 8136 8137 def _parse_if(self) -> exp.Expr | None: 8138 if self._match(TokenType.L_PAREN): 8139 args = self._parse_csv( 8140 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8141 ) 8142 this = self.validate_expression(exp.If.from_arg_list(args), args) 8143 self._match_r_paren() 8144 else: 8145 index = self._index - 1 8146 8147 if self.NO_PAREN_IF_COMMANDS and index == 0: 8148 return self._parse_as_command(self._prev) 8149 8150 condition = self._parse_disjunction() 8151 8152 if not condition: 8153 self._retreat(index) 8154 return None 8155 8156 self._match(TokenType.THEN) 8157 true = self._parse_disjunction() 8158 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8159 self._match(TokenType.END) 8160 this = self.expression(exp.If(this=condition, true=true, false=false)) 8161 8162 return this 8163 8164 def _parse_next_value_for(self) -> exp.Expr | None: 8165 if not self._match_text_seq("VALUE", "FOR"): 8166 self._retreat(self._index - 1) 8167 return None 8168 8169 return self.expression( 8170 exp.NextValueFor( 8171 this=self._parse_column(), 8172 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8173 ) 8174 ) 8175 8176 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8177 this = self._parse_function() or self._parse_var_or_string(upper=True) 8178 8179 if self._match(TokenType.FROM): 8180 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8181 8182 if not self._match(TokenType.COMMA): 8183 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8184 8185 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8186 8187 def _parse_gap_fill(self) -> exp.GapFill: 8188 self._match(TokenType.TABLE) 8189 this = self._parse_table() 8190 8191 self._match(TokenType.COMMA) 8192 args = [this, *self._parse_csv(self._parse_lambda)] 8193 8194 gap_fill = exp.GapFill.from_arg_list(args) 8195 return self.validate_expression(gap_fill, args) 8196 8197 def _parse_char(self) -> exp.Chr: 8198 return self.expression( 8199 exp.Chr( 8200 expressions=self._parse_csv(self._parse_assignment), 8201 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8202 ) 8203 ) 8204 8205 def _parse_charset_name(self) -> exp.Expr | None: 8206 """ 8207 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8208 for specific name shapes override this. 8209 """ 8210 return self._parse_var( 8211 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8212 ) 8213 8214 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8215 this = self._parse_assignment() 8216 8217 if not self._match(TokenType.ALIAS): 8218 if self._match(TokenType.COMMA): 8219 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8220 8221 self.raise_error("Expected AS after CAST") 8222 8223 fmt = None 8224 to = self._parse_types(with_collation=True) 8225 8226 default = None 8227 if self._match(TokenType.DEFAULT): 8228 default = self._parse_bitwise() 8229 self._match_text_seq("ON", "CONVERSION", "ERROR") 8230 8231 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8232 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8233 fmt = self._parse_at_time_zone(fmt_string) 8234 8235 if not to: 8236 to = exp.DType.UNKNOWN.into_expr() 8237 if to.this in exp.DataType.TEMPORAL_TYPES: 8238 this = self.expression( 8239 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8240 this=this, 8241 format=exp.Literal.string( 8242 format_time( 8243 fmt_string.this if fmt_string else "", 8244 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8245 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8246 ) 8247 ), 8248 safe=safe, 8249 ) 8250 ) 8251 8252 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8253 this.set("zone", fmt.args["zone"]) 8254 return this 8255 elif not to: 8256 self.raise_error("Expected TYPE after CAST") 8257 elif isinstance(to, exp.Identifier): 8258 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8259 elif to.this == exp.DType.CHAR and ( 8260 self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET") 8261 ): 8262 to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string()) 8263 8264 return self.build_cast( 8265 strict=strict, 8266 this=this, 8267 to=to, 8268 format=fmt, 8269 safe=safe, 8270 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8271 default=default, 8272 ) 8273 8274 def _parse_string_agg(self) -> exp.GroupConcat: 8275 if self._match(TokenType.DISTINCT): 8276 args: list[exp.Expr | None] = [ 8277 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8278 ] 8279 if self._match(TokenType.COMMA): 8280 args.extend(self._parse_csv(self._parse_disjunction)) 8281 else: 8282 args = self._parse_csv(self._parse_disjunction) # type: ignore 8283 8284 if self._match_text_seq("ON", "OVERFLOW"): 8285 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8286 if self._match_text_seq("ERROR"): 8287 on_overflow: exp.Expr | None = exp.var("ERROR") 8288 else: 8289 self._match_text_seq("TRUNCATE") 8290 on_overflow = self.expression( 8291 exp.OverflowTruncateBehavior( 8292 this=self._parse_string(), 8293 with_count=( 8294 self._match_text_seq("WITH", "COUNT") 8295 or not self._match_text_seq("WITHOUT", "COUNT") 8296 ), 8297 ) 8298 ) 8299 else: 8300 on_overflow = None 8301 8302 index = self._index 8303 if not self._match(TokenType.R_PAREN) and args: 8304 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8305 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8306 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8307 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8308 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8309 8310 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8311 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8312 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8313 if not self._match_text_seq("WITHIN", "GROUP"): 8314 self._retreat(index) 8315 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8316 8317 # The corresponding match_r_paren will be called in parse_function (caller) 8318 self._match_l_paren() 8319 8320 return self.expression( 8321 exp.GroupConcat( 8322 this=self._parse_order(this=seq_get(args, 0)), 8323 separator=seq_get(args, 1), 8324 on_overflow=on_overflow, 8325 ) 8326 ) 8327 8328 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8329 this = self._parse_bitwise() 8330 8331 if self._match(TokenType.USING): 8332 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8333 elif self._match(TokenType.COMMA): 8334 to = self._parse_types() 8335 else: 8336 to = None 8337 8338 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8339 8340 def _parse_xml_element(self) -> exp.XMLElement: 8341 if self._match_text_seq("EVALNAME"): 8342 evalname = True 8343 this = self._parse_bitwise() 8344 else: 8345 evalname = None 8346 self._match_text_seq("NAME") 8347 this = self._parse_id_var() 8348 8349 return self.expression( 8350 exp.XMLElement( 8351 this=this, 8352 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8353 evalname=evalname, 8354 ) 8355 ) 8356 8357 def _parse_xml_table(self) -> exp.XMLTable: 8358 namespaces = None 8359 passing = None 8360 columns = None 8361 8362 if self._match_text_seq("XMLNAMESPACES", "("): 8363 namespaces = self._parse_xml_namespace() 8364 self._match_text_seq(")", ",") 8365 8366 this = self._parse_string() 8367 8368 if self._match_text_seq("PASSING"): 8369 # The BY VALUE keywords are optional and are provided for semantic clarity 8370 self._match_text_seq("BY", "VALUE") 8371 passing = self._parse_csv(self._parse_column) 8372 8373 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8374 8375 if self._match_text_seq("COLUMNS"): 8376 columns = self._parse_csv(self._parse_field_def) 8377 8378 return self.expression( 8379 exp.XMLTable( 8380 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8381 ) 8382 ) 8383 8384 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8385 namespaces = [] 8386 8387 while True: 8388 if self._match(TokenType.DEFAULT): 8389 uri = self._parse_string() 8390 else: 8391 uri = self._parse_alias(self._parse_string()) 8392 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8393 if not self._match(TokenType.COMMA): 8394 break 8395 8396 return namespaces 8397 8398 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8399 args = self._parse_csv(self._parse_disjunction) 8400 8401 if len(args) < 3: 8402 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8403 8404 return self.expression(exp.DecodeCase(expressions=args)) 8405 8406 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8407 self._match_text_seq("KEY") 8408 key = self._parse_column() 8409 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8410 self._match_text_seq("VALUE") 8411 value = self._parse_bitwise() 8412 8413 if not key and not value: 8414 return None 8415 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8416 8417 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8418 if not this or not self._match_text_seq("FORMAT", "JSON"): 8419 return this 8420 8421 return self.expression(exp.FormatJson(this=this)) 8422 8423 def _parse_on_condition(self) -> exp.OnCondition | None: 8424 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8425 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8426 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8427 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8428 else: 8429 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8430 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8431 8432 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8433 8434 if not empty and not error and not null: 8435 return None 8436 8437 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8438 8439 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8440 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8441 for value in values: 8442 if self._match_text_seq(value, "ON", on): 8443 return f"{value} ON {on}" 8444 8445 index = self._index 8446 if self._match(TokenType.DEFAULT): 8447 default_value = self._parse_bitwise() 8448 if self._match_text_seq("ON", on): 8449 return default_value 8450 8451 self._retreat(index) 8452 8453 return None 8454 8455 @t.overload 8456 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8457 8458 @t.overload 8459 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8460 8461 def _parse_json_object(self, agg=False): 8462 star = self._parse_star() 8463 expressions = ( 8464 [star] 8465 if star 8466 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8467 ) 8468 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8469 8470 unique_keys = None 8471 if self._match_text_seq("WITH", "UNIQUE"): 8472 unique_keys = True 8473 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8474 unique_keys = False 8475 8476 self._match_text_seq("KEYS") 8477 8478 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8479 self._parse_type() 8480 ) 8481 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8482 8483 return self.expression( 8484 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8485 expressions=expressions, 8486 null_handling=null_handling, 8487 unique_keys=unique_keys, 8488 return_type=return_type, 8489 encoding=encoding, 8490 ) 8491 ) 8492 8493 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8494 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8495 if not self._match_text_seq("NESTED"): 8496 this = self._parse_id_var() 8497 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8498 kind = self._parse_types(allow_identifiers=False) 8499 nested = None 8500 else: 8501 this = None 8502 ordinality = None 8503 kind = None 8504 nested = True 8505 8506 format_json = self._match_text_seq("FORMAT", "JSON") 8507 path = self._match_text_seq("PATH") and self._parse_string() 8508 nested_schema = nested and self._parse_json_schema() 8509 8510 return self.expression( 8511 exp.JSONColumnDef( 8512 this=this, 8513 kind=kind, 8514 path=path, 8515 nested_schema=nested_schema, 8516 ordinality=ordinality, 8517 format_json=format_json, 8518 ) 8519 ) 8520 8521 def _parse_json_schema(self) -> exp.JSONSchema: 8522 self._match_text_seq("COLUMNS") 8523 return self.expression( 8524 exp.JSONSchema( 8525 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8526 ) 8527 ) 8528 8529 def _parse_json_table(self) -> exp.JSONTable: 8530 this = self._parse_format_json(self._parse_bitwise()) 8531 path = self._match(TokenType.COMMA) and self._parse_string() 8532 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8533 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8534 schema = self._parse_json_schema() 8535 8536 return exp.JSONTable( 8537 this=this, 8538 schema=schema, 8539 path=path, 8540 error_handling=error_handling, 8541 empty_handling=empty_handling, 8542 ) 8543 8544 def _parse_match_against(self) -> exp.MatchAgainst: 8545 if self._match_text_seq("TABLE"): 8546 # parse SingleStore MATCH(TABLE ...) syntax 8547 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8548 expressions = [] 8549 table = self._parse_table() 8550 if table: 8551 expressions = [table] 8552 else: 8553 expressions = self._parse_csv(self._parse_column) 8554 8555 self._match_text_seq(")", "AGAINST", "(") 8556 8557 this = self._parse_string() 8558 8559 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8560 modifier = "IN NATURAL LANGUAGE MODE" 8561 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8562 modifier = f"{modifier} WITH QUERY EXPANSION" 8563 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8564 modifier = "IN BOOLEAN MODE" 8565 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8566 modifier = "WITH QUERY EXPANSION" 8567 else: 8568 modifier = None 8569 8570 return self.expression( 8571 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8572 ) 8573 8574 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8575 def _parse_open_json(self) -> exp.OpenJSON: 8576 this = self._parse_bitwise() 8577 path = self._match(TokenType.COMMA) and self._parse_string() 8578 8579 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8580 this = self._parse_field(any_token=True) 8581 kind = self._parse_types() 8582 path = self._parse_string() 8583 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8584 8585 return self.expression( 8586 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8587 ) 8588 8589 expressions = None 8590 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8591 self._match_l_paren() 8592 expressions = self._parse_csv(_parse_open_json_column_def) 8593 8594 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8595 8596 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8597 args = self._parse_csv(self._parse_bitwise) 8598 8599 if self._match(TokenType.IN): 8600 return self.expression( 8601 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8602 ) 8603 8604 if haystack_first: 8605 haystack = seq_get(args, 0) 8606 needle = seq_get(args, 1) 8607 else: 8608 haystack = seq_get(args, 1) 8609 needle = seq_get(args, 0) 8610 8611 return self.expression( 8612 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8613 ) 8614 8615 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8616 args = self._parse_csv(self._parse_table) 8617 return exp.JoinHint(this=func_name.upper(), expressions=args) 8618 8619 def _parse_substring(self) -> exp.Substring: 8620 # Postgres supports the form: substring(string [from int] [for int]) 8621 # (despite being undocumented, the reverse order also works) 8622 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8623 8624 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8625 8626 start, length = None, None 8627 8628 while self._curr: 8629 if self._match(TokenType.FROM): 8630 start = self._parse_bitwise() 8631 elif self._match(TokenType.FOR): 8632 if not start: 8633 start = exp.Literal.number(1) 8634 length = self._parse_bitwise() 8635 else: 8636 break 8637 8638 if start: 8639 args.append(start) 8640 if length: 8641 args.append(length) 8642 8643 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8644 8645 def _parse_trim(self) -> exp.Trim: 8646 # https://www.w3resource.com/sql/character-functions/trim.php 8647 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8648 8649 position = None 8650 collation = None 8651 expression = None 8652 8653 if self._match_texts(self.TRIM_TYPES): 8654 position = self._prev.text.upper() 8655 8656 this = self._parse_bitwise() 8657 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8658 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8659 expression = self._parse_bitwise() 8660 8661 if invert_order: 8662 this, expression = expression, this 8663 8664 if self._match(TokenType.COLLATE): 8665 collation = self._parse_bitwise() 8666 8667 return self.expression( 8668 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8669 ) 8670 8671 def _parse_window_clause(self) -> list[exp.Expr] | None: 8672 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8673 8674 def _parse_named_window(self) -> exp.Expr | None: 8675 return self._parse_window(self._parse_id_var(), alias=True) 8676 8677 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8678 if self._curr.token_type == TokenType.VAR: 8679 if self._match_text_seq("IGNORE", "NULLS"): 8680 return self.expression(exp.IgnoreNulls(this=this)) 8681 if self._match_text_seq("RESPECT", "NULLS"): 8682 return self.expression(exp.RespectNulls(this=this)) 8683 return this 8684 8685 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8686 if self._match(TokenType.HAVING): 8687 self._match_texts(("MAX", "MIN")) 8688 max = self._prev.text.upper() != "MIN" 8689 return self.expression( 8690 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8691 ) 8692 8693 return this 8694 8695 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8696 func = this 8697 comments = func.comments if isinstance(func, exp.Expr) else None 8698 8699 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8700 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8701 if self._match_text_seq("FROM", "FIRST"): 8702 this.set("from_first", True) 8703 elif self._match_text_seq("FROM", "LAST"): 8704 this.set("from_first", False) 8705 8706 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8707 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8708 if self._match_text_seq("WITHIN", "GROUP"): 8709 order = self._parse_wrapped(self._parse_order) 8710 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8711 8712 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8713 self._match(TokenType.WHERE) 8714 this = self.expression( 8715 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8716 ) 8717 self._match_r_paren() 8718 8719 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8720 # Some dialects choose to implement and some do not. 8721 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8722 8723 # There is some code above in _parse_lambda that handles 8724 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8725 8726 # The below changes handle 8727 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8728 8729 # Oracle allows both formats 8730 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8731 # and Snowflake chose to do the same for familiarity 8732 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8733 if isinstance(this, exp.AggFunc): 8734 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8735 8736 if ignore_respect and ignore_respect is not this: 8737 ignore_respect.replace(ignore_respect.this) 8738 this = self.expression(ignore_respect.__class__(this=this)) 8739 8740 this = self._parse_respect_or_ignore_nulls(this) 8741 8742 # bigquery select from window x AS (partition by ...) 8743 if alias: 8744 over = None 8745 self._match(TokenType.ALIAS) 8746 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8747 return this 8748 else: 8749 over = self._prev.text.upper() 8750 8751 if comments and isinstance(func, exp.Expr): 8752 func.pop_comments() 8753 8754 if not self._match(TokenType.L_PAREN): 8755 return self.expression( 8756 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8757 ) 8758 8759 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8760 8761 first: bool | None = True if self._match(TokenType.FIRST) else None 8762 if self._match_text_seq("LAST"): 8763 first = False 8764 8765 partition, order = self._parse_partition_and_order() 8766 kind = ( 8767 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8768 ) and self._prev.text 8769 8770 if kind: 8771 self._match(TokenType.BETWEEN) 8772 start = self._parse_window_spec() 8773 8774 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8775 exclude = ( 8776 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8777 if self._match_text_seq("EXCLUDE") 8778 else None 8779 ) 8780 8781 spec = self.expression( 8782 exp.WindowSpec( 8783 kind=kind, 8784 start=start["value"], 8785 start_side=start["side"], 8786 end=end.get("value"), 8787 end_side=end.get("side"), 8788 exclude=exclude, 8789 ) 8790 ) 8791 else: 8792 spec = None 8793 8794 self._match_r_paren() 8795 8796 window = self.expression( 8797 exp.Window( 8798 this=this, 8799 partition_by=partition, 8800 order=order, 8801 spec=spec, 8802 alias=window_alias, 8803 over=over, 8804 first=first, 8805 ), 8806 comments=comments, 8807 ) 8808 8809 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 8810 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 8811 return self._parse_window(window, alias=alias) 8812 8813 return window 8814 8815 def _parse_partition_and_order( 8816 self, 8817 ) -> tuple[list[exp.Expr], exp.Expr | None]: 8818 return self._parse_partition_by(), self._parse_order() 8819 8820 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 8821 self._match(TokenType.BETWEEN) 8822 8823 return { 8824 "value": ( 8825 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 8826 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 8827 or self._parse_bitwise() 8828 ), 8829 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 8830 } 8831 8832 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 8833 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 8834 # so this section tries to parse the clause version and if it fails, it treats the token 8835 # as an identifier (alias) 8836 if self._can_parse_limit_or_offset(): 8837 return this 8838 8839 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 8840 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 8841 if self._can_parse_named_window(): 8842 return this 8843 8844 any_token = self._match(TokenType.ALIAS) 8845 comments = self._prev_comments 8846 8847 if explicit and not any_token: 8848 return this 8849 8850 if self._match(TokenType.L_PAREN): 8851 aliases = self.expression( 8852 exp.Aliases( 8853 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 8854 ), 8855 comments=comments, 8856 ) 8857 self._match_r_paren(aliases) 8858 return aliases 8859 8860 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 8861 self.STRING_ALIASES and self._parse_string_as_identifier() 8862 ) 8863 8864 if alias: 8865 comments.extend(alias.pop_comments()) 8866 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 8867 column = this.this 8868 8869 # Moves the comment next to the alias in `expr /* comment */ AS alias` 8870 if not this.comments and column and column.comments: 8871 this.comments = column.pop_comments() 8872 8873 return this 8874 8875 def _parse_id_var( 8876 self, 8877 any_token: bool = True, 8878 tokens: t.Collection[TokenType] | None = None, 8879 ) -> exp.Expr | None: 8880 expression = self._parse_identifier() 8881 if not expression and ( 8882 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 8883 ): 8884 quoted = self._prev.token_type == TokenType.STRING 8885 expression = self._identifier_expression(quoted=quoted) 8886 8887 return expression 8888 8889 def _parse_string(self) -> exp.Expr | None: 8890 if self._match_set(self.STRING_PARSERS): 8891 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 8892 return self._parse_placeholder() 8893 8894 def _parse_string_as_identifier(self) -> exp.Identifier | None: 8895 if not self._match(TokenType.STRING): 8896 return None 8897 output = exp.to_identifier(self._prev.text, quoted=True) 8898 output.update_positions(self._prev) 8899 return output 8900 8901 def _parse_number(self) -> exp.Expr | None: 8902 if self._match_set(self.NUMERIC_PARSERS): 8903 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 8904 return self._parse_placeholder() 8905 8906 def _parse_identifier(self) -> exp.Expr | None: 8907 if self._match(TokenType.IDENTIFIER): 8908 return self._identifier_expression(quoted=True) 8909 return self._parse_placeholder() 8910 8911 def _parse_var( 8912 self, 8913 any_token: bool = False, 8914 tokens: t.Collection[TokenType] | None = None, 8915 upper: bool = False, 8916 ) -> exp.Expr | None: 8917 if ( 8918 (any_token and self._advance_any()) 8919 or self._match(TokenType.VAR) 8920 or (self._match_set(tokens) if tokens else False) 8921 ): 8922 return self.expression( 8923 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 8924 ) 8925 return self._parse_placeholder() 8926 8927 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 8928 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 8929 self._advance() 8930 return self._prev 8931 return None 8932 8933 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 8934 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 8935 8936 def _parse_primary_or_var(self) -> exp.Expr | None: 8937 return self._parse_primary() or self._parse_var(any_token=True) 8938 8939 def _parse_null(self) -> exp.Expr | None: 8940 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 8941 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 8942 return self._parse_placeholder() 8943 8944 def _parse_boolean(self) -> exp.Expr | None: 8945 if self._match(TokenType.TRUE): 8946 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 8947 if self._match(TokenType.FALSE): 8948 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 8949 return self._parse_placeholder() 8950 8951 def _parse_star(self) -> exp.Expr | None: 8952 if self._match(TokenType.STAR): 8953 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 8954 return self._parse_placeholder() 8955 8956 def _parse_parameter(self) -> exp.Parameter: 8957 this = self._parse_identifier() or self._parse_primary_or_var() 8958 return self.expression(exp.Parameter(this=this)) 8959 8960 def _parse_placeholder(self) -> exp.Expr | None: 8961 if self._match_set(self.PLACEHOLDER_PARSERS): 8962 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 8963 if placeholder: 8964 return placeholder 8965 self._advance(-1) 8966 return None 8967 8968 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 8969 if not self._match_texts(keywords): 8970 return None 8971 if self._match(TokenType.L_PAREN, advance=False): 8972 return self._parse_wrapped_csv(self._parse_expression) 8973 8974 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 8975 return [expression] if expression else None 8976 8977 def _parse_csv( 8978 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 8979 ) -> list[T]: 8980 parse_result = parse_method() 8981 items = [parse_result] if parse_result is not None else [] 8982 8983 while self._match(sep): 8984 if isinstance(parse_result, exp.Expr): 8985 self._add_comments(parse_result) 8986 parse_result = parse_method() 8987 if parse_result is not None: 8988 items.append(parse_result) 8989 8990 return items 8991 8992 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 8993 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 8994 8995 def _parse_wrapped_csv( 8996 self, 8997 parse_method: t.Callable[[], T | None], 8998 sep: TokenType = TokenType.COMMA, 8999 optional: bool = False, 9000 ) -> list[T]: 9001 return self._parse_wrapped( 9002 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 9003 ) 9004 9005 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 9006 wrapped = self._match(TokenType.L_PAREN) 9007 if not wrapped and not optional: 9008 self.raise_error("Expecting (") 9009 parse_result = parse_method() 9010 if wrapped: 9011 self._match_r_paren() 9012 return parse_result 9013 9014 def _parse_expressions(self) -> list[exp.Expr]: 9015 return self._parse_csv(self._parse_expression) 9016 9017 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 9018 return ( 9019 self._parse_set_operations( 9020 self._parse_alias(self._parse_assignment(), explicit=True) 9021 if alias 9022 else self._parse_assignment() 9023 ) 9024 or self._parse_select() 9025 ) 9026 9027 def _parse_ddl_select(self) -> exp.Expr | None: 9028 return self._parse_query_modifiers( 9029 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 9030 ) 9031 9032 def _parse_transaction(self) -> exp.Transaction | exp.Command: 9033 this = None 9034 if self._match_texts(self.TRANSACTION_KIND): 9035 this = self._prev.text 9036 9037 self._match_texts(("TRANSACTION", "WORK")) 9038 9039 modes = [] 9040 while True: 9041 mode = [] 9042 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 9043 mode.append(self._prev.text) 9044 9045 if mode: 9046 modes.append(" ".join(mode)) 9047 if not self._match(TokenType.COMMA): 9048 break 9049 9050 return self.expression(exp.Transaction(this=this, modes=modes)) 9051 9052 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 9053 chain = None 9054 savepoint = None 9055 is_rollback = self._prev.token_type == TokenType.ROLLBACK 9056 9057 self._match_texts(("TRANSACTION", "WORK")) 9058 9059 if self._match_text_seq("TO"): 9060 self._match_text_seq("SAVEPOINT") 9061 savepoint = self._parse_id_var() 9062 9063 if self._match(TokenType.AND): 9064 chain = not self._match_text_seq("NO") 9065 self._match_text_seq("CHAIN") 9066 9067 if is_rollback: 9068 return self.expression(exp.Rollback(savepoint=savepoint)) 9069 9070 return self.expression(exp.Commit(chain=chain)) 9071 9072 def _parse_refresh(self) -> exp.Refresh | exp.Command: 9073 if self._match_text_seq("EXTERNAL", "TABLE"): 9074 kind = "EXTERNAL TABLE" 9075 elif self._match(TokenType.TABLE): 9076 kind = "TABLE" 9077 elif self._match_text_seq("MATERIALIZED", "VIEW"): 9078 kind = "MATERIALIZED VIEW" 9079 else: 9080 kind = "" 9081 9082 this = self._parse_string() or self._parse_table() 9083 if not kind and not isinstance(this, exp.Literal): 9084 return self._parse_as_command(self._prev) 9085 9086 return self.expression(exp.Refresh(this=this, kind=kind)) 9087 9088 def _parse_column_def_with_exists(self): 9089 start = self._index 9090 self._match(TokenType.COLUMN) 9091 9092 exists_column = self._parse_exists(not_=True) 9093 expression = self._parse_field_def() 9094 9095 if not isinstance(expression, exp.ColumnDef): 9096 self._retreat(start) 9097 return None 9098 9099 expression.set("exists", exists_column) 9100 9101 return expression 9102 9103 def _parse_add_column(self) -> exp.ColumnDef | None: 9104 if not self._prev.text.upper() == "ADD": 9105 return None 9106 9107 return self._parse_column_def_with_exists() 9108 9109 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9110 drop = self._parse_drop() if self._match(TokenType.DROP) else None 9111 if drop and not isinstance(drop, exp.Command): 9112 drop.set("kind", drop.args.get("kind", "COLUMN")) 9113 return drop 9114 9115 def _parse_alter_drop_action(self) -> exp.Expr | None: 9116 return self._parse_drop_column() 9117 9118 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9119 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9120 return self.expression( 9121 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9122 ) 9123 9124 def _parse_alter_table_add(self) -> list[exp.Expr]: 9125 def _parse_add_alteration() -> exp.Expr | None: 9126 self._match_text_seq("ADD") 9127 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False): 9128 return self.expression( 9129 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9130 ) 9131 9132 column_def = self._parse_add_column() 9133 if isinstance(column_def, exp.ColumnDef): 9134 return column_def 9135 9136 exists = self._parse_exists(not_=True) 9137 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9138 return self.expression( 9139 exp.AddPartition( 9140 exists=exists, 9141 this=self._parse_field(any_token=True), 9142 location=self._match_text_seq("LOCATION", advance=False) 9143 and self._parse_property(), 9144 ) 9145 ) 9146 9147 return None 9148 9149 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9150 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9151 or self._match_text_seq("COLUMNS") 9152 ): 9153 schema = self._parse_schema() 9154 9155 return ( 9156 ensure_list(schema) 9157 if schema 9158 else self._parse_csv(self._parse_column_def_with_exists) 9159 ) 9160 9161 return self._parse_csv(_parse_add_alteration) 9162 9163 def _parse_alter_table_alter(self) -> exp.Expr | None: 9164 if self._match_texts(self.ALTER_ALTER_PARSERS): 9165 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9166 9167 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9168 # keyword after ALTER we default to parsing this statement 9169 self._match(TokenType.COLUMN) 9170 exists = self._parse_exists() 9171 column = self._parse_field(any_token=True) 9172 9173 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9174 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9175 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9176 return self.expression( 9177 exp.AlterColumn( 9178 this=column, default=self._parse_disjunction(), exists=exists or None 9179 ) 9180 ) 9181 if self._match(TokenType.COMMENT): 9182 return self.expression( 9183 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9184 ) 9185 if self._match_text_seq("DROP", "NOT", "NULL"): 9186 return self.expression( 9187 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9188 ) 9189 if self._match_text_seq("SET", "NOT", "NULL"): 9190 return self.expression( 9191 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9192 ) 9193 9194 if self._match_text_seq("SET", "VISIBLE"): 9195 return self.expression( 9196 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9197 ) 9198 if self._match_text_seq("SET", "INVISIBLE"): 9199 return self.expression( 9200 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9201 ) 9202 9203 self._match_text_seq("SET", "DATA") 9204 self._match_text_seq("TYPE") 9205 return self.expression( 9206 exp.AlterColumn( 9207 this=column, 9208 dtype=self._parse_types(), 9209 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9210 using=self._match(TokenType.USING) and self._parse_disjunction(), 9211 exists=exists or None, 9212 ) 9213 ) 9214 9215 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9216 if self._match_texts(("ALL", "EVEN", "AUTO")): 9217 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9218 9219 self._match_text_seq("KEY", "DISTKEY") 9220 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9221 9222 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9223 if compound: 9224 self._match_text_seq("SORTKEY") 9225 9226 if self._match(TokenType.L_PAREN, advance=False): 9227 return self.expression( 9228 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9229 ) 9230 9231 self._match_texts(("AUTO", "NONE")) 9232 return self.expression( 9233 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9234 ) 9235 9236 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9237 index = self._index - 1 9238 9239 partition_exists = self._parse_exists() 9240 if self._match(TokenType.PARTITION, advance=False): 9241 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9242 9243 self._retreat(index) 9244 return self._parse_csv(self._parse_alter_drop_action) 9245 9246 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9247 if self._match(TokenType.COLUMN) or ( 9248 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9249 ): 9250 exists = self._parse_exists() 9251 old_column = self._parse_column() 9252 to = self._match_text_seq("TO") 9253 new_column = self._parse_column() 9254 9255 if old_column is None or not to or new_column is None: 9256 return None 9257 9258 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9259 9260 self._match_text_seq("TO") 9261 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9262 9263 def _parse_alter_table_set(self) -> exp.AlterSet: 9264 alter_set = self.expression(exp.AlterSet()) 9265 9266 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9267 "TABLE", "PROPERTIES" 9268 ): 9269 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9270 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9271 alter_set.set("expressions", [self._parse_assignment()]) 9272 elif self._match_texts(("LOGGED", "UNLOGGED")): 9273 alter_set.set("option", exp.var(self._prev.text.upper())) 9274 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9275 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9276 elif self._match_text_seq("LOCATION"): 9277 alter_set.set("location", self._parse_field()) 9278 elif self._match_text_seq("ACCESS", "METHOD"): 9279 alter_set.set("access_method", self._parse_field()) 9280 elif self._match_text_seq("TABLESPACE"): 9281 alter_set.set("tablespace", self._parse_field()) 9282 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9283 alter_set.set("file_format", [self._parse_field()]) 9284 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9285 alter_set.set("file_format", self._parse_wrapped_options()) 9286 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9287 alter_set.set("copy_options", self._parse_wrapped_options()) 9288 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9289 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9290 else: 9291 if self._match_text_seq("SERDE"): 9292 alter_set.set("serde", self._parse_field()) 9293 9294 properties = self._parse_wrapped(self._parse_properties, optional=True) 9295 alter_set.set("expressions", [properties]) 9296 9297 return alter_set 9298 9299 def _parse_alter_session(self) -> exp.AlterSession: 9300 """Parse ALTER SESSION SET/UNSET statements.""" 9301 if self._match(TokenType.SET): 9302 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9303 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9304 9305 self._match_text_seq("UNSET") 9306 expressions = self._parse_csv( 9307 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9308 ) 9309 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9310 9311 def _parse_alter(self) -> exp.Alter | exp.Command: 9312 start = self._prev 9313 9314 iceberg = self._match_text_seq("ICEBERG") 9315 9316 alter_token = self._match_set(self.ALTERABLES) and self._prev 9317 if not alter_token: 9318 return self._parse_as_command(start) 9319 if iceberg and alter_token.token_type != TokenType.TABLE: 9320 return self._parse_as_command(start) 9321 9322 exists = self._parse_exists() 9323 only = self._match_text_seq("ONLY") 9324 9325 if alter_token.token_type == TokenType.SESSION: 9326 this = None 9327 check = None 9328 cluster = None 9329 else: 9330 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9331 check = self._match_text_seq("WITH", "CHECK") 9332 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9333 9334 if self._next: 9335 self._advance() 9336 9337 parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None 9338 if parser: 9339 actions = ensure_list(parser(self)) 9340 not_valid = self._match_text_seq("NOT", "VALID") 9341 options = self._parse_csv(self._parse_property) 9342 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9343 9344 if not self._curr and actions: 9345 return self.expression( 9346 exp.Alter( 9347 this=this, 9348 kind=alter_token.text.upper(), 9349 exists=exists, 9350 actions=actions, 9351 only=only, 9352 options=options, 9353 cluster=cluster, 9354 not_valid=not_valid, 9355 check=check, 9356 cascade=cascade, 9357 iceberg=iceberg, 9358 ) 9359 ) 9360 9361 return self._parse_as_command(start) 9362 9363 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9364 start = self._prev 9365 # https://duckdb.org/docs/sql/statements/analyze 9366 if not self._curr: 9367 return self.expression(exp.Analyze()) 9368 9369 options = [] 9370 while self._match_texts(self.ANALYZE_STYLES): 9371 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9372 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9373 else: 9374 options.append(self._prev.text.upper()) 9375 9376 tables: exp.Expr | list[exp.Expr] | None = None 9377 inner_expression: exp.Expr | None = None 9378 9379 kind = self._curr.text.upper() if self._curr else None 9380 9381 if self._match(TokenType.TABLE): 9382 tables = self._parse_csv(self._parse_table_parts) 9383 elif self._match(TokenType.INDEX): 9384 tables = self._parse_table_parts() 9385 elif self._match_text_seq("TABLES"): 9386 if self._match_set((TokenType.FROM, TokenType.IN)): 9387 kind = f"{kind} {self._prev.text.upper()}" 9388 tables = self._parse_table(schema=True, is_db_reference=True) 9389 elif self._match_text_seq("DATABASE"): 9390 tables = self._parse_table(schema=True, is_db_reference=True) 9391 elif self._match_text_seq("CLUSTER"): 9392 tables = self._parse_table() 9393 # Try matching inner expr keywords before fallback to parse table. 9394 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9395 kind = None 9396 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9397 else: 9398 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9399 kind = None 9400 tables = self._parse_csv(self._parse_table_parts) 9401 9402 partition = self._try_parse(self._parse_partition) 9403 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9404 return self._parse_as_command(start) 9405 9406 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9407 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9408 "WITH", "ASYNC", "MODE" 9409 ): 9410 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9411 else: 9412 mode = None 9413 9414 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9415 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9416 9417 properties = self._parse_properties() 9418 return self.expression( 9419 exp.Analyze( 9420 kind=kind, 9421 tables=ensure_list(tables), 9422 mode=mode, 9423 partition=partition, 9424 properties=properties, 9425 expression=inner_expression, 9426 options=options, 9427 ) 9428 ) 9429 9430 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9431 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9432 this = None 9433 kind = self._prev.text.upper() 9434 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9435 expressions = [] 9436 9437 if not self._match_text_seq("STATISTICS"): 9438 self.raise_error("Expecting token STATISTICS") 9439 9440 if self._match_text_seq("NOSCAN"): 9441 this = "NOSCAN" 9442 elif self._match(TokenType.FOR): 9443 if self._match_text_seq("ALL", "COLUMNS"): 9444 this = "FOR ALL COLUMNS" 9445 if self._match_text_seq("COLUMNS"): 9446 this = "FOR COLUMNS" 9447 expressions = self._parse_csv(self._parse_column_reference) 9448 elif self._match_text_seq("SAMPLE"): 9449 sample = self._parse_number() 9450 expressions = [ 9451 self.expression( 9452 exp.AnalyzeSample( 9453 sample=sample, 9454 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9455 ) 9456 ) 9457 ] 9458 9459 return self.expression( 9460 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9461 ) 9462 9463 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9464 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9465 kind = None 9466 this = None 9467 expression: exp.Expr | None = None 9468 if self._match_text_seq("REF", "UPDATE"): 9469 kind = "REF" 9470 this = "UPDATE" 9471 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9472 this = "UPDATE SET DANGLING TO NULL" 9473 elif self._match_text_seq("STRUCTURE"): 9474 kind = "STRUCTURE" 9475 if self._match_text_seq("CASCADE", "FAST"): 9476 this = "CASCADE FAST" 9477 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9478 ("ONLINE", "OFFLINE") 9479 ): 9480 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9481 expression = self._parse_into() 9482 9483 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9484 9485 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9486 this = self._prev.text.upper() 9487 if self._match_text_seq("COLUMNS"): 9488 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9489 return None 9490 9491 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9492 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9493 if self._match_text_seq("STATISTICS"): 9494 return self.expression(exp.AnalyzeDelete(kind=kind)) 9495 return None 9496 9497 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9498 if self._match_text_seq("CHAINED", "ROWS"): 9499 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9500 return None 9501 9502 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9503 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9504 this = self._prev.text.upper() 9505 expression: exp.Expr | None = None 9506 expressions = [] 9507 update_options = None 9508 9509 if self._match_text_seq("HISTOGRAM", "ON"): 9510 expressions = self._parse_csv(self._parse_column_reference) 9511 with_expressions = [] 9512 while self._match(TokenType.WITH): 9513 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9514 if self._match_texts(("SYNC", "ASYNC")): 9515 if self._match_text_seq("MODE", advance=False): 9516 with_expressions.append(f"{self._prev.text.upper()} MODE") 9517 self._advance() 9518 else: 9519 buckets = self._parse_number() 9520 if self._match_text_seq("BUCKETS"): 9521 with_expressions.append(f"{buckets} BUCKETS") 9522 if with_expressions: 9523 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9524 9525 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9526 TokenType.UPDATE, advance=False 9527 ): 9528 update_options = self._prev.text.upper() 9529 self._advance() 9530 elif self._match_text_seq("USING", "DATA"): 9531 expression = self.expression(exp.UsingData(this=self._parse_string())) 9532 9533 return self.expression( 9534 exp.AnalyzeHistogram( 9535 this=this, 9536 expressions=expressions, 9537 expression=expression, 9538 update_options=update_options, 9539 ) 9540 ) 9541 9542 def _parse_merge(self) -> exp.Merge: 9543 self._match(TokenType.INTO) 9544 target = self._parse_table() 9545 9546 if target and self._match(TokenType.ALIAS, advance=False): 9547 target.set("alias", self._parse_table_alias()) 9548 9549 self._match(TokenType.USING) 9550 using = self._parse_table() 9551 9552 return self.expression( 9553 exp.Merge( 9554 this=target, 9555 using=using, 9556 on=self._match(TokenType.ON) and self._parse_disjunction(), 9557 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9558 whens=self._parse_when_matched(), 9559 returning=self._parse_returning(), 9560 ) 9561 ) 9562 9563 def _parse_when_matched(self) -> exp.Whens: 9564 whens = [] 9565 9566 while self._match(TokenType.WHEN): 9567 matched = not self._match(TokenType.NOT) 9568 self._match_text_seq("MATCHED") 9569 source = ( 9570 False 9571 if self._match_text_seq("BY", "TARGET") 9572 else self._match_text_seq("BY", "SOURCE") 9573 ) 9574 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9575 9576 self._match(TokenType.THEN) 9577 9578 if self._match(TokenType.INSERT): 9579 this = self._parse_star() 9580 if this: 9581 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9582 else: 9583 then = self.expression( 9584 exp.Insert( 9585 this=exp.var("ROW") 9586 if self._match_text_seq("ROW") 9587 else self._parse_value(values=False), 9588 expression=self._match_text_seq("VALUES") and self._parse_value(), 9589 where=self._parse_where(), 9590 ) 9591 ) 9592 elif self._match(TokenType.UPDATE): 9593 expressions = self._parse_star() 9594 if expressions: 9595 then = self.expression(exp.Update(expressions=expressions)) 9596 else: 9597 then = self.expression( 9598 exp.Update( 9599 expressions=self._match(TokenType.SET) 9600 and self._parse_csv(self._parse_update_assignment), 9601 where=self._parse_where(), 9602 ) 9603 ) 9604 elif self._match(TokenType.DELETE): 9605 then = self.expression(exp.Var(this=self._prev.text)) 9606 else: 9607 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9608 9609 whens.append( 9610 self.expression( 9611 exp.When(matched=matched, source=source, condition=condition, then=then) 9612 ) 9613 ) 9614 return self.expression(exp.Whens(expressions=whens)) 9615 9616 def _parse_show(self) -> exp.Expr | None: 9617 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9618 if parser: 9619 return parser(self) 9620 return self._parse_as_command(self._prev) 9621 9622 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9623 index = self._index 9624 9625 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9626 return self._parse_set_transaction(global_=kind == "GLOBAL") 9627 9628 left = self._parse_primary() or self._parse_column() 9629 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 9630 9631 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 9632 self._retreat(index) 9633 return None 9634 9635 right = self._parse_statement() or self._parse_id_var() 9636 if isinstance(right, (exp.Column, exp.Identifier)): 9637 right = exp.var(right.name) 9638 9639 this = self.expression(exp.EQ(this=left, expression=right)) 9640 return self.expression(exp.SetItem(this=this, kind=kind)) 9641 9642 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 9643 self._match_text_seq("TRANSACTION") 9644 characteristics = self._parse_csv( 9645 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 9646 ) 9647 return self.expression( 9648 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 9649 ) 9650 9651 def _parse_set_item(self) -> exp.Expr | None: 9652 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 9653 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 9654 9655 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 9656 index = self._index 9657 set_ = self.expression( 9658 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 9659 ) 9660 9661 if self._curr: 9662 self._retreat(index) 9663 return self._parse_as_command(self._prev) 9664 9665 return set_ 9666 9667 def _parse_var_from_options( 9668 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 9669 ) -> exp.Var | None: 9670 start = self._curr 9671 if not start: 9672 return None 9673 9674 option = start.text.upper() 9675 continuations = ( 9676 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 9677 ) 9678 9679 index = self._index 9680 self._advance() 9681 for keywords in continuations or []: 9682 if isinstance(keywords, str): 9683 keywords = (keywords,) 9684 9685 if self._match_text_seq(*keywords): 9686 option = f"{option} {' '.join(keywords)}" 9687 break 9688 else: 9689 if continuations or continuations is None: 9690 if raise_unmatched: 9691 self.raise_error(f"Unknown option {option}") 9692 9693 self._retreat(index) 9694 return None 9695 9696 return exp.var(option) 9697 9698 def _parse_as_command(self, start: Token) -> exp.Command: 9699 while self._curr: 9700 self._advance() 9701 text = self._find_sql(start, self._prev) 9702 size = len(start.text) 9703 self._warn_unsupported() 9704 return exp.Command(this=text[:size], expression=text[size:]) 9705 9706 def _parse_dict_property(self, this: str) -> exp.DictProperty: 9707 settings = [] 9708 9709 self._match_l_paren() 9710 kind = self._parse_id_var() 9711 9712 if self._match(TokenType.L_PAREN): 9713 while True: 9714 key = self._parse_id_var() 9715 value = self._parse_function() or self._parse_primary_or_var() 9716 if not key and value is None: 9717 break 9718 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 9719 self._match(TokenType.R_PAREN) 9720 9721 self._match_r_paren() 9722 9723 return self.expression( 9724 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 9725 ) 9726 9727 def _parse_dict_range(self, this: str) -> exp.DictRange: 9728 self._match_l_paren() 9729 has_min = self._match_text_seq("MIN") 9730 if has_min: 9731 min = self._parse_var() or self._parse_primary() 9732 self._match_text_seq("MAX") 9733 max = self._parse_var() or self._parse_primary() 9734 else: 9735 max = self._parse_var() or self._parse_primary() 9736 min = exp.Literal.number(0) 9737 self._match_r_paren() 9738 return self.expression(exp.DictRange(this=this, min=min, max=max)) 9739 9740 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 9741 index = self._index 9742 expression = self._parse_column() 9743 position = self._match(TokenType.COMMA) and self._parse_column() 9744 9745 if not self._match(TokenType.IN): 9746 self._retreat(index - 1) 9747 return None 9748 iterator = self._parse_column() 9749 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 9750 return self.expression( 9751 exp.Comprehension( 9752 this=this, 9753 expression=expression, 9754 position=position, 9755 iterator=iterator, 9756 condition=condition, 9757 ) 9758 ) 9759 9760 def _parse_heredoc(self) -> exp.Heredoc | None: 9761 if self._match(TokenType.HEREDOC_STRING): 9762 return self.expression(exp.Heredoc(this=self._prev.text)) 9763 9764 if not self._match_text_seq("$"): 9765 return None 9766 9767 tags = ["$"] 9768 tag_text = None 9769 9770 if self._is_connected(): 9771 self._advance() 9772 tags.append(self._prev.text.upper()) 9773 else: 9774 self.raise_error("No closing $ found") 9775 9776 if tags[-1] != "$": 9777 if self._is_connected() and self._match_text_seq("$"): 9778 tag_text = tags[-1] 9779 tags.append("$") 9780 else: 9781 self.raise_error("No closing $ found") 9782 9783 heredoc_start = self._curr 9784 9785 while self._curr: 9786 if self._match_text_seq(*tags, advance=False): 9787 this = self._find_sql(heredoc_start, self._prev) 9788 self._advance(len(tags)) 9789 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 9790 9791 self._advance() 9792 9793 self.raise_error(f"No closing {''.join(tags)} found") 9794 return None 9795 9796 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 9797 if not self._curr: 9798 return None 9799 9800 index = self._index 9801 this = [] 9802 while True: 9803 # The current token might be multiple words 9804 curr = self._curr.text.upper() 9805 key = curr.split(" ") 9806 this.append(curr) 9807 9808 self._advance() 9809 result, trie = in_trie(trie, key) 9810 if result == TrieResult.FAILED: 9811 break 9812 9813 if result == TrieResult.EXISTS: 9814 subparser = parsers[" ".join(this)] 9815 return subparser 9816 9817 self._retreat(index) 9818 return None 9819 9820 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 9821 if not self._match(TokenType.L_PAREN, expression=expression): 9822 self.raise_error("Expecting (") 9823 9824 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 9825 if not self._match(TokenType.R_PAREN, expression=expression): 9826 self.raise_error("Expecting )") 9827 9828 def _replace_lambda( 9829 self, node: exp.Expr | None, expressions: list[exp.Expr] 9830 ) -> exp.Expr | None: 9831 if not node: 9832 return node 9833 9834 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 9835 9836 for column in node.find_all(exp.Column): 9837 typ = lambda_types.get(column.parts[0].name) 9838 if typ is not None: 9839 dot_or_id = column.to_dot() if column.table else column.this 9840 9841 if typ: 9842 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ)) 9843 9844 parent = column.parent 9845 9846 while isinstance(parent, exp.Dot): 9847 if not isinstance(parent.parent, exp.Dot): 9848 parent.replace(dot_or_id) 9849 break 9850 parent = parent.parent 9851 else: 9852 if column is node: 9853 node = dot_or_id 9854 else: 9855 column.replace(dot_or_id) 9856 return node 9857 9858 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 9859 start = self._prev 9860 9861 # Not to be confused with TRUNCATE(number, decimals) function call 9862 if self._match(TokenType.L_PAREN): 9863 self._retreat(self._index - 2) 9864 return self._parse_function() 9865 9866 # Clickhouse supports TRUNCATE DATABASE as well 9867 is_database = self._match(TokenType.DATABASE) 9868 9869 self._match(TokenType.TABLE) 9870 9871 exists = self._parse_exists(not_=False) 9872 9873 expressions = self._parse_csv( 9874 lambda: self._parse_table(schema=True, is_db_reference=is_database) 9875 ) 9876 9877 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9878 9879 if self._match_text_seq("RESTART", "IDENTITY"): 9880 identity = "RESTART" 9881 elif self._match_text_seq("CONTINUE", "IDENTITY"): 9882 identity = "CONTINUE" 9883 else: 9884 identity = None 9885 9886 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 9887 option = self._prev.text 9888 else: 9889 option = None 9890 9891 partition = self._parse_partition() 9892 9893 # Fallback case 9894 if self._curr: 9895 return self._parse_as_command(start) 9896 9897 return self.expression( 9898 exp.TruncateTable( 9899 expressions=expressions, 9900 is_database=is_database, 9901 exists=exists, 9902 cluster=cluster, 9903 identity=identity, 9904 option=option, 9905 partition=partition, 9906 ) 9907 ) 9908 9909 def _parse_indexed_column(self) -> exp.Expr | None: 9910 return self._parse_ordered(self._parse_opclass) 9911 9912 def _parse_with_operator(self) -> exp.Expr | None: 9913 this = self._parse_indexed_column() 9914 9915 if not self._match(TokenType.WITH): 9916 return this 9917 9918 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 9919 9920 return self.expression(exp.WithOperator(this=this, op=op)) 9921 9922 def _parse_wrapped_options(self) -> list[exp.Expr]: 9923 self._match(TokenType.EQ) 9924 self._match(TokenType.L_PAREN) 9925 9926 opts: list[exp.Expr] = [] 9927 option: exp.Expr | list[exp.Expr] | None 9928 while self._curr and not self._match(TokenType.R_PAREN): 9929 if self._match_text_seq("FORMAT_NAME", "="): 9930 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 9931 option = self._parse_format_name() 9932 else: 9933 option = self._parse_property() 9934 9935 if option is None: 9936 self.raise_error("Unable to parse option") 9937 break 9938 9939 opts.extend(ensure_list(option)) 9940 9941 return opts 9942 9943 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 9944 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 9945 9946 options = [] 9947 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 9948 option = self._parse_var(any_token=True) 9949 prev = self._prev.text.upper() 9950 9951 # Different dialects might separate options and values by white space, "=" and "AS" 9952 self._match(TokenType.EQ) 9953 self._match(TokenType.ALIAS) 9954 9955 param = self.expression(exp.CopyParameter(this=option)) 9956 9957 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 9958 TokenType.L_PAREN, advance=False 9959 ): 9960 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 9961 param.set("expressions", self._parse_wrapped_options()) 9962 elif prev == "FILE_FORMAT": 9963 # T-SQL's external file format case 9964 param.set("expression", self._parse_field()) 9965 elif ( 9966 prev == "FORMAT" 9967 and self._prev.token_type == TokenType.ALIAS 9968 and self._match_texts(("AVRO", "JSON")) 9969 ): 9970 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 9971 param.set("expression", self._parse_field()) 9972 else: 9973 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 9974 9975 options.append(param) 9976 9977 if sep: 9978 self._match(sep) 9979 9980 return options 9981 9982 def _parse_credentials(self) -> exp.Credentials | None: 9983 expr = self.expression(exp.Credentials()) 9984 9985 if self._match_text_seq("STORAGE_INTEGRATION", "="): 9986 expr.set("storage", self._parse_field()) 9987 if self._match_text_seq("CREDENTIALS"): 9988 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 9989 creds = ( 9990 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 9991 ) 9992 expr.set("credentials", creds) 9993 if self._match_text_seq("ENCRYPTION"): 9994 expr.set("encryption", self._parse_wrapped_options()) 9995 if self._match_text_seq("IAM_ROLE"): 9996 expr.set( 9997 "iam_role", 9998 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 9999 ) 10000 if self._match_text_seq("REGION"): 10001 expr.set("region", self._parse_field()) 10002 10003 return expr 10004 10005 def _parse_file_location(self) -> exp.Expr | None: 10006 return self._parse_field() 10007 10008 def _parse_copy(self) -> exp.Copy | exp.Command: 10009 start = self._prev 10010 10011 self._match(TokenType.INTO) 10012 10013 this = ( 10014 self._parse_select(nested=True, parse_subquery_alias=False) 10015 if self._match(TokenType.L_PAREN, advance=False) 10016 else self._parse_table(schema=True) 10017 ) 10018 10019 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 10020 10021 files = self._parse_csv(self._parse_file_location) 10022 if self._match(TokenType.EQ, advance=False): 10023 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 10024 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 10025 # list via `_parse_wrapped(..)` below. 10026 self._advance(-1) 10027 files = [] 10028 10029 credentials = self._parse_credentials() 10030 10031 self._match_text_seq("WITH") 10032 10033 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 10034 10035 # Fallback case 10036 if self._curr: 10037 return self._parse_as_command(start) 10038 10039 return self.expression( 10040 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 10041 ) 10042 10043 def _parse_normalize(self) -> exp.Normalize: 10044 return self.expression( 10045 exp.Normalize( 10046 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 10047 ) 10048 ) 10049 10050 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 10051 args = self._parse_csv(lambda: self._parse_lambda()) 10052 10053 this = seq_get(args, 0) 10054 decimals = seq_get(args, 1) 10055 10056 return expr_type( 10057 this=this, 10058 decimals=decimals, 10059 to=self._parse_var() if self._match_text_seq("TO") else None, 10060 ) 10061 10062 def _parse_star_ops(self) -> exp.Expr | None: 10063 star_token = self._prev 10064 10065 if self._match_text_seq("COLUMNS", "(", advance=False): 10066 this = self._parse_function() 10067 if isinstance(this, exp.Columns): 10068 this.set("unpack", True) 10069 return this 10070 10071 index = self._index 10072 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 10073 if not ilike: 10074 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 10075 self._retreat(index) 10076 10077 return self.expression( 10078 exp.Star( 10079 ilike=ilike, 10080 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 10081 replace=self._parse_star_op("REPLACE"), 10082 rename=self._parse_star_op("RENAME"), 10083 ) 10084 ).update_positions(star_token) 10085 10086 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 10087 privilege_parts = [] 10088 10089 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 10090 # (end of privilege list) or L_PAREN (start of column list) are met 10091 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 10092 privilege_parts.append(self._curr.text.upper()) 10093 self._advance() 10094 10095 if not privilege_parts: 10096 self.raise_error("Expected privilege") 10097 return None 10098 10099 this = exp.var(" ".join(privilege_parts)) 10100 expressions = ( 10101 self._parse_wrapped_csv(self._parse_column) 10102 if self._match(TokenType.L_PAREN, advance=False) 10103 else None 10104 ) 10105 10106 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10107 10108 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10109 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10110 principal = self._parse_id_var() 10111 10112 if not principal: 10113 return None 10114 10115 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10116 10117 def _parse_grant_revoke_common( 10118 self, 10119 ) -> tuple[list | None, str | None, exp.Expr | None]: 10120 privileges = self._parse_csv(self._parse_grant_privilege) 10121 10122 self._match(TokenType.ON) 10123 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10124 10125 # Attempt to parse the securable e.g. MySQL allows names 10126 # such as "foo.*", "*.*" which are not easily parseable yet 10127 securable = self._try_parse(self._parse_table_parts) 10128 10129 return privileges, kind, securable 10130 10131 def _parse_grant(self) -> exp.Grant | exp.Command: 10132 start = self._prev 10133 10134 privileges, kind, securable = self._parse_grant_revoke_common() 10135 10136 if not securable or not self._match_text_seq("TO"): 10137 return self._parse_as_command(start) 10138 10139 principals = self._parse_csv(self._parse_grant_principal) 10140 10141 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10142 10143 if self._curr: 10144 return self._parse_as_command(start) 10145 10146 return self.expression( 10147 exp.Grant( 10148 privileges=privileges, 10149 kind=kind, 10150 securable=securable, 10151 principals=principals, 10152 grant_option=grant_option, 10153 ) 10154 ) 10155 10156 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10157 start = self._prev 10158 10159 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10160 10161 privileges, kind, securable = self._parse_grant_revoke_common() 10162 10163 if not securable or not self._match_text_seq("FROM"): 10164 return self._parse_as_command(start) 10165 10166 principals = self._parse_csv(self._parse_grant_principal) 10167 10168 cascade = None 10169 if self._match_texts(("CASCADE", "RESTRICT")): 10170 cascade = self._prev.text.upper() 10171 10172 if self._curr: 10173 return self._parse_as_command(start) 10174 10175 return self.expression( 10176 exp.Revoke( 10177 privileges=privileges, 10178 kind=kind, 10179 securable=securable, 10180 principals=principals, 10181 grant_option=grant_option, 10182 cascade=cascade, 10183 ) 10184 ) 10185 10186 def _parse_overlay(self) -> exp.Overlay: 10187 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10188 return ( 10189 self._parse_bitwise() 10190 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10191 else None 10192 ) 10193 10194 return self.expression( 10195 exp.Overlay( 10196 this=self._parse_bitwise(), 10197 expression=_parse_overlay_arg("PLACING"), 10198 from_=_parse_overlay_arg("FROM"), 10199 for_=_parse_overlay_arg("FOR"), 10200 ) 10201 ) 10202 10203 def _parse_format_name(self) -> exp.Property: 10204 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10205 # for FILE_FORMAT = <format_name> 10206 return self.expression( 10207 exp.Property( 10208 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10209 ) 10210 ) 10211 10212 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10213 is_distinct = self._match(TokenType.DISTINCT) 10214 if not is_distinct: 10215 self._match(TokenType.ALL) 10216 10217 args = [self._parse_lambda()] 10218 if self._match(TokenType.COMMA): 10219 args.extend(self._parse_function_args()) 10220 10221 target = seq_get(args, distinct_index) 10222 if is_distinct and target: 10223 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10224 10225 return func.from_arg_list(args) 10226 10227 def _identifier_expression( 10228 self, token: Token | None = None, quoted: bool | None = None 10229 ) -> exp.Identifier: 10230 token = token or self._prev 10231 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10232 10233 def _build_pipe_cte( 10234 self, 10235 query: exp.Query, 10236 expressions: list[exp.Expr], 10237 alias_cte: exp.TableAlias | None = None, 10238 ) -> exp.Select: 10239 new_cte: str | exp.TableAlias | None 10240 if alias_cte: 10241 new_cte = alias_cte 10242 else: 10243 self._pipe_cte_counter += 1 10244 new_cte = f"__tmp{self._pipe_cte_counter}" 10245 10246 with_ = query.args.get("with_") 10247 ctes = with_.pop() if with_ else None 10248 10249 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10250 if ctes: 10251 new_select.set("with_", ctes) 10252 10253 return new_select.with_(new_cte, as_=query, copy=False) 10254 10255 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10256 select = self._parse_select(consume_pipe=False) 10257 if not select: 10258 return query 10259 10260 return self._build_pipe_cte( 10261 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10262 ) 10263 10264 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10265 limit = self._parse_limit() 10266 offset = self._parse_offset() 10267 if limit: 10268 curr_limit = query.args.get("limit", limit) 10269 if curr_limit.expression.to_py() >= limit.expression.to_py(): 10270 query.limit(limit, copy=False) 10271 if offset: 10272 curr_offset = query.args.get("offset") 10273 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10274 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10275 10276 return query 10277 10278 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10279 this = self._parse_disjunction() 10280 if self._match_text_seq("GROUP", "AND", advance=False): 10281 return this 10282 10283 this = self._parse_alias(this) 10284 10285 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10286 return self._parse_ordered(lambda: this) 10287 10288 return this 10289 10290 def _parse_pipe_syntax_aggregate_group_order_by( 10291 self, query: exp.Select, group_by_exists: bool = True 10292 ) -> exp.Select: 10293 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10294 aggregates_or_groups, orders = [], [] 10295 for element in expr: 10296 if isinstance(element, exp.Ordered): 10297 this = element.this 10298 if isinstance(this, exp.Alias): 10299 element.set("this", this.args["alias"]) 10300 orders.append(element) 10301 else: 10302 this = element 10303 aggregates_or_groups.append(this) 10304 10305 if group_by_exists: 10306 query.select( 10307 *aggregates_or_groups, *query.expressions, append=False, copy=False 10308 ).group_by( 10309 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10310 copy=False, 10311 ) 10312 else: 10313 query.select(*aggregates_or_groups, append=False, copy=False) 10314 10315 if orders: 10316 return query.order_by(*orders, append=False, copy=False) 10317 10318 return query 10319 10320 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10321 self._match_text_seq("AGGREGATE") 10322 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10323 10324 if self._match(TokenType.GROUP_BY) or ( 10325 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10326 ): 10327 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10328 10329 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10330 10331 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10332 first_setop = self.parse_set_operation(this=query) 10333 if not first_setop: 10334 return None 10335 10336 def _parse_and_unwrap_query() -> exp.Expr | None: 10337 expr = self._parse_paren() 10338 return expr.assert_is(exp.Subquery).unnest() if expr else None 10339 10340 first_setop.this.pop() 10341 10342 setops = [ 10343 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10344 *self._parse_csv(_parse_and_unwrap_query), 10345 ] 10346 10347 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10348 with_ = query.args.get("with_") 10349 ctes = with_.pop() if with_ else None 10350 10351 if isinstance(first_setop, exp.Union): 10352 query = query.union(*setops, copy=False, **first_setop.args) 10353 elif isinstance(first_setop, exp.Except): 10354 query = query.except_(*setops, copy=False, **first_setop.args) 10355 else: 10356 query = query.intersect(*setops, copy=False, **first_setop.args) 10357 10358 query.set("with_", ctes) 10359 10360 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10361 10362 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10363 join = self._parse_join() 10364 if not join: 10365 return None 10366 10367 if isinstance(query, exp.Select): 10368 return query.join(join, copy=False) 10369 10370 return query 10371 10372 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10373 pivots = self._parse_pivots() 10374 if not pivots: 10375 return query 10376 10377 from_ = query.args.get("from_") 10378 if from_: 10379 from_.this.set("pivots", pivots) 10380 10381 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10382 10383 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10384 self._match_text_seq("EXTEND") 10385 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10386 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10387 10388 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10389 sample = self._parse_table_sample() 10390 10391 with_ = query.args.get("with_") 10392 if with_: 10393 with_.expressions[-1].this.set("sample", sample) 10394 else: 10395 query.set("sample", sample) 10396 10397 return query 10398 10399 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10400 if isinstance(query, exp.Subquery): 10401 query = exp.select("*").from_(query, copy=False) 10402 10403 if not query.args.get("from_"): 10404 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10405 10406 while self._match(TokenType.PIPE_GT): 10407 start_index = self._index 10408 start_text = self._curr.text.upper() 10409 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10410 if not parser: 10411 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10412 # keywords, making it tricky to disambiguate them without lookahead. The approach 10413 # here is to try and parse a set operation and if that fails, then try to parse a 10414 # join operator. If that fails as well, then the operator is not supported. 10415 parsed_query = self._parse_pipe_syntax_set_operator(query) 10416 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10417 if not parsed_query: 10418 self._retreat(start_index) 10419 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10420 break 10421 query = parsed_query 10422 else: 10423 query = parser(self, query) 10424 10425 return query 10426 10427 def _parse_declareitem(self) -> exp.DeclareItem | None: 10428 self._match_texts(("VAR", "VARIABLE")) 10429 10430 vars = self._parse_csv(self._parse_id_var) 10431 if not vars: 10432 return None 10433 10434 self._match(TokenType.ALIAS) 10435 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10436 default = ( 10437 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10438 ) and self._parse_bitwise() 10439 10440 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10441 10442 def _parse_declare(self) -> exp.Declare | exp.Command: 10443 start = self._prev 10444 replace = self._match_text_seq("OR", "REPLACE") 10445 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10446 10447 if not expressions or self._curr: 10448 return self._parse_as_command(start) 10449 10450 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10451 10452 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10453 exp_class = exp.Cast if strict else exp.TryCast 10454 10455 if exp_class == exp.TryCast: 10456 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10457 10458 return self.expression(exp_class(**kwargs)) 10459 10460 def _parse_json_value(self) -> exp.JSONValue: 10461 this = self._parse_bitwise() 10462 self._match(TokenType.COMMA) 10463 path = self._parse_bitwise() 10464 10465 returning = self._match(TokenType.RETURNING) and self._parse_type() 10466 10467 return self.expression( 10468 exp.JSONValue( 10469 this=this, 10470 path=self.dialect.to_json_path(path), 10471 returning=returning, 10472 on_condition=self._parse_on_condition(), 10473 ) 10474 ) 10475 10476 def _parse_group_concat(self) -> exp.Expr | None: 10477 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10478 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10479 concat_exprs = [ 10480 self.expression( 10481 exp.Concat( 10482 expressions=node.expressions, 10483 safe=True, 10484 coalesce=self.dialect.CONCAT_COALESCE, 10485 ) 10486 ) 10487 ] 10488 node.set("expressions", concat_exprs) 10489 return node 10490 if len(exprs) == 1: 10491 return exprs[0] 10492 return self.expression( 10493 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10494 ) 10495 10496 args = self._parse_csv(self._parse_lambda) 10497 10498 if args: 10499 order = args[-1] if isinstance(args[-1], exp.Order) else None 10500 10501 if order: 10502 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10503 # remove 'expr' from exp.Order and add it back to args 10504 args[-1] = order.this 10505 order.set("this", concat_exprs(order.this, args)) 10506 10507 this = order or concat_exprs(args[0], args) 10508 else: 10509 this = None 10510 10511 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10512 10513 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10514 10515 def _parse_initcap(self) -> exp.Initcap: 10516 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10517 10518 # attach dialect's default delimiters 10519 if expr.args.get("expression") is None: 10520 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10521 10522 return expr 10523 10524 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10525 if not self._match(TokenType.L_PAREN): 10526 self._retreat(self._index - 1) 10527 return None 10528 10529 op = "" 10530 while self._curr and not self._match(TokenType.R_PAREN): 10531 op += self._curr.text 10532 self._advance() 10533 10534 comments = self._prev_comments 10535 return self.expression( 10536 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10537 comments=comments, 10538 )
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.
1957 def __init__( 1958 self, 1959 error_level: ErrorLevel | None = None, 1960 error_message_context: int = 100, 1961 max_errors: int = 3, 1962 max_nodes: int = -1, 1963 dialect: DialectType = None, 1964 ): 1965 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 1966 self.error_message_context: int = error_message_context 1967 self.max_errors: int = max_errors 1968 self.max_nodes: int = max_nodes 1969 self.dialect: t.Any = _resolve_dialect(dialect) 1970 self.sql: str = "" 1971 self.errors: list[ParseError] = [] 1972 self._tokens: list[Token] = [] 1973 self._tokens_size: i64 = 0 1974 self._index: i64 = 0 1975 self._curr: Token = SENTINEL_NONE 1976 self._next: Token = SENTINEL_NONE 1977 self._prev: Token = SENTINEL_NONE 1978 self._prev_comments: list[str] = [] 1979 self._pipe_cte_counter: int = 0 1980 self._chunks: list[list[Token]] = [] 1981 self._chunk_index: i64 = 0 1982 self._node_count: int = 0
1984 def reset(self) -> None: 1985 self.sql = "" 1986 self.errors = [] 1987 self._tokens = [] 1988 self._tokens_size = 0 1989 self._index = 0 1990 self._curr = SENTINEL_NONE 1991 self._next = SENTINEL_NONE 1992 self._prev = SENTINEL_NONE 1993 self._prev_comments = [] 1994 self._pipe_cte_counter = 0 1995 self._chunks = [] 1996 self._chunk_index = 0 1997 self._node_count = 0
2090 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2091 token = token or self._curr or self._prev or Token.string("") 2092 formatted_sql, start_context, highlight, end_context = highlight_sql( 2093 sql=self.sql, 2094 positions=[(token.start, token.end)], 2095 context_length=self.error_message_context, 2096 ) 2097 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2098 2099 error = ParseError.new( 2100 formatted_message, 2101 description=message, 2102 line=token.line, 2103 col=token.col, 2104 start_context=start_context, 2105 highlight=highlight, 2106 end_context=end_context, 2107 ) 2108 2109 if self.error_level == ErrorLevel.IMMEDIATE: 2110 raise error 2111 2112 self.errors.append(error)
2114 def validate_expression(self, expression: E, args: list | None = None) -> E: 2115 if self.max_nodes > -1: 2116 self._node_count += 1 2117 if self._node_count > self.max_nodes: 2118 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2119 if self.error_level != ErrorLevel.IGNORE: 2120 for error_message in expression.error_messages(args): 2121 self.raise_error(error_message) 2122 return expression
2141 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2142 """ 2143 Parses a list of tokens and returns a list of syntax trees, one tree 2144 per parsed SQL statement. 2145 2146 Args: 2147 raw_tokens: The list of tokens. 2148 sql: The original SQL string. 2149 2150 Returns: 2151 The list of the produced syntax trees. 2152 """ 2153 return self._parse( 2154 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2155 )
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.
2157 def parse_into( 2158 self, 2159 expression_types: exp.IntoType, 2160 raw_tokens: list[Token], 2161 sql: str | None = None, 2162 ) -> list[exp.Expr | None]: 2163 """ 2164 Parses a list of tokens into a given Expr type. If a collection of Expr 2165 types is given instead, this method will try to parse the token list into each one 2166 of them, stopping at the first for which the parsing succeeds. 2167 2168 Args: 2169 expression_types: The expression type(s) to try and parse the token list into. 2170 raw_tokens: The list of tokens. 2171 sql: The original SQL string, used to produce helpful debug messages. 2172 2173 Returns: 2174 The target Expr. 2175 """ 2176 errors = [] 2177 for expression_type in ensure_list(expression_types): 2178 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2179 if not parser: 2180 raise TypeError(f"No parser registered for {expression_type}") 2181 2182 try: 2183 return self._parse(parser, raw_tokens, sql) 2184 except ParseError as e: 2185 e.errors[0]["into_expression"] = expression_type 2186 errors.append(e) 2187 2188 raise ParseError( 2189 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2190 errors=merge_errors(errors), 2191 ) 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.
2193 def check_errors(self) -> None: 2194 """Logs or raises any found errors, depending on the chosen error level setting.""" 2195 if self.error_level == ErrorLevel.WARN: 2196 for error in self.errors: 2197 logger.error(str(error)) 2198 elif self.error_level == ErrorLevel.RAISE and self.errors: 2199 raise ParseError( 2200 concat_messages(self.errors, self.max_errors), 2201 errors=merge_errors(self.errors), 2202 )
Logs or raises any found errors, depending on the chosen error level setting.
2204 def expression( 2205 self, 2206 instance: E, 2207 token: Token | None = None, 2208 comments: list[str] | None = None, 2209 ) -> E: 2210 if token: 2211 instance.update_positions(token) 2212 instance.add_comments(comments) if comments else self._add_comments(instance) 2213 if not instance.is_primitive: 2214 instance = self.validate_expression(instance) 2215 return instance
5947 def parse_set_operation( 5948 self, this: exp.Expr | None, consume_pipe: bool = False 5949 ) -> exp.Expr | None: 5950 start = self._index 5951 _, side_token, kind_token = self._parse_join_parts() 5952 5953 side = side_token.text if side_token else None 5954 kind = kind_token.text if kind_token else None 5955 5956 if not self._match_set(self.SET_OPERATIONS): 5957 self._retreat(start) 5958 return None 5959 5960 token_type = self._prev.token_type 5961 5962 if token_type == TokenType.UNION: 5963 operation: type[exp.SetOperation] = exp.Union 5964 elif token_type == TokenType.EXCEPT: 5965 operation = exp.Except 5966 else: 5967 operation = exp.Intersect 5968 5969 comments = self._prev.comments 5970 5971 if self._match(TokenType.DISTINCT): 5972 distinct: bool | None = True 5973 elif self._match(TokenType.ALL): 5974 distinct = False 5975 else: 5976 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 5977 if distinct is None: 5978 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 5979 5980 by_name = ( 5981 self._match_text_seq("BY", "NAME") 5982 or self._match_text_seq("STRICT", "CORRESPONDING") 5983 or None 5984 ) 5985 if self._match_text_seq("CORRESPONDING"): 5986 by_name = True 5987 if not side and not kind: 5988 kind = "INNER" 5989 5990 on_column_list = None 5991 if by_name and self._match_texts(("ON", "BY")): 5992 on_column_list = self._parse_wrapped_csv(self._parse_column) 5993 5994 expression = self._parse_select( 5995 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 5996 ) 5997 5998 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 5999 # in _parse_cte and so that alias pushdown can reach into set operation branches 6000 if isinstance(this, exp.Values): 6001 this = self._values_to_select(this) 6002 if isinstance(expression, exp.Values): 6003 expression = self._values_to_select(expression) 6004 6005 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 6006 subquery = this.this 6007 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 6008 subquery.add_comments(this.pop_comments()) 6009 this = subquery 6010 6011 return self.expression( 6012 operation( 6013 this=this, 6014 distinct=distinct, 6015 by_name=by_name, 6016 expression=expression, 6017 side=side, 6018 kind=kind, 6019 on=on_column_list, 6020 ), 6021 comments=comments, 6022 )