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: 153 array_exp.set("value_constructor", len(args) == 1 and isinstance(args[0], exp.Query)) 154 if dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS: 155 array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET) 156 157 return array_exp 158 159 160def build_convert_timezone( 161 args: BuilderArgs, default_source_tz: str | None = None 162) -> exp.ConvertTimezone | exp.Anonymous: 163 if len(args) == 2: 164 source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None 165 return exp.ConvertTimezone( 166 source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1) 167 ) 168 169 return exp.ConvertTimezone.from_arg_list(args) 170 171 172def build_trim(args: BuilderArgs, is_left: bool = True, reverse_args: bool = False) -> exp.Trim: 173 this, expression = seq_get(args, 0), seq_get(args, 1) 174 175 if expression and reverse_args: 176 this, expression = expression, this 177 178 return exp.Trim(this=this, expression=expression, position="LEADING" if is_left else "TRAILING") 179 180 181def build_coalesce( 182 args: BuilderArgs, is_nvl: bool | None = None, is_null: bool | None = None 183) -> exp.Coalesce: 184 return exp.Coalesce(this=seq_get(args, 0), expressions=args[1:], is_nvl=is_nvl, is_null=is_null) 185 186 187def build_locate_strposition(args: BuilderArgs) -> exp.StrPosition: 188 return exp.StrPosition( 189 this=seq_get(args, 1), 190 substr=seq_get(args, 0), 191 position=seq_get(args, 2), 192 ) 193 194 195def build_array_append(args: BuilderArgs, dialect: Dialect) -> exp.ArrayAppend: 196 """ 197 Builds ArrayAppend with NULL propagation semantics based on the dialect configuration. 198 199 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 200 Others (DuckDB, PostgreSQL) create a new single-element array instead. 201 202 Args: 203 args: Function arguments [array, element] 204 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 205 206 Returns: 207 ArrayAppend expression with appropriate null_propagation flag 208 """ 209 return exp.ArrayAppend( 210 this=seq_get(args, 0), 211 expression=seq_get(args, 1), 212 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 213 ) 214 215 216def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend: 217 """ 218 Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration. 219 220 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 221 Others (DuckDB, PostgreSQL) create a new single-element array instead. 222 223 Args: 224 args: Function arguments [array, element] 225 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 226 227 Returns: 228 ArrayPrepend expression with appropriate null_propagation flag 229 """ 230 return exp.ArrayPrepend( 231 this=seq_get(args, 0), 232 expression=seq_get(args, 1), 233 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 234 ) 235 236 237def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat: 238 """ 239 Builds ArrayConcat with NULL propagation semantics based on the dialect configuration. 240 241 Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. 242 Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation. 243 244 Args: 245 args: Function arguments [array1, array2, ...] (variadic) 246 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 247 248 Returns: 249 ArrayConcat expression with appropriate null_propagation flag 250 """ 251 return exp.ArrayConcat( 252 this=seq_get(args, 0), 253 expressions=args[1:], 254 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 255 ) 256 257 258def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove: 259 """ 260 Builds ArrayRemove with NULL propagation semantics based on the dialect configuration. 261 262 Some dialects (Snowflake) return NULL when the removal value is NULL. 263 Others (DuckDB) may return empty array due to NULL comparison semantics. 264 265 Args: 266 args: Function arguments [array, value_to_remove] 267 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 268 269 Returns: 270 ArrayRemove expression with appropriate null_propagation flag 271 """ 272 return exp.ArrayRemove( 273 this=seq_get(args, 0), 274 expression=seq_get(args, 1), 275 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 276 ) 277 278 279def _resolve_dialect(dialect: DialectType) -> Dialect: 280 from sqlglot.dialects.dialect import Dialect 281 282 return Dialect.get_or_raise(dialect) 283 284 285def _unpivot_target(expr: exp.Expr) -> exp.Expr: 286 # UNPIVOT's pre-FOR values and FOR field are new output names, not column references. 287 if isinstance(expr, exp.Column) and not expr.table: 288 return expr.this 289 if isinstance(expr, exp.Tuple): 290 expr.set("expressions", [_unpivot_target(e) for e in expr.expressions]) 291 return expr 292 293 294# Builders for the JSON `->` / `->>` / `#>` / `#>>` / `?` operators, shared between 295# COLUMN_OPERATORS (accessor-tier dialects) and JSON_OPERATORS (Postgres/DuckDB's 296# binary-operator tier). 297def build_json_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONExtract: 298 return self.expression( 299 exp.JSONExtract( 300 this=this, 301 expression=self.dialect.to_json_path(path), 302 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 303 ) 304 ) 305 306 307def build_json_extract_scalar( 308 self: Parser, this: exp.Expr, path: exp.Expr 309) -> exp.JSONExtractScalar: 310 return self.expression( 311 exp.JSONExtractScalar( 312 this=this, 313 expression=self.dialect.to_json_path(path), 314 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 315 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 316 ) 317 ) 318 319 320def build_jsonb_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONBExtract: 321 return self.expression(exp.JSONBExtract(this=this, expression=path)) 322 323 324def build_jsonb_extract_scalar( 325 self: Parser, this: exp.Expr, path: exp.Expr 326) -> exp.JSONBExtractScalar: 327 return self.expression(exp.JSONBExtractScalar(this=this, expression=path)) 328 329 330def build_jsonb_contains_top_key( 331 self: Parser, this: exp.Expr, key: exp.Expr 332) -> exp.JSONBContainsTopKey: 333 return self.expression(exp.JSONBContainsTopKey(this=this, expression=key)) 334 335 336SENTINEL_NONE: Token = Token(TokenType.SENTINEL, "SENTINEL") 337 338 339class Parser: 340 """ 341 Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree. 342 343 Args: 344 error_level: The desired error level. 345 Default: ErrorLevel.IMMEDIATE 346 error_message_context: The amount of context to capture from a query string when displaying 347 the error message (in number of characters). 348 Default: 100 349 max_errors: Maximum number of error messages to include in a raised ParseError. 350 This is only relevant if error_level is ErrorLevel.RAISE. 351 Default: 3 352 max_nodes: Maximum number of AST nodes to prevent memory exhaustion. 353 Set to -1 (default) to disable the check. 354 """ 355 356 __slots__ = ( 357 "error_level", 358 "error_message_context", 359 "max_errors", 360 "max_nodes", 361 "dialect", 362 "sql", 363 "errors", 364 "_tokens", 365 "_index", 366 "_curr", 367 "_next", 368 "_prev", 369 "_prev_comments", 370 "_pipe_cte_counter", 371 "_chunks", 372 "_chunk_index", 373 "_tokens_size", 374 "_node_count", 375 ) 376 377 FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = { 378 **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()}, 379 **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce), 380 "ARRAY": lambda args, dialect: exp.Array(expressions=args), 381 "ARRAYAGG": lambda args, dialect: exp.ArrayAgg( 382 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 383 ), 384 "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg( 385 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 386 ), 387 "ARRAY_APPEND": build_array_append, 388 "ARRAY_CAT": build_array_concat, 389 "ARRAY_CONCAT": build_array_concat, 390 "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args), 391 "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args), 392 "ARRAY_PREPEND": build_array_prepend, 393 "ARRAY_REMOVE": build_array_remove, 394 "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True), 395 "CONCAT": lambda args, dialect: exp.Concat( 396 expressions=args, 397 safe=not dialect.STRICT_STRING_CONCAT, 398 coalesce=dialect.CONCAT_COALESCE, 399 ), 400 "CONCAT_WS": lambda args, dialect: exp.ConcatWs( 401 expressions=args, 402 safe=not dialect.STRICT_STRING_CONCAT, 403 coalesce=dialect.CONCAT_WS_COALESCE, 404 ), 405 "CONVERT_TIMEZONE": build_convert_timezone, 406 "DATE_TO_DATE_STR": lambda args: exp.Cast( 407 this=seq_get(args, 0), 408 to=exp.DataType(this=exp.DType.TEXT), 409 ), 410 "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray( 411 start=seq_get(args, 0), 412 end=seq_get(args, 1), 413 step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")), 414 ), 415 "GENERATE_UUID": lambda args, dialect: exp.Uuid( 416 is_string=dialect.UUID_IS_STRING_TYPE or None 417 ), 418 "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)), 419 "GREATEST": lambda args, dialect: exp.Greatest( 420 this=seq_get(args, 0), 421 expressions=args[1:], 422 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 423 ), 424 "LEAST": lambda args, dialect: exp.Least( 425 this=seq_get(args, 0), 426 expressions=args[1:], 427 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 428 ), 429 "HEX": build_hex, 430 "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract), 431 "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar), 432 "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar), 433 "JSON_KEYS": lambda args, dialect: exp.JSONKeys( 434 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 435 ), 436 "LIKE": build_like, 437 "LOG": build_logarithm, 438 "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)), 439 "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)), 440 "LOWER": build_lower, 441 "LPAD": lambda args: build_pad(args), 442 "LEFTPAD": lambda args: build_pad(args), 443 "LTRIM": lambda args: build_trim(args), 444 "MOD": build_mod, 445 "RIGHTPAD": lambda args: build_pad(args, is_left=False), 446 "RPAD": lambda args: build_pad(args, is_left=False), 447 "RTRIM": lambda args: build_trim(args, is_left=False), 448 "SCOPE_RESOLUTION": lambda args: ( 449 exp.ScopeResolution(expression=seq_get(args, 0)) 450 if len(args) != 2 451 else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1)) 452 ), 453 "STRPOS": exp.StrPosition.from_arg_list, 454 "CHARINDEX": lambda args: build_locate_strposition(args), 455 "INSTR": exp.StrPosition.from_arg_list, 456 "LOCATE": lambda args: build_locate_strposition(args), 457 "TIME_TO_TIME_STR": lambda args: exp.Cast( 458 this=seq_get(args, 0), 459 to=exp.DataType(this=exp.DType.TEXT), 460 ), 461 "TO_HEX": build_hex, 462 "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring( 463 this=exp.Cast( 464 this=seq_get(args, 0), 465 to=exp.DataType(this=exp.DType.TEXT), 466 ), 467 start=exp.Literal.number(1), 468 length=exp.Literal.number(10), 469 ), 470 "UNNEST": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))), 471 "UPPER": build_upper, 472 "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None), 473 "UUID_STRING": lambda args, dialect: exp.Uuid( 474 this=seq_get(args, 0), 475 name=seq_get(args, 1), 476 is_string=dialect.UUID_IS_STRING_TYPE or None, 477 ), 478 "VAR_MAP": build_var_map, 479 } 480 481 NO_PAREN_FUNCTIONS: t.ClassVar[dict] = { 482 TokenType.CURRENT_DATE: exp.CurrentDate, 483 TokenType.CURRENT_DATETIME: exp.CurrentDate, 484 TokenType.CURRENT_TIME: exp.CurrentTime, 485 TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp, 486 TokenType.CURRENT_USER: exp.CurrentUser, 487 TokenType.CURRENT_ROLE: exp.CurrentRole, 488 } 489 490 STRUCT_TYPE_TOKENS: t.ClassVar = { 491 TokenType.NESTED, 492 TokenType.OBJECT, 493 TokenType.STRUCT, 494 TokenType.UNION, 495 } 496 497 NESTED_TYPE_TOKENS: t.ClassVar = { 498 TokenType.ARRAY, 499 TokenType.LIST, 500 TokenType.LOWCARDINALITY, 501 TokenType.MAP, 502 TokenType.NULLABLE, 503 TokenType.RANGE, 504 *STRUCT_TYPE_TOKENS, 505 } 506 507 ENUM_TYPE_TOKENS: t.ClassVar = { 508 TokenType.DYNAMIC, 509 TokenType.ENUM, 510 TokenType.ENUM8, 511 TokenType.ENUM16, 512 } 513 514 AGGREGATE_TYPE_TOKENS: t.ClassVar = { 515 TokenType.AGGREGATEFUNCTION, 516 TokenType.SIMPLEAGGREGATEFUNCTION, 517 } 518 519 TYPE_TOKENS: t.ClassVar = { 520 TokenType.BIT, 521 TokenType.BOOLEAN, 522 TokenType.TINYINT, 523 TokenType.UTINYINT, 524 TokenType.SMALLINT, 525 TokenType.USMALLINT, 526 TokenType.INT, 527 TokenType.UINT, 528 TokenType.BIGINT, 529 TokenType.UBIGINT, 530 TokenType.BIGNUM, 531 TokenType.INT128, 532 TokenType.UINT128, 533 TokenType.INT256, 534 TokenType.UINT256, 535 TokenType.MEDIUMINT, 536 TokenType.UMEDIUMINT, 537 TokenType.FIXEDSTRING, 538 TokenType.FLOAT, 539 TokenType.DOUBLE, 540 TokenType.UDOUBLE, 541 TokenType.CHAR, 542 TokenType.NCHAR, 543 TokenType.VARCHAR, 544 TokenType.NVARCHAR, 545 TokenType.BPCHAR, 546 TokenType.TEXT, 547 TokenType.MEDIUMTEXT, 548 TokenType.LONGTEXT, 549 TokenType.BLOB, 550 TokenType.MEDIUMBLOB, 551 TokenType.LONGBLOB, 552 TokenType.BINARY, 553 TokenType.VARBINARY, 554 TokenType.JSON, 555 TokenType.JSONB, 556 TokenType.INTERVAL, 557 TokenType.TINYBLOB, 558 TokenType.TINYTEXT, 559 TokenType.TIME, 560 TokenType.TIMETZ, 561 TokenType.TIME_NS, 562 TokenType.TIMESTAMP, 563 TokenType.TIMESTAMP_S, 564 TokenType.TIMESTAMP_MS, 565 TokenType.TIMESTAMP_NS, 566 TokenType.TIMESTAMPTZ, 567 TokenType.TIMESTAMPLTZ, 568 TokenType.TIMESTAMPNTZ, 569 TokenType.DATETIME, 570 TokenType.DATETIME2, 571 TokenType.DATETIME64, 572 TokenType.SMALLDATETIME, 573 TokenType.DATE, 574 TokenType.DATE32, 575 TokenType.INT4RANGE, 576 TokenType.INT4MULTIRANGE, 577 TokenType.INT8RANGE, 578 TokenType.INT8MULTIRANGE, 579 TokenType.NUMRANGE, 580 TokenType.NUMMULTIRANGE, 581 TokenType.TSRANGE, 582 TokenType.TSMULTIRANGE, 583 TokenType.TSTZRANGE, 584 TokenType.TSTZMULTIRANGE, 585 TokenType.DATERANGE, 586 TokenType.DATEMULTIRANGE, 587 TokenType.DECIMAL, 588 TokenType.DECIMAL32, 589 TokenType.DECIMAL64, 590 TokenType.DECIMAL128, 591 TokenType.DECIMAL256, 592 TokenType.DECFLOAT, 593 TokenType.UDECIMAL, 594 TokenType.BIGDECIMAL, 595 TokenType.UUID, 596 TokenType.GEOGRAPHY, 597 TokenType.GEOGRAPHYPOINT, 598 TokenType.GEOMETRY, 599 TokenType.POINT, 600 TokenType.RING, 601 TokenType.LINESTRING, 602 TokenType.MULTILINESTRING, 603 TokenType.POLYGON, 604 TokenType.MULTIPOLYGON, 605 TokenType.HLLSKETCH, 606 TokenType.HSTORE, 607 TokenType.PSEUDO_TYPE, 608 TokenType.SUPER, 609 TokenType.SERIAL, 610 TokenType.SMALLSERIAL, 611 TokenType.BIGSERIAL, 612 TokenType.XML, 613 TokenType.YEAR, 614 TokenType.USERDEFINED, 615 TokenType.MONEY, 616 TokenType.SMALLMONEY, 617 TokenType.ROWVERSION, 618 TokenType.IMAGE, 619 TokenType.VARIANT, 620 TokenType.VECTOR, 621 TokenType.VOID, 622 TokenType.OBJECT, 623 TokenType.OBJECT_IDENTIFIER, 624 TokenType.INET, 625 TokenType.IPADDRESS, 626 TokenType.IPPREFIX, 627 TokenType.IPV4, 628 TokenType.IPV6, 629 TokenType.UNKNOWN, 630 TokenType.NOTHING, 631 TokenType.NULL, 632 TokenType.NAME, 633 TokenType.TDIGEST, 634 TokenType.DYNAMIC, 635 *ENUM_TYPE_TOKENS, 636 *NESTED_TYPE_TOKENS, 637 *AGGREGATE_TYPE_TOKENS, 638 } 639 640 SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = { 641 TokenType.BIGINT: TokenType.UBIGINT, 642 TokenType.INT: TokenType.UINT, 643 TokenType.MEDIUMINT: TokenType.UMEDIUMINT, 644 TokenType.SMALLINT: TokenType.USMALLINT, 645 TokenType.TINYINT: TokenType.UTINYINT, 646 TokenType.DECIMAL: TokenType.UDECIMAL, 647 TokenType.DOUBLE: TokenType.UDOUBLE, 648 } 649 650 SUBQUERY_PREDICATES: t.ClassVar = { 651 TokenType.ANY: exp.Any, 652 TokenType.ALL: exp.All, 653 TokenType.EXISTS: exp.Exists, 654 TokenType.SOME: exp.Any, 655 } 656 657 SUBQUERY_TOKENS: t.ClassVar = { 658 TokenType.SELECT, 659 TokenType.WITH, 660 TokenType.FROM, 661 } 662 663 RESERVED_TOKENS: t.ClassVar = { 664 *Tokenizer.SINGLE_TOKENS.values(), 665 TokenType.SELECT, 666 } - {TokenType.IDENTIFIER} 667 668 # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers, 669 # string literals), so they must never be treated as keywords when matching by text 670 TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset( 671 { 672 TokenType.BIT_STRING, 673 TokenType.BYTE_STRING, 674 TokenType.HEREDOC_STRING, 675 TokenType.HEX_STRING, 676 TokenType.IDENTIFIER, 677 TokenType.NATIONAL_STRING, 678 TokenType.RAW_STRING, 679 TokenType.STRING, 680 TokenType.UNICODE_STRING, 681 } 682 ) 683 684 DB_CREATABLES: t.ClassVar = { 685 TokenType.DATABASE, 686 TokenType.DICTIONARY, 687 TokenType.FILE_FORMAT, 688 TokenType.MODEL, 689 TokenType.NAMESPACE, 690 TokenType.SCHEMA, 691 TokenType.SEMANTIC_VIEW, 692 TokenType.SEQUENCE, 693 TokenType.SINK, 694 TokenType.SOURCE, 695 TokenType.STAGE, 696 TokenType.STORAGE_INTEGRATION, 697 TokenType.STREAMLIT, 698 TokenType.TABLE, 699 TokenType.TAG, 700 TokenType.VIEW, 701 TokenType.WAREHOUSE, 702 } 703 704 CREATABLES: t.ClassVar = { 705 TokenType.COLUMN, 706 TokenType.CONSTRAINT, 707 TokenType.FOREIGN_KEY, 708 TokenType.FUNCTION, 709 TokenType.INDEX, 710 TokenType.PROCEDURE, 711 TokenType.TRIGGER, 712 TokenType.TYPE, 713 *DB_CREATABLES, 714 } 715 716 TRIGGER_EVENTS: t.ClassVar = { 717 TokenType.INSERT, 718 TokenType.UPDATE, 719 TokenType.DELETE, 720 TokenType.TRUNCATE, 721 } 722 723 ALTERABLES: t.ClassVar = { 724 TokenType.INDEX, 725 TokenType.TABLE, 726 TokenType.VIEW, 727 TokenType.SESSION, 728 } 729 730 # Tokens that can represent identifiers 731 ID_VAR_TOKENS: t.ClassVar[set] = { 732 TokenType.ALL, 733 TokenType.ANALYZE, 734 TokenType.ATTACH, 735 TokenType.VAR, 736 TokenType.ANTI, 737 TokenType.APPLY, 738 TokenType.ASC, 739 TokenType.ASOF, 740 TokenType.AUTO_INCREMENT, 741 TokenType.BEGIN, 742 TokenType.BPCHAR, 743 TokenType.CACHE, 744 TokenType.CASE, 745 TokenType.COLLATE, 746 TokenType.COMMAND, 747 TokenType.COMMENT, 748 TokenType.COMMIT, 749 TokenType.CONSTRAINT, 750 TokenType.COPY, 751 TokenType.CUBE, 752 TokenType.CURRENT_SCHEMA, 753 TokenType.DECLARE, 754 TokenType.DEFAULT, 755 TokenType.DELETE, 756 TokenType.DESC, 757 TokenType.DESCRIBE, 758 TokenType.DETACH, 759 TokenType.DICTIONARY, 760 TokenType.DIV, 761 TokenType.END, 762 TokenType.EXECUTE, 763 TokenType.EXPORT, 764 TokenType.ESCAPE, 765 TokenType.FALSE, 766 TokenType.FIRST, 767 TokenType.FILE, 768 TokenType.FILTER, 769 TokenType.FINAL, 770 TokenType.FORMAT, 771 TokenType.FULL, 772 TokenType.GET, 773 TokenType.IDENTIFIER, 774 TokenType.INOUT, 775 TokenType.IS, 776 TokenType.ISNULL, 777 TokenType.INTERVAL, 778 TokenType.KEEP, 779 TokenType.KILL, 780 TokenType.LEFT, 781 TokenType.LIMIT, 782 TokenType.LOAD, 783 TokenType.LOCK, 784 TokenType.MATCH, 785 TokenType.MERGE, 786 TokenType.NATURAL, 787 TokenType.NEXT, 788 TokenType.OFFSET, 789 TokenType.OPERATOR, 790 TokenType.ORDINALITY, 791 TokenType.OUT, 792 TokenType.OVER, 793 TokenType.OVERLAPS, 794 TokenType.OVERWRITE, 795 TokenType.PARTITION, 796 TokenType.PERCENT, 797 TokenType.PIVOT, 798 TokenType.PROJECTION, 799 TokenType.PRAGMA, 800 TokenType.PUT, 801 TokenType.RANGE, 802 TokenType.RECURSIVE, 803 TokenType.REFERENCES, 804 TokenType.REFRESH, 805 TokenType.RENAME, 806 TokenType.REPLACE, 807 TokenType.RIGHT, 808 TokenType.ROLLUP, 809 TokenType.ROW, 810 TokenType.ROWS, 811 TokenType.SEMI, 812 TokenType.SET, 813 TokenType.SETTINGS, 814 TokenType.SHOW, 815 TokenType.STREAM, 816 TokenType.STREAMLIT, 817 TokenType.TEMPORARY, 818 TokenType.TOP, 819 TokenType.TRUE, 820 TokenType.TRUNCATE, 821 TokenType.UNIQUE, 822 TokenType.UNNEST, 823 TokenType.UNPIVOT, 824 TokenType.UPDATE, 825 TokenType.USE, 826 TokenType.VOLATILE, 827 TokenType.WINDOW, 828 TokenType.CURRENT_CATALOG, 829 TokenType.LOCALTIME, 830 TokenType.LOCALTIMESTAMP, 831 TokenType.SESSION_USER, 832 TokenType.STRAIGHT_JOIN, 833 *ALTERABLES, 834 *CREATABLES, 835 *SUBQUERY_PREDICATES, 836 *TYPE_TOKENS, 837 *NO_PAREN_FUNCTIONS, 838 } - {TokenType.UNION} 839 840 TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - { 841 TokenType.ANTI, 842 TokenType.ASOF, 843 TokenType.FULL, 844 TokenType.LEFT, 845 TokenType.LOCK, 846 TokenType.NATURAL, 847 TokenType.RIGHT, 848 TokenType.SEMI, 849 TokenType.WINDOW, 850 } 851 852 ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS 853 854 COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS 855 856 ARRAY_CONSTRUCTORS: t.ClassVar = { 857 "ARRAY": exp.Array, 858 "LIST": exp.List, 859 } 860 861 COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS} 862 863 UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET} 864 865 TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"} 866 867 # Tokens that indicate a simple column reference 868 IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER}) 869 870 BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE}) 871 872 # Postfix tokens that prevent the bare column fast path 873 COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 874 { 875 TokenType.L_PAREN, 876 TokenType.L_BRACKET, 877 TokenType.L_BRACE, 878 TokenType.COLON, 879 TokenType.JOIN_MARKER, 880 } 881 ) 882 883 TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 884 { 885 TokenType.L_PAREN, 886 TokenType.L_BRACKET, 887 TokenType.L_BRACE, 888 TokenType.PIVOT, 889 TokenType.UNPIVOT, 890 TokenType.TABLE_SAMPLE, 891 } 892 ) 893 894 FUNC_TOKENS: t.ClassVar = { 895 TokenType.COLLATE, 896 TokenType.COMMAND, 897 TokenType.CURRENT_DATE, 898 TokenType.CURRENT_DATETIME, 899 TokenType.CURRENT_SCHEMA, 900 TokenType.CURRENT_TIMESTAMP, 901 TokenType.CURRENT_TIME, 902 TokenType.CURRENT_USER, 903 TokenType.CURRENT_CATALOG, 904 TokenType.DECLARE, 905 TokenType.FILTER, 906 TokenType.FIRST, 907 TokenType.FORMAT, 908 TokenType.GET, 909 TokenType.GLOB, 910 TokenType.IDENTIFIER, 911 TokenType.INDEX, 912 TokenType.ISNULL, 913 TokenType.ILIKE, 914 TokenType.INSERT, 915 TokenType.LIKE, 916 TokenType.LOCALTIME, 917 TokenType.LOCALTIMESTAMP, 918 TokenType.MERGE, 919 TokenType.NEXT, 920 TokenType.OFFSET, 921 TokenType.PRIMARY_KEY, 922 TokenType.RANGE, 923 TokenType.REPLACE, 924 TokenType.RLIKE, 925 TokenType.ROW, 926 TokenType.SESSION_USER, 927 TokenType.UNNEST, 928 TokenType.VAR, 929 TokenType.LEFT, 930 TokenType.RIGHT, 931 TokenType.SEQUENCE, 932 TokenType.DATE, 933 TokenType.DATETIME, 934 TokenType.TABLE, 935 TokenType.TIMESTAMP, 936 TokenType.TIMESTAMPTZ, 937 TokenType.TRUNCATE, 938 TokenType.UTC_DATE, 939 TokenType.UTC_TIME, 940 TokenType.UTC_TIMESTAMP, 941 TokenType.WINDOW, 942 TokenType.XOR, 943 *TYPE_TOKENS, 944 *SUBQUERY_PREDICATES, 945 } 946 947 CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 948 TokenType.AND: exp.And, 949 } 950 951 ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 952 TokenType.COLON_EQ: exp.PropertyEQ, 953 } 954 955 DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 956 TokenType.OR: exp.Or, 957 } 958 959 EQUALITY: t.ClassVar = { 960 TokenType.EQ: exp.EQ, 961 TokenType.NEQ: exp.NEQ, 962 TokenType.NULLSAFE_EQ: exp.NullSafeEQ, 963 } 964 965 COMPARISON: t.ClassVar = { 966 TokenType.GT: exp.GT, 967 TokenType.GTE: exp.GTE, 968 TokenType.LT: exp.LT, 969 TokenType.LTE: exp.LTE, 970 } 971 972 BITWISE: t.ClassVar = { 973 TokenType.AMP: exp.BitwiseAnd, 974 TokenType.CARET: exp.BitwiseXor, 975 TokenType.PIPE: exp.BitwiseOr, 976 } 977 978 TERM: t.ClassVar = { 979 TokenType.DASH: exp.Sub, 980 TokenType.PLUS: exp.Add, 981 TokenType.COLLATE: exp.Collate, 982 } 983 984 FACTOR: t.ClassVar = { 985 TokenType.DIV: exp.IntDiv, 986 TokenType.LR_ARROW: exp.Distance, 987 TokenType.LLRR_ARROW: exp.DistanceNd, 988 TokenType.MOD: exp.Mod, 989 TokenType.SLASH: exp.Div, 990 TokenType.STAR: exp.Mul, 991 } 992 993 EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {} 994 995 TIMES: t.ClassVar = { 996 TokenType.TIME, 997 TokenType.TIMETZ, 998 } 999 1000 TIMESTAMPS: t.ClassVar = { 1001 TokenType.TIMESTAMP, 1002 TokenType.TIMESTAMPNTZ, 1003 TokenType.TIMESTAMPTZ, 1004 TokenType.TIMESTAMPLTZ, 1005 *TIMES, 1006 } 1007 1008 SET_OPERATIONS: t.ClassVar = { 1009 TokenType.UNION, 1010 TokenType.INTERSECT, 1011 TokenType.EXCEPT, 1012 } 1013 1014 JOIN_METHODS: t.ClassVar = { 1015 TokenType.ASOF, 1016 TokenType.NATURAL, 1017 TokenType.POSITIONAL, 1018 } 1019 1020 JOIN_SIDES: t.ClassVar = { 1021 TokenType.LEFT, 1022 TokenType.RIGHT, 1023 TokenType.FULL, 1024 } 1025 1026 JOIN_KINDS: t.ClassVar = { 1027 TokenType.ANTI, 1028 TokenType.CROSS, 1029 TokenType.INNER, 1030 TokenType.OUTER, 1031 TokenType.SEMI, 1032 TokenType.STRAIGHT_JOIN, 1033 } 1034 1035 JOIN_HINTS: t.ClassVar[set[str]] = set() 1036 1037 # Tokens that unambiguously end a table reference on the fast path 1038 TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset( 1039 { 1040 TokenType.COMMA, 1041 TokenType.GROUP_BY, 1042 TokenType.HAVING, 1043 TokenType.JOIN, 1044 TokenType.LIMIT, 1045 TokenType.ON, 1046 TokenType.ORDER_BY, 1047 TokenType.R_PAREN, 1048 TokenType.SEMICOLON, 1049 TokenType.SENTINEL, 1050 TokenType.WHERE, 1051 *SET_OPERATIONS, 1052 *JOIN_KINDS, 1053 *JOIN_METHODS, 1054 *JOIN_SIDES, 1055 } 1056 ) 1057 1058 LAMBDAS: t.ClassVar = { 1059 TokenType.ARROW: lambda self, expressions: self.expression( 1060 exp.Lambda( 1061 this=self._replace_lambda( 1062 self._parse_disjunction(), 1063 expressions, 1064 ), 1065 expressions=expressions, 1066 ) 1067 ), 1068 TokenType.FARROW: lambda self, expressions: self.expression( 1069 exp.Kwarg( 1070 this=exp.var(expressions[0].name), 1071 expression=self._parse_disjunction() or self._parse_select(), 1072 ) 1073 ), 1074 } 1075 1076 # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake 1077 TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False 1078 1079 LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN}) 1080 1081 COLUMN_OPERATORS: t.ClassVar = { 1082 TokenType.DOT: None, 1083 TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)), 1084 TokenType.DCOLON: lambda self, this, to: self.build_cast( 1085 strict=self.STRICT_CAST, this=this, to=to 1086 ), 1087 TokenType.ARROW: lambda self, this, path: self.expression( 1088 exp.JSONExtract( 1089 this=this, 1090 expression=self.dialect.to_json_path(path), 1091 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1092 ) 1093 ), 1094 TokenType.DARROW: lambda self, this, path: self.expression( 1095 exp.JSONExtractScalar( 1096 this=this, 1097 expression=self.dialect.to_json_path(path), 1098 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1099 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 1100 ) 1101 ), 1102 TokenType.HASH_ARROW: lambda self, this, path: self.expression( 1103 exp.JSONBExtract(this=this, expression=path) 1104 ), 1105 TokenType.DHASH_ARROW: lambda self, this, path: self.expression( 1106 exp.JSONBExtractScalar(this=this, expression=path) 1107 ), 1108 TokenType.PLACEHOLDER: lambda self, this, key: self.expression( 1109 exp.JSONBContainsTopKey(this=this, expression=key) 1110 ), 1111 } 1112 1113 # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator" 1114 # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs). 1115 JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {} 1116 1117 CAST_COLUMN_OPERATORS: t.ClassVar = { 1118 TokenType.DOTCOLON, 1119 TokenType.DCOLON, 1120 } 1121 1122 EXPRESSION_PARSERS: t.ClassVar = { 1123 exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), 1124 exp.Column: lambda self: self._parse_column(), 1125 exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()), 1126 exp.Condition: lambda self: self._parse_disjunction(), 1127 exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True), 1128 exp.Expr: lambda self: self._parse_expression(), 1129 exp.From: lambda self: self._parse_from(joins=True), 1130 exp.GrantPrincipal: lambda self: self._parse_grant_principal(), 1131 exp.GrantPrivilege: lambda self: self._parse_grant_privilege(), 1132 exp.Group: lambda self: self._parse_group(), 1133 exp.Having: lambda self: self._parse_having(), 1134 exp.Hint: lambda self: self._parse_hint_body(), 1135 exp.Identifier: lambda self: self._parse_id_var(), 1136 exp.Join: lambda self: self._parse_join(), 1137 exp.Lambda: lambda self: self._parse_lambda(), 1138 exp.Lateral: lambda self: self._parse_lateral(), 1139 exp.Limit: lambda self: self._parse_limit(), 1140 exp.Offset: lambda self: self._parse_offset(), 1141 exp.Order: lambda self: self._parse_order(), 1142 exp.Ordered: lambda self: self._parse_ordered(), 1143 exp.Properties: lambda self: self._parse_properties(), 1144 exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(), 1145 exp.Qualify: lambda self: self._parse_qualify(), 1146 exp.Returning: lambda self: self._parse_returning(), 1147 exp.Select: lambda self: self._parse_select(), 1148 exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), 1149 exp.Table: lambda self: self._parse_table_parts(), 1150 exp.TableAlias: lambda self: self._parse_table_alias(), 1151 exp.Tuple: lambda self: self._parse_value(values=False), 1152 exp.Whens: lambda self: self._parse_when_matched(), 1153 exp.Where: lambda self: self._parse_where(), 1154 exp.Window: lambda self: self._parse_named_window(), 1155 exp.With: lambda self: self._parse_with(), 1156 } 1157 1158 STATEMENT_PARSERS: t.ClassVar = { 1159 TokenType.ALTER: lambda self: self._parse_alter(), 1160 TokenType.ANALYZE: lambda self: self._parse_analyze(), 1161 TokenType.BEGIN: lambda self: self._parse_transaction(), 1162 TokenType.CACHE: lambda self: self._parse_cache(), 1163 TokenType.COMMENT: lambda self: self._parse_comment(), 1164 TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(), 1165 TokenType.COPY: lambda self: self._parse_copy(), 1166 TokenType.CREATE: lambda self: self._parse_create(), 1167 TokenType.DECLARE: lambda self: self._parse_declare(), 1168 TokenType.DELETE: lambda self: self._parse_delete(), 1169 TokenType.DESC: lambda self: self._parse_describe(), 1170 TokenType.DESCRIBE: lambda self: self._parse_describe(), 1171 TokenType.DROP: lambda self: self._parse_drop(), 1172 TokenType.GRANT: lambda self: self._parse_grant(), 1173 TokenType.REVOKE: lambda self: self._parse_revoke(), 1174 TokenType.INSERT: lambda self: self._parse_insert(), 1175 TokenType.KILL: lambda self: self._parse_kill(), 1176 TokenType.LOAD: lambda self: self._parse_load(), 1177 TokenType.MERGE: lambda self: self._parse_merge(), 1178 TokenType.PIVOT: lambda self: self._parse_simplified_pivot(), 1179 TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())), 1180 TokenType.REFRESH: lambda self: self._parse_refresh(), 1181 TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(), 1182 TokenType.SET: lambda self: self._parse_set(), 1183 TokenType.TRUNCATE: lambda self: self._parse_truncate_table(), 1184 TokenType.UNCACHE: lambda self: self._parse_uncache(), 1185 TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True), 1186 TokenType.UPDATE: lambda self: self._parse_update(), 1187 TokenType.USE: lambda self: self._parse_use(), 1188 TokenType.SEMICOLON: lambda self: exp.Semicolon(), 1189 } 1190 1191 UNARY_PARSERS: t.ClassVar = { 1192 TokenType.PLUS: lambda self: self._parse_unary(), # Unary + is handled as a no-op 1193 TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())), 1194 TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())), 1195 TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())), 1196 TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())), 1197 TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())), 1198 } 1199 1200 STRING_PARSERS: t.ClassVar = { 1201 TokenType.HEREDOC_STRING: lambda self, token: self.expression( 1202 exp.RawString(this=token.text), token 1203 ), 1204 TokenType.NATIONAL_STRING: lambda self, token: self.expression( 1205 exp.National(this=token.text), token 1206 ), 1207 TokenType.RAW_STRING: lambda self, token: self.expression( 1208 exp.RawString(this=token.text), token 1209 ), 1210 TokenType.STRING: lambda self, token: self.expression( 1211 exp.Literal(this=token.text, is_string=True), token 1212 ), 1213 TokenType.UNICODE_STRING: lambda self, token: self.expression( 1214 exp.UnicodeString( 1215 this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string() 1216 ), 1217 token, 1218 ), 1219 } 1220 1221 NUMERIC_PARSERS: t.ClassVar = { 1222 TokenType.BIT_STRING: lambda self, token: self.expression( 1223 exp.BitString(this=token.text), token 1224 ), 1225 TokenType.BYTE_STRING: lambda self, token: self.expression( 1226 exp.ByteString( 1227 this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None 1228 ), 1229 token, 1230 ), 1231 TokenType.HEX_STRING: lambda self, token: self.expression( 1232 exp.HexString( 1233 this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None 1234 ), 1235 token, 1236 ), 1237 TokenType.NUMBER: lambda self, token: self.expression( 1238 exp.Literal(this=token.text, is_string=False), token 1239 ), 1240 } 1241 1242 PRIMARY_PARSERS: t.ClassVar = { 1243 **STRING_PARSERS, 1244 **NUMERIC_PARSERS, 1245 TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token), 1246 TokenType.NULL: lambda self, _: self.expression(exp.Null()), 1247 TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)), 1248 TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)), 1249 TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(), 1250 TokenType.STAR: lambda self, _: self._parse_star_ops(), 1251 } 1252 1253 PLACEHOLDER_PARSERS: t.ClassVar = { 1254 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()), 1255 TokenType.PARAMETER: lambda self: self._parse_parameter(), 1256 TokenType.COLON: lambda self: ( 1257 self.expression(exp.Placeholder(this=self._prev.text)) 1258 if self._match_set(self.COLON_PLACEHOLDER_TOKENS) 1259 else None 1260 ), 1261 } 1262 1263 RANGE_PARSERS: t.ClassVar = { 1264 TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll), 1265 TokenType.BETWEEN: lambda self, this: self._parse_between(this), 1266 TokenType.GLOB: binary_range_parser(exp.Glob), 1267 TokenType.ILIKE: binary_range_parser(exp.ILike), 1268 TokenType.IN: lambda self, this: self._parse_in(this), 1269 TokenType.IRLIKE: binary_range_parser(exp.RegexpILike), 1270 TokenType.IS: lambda self, this: self._parse_is(this), 1271 TokenType.LIKE: binary_range_parser(exp.Like), 1272 TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy), 1273 TokenType.OVERLAPS: binary_range_parser(exp.Overlaps), 1274 TokenType.RLIKE: binary_range_parser(exp.RegexpLike), 1275 TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo), 1276 TokenType.FOR: lambda self, this: self._parse_comprehension(this), 1277 TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys), 1278 TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys), 1279 TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath), 1280 TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists), 1281 TokenType.ADJACENT: binary_range_parser(exp.Adjacent), 1282 TokenType.OPERATOR: lambda self, this: self._parse_operator(this), 1283 TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft), 1284 TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight), 1285 TokenType.CARET_AT: binary_range_parser(exp.StartsWith), 1286 } 1287 1288 PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = { 1289 "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query), 1290 "AS": lambda self, query: self._build_pipe_cte( 1291 query, [exp.Star()], self._parse_table_alias() 1292 ), 1293 "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False), 1294 "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query), 1295 "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query), 1296 "ORDER BY": lambda self, query: query.order_by( 1297 self._parse_order(), append=False, copy=False 1298 ), 1299 "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1300 "SELECT": lambda self, query: self._parse_pipe_syntax_select(query), 1301 "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query), 1302 "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1303 "WHERE": lambda self, query: query.where(self._parse_where(), copy=False), 1304 } 1305 1306 PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1307 "ALLOWED_VALUES": lambda self: self.expression( 1308 exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary)) 1309 ), 1310 "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty), 1311 "AUTO": lambda self: self._parse_auto_property(), 1312 "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty), 1313 "BACKUP": lambda self: self.expression( 1314 exp.BackupProperty(this=self._parse_var(any_token=True)) 1315 ), 1316 "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(), 1317 "CALLED": lambda self: self._parse_called_on_null_input_property(), 1318 "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs), 1319 "CHECKSUM": lambda self: self._parse_checksum(), 1320 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1321 "CLUSTERED": lambda self: self._parse_clustered_by(), 1322 "COLLATE": lambda self, **kwargs: self._parse_property_assignment( 1323 exp.CollateProperty, **kwargs 1324 ), 1325 "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty), 1326 "CONTAINS": lambda self: self._parse_contains_property(), 1327 "COPY": lambda self: self._parse_copy_property(), 1328 "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs), 1329 "DATA_DELETION": lambda self: self._parse_data_deletion_property(), 1330 "DEFINER": lambda self: self._parse_definer(), 1331 "DETERMINISTIC": lambda self: self.expression( 1332 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1333 ), 1334 "DISTRIBUTED": lambda self: self._parse_distributed_property(), 1335 "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty), 1336 "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()), 1337 "DISTKEY": lambda self: self._parse_distkey(), 1338 "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty), 1339 "EMPTY": lambda self: self.expression(exp.EmptyProperty()), 1340 "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty), 1341 "ENVIRONMENT": lambda self: self.expression( 1342 exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment)) 1343 ), 1344 "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty), 1345 "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty), 1346 "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()), 1347 "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs), 1348 "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1349 "FREESPACE": lambda self: self._parse_freespace(), 1350 "GLOBAL": lambda self: self.expression(exp.GlobalProperty()), 1351 "HEAP": lambda self: self.expression(exp.HeapProperty()), 1352 "ICEBERG": lambda self: self.expression(exp.IcebergProperty()), 1353 "IMMUTABLE": lambda self: self.expression( 1354 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1355 ), 1356 "INHERITS": lambda self: self.expression( 1357 exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table)) 1358 ), 1359 "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())), 1360 "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs), 1361 "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty), 1362 "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"), 1363 "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"), 1364 "LIKE": lambda self: self._parse_create_like(), 1365 "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty), 1366 "LOCK": lambda self: self._parse_locking(), 1367 "LOCKING": lambda self: self._parse_locking(), 1368 "LOG": lambda self, **kwargs: self._parse_log(**kwargs), 1369 "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()), 1370 "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs), 1371 "MODIFIES": lambda self: self._parse_modifies_property(), 1372 "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)), 1373 "NO": lambda self: self._parse_no_property(), 1374 "ON": lambda self: self._parse_on_property(), 1375 "ORDER BY": lambda self: self._parse_order(skip_order_token=True), 1376 "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())), 1377 "PARTITION": lambda self: self._parse_partitioned_of(), 1378 "PARTITION BY": lambda self: self._parse_partitioned_by(), 1379 "PARTITIONED BY": lambda self: self._parse_partitioned_by(), 1380 "PARTITIONED_BY": lambda self: self._parse_partitioned_by(), 1381 "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True), 1382 "RANGE": lambda self: self._parse_dict_range(this="RANGE"), 1383 "READS": lambda self: self._parse_reads_property(), 1384 "REMOTE": lambda self: self._parse_remote_with_connection(), 1385 "RETURNS": lambda self: self._parse_returns(), 1386 "STRICT": lambda self: self.expression(exp.StrictProperty()), 1387 "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()), 1388 "ROW": lambda self: self._parse_row(), 1389 "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty), 1390 "SAMPLE": lambda self: self.expression( 1391 exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise()) 1392 ), 1393 "SECURE": lambda self: self.expression(exp.SecureProperty()), 1394 "SECURITY": lambda self: self._parse_sql_security(), 1395 "SQL SECURITY": lambda self: self._parse_sql_security(), 1396 "SET": lambda self: self.expression(exp.SetProperty(multi=False)), 1397 "SETTINGS": lambda self: self._parse_settings_property(), 1398 "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty), 1399 "SORTKEY": lambda self: self._parse_sortkey(), 1400 "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"), 1401 "STABLE": lambda self: self.expression( 1402 exp.StabilityProperty(this=exp.Literal.string("STABLE")) 1403 ), 1404 "STORED": lambda self: self._parse_stored(), 1405 "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(), 1406 "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(), 1407 "TEMP": lambda self: self.expression(exp.TemporaryProperty()), 1408 "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()), 1409 "TO": lambda self: self._parse_to_table(), 1410 "TRANSIENT": lambda self: self.expression(exp.TransientProperty()), 1411 "TRANSFORM": lambda self: self.expression( 1412 exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression)) 1413 ), 1414 "TTL": lambda self: self._parse_ttl(), 1415 "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1416 "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()), 1417 "VOLATILE": lambda self: self._parse_volatile_property(), 1418 "WITH": lambda self: self._parse_with_property(), 1419 } 1420 1421 CONSTRAINT_PARSERS: t.ClassVar = { 1422 "AUTOINCREMENT": lambda self: self._parse_auto_increment(), 1423 "AUTO_INCREMENT": lambda self: self._parse_auto_increment(), 1424 "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)), 1425 "CHECK": lambda self: self._parse_check_constraint(), 1426 "COLLATE": lambda self: self.expression( 1427 exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column()) 1428 ), 1429 "COMMENT": lambda self: self.expression( 1430 exp.CommentColumnConstraint(this=self._parse_string()) 1431 ), 1432 "COMPRESS": lambda self: self._parse_compress(), 1433 "CLUSTERED": lambda self: self.expression( 1434 exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1435 ), 1436 "NONCLUSTERED": lambda self: self.expression( 1437 exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1438 ), 1439 "DEFAULT": lambda self: self.expression( 1440 exp.DefaultColumnConstraint(this=self._parse_bitwise()) 1441 ), 1442 "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())), 1443 "EPHEMERAL": lambda self: self.expression( 1444 exp.EphemeralColumnConstraint(this=self._parse_bitwise()) 1445 ), 1446 "EXCLUDE": lambda self: self.expression( 1447 exp.ExcludeColumnConstraint(this=self._parse_index_params()) 1448 ), 1449 "FOREIGN KEY": lambda self: self._parse_foreign_key(), 1450 "FORMAT": lambda self: self.expression( 1451 exp.DateFormatColumnConstraint(this=self._parse_var_or_string()) 1452 ), 1453 "GENERATED": lambda self: self._parse_generated_as_identity(), 1454 "IDENTITY": lambda self: self._parse_auto_increment(), 1455 "INLINE": lambda self: self._parse_inline(), 1456 "LIKE": lambda self: self._parse_create_like(), 1457 "NOT": lambda self: self._parse_not_constraint(), 1458 "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)), 1459 "ON": lambda self: ( 1460 ( 1461 self._match(TokenType.UPDATE) 1462 and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function())) 1463 ) 1464 or self.expression(exp.OnProperty(this=self._parse_id_var())) 1465 ), 1466 "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())), 1467 "PERIOD": lambda self: self._parse_period_for_system_time(), 1468 "PRIMARY KEY": lambda self: self._parse_primary_key(), 1469 "REFERENCES": lambda self: self._parse_references(match=False), 1470 "TITLE": lambda self: self.expression( 1471 exp.TitleColumnConstraint(this=self._parse_var_or_string()) 1472 ), 1473 "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])), 1474 "UNIQUE": lambda self: self._parse_unique(), 1475 "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()), 1476 "WITH": lambda self: self.expression( 1477 exp.Properties(expressions=self._parse_wrapped_properties()) 1478 ), 1479 "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1480 "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1481 } 1482 1483 def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None: 1484 if not self._match(TokenType.L_PAREN, advance=False): 1485 # Partitioning by bucket or truncate follows the syntax: 1486 # PARTITION BY (BUCKET(..) | TRUNCATE(..)) 1487 # If we don't have parenthesis after each keyword, we should instead parse this as an identifier 1488 self._retreat(self._index - 1) 1489 return None 1490 1491 klass = ( 1492 exp.PartitionedByBucket 1493 if self._prev.text.upper() == "BUCKET" 1494 else exp.PartitionByTruncate 1495 ) 1496 1497 args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column()) 1498 this, expression = seq_get(args, 0), seq_get(args, 1) 1499 1500 if isinstance(this, exp.Literal): 1501 # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order 1502 # - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)` 1503 # - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)` 1504 # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)` 1505 # 1506 # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning 1507 # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties 1508 this, expression = expression, this 1509 1510 return self.expression(klass(this=this, expression=expression)) 1511 1512 ALTER_PARSERS: t.ClassVar = { 1513 "ADD": lambda self: self._parse_alter_table_add(), 1514 "AS": lambda self: self._parse_select(), 1515 "ALTER": lambda self: self._parse_alter_table_alter(), 1516 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1517 "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())), 1518 "DROP": lambda self: self._parse_alter_table_drop(), 1519 "RENAME": lambda self: self._parse_alter_table_rename(), 1520 "SET": lambda self: self._parse_alter_table_set(), 1521 "SWAP": lambda self: self.expression( 1522 exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True)) 1523 ), 1524 } 1525 1526 ALTER_ALTER_PARSERS: t.ClassVar = { 1527 "DISTKEY": lambda self: self._parse_alter_diststyle(), 1528 "DISTSTYLE": lambda self: self._parse_alter_diststyle(), 1529 "SORTKEY": lambda self: self._parse_alter_sortkey(), 1530 "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True), 1531 } 1532 1533 SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = { 1534 "CHECK", 1535 "EXCLUDE", 1536 "FOREIGN KEY", 1537 "LIKE", 1538 "PERIOD", 1539 "PRIMARY KEY", 1540 "UNIQUE", 1541 "BUCKET", 1542 "TRUNCATE", 1543 } 1544 1545 NO_PAREN_FUNCTION_PARSERS: t.ClassVar = { 1546 "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())), 1547 "CASE": lambda self: self._parse_case(), 1548 "CONNECT_BY_ROOT": lambda self: self.expression( 1549 exp.ConnectByRoot(this=self._parse_column()) 1550 ), 1551 "IF": lambda self: self._parse_if(), 1552 } 1553 1554 INVALID_FUNC_NAME_TOKENS: t.ClassVar = { 1555 TokenType.IDENTIFIER, 1556 TokenType.STRING, 1557 } 1558 1559 FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"} 1560 1561 KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice) 1562 1563 FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1564 **{ 1565 name: lambda self: self._parse_distinct_arg_function(exp.ArgMax) 1566 for name in exp.ArgMax.sql_names() 1567 }, 1568 **{ 1569 name: lambda self: self._parse_distinct_arg_function(exp.ArgMin) 1570 for name in exp.ArgMin.sql_names() 1571 }, 1572 "CAST": lambda self: self._parse_cast(self.STRICT_CAST), 1573 "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), 1574 "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), 1575 "CHAR": lambda self: self._parse_char(), 1576 "CHR": lambda self: self._parse_char(), 1577 "DECODE": lambda self: self._parse_decode(), 1578 "EXTRACT": lambda self: self._parse_extract(), 1579 "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), 1580 "GAP_FILL": lambda self: self._parse_gap_fill(), 1581 "INITCAP": lambda self: self._parse_initcap(), 1582 "JSON_OBJECT": lambda self: self._parse_json_object(), 1583 "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True), 1584 "JSON_TABLE": lambda self: self._parse_json_table(), 1585 "MATCH": lambda self: self._parse_match_against(), 1586 "NORMALIZE": lambda self: self._parse_normalize(), 1587 "OPENJSON": lambda self: self._parse_open_json(), 1588 "OVERLAY": lambda self: self._parse_overlay(), 1589 "POSITION": lambda self: self._parse_position(), 1590 "SAFE_CAST": lambda self: self._parse_cast(False, safe=True), 1591 "STRING_AGG": lambda self: self._parse_string_agg(), 1592 "SUBSTRING": lambda self: self._parse_substring(), 1593 "TRIM": lambda self: self._parse_trim(), 1594 "TRY_CAST": lambda self: self._parse_cast(False, safe=True), 1595 "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True), 1596 "WEIGHT_STRING": lambda self: self._parse_weight_string(), 1597 "XMLELEMENT": lambda self: self._parse_xml_element(), 1598 "XMLTABLE": lambda self: self._parse_xml_table(), 1599 } 1600 1601 QUERY_MODIFIER_PARSERS: t.ClassVar = { 1602 TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()), 1603 TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()), 1604 TokenType.WHERE: lambda self: ("where", self._parse_where()), 1605 TokenType.GROUP_BY: lambda self: ("group", self._parse_group()), 1606 TokenType.HAVING: lambda self: ("having", self._parse_having()), 1607 TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()), 1608 TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()), 1609 TokenType.ORDER_BY: lambda self: ("order", self._parse_order()), 1610 TokenType.LIMIT: lambda self: ("limit", self._parse_limit()), 1611 TokenType.FETCH: lambda self: ("limit", self._parse_limit()), 1612 TokenType.OFFSET: lambda self: ("offset", self._parse_offset()), 1613 TokenType.FOR: lambda self: ("locks", self._parse_locks()), 1614 TokenType.LOCK: lambda self: ("locks", self._parse_locks()), 1615 TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1616 TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1617 TokenType.CLUSTER_BY: lambda self: ( 1618 "cluster", 1619 self._parse_cluster(), 1620 ), 1621 TokenType.DISTRIBUTE_BY: lambda self: ( 1622 "distribute", 1623 self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), 1624 ), 1625 TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)), 1626 TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)), 1627 } 1628 QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS) 1629 1630 SET_PARSERS: t.ClassVar = { 1631 "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"), 1632 "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"), 1633 "SESSION": lambda self: self._parse_set_item_assignment("SESSION"), 1634 "TRANSACTION": lambda self: self._parse_set_transaction(), 1635 } 1636 1637 SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {} 1638 1639 TYPE_LITERAL_PARSERS: t.ClassVar = { 1640 exp.DType.JSON: lambda self, this, _: self.expression( 1641 exp.ParseJSON(this=this, is_literal=True) 1642 ), 1643 } 1644 1645 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1646 1647 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1648 1649 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1650 1651 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1652 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1653 "ISOLATION": ( 1654 ("LEVEL", "REPEATABLE", "READ"), 1655 ("LEVEL", "READ", "COMMITTED"), 1656 ("LEVEL", "READ", "UNCOMITTED"), 1657 ("LEVEL", "SERIALIZABLE"), 1658 ), 1659 "READ": ("WRITE", "ONLY"), 1660 } 1661 1662 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1663 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1664 "DO": ("NOTHING", "UPDATE"), 1665 } 1666 1667 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1668 "INSTEAD": (("OF",),), 1669 "BEFORE": tuple(), 1670 "AFTER": tuple(), 1671 } 1672 1673 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1674 "NOT": (("DEFERRABLE",),), 1675 "DEFERRABLE": tuple(), 1676 } 1677 1678 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1679 "SCALE": ("EXTEND", "NOEXTEND"), 1680 "SHARD": ("EXTEND", "NOEXTEND"), 1681 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1682 **dict.fromkeys( 1683 ( 1684 "SESSION", 1685 "GLOBAL", 1686 "KEEP", 1687 "NOKEEP", 1688 "ORDER", 1689 "NOORDER", 1690 "NOCACHE", 1691 "CYCLE", 1692 "NOCYCLE", 1693 "NOMINVALUE", 1694 "NOMAXVALUE", 1695 "NOSCALE", 1696 "NOSHARD", 1697 ), 1698 tuple(), 1699 ), 1700 } 1701 1702 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1703 1704 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1705 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1706 ) 1707 1708 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1709 1710 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1711 "TYPE": ("EVOLUTION",), 1712 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1713 } 1714 1715 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1716 1717 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1718 ("CALLER", "SELF", "OWNER"), tuple() 1719 ) 1720 1721 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1722 "NOT": ("ENFORCED",), 1723 "MATCH": ( 1724 "FULL", 1725 "PARTIAL", 1726 "SIMPLE", 1727 ), 1728 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1729 "USING": ( 1730 "BTREE", 1731 "HASH", 1732 ), 1733 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1734 } 1735 1736 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1737 "NO": ("OTHERS",), 1738 "CURRENT": ("ROW",), 1739 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1740 } 1741 1742 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1743 1744 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1745 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1746 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1747 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1748 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1749 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1750 ("FOR", "VERSION"): "VERSION", 1751 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1752 ("VERSION", "AS", "OF"): "VERSION", 1753 } 1754 1755 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1756 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1757 1758 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1759 1760 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1761 1762 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1763 1764 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1765 1766 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1767 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1768 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1769 1770 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1771 1772 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1773 1774 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1775 TokenType.CONSTRAINT, 1776 TokenType.FOREIGN_KEY, 1777 TokenType.INDEX, 1778 TokenType.KEY, 1779 TokenType.PRIMARY_KEY, 1780 TokenType.UNIQUE, 1781 } 1782 1783 # Constraint kinds that start an ALTER ... ADD action but aren't tokens of their own 1784 ADD_CONSTRAINT_KEYWORDS: t.ClassVar[set[str]] = set() 1785 1786 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1787 1788 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1789 1790 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1791 1792 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1793 "FILE_FORMAT", 1794 "COPY_OPTIONS", 1795 "FORMAT_OPTIONS", 1796 "CREDENTIAL", 1797 } 1798 1799 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1800 1801 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1802 1803 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1804 1805 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1806 1807 # The style options for the DESCRIBE statement 1808 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1809 1810 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1811 1812 # The style options for the ANALYZE statement 1813 ANALYZE_STYLES: t.ClassVar = { 1814 "BUFFER_USAGE_LIMIT", 1815 "FULL", 1816 "LOCAL", 1817 "NO_WRITE_TO_BINLOG", 1818 "SAMPLE", 1819 "SKIP_LOCKED", 1820 "VERBOSE", 1821 } 1822 1823 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1824 "ALL": lambda self: self._parse_analyze_columns(), 1825 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1826 "DELETE": lambda self: self._parse_analyze_delete(), 1827 "DROP": lambda self: self._parse_analyze_histogram(), 1828 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1829 "LIST": lambda self: self._parse_analyze_list(), 1830 "PREDICATE": lambda self: self._parse_analyze_columns(), 1831 "UPDATE": lambda self: self._parse_analyze_histogram(), 1832 "VALIDATE": lambda self: self._parse_analyze_validate(), 1833 } 1834 1835 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1836 1837 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1838 1839 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1840 1841 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1842 1843 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1844 1845 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1846 1847 STRICT_CAST: t.ClassVar = True 1848 1849 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1850 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1851 # Whether an UNPIVOT outputs its value column(s) before the name column 1852 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1853 # Controls when an aggregation's name is included in a pivoted column's name: 1854 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1855 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1856 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1857 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1858 1859 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1860 1861 # Whether the table sample clause expects CSV syntax 1862 TABLESAMPLE_CSV: t.ClassVar = False 1863 1864 # The default method used for table sampling 1865 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1866 1867 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1868 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1869 1870 # Whether the TRIM function expects the characters to trim as its first argument 1871 TRIM_PATTERN_FIRST: t.ClassVar = False 1872 1873 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1874 STRING_ALIASES: t.ClassVar = False 1875 1876 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1877 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1878 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"} 1879 1880 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1881 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1882 1883 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1884 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1885 1886 # Whether the `:` operator is used to extract a value from a VARIANT column 1887 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1888 1889 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1890 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1891 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1892 1893 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1894 # If this is True and '(' is not found, the keyword will be treated as an identifier 1895 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1896 1897 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1898 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1899 1900 # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery) 1901 SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False 1902 1903 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1904 INTERVAL_SPANS: t.ClassVar = True 1905 1906 # Whether a PARTITION clause can follow a table reference 1907 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1908 1909 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1910 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1911 1912 # Whether the 'AS' keyword is optional in the CTE definition syntax 1913 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1914 1915 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1916 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1917 1918 # Whether dropping a column with an ALTER statement requires the presence of the COLUMN keyword 1919 ALTER_DROP_REQUIRES_COLUMN: t.ClassVar = True 1920 1921 # Whether Alter statements are allowed to contain Partition specifications 1922 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1923 1924 # Whether an ALTER can combine different actions, e.g. ADD COLUMN a INT, DROP COLUMN b 1925 ALTER_TABLE_MIXED_ACTIONS: t.ClassVar = False 1926 1927 # Whether an ALTER TABLE needs an action, e.g. MySQL's ALTER TABLE t ENGINE=InnoDB has none 1928 ALTER_TABLE_REQUIRES_ACTION: t.ClassVar = True 1929 1930 # Whether changing a column's type with ALTER COLUMN requires the TYPE keyword 1931 ALTER_COLUMN_TYPE_REQUIRES_KEYWORD: t.ClassVar = False 1932 1933 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1934 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1935 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1936 # as BigQuery, where all joins have the same precedence. 1937 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1938 1939 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1940 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1941 1942 # Whether map literals support arbitrary expressions as keys. 1943 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1944 # When False, keys are typically restricted to identifiers. 1945 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1946 1947 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1948 # is true for Snowflake but not for BigQuery which can also process strings 1949 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1950 1951 # Dialects like Databricks support JOINS without join criteria 1952 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 1953 ADD_JOIN_ON_TRUE: t.ClassVar = False 1954 1955 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 1956 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 1957 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 1958 1959 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 1960 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 1961 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 1962 1963 # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause, 1964 # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake) 1965 SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False 1966 1967 # Type names that denote a different type when they're quoted, so quoting has to be 1968 # preserved instead of resolving them into the built-in type of the same name. These 1969 # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR 1970 QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set() 1971 1972 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 1973 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 1974 1975 def __init__( 1976 self, 1977 error_level: ErrorLevel | None = None, 1978 error_message_context: int = 100, 1979 max_errors: int = 3, 1980 max_nodes: int = -1, 1981 dialect: DialectType = None, 1982 ): 1983 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 1984 self.error_message_context: int = error_message_context 1985 self.max_errors: int = max_errors 1986 self.max_nodes: int = max_nodes 1987 self.dialect: t.Any = _resolve_dialect(dialect) 1988 self.sql: str = "" 1989 self.errors: list[ParseError] = [] 1990 self._tokens: list[Token] = [] 1991 self._tokens_size: i64 = 0 1992 self._index: i64 = 0 1993 self._curr: Token = SENTINEL_NONE 1994 self._next: Token = SENTINEL_NONE 1995 self._prev: Token = SENTINEL_NONE 1996 self._prev_comments: list[str] = [] 1997 self._pipe_cte_counter: int = 0 1998 self._chunks: list[list[Token]] = [] 1999 self._chunk_index: i64 = 0 2000 self._node_count: int = 0 2001 2002 def reset(self) -> None: 2003 self.sql = "" 2004 self.errors = [] 2005 self._tokens = [] 2006 self._tokens_size = 0 2007 self._index = 0 2008 self._curr = SENTINEL_NONE 2009 self._next = SENTINEL_NONE 2010 self._prev = SENTINEL_NONE 2011 self._prev_comments = [] 2012 self._pipe_cte_counter = 0 2013 self._chunks = [] 2014 self._chunk_index = 0 2015 self._node_count = 0 2016 2017 def _advance(self, times: i64 = 1) -> None: 2018 index = self._index + times 2019 self._index = index 2020 tokens = self._tokens 2021 size = self._tokens_size 2022 self._curr = tokens[index] if index < size else SENTINEL_NONE 2023 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 2024 2025 if index > 0: 2026 prev = tokens[index - 1] 2027 self._prev = prev 2028 self._prev_comments = prev.comments 2029 else: 2030 self._prev = SENTINEL_NONE 2031 self._prev_comments = [] 2032 2033 def _advance_chunk(self) -> None: 2034 self._index = -1 2035 self._tokens = self._chunks[self._chunk_index] 2036 self._tokens_size = i64(len(self._tokens)) 2037 self._chunk_index += 1 2038 self._advance() 2039 2040 def _retreat(self, index: i64) -> None: 2041 if index != self._index: 2042 self._advance(index - self._index) 2043 2044 def _add_comments(self, expression: exp.Expr | None) -> None: 2045 if expression and self._prev_comments: 2046 expression.add_comments(self._prev_comments) 2047 self._prev_comments = [] 2048 2049 def _match( 2050 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2051 ) -> bool: 2052 if self._curr.token_type == token_type: 2053 if advance: 2054 self._advance() 2055 self._add_comments(expression) 2056 return True 2057 return False 2058 2059 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2060 if self._curr.token_type in types: 2061 if advance: 2062 self._advance() 2063 return True 2064 return False 2065 2066 def _match_pair( 2067 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2068 ) -> bool: 2069 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2070 if advance: 2071 self._advance(2) 2072 return True 2073 return False 2074 2075 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2076 if ( 2077 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2078 and self._curr.text.upper() in texts 2079 ): 2080 if advance: 2081 self._advance() 2082 return True 2083 return False 2084 2085 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2086 index = self._index 2087 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2088 for text in texts: 2089 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2090 self._advance() 2091 else: 2092 self._retreat(index) 2093 return False 2094 2095 if not advance: 2096 self._retreat(index) 2097 2098 return True 2099 2100 def _is_connected(self) -> bool: 2101 prev = self._prev 2102 curr = self._curr 2103 return bool(prev and curr and prev.end + 1 == curr.start) 2104 2105 def _find_sql(self, start: Token, end: Token) -> str: 2106 return self.sql[start.start : end.end + 1] 2107 2108 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2109 token = token or self._curr or self._prev or Token.string("") 2110 formatted_sql, start_context, highlight, end_context = highlight_sql( 2111 sql=self.sql, 2112 positions=[(token.start, token.end)], 2113 context_length=self.error_message_context, 2114 ) 2115 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2116 2117 error = ParseError.new( 2118 formatted_message, 2119 description=message, 2120 line=token.line, 2121 col=token.col, 2122 start_context=start_context, 2123 highlight=highlight, 2124 end_context=end_context, 2125 ) 2126 2127 if self.error_level == ErrorLevel.IMMEDIATE: 2128 raise error 2129 2130 self.errors.append(error) 2131 2132 def validate_expression(self, expression: E, args: list | None = None) -> E: 2133 if self.max_nodes > -1: 2134 self._node_count += 1 2135 if self._node_count > self.max_nodes: 2136 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2137 if self.error_level != ErrorLevel.IGNORE: 2138 for error_message in expression.error_messages(args): 2139 self.raise_error(error_message) 2140 return expression 2141 2142 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2143 index = self._index 2144 error_level = self.error_level 2145 this: T | None = None 2146 2147 self.error_level = ErrorLevel.IMMEDIATE 2148 try: 2149 this = parse_method() 2150 except ParseError: 2151 this = None 2152 finally: 2153 if not this or retreat: 2154 self._retreat(index) 2155 self.error_level = error_level 2156 2157 return this 2158 2159 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2160 """ 2161 Parses a list of tokens and returns a list of syntax trees, one tree 2162 per parsed SQL statement. 2163 2164 Args: 2165 raw_tokens: The list of tokens. 2166 sql: The original SQL string. 2167 2168 Returns: 2169 The list of the produced syntax trees. 2170 """ 2171 return self._parse( 2172 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2173 ) 2174 2175 def parse_into( 2176 self, 2177 expression_types: exp.IntoType, 2178 raw_tokens: list[Token], 2179 sql: str | None = None, 2180 ) -> list[exp.Expr | None]: 2181 """ 2182 Parses a list of tokens into a given Expr type. If a collection of Expr 2183 types is given instead, this method will try to parse the token list into each one 2184 of them, stopping at the first for which the parsing succeeds. 2185 2186 Args: 2187 expression_types: The expression type(s) to try and parse the token list into. 2188 raw_tokens: The list of tokens. 2189 sql: The original SQL string, used to produce helpful debug messages. 2190 2191 Returns: 2192 The target Expr. 2193 """ 2194 errors = [] 2195 for expression_type in ensure_list(expression_types): 2196 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2197 if not parser: 2198 raise TypeError(f"No parser registered for {expression_type}") 2199 2200 try: 2201 return self._parse(parser, raw_tokens, sql) 2202 except ParseError as e: 2203 e.errors[0]["into_expression"] = expression_type 2204 errors.append(e) 2205 2206 raise ParseError( 2207 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2208 errors=merge_errors(errors), 2209 ) from errors[-1] 2210 2211 def check_errors(self) -> None: 2212 """Logs or raises any found errors, depending on the chosen error level setting.""" 2213 if self.error_level == ErrorLevel.WARN: 2214 for error in self.errors: 2215 logger.error(str(error)) 2216 elif self.error_level == ErrorLevel.RAISE and self.errors: 2217 raise ParseError( 2218 concat_messages(self.errors, self.max_errors), 2219 errors=merge_errors(self.errors), 2220 ) 2221 2222 def expression( 2223 self, 2224 instance: E, 2225 token: Token | None = None, 2226 comments: list[str] | None = None, 2227 ) -> E: 2228 if token: 2229 instance.update_positions(token) 2230 instance.add_comments(comments) if comments else self._add_comments(instance) 2231 if not instance.is_primitive: 2232 instance = self.validate_expression(instance) 2233 return instance 2234 2235 def _parse_batch_statements( 2236 self, 2237 parse_method: t.Callable[[Parser], exp.Expr | None], 2238 sep_first_statement: bool = True, 2239 ) -> list[exp.Expr | None]: 2240 expressions = [] 2241 2242 # Chunkification binds if/while statements with the first statement of the body 2243 if sep_first_statement: 2244 self._match(TokenType.BEGIN) 2245 expressions.append(parse_method(self)) 2246 2247 chunks_length = len(self._chunks) 2248 while self._chunk_index < chunks_length: 2249 self._advance_chunk() 2250 2251 if self._match(TokenType.ELSE, advance=False): 2252 return expressions 2253 2254 if expressions and not self._next and self._match(TokenType.END): 2255 expressions.append(exp.EndStatement()) 2256 continue 2257 2258 expressions.append(parse_method(self)) 2259 2260 if self._index < self._tokens_size: 2261 self.raise_error("Invalid expression / Unexpected token") 2262 2263 self.check_errors() 2264 2265 return expressions 2266 2267 def _parse( 2268 self, 2269 parse_method: t.Callable[[Parser], exp.Expr | None], 2270 raw_tokens: list[Token], 2271 sql: str | None = None, 2272 ) -> list[exp.Expr | None]: 2273 self.reset() 2274 self.sql = sql or "" 2275 2276 total = len(raw_tokens) 2277 chunks: list[list[Token]] = [[]] 2278 2279 for i, token in enumerate(raw_tokens): 2280 if token.token_type == TokenType.SEMICOLON: 2281 if token.comments: 2282 chunks.append([token]) 2283 2284 if i < total - 1: 2285 chunks.append([]) 2286 else: 2287 chunks[-1].append(token) 2288 2289 self._chunks = chunks 2290 2291 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2292 2293 def _warn_unsupported(self) -> None: 2294 if self._tokens_size <= 1: 2295 return 2296 2297 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2298 # interested in emitting a warning for the one being currently processed. 2299 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2300 2301 logger.warning( 2302 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2303 ) 2304 2305 def _parse_command(self) -> exp.Command: 2306 self._warn_unsupported() 2307 comments = self._prev_comments 2308 return self.expression( 2309 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2310 comments=comments, 2311 ) 2312 2313 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2314 start = self._prev 2315 exists = self._parse_exists() if allow_exists else None 2316 2317 self._match(TokenType.ON) 2318 2319 materialized = self._match_text_seq("MATERIALIZED") 2320 kind = self._match_set(self.CREATABLES) and self._prev 2321 if not kind: 2322 return self._parse_as_command(start) 2323 2324 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2325 this = self._parse_user_defined_function(kind=kind.token_type) 2326 elif kind.token_type == TokenType.TABLE: 2327 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2328 elif kind.token_type == TokenType.COLUMN: 2329 this = self._parse_column() 2330 else: 2331 this = self._parse_table_parts(schema=True) 2332 2333 self._match(TokenType.IS) 2334 2335 return self.expression( 2336 exp.Comment( 2337 this=this, 2338 kind=kind.text, 2339 expression=self._parse_string(), 2340 exists=exists, 2341 materialized=materialized, 2342 ) 2343 ) 2344 2345 def _parse_to_table( 2346 self, 2347 ) -> exp.ToTableProperty: 2348 table = self._parse_table_parts(schema=True) 2349 return self.expression(exp.ToTableProperty(this=table)) 2350 2351 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2352 def _parse_ttl(self) -> exp.Expr: 2353 def _parse_ttl_action() -> exp.Expr | None: 2354 this = self._parse_bitwise() 2355 2356 if self._match_text_seq("DELETE"): 2357 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2358 if self._match_text_seq("RECOMPRESS"): 2359 return self.expression( 2360 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2361 ) 2362 if self._match_text_seq("TO", "DISK"): 2363 return self.expression( 2364 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2365 ) 2366 if self._match_text_seq("TO", "VOLUME"): 2367 return self.expression( 2368 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2369 ) 2370 2371 return this 2372 2373 expressions = self._parse_csv(_parse_ttl_action) 2374 where = self._parse_where() 2375 group = self._parse_group() 2376 2377 aggregates = None 2378 if group and self._match(TokenType.SET): 2379 aggregates = self._parse_csv(self._parse_set_item) 2380 2381 return self.expression( 2382 exp.MergeTreeTTL( 2383 expressions=expressions, where=where, group=group, aggregates=aggregates 2384 ) 2385 ) 2386 2387 def _parse_condition(self) -> exp.Expr | None: 2388 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2389 2390 def _parse_block(self) -> exp.Block: 2391 return self.expression( 2392 exp.Block( 2393 expressions=self._parse_batch_statements( 2394 parse_method=lambda self: self._parse_statement() 2395 ) 2396 ) 2397 ) 2398 2399 def _parse_whileblock(self) -> exp.WhileBlock: 2400 return self.expression( 2401 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2402 ) 2403 2404 def _parse_statement(self) -> exp.Expr | None: 2405 if not self._curr: 2406 return None 2407 2408 if self._match_set(self.STATEMENT_PARSERS): 2409 comments = self._prev_comments 2410 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2411 stmt.add_comments(comments, prepend=True) 2412 return stmt 2413 2414 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2415 return self._parse_command() 2416 2417 if self._match_text_seq("WHILE"): 2418 return self._parse_whileblock() 2419 2420 expression = self._parse_expression() 2421 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2422 2423 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2424 expression = self._parse_pipe_syntax_query(expression) 2425 2426 return self._parse_query_modifiers(expression) 2427 2428 def _parse_drop(self, exists: bool = False, kind: str | None = None) -> exp.Drop | exp.Command: 2429 start = self._prev 2430 2431 if not kind: 2432 temporary = self._match(TokenType.TEMPORARY) 2433 materialized = self._match_text_seq("MATERIALIZED") 2434 iceberg = self._match_text_seq("ICEBERG") 2435 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 2436 else: 2437 temporary = False 2438 materialized = False 2439 iceberg = False 2440 2441 if not kind or (iceberg and kind != "TABLE"): 2442 return self._parse_as_command(start) 2443 2444 concurrently = ( 2445 kind == "INDEX" 2446 and self._next.token_type in self.ID_VAR_TOKENS 2447 and self._match_text_seq("CONCURRENTLY") 2448 ) 2449 2450 if_exists = exists or self._parse_exists() 2451 2452 tables: exp.Expr | list[exp.Expr] | None 2453 if kind == "COLUMN": 2454 # A dropped column named after a keyword, like `any` or `charset`, is only a name 2455 name = ( 2456 None 2457 if self._curr.text.upper() in self.NO_PAREN_FUNCTION_PARSERS 2458 else self._parse_column() 2459 ) or self._parse_id_var() 2460 tables = ( 2461 self.expression(exp.Column(this=name)) if isinstance(name, exp.Identifier) else name 2462 ) 2463 elif kind in ("TABLE", "VIEW"): 2464 tables = self._parse_csv(lambda: self._parse_table_parts(schema=True)) 2465 else: 2466 tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2467 2468 cluster: exp.Expr | None = None 2469 if self._match(TokenType.ON): 2470 if kind == "INDEX": 2471 cluster = self.expression(exp.OnProperty(this=self._parse_table_parts())) 2472 else: 2473 cluster = self._parse_on_property() 2474 2475 if self._match(TokenType.L_PAREN, advance=False): 2476 expressions = self._parse_wrapped_csv(self._parse_types) 2477 else: 2478 expressions = None 2479 2480 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2481 2482 return self.expression( 2483 exp.Drop( 2484 exists=if_exists, 2485 tables=ensure_list(tables), 2486 expressions=expressions, 2487 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2488 temporary=temporary, 2489 materialized=materialized, 2490 cascade=cascade_or_restrict == "CASCADE", 2491 restrict=cascade_or_restrict == "RESTRICT", 2492 constraints=self._match_text_seq("CONSTRAINTS"), 2493 purge=self._match_text_seq("PURGE"), 2494 cluster=cluster, 2495 concurrently=concurrently, 2496 sync=self._match_text_seq("SYNC"), 2497 iceberg=iceberg, 2498 force=self._match_text_seq("FORCE"), 2499 ) 2500 ) 2501 2502 def _parse_exists(self, not_: bool = False) -> bool | None: 2503 if not_: 2504 return self._match_text_seq("IF", "NOT", "EXISTS") 2505 return self._match_text_seq("IF", "EXISTS") 2506 2507 def _parse_create(self) -> exp.Create | exp.Command: 2508 # Note: this can't be None because we've matched a statement parser 2509 start = self._prev 2510 2511 replace = ( 2512 start.token_type == TokenType.REPLACE 2513 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2514 or self._match_pair(TokenType.OR, TokenType.ALTER) 2515 ) 2516 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2517 2518 unique = self._match(TokenType.UNIQUE) 2519 2520 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2521 clustered = True 2522 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2523 "COLUMNSTORE" 2524 ): 2525 clustered = False 2526 else: 2527 clustered = None 2528 2529 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2530 self._advance() 2531 2532 properties = None 2533 create_token = self._match_set(self.CREATABLES) and self._prev 2534 2535 if not create_token: 2536 # exp.Properties.Location.POST_CREATE 2537 properties = self._parse_properties() 2538 create_token = self._match_set(self.CREATABLES) and self._prev 2539 2540 if not properties or not create_token: 2541 return self._parse_as_command(start) 2542 2543 create_token_type = t.cast(Token, create_token).token_type 2544 2545 concurrently = self._match_text_seq("CONCURRENTLY") 2546 exists = self._parse_exists(not_=True) 2547 this = None 2548 expression: exp.Expr | None = None 2549 indexes = None 2550 no_schema_binding = None 2551 begin = None 2552 clone = None 2553 2554 def extend_props(temp_props: exp.Properties | None) -> None: 2555 nonlocal properties 2556 if properties and temp_props: 2557 properties.expressions.extend(temp_props.expressions) 2558 elif temp_props: 2559 properties = temp_props 2560 2561 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2562 this = self._parse_user_defined_function(kind=create_token_type) 2563 2564 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2565 extend_props(self._parse_properties()) 2566 2567 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2568 2569 if ( 2570 not expression 2571 and create_token_type == TokenType.FUNCTION 2572 and isinstance(this, exp.UserDefinedFunction) 2573 and this.args.get("wrapped") 2574 ): 2575 pre_table_index = self._index 2576 is_table = self._match(TokenType.TABLE) 2577 2578 expression = self._parse_expression() 2579 overload_mode = bool( 2580 expression 2581 and self._curr.token_type == TokenType.COMMA 2582 and self._next.token_type == TokenType.L_PAREN 2583 ) 2584 if not overload_mode: 2585 self._retreat(pre_table_index) 2586 is_table = False 2587 expression = None 2588 else: 2589 is_table = False 2590 overload_mode = False 2591 2592 extend_props(self._parse_function_properties()) 2593 2594 if not expression: 2595 if self._match(TokenType.COMMAND): 2596 expression = self._parse_as_command(self._prev) 2597 else: 2598 begin = self._match(TokenType.BEGIN) 2599 return_ = self._match_text_seq("RETURN") 2600 2601 if self._match(TokenType.STRING, advance=False): 2602 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2603 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2604 expression = self._parse_string() 2605 extend_props(self._parse_properties()) 2606 else: 2607 expression = ( 2608 self._parse_user_defined_function_expression() 2609 if create_token_type == TokenType.FUNCTION 2610 else self._parse_block() 2611 ) 2612 2613 if return_: 2614 expression = self.expression(exp.Return(this=expression)) 2615 2616 if overload_mode and expression: 2617 expression = self._parse_macro_overloads( 2618 t.cast(exp.UserDefinedFunction, this), expression, is_table 2619 ) 2620 elif create_token_type == TokenType.INDEX: 2621 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2622 if not self._match(TokenType.ON): 2623 index = self._parse_id_var() 2624 anonymous = False 2625 else: 2626 index = None 2627 anonymous = True 2628 2629 this = self._parse_index(index=index, anonymous=anonymous) 2630 elif ( 2631 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2632 ) or create_token_type == TokenType.TRIGGER: 2633 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2634 create_token = self._prev 2635 2636 trigger_name = self._parse_id_var() 2637 if not trigger_name: 2638 return self._parse_as_command(start) 2639 2640 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2641 timing = timing_var.this if timing_var else None 2642 if not timing: 2643 return self._parse_as_command(start) 2644 2645 events = self._parse_trigger_events() 2646 if not self._match(TokenType.ON): 2647 self.raise_error("Expected ON in trigger definition") 2648 2649 table = self._parse_table_parts() 2650 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2651 deferrable, initially = self._parse_trigger_deferrable() 2652 referencing = self._parse_trigger_referencing() 2653 for_each = self._parse_trigger_for_each() 2654 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2655 self._parse_disjunction, optional=True 2656 ) 2657 execute = self._parse_trigger_execute() 2658 2659 if execute is None: 2660 return self._parse_as_command(start) 2661 2662 trigger_props = self.expression( 2663 exp.TriggerProperties( 2664 table=table, 2665 timing=timing, 2666 events=events, 2667 execute=execute, 2668 constraint=is_constraint, 2669 referenced_table=referenced_table, 2670 deferrable=deferrable, 2671 initially=initially, 2672 referencing=referencing, 2673 for_each=for_each, 2674 when=when, 2675 ) 2676 ) 2677 2678 this = trigger_name 2679 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2680 elif create_token_type == TokenType.TYPE: 2681 this = self._parse_table_parts(schema=True) 2682 if not this or not self._match(TokenType.ALIAS): 2683 return self._parse_as_command(start) 2684 2685 if self._match(TokenType.ENUM): 2686 expression = exp.DataType( 2687 this=exp.DType.ENUM, 2688 expressions=self._parse_wrapped_csv(self._parse_string), 2689 ) 2690 elif self._match(TokenType.L_PAREN, advance=False): 2691 expression = self._parse_schema() 2692 else: 2693 return self._parse_as_command(start) 2694 elif create_token_type in self.DB_CREATABLES: 2695 table_parts = self._parse_table_parts( 2696 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2697 ) 2698 2699 # exp.Properties.Location.POST_NAME 2700 self._match(TokenType.COMMA) 2701 extend_props(self._parse_properties(before=True)) 2702 2703 this = self._parse_schema(this=table_parts) 2704 2705 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2706 extend_props(self._parse_properties()) 2707 2708 has_alias = self._match(TokenType.ALIAS) 2709 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2710 # exp.Properties.Location.POST_ALIAS 2711 extend_props(self._parse_properties()) 2712 2713 if create_token_type == TokenType.SEQUENCE: 2714 expression = self._parse_types() 2715 props = self._parse_properties() 2716 if props: 2717 sequence_props = exp.SequenceProperties() 2718 options = [] 2719 for prop in props: 2720 if isinstance(prop, exp.SequenceProperties): 2721 for arg, value in prop.args.items(): 2722 if arg == "options": 2723 options.extend(value) 2724 else: 2725 sequence_props.set(arg, value) 2726 prop.pop() 2727 2728 if options: 2729 sequence_props.set("options", options) 2730 2731 props.append("expressions", sequence_props) 2732 extend_props(props) 2733 else: 2734 expression = self._parse_ddl_select() 2735 2736 # Some dialects also support using a table as an alias instead of a SELECT. 2737 # Here we fallback to this as an alternative. 2738 if not expression and has_alias: 2739 expression = self._try_parse(self._parse_table_parts) 2740 2741 if create_token_type == TokenType.TABLE: 2742 # exp.Properties.Location.POST_EXPRESSION 2743 extend_props(self._parse_properties()) 2744 2745 indexes = [] 2746 while True: 2747 index = self._parse_index() 2748 2749 # exp.Properties.Location.POST_INDEX 2750 extend_props(self._parse_properties()) 2751 if not index: 2752 break 2753 else: 2754 self._match(TokenType.COMMA) 2755 indexes.append(index) 2756 elif create_token_type == TokenType.VIEW: 2757 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2758 no_schema_binding = True 2759 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2760 extend_props(self._parse_properties()) 2761 2762 shallow = self._match_text_seq("SHALLOW") 2763 2764 if self._match_texts(self.CLONE_KEYWORDS): 2765 copy = self._prev.text.lower() == "copy" 2766 clone = self.expression( 2767 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2768 ) 2769 2770 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2771 return self._parse_as_command(start) 2772 2773 create_kind_text = create_token.text.upper() 2774 return self.expression( 2775 exp.Create( 2776 this=this, 2777 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2778 replace=replace, 2779 refresh=refresh, 2780 unique=unique, 2781 expression=expression, 2782 exists=exists, 2783 properties=properties, 2784 indexes=indexes, 2785 no_schema_binding=no_schema_binding, 2786 begin=begin, 2787 clone=clone, 2788 concurrently=concurrently, 2789 clustered=clustered, 2790 ) 2791 ) 2792 2793 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2794 seq = exp.SequenceProperties() 2795 2796 options = [] 2797 index = self._index 2798 2799 while self._curr: 2800 self._match(TokenType.COMMA) 2801 if self._match_text_seq("INCREMENT"): 2802 self._match_text_seq("BY") 2803 self._match_text_seq("=") 2804 seq.set("increment", self._parse_term()) 2805 elif self._match_text_seq("MINVALUE"): 2806 seq.set("minvalue", self._parse_term()) 2807 elif self._match_text_seq("MAXVALUE"): 2808 seq.set("maxvalue", self._parse_term()) 2809 elif self._match_text_seq("START"): 2810 self._match_text_seq("WITH") 2811 self._match_text_seq("=") 2812 seq.set("start", self._parse_term()) 2813 elif self._match_text_seq("CACHE"): 2814 # T-SQL allows empty CACHE which is initialized dynamically 2815 seq.set("cache", self._parse_number() or True) 2816 elif self._match_text_seq("OWNED", "BY"): 2817 # "OWNED BY NONE" is the default 2818 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2819 else: 2820 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2821 if opt: 2822 options.append(opt) 2823 else: 2824 break 2825 2826 seq.set("options", options if options else None) 2827 return None if self._index == index else seq 2828 2829 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2830 events = [] 2831 2832 while True: 2833 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2834 2835 if not event_type: 2836 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2837 2838 columns = ( 2839 self._parse_csv(self._parse_column) 2840 if event_type == "UPDATE" and self._match_text_seq("OF") 2841 else None 2842 ) 2843 2844 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2845 2846 if not self._match(TokenType.OR): 2847 break 2848 2849 return events 2850 2851 def _parse_trigger_deferrable( 2852 self, 2853 ) -> tuple[str | None, str | None]: 2854 deferrable_var = self._parse_var_from_options( 2855 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2856 ) 2857 deferrable = deferrable_var.this if deferrable_var else None 2858 2859 initially = None 2860 if deferrable and self._match_text_seq("INITIALLY"): 2861 initially = ( 2862 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2863 ) 2864 2865 return deferrable, initially 2866 2867 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2868 if not self._match_text_seq(keyword): 2869 return None 2870 if not self._match_text_seq("TABLE"): 2871 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2872 self._match_text_seq("AS") 2873 return self._parse_id_var() 2874 2875 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2876 if not self._match_text_seq("REFERENCING"): 2877 return None 2878 2879 old_alias = None 2880 new_alias = None 2881 2882 while True: 2883 if alias := self._parse_trigger_referencing_clause("OLD"): 2884 if old_alias is not None: 2885 self.raise_error("Duplicate OLD clause in REFERENCING") 2886 old_alias = alias 2887 elif alias := self._parse_trigger_referencing_clause("NEW"): 2888 if new_alias is not None: 2889 self.raise_error("Duplicate NEW clause in REFERENCING") 2890 new_alias = alias 2891 else: 2892 break 2893 2894 if old_alias is None and new_alias is None: 2895 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2896 2897 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2898 2899 def _parse_trigger_for_each(self) -> str | None: 2900 if not self._match_text_seq("FOR", "EACH"): 2901 return None 2902 2903 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2904 2905 def _parse_trigger_execute(self) -> exp.TriggerExecute | None: 2906 if not self._match(TokenType.EXECUTE): 2907 return None 2908 2909 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2910 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2911 2912 func_call = self._parse_column() 2913 return self.expression(exp.TriggerExecute(this=func_call)) 2914 2915 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2916 # only used for teradata currently 2917 self._match(TokenType.COMMA) 2918 2919 kwargs = { 2920 "no": self._match_text_seq("NO"), 2921 "dual": self._match_text_seq("DUAL"), 2922 "before": self._match_text_seq("BEFORE"), 2923 "default": self._match_text_seq("DEFAULT"), 2924 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2925 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2926 "after": self._match_text_seq("AFTER"), 2927 "minimum": self._match_texts(("MIN", "MINIMUM")), 2928 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2929 } 2930 2931 if self._match_texts(self.PROPERTY_PARSERS): 2932 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2933 try: 2934 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2935 except TypeError: 2936 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2937 2938 if self._match_pair(TokenType.CHAR, TokenType.SET): 2939 return self._parse_character_set(default=bool(kwargs["default"])) 2940 2941 return None 2942 2943 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2944 return self._parse_wrapped_csv(self._parse_property) 2945 2946 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2947 if self._match_texts(self.PROPERTY_PARSERS): 2948 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2949 2950 if self._match_pair(TokenType.CHAR, TokenType.SET): 2951 return self._parse_character_set() 2952 2953 if self._match(TokenType.DEFAULT): 2954 if self._match_texts(self.PROPERTY_PARSERS): 2955 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 2956 2957 if self._match_pair(TokenType.CHAR, TokenType.SET): 2958 return self._parse_character_set(default=True) 2959 2960 if self._match_text_seq("COMPOUND", "SORTKEY"): 2961 return self._parse_sortkey(compound=True) 2962 2963 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 2964 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 2965 2966 if self._match_text_seq("NOT", "DETERMINISTIC"): 2967 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 2968 2969 index = self._index 2970 2971 seq_props = self._parse_sequence_properties() 2972 if seq_props: 2973 return seq_props 2974 2975 self._retreat(index) 2976 return self._parse_key_value_property() 2977 2978 def _parse_key_value_property( 2979 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 2980 ) -> exp.Property | None: 2981 index = self._index 2982 key = self._parse_column() 2983 2984 if not self._match(TokenType.EQ): 2985 self._retreat(index) 2986 return None 2987 2988 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 2989 if isinstance(key, exp.Column): 2990 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 2991 2992 value = ( 2993 parse_value() 2994 if parse_value 2995 else self._parse_bitwise() or self._parse_var(any_token=True) 2996 ) 2997 2998 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 2999 if isinstance(value, exp.Column): 3000 value = exp.var(value.name) 3001 3002 return self.expression(exp.Property(this=key, value=value)) 3003 3004 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 3005 if self._match_text_seq("BY"): 3006 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 3007 3008 self._match(TokenType.ALIAS) 3009 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 3010 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 3011 3012 return self.expression( 3013 exp.FileFormatProperty( 3014 this=( 3015 self.expression( 3016 exp.InputOutputFormat( 3017 input_format=input_format, output_format=output_format 3018 ) 3019 ) 3020 if input_format or output_format 3021 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 3022 ), 3023 hive_format=True, 3024 ) 3025 ) 3026 3027 def _parse_unquoted_field(self) -> exp.Expr | None: 3028 field = self._parse_field() 3029 if isinstance(field, exp.Identifier) and not field.quoted: 3030 field = exp.var(field) 3031 3032 return field 3033 3034 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 3035 self._match(TokenType.EQ) 3036 self._match(TokenType.ALIAS) 3037 3038 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 3039 3040 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 3041 properties = [] 3042 while True: 3043 if before: 3044 prop = self._parse_property_before() 3045 else: 3046 prop = self._parse_property() 3047 if not prop: 3048 break 3049 for p in ensure_list(prop): 3050 properties.append(p) 3051 3052 self._match(TokenType.COMMA, expression=properties[-1]) 3053 3054 if properties: 3055 return self.expression(exp.Properties(expressions=properties)) 3056 3057 return None 3058 3059 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3060 return self.expression( 3061 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3062 ) 3063 3064 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3065 return self.expression( 3066 exp.SqlSecurityProperty( 3067 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3068 ) 3069 ) 3070 3071 def _parse_settings_property(self) -> exp.SettingsProperty: 3072 return self.expression( 3073 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3074 ) 3075 3076 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3077 if not self._match_text_seq("ON", "NULL", "INPUT"): 3078 self._retreat(self._index - 1) 3079 return None 3080 3081 return self.expression(exp.CalledOnNullInputProperty()) 3082 3083 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3084 if self._index >= 2: 3085 pre_volatile_token = self._tokens[self._index - 2] 3086 else: 3087 pre_volatile_token = None 3088 3089 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3090 return exp.VolatileProperty() 3091 3092 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3093 3094 def _parse_retention_period(self) -> exp.Var: 3095 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3096 number = self._parse_number() 3097 number_str = f"{number} " if number else "" 3098 unit = self._parse_var(any_token=True) 3099 return exp.var(f"{number_str}{unit}") 3100 3101 def _parse_system_versioning_property( 3102 self, with_: bool = False 3103 ) -> exp.WithSystemVersioningProperty: 3104 self._match(TokenType.EQ) 3105 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3106 3107 if self._match_text_seq("OFF"): 3108 prop.set("on", False) 3109 return prop 3110 3111 self._match(TokenType.ON) 3112 if self._match(TokenType.L_PAREN): 3113 while self._curr and not self._match(TokenType.R_PAREN): 3114 if self._match_text_seq("HISTORY_TABLE", "="): 3115 prop.set("this", self._parse_table_parts()) 3116 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3117 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3118 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3119 prop.set("retention_period", self._parse_retention_period()) 3120 3121 self._match(TokenType.COMMA) 3122 3123 return prop 3124 3125 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3126 self._match(TokenType.EQ) 3127 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3128 prop = self.expression(exp.DataDeletionProperty(on=on)) 3129 3130 if self._match(TokenType.L_PAREN): 3131 while self._curr and not self._match(TokenType.R_PAREN): 3132 if self._match_text_seq("FILTER_COLUMN", "="): 3133 prop.set("filter_column", self._parse_column()) 3134 elif self._match_text_seq("RETENTION_PERIOD", "="): 3135 prop.set("retention_period", self._parse_retention_period()) 3136 3137 self._match(TokenType.COMMA) 3138 3139 return prop 3140 3141 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3142 kind = "HASH" 3143 expressions: list[exp.Expr] | None = None 3144 if self._match_text_seq("BY", "HASH"): 3145 expressions = self._parse_wrapped_csv(self._parse_id_var) 3146 elif self._match_text_seq("BY", "RANDOM"): 3147 kind = "RANDOM" 3148 3149 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3150 buckets: exp.Expr | None = None 3151 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3152 buckets = self._parse_number() 3153 3154 return self.expression( 3155 exp.DistributedByProperty( 3156 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3157 ) 3158 ) 3159 3160 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3161 self._match_text_seq("KEY") 3162 expressions = self._parse_wrapped_id_vars() 3163 return self.expression(expr_type(expressions=expressions)) 3164 3165 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3166 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3167 prop = self._parse_system_versioning_property(with_=True) 3168 self._match_r_paren() 3169 return prop 3170 3171 if self._match(TokenType.L_PAREN, advance=False): 3172 result: list[exp.Expr] = [] 3173 for i in self._parse_wrapped_properties(): 3174 result.extend(i) if isinstance(i, list) else result.append(i) 3175 return result 3176 3177 if self._match_text_seq("JOURNAL"): 3178 return self._parse_withjournaltable() 3179 3180 if self._match_texts(self.VIEW_ATTRIBUTES): 3181 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3182 3183 if self._match_text_seq("DATA"): 3184 return self._parse_withdata(no=False) 3185 elif self._match_text_seq("NO", "DATA"): 3186 return self._parse_withdata(no=True) 3187 3188 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3189 return self._parse_serde_properties(with_=True) 3190 3191 if self._match(TokenType.SCHEMA): 3192 return self.expression( 3193 exp.WithSchemaBindingProperty( 3194 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3195 ) 3196 ) 3197 3198 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3199 return self.expression( 3200 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3201 ) 3202 3203 if not self._next: 3204 return None 3205 3206 return self._parse_withisolatedloading() 3207 3208 def _parse_procedure_option(self) -> exp.Expr | None: 3209 if self._match_text_seq("EXECUTE", "AS"): 3210 return self.expression( 3211 exp.ExecuteAsProperty( 3212 this=self._parse_var_from_options( 3213 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3214 ) 3215 or self._parse_string() 3216 ) 3217 ) 3218 3219 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3220 3221 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3222 def _parse_definer(self) -> exp.DefinerProperty | None: 3223 self._match(TokenType.EQ) 3224 3225 user = self._parse_id_var() 3226 self._match(TokenType.PARAMETER) 3227 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3228 3229 if not user or not host: 3230 return None 3231 3232 return exp.DefinerProperty(this=f"{user}@{host}") 3233 3234 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3235 self._match(TokenType.TABLE) 3236 self._match(TokenType.EQ) 3237 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3238 3239 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3240 return self.expression(exp.LogProperty(no=no)) 3241 3242 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3243 return self.expression(exp.JournalProperty(**kwargs)) 3244 3245 def _parse_checksum(self) -> exp.ChecksumProperty: 3246 self._match(TokenType.EQ) 3247 3248 on = None 3249 if self._match(TokenType.ON): 3250 on = True 3251 elif self._match_text_seq("OFF"): 3252 on = False 3253 3254 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3255 3256 def _parse_cluster(self) -> exp.Cluster: 3257 self._match(TokenType.CLUSTER_BY) 3258 return self.expression( 3259 exp.Cluster( 3260 expressions=self._parse_csv(self._parse_column), 3261 ) 3262 ) 3263 3264 def _parse_cluster_property(self) -> exp.ClusterProperty: 3265 return self.expression( 3266 exp.ClusterProperty( 3267 expressions=self._parse_wrapped_csv(self._parse_column), 3268 ) 3269 ) 3270 3271 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3272 self._match_text_seq("BY") 3273 3274 self._match_l_paren() 3275 expressions = self._parse_csv(self._parse_column) 3276 self._match_r_paren() 3277 3278 if self._match_text_seq("SORTED", "BY"): 3279 self._match_l_paren() 3280 sorted_by = self._parse_csv(self._parse_ordered) 3281 self._match_r_paren() 3282 else: 3283 sorted_by = None 3284 3285 self._match(TokenType.INTO) 3286 buckets = self._parse_number() 3287 self._match_text_seq("BUCKETS") 3288 3289 return self.expression( 3290 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3291 ) 3292 3293 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3294 if not self._match_text_seq("GRANTS"): 3295 self._retreat(self._index - 1) 3296 return None 3297 3298 return self.expression(exp.CopyGrantsProperty()) 3299 3300 def _parse_freespace(self) -> exp.FreespaceProperty: 3301 self._match(TokenType.EQ) 3302 return self.expression( 3303 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3304 ) 3305 3306 def _parse_mergeblockratio( 3307 self, no: bool = False, default: bool = False 3308 ) -> exp.MergeBlockRatioProperty: 3309 if self._match(TokenType.EQ): 3310 return self.expression( 3311 exp.MergeBlockRatioProperty( 3312 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3313 ) 3314 ) 3315 3316 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3317 3318 def _parse_datablocksize( 3319 self, 3320 default: bool | None = None, 3321 minimum: bool | None = None, 3322 maximum: bool | None = None, 3323 ) -> exp.DataBlocksizeProperty: 3324 self._match(TokenType.EQ) 3325 size = self._parse_number() 3326 3327 units = None 3328 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3329 units = self._prev.text 3330 3331 return self.expression( 3332 exp.DataBlocksizeProperty( 3333 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3334 ) 3335 ) 3336 3337 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3338 self._match(TokenType.EQ) 3339 always = self._match_text_seq("ALWAYS") 3340 manual = self._match_text_seq("MANUAL") 3341 never = self._match_text_seq("NEVER") 3342 default = self._match_text_seq("DEFAULT") 3343 3344 autotemp = None 3345 if self._match_text_seq("AUTOTEMP"): 3346 autotemp = self._parse_schema() 3347 3348 return self.expression( 3349 exp.BlockCompressionProperty( 3350 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3351 ) 3352 ) 3353 3354 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3355 index = self._index 3356 no = self._match_text_seq("NO") 3357 concurrent = self._match_text_seq("CONCURRENT") 3358 3359 if not self._match_text_seq("ISOLATED", "LOADING"): 3360 self._retreat(index) 3361 return None 3362 3363 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3364 return self.expression( 3365 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3366 ) 3367 3368 def _parse_locking(self) -> exp.LockingProperty: 3369 if self._match(TokenType.TABLE): 3370 kind = "TABLE" 3371 elif self._match(TokenType.VIEW): 3372 kind = "VIEW" 3373 elif self._match(TokenType.ROW): 3374 kind = "ROW" 3375 elif self._match_text_seq("DATABASE"): 3376 kind = "DATABASE" 3377 else: 3378 kind = None 3379 3380 if kind in ("DATABASE", "TABLE", "VIEW"): 3381 this = self._parse_table_parts() 3382 else: 3383 this = None 3384 3385 if self._match(TokenType.FOR): 3386 for_or_in = "FOR" 3387 elif self._match(TokenType.IN): 3388 for_or_in = "IN" 3389 else: 3390 for_or_in = None 3391 3392 if self._match_text_seq("ACCESS"): 3393 lock_type = "ACCESS" 3394 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3395 lock_type = "EXCLUSIVE" 3396 elif self._match_text_seq("SHARE"): 3397 lock_type = "SHARE" 3398 elif self._match_text_seq("READ"): 3399 lock_type = "READ" 3400 elif self._match_text_seq("WRITE"): 3401 lock_type = "WRITE" 3402 elif self._match_text_seq("CHECKSUM"): 3403 lock_type = "CHECKSUM" 3404 else: 3405 lock_type = None 3406 3407 override = self._match_text_seq("OVERRIDE") 3408 3409 return self.expression( 3410 exp.LockingProperty( 3411 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3412 ) 3413 ) 3414 3415 def _parse_partition_by(self) -> list[exp.Expr]: 3416 if self._match(TokenType.PARTITION_BY): 3417 return self._parse_csv(self._parse_disjunction) 3418 return [] 3419 3420 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3421 def _parse_partition_bound_expr() -> exp.Expr | None: 3422 if self._match_text_seq("MINVALUE"): 3423 return exp.var("MINVALUE") 3424 if self._match_text_seq("MAXVALUE"): 3425 return exp.var("MAXVALUE") 3426 return self._parse_bitwise() 3427 3428 this: exp.Expr | list[exp.Expr] | None = None 3429 expression = None 3430 from_expressions = None 3431 to_expressions = None 3432 3433 if self._match(TokenType.IN): 3434 this = self._parse_wrapped_csv(self._parse_bitwise) 3435 elif self._match(TokenType.FROM): 3436 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3437 self._match_text_seq("TO") 3438 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3439 elif self._match_text_seq("WITH", "(", "MODULUS"): 3440 this = self._parse_number() 3441 self._match_text_seq(",", "REMAINDER") 3442 expression = self._parse_number() 3443 self._match_r_paren() 3444 else: 3445 self.raise_error("Failed to parse partition bound spec.") 3446 3447 return self.expression( 3448 exp.PartitionBoundSpec( 3449 this=this, 3450 expression=expression, 3451 from_expressions=from_expressions, 3452 to_expressions=to_expressions, 3453 ) 3454 ) 3455 3456 # https://www.postgresql.org/docs/current/sql-createtable.html 3457 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3458 if not self._match_text_seq("OF"): 3459 self._retreat(self._index - 1) 3460 return None 3461 3462 this = self._parse_table(schema=True) 3463 3464 if self._match(TokenType.DEFAULT): 3465 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3466 elif self._match_text_seq("FOR", "VALUES"): 3467 expression = self._parse_partition_bound_spec() 3468 else: 3469 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3470 3471 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3472 3473 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3474 self._match(TokenType.EQ) 3475 return self.expression( 3476 exp.PartitionedByProperty( 3477 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3478 ) 3479 ) 3480 3481 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3482 if self._match_text_seq("AND", "STATISTICS"): 3483 statistics = True 3484 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3485 statistics = False 3486 else: 3487 statistics = None 3488 3489 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3490 3491 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3492 if self._match_text_seq("SQL"): 3493 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3494 return None 3495 3496 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3497 if self._match_text_seq("SQL", "DATA"): 3498 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3499 return None 3500 3501 def _parse_no_property(self) -> exp.Expr | None: 3502 if self._match_text_seq("PRIMARY", "INDEX"): 3503 return exp.NoPrimaryIndexProperty() 3504 if self._match_text_seq("SQL"): 3505 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3506 return None 3507 3508 def _parse_on_property(self) -> exp.Expr | None: 3509 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3510 return exp.OnCommitProperty() 3511 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3512 return exp.OnCommitProperty(delete=True) 3513 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3514 3515 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3516 if self._match_text_seq("SQL", "DATA"): 3517 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3518 return None 3519 3520 def _parse_distkey(self) -> exp.DistKeyProperty: 3521 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3522 3523 def _parse_create_like(self) -> exp.LikeProperty | None: 3524 table = self._parse_table(schema=True) 3525 3526 options = [] 3527 while self._match_texts(("INCLUDING", "EXCLUDING")): 3528 this = self._prev.text.upper() 3529 3530 id_var = self._parse_id_var() 3531 if not id_var: 3532 return None 3533 3534 options.append( 3535 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3536 ) 3537 3538 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3539 3540 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3541 return self.expression( 3542 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3543 ) 3544 3545 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3546 self._match(TokenType.EQ) 3547 return self.expression( 3548 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3549 ) 3550 3551 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3552 self._match_text_seq("WITH", "CONNECTION") 3553 return self.expression( 3554 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3555 ) 3556 3557 def _parse_returns(self) -> exp.ReturnsProperty: 3558 value: exp.Expr | None 3559 null = None 3560 is_table = self._match(TokenType.TABLE) 3561 3562 if is_table: 3563 if self._match(TokenType.LT): 3564 value = self.expression( 3565 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3566 ) 3567 if not self._match(TokenType.GT): 3568 self.raise_error("Expecting >") 3569 else: 3570 value = self._parse_schema(exp.var("TABLE")) 3571 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3572 null = True 3573 value = None 3574 else: 3575 value = self._parse_types() 3576 3577 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3578 3579 def _parse_describe(self) -> exp.Describe: 3580 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3581 style: str | None = ( 3582 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3583 ) 3584 if self._match(TokenType.DOT): 3585 style = None 3586 self._retreat(self._index - 2) 3587 3588 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3589 3590 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3591 this = self._parse_statement() 3592 else: 3593 this = self._parse_table(schema=True) 3594 3595 properties = self._parse_properties() 3596 expressions = properties.expressions if properties else None 3597 partition = self._parse_partition() 3598 return self.expression( 3599 exp.Describe( 3600 this=this, 3601 style=style, 3602 kind=kind, 3603 expressions=expressions, 3604 partition=partition, 3605 format=format, 3606 as_json=self._match_text_seq("AS", "JSON"), 3607 ) 3608 ) 3609 3610 def _parse_multitable_inserts( 3611 self, comments: list[str] | None, kind: str | None = None 3612 ) -> exp.MultitableInserts: 3613 kind = kind or self._prev.text.upper() 3614 expressions = [] 3615 3616 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3617 if self._match(TokenType.WHEN): 3618 expression = self._parse_disjunction() 3619 self._match(TokenType.THEN) 3620 else: 3621 expression = None 3622 3623 else_ = self._match(TokenType.ELSE) 3624 3625 if not self._match(TokenType.INTO): 3626 return None 3627 3628 return self.expression( 3629 exp.ConditionalInsert( 3630 this=self.expression( 3631 exp.Insert( 3632 this=self._parse_table(schema=True), 3633 expression=self._parse_derived_table_values(), 3634 ) 3635 ), 3636 expression=expression, 3637 else_=else_, 3638 ) 3639 ) 3640 3641 expression = parse_conditional_insert() 3642 while expression is not None: 3643 expressions.append(expression) 3644 expression = parse_conditional_insert() 3645 3646 return self.expression( 3647 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3648 comments=comments, 3649 ) 3650 3651 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3652 comments: list[str] = [] 3653 hint = self._parse_hint() 3654 overwrite = self._match(TokenType.OVERWRITE) 3655 ignore = self._match(TokenType.IGNORE) 3656 local = self._match_text_seq("LOCAL") 3657 alternative = None 3658 is_function = None 3659 3660 if self._match_text_seq("DIRECTORY"): 3661 this: exp.Expr | None = self.expression( 3662 exp.Directory( 3663 this=self._parse_var_or_string(), 3664 local=local, 3665 row_format=self._parse_row_format(match_row=True), 3666 ) 3667 ) 3668 else: 3669 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3670 comments += ensure_list(self._prev_comments) 3671 return self._parse_multitable_inserts(comments) 3672 3673 # Oracle's conditional form defaults to ALL when the keyword is omitted 3674 if self._match(TokenType.WHEN, advance=False): 3675 return self._parse_multitable_inserts(comments, kind="ALL") 3676 3677 if self._match(TokenType.OR): 3678 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3679 3680 self._match(TokenType.INTO) 3681 comments += ensure_list(self._prev_comments) 3682 self._match(TokenType.TABLE) 3683 is_function = self._match(TokenType.FUNCTION) 3684 3685 this = self._parse_function() if is_function else self._parse_insert_table() 3686 3687 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3688 set_values = None 3689 if self._match(TokenType.SET): 3690 columns = [] 3691 values = [] 3692 3693 def _parse_set_assignment() -> exp.Expr | None: 3694 target = self._parse_column() 3695 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3696 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3697 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3698 else: 3699 value = self._parse_disjunction() 3700 3701 if value: 3702 columns.append(target.this) 3703 values.append(value) 3704 return value 3705 3706 self.raise_error("Expected column assignment in INSERT ... SET") 3707 return None 3708 3709 self._parse_csv(_parse_set_assignment) 3710 3711 this = self.expression(exp.Schema(this=this, expressions=columns)) 3712 set_values = self.expression( 3713 exp.Values( 3714 expressions=[exp.Tuple(expressions=values)], 3715 alias=self._parse_table_alias(), 3716 ) 3717 ) 3718 3719 returning = self._parse_returning() # TSQL allows RETURNING before source 3720 3721 stored = self._match_text_seq("STORED") and self._parse_stored() 3722 by_name = self._match_text_seq("BY", "NAME") 3723 exists = self._parse_exists() 3724 replace_where = None 3725 replace_using = None 3726 3727 if self._match(TokenType.REPLACE): 3728 if self._match(TokenType.WHERE): 3729 replace_where = self._parse_disjunction() 3730 elif self._match(TokenType.USING): 3731 replace_using = self._parse_using_identifiers() 3732 3733 return self.expression( 3734 exp.Insert( 3735 hint=hint, 3736 is_function=is_function, 3737 this=this, 3738 stored=stored, 3739 by_name=by_name, 3740 exists=exists, 3741 where=replace_where, 3742 using=replace_using, 3743 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3744 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3745 default=self._match_text_seq("DEFAULT", "VALUES"), 3746 expression=set_values 3747 or self._parse_derived_table_values(allow_value_synonym=True) 3748 or self._parse_ddl_select(), 3749 conflict=self._parse_on_conflict(), 3750 returning=returning or self._parse_returning(), 3751 overwrite=overwrite, 3752 alternative=alternative, 3753 ignore=ignore, 3754 source=self._match(TokenType.TABLE) and self._parse_table(), 3755 ), 3756 comments=comments, 3757 ) 3758 3759 def _parse_insert_table(self) -> exp.Expr | None: 3760 this = self._parse_table(schema=True, parse_partition=True) 3761 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3762 this.set("alias", self._parse_table_alias()) 3763 return this 3764 3765 def _parse_kill(self) -> exp.Kill: 3766 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3767 3768 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3769 3770 def _parse_on_conflict(self) -> exp.OnConflict | None: 3771 conflict = self._match_text_seq("ON", "CONFLICT") 3772 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3773 3774 if not conflict and not duplicate: 3775 return None 3776 3777 conflict_keys = None 3778 constraint = None 3779 3780 if conflict: 3781 if self._match_text_seq("ON", "CONSTRAINT"): 3782 constraint = self._parse_id_var() 3783 elif self._match(TokenType.L_PAREN): 3784 conflict_keys = self._parse_csv(self._parse_indexed_column) 3785 self._match_r_paren() 3786 3787 index_predicate = self._parse_where() 3788 3789 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3790 if self._prev.token_type == TokenType.UPDATE: 3791 self._match(TokenType.SET) 3792 expressions = self._parse_csv(self._parse_update_assignment) 3793 else: 3794 expressions = None 3795 3796 return self.expression( 3797 exp.OnConflict( 3798 duplicate=duplicate, 3799 expressions=expressions, 3800 action=action, 3801 conflict_keys=conflict_keys, 3802 index_predicate=index_predicate, 3803 constraint=constraint, 3804 where=self._parse_where(), 3805 ) 3806 ) 3807 3808 def _parse_returning(self) -> exp.Returning | None: 3809 if not self._match(TokenType.RETURNING): 3810 return None 3811 return self.expression( 3812 exp.Returning( 3813 expressions=self._parse_csv(self._parse_expression), 3814 into=self._match(TokenType.INTO) and self._parse_table_part(), 3815 ) 3816 ) 3817 3818 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3819 if not self._match(TokenType.FORMAT): 3820 return None 3821 return self._parse_row_format() 3822 3823 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3824 index = self._index 3825 with_ = with_ or self._match_text_seq("WITH") 3826 3827 if not self._match(TokenType.SERDE_PROPERTIES): 3828 self._retreat(index) 3829 return None 3830 return self.expression( 3831 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3832 ) 3833 3834 def _parse_row_format( 3835 self, match_row: bool = False 3836 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3837 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3838 return None 3839 3840 if self._match_text_seq("SERDE"): 3841 this = self._parse_string() 3842 3843 serde_properties = self._parse_serde_properties() 3844 3845 return self.expression( 3846 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3847 ) 3848 3849 self._match_text_seq("DELIMITED") 3850 3851 kwargs = {} 3852 3853 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3854 kwargs["fields"] = self._parse_string() 3855 if self._match_text_seq("ESCAPED", "BY"): 3856 kwargs["escaped"] = self._parse_string() 3857 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3858 kwargs["collection_items"] = self._parse_string() 3859 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3860 kwargs["map_keys"] = self._parse_string() 3861 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3862 kwargs["lines"] = self._parse_string() 3863 if self._match_text_seq("NULL", "DEFINED", "AS"): 3864 kwargs["null"] = self._parse_string() 3865 3866 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3867 3868 def _parse_load(self) -> exp.LoadData | exp.Command: 3869 if self._match_text_seq("DATA"): 3870 local = self._match_text_seq("LOCAL") 3871 self._match_text_seq("INPATH") 3872 inpath = self._parse_string() 3873 overwrite = self._match(TokenType.OVERWRITE) 3874 temp: bool | None = None 3875 if self._match(TokenType.INTO): 3876 temp = self._match(TokenType.TEMPORARY) 3877 self._match(TokenType.TABLE) 3878 3879 return self.expression( 3880 exp.LoadData( 3881 this=self._parse_table(schema=True), 3882 local=local, 3883 overwrite=overwrite, 3884 temp=temp, 3885 inpath=inpath, 3886 files=self._match_text_seq("FROM", "FILES") 3887 and exp.Properties(expressions=self._parse_wrapped_properties()), 3888 partition=self._parse_partition(), 3889 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3890 serde=self._match_text_seq("SERDE") and self._parse_string(), 3891 ) 3892 ) 3893 return self._parse_as_command(self._prev) 3894 3895 def _parse_delete(self) -> exp.Delete: 3896 hint = self._parse_hint() 3897 3898 # This handles MySQL's "Multiple-Table Syntax" 3899 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3900 tables = None 3901 if not self._match(TokenType.FROM, advance=False): 3902 tables = self._parse_csv(self._parse_table) or None 3903 3904 returning = self._parse_returning() 3905 3906 return self.expression( 3907 exp.Delete( 3908 hint=hint, 3909 tables=tables, 3910 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3911 using=self._match(TokenType.USING) 3912 and self._parse_csv(lambda: self._parse_table(joins=True)), 3913 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3914 where=self._parse_where(), 3915 returning=returning or self._parse_returning(), 3916 order=self._parse_order(), 3917 limit=self._parse_limit(), 3918 ) 3919 ) 3920 3921 def _parse_update_assignment(self) -> exp.Expr | None: 3922 this = self._parse_comparison() 3923 if self._match(TokenType.EQ): 3924 comments = self._prev_comments 3925 this = self.expression( 3926 exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments 3927 ) 3928 return this 3929 3930 def _parse_update(self) -> exp.Update: 3931 hint = self._parse_hint() 3932 kwargs: dict[str, object] = { 3933 "hint": hint, 3934 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3935 } 3936 while self._curr: 3937 if self._match(TokenType.SET): 3938 kwargs["expressions"] = self._parse_csv(self._parse_update_assignment) 3939 elif self._match(TokenType.RETURNING, advance=False): 3940 kwargs["returning"] = self._parse_returning() 3941 elif self._match(TokenType.FROM, advance=False): 3942 from_ = self._parse_from(joins=True) 3943 table = from_.this if from_ else None 3944 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3945 table.set("joins", list(self._parse_joins()) or None) 3946 3947 kwargs["from_"] = from_ 3948 elif self._match(TokenType.WHERE, advance=False): 3949 kwargs["where"] = self._parse_where() 3950 elif self._match(TokenType.ORDER_BY, advance=False): 3951 kwargs["order"] = self._parse_order() 3952 elif self._match(TokenType.LIMIT, advance=False): 3953 kwargs["limit"] = self._parse_limit() 3954 else: 3955 break 3956 3957 return self.expression(exp.Update(**kwargs)) 3958 3959 def _parse_use(self) -> exp.Use: 3960 return self.expression( 3961 exp.Use( 3962 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 3963 this=self._parse_table(schema=False), 3964 ) 3965 ) 3966 3967 def _parse_uncache(self) -> exp.Uncache: 3968 if not self._match(TokenType.TABLE): 3969 self.raise_error("Expecting TABLE after UNCACHE") 3970 3971 return self.expression( 3972 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 3973 ) 3974 3975 def _parse_cache(self) -> exp.Cache: 3976 lazy = self._match_text_seq("LAZY") 3977 self._match(TokenType.TABLE) 3978 table = self._parse_table(schema=True) 3979 3980 options = [] 3981 if self._match_text_seq("OPTIONS"): 3982 self._match_l_paren() 3983 k = self._parse_string() 3984 self._match(TokenType.EQ) 3985 v = self._parse_string() 3986 options = [k, v] 3987 self._match_r_paren() 3988 3989 self._match(TokenType.ALIAS) 3990 return self.expression( 3991 exp.Cache( 3992 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 3993 ) 3994 ) 3995 3996 def _parse_partition(self) -> exp.Partition | None: 3997 if not self._match_texts(self.PARTITION_KEYWORDS): 3998 return None 3999 4000 return self.expression( 4001 exp.Partition( 4002 subpartition=self._prev.text.upper() == "SUBPARTITION", 4003 expressions=self._parse_wrapped_csv(self._parse_disjunction), 4004 ) 4005 ) 4006 4007 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 4008 def _parse_value_expression() -> exp.Expr | None: 4009 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 4010 return exp.var(self._prev.text.upper()) 4011 return self._parse_expression() 4012 4013 if self._match(TokenType.L_PAREN): 4014 expressions = self._parse_csv(_parse_value_expression) 4015 self._match_r_paren() 4016 return self.expression(exp.Tuple(expressions=expressions)) 4017 4018 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 4019 expression = self._parse_expression() 4020 if expression: 4021 return self.expression(exp.Tuple(expressions=[expression])) 4022 return None 4023 4024 def _parse_projections( 4025 self, 4026 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 4027 return self._parse_expressions(), None 4028 4029 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 4030 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 4031 this: exp.Expr | None = self._parse_simplified_pivot( 4032 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 4033 ) 4034 else: 4035 # Support parentheses for duckdb FROM-first syntax 4036 this = ( 4037 self._parse_table(consume_pipe=True) 4038 if table and not self._match(TokenType.FROM, advance=False) 4039 else self._parse_select(nested=True, parse_set_operation=False) 4040 ) 4041 4042 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 4043 # in case a modifier (e.g. join) is following 4044 if table and isinstance(this, exp.Values) and this.alias: 4045 alias = this.args["alias"].pop() 4046 this = exp.Table(this=this, alias=alias) 4047 4048 this = self._parse_query_modifiers(self._parse_set_operations(this)) 4049 4050 return this 4051 4052 def _parse_select( 4053 self, 4054 nested: bool = False, 4055 table: bool = False, 4056 parse_subquery_alias: bool = True, 4057 parse_set_operation: bool = True, 4058 consume_pipe: bool = True, 4059 ) -> exp.Expr | None: 4060 query = self._parse_select_query( 4061 nested=nested, 4062 table=table, 4063 parse_subquery_alias=parse_subquery_alias, 4064 parse_set_operation=parse_set_operation, 4065 ) 4066 4067 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4068 if isinstance(query, exp.Query): 4069 query = self._parse_pipe_syntax_query(query) 4070 query = query.subquery(copy=False) if query and table else query 4071 4072 return query 4073 4074 def _parse_select_query( 4075 self, 4076 nested: bool = False, 4077 table: bool = False, 4078 parse_subquery_alias: bool = True, 4079 parse_set_operation: bool = True, 4080 ) -> exp.Expr | None: 4081 cte = self._parse_with() 4082 4083 if cte: 4084 this = self._parse_statement() 4085 4086 if not this: 4087 self.raise_error("Failed to parse any statement following CTE") 4088 return cte 4089 4090 while isinstance(this, exp.Subquery) and this.is_wrapper: 4091 this = this.this 4092 4093 assert this is not None 4094 if "with_" in this.arg_types: 4095 if inner_cte := this.args.get("with_"): 4096 cte.set("expressions", cte.expressions + inner_cte.expressions) 4097 if inner_cte.args.get("recursive"): 4098 cte.set("recursive", True) 4099 this.set("with_", cte) 4100 else: 4101 self.raise_error(f"{this.key} does not support CTE") 4102 this = cte 4103 4104 return this 4105 4106 # duckdb supports leading with FROM x 4107 from_ = ( 4108 self._parse_from(joins=True, consume_pipe=True) 4109 if self._match(TokenType.FROM, advance=False) 4110 else None 4111 ) 4112 laterals = list(iter(self._parse_lateral, None)) if from_ else None 4113 4114 # Hive supports multi-table inserts: FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 4115 if from_ and self._match(TokenType.INSERT, advance=False): 4116 source = from_.this 4117 source.set("laterals", laterals or None) 4118 4119 inserts = [] 4120 while self._match(TokenType.INSERT, advance=False): 4121 inserts.append(self._parse_statement()) 4122 4123 return self.expression( 4124 exp.MultitableInserts(expressions=inserts, source=source), 4125 comments=from_.comments, 4126 ) 4127 4128 if self._match(TokenType.SELECT): 4129 comments = self._prev_comments 4130 4131 hint = self._parse_hint() 4132 4133 if self._next and not self._next.token_type == TokenType.DOT: 4134 all_ = self._match(TokenType.ALL) 4135 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4136 else: 4137 all_, matched_distinct = None, False 4138 4139 kind = ( 4140 self._prev.text.upper() 4141 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4142 else None 4143 ) 4144 4145 distinct: exp.Expr | None = ( 4146 self.expression( 4147 exp.Distinct( 4148 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4149 ) 4150 ) 4151 if matched_distinct 4152 else None 4153 ) 4154 4155 operation_modifiers = [] 4156 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4157 operation_modifiers.append(exp.var(self._prev.text.upper())) 4158 4159 limit = self._parse_limit(top=True) 4160 4161 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4162 if limit and not matched_distinct and not all_: 4163 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4164 if matched_distinct: 4165 distinct = self.expression( 4166 exp.Distinct( 4167 on=self._parse_value(values=False) 4168 if self._match(TokenType.ON) 4169 else None 4170 ) 4171 ) 4172 else: 4173 all_ = self._match(TokenType.ALL) 4174 4175 if all_ and distinct: 4176 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4177 4178 projections, exclude = self._parse_projections() 4179 4180 this = self.expression( 4181 exp.Select( 4182 kind=kind, 4183 hint=hint, 4184 distinct=distinct, 4185 expressions=projections, 4186 limit=limit, 4187 exclude=exclude, 4188 operation_modifiers=operation_modifiers or None, 4189 ) 4190 ) 4191 this.comments = comments 4192 4193 into = self._parse_into() 4194 if into: 4195 this.set("into", into) 4196 4197 if not from_: 4198 from_ = self._parse_from() 4199 4200 if from_: 4201 this.set("from_", from_) 4202 4203 this = self._parse_query_modifiers(this) 4204 elif (table or nested) and self._match(TokenType.L_PAREN): 4205 comments = self._prev_comments 4206 this = self._parse_wrapped_select(table=table) 4207 4208 if this: 4209 this.add_comments(comments, prepend=True) 4210 4211 # We return early here so that the UNION isn't attached to the subquery by the 4212 # following call to _parse_set_operations, but instead becomes the parent node 4213 self._match_r_paren() 4214 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4215 elif self._match(TokenType.VALUES, advance=False): 4216 this = self._parse_derived_table_values() 4217 elif from_: 4218 this = exp.select("*").from_(from_.this, copy=False) 4219 this = self._parse_query_modifiers(this) 4220 elif self._match(TokenType.SUMMARIZE): 4221 table = self._match(TokenType.TABLE) 4222 this = self._parse_select() or self._parse_string() or self._parse_table() 4223 return self.expression(exp.Summarize(this=this, table=table)) 4224 elif self._match(TokenType.DESCRIBE): 4225 this = self._parse_describe() 4226 else: 4227 this = None 4228 4229 if laterals and isinstance(this, exp.Select): 4230 this.set("laterals", [*laterals, *(this.args.get("laterals") or [])]) 4231 4232 return self._parse_set_operations(this) if parse_set_operation else this 4233 4234 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4235 self._match_text_seq("SEARCH") 4236 4237 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4238 4239 if not kind: 4240 return None 4241 4242 self._match_text_seq("FIRST", "BY") 4243 4244 return self.expression( 4245 exp.RecursiveWithSearch( 4246 kind=kind, 4247 this=self._parse_id_var(), 4248 expression=self._match_text_seq("SET") and self._parse_id_var(), 4249 to=self._match_text_seq("TO") and self._parse_bitwise(), 4250 default=self._match(TokenType.DEFAULT) and self._parse_bitwise(), 4251 using=self._match_text_seq("USING") and self._parse_id_var(), 4252 ) 4253 ) 4254 4255 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4256 if not skip_with_token and not self._match(TokenType.WITH): 4257 return None 4258 4259 comments = self._prev_comments 4260 recursive = self._match(TokenType.RECURSIVE) 4261 4262 last_comments = None 4263 expressions = [] 4264 udfs = [] 4265 while True: 4266 cte = self._parse_cte() 4267 if cte: 4268 if isinstance(cte, exp.FunctionSpecification): 4269 udfs.append(cte) 4270 else: 4271 expressions.append(cte) 4272 4273 if last_comments: 4274 cte.add_comments(last_comments) 4275 4276 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4277 break 4278 else: 4279 self._match(TokenType.WITH) 4280 recursive = self._match(TokenType.RECURSIVE) or recursive 4281 4282 last_comments = self._prev_comments 4283 4284 search = self._parse_recursive_with_search() 4285 4286 # A SEARCH clause can be followed by a CYCLE clause 4287 cycle = ( 4288 self._parse_recursive_with_search() 4289 if search 4290 and search.args.get("kind") != "CYCLE" 4291 and self._match_text_seq("CYCLE", advance=False) 4292 else None 4293 ) 4294 4295 return self.expression( 4296 exp.With( 4297 expressions=expressions, 4298 recursive=recursive or None, 4299 search=search, 4300 cycle=cycle, 4301 udfs=udfs or None, 4302 ), 4303 comments=comments, 4304 ) 4305 4306 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4307 index = self._index 4308 4309 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4310 if not alias or not alias.this: 4311 self.raise_error("Expected CTE to have alias") 4312 4313 key_expressions = ( 4314 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4315 ) 4316 4317 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4318 self._retreat(index) 4319 return None 4320 4321 comments = self._prev_comments 4322 4323 if self._match_text_seq("NOT", "MATERIALIZED"): 4324 materialized = False 4325 elif self._match_text_seq("MATERIALIZED"): 4326 materialized = True 4327 else: 4328 materialized = None 4329 4330 cte = self.expression( 4331 exp.CTE( 4332 this=self._parse_wrapped(self._parse_statement), 4333 alias=alias, 4334 materialized=materialized, 4335 key_expressions=key_expressions, 4336 ), 4337 comments=comments, 4338 ) 4339 4340 values = cte.this 4341 if isinstance(values, exp.Values): 4342 cte.set("this", self._values_to_select(values)) 4343 4344 return cte 4345 4346 def _values_to_select(self, values: exp.Values) -> exp.Select: 4347 if values.alias: 4348 return exp.select("*").from_(values) 4349 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4350 4351 def _parse_table_alias( 4352 self, alias_tokens: t.Collection[TokenType] | None = None 4353 ) -> exp.TableAlias | None: 4354 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4355 # so this section tries to parse the clause version and if it fails, it treats the token 4356 # as an identifier (alias) 4357 if self._can_parse_limit_or_offset(): 4358 return None 4359 4360 # START is never treated as an implicit alias when followed by WITH, since that 4361 # would swallow the beginning of a START WITH ... CONNECT BY clause 4362 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4363 return None 4364 4365 any_token = self._match(TokenType.ALIAS) 4366 alias = ( 4367 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4368 or self._parse_string_as_identifier() 4369 ) 4370 4371 index = self._index 4372 if self._match(TokenType.L_PAREN): 4373 columns = self._parse_csv(self._parse_function_parameter) 4374 self._match_r_paren() if columns else self._retreat(index) 4375 else: 4376 columns = None 4377 4378 if not alias and not columns: 4379 return None 4380 4381 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4382 4383 # We bubble up comments from the Identifier to the TableAlias 4384 if isinstance(alias, exp.Identifier): 4385 table_alias.add_comments(alias.pop_comments()) 4386 4387 return table_alias 4388 4389 def _parse_subquery( 4390 self, this: exp.Expr | None, parse_alias: bool = True 4391 ) -> exp.Subquery | None: 4392 if not this: 4393 return None 4394 4395 return self.expression( 4396 exp.Subquery( 4397 this=this, 4398 pivots=self._parse_pivots(), 4399 alias=self._parse_table_alias() if parse_alias else None, 4400 sample=self._parse_table_sample(), 4401 ) 4402 ) 4403 4404 def _implicit_unnests_to_explicit(self, this: E) -> E: 4405 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4406 4407 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4408 for i, join in enumerate(this.args.get("joins") or []): 4409 table = join.this 4410 normalized_table = table.copy() 4411 normalized_table.meta["maybe_column"] = True 4412 normalized_table = _norm(normalized_table, dialect=self.dialect) 4413 4414 if isinstance(table, exp.Table) and not join.args.get("on"): 4415 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4416 table_as_column = table.to_column() 4417 unnest = exp.Unnest(expressions=[table_as_column]) 4418 4419 # Table.to_column creates a parent Alias node that we want to convert to 4420 # a TableAlias and attach to the Unnest, so it matches the parser's output 4421 if isinstance(table.args.get("alias"), exp.TableAlias): 4422 table_as_column.replace(table_as_column.this) 4423 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4424 4425 table.replace(unnest) 4426 4427 refs.add(normalized_table.alias_or_name) 4428 4429 return this 4430 4431 @t.overload 4432 def _parse_query_modifiers(self, this: E) -> E: ... 4433 4434 @t.overload 4435 def _parse_query_modifiers(self, this: None) -> None: ... 4436 4437 def _parse_query_modifiers(self, this): 4438 if isinstance(this, self.MODIFIABLES): 4439 for join in self._parse_joins(): 4440 this.append("joins", join) 4441 for lateral in iter(self._parse_lateral, None): 4442 this.append("laterals", lateral) 4443 4444 while True: 4445 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4446 modifier_token = self._curr 4447 4448 # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result 4449 # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union 4450 if ( 4451 modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH) 4452 and (limit := this.args.get("limit")) 4453 and limit.meta.get("top") 4454 ): 4455 break 4456 4457 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4458 key, expression = parser(self) 4459 4460 if expression: 4461 if this.args.get(key): 4462 self.raise_error( 4463 f"Found multiple '{modifier_token.text.upper()}' clauses", 4464 token=modifier_token, 4465 ) 4466 4467 this.set(key, expression) 4468 if key == "limit": 4469 offset = expression.args.get("offset") 4470 expression.set("offset", None) 4471 4472 if offset: 4473 if this.args.get("offset"): 4474 self.raise_error( 4475 "Found multiple 'OFFSET' clauses", token=modifier_token 4476 ) 4477 4478 offset = exp.Offset(expression=offset) 4479 this.set("offset", offset) 4480 4481 limit_by_expressions = expression.expressions 4482 expression.set("expressions", None) 4483 offset.set("expressions", limit_by_expressions) 4484 continue 4485 4486 if self._curr.text.upper() == "START": 4487 modifier_token = self._curr 4488 connect = self._parse_connect() 4489 if connect: 4490 if this.args.get("connect"): 4491 self.raise_error( 4492 "Found multiple 'START WITH' clauses", token=modifier_token 4493 ) 4494 4495 this.set("connect", connect) 4496 continue 4497 break 4498 4499 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4500 this = self._implicit_unnests_to_explicit(this) 4501 4502 return this 4503 4504 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4505 start = self._curr 4506 while self._curr: 4507 self._advance() 4508 4509 end = self._tokens[self._index - 1] 4510 return exp.Hint(expressions=[self._find_sql(start, end)]) 4511 4512 def _parse_hint_function_call(self) -> exp.Expr | None: 4513 return self._parse_function_call() 4514 4515 def _parse_hint_body(self) -> exp.Hint | None: 4516 start_index = self._index 4517 should_fallback_to_string = False 4518 4519 hints = [] 4520 try: 4521 for hint in iter( 4522 lambda: self._parse_csv( 4523 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4524 ), 4525 [], 4526 ): 4527 hints.extend(hint) 4528 except ParseError: 4529 should_fallback_to_string = True 4530 4531 if should_fallback_to_string or self._curr: 4532 self._retreat(start_index) 4533 return self._parse_hint_fallback_to_string() 4534 4535 return self.expression(exp.Hint(expressions=hints)) 4536 4537 def _parse_hint(self) -> exp.Hint | None: 4538 if self._match(TokenType.HINT) and self._prev_comments: 4539 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4540 4541 return None 4542 4543 def _parse_into(self) -> exp.Into | None: 4544 if not self._match(TokenType.INTO): 4545 return None 4546 4547 temp = self._match(TokenType.TEMPORARY) 4548 unlogged = self._match_text_seq("UNLOGGED") 4549 self._match(TokenType.TABLE) 4550 4551 return self.expression( 4552 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4553 ) 4554 4555 def _parse_from( 4556 self, 4557 joins: bool = False, 4558 skip_from_token: bool = False, 4559 consume_pipe: bool = False, 4560 ) -> exp.From | None: 4561 if not skip_from_token and not self._match(TokenType.FROM): 4562 return None 4563 4564 comments = self._prev_comments 4565 return self.expression( 4566 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4567 comments=comments, 4568 ) 4569 4570 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4571 return self.expression( 4572 exp.MatchRecognizeMeasure( 4573 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4574 this=self._parse_expression(), 4575 ) 4576 ) 4577 4578 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4579 if not self._match(TokenType.MATCH_RECOGNIZE): 4580 return None 4581 4582 self._match_l_paren() 4583 4584 partition = self._parse_partition_by() 4585 order = self._parse_order() 4586 4587 measures = ( 4588 self._parse_csv(self._parse_match_recognize_measure) 4589 if self._match_text_seq("MEASURES") 4590 else None 4591 ) 4592 4593 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4594 rows = exp.var("ONE ROW PER MATCH") 4595 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4596 text = "ALL ROWS PER MATCH" 4597 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4598 text += " SHOW EMPTY MATCHES" 4599 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4600 text += " OMIT EMPTY MATCHES" 4601 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4602 text += " WITH UNMATCHED ROWS" 4603 rows = exp.var(text) 4604 else: 4605 rows = None 4606 4607 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4608 text = "AFTER MATCH SKIP" 4609 if self._match_text_seq("PAST", "LAST", "ROW"): 4610 text += " PAST LAST ROW" 4611 elif self._match_text_seq("TO", "NEXT", "ROW"): 4612 text += " TO NEXT ROW" 4613 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4614 direction = self._prev.text.upper() 4615 pattern_var = self._advance_any() 4616 if not pattern_var: 4617 self.raise_error( 4618 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4619 ) 4620 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4621 after = exp.var(text) 4622 else: 4623 after = None 4624 4625 if self._match_text_seq("PATTERN"): 4626 self._match_l_paren() 4627 4628 if not self._curr: 4629 self.raise_error("Expecting )", self._curr) 4630 4631 paren = 1 4632 start = self._curr 4633 4634 while self._curr and paren > 0: 4635 if self._curr.token_type == TokenType.L_PAREN: 4636 paren += 1 4637 if self._curr.token_type == TokenType.R_PAREN: 4638 paren -= 1 4639 4640 end = self._prev 4641 self._advance() 4642 4643 if paren > 0: 4644 self.raise_error("Expecting )", self._curr) 4645 4646 pattern = exp.var(self._find_sql(start, end)) 4647 else: 4648 pattern = None 4649 4650 define = ( 4651 self._parse_csv(self._parse_name_as_expression) 4652 if self._match_text_seq("DEFINE") 4653 else None 4654 ) 4655 4656 self._match_r_paren() 4657 4658 return self.expression( 4659 exp.MatchRecognize( 4660 partition_by=partition, 4661 order=order, 4662 measures=measures, 4663 rows=rows, 4664 after=after, 4665 pattern=pattern, 4666 define=define, 4667 alias=self._parse_table_alias(), 4668 ) 4669 ) 4670 4671 def _parse_lateral(self) -> exp.Lateral | None: 4672 cross_apply: bool | None = None 4673 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4674 cross_apply = True 4675 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4676 cross_apply = False 4677 4678 if cross_apply is not None: 4679 this = self._parse_select(table=True) 4680 view = None 4681 outer = None 4682 elif self._match(TokenType.LATERAL): 4683 this = self._parse_select(table=True) 4684 view = self._match(TokenType.VIEW) 4685 outer = self._match(TokenType.OUTER) 4686 else: 4687 return None 4688 4689 if not this: 4690 this = ( 4691 self._parse_unnest() 4692 or self._parse_function() 4693 or self._parse_id_var(any_token=False) 4694 ) 4695 4696 while self._match(TokenType.DOT): 4697 this = exp.Dot( 4698 this=this, 4699 expression=self._parse_function() or self._parse_id_var(any_token=False), 4700 ) 4701 4702 ordinality: bool | None = None 4703 4704 if view: 4705 table = self._parse_id_var(any_token=False) 4706 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4707 table_alias: exp.TableAlias | None = self.expression( 4708 exp.TableAlias(this=table, columns=columns) 4709 ) 4710 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4711 # We move the alias from the lateral's child node to the lateral itself 4712 table_alias = this.args["alias"].pop() 4713 else: 4714 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4715 table_alias = self._parse_table_alias() 4716 4717 return self.expression( 4718 exp.Lateral( 4719 this=this, 4720 view=view, 4721 outer=outer, 4722 alias=table_alias, 4723 cross_apply=cross_apply, 4724 ordinality=ordinality, 4725 ) 4726 ) 4727 4728 def _parse_stream(self) -> exp.Stream | None: 4729 index = self._index 4730 if self._match(TokenType.STREAM): 4731 if this := self._try_parse(self._parse_table): 4732 return self.expression(exp.Stream(this=this)) 4733 self._retreat(index) 4734 return None 4735 4736 def _parse_join_parts( 4737 self, 4738 ) -> tuple[Token | None, Token | None, Token | None]: 4739 return ( 4740 self._prev if self._match_set(self.JOIN_METHODS) else None, 4741 self._prev if self._match_set(self.JOIN_SIDES) else None, 4742 self._prev if self._match_set(self.JOIN_KINDS) else None, 4743 ) 4744 4745 def _parse_using_identifiers(self) -> list[exp.Expr]: 4746 def _parse_column_as_identifier() -> exp.Expr | None: 4747 this = self._parse_column() 4748 if isinstance(this, exp.Column): 4749 return this.this 4750 return this 4751 4752 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4753 4754 def _parse_join( 4755 self, 4756 skip_join_token: bool = False, 4757 parse_bracket: bool = False, 4758 alias_tokens: t.Collection[TokenType] | None = None, 4759 ) -> exp.Join | None: 4760 if self._match(TokenType.COMMA): 4761 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4762 cross_join = self.expression(exp.Join(this=table)) if table else None 4763 4764 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4765 cross_join.set("kind", "CROSS") 4766 4767 return cross_join 4768 4769 index = self._index 4770 method, side, kind = self._parse_join_parts() 4771 directed = self._match_text_seq("DIRECTED") 4772 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4773 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4774 join_comments = self._prev_comments 4775 4776 if not skip_join_token and not join: 4777 self._retreat(index) 4778 kind = None 4779 method = None 4780 side = None 4781 4782 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4783 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4784 4785 if not skip_join_token and not join and not outer_apply and not cross_apply: 4786 return None 4787 4788 kwargs: dict[str, t.Any] = { 4789 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4790 } 4791 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4792 kwargs["expressions"] = self._parse_csv( 4793 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4794 ) 4795 4796 if method: 4797 kwargs["method"] = method.text.upper() 4798 if side: 4799 kwargs["side"] = side.text.upper() 4800 if kind: 4801 kwargs["kind"] = kind.text.upper() 4802 if hint: 4803 kwargs["hint"] = hint 4804 4805 if self._match(TokenType.MATCH_CONDITION): 4806 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4807 4808 if self._match(TokenType.ON): 4809 kwargs["on"] = self._parse_disjunction() 4810 elif self._match(TokenType.USING): 4811 kwargs["using"] = self._parse_using_identifiers() 4812 elif ( 4813 not method 4814 and not (outer_apply or cross_apply) 4815 and not isinstance(kwargs["this"], exp.Unnest) 4816 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4817 ): 4818 index = self._index 4819 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4820 4821 if joins and self._match(TokenType.ON): 4822 kwargs["on"] = self._parse_disjunction() 4823 elif joins and self._match(TokenType.USING): 4824 kwargs["using"] = self._parse_using_identifiers() 4825 else: 4826 joins = None 4827 self._retreat(index) 4828 4829 kwargs["this"].set("joins", joins if joins else None) 4830 4831 kwargs["pivots"] = self._parse_pivots() 4832 4833 comments = [c for token in (method, side, kind) if token for c in token.comments] 4834 comments = (join_comments or []) + comments 4835 4836 if ( 4837 self.ADD_JOIN_ON_TRUE 4838 and not kwargs.get("on") 4839 and not kwargs.get("using") 4840 and not kwargs.get("method") 4841 and kwargs.get("kind") in (None, "INNER", "OUTER") 4842 ): 4843 kwargs["on"] = exp.true() 4844 4845 if directed: 4846 kwargs["directed"] = directed 4847 4848 return self.expression(exp.Join(**kwargs), comments=comments) 4849 4850 def _parse_opclass(self) -> exp.Expr | None: 4851 this = self._parse_disjunction() 4852 4853 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4854 return this 4855 4856 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4857 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4858 4859 return this 4860 4861 def _parse_index_params(self) -> exp.IndexParameters: 4862 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4863 4864 if self._match(TokenType.L_PAREN, advance=False): 4865 columns = self._parse_wrapped_csv(self._parse_with_operator) 4866 else: 4867 columns = None 4868 4869 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4870 partition_by = self._parse_partition_by() 4871 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4872 tablespace = ( 4873 self._parse_var(any_token=True) 4874 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4875 else None 4876 ) 4877 where = self._parse_where() 4878 4879 on = self._parse_field() if self._match(TokenType.ON) else None 4880 4881 return self.expression( 4882 exp.IndexParameters( 4883 using=using, 4884 columns=columns, 4885 include=include, 4886 partition_by=partition_by, 4887 where=where, 4888 with_storage=with_storage, 4889 tablespace=tablespace, 4890 on=on, 4891 ) 4892 ) 4893 4894 def _parse_index( 4895 self, index: exp.Expr | None = None, anonymous: bool = False 4896 ) -> exp.Index | None: 4897 if index or anonymous: 4898 unique = None 4899 primary = None 4900 amp = None 4901 4902 self._match(TokenType.ON) 4903 self._match(TokenType.TABLE) # hive 4904 table = self._parse_table_parts(schema=True) 4905 else: 4906 unique = self._match(TokenType.UNIQUE) 4907 primary = self._match_text_seq("PRIMARY") 4908 amp = self._match_text_seq("AMP") 4909 4910 if not self._match(TokenType.INDEX): 4911 return None 4912 4913 index = self._parse_id_var() 4914 table = None 4915 4916 params = self._parse_index_params() 4917 4918 return self.expression( 4919 exp.Index( 4920 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4921 ) 4922 ) 4923 4924 def _parse_table_hints(self) -> list[exp.Expr] | None: 4925 hints: list[exp.Expr] = [] 4926 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4927 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4928 hints.append( 4929 self.expression( 4930 exp.WithTableHint( 4931 expressions=self._parse_csv( 4932 lambda: self._parse_function() or self._parse_var(any_token=True) 4933 ) 4934 ) 4935 ) 4936 ) 4937 self._match_r_paren() 4938 else: 4939 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4940 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4941 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4942 4943 self._match_set((TokenType.INDEX, TokenType.KEY)) 4944 if self._match(TokenType.FOR): 4945 hint.set("target", self._advance_any() and self._prev.text.upper()) 4946 4947 hint.set("expressions", self._parse_wrapped_id_vars()) 4948 hints.append(hint) 4949 4950 return hints or None 4951 4952 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 4953 return ( 4954 (not schema and self._parse_function(optional_parens=False)) 4955 or self._parse_id_var(any_token=False) 4956 or self._parse_string_as_identifier() 4957 or self._parse_placeholder() 4958 ) 4959 4960 def _parse_table_parts_fast(self) -> exp.Table | None: 4961 index = self._index 4962 parts: list[exp.Identifier] | None = None 4963 all_comments: list[str] | None = None 4964 4965 while self._match_set(self.IDENTIFIER_TOKENS): 4966 token = self._prev 4967 comments = self._prev_comments 4968 4969 has_dot = self._match(TokenType.DOT) 4970 curr_tt = self._curr.token_type 4971 4972 if not has_dot: 4973 if curr_tt in self.TABLE_POSTFIX_TOKENS: 4974 self._retreat(index) 4975 return None 4976 elif curr_tt not in self.IDENTIFIER_TOKENS: 4977 self._retreat(index) 4978 return None 4979 4980 if parts is None: 4981 parts = [] 4982 4983 if comments: 4984 if all_comments is None: 4985 all_comments = [] 4986 all_comments.extend(comments) 4987 self._prev_comments = [] 4988 4989 parts.append( 4990 self.expression( 4991 exp.Identifier( 4992 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 4993 ), 4994 token, 4995 ) 4996 ) 4997 4998 if not has_dot: 4999 break 5000 5001 if parts is None: 5002 return None 5003 5004 n = len(parts) 5005 5006 if n == 1: 5007 table: exp.Table = exp.Table(this=parts[0]) 5008 elif n == 2: 5009 table = exp.Table(this=parts[1], db=parts[0]) 5010 elif n >= 3: 5011 this: exp.Identifier | exp.Dot = parts[2] 5012 for i in range(3, n): 5013 this = exp.Dot(this=this, expression=parts[i]) 5014 5015 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 5016 5017 if table is None: 5018 self._retreat(index) 5019 elif all_comments: 5020 table.add_comments(all_comments) 5021 return table 5022 5023 def _parse_table_parts( 5024 self, 5025 schema: bool = False, 5026 is_db_reference: bool = False, 5027 wildcard: bool = False, 5028 fast: bool = False, 5029 ) -> exp.Table | exp.Dot | None: 5030 if fast: 5031 return self._parse_table_parts_fast() 5032 5033 catalog: exp.Expr | str | None = None 5034 db: exp.Expr | str | None = None 5035 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 5036 5037 while self._match(TokenType.DOT): 5038 if catalog: 5039 # This allows nesting the table in arbitrarily many dot expressions if needed 5040 table = self.expression( 5041 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 5042 ) 5043 else: 5044 catalog = db 5045 db = table 5046 # "" used for tsql FROM a..b case 5047 table = self._parse_table_part(schema=schema) or "" 5048 5049 if ( 5050 wildcard 5051 and self._is_connected() 5052 and (isinstance(table, exp.Identifier) or not table) 5053 and self._match(TokenType.STAR) 5054 ): 5055 if isinstance(table, exp.Identifier): 5056 table.args["this"] += "*" 5057 else: 5058 table = exp.Identifier(this="*") 5059 5060 if is_db_reference: 5061 catalog = db 5062 db = table 5063 table = None 5064 5065 if not table and not is_db_reference: 5066 self.raise_error(f"Expected table name but got {self._curr}") 5067 if not db and is_db_reference: 5068 self.raise_error(f"Expected database name but got {self._curr}") 5069 5070 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 5071 5072 # Bubble up comments from identifier parts to the Table 5073 comments = [] 5074 for part in table.parts: 5075 if part_comments := part.pop_comments(): 5076 comments.extend(part_comments) 5077 if comments: 5078 table.add_comments(comments) 5079 5080 changes = self._parse_changes() 5081 if changes: 5082 table.set("changes", changes) 5083 5084 at_before = self._parse_historical_data() 5085 if at_before: 5086 table.set("when", at_before) 5087 5088 pivots = self._parse_pivots() 5089 if pivots: 5090 table.set("pivots", pivots) 5091 5092 return table 5093 5094 def _parse_table( 5095 self, 5096 schema: bool = False, 5097 joins: bool = False, 5098 alias_tokens: t.Collection[TokenType] | None = None, 5099 parse_bracket: bool = False, 5100 is_db_reference: bool = False, 5101 parse_partition: bool = False, 5102 consume_pipe: bool = False, 5103 ) -> exp.Expr | None: 5104 if not schema and not is_db_reference and not consume_pipe and not joins: 5105 index = self._index 5106 table = self._parse_table_parts(fast=True) 5107 5108 if table is not None: 5109 curr_tt = self._curr.token_type 5110 next_tt = self._next.token_type 5111 5112 fast_terminators = self.TABLE_TERMINATORS 5113 5114 # only return the table if we're sure there are no other operators 5115 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5116 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5117 return table 5118 5119 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5120 5121 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5122 if alias := self._parse_table_alias( 5123 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5124 ): 5125 table.set("alias", alias) 5126 5127 if self._curr.token_type in fast_terminators: 5128 return table 5129 5130 self._retreat(index) 5131 5132 if stream := self._parse_stream(): 5133 return stream 5134 5135 if lateral := self._parse_lateral(): 5136 return lateral 5137 5138 if unnest := self._parse_unnest(): 5139 return unnest 5140 5141 if values := self._parse_derived_table_values(): 5142 return values 5143 5144 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5145 if not subquery.args.get("pivots"): 5146 subquery.set("pivots", self._parse_pivots()) 5147 if joins: 5148 for join in self._parse_joins(): 5149 subquery.append("joins", join) 5150 return subquery 5151 5152 bracket = parse_bracket and self._parse_bracket(None) 5153 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5154 5155 rows_from_tables = ( 5156 self._parse_wrapped_csv(self._parse_table) 5157 if self._match_text_seq("ROWS", "FROM") 5158 else None 5159 ) 5160 rows_from = ( 5161 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5162 ) 5163 5164 only = self._match(TokenType.ONLY) 5165 5166 this = t.cast( 5167 exp.Expr, 5168 bracket 5169 or rows_from 5170 or self._parse_bracket( 5171 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5172 ), 5173 ) 5174 5175 if only: 5176 this.set("only", only) 5177 5178 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5179 self._match(TokenType.STAR) 5180 5181 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5182 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5183 this.set("partition", self._parse_partition()) 5184 5185 if schema: 5186 return self._parse_schema(this=this) 5187 5188 if self.dialect.ALIAS_POST_VERSION: 5189 this.set("version", self._parse_version()) 5190 5191 if self.dialect.ALIAS_POST_TABLESAMPLE: 5192 this.set("sample", self._parse_table_sample()) 5193 5194 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5195 if alias: 5196 this.set("alias", alias) 5197 5198 # DuckDB requires the time-travel clause to come after the alias, e.g. 5199 # SELECT * FROM t AS a AT (VERSION => 1) 5200 if isinstance(this, exp.Table) and not this.args.get("when"): 5201 this.set("when", self._parse_historical_data()) 5202 5203 if self._match(TokenType.INDEXED_BY): 5204 this.set("indexed", self._parse_table_parts()) 5205 elif self._match_text_seq("NOT", "INDEXED"): 5206 this.set("indexed", False) 5207 5208 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5209 return self.expression( 5210 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5211 ) 5212 5213 this.set("hints", self._parse_table_hints()) 5214 5215 if not this.args.get("pivots"): 5216 this.set("pivots", self._parse_pivots()) 5217 5218 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5219 this.set("sample", self._parse_table_sample()) 5220 5221 if not self.dialect.ALIAS_POST_VERSION: 5222 this.set("version", self._parse_version()) 5223 5224 if joins: 5225 for join in self._parse_joins(alias_tokens=alias_tokens): 5226 this.append("joins", join) 5227 5228 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5229 this.set("ordinality", True) 5230 this.set("alias", self._parse_table_alias()) 5231 5232 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5233 # hoist the table args onto the latter and return it instead 5234 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5235 table_from_rows = this.this 5236 for arg in exp.TableFromRows.arg_types: 5237 if arg != "this": 5238 table_from_rows.set(arg, this.args.get(arg)) 5239 5240 this = table_from_rows 5241 5242 return this 5243 5244 def _parse_version(self) -> exp.Version | None: 5245 for phrase, this in self.VERSION_PHRASES.items(): 5246 if self._match_text_seq(*phrase): 5247 break 5248 else: 5249 return None 5250 5251 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5252 kind = self._prev.text.upper() 5253 start = self._parse_bitwise() 5254 self._match_texts(("TO", "AND")) 5255 end = self._parse_bitwise() 5256 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5257 elif self._match_text_seq("CONTAINED", "IN"): 5258 kind = "CONTAINED IN" 5259 expression = self.expression( 5260 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5261 ) 5262 elif self._match(TokenType.ALL): 5263 kind = "ALL" 5264 expression = None 5265 else: 5266 self._match_text_seq("AS", "OF") 5267 kind = "AS OF" 5268 expression = self._parse_type() 5269 5270 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5271 5272 def _parse_historical_data(self) -> exp.HistoricalData | None: 5273 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5274 index = self._index 5275 historical_data = None 5276 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5277 this = self._prev.text.upper() 5278 kind = ( 5279 self._match(TokenType.L_PAREN) 5280 and self._match_texts(self.HISTORICAL_DATA_KIND) 5281 and self._prev.text.upper() 5282 ) 5283 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5284 5285 if expression: 5286 self._match_r_paren() 5287 historical_data = self.expression( 5288 exp.HistoricalData(this=this, kind=kind, expression=expression) 5289 ) 5290 else: 5291 self._retreat(index) 5292 5293 return historical_data 5294 5295 def _parse_changes(self) -> exp.Changes | None: 5296 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5297 return None 5298 5299 information = self._parse_var(any_token=True) 5300 self._match_r_paren() 5301 5302 return self.expression( 5303 exp.Changes( 5304 information=information, 5305 at_before=self._parse_historical_data(), 5306 end=self._parse_historical_data(), 5307 ) 5308 ) 5309 5310 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5311 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5312 return None 5313 5314 self._advance() 5315 5316 expressions = self._parse_wrapped_csv(self._parse_equality) 5317 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5318 5319 alias = self._parse_table_alias() if with_alias else None 5320 5321 if alias: 5322 if self.dialect.UNNEST_COLUMN_ONLY: 5323 if alias.args.get("columns"): 5324 self.raise_error("Unexpected extra column alias in unnest.") 5325 5326 alias.set("columns", [alias.this]) 5327 alias.set("this", None) 5328 5329 columns = alias.args.get("columns") or [] 5330 if offset and len(expressions) < len(columns): 5331 offset = columns.pop() 5332 5333 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5334 self._match(TokenType.ALIAS) 5335 offset = self._parse_id_var( 5336 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5337 ) or exp.to_identifier("offset") 5338 5339 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5340 5341 def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None: 5342 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5343 if not is_derived and not ( 5344 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5345 self._match_text_seq("VALUES") 5346 or self._match_text_seq("FORMAT", "VALUES") 5347 # MySQL accepts VALUE as a synonym for VALUES in INSERT statements. 5348 or (allow_value_synonym and self._match_text_seq("VALUE")) 5349 ): 5350 return None 5351 5352 expressions = self._parse_csv(self._parse_value) 5353 alias = self._parse_table_alias() 5354 5355 if is_derived: 5356 self._match_r_paren() 5357 5358 return self.expression( 5359 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5360 ) 5361 5362 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5363 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5364 as_modifier and self._match_text_seq("USING", "SAMPLE") 5365 ): 5366 return None 5367 5368 bucket_numerator = None 5369 bucket_denominator = None 5370 bucket_field = None 5371 percent = None 5372 size = None 5373 seed = None 5374 5375 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5376 matched_l_paren = self._match(TokenType.L_PAREN) 5377 5378 if self.TABLESAMPLE_CSV: 5379 num = None 5380 expressions = self._parse_csv(self._parse_primary) 5381 else: 5382 expressions = None 5383 num = ( 5384 self._parse_factor(parse_mod=False) 5385 if self._match(TokenType.NUMBER, advance=False) 5386 else self._parse_primary() or self._parse_placeholder() 5387 ) 5388 5389 if self._match_text_seq("BUCKET"): 5390 bucket_numerator = self._parse_number() 5391 self._match_text_seq("OUT", "OF") 5392 bucket_denominator = bucket_denominator = self._parse_number() 5393 self._match(TokenType.ON) 5394 bucket_field = self._parse_field() 5395 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5396 percent = num 5397 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5398 size = num 5399 else: 5400 percent = num 5401 5402 if matched_l_paren: 5403 self._match_r_paren() 5404 5405 if self._match(TokenType.L_PAREN): 5406 method = self._parse_var(upper=True) 5407 seed = self._match(TokenType.COMMA) and self._parse_number() 5408 self._match_r_paren() 5409 elif self._match_texts(("SEED", "REPEATABLE")): 5410 seed = self._parse_wrapped(self._parse_number) 5411 5412 if not method and self.DEFAULT_SAMPLING_METHOD: 5413 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5414 5415 return self.expression( 5416 exp.TableSample( 5417 expressions=expressions, 5418 method=method, 5419 bucket_numerator=bucket_numerator, 5420 bucket_denominator=bucket_denominator, 5421 bucket_field=bucket_field, 5422 percent=percent, 5423 size=size, 5424 seed=seed, 5425 ) 5426 ) 5427 5428 def _parse_pivots(self) -> list[exp.Pivot] | None: 5429 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5430 return None 5431 return list(iter(self._parse_pivot, None)) or None 5432 5433 def _parse_joins( 5434 self, alias_tokens: t.Collection[TokenType] | None = None 5435 ) -> t.Iterator[exp.Join]: 5436 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5437 5438 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5439 if not self._match(TokenType.INTO): 5440 return None 5441 5442 return self.expression( 5443 exp.UnpivotColumns( 5444 this=self._match_text_seq("NAME") and self._parse_column(), 5445 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5446 ) 5447 ) 5448 5449 # https://duckdb.org/docs/sql/statements/pivot 5450 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5451 def _parse_on() -> exp.Expr | None: 5452 this = self._parse_bitwise() 5453 5454 if self._match(TokenType.IN): 5455 # PIVOT ... ON col IN (row_val1, row_val2) 5456 return self._parse_in(this) 5457 if self._match(TokenType.ALIAS, advance=False): 5458 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5459 return self._parse_alias(this) 5460 5461 return this 5462 5463 this = self._parse_table() 5464 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5465 into = self._parse_unpivot_columns() 5466 using = self._match(TokenType.USING) and self._parse_csv( 5467 lambda: self._parse_alias(self._parse_column()) 5468 ) 5469 group = self._parse_group() 5470 5471 return self.expression( 5472 exp.Pivot( 5473 this=this, 5474 expressions=expressions, 5475 using=using, 5476 group=group, 5477 unpivot=is_unpivot, 5478 into=into, 5479 ) 5480 ) 5481 5482 def _parse_pivot_in(self) -> exp.In: 5483 def _parse_aliased_expression() -> exp.Expr | None: 5484 this = self._parse_select_or_expression() 5485 5486 self._match(TokenType.ALIAS) 5487 alias = self._parse_bitwise() 5488 if alias: 5489 if isinstance(alias, exp.Column) and not alias.db: 5490 alias = alias.this 5491 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5492 5493 return this 5494 5495 value = self._parse_column() 5496 5497 if not self._match(TokenType.IN): 5498 self.raise_error("Expecting IN") 5499 5500 if self._match(TokenType.L_PAREN): 5501 if self._match(TokenType.ANY): 5502 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5503 else: 5504 exprs = self._parse_csv(_parse_aliased_expression) 5505 self._match_r_paren() 5506 return self.expression(exp.In(this=value, expressions=exprs)) 5507 5508 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5509 5510 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5511 func = self._parse_function() 5512 if not func: 5513 if self._prev.token_type == TokenType.COMMA: 5514 return None 5515 self.raise_error("Expecting an aggregation function in PIVOT") 5516 5517 return self._parse_alias(func) 5518 5519 def _parse_pivot(self) -> exp.Pivot | None: 5520 index = self._index 5521 include_nulls = None 5522 5523 if self._match(TokenType.PIVOT): 5524 unpivot = False 5525 elif self._match(TokenType.UNPIVOT): 5526 unpivot = True 5527 5528 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5529 if self._match_text_seq("INCLUDE", "NULLS"): 5530 include_nulls = True 5531 elif self._match_text_seq("EXCLUDE", "NULLS"): 5532 include_nulls = False 5533 else: 5534 return None 5535 5536 expressions = [] 5537 5538 if not self._match(TokenType.L_PAREN): 5539 self._retreat(index) 5540 return None 5541 5542 if unpivot: 5543 expressions = self._parse_csv(self._parse_column) 5544 else: 5545 expressions = self._parse_csv(self._parse_pivot_aggregation) 5546 5547 if not expressions: 5548 self.raise_error("Failed to parse PIVOT's aggregation list") 5549 5550 if not self._match(TokenType.FOR): 5551 self.raise_error("Expecting FOR") 5552 5553 fields = [] 5554 while True: 5555 field = self._try_parse(self._parse_pivot_in) 5556 if not field: 5557 break 5558 fields.append(field) 5559 5560 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5561 self._parse_bitwise 5562 ) 5563 5564 group = self._parse_group() 5565 5566 self._match_r_paren() 5567 5568 pivot = self.expression( 5569 exp.Pivot( 5570 expressions=expressions, 5571 fields=fields, 5572 unpivot=unpivot, 5573 include_nulls=include_nulls, 5574 default_on_null=default_on_null, 5575 group=group, 5576 ) 5577 ) 5578 5579 if unpivot: 5580 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5581 for pivot_field in pivot.fields: 5582 if isinstance(pivot_field, exp.In): 5583 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5584 5585 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5586 5587 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5588 pivot.set("alias", self._parse_table_alias()) 5589 5590 if not unpivot: 5591 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5592 5593 columns: list[exp.Expr] = [] 5594 all_fields = [] 5595 for pivot_field in pivot.fields: 5596 pivot_field_expressions = pivot_field.expressions 5597 5598 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5599 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5600 continue 5601 5602 all_fields.append( 5603 [ 5604 # An explicit `<field> AS <alias>` names the output column directly, 5605 # so it wins over the dialect's string-identifying convention 5606 fld.sql() 5607 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5608 else fld.alias_or_name 5609 for fld in pivot_field_expressions 5610 ] 5611 ) 5612 5613 if all_fields: 5614 if names: 5615 all_fields.append(names) 5616 5617 # Generate all possible combinations of the pivot columns 5618 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5619 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5620 for fld_parts_tuple in itertools.product(*all_fields): 5621 fld_parts = list(fld_parts_tuple) 5622 5623 if names and self.PREFIXED_PIVOT_COLUMNS: 5624 # Move the "name" to the front of the list 5625 fld_parts.insert(0, fld_parts.pop(-1)) 5626 5627 columns.append(exp.to_identifier("_".join(fld_parts))) 5628 5629 pivot.set("columns", columns) 5630 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5631 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5632 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5633 5634 return pivot 5635 5636 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5637 return [agg.alias for agg in aggregations if agg.alias] 5638 5639 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5640 if not skip_where_token and not self._match(TokenType.PREWHERE): 5641 return None 5642 5643 comments = self._prev_comments 5644 return self.expression( 5645 exp.PreWhere(this=self._parse_disjunction()), 5646 comments=comments, 5647 ) 5648 5649 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5650 if not skip_where_token and not self._match(TokenType.WHERE): 5651 return None 5652 5653 comments = self._prev_comments 5654 return self.expression( 5655 exp.Where(this=self._parse_disjunction()), 5656 comments=comments, 5657 ) 5658 5659 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5660 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5661 return None 5662 comments = self._prev_comments 5663 5664 elements: dict[str, t.Any] = defaultdict(list) 5665 5666 if self._match(TokenType.ALL): 5667 elements["all"] = True 5668 elif self._match(TokenType.DISTINCT): 5669 elements["all"] = False 5670 5671 while True: 5672 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5673 # which are also valid identifiers 5674 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5675 break 5676 5677 elements["expressions"].extend( 5678 self._parse_csv( 5679 lambda: ( 5680 self._parse_grouping_sets() 5681 or self._parse_cube_or_rollup() 5682 or self._parse_disjunction() 5683 ) 5684 ) 5685 ) 5686 5687 before_with_index = self._index 5688 5689 if self._match(TokenType.WITH) and ( 5690 cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True) 5691 ): 5692 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5693 elements[key].append(cube_or_rollup) 5694 elif grouping_sets := self._parse_grouping_sets(): 5695 # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...) 5696 elements["grouping_sets"].append(grouping_sets) 5697 break 5698 elif self._match_text_seq("TOTALS"): 5699 elements["totals"] = True # type: ignore 5700 5701 if before_with_index <= self._index <= before_with_index + 1: 5702 self._retreat(before_with_index) 5703 break 5704 5705 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5706 5707 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5708 if self._match(TokenType.CUBE): 5709 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5710 elif self._match(TokenType.ROLLUP): 5711 kind = exp.Rollup 5712 else: 5713 return None 5714 5715 return self.expression( 5716 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5717 ) 5718 5719 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5720 if self._match(TokenType.GROUPING_SETS): 5721 return self.expression( 5722 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5723 ) 5724 return None 5725 5726 def _parse_grouping_set(self) -> exp.Expr | None: 5727 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5728 5729 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5730 if not skip_having_token and not self._match(TokenType.HAVING): 5731 return None 5732 comments = self._prev_comments 5733 return self.expression( 5734 exp.Having(this=self._parse_disjunction()), 5735 comments=comments, 5736 ) 5737 5738 def _parse_qualify(self) -> exp.Qualify | None: 5739 if not self._match(TokenType.QUALIFY): 5740 return None 5741 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5742 5743 def _parse_connect_with_prior(self) -> exp.Expr | None: 5744 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5745 exp.Prior(this=self._parse_bitwise()) 5746 ) 5747 connect = self._parse_disjunction() 5748 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5749 return connect 5750 5751 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5752 if skip_start_token: 5753 start = None 5754 elif self._match_text_seq("START", "WITH"): 5755 start = self._parse_disjunction() 5756 else: 5757 return None 5758 5759 self._match(TokenType.CONNECT_BY) 5760 nocycle = self._match_text_seq("NOCYCLE") 5761 connect = self._parse_connect_with_prior() 5762 5763 if not start and self._match_text_seq("START", "WITH"): 5764 start = self._parse_disjunction() 5765 5766 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5767 5768 def _parse_name_as_expression(self) -> exp.Expr | None: 5769 this = self._parse_id_var(any_token=True) 5770 if self._match(TokenType.ALIAS): 5771 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5772 return this 5773 5774 def _parse_interpolate(self) -> list[exp.Expr] | None: 5775 if self._match_text_seq("INTERPOLATE"): 5776 return self._parse_wrapped_csv(self._parse_name_as_expression) 5777 return None 5778 5779 def _parse_order( 5780 self, this: exp.Expr | None = None, skip_order_token: bool = False 5781 ) -> exp.Expr | None: 5782 siblings = None 5783 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5784 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5785 return this 5786 5787 siblings = True 5788 5789 comments = self._prev_comments 5790 return self.expression( 5791 exp.Order( 5792 this=this, 5793 expressions=self._parse_csv(self._parse_ordered), 5794 siblings=siblings, 5795 ), 5796 comments=comments, 5797 ) 5798 5799 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5800 if not self._match(token): 5801 return None 5802 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5803 5804 def _parse_ordered( 5805 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5806 ) -> exp.Ordered | None: 5807 this = parse_method() if parse_method else self._parse_disjunction() 5808 if not this: 5809 return None 5810 5811 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5812 this = exp.var("ALL") 5813 5814 asc = self._match(TokenType.ASC) 5815 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5816 5817 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5818 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5819 5820 nulls_first = is_nulls_first or False 5821 explicitly_null_ordered = is_nulls_first or is_nulls_last 5822 5823 if ( 5824 not explicitly_null_ordered 5825 and ( 5826 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5827 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5828 ) 5829 and self.dialect.NULL_ORDERING != "nulls_are_last" 5830 ): 5831 nulls_first = True 5832 5833 if self._match_text_seq("WITH", "FILL"): 5834 with_fill = self.expression( 5835 exp.WithFill( 5836 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5837 to=self._match_text_seq("TO") and self._parse_bitwise(), 5838 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5839 interpolate=self._parse_interpolate(), 5840 ) 5841 ) 5842 else: 5843 with_fill = None 5844 5845 return self.expression( 5846 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5847 ) 5848 5849 def _parse_limit_options(self) -> exp.LimitOptions | None: 5850 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5851 rows = self._match_texts(("ROW", "ROWS")) 5852 self._match_text_seq("ONLY") 5853 with_ties = self._match_text_seq("WITH", "TIES") 5854 5855 if not (percent or rows or with_ties): 5856 return None 5857 5858 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5859 5860 def _parse_limit( 5861 self, 5862 this: exp.Expr | None = None, 5863 top: bool = False, 5864 skip_limit_token: bool = False, 5865 ) -> exp.Expr | None: 5866 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5867 comments = self._prev_comments 5868 if top: 5869 limit_paren = self._match(TokenType.L_PAREN) 5870 expression = ( 5871 self._parse_term() or self._parse_select() 5872 if limit_paren 5873 else self._parse_number() 5874 ) 5875 5876 if limit_paren: 5877 self._match_r_paren() 5878 5879 else: 5880 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5881 expression = exp.var("ALL") 5882 else: 5883 expression = self._parse_term(parse_mod=False) 5884 limit_options = self._parse_limit_options() 5885 5886 if self._match(TokenType.COMMA): 5887 offset = expression 5888 expression = self._parse_term() 5889 else: 5890 offset = None 5891 5892 limit_exp = self.expression( 5893 exp.Limit( 5894 this=this, 5895 expression=expression, 5896 offset=offset, 5897 limit_options=limit_options, 5898 expressions=self._parse_limit_by(), 5899 ), 5900 comments=comments, 5901 ) 5902 5903 if top: 5904 limit_exp.meta["top"] = True 5905 5906 return limit_exp 5907 5908 if self._match(TokenType.FETCH): 5909 direction = ( 5910 self._prev.text.upper() 5911 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5912 else "FIRST" 5913 ) 5914 5915 count = ( 5916 None 5917 if self._match_texts(("ROW", "ROWS"), advance=False) 5918 else self._parse_field(tokens=self.FETCH_TOKENS) 5919 ) 5920 5921 return self.expression( 5922 exp.Fetch( 5923 direction=direction, count=count, limit_options=self._parse_limit_options() 5924 ) 5925 ) 5926 5927 return this 5928 5929 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5930 if not self._match(TokenType.OFFSET): 5931 return this 5932 5933 count = self._parse_term() 5934 self._match_texts(("ROW", "ROWS")) 5935 5936 return self.expression( 5937 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5938 ) 5939 5940 def _can_parse_limit_or_offset(self) -> bool: 5941 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5942 return False 5943 5944 index = self._index 5945 result = bool( 5946 self._try_parse(self._parse_limit, retreat=True) 5947 or self._try_parse(self._parse_offset, retreat=True) 5948 ) 5949 self._retreat(index) 5950 5951 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 5952 if self._next.token_type == TokenType.MATCH_CONDITION: 5953 result = False 5954 5955 return result 5956 5957 def _can_parse_named_window(self) -> bool: 5958 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 5959 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 5960 if not self._match(TokenType.WINDOW, advance=False): 5961 return False 5962 5963 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 5964 if name is None or name.token_type not in self.ID_VAR_TOKENS: 5965 return False 5966 5967 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 5968 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 5969 return False 5970 5971 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 5972 return body is not None and body.token_type == TokenType.L_PAREN 5973 5974 def _parse_limit_by(self) -> list[exp.Expr] | None: 5975 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 5976 5977 def _parse_locks(self) -> list[exp.Lock]: 5978 locks = [] 5979 while True: 5980 update, key = None, None 5981 if self._match_text_seq("FOR", "UPDATE"): 5982 update = True 5983 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 5984 "LOCK", "IN", "SHARE", "MODE" 5985 ): 5986 update = False 5987 elif self._match_text_seq("FOR", "KEY", "SHARE"): 5988 update, key = False, True 5989 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 5990 update, key = True, True 5991 else: 5992 break 5993 5994 expressions = None 5995 if self._match_text_seq("OF"): 5996 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 5997 5998 wait: bool | exp.Expr | None = None 5999 if self._match_text_seq("NOWAIT"): 6000 wait = True 6001 elif self._match_text_seq("WAIT"): 6002 wait = self._parse_primary() 6003 elif self._match_text_seq("SKIP", "LOCKED"): 6004 wait = False 6005 6006 locks.append( 6007 self.expression( 6008 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 6009 ) 6010 ) 6011 6012 return locks 6013 6014 def parse_set_operation( 6015 self, this: exp.Expr | None, consume_pipe: bool = False 6016 ) -> exp.Expr | None: 6017 start = self._index 6018 _, side_token, kind_token = self._parse_join_parts() 6019 6020 side = side_token.text if side_token else None 6021 kind = kind_token.text if kind_token else None 6022 6023 if not self._match_set(self.SET_OPERATIONS): 6024 self._retreat(start) 6025 return None 6026 6027 token_type = self._prev.token_type 6028 6029 if token_type == TokenType.UNION: 6030 operation: type[exp.SetOperation] = exp.Union 6031 elif token_type == TokenType.EXCEPT: 6032 operation = exp.Except 6033 else: 6034 operation = exp.Intersect 6035 6036 comments = self._prev.comments 6037 6038 if self._match(TokenType.DISTINCT): 6039 distinct: bool | None = True 6040 elif self._match(TokenType.ALL): 6041 distinct = False 6042 else: 6043 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 6044 if distinct is None: 6045 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 6046 6047 by_name = ( 6048 self._match_text_seq("BY", "NAME") 6049 or self._match_text_seq("STRICT", "CORRESPONDING") 6050 or None 6051 ) 6052 if self._match_text_seq("CORRESPONDING"): 6053 by_name = True 6054 if not side and not kind: 6055 kind = "INNER" 6056 6057 on_column_list = None 6058 if by_name and self._match_texts(("ON", "BY")): 6059 on_column_list = self._parse_wrapped_csv(self._parse_column) 6060 6061 expression = self._parse_select( 6062 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 6063 ) 6064 6065 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 6066 # in _parse_cte and so that alias pushdown can reach into set operation branches 6067 if isinstance(this, exp.Values): 6068 this = self._values_to_select(this) 6069 if isinstance(expression, exp.Values): 6070 expression = self._values_to_select(expression) 6071 6072 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 6073 subquery = this.this 6074 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 6075 subquery.add_comments(this.pop_comments()) 6076 this = subquery 6077 6078 return self.expression( 6079 operation( 6080 this=this, 6081 distinct=distinct, 6082 by_name=by_name, 6083 expression=expression, 6084 side=side, 6085 kind=kind, 6086 on=on_column_list, 6087 ), 6088 comments=comments, 6089 ) 6090 6091 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 6092 while this: 6093 setop = self.parse_set_operation(this) 6094 if not setop: 6095 break 6096 this = setop 6097 6098 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6099 expression = this.expression 6100 6101 if expression: 6102 for arg in self.SET_OP_MODIFIERS: 6103 expr = expression.args.get(arg) 6104 if expr and not (arg == "limit" and expr.meta.get("top")): 6105 expression.set(arg, None) 6106 this.set(arg, expr) 6107 6108 # A trailing LIMIT/FETCH can coexist with TOP on the final operand. 6109 if self._curr.token_type in (TokenType.LIMIT, TokenType.FETCH): 6110 this = self._parse_query_modifiers(this) 6111 6112 return this 6113 6114 def _parse_expression(self) -> exp.Expr | None: 6115 return self._parse_alias(self._parse_assignment()) 6116 6117 def _parse_assignment(self) -> exp.Expr | None: 6118 this = self._parse_disjunction() 6119 if not this and self._next.token_type in self.ASSIGNMENT: 6120 # This allows us to parse <non-identifier token> := <expr> 6121 this = exp.column( 6122 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6123 ) 6124 6125 while self._match_set(self.ASSIGNMENT): 6126 if isinstance(this, exp.Column) and len(this.parts) == 1: 6127 this = this.this 6128 6129 comments = self._prev_comments 6130 this = self.expression( 6131 self.ASSIGNMENT[self._prev.token_type]( 6132 this=this, expression=self._parse_assignment() 6133 ), 6134 comments=comments, 6135 ) 6136 6137 return this 6138 6139 def _parse_disjunction(self) -> exp.Expr | None: 6140 this = self._parse_conjunction() 6141 while self._match_set(self.DISJUNCTION): 6142 comments = self._prev_comments 6143 this = self.expression( 6144 self.DISJUNCTION[self._prev.token_type]( 6145 this=this, expression=self._parse_conjunction() 6146 ), 6147 comments=comments, 6148 ) 6149 return this 6150 6151 def _parse_conjunction(self) -> exp.Expr | None: 6152 this = self._parse_equality() 6153 while self._match_set(self.CONJUNCTION): 6154 comments = self._prev_comments 6155 this = self.expression( 6156 self.CONJUNCTION[self._prev.token_type]( 6157 this=this, expression=self._parse_equality() 6158 ), 6159 comments=comments, 6160 ) 6161 return this 6162 6163 def _parse_equality(self) -> exp.Expr | None: 6164 this = self._parse_comparison() 6165 while self._match_set(self.EQUALITY): 6166 comments = self._prev_comments 6167 this = self.expression( 6168 self.EQUALITY[self._prev.token_type]( 6169 this=this, expression=self._parse_comparison() 6170 ), 6171 comments=comments, 6172 ) 6173 return this 6174 6175 def _parse_comparison(self) -> exp.Expr | None: 6176 this = self._parse_range() 6177 while self._match_set(self.COMPARISON): 6178 comments = self._prev_comments 6179 this = self.expression( 6180 self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()), 6181 comments=comments, 6182 ) 6183 return this 6184 6185 def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6186 this = this or self._parse_bitwise() 6187 6188 while True: 6189 negate = self._match(TokenType.NOT) 6190 if self._match_set(self.RANGE_PARSERS): 6191 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6192 if not expression: 6193 return this 6194 6195 this = expression 6196 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6197 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6198 elif self._match(TokenType.NOTNULL): 6199 # Postgres supports ISNULL and NOTNULL for conditions. 6200 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6201 if self.dialect.NORMALIZE_NOT_NULL: 6202 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6203 this = self.expression(exp.Not(this=this)) 6204 else: 6205 this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True)) 6206 else: 6207 if negate: 6208 self._retreat(self._index - 1) 6209 break 6210 6211 if negate: 6212 this = self._negate_range(this) 6213 if self._curr and ( 6214 self._curr.token_type == TokenType.NOT 6215 or self._curr.token_type in self.RANGE_PARSERS 6216 ): 6217 this = self.expression(exp.Paren(this=this)) 6218 6219 return this 6220 6221 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6222 if not this: 6223 return this 6224 6225 expression = this.this if isinstance(this, exp.Escape) else this 6226 if isinstance(expression, (exp.Like, exp.ILike)): 6227 expression.set("negate", True) 6228 return this 6229 6230 return self.expression(exp.Not(this=this)) 6231 6232 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6233 index = self._index - 1 6234 negate = self._match(TokenType.NOT) 6235 6236 if self._match_text_seq("DISTINCT", "FROM"): 6237 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6238 return self.expression(klass(this=this, expression=self._parse_bitwise())) 6239 6240 if self._match(TokenType.JSON): 6241 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6242 6243 if self._match_text_seq("WITH"): 6244 _with = True 6245 elif self._match_text_seq("WITHOUT"): 6246 _with = False 6247 else: 6248 _with = None 6249 6250 unique = self._match(TokenType.UNIQUE) 6251 self._match_text_seq("KEYS") 6252 expression: exp.Expr | None = self.expression( 6253 exp.JSON(this=kind, with_=_with, unique=unique) 6254 ) 6255 else: 6256 expression = self._parse_null() or self._parse_bitwise() 6257 if not expression: 6258 self._retreat(index) 6259 return None 6260 6261 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6262 this = self.expression(exp.Is(this=this, expression=expression, negate=True)) 6263 else: 6264 this = self.expression(exp.Is(this=this, expression=expression)) 6265 this = self.expression(exp.Not(this=this)) if negate else this 6266 6267 return self._parse_column_ops(this) 6268 6269 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6270 unnest = self._parse_unnest(with_alias=False) 6271 if unnest: 6272 this = self.expression(exp.In(this=this, unnest=unnest)) 6273 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6274 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6275 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6276 6277 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6278 this = self.expression( 6279 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6280 ) 6281 else: 6282 this = self.expression(exp.In(this=this, expressions=expressions)) 6283 6284 if matched_l_paren: 6285 self._match_r_paren(this) 6286 elif not self._match(TokenType.R_BRACKET, expression=this): 6287 self.raise_error("Expecting ]") 6288 else: 6289 this = self.expression(exp.In(this=this, field=self._parse_column())) 6290 6291 return this 6292 6293 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6294 symmetric = None 6295 if self._match_text_seq("SYMMETRIC"): 6296 symmetric = True 6297 elif self._match_text_seq("ASYMMETRIC"): 6298 symmetric = False 6299 6300 low = self._parse_bitwise() 6301 self._match(TokenType.AND) 6302 high = self._parse_bitwise() 6303 6304 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6305 6306 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6307 if not self._match(TokenType.ESCAPE): 6308 return this 6309 return self.expression( 6310 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6311 ) 6312 6313 def _parse_interval_span( 6314 self, this: exp.Expr, parse_function_unit: bool = True 6315 ) -> exp.Interval: 6316 # handle day-time format interval span with omitted units: 6317 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6318 interval_span_units_omitted = None 6319 if ( 6320 this 6321 and this.is_string 6322 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6323 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6324 ): 6325 index = self._index 6326 6327 # Var "TO" Var 6328 first_unit = self._parse_var(any_token=True, upper=True) 6329 second_unit = None 6330 if first_unit and self._match_text_seq("TO"): 6331 second_unit = self._parse_var(any_token=True, upper=True) 6332 6333 interval_span_units_omitted = not (first_unit and second_unit) 6334 6335 self._retreat(index) 6336 6337 unit_index = self._index 6338 if interval_span_units_omitted: 6339 unit = None 6340 else: 6341 # Only attempt to parse a unit if the current token can actually be one, so that a 6342 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6343 is_unit = self._curr is not None and ( 6344 self._curr.token_type == TokenType.VAR 6345 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6346 ) 6347 unit = self._parse_function() if parse_function_unit and is_unit else None 6348 if not unit and is_unit: 6349 unit = self._parse_var(any_token=True, upper=True) 6350 6351 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6352 # each INTERVAL expression into this canonical form so it's easy to transpile 6353 if this and this.is_number: 6354 try: 6355 this = exp.Literal.string(this.to_py()) 6356 except ValueError: 6357 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6358 elif this and this.is_string: 6359 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6360 if parts and unit: 6361 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6362 unit = None 6363 self._retreat(unit_index) 6364 6365 if len(parts) == 1: 6366 this = exp.Literal.string(parts[0][0]) 6367 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6368 6369 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6370 unit = self.expression( 6371 exp.IntervalSpan( 6372 this=unit, 6373 expression=self._parse_function() 6374 or self._parse_var(any_token=True, upper=True), 6375 ) 6376 ) 6377 6378 return self.expression(exp.Interval(this=this, unit=unit)) 6379 6380 def _parse_interval( 6381 self, require_interval: bool = True, parse_function_unit: bool = True 6382 ) -> exp.Add | exp.Interval | None: 6383 index = self._index 6384 6385 if not self._match(TokenType.INTERVAL) and require_interval: 6386 return None 6387 6388 if self._match(TokenType.STRING, advance=False): 6389 this = self._parse_primary() 6390 else: 6391 this = self._parse_term() 6392 6393 if not this or ( 6394 isinstance(this, exp.Column) 6395 and not this.table 6396 and not this.this.quoted 6397 and self._curr 6398 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6399 ): 6400 self._retreat(index) 6401 return None 6402 6403 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6404 6405 index = self._index 6406 self._match(TokenType.PLUS) 6407 6408 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6409 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6410 return self.expression( 6411 exp.Add( 6412 this=interval, 6413 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6414 ) 6415 ) 6416 6417 self._retreat(index) 6418 return interval 6419 6420 def _parse_bitwise(self) -> exp.Expr | None: 6421 this = self._parse_term() 6422 6423 while True: 6424 if self._match_set(self.BITWISE): 6425 this = self.expression( 6426 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6427 ) 6428 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6429 this = self.expression( 6430 exp.DPipe( 6431 this=this, 6432 expression=self._parse_term(), 6433 safe=not self.dialect.STRICT_STRING_CONCAT, 6434 ) 6435 ) 6436 elif self._match(TokenType.DQMARK): 6437 this = self.expression( 6438 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6439 ) 6440 elif self._match_pair(TokenType.LT, TokenType.LT): 6441 this = self.expression( 6442 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6443 ) 6444 elif self._match_pair(TokenType.GT, TokenType.GT): 6445 this = self.expression( 6446 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6447 ) 6448 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6449 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6450 else: 6451 break 6452 6453 return this 6454 6455 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6456 this = self._parse_factor(parse_mod=parse_mod) 6457 6458 while self._match_set(self.TERM): 6459 klass = self.TERM[self._prev.token_type] 6460 comments = self._prev_comments 6461 expression = self._parse_factor(parse_mod=parse_mod) 6462 6463 this = self.expression(klass(this=this, expression=expression), comments=comments) 6464 6465 if isinstance(this, exp.Collate): 6466 self._normalize_collate(this) 6467 6468 return this 6469 6470 def _normalize_collate(self, collate: exp.Collate) -> None: 6471 expr = collate.expression 6472 6473 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6474 # fallback to Identifier / Var 6475 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6476 ident = expr.this 6477 if isinstance(ident, exp.Identifier): 6478 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6479 6480 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6481 parse_method = self._parse_factor_operand 6482 this = self._parse_at_time_zone(parse_method()) 6483 6484 while self._match_set(self.FACTOR, advance=False): 6485 if not parse_mod and self._curr.token_type == TokenType.MOD: 6486 break 6487 6488 self._advance() 6489 klass = self.FACTOR[self._prev.token_type] 6490 comments = self._prev_comments 6491 expression = parse_method() 6492 6493 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6494 self._retreat(self._index - 1) 6495 return this 6496 6497 this = self.expression(klass(this=this, expression=expression), comments=comments) 6498 6499 if isinstance(this, exp.Div): 6500 this.set("typed", self.dialect.TYPED_DIVISION) 6501 this.set("safe", self.dialect.SAFE_DIVISION) 6502 6503 return this 6504 6505 def _parse_factor_operand(self) -> exp.Expr | None: 6506 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6507 6508 def _parse_exponent(self) -> exp.Expr | None: 6509 this = self._parse_unary() 6510 while self._match_set(self.EXPONENT): 6511 comments = self._prev_comments 6512 this = self.expression( 6513 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6514 comments=comments, 6515 ) 6516 return this 6517 6518 def _parse_unary(self) -> exp.Expr | None: 6519 if self._match_set(self.UNARY_PARSERS): 6520 return self.UNARY_PARSERS[self._prev.token_type](self) 6521 return self._parse_type() 6522 6523 def _parse_type( 6524 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6525 ) -> exp.Expr | None: 6526 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6527 return atom 6528 6529 if interval := parse_interval and self._parse_interval(): 6530 return self._parse_column_ops(interval) 6531 6532 index = self._index 6533 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6534 6535 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6536 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6537 if isinstance(data_type, exp.Cast): 6538 # This constructor can contain ops directly after it, for instance struct unnesting: 6539 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6540 return self._parse_column_ops(data_type) 6541 6542 if data_type: 6543 index2 = self._index 6544 this = self._parse_primary() 6545 6546 if isinstance(this, exp.Literal): 6547 literal = this.name 6548 this = self._parse_column_ops(this) 6549 6550 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6551 if data_type.is_type(exp.DType.TIMESTAMP): 6552 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6553 elif data_type.is_type(exp.DType.TIME): 6554 data_type = exp.DType.TIMETZ.into_expr() 6555 6556 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6557 if parser: 6558 return parser(self, this, data_type) 6559 6560 return self.expression(exp.Cast(this=this, to=data_type)) 6561 6562 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6563 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6564 # 6565 # If the index difference here is greater than 1, that means the parser itself must have 6566 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6567 # 6568 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6569 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6570 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6571 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6572 # 6573 # In these cases, we don't really want to return the converted type, but instead retreat 6574 # and try to parse a Column or Identifier in the section below. 6575 if data_type.expressions and index2 - index > 1: 6576 self._retreat(index2) 6577 return self._parse_column_ops(data_type) 6578 6579 self._retreat(index) 6580 6581 if fallback_to_identifier: 6582 return self._parse_id_var() 6583 6584 return self._parse_column() 6585 6586 def _parse_type_size(self) -> exp.DataTypeParam | None: 6587 this = self._parse_type() 6588 if not this: 6589 return None 6590 6591 if isinstance(this, exp.Column) and not this.table: 6592 this = exp.var(this.name.upper()) 6593 6594 return self.expression( 6595 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6596 ) 6597 6598 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6599 type_name = identifier.name 6600 6601 while self._match(TokenType.DOT): 6602 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6603 6604 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6605 6606 def _parse_types( 6607 self, 6608 check_func: bool = False, 6609 schema: bool = False, 6610 allow_identifiers: bool = True, 6611 with_collation: bool = False, 6612 ) -> exp.Expr | None: 6613 index = self._index 6614 this: exp.Expr | None = None 6615 6616 if self._match_set(self.TYPE_TOKENS): 6617 type_token = self._prev.token_type 6618 else: 6619 type_token = None 6620 identifier = allow_identifiers and self._parse_id_var( 6621 any_token=False, tokens=(TokenType.VAR,) 6622 ) 6623 if isinstance(identifier, exp.Identifier): 6624 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6625 this = exp.DataType.build(identifier, udt=True) 6626 else: 6627 try: 6628 tokens = self.dialect.tokenize(identifier.name) 6629 except TokenError: 6630 tokens = None 6631 6632 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6633 if len(tokens) > 1: 6634 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6635 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6636 this = self._parse_user_defined_type(identifier) 6637 else: 6638 self._retreat(self._index - 1) 6639 return None 6640 else: 6641 return None 6642 6643 if type_token == TokenType.PSEUDO_TYPE: 6644 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6645 6646 if type_token == TokenType.OBJECT_IDENTIFIER: 6647 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6648 6649 # https://materialize.com/docs/sql/types/map/ 6650 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6651 key_type = self._parse_types( 6652 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6653 ) 6654 if not self._match(TokenType.FARROW): 6655 self._retreat(index) 6656 return None 6657 6658 value_type = self._parse_types( 6659 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6660 ) 6661 if not self._match(TokenType.R_BRACKET): 6662 self._retreat(index) 6663 return None 6664 6665 return exp.DataType( 6666 this=exp.DType.MAP, 6667 expressions=[key_type, value_type], 6668 nested=True, 6669 ) 6670 6671 nested = type_token in self.NESTED_TYPE_TOKENS 6672 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6673 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6674 expressions = None 6675 maybe_func = False 6676 6677 if self._match(TokenType.L_PAREN): 6678 if is_struct: 6679 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6680 elif nested: 6681 expressions = self._parse_csv( 6682 lambda: self._parse_types( 6683 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6684 ) 6685 ) 6686 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6687 this = expressions[0] 6688 this.set("nullable", True) 6689 self._match_r_paren() 6690 return this 6691 elif type_token in self.ENUM_TYPE_TOKENS: 6692 expressions = self._parse_csv(self._parse_equality) 6693 elif type_token == TokenType.JSON: 6694 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6695 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6696 expressions = self._parse_csv(self._parse_json_type_arg) 6697 elif is_aggregate: 6698 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6699 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6700 ) 6701 if not func_or_ident: 6702 return None 6703 expressions = [func_or_ident] 6704 if self._match(TokenType.COMMA): 6705 expressions.extend( 6706 self._parse_csv( 6707 lambda: self._parse_types( 6708 check_func=check_func, 6709 schema=schema, 6710 allow_identifiers=allow_identifiers, 6711 ) 6712 ) 6713 ) 6714 else: 6715 expressions = self._parse_csv(self._parse_type_size) 6716 6717 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6718 if type_token == TokenType.VECTOR and len(expressions) == 2: 6719 expressions = self._parse_vector_expressions(expressions) 6720 6721 if not self._match(TokenType.R_PAREN): 6722 self._retreat(index) 6723 return None 6724 6725 maybe_func = True 6726 6727 values: list[exp.Expr] | None = None 6728 6729 if nested and self._match(TokenType.LT): 6730 if is_struct: 6731 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6732 else: 6733 expressions = self._parse_csv( 6734 lambda: self._parse_types( 6735 check_func=check_func, 6736 schema=schema, 6737 allow_identifiers=allow_identifiers, 6738 with_collation=True, 6739 ) 6740 ) 6741 6742 if not self._match(TokenType.GT): 6743 self.raise_error("Expecting >") 6744 6745 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6746 values = self._parse_csv(self._parse_disjunction) 6747 if not values and is_struct: 6748 values = None 6749 self._retreat(self._index - 1) 6750 else: 6751 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6752 6753 if type_token in self.TIMESTAMPS: 6754 if self._match_text_seq("WITH", "TIME", "ZONE"): 6755 maybe_func = False 6756 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6757 this = exp.DataType(this=tz_type, expressions=expressions) 6758 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6759 maybe_func = False 6760 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6761 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6762 maybe_func = False 6763 elif type_token == TokenType.INTERVAL: 6764 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6765 unit = self._parse_var(upper=True) 6766 if self._match_text_seq("TO"): 6767 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6768 6769 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6770 else: 6771 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6772 elif type_token == TokenType.VOID: 6773 this = exp.DataType(this=exp.DType.NULL) 6774 6775 if maybe_func and check_func: 6776 index2 = self._index 6777 peek = self._parse_string() 6778 6779 if not peek: 6780 self._retreat(index) 6781 return None 6782 6783 self._retreat(index2) 6784 6785 if not this: 6786 assert type_token is not None 6787 if self._match_text_seq("UNSIGNED"): 6788 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6789 if not unsigned_type_token: 6790 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6791 6792 type_token = unsigned_type_token or type_token 6793 6794 # NULLABLE without parentheses can be a column (Presto/Trino) 6795 if type_token == TokenType.NULLABLE and not expressions: 6796 self._retreat(index) 6797 return None 6798 6799 this = exp.DataType( 6800 this=exp.DType[type_token.name], 6801 expressions=expressions, 6802 nested=nested, 6803 ) 6804 6805 # Empty arrays/structs are allowed 6806 if values is not None: 6807 cls = exp.Struct if is_struct else exp.Array 6808 this = exp.cast(cls(expressions=values), this, copy=False) 6809 6810 elif expressions: 6811 this.set("expressions", expressions) 6812 6813 # https://materialize.com/docs/sql/types/list/#type-name 6814 while self._match(TokenType.LIST): 6815 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6816 6817 index = self._index 6818 6819 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6820 matched_array = self._match(TokenType.ARRAY) 6821 6822 while self._curr: 6823 datatype_token = self._prev.token_type 6824 matched_l_bracket = self._match(TokenType.L_BRACKET) 6825 6826 if (not matched_l_bracket and not matched_array) or ( 6827 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6828 ): 6829 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6830 # not to be confused with the fixed size array parsing 6831 break 6832 6833 matched_array = False 6834 values = self._parse_csv(self._parse_disjunction) or None 6835 if ( 6836 values 6837 and not schema 6838 and ( 6839 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6840 or datatype_token == TokenType.ARRAY 6841 or not self._match(TokenType.R_BRACKET, advance=False) 6842 ) 6843 ): 6844 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6845 # 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 6846 self._retreat(index) 6847 break 6848 6849 this = exp.DataType( 6850 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6851 ) 6852 self._match(TokenType.R_BRACKET) 6853 6854 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6855 converter = self.TYPE_CONVERTERS.get(this.this) 6856 if converter: 6857 this = converter(t.cast(exp.DataType, this)) 6858 6859 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6860 this.set("collate", self._parse_identifier() or self._parse_column()) 6861 6862 return this 6863 6864 def _parse_json_type_arg(self) -> exp.Expr | None: 6865 """Parse a single argument to ClickHouse's JSON type.""" 6866 6867 # SKIP col or SKIP REGEXP 'pattern' 6868 if self._match_text_seq("SKIP"): 6869 regexp = self._match(TokenType.RLIKE) 6870 arg = self._parse_column() 6871 if isinstance(arg, exp.Column): 6872 arg = arg.to_dot() 6873 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6874 6875 param_or_col = self._parse_column() 6876 if not isinstance(param_or_col, exp.Column): 6877 return None 6878 6879 # Parameter: name=value (e.g., max_dynamic_paths=2) 6880 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 6881 param = param_or_col.name 6882 value = self._parse_primary() 6883 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 6884 6885 # Column type hint: col_name Type 6886 col = param_or_col.to_dot() 6887 kind = self._parse_types(check_func=False, allow_identifiers=False) 6888 return self.expression(exp.ColumnDef(this=col, kind=kind)) 6889 6890 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 6891 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 6892 6893 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 6894 index = self._index 6895 6896 if ( 6897 self._curr 6898 and self._next 6899 and self._curr.token_type in self.TYPE_TOKENS 6900 and self._next.token_type in self.TYPE_TOKENS 6901 ): 6902 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 6903 # type token. Without this, the list will be parsed as a type and we'll eventually crash 6904 this = self._parse_id_var() 6905 else: 6906 this = ( 6907 self._parse_type(parse_interval=False, fallback_to_identifier=True) 6908 or self._parse_id_var() 6909 ) 6910 6911 self._match(TokenType.COLON) 6912 6913 if ( 6914 type_required 6915 and not isinstance(this, exp.DataType) 6916 and not self._match_set(self.TYPE_TOKENS, advance=False) 6917 ): 6918 self._retreat(index) 6919 return self._parse_types() 6920 6921 return self._parse_column_def(this) 6922 6923 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 6924 if not self._match_text_seq("AT", "TIME", "ZONE"): 6925 return this 6926 return self._parse_at_time_zone( 6927 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 6928 ) 6929 6930 def _parse_atom(self) -> exp.Expr | None: 6931 if ( 6932 self._curr.token_type in self.IDENTIFIER_TOKENS 6933 and (column := self._parse_column()) is not None 6934 ): 6935 return column 6936 6937 token = self._curr 6938 token_type = token.token_type 6939 6940 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 6941 return None 6942 6943 next_type = self._next.token_type 6944 6945 if ( 6946 next_type in self.COLUMN_OPERATORS 6947 or next_type in self.COLUMN_POSTFIX_TOKENS 6948 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 6949 ): 6950 return None 6951 6952 self._advance() 6953 return primary_parser(self, token) 6954 6955 def _parse_column(self) -> exp.Expr | None: 6956 column: exp.Expr | None = self._parse_column_parts_fast() 6957 if column is None: 6958 this = self._parse_column_reference() 6959 if not this: 6960 this = self._parse_bracket(this) 6961 column = self._parse_column_ops(this) if this else this 6962 6963 if column: 6964 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS and self._match(TokenType.JOIN_MARKER): 6965 column.set("join_mark", True) 6966 if self.COLON_IS_VARIANT_EXTRACT: 6967 column = self._parse_colon_as_variant_extract(column) 6968 6969 return column 6970 6971 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 6972 """Fast path for simple column and dot references (a, a.b, ...). 6973 6974 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 6975 that nothing complex follows. If it does, retreats and returns None so 6976 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 6977 """ 6978 index = self._index 6979 parts: list[exp.Identifier] | None = None 6980 all_comments: list[str] | None = None 6981 6982 while self._match_set(self.IDENTIFIER_TOKENS): 6983 token = self._prev 6984 comments = self._prev_comments 6985 6986 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 6987 self._retreat(index) 6988 return None 6989 6990 has_dot = self._match(TokenType.DOT) 6991 curr_tt = self._curr.token_type 6992 6993 if not has_dot: 6994 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 6995 self._retreat(index) 6996 return None 6997 elif curr_tt not in self.IDENTIFIER_TOKENS: 6998 self._retreat(index) 6999 return None 7000 7001 if parts is None: 7002 parts = [] 7003 7004 if comments: 7005 if all_comments is None: 7006 all_comments = [] 7007 all_comments.extend(comments) 7008 self._prev_comments = [] 7009 7010 parts.append( 7011 self.expression( 7012 exp.Identifier( 7013 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 7014 ), 7015 token, 7016 ) 7017 ) 7018 7019 if not has_dot: 7020 break 7021 7022 if parts is None: 7023 return None 7024 7025 n = len(parts) 7026 7027 if n == 1: 7028 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 7029 elif n == 2: 7030 column = exp.Column(this=parts[1], table=parts[0]) 7031 elif n == 3: 7032 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 7033 else: 7034 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 7035 7036 for i in range(4, n): 7037 column = exp.Dot(this=column, expression=parts[i]) 7038 7039 if all_comments: 7040 column.add_comments(all_comments) 7041 7042 return column 7043 7044 def _parse_column_reference(self) -> exp.Expr | None: 7045 this = self._parse_field() 7046 if ( 7047 not this 7048 and self._match(TokenType.VALUES, advance=False) 7049 and self.VALUES_FOLLOWED_BY_PAREN 7050 and (not self._next or self._next.token_type != TokenType.L_PAREN) 7051 ): 7052 this = self._parse_id_var() 7053 7054 if isinstance(this, exp.Identifier): 7055 # We bubble up comments from the Identifier to the Column 7056 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 7057 7058 return this 7059 7060 def _build_json_extract( 7061 self, 7062 this: exp.Expr | None, 7063 path_parts: list[exp.JSONPathPart], 7064 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 7065 if len(path_parts) > 1: 7066 this = self.expression( 7067 exp.JSONExtract( 7068 this=this, 7069 expression=exp.JSONPath(expressions=path_parts), 7070 variant_extract=True, 7071 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 7072 ) 7073 ) 7074 path_parts = [exp.JSONPathRoot()] 7075 7076 return this, path_parts 7077 7078 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 7079 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 7080 7081 while self._match(TokenType.COLON): 7082 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 7083 this, path_parts = self._build_json_extract(this, path_parts) 7084 7085 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7086 7087 if key: 7088 quoted = isinstance(key, exp.Identifier) and key.quoted 7089 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 7090 7091 while True: 7092 if self._match(TokenType.DOT): 7093 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7094 7095 if next_key: 7096 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 7097 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 7098 elif self._match(TokenType.L_BRACKET): 7099 bracket_expr = self._parse_bracket_key_value() 7100 7101 if not self._match(TokenType.R_BRACKET): 7102 self.raise_error("Expected ]") 7103 7104 if bracket_expr: 7105 if bracket_expr.is_string: 7106 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7107 elif bracket_expr.is_star: 7108 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7109 elif bracket_expr.is_number: 7110 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7111 else: 7112 this, path_parts = self._build_json_extract(this, path_parts) 7113 7114 this = self.expression( 7115 exp.Bracket( 7116 this=this, expressions=[bracket_expr], json_access=True 7117 ), 7118 ) 7119 7120 elif self._match(TokenType.DCOLON): 7121 this, path_parts = self._build_json_extract(this, path_parts) 7122 7123 cast_type = self._parse_types() 7124 if cast_type: 7125 this = self.expression(exp.Cast(this=this, to=cast_type)) 7126 else: 7127 self.raise_error("Expected type after '::'") 7128 else: 7129 break 7130 7131 this, _ = self._build_json_extract(this, path_parts) 7132 7133 return this 7134 7135 def _parse_dcolon(self) -> exp.Expr | None: 7136 return self._parse_types() 7137 7138 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7139 while self._curr.token_type in self.BRACKETS: 7140 this = self._parse_bracket(this) 7141 7142 column_operators = self.COLUMN_OPERATORS 7143 cast_column_operators = self.CAST_COLUMN_OPERATORS 7144 while self._curr: 7145 op_token = self._curr.token_type 7146 7147 if op_token not in column_operators: 7148 break 7149 op = column_operators[op_token] 7150 self._advance() 7151 7152 if op_token in cast_column_operators: 7153 field = self._parse_dcolon() 7154 if not field: 7155 self.raise_error("Expected type") 7156 elif op and self._curr: 7157 field = self._parse_column_reference() or self._parse_bitwise() 7158 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7159 field = self._parse_column_ops(field) 7160 else: 7161 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7162 field = self._parse_field(any_token=True, anonymous_func=True) 7163 7164 # In t.true, t.null we should produce an Identifier node 7165 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7166 field = self.expression( 7167 exp.Identifier(this=self._prev.text), 7168 comments=field.comments, 7169 ) 7170 7171 # Function calls can be qualified, e.g., x.y.FOO() 7172 # This converts the final AST to a series of Dots leading to the function call 7173 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7174 if isinstance(field, (exp.Func, exp.Window)) and this: 7175 this = this.transform( 7176 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7177 ) 7178 7179 if op: 7180 this = op(self, this, field) 7181 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7182 this = self.expression( 7183 exp.Column( 7184 this=field, 7185 table=this.this, 7186 db=this.args.get("table"), 7187 catalog=this.args.get("db"), 7188 ), 7189 comments=this.comments, 7190 ) 7191 elif isinstance(field, exp.Window): 7192 # Move the exp.Dot's to the window's function 7193 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7194 field.set("this", window_func) 7195 this = field 7196 else: 7197 this = self.expression(exp.Dot(this=this, expression=field)) 7198 7199 if field and field.comments: 7200 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7201 7202 this = self._parse_bracket(this) 7203 7204 return this 7205 7206 def _parse_paren(self) -> exp.Expr | None: 7207 if not self._match(TokenType.L_PAREN): 7208 return None 7209 7210 comments = self._prev_comments 7211 query = self._parse_select() 7212 7213 if query: 7214 expressions = [query] 7215 else: 7216 expressions = self._parse_expressions() 7217 7218 this = seq_get(expressions, 0) 7219 7220 if not this and self._match(TokenType.R_PAREN, advance=False): 7221 this = self.expression(exp.Tuple()) 7222 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7223 this = self.expression(exp.Tuple(expressions=expressions)) 7224 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7225 this = self._parse_subquery(this=this, parse_alias=False) 7226 elif isinstance(this, (exp.Subquery, exp.Values)): 7227 this = self._parse_subquery( 7228 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7229 parse_alias=False, 7230 ) 7231 else: 7232 this = self.expression(exp.Paren(this=this)) 7233 7234 if this: 7235 this.add_comments(comments) 7236 7237 self._match_r_paren(expression=this) 7238 7239 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7240 return self._parse_window(this) 7241 7242 return this 7243 7244 def _parse_primary(self) -> exp.Expr | None: 7245 if self._match_set(self.PRIMARY_PARSERS): 7246 token_type = self._prev.token_type 7247 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7248 7249 if token_type == TokenType.STRING: 7250 expressions = [primary] 7251 while self._match(TokenType.STRING, advance=False): 7252 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7253 self.raise_error( 7254 "Adjacent string literals need to be separated by whitespace or comments" 7255 ) 7256 7257 self._advance() 7258 expressions.append(exp.Literal.string(self._prev.text)) 7259 7260 if len(expressions) > 1: 7261 return self.expression( 7262 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7263 ) 7264 7265 return primary 7266 7267 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7268 return exp.Literal.number(f"0.{self._prev.text}") 7269 7270 return self._parse_paren() 7271 7272 def _parse_field( 7273 self, 7274 any_token: bool = False, 7275 tokens: t.Collection[TokenType] | None = None, 7276 anonymous_func: bool = False, 7277 ) -> exp.Expr | None: 7278 after_dot = ( 7279 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7280 ) 7281 7282 if anonymous_func: 7283 field = ( 7284 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7285 or self._parse_primary() 7286 ) 7287 else: 7288 field = self._parse_primary() or self._parse_function( 7289 anonymous=anonymous_func, any_token=any_token 7290 ) 7291 7292 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7293 7294 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7295 name = field.name 7296 if self._is_connected() and self._parse_var(any_token=True): 7297 name += self._prev.text 7298 7299 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7300 7301 return field 7302 7303 def _parse_function( 7304 self, 7305 functions: dict[str, t.Callable] | None = None, 7306 anonymous: bool = False, 7307 optional_parens: bool = True, 7308 any_token: bool = False, 7309 ) -> exp.Expr | None: 7310 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7311 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7312 fn_syntax = False 7313 if ( 7314 self._match(TokenType.L_BRACE, advance=False) 7315 and self._next 7316 and self._next.text.upper() == "FN" 7317 ): 7318 self._advance(2) 7319 fn_syntax = True 7320 7321 func = self._parse_function_call( 7322 functions=functions, 7323 anonymous=anonymous, 7324 optional_parens=optional_parens, 7325 any_token=any_token, 7326 ) 7327 7328 if fn_syntax: 7329 self._match(TokenType.R_BRACE) 7330 7331 return func 7332 7333 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7334 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7335 7336 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7337 args = self._parse_function_args(alias=False) 7338 if not args: 7339 self.raise_error("Expected at least one argument") 7340 7341 # Wrapped so the connector keeps its precedence in the parent context 7342 return exp.Paren(this=connector(*args, copy=False)) 7343 7344 def _parse_function_call( 7345 self, 7346 functions: dict[str, t.Callable] | None = None, 7347 anonymous: bool = False, 7348 optional_parens: bool = True, 7349 any_token: bool = False, 7350 ) -> exp.Expr | None: 7351 if not self._curr: 7352 return None 7353 7354 comments = self._curr.comments 7355 prev = self._prev 7356 token = self._curr 7357 token_type = self._curr.token_type 7358 this: str | exp.Expr = self._curr.text 7359 upper = self._curr.text.upper() 7360 7361 after_dot = prev.token_type == TokenType.DOT 7362 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7363 if ( 7364 optional_parens 7365 and parser 7366 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7367 and not after_dot 7368 ): 7369 self._advance() 7370 return self._parse_window(parser(self)) 7371 7372 if self._next.token_type != TokenType.L_PAREN: 7373 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7374 self._advance() 7375 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7376 7377 return None 7378 7379 if any_token: 7380 if token_type in self.RESERVED_TOKENS: 7381 return None 7382 elif token_type not in self.FUNC_TOKENS: 7383 return None 7384 7385 self._advance(2) 7386 7387 parser = self.FUNCTION_PARSERS.get(upper) 7388 if parser and not anonymous: 7389 result = parser(self) 7390 else: 7391 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7392 7393 if subquery_predicate: 7394 expr = None 7395 if self._curr.token_type in self.SUBQUERY_TOKENS: 7396 expr = self._parse_select() 7397 self._match_r_paren() 7398 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7399 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7400 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7401 self._advance(-1) 7402 expr = self._parse_bitwise() 7403 7404 if expr: 7405 return self.expression(subquery_predicate(this=expr), comments=comments) 7406 7407 if functions is None: 7408 functions = self.FUNCTIONS 7409 7410 function = functions.get(upper) 7411 known_function = function and not anonymous 7412 7413 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7414 args = self._parse_function_args(alias) 7415 7416 post_func_comments = self._curr.comments if self._curr else None 7417 if known_function and post_func_comments: 7418 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7419 # call we'll construct it as exp.Anonymous, even if it's "known" 7420 if any( 7421 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7422 for comment in post_func_comments 7423 ): 7424 known_function = False 7425 7426 if alias and known_function: 7427 args = self._kv_to_prop_eq(args) 7428 7429 if known_function: 7430 func_builder = t.cast(t.Callable, function) 7431 7432 # mypyc compiled functions don't have __code__, so we use 7433 # try/except to check if func_builder accepts 'dialect'. 7434 try: 7435 func = func_builder(args) 7436 except TypeError: 7437 func = func_builder(args, dialect=self.dialect) 7438 7439 func = self.validate_expression(func, args) 7440 if name_key := self.dialect.ORIGINAL_NAME_META_KEY: 7441 func.meta[name_key] = this 7442 7443 result = func 7444 else: 7445 if token_type == TokenType.IDENTIFIER: 7446 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7447 7448 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7449 7450 result = result.update_positions(token) 7451 7452 if isinstance(result, exp.Expr): 7453 result.add_comments(comments) 7454 7455 if parser: 7456 self._match(TokenType.R_PAREN, expression=result) 7457 else: 7458 self._match_r_paren(result) 7459 return self._parse_window(result) 7460 7461 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7462 return expression 7463 7464 def _kv_to_prop_eq( 7465 self, expressions: list[exp.Expr], parse_map: bool = False 7466 ) -> list[exp.Expr]: 7467 transformed = [] 7468 7469 for index, e in enumerate(expressions): 7470 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7471 if isinstance(e, exp.Alias): 7472 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7473 7474 if not isinstance(e, exp.PropertyEQ): 7475 e = self.expression( 7476 exp.PropertyEQ( 7477 this=e.this if parse_map else exp.to_identifier(e.this.name), 7478 expression=e.expression, 7479 ) 7480 ) 7481 7482 if isinstance(e.this, exp.Column): 7483 e.this.replace(e.this.this) 7484 else: 7485 e = self._to_prop_eq(e, index) 7486 7487 transformed.append(e) 7488 7489 return transformed 7490 7491 def _parse_function_properties(self) -> exp.Properties | None: 7492 # Skip the generic `key = value` fallback in _parse_property since this 7493 # runs post-AS where a function body like `name = expr` can be misread 7494 # as a property. 7495 properties = [] 7496 while True: 7497 if self._match_texts(self.PROPERTY_PARSERS): 7498 keyword = self._prev.text.upper() 7499 prop = self.PROPERTY_PARSERS[keyword](self) 7500 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7501 keyword = self._prev.text.upper() 7502 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7503 else: 7504 break 7505 if not prop: 7506 self.raise_error(f"Failed to parse property '{keyword}'") 7507 break 7508 for p in ensure_list(prop): 7509 properties.append(p) 7510 7511 return self.expression(exp.Properties(expressions=properties)) if properties else None 7512 7513 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7514 return self._parse_statement() 7515 7516 def _parse_function_parameter(self) -> exp.Expr | None: 7517 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7518 7519 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7520 this = self._parse_table_parts(schema=True) 7521 7522 if not self._match(TokenType.L_PAREN): 7523 return this 7524 7525 expressions = self._parse_csv(self._parse_function_parameter) 7526 self._match_r_paren() 7527 return self.expression( 7528 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7529 ) 7530 7531 def _parse_macro_overloads( 7532 self, 7533 this: exp.UserDefinedFunction, 7534 first_body: exp.Expr, 7535 first_is_table: bool = False, 7536 ) -> exp.MacroOverloads: 7537 overloads = [ 7538 self.expression( 7539 exp.MacroOverload( 7540 this=first_body, 7541 expressions=this.expressions or None, 7542 is_table=first_is_table, 7543 ) 7544 ) 7545 ] 7546 this.set("expressions", None) 7547 this.set("wrapped", False) 7548 7549 while self._match(TokenType.COMMA): 7550 if not self._match(TokenType.L_PAREN): 7551 break 7552 7553 params = self._parse_csv(self._parse_function_parameter) 7554 self._match_r_paren() 7555 7556 if not self._match(TokenType.ALIAS): 7557 break 7558 7559 is_table = self._match(TokenType.TABLE) 7560 body = self._parse_expression() 7561 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7562 overloads.append(self.expression(macro)) 7563 7564 return self.expression(exp.MacroOverloads(expressions=overloads)) 7565 7566 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7567 literal = self._parse_primary() 7568 if literal: 7569 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7570 7571 return self._identifier_expression(token) 7572 7573 def _parse_session_parameter(self) -> exp.SessionParameter: 7574 kind = None 7575 this = self._parse_id_var() or self._parse_primary() 7576 7577 if this and self._match(TokenType.DOT): 7578 kind = this.name 7579 this = self._parse_var() or self._parse_primary() 7580 7581 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7582 7583 def _parse_lambda_arg(self) -> exp.Expr | None: 7584 return self._parse_id_var() 7585 7586 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7587 next_token_type = self._next.token_type 7588 7589 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7590 if ( 7591 next_token_type in self.LAMBDA_ARG_TERMINATORS 7592 and (atom := self._parse_atom()) is not None 7593 ): 7594 return atom 7595 7596 index = self._index 7597 7598 if self._match(TokenType.L_PAREN): 7599 expressions = t.cast( 7600 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7601 ) 7602 7603 if not self._match(TokenType.R_PAREN): 7604 self._retreat(index) 7605 elif self._match_set(self.LAMBDAS): 7606 return self.LAMBDAS[self._prev.token_type](self, expressions) 7607 else: 7608 self._retreat(index) 7609 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7610 expressions = [self._parse_lambda_arg()] 7611 7612 if self._match_set(self.LAMBDAS): 7613 return self.LAMBDAS[self._prev.token_type](self, expressions) 7614 7615 self._retreat(index) 7616 7617 this: exp.Expr | None 7618 7619 if self._match(TokenType.DISTINCT): 7620 this = self.expression( 7621 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7622 ) 7623 else: 7624 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7625 this = self._parse_select_or_expression(alias=alias) 7626 7627 return self._parse_limit( 7628 self._parse_respect_or_ignore_nulls( 7629 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7630 ) 7631 ) 7632 7633 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7634 index = self._index 7635 if not self._match(TokenType.L_PAREN): 7636 return this 7637 7638 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7639 # expr can be of both types 7640 if self._match_set(self.SELECT_START_TOKENS): 7641 self._retreat(index) 7642 return this 7643 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7644 self._match_r_paren() 7645 return self.expression(exp.Schema(this=this, expressions=args)) 7646 7647 def _parse_field_def(self) -> exp.Expr | None: 7648 return self._parse_column_def(self._parse_field(any_token=True)) 7649 7650 def _parse_column_def( 7651 self, this: exp.Expr | None, computed_column: bool = True 7652 ) -> exp.Expr | None: 7653 # column defs are not really columns, they're identifiers 7654 if isinstance(this, exp.Column): 7655 this = this.this 7656 7657 if not computed_column: 7658 self._match(TokenType.ALIAS) 7659 7660 kind = self._parse_types(schema=True) 7661 7662 if self._match_text_seq("FOR", "ORDINALITY"): 7663 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7664 7665 constraints: list[exp.Expr] = [] 7666 7667 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7668 ("ALIAS", "MATERIALIZED") 7669 ): 7670 # Match storage before _parse_types so STORED is not treated as a data type 7671 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7672 persisted = self._prev.text.upper() == "MATERIALIZED" 7673 expression = self._parse_disjunction() 7674 if not persisted: 7675 if self._match_text_seq("PERSISTED"): 7676 persisted = True 7677 elif self._match_texts(("STORED", "VIRTUAL")): 7678 persisted = self._prev.text.upper() == "STORED" 7679 constraint_kind = exp.ComputedColumnConstraint( 7680 this=expression, 7681 persisted=persisted, 7682 data_type=exp.Var(this="AUTO") 7683 if self._match_text_seq("AUTO") 7684 else self._parse_types(), 7685 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7686 ) 7687 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7688 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7689 in_out_constraint = self.expression( 7690 exp.InOutColumnConstraint( 7691 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7692 ) 7693 ) 7694 constraints.append(in_out_constraint) 7695 kind = self._parse_types() 7696 elif ( 7697 kind 7698 and self._match(TokenType.ALIAS, advance=False) 7699 and ( 7700 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7701 or self._next.token_type == TokenType.L_PAREN 7702 ) 7703 ): 7704 self._advance() 7705 constraints.append( 7706 self.expression( 7707 exp.ColumnConstraint( 7708 kind=exp.ComputedColumnConstraint( 7709 this=self._parse_disjunction(), 7710 persisted=self._match_texts(("STORED", "VIRTUAL")) 7711 and self._prev.text.upper() == "STORED", 7712 ) 7713 ) 7714 ) 7715 ) 7716 7717 while True: 7718 constraint = self._parse_column_constraint() 7719 if not constraint: 7720 break 7721 constraints.append(constraint) 7722 7723 if not kind and not constraints: 7724 return this 7725 7726 position = None 7727 if self._match_texts(("FIRST", "AFTER")): 7728 pos = self._prev.text 7729 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7730 7731 return self.expression( 7732 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7733 ) 7734 7735 def _parse_auto_increment( 7736 self, 7737 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7738 start = None 7739 increment = None 7740 order = None 7741 7742 if self._match(TokenType.L_PAREN, advance=False): 7743 args = self._parse_wrapped_csv(self._parse_bitwise) 7744 start = seq_get(args, 0) 7745 increment = seq_get(args, 1) 7746 7747 # The remaining parts form an unordered bag and any of them can be omitted, in which 7748 # case the engine falls back to its own default, so they're parsed independently. 7749 while True: 7750 if self._match_text_seq("START"): 7751 start = self._parse_bitwise() 7752 elif self._match_text_seq("INCREMENT"): 7753 increment = self._parse_bitwise() 7754 elif self._match_text_seq("ORDER"): 7755 order = True 7756 elif self._match_text_seq("NOORDER"): 7757 order = False 7758 else: 7759 break 7760 7761 if start or increment or order is not None: 7762 return exp.GeneratedAsIdentityColumnConstraint( 7763 start=start, increment=increment, this=False, order=order 7764 ) 7765 7766 return exp.AutoIncrementColumnConstraint() 7767 7768 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7769 if not self._match(TokenType.L_PAREN, advance=False): 7770 return None 7771 7772 return self.expression( 7773 exp.CheckColumnConstraint( 7774 this=self._parse_wrapped(self._parse_assignment), 7775 enforced=self._match_text_seq("ENFORCED"), 7776 ) 7777 ) 7778 7779 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7780 if not self._match_text_seq("REFRESH"): 7781 self._retreat(self._index - 1) 7782 return None 7783 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7784 7785 def _parse_compress(self) -> exp.CompressColumnConstraint: 7786 if self._match(TokenType.L_PAREN, advance=False): 7787 return self.expression( 7788 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7789 ) 7790 7791 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7792 7793 def _parse_generated_as_identity( 7794 self, 7795 ) -> ( 7796 exp.GeneratedAsIdentityColumnConstraint 7797 | exp.ComputedColumnConstraint 7798 | exp.GeneratedAsRowColumnConstraint 7799 ): 7800 if self._match_text_seq("BY", "DEFAULT"): 7801 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7802 this = self.expression( 7803 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7804 ) 7805 else: 7806 self._match_text_seq("ALWAYS") 7807 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7808 7809 self._match(TokenType.ALIAS) 7810 7811 if self._match_text_seq("ROW"): 7812 start = self._match_text_seq("START") 7813 if not start: 7814 self._match(TokenType.END) 7815 hidden = self._match_text_seq("HIDDEN") 7816 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7817 7818 identity = self._match_text_seq("IDENTITY") 7819 7820 if self._match(TokenType.L_PAREN): 7821 if self._match_text_seq("START", "WITH"): 7822 this.set("start", self._parse_bitwise()) 7823 if self._match_text_seq("INCREMENT", "BY"): 7824 this.set("increment", self._parse_bitwise()) 7825 if self._match_text_seq("MINVALUE"): 7826 this.set("minvalue", self._parse_bitwise()) 7827 if self._match_text_seq("MAXVALUE"): 7828 this.set("maxvalue", self._parse_bitwise()) 7829 7830 if self._match_text_seq("CYCLE"): 7831 this.set("cycle", True) 7832 elif self._match_text_seq("NO", "CYCLE"): 7833 this.set("cycle", False) 7834 7835 if not identity: 7836 this.set("expression", self._parse_range()) 7837 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7838 args = self._parse_csv(self._parse_bitwise) 7839 this.set("start", seq_get(args, 0)) 7840 this.set("increment", seq_get(args, 1)) 7841 7842 self._match_r_paren() 7843 7844 return this 7845 7846 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7847 self._match_text_seq("LENGTH") 7848 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7849 7850 def _parse_not_constraint(self) -> exp.Expr | None: 7851 if self._match_text_seq("NULL"): 7852 return self.expression(exp.NotNullColumnConstraint()) 7853 if self._match_text_seq("CASESPECIFIC"): 7854 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7855 if self._match_text_seq("FOR", "REPLICATION"): 7856 return self.expression(exp.NotForReplicationColumnConstraint()) 7857 7858 # Unconsume the `NOT` token 7859 self._retreat(self._index - 1) 7860 return None 7861 7862 def _parse_column_constraint(self) -> exp.Expr | None: 7863 this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None 7864 7865 procedure_option_follows = ( 7866 self._match(TokenType.WITH, advance=False) 7867 and self._next 7868 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7869 ) 7870 7871 index = self._index 7872 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 7873 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 7874 if not constraint: 7875 self._retreat(index) 7876 return None 7877 7878 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 7879 7880 if self._match(TokenType.CHARACTER_SET) or self._match_pair(TokenType.CHAR, TokenType.SET): 7881 return self.expression( 7882 exp.ColumnConstraint( 7883 this=this, 7884 kind=self.expression( 7885 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 7886 ), 7887 ) 7888 ) 7889 7890 return this 7891 7892 def _parse_constraint(self) -> exp.Expr | None: 7893 if not self._match(TokenType.CONSTRAINT): 7894 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 7895 7896 return self.expression( 7897 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 7898 ) 7899 7900 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 7901 constraints = [] 7902 while True: 7903 constraint = self._parse_unnamed_constraint() or self._parse_function() 7904 if not constraint: 7905 break 7906 constraints.append(constraint) 7907 7908 return constraints 7909 7910 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 7911 index = self._index 7912 7913 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 7914 constraints or self.CONSTRAINT_PARSERS 7915 ): 7916 return None 7917 7918 constraint_key = self._prev.text.upper() 7919 if constraint_key not in self.CONSTRAINT_PARSERS: 7920 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 7921 7922 result = self.CONSTRAINT_PARSERS[constraint_key](self) 7923 if not result: 7924 self._retreat(index) 7925 7926 return result 7927 7928 def _parse_unique_key(self) -> exp.Expr | None: 7929 if ( 7930 self._curr 7931 and self._curr.token_type != TokenType.IDENTIFIER 7932 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 7933 ): 7934 return None 7935 return self._parse_id_var(any_token=False) 7936 7937 def _parse_unique(self) -> exp.UniqueColumnConstraint: 7938 self._match_texts(("KEY", "INDEX")) 7939 return self.expression( 7940 exp.UniqueColumnConstraint( 7941 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 7942 this=self._parse_schema(self._parse_unique_key()), 7943 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 7944 on_conflict=self._parse_on_conflict(), 7945 options=self._parse_key_constraint_options(), 7946 ) 7947 ) 7948 7949 def _parse_key_constraint_options(self) -> list[str]: 7950 options = [] 7951 while True: 7952 if not self._curr: 7953 break 7954 7955 if self._match(TokenType.ON): 7956 action = None 7957 on = self._advance_any() and self._prev.text 7958 7959 if self._match_text_seq("NO", "ACTION"): 7960 action = "NO ACTION" 7961 elif self._match_text_seq("CASCADE"): 7962 action = "CASCADE" 7963 elif self._match_text_seq("RESTRICT"): 7964 action = "RESTRICT" 7965 elif self._match_pair(TokenType.SET, TokenType.NULL): 7966 action = "SET NULL" 7967 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 7968 action = "SET DEFAULT" 7969 else: 7970 self.raise_error("Invalid key constraint") 7971 7972 options.append(f"ON {on} {action}") 7973 else: 7974 var = self._parse_var_from_options( 7975 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 7976 ) 7977 if not var: 7978 break 7979 options.append(var.name) 7980 7981 return options 7982 7983 def _parse_references(self, match: bool = True) -> exp.Reference | None: 7984 if match and not self._match(TokenType.REFERENCES): 7985 return None 7986 7987 expressions: list | None = None 7988 this = self._parse_table(schema=True) 7989 options = self._parse_key_constraint_options() 7990 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 7991 7992 def _parse_foreign_key(self) -> exp.ForeignKey: 7993 expressions = ( 7994 self._parse_wrapped_id_vars() 7995 if not self._match(TokenType.REFERENCES, advance=False) 7996 else None 7997 ) 7998 reference = self._parse_references() 7999 on_options = {} 8000 8001 while self._match(TokenType.ON): 8002 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 8003 self.raise_error("Expected DELETE or UPDATE") 8004 8005 kind = self._prev.text.lower() 8006 8007 if self._match_text_seq("NO", "ACTION"): 8008 action = "NO ACTION" 8009 elif self._match(TokenType.SET): 8010 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 8011 action = "SET " + self._prev.text.upper() 8012 else: 8013 self._advance() 8014 action = self._prev.text.upper() 8015 8016 on_options[kind] = action 8017 8018 return self.expression( 8019 exp.ForeignKey( 8020 expressions=expressions, 8021 reference=reference, 8022 options=self._parse_key_constraint_options(), 8023 **on_options, 8024 ) 8025 ) 8026 8027 def _parse_primary_key_part(self) -> exp.Expr | None: 8028 return self._parse_field() 8029 8030 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 8031 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 8032 self._retreat(self._index - 1) 8033 return None 8034 8035 id_vars = self._parse_wrapped_id_vars() 8036 return self.expression( 8037 exp.PeriodForSystemTimeConstraint( 8038 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 8039 ) 8040 ) 8041 8042 def _parse_primary_key( 8043 self, 8044 wrapped_optional: bool = False, 8045 in_props: bool = False, 8046 named_primary_key: bool = False, 8047 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 8048 desc = ( 8049 self._prev.token_type == TokenType.DESC 8050 if self._match_set((TokenType.ASC, TokenType.DESC)) 8051 else None 8052 ) 8053 8054 this = None 8055 if ( 8056 named_primary_key 8057 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 8058 and self._next 8059 and self._next.token_type == TokenType.L_PAREN 8060 ): 8061 this = self._parse_id_var() 8062 8063 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 8064 return self.expression( 8065 exp.PrimaryKeyColumnConstraint( 8066 desc=desc, options=self._parse_key_constraint_options() 8067 ) 8068 ) 8069 8070 expressions = self._parse_wrapped_csv( 8071 self._parse_primary_key_part, optional=wrapped_optional 8072 ) 8073 8074 return self.expression( 8075 exp.PrimaryKey( 8076 this=this, 8077 expressions=expressions, 8078 include=self._parse_index_params(), 8079 options=self._parse_key_constraint_options(), 8080 ) 8081 ) 8082 8083 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 8084 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 8085 8086 def _parse_odbc_datetime_literal(self) -> exp.Expr: 8087 """ 8088 Parses a datetime column in ODBC format. We parse the column into the corresponding 8089 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 8090 same as we did for `DATE('yyyy-mm-dd')`. 8091 8092 Reference: 8093 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 8094 """ 8095 self._match(TokenType.VAR) 8096 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 8097 expression = self.expression(exp_class(this=self._parse_string())) 8098 if not self._match(TokenType.R_BRACE): 8099 self.raise_error("Expected }") 8100 return expression 8101 8102 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 8103 if not self._match_set(self.BRACKETS): 8104 return this 8105 8106 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8107 map_token = seq_get(self._tokens, self._index - 2) 8108 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8109 else: 8110 parse_map = False 8111 8112 bracket_kind = self._prev.token_type 8113 if ( 8114 bracket_kind == TokenType.L_BRACE 8115 and self._curr 8116 and self._curr.token_type == TokenType.VAR 8117 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8118 ): 8119 return self._parse_odbc_datetime_literal() 8120 8121 expressions = self._parse_csv( 8122 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8123 ) 8124 8125 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8126 self.raise_error("Expected ]") 8127 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8128 self.raise_error("Expected }") 8129 8130 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8131 if bracket_kind == TokenType.L_BRACE: 8132 this = self.expression( 8133 exp.Struct( 8134 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8135 ) 8136 ) 8137 elif not this: 8138 this = build_array_constructor( 8139 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8140 ) 8141 else: 8142 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8143 if constructor_type: 8144 return build_array_constructor( 8145 constructor_type, 8146 args=expressions, 8147 bracket_kind=bracket_kind, 8148 dialect=self.dialect, 8149 ) 8150 8151 expressions = apply_index_offset( 8152 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8153 ) 8154 this = self.expression( 8155 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8156 ) 8157 8158 self._add_comments(this) 8159 return self._parse_bracket(this) 8160 8161 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8162 if not self._match(TokenType.COLON): 8163 return this 8164 8165 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8166 self._advance() 8167 end: exp.Expr | None = -exp.Literal.number("1") 8168 else: 8169 end = self._parse_assignment() 8170 step = self._parse_unary() if self._match(TokenType.COLON) else None 8171 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8172 8173 def _parse_case(self) -> exp.Expr | None: 8174 if self._match(TokenType.DOT, advance=False): 8175 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8176 self._retreat(self._index - 1) 8177 return None 8178 8179 ifs = [] 8180 default = None 8181 8182 comments = self._prev_comments 8183 expression = self._parse_disjunction() 8184 8185 while self._match(TokenType.WHEN): 8186 this = self._parse_disjunction() 8187 self._match(TokenType.THEN) 8188 then = self._parse_disjunction() 8189 ifs.append(self.expression(exp.If(this=this, true=then))) 8190 8191 if self._match(TokenType.ELSE): 8192 default = self._parse_disjunction() 8193 8194 if not self._match(TokenType.END): 8195 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8196 default = exp.column("interval") 8197 else: 8198 self.raise_error("Expected END after CASE", self._prev) 8199 8200 return self.expression( 8201 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8202 ) 8203 8204 def _parse_if(self) -> exp.Expr | None: 8205 if self._match(TokenType.L_PAREN): 8206 args = self._parse_csv( 8207 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8208 ) 8209 this = self.validate_expression(exp.If.from_arg_list(args), args) 8210 self._match_r_paren() 8211 else: 8212 index = self._index - 1 8213 8214 if self.NO_PAREN_IF_COMMANDS and index == 0: 8215 return self._parse_as_command(self._prev) 8216 8217 condition = self._parse_disjunction() 8218 8219 if not condition or not self._match(TokenType.THEN): 8220 self._retreat(index) 8221 return None 8222 8223 true = self._parse_disjunction() 8224 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8225 self._match(TokenType.END) 8226 this = self.expression(exp.If(this=condition, true=true, false=false)) 8227 8228 return this 8229 8230 def _parse_next_value_for(self) -> exp.Expr | None: 8231 if not self._match_text_seq("VALUE", "FOR"): 8232 self._retreat(self._index - 1) 8233 return None 8234 8235 return self.expression( 8236 exp.NextValueFor( 8237 this=self._parse_column(), 8238 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8239 ) 8240 ) 8241 8242 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8243 this = self._parse_function() or self._parse_var_or_string(upper=True) 8244 8245 if self._match(TokenType.FROM): 8246 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8247 8248 if not self._match(TokenType.COMMA): 8249 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8250 8251 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8252 8253 def _parse_gap_fill(self) -> exp.GapFill: 8254 self._match(TokenType.TABLE) 8255 this = self._parse_table() 8256 8257 self._match(TokenType.COMMA) 8258 args = [this, *self._parse_csv(self._parse_lambda)] 8259 8260 gap_fill = exp.GapFill.from_arg_list(args) 8261 return self.validate_expression(gap_fill, args) 8262 8263 def _parse_char(self) -> exp.Chr: 8264 return self.expression( 8265 exp.Chr( 8266 expressions=self._parse_csv(self._parse_assignment), 8267 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8268 ) 8269 ) 8270 8271 def _parse_charset_name(self) -> exp.Expr | None: 8272 """ 8273 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8274 for specific name shapes override this. 8275 """ 8276 return self._parse_var( 8277 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8278 ) 8279 8280 def _parse_weight_string(self) -> exp.WeightString | exp.Anonymous: 8281 name = self._tokens[self._index - 2].text 8282 args = self._parse_csv(self._parse_assignment) 8283 8284 # MySQL also accepts an undocumented WEIGHT_STRING(str, n, n, n) form 8285 if len(args) != 1: 8286 return self.expression(exp.Anonymous(this=name, expressions=args)) 8287 8288 return self.expression( 8289 exp.WeightString(this=args[0], to=self._match(TokenType.ALIAS) and self._parse_types()) 8290 ) 8291 8292 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8293 this = self._parse_assignment() 8294 8295 if not self._match(TokenType.ALIAS): 8296 if self._match(TokenType.COMMA): 8297 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8298 8299 self.raise_error("Expected AS after CAST") 8300 8301 fmt = None 8302 to = self._parse_types(with_collation=True) 8303 8304 default = None 8305 if self._match(TokenType.DEFAULT): 8306 default = self._parse_bitwise() 8307 self._match_text_seq("ON", "CONVERSION", "ERROR") 8308 8309 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8310 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8311 fmt = self._parse_at_time_zone(fmt_string) 8312 8313 if not to: 8314 to = exp.DType.UNKNOWN.into_expr() 8315 if to.this in exp.DataType.TEMPORAL_TYPES: 8316 this = self.expression( 8317 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8318 this=this, 8319 format=exp.Literal.string( 8320 format_time( 8321 fmt_string.this if fmt_string else "", 8322 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8323 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8324 ) 8325 ), 8326 safe=safe, 8327 ) 8328 ) 8329 8330 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8331 this.set("zone", fmt.args["zone"]) 8332 return this 8333 elif not to: 8334 self.raise_error("Expected TYPE after CAST") 8335 elif isinstance(to, exp.Identifier): 8336 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8337 else: 8338 to = self._parse_char_charset(to) 8339 8340 return self.build_cast( 8341 strict=strict, 8342 this=this, 8343 to=to, 8344 format=fmt, 8345 safe=safe, 8346 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8347 default=default, 8348 ) 8349 8350 def _parse_string_agg(self) -> exp.GroupConcat: 8351 if self._match(TokenType.DISTINCT): 8352 args: list[exp.Expr | None] = [ 8353 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8354 ] 8355 if self._match(TokenType.COMMA): 8356 args.extend(self._parse_csv(self._parse_disjunction)) 8357 else: 8358 args = self._parse_csv(self._parse_disjunction) # type: ignore 8359 8360 if self._match_text_seq("ON", "OVERFLOW"): 8361 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8362 if self._match_text_seq("ERROR"): 8363 on_overflow: exp.Expr | None = exp.var("ERROR") 8364 else: 8365 self._match_text_seq("TRUNCATE") 8366 on_overflow = self.expression( 8367 exp.OverflowTruncateBehavior( 8368 this=self._parse_string(), 8369 with_count=( 8370 self._match_text_seq("WITH", "COUNT") 8371 or not self._match_text_seq("WITHOUT", "COUNT") 8372 ), 8373 ) 8374 ) 8375 else: 8376 on_overflow = None 8377 8378 index = self._index 8379 if not self._match(TokenType.R_PAREN) and args: 8380 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8381 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8382 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8383 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8384 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8385 8386 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8387 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8388 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8389 if not self._match_text_seq("WITHIN", "GROUP"): 8390 self._retreat(index) 8391 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8392 8393 # The corresponding match_r_paren will be called in parse_function (caller) 8394 self._match_l_paren() 8395 8396 return self.expression( 8397 exp.GroupConcat( 8398 this=self._parse_order(this=seq_get(args, 0)), 8399 separator=seq_get(args, 1), 8400 on_overflow=on_overflow, 8401 ) 8402 ) 8403 8404 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8405 this = self._parse_bitwise() 8406 8407 if self._match(TokenType.USING): 8408 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8409 elif self._match(TokenType.COMMA): 8410 to = self._parse_char_charset(self._parse_types()) 8411 else: 8412 to = None 8413 8414 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8415 8416 def _parse_char_charset(self, to: exp.Expr | None) -> exp.Expr | None: 8417 # MySQL: CHAR[(N)] CHARACTER SET charset_name, the length is kept 8418 if ( 8419 isinstance(to, exp.DataType) 8420 and to.this == exp.DType.CHAR 8421 and (self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")) 8422 ): 8423 to.set("this", exp.DType.CHARACTER_SET) 8424 to.set("kind", self._parse_var_or_string()) 8425 return to 8426 8427 def _parse_xml_element(self) -> exp.XMLElement: 8428 if self._match_text_seq("EVALNAME"): 8429 evalname = True 8430 this = self._parse_bitwise() 8431 else: 8432 evalname = None 8433 self._match_text_seq("NAME") 8434 this = self._parse_id_var() 8435 8436 return self.expression( 8437 exp.XMLElement( 8438 this=this, 8439 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8440 evalname=evalname, 8441 ) 8442 ) 8443 8444 def _parse_xml_table(self) -> exp.XMLTable: 8445 namespaces = None 8446 passing = None 8447 columns = None 8448 8449 if self._match_text_seq("XMLNAMESPACES", "("): 8450 namespaces = self._parse_xml_namespace() 8451 self._match_text_seq(")", ",") 8452 8453 this = self._parse_string() 8454 8455 if self._match_text_seq("PASSING"): 8456 # The BY VALUE keywords are optional and are provided for semantic clarity 8457 self._match_text_seq("BY", "VALUE") 8458 passing = self._parse_csv(self._parse_column) 8459 8460 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8461 8462 if self._match_text_seq("COLUMNS"): 8463 columns = self._parse_csv(self._parse_field_def) 8464 8465 return self.expression( 8466 exp.XMLTable( 8467 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8468 ) 8469 ) 8470 8471 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8472 namespaces = [] 8473 8474 while True: 8475 if self._match(TokenType.DEFAULT): 8476 uri = self._parse_string() 8477 else: 8478 uri = self._parse_alias(self._parse_string()) 8479 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8480 if not self._match(TokenType.COMMA): 8481 break 8482 8483 return namespaces 8484 8485 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8486 args = self._parse_csv(self._parse_disjunction) 8487 8488 if len(args) < 3: 8489 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8490 8491 return self.expression(exp.DecodeCase(expressions=args)) 8492 8493 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8494 self._match_text_seq("KEY") 8495 key = self._parse_column() 8496 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8497 self._match_text_seq("VALUE") 8498 value = self._parse_bitwise() 8499 8500 if not key and not value: 8501 return None 8502 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8503 8504 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8505 if not this or not self._match_text_seq("FORMAT", "JSON"): 8506 return this 8507 8508 return self.expression(exp.FormatJson(this=this)) 8509 8510 def _parse_on_condition(self) -> exp.OnCondition | None: 8511 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8512 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8513 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8514 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8515 else: 8516 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8517 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8518 8519 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8520 8521 if not empty and not error and not null: 8522 return None 8523 8524 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8525 8526 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8527 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8528 for value in values: 8529 if self._match_text_seq(value, "ON", on): 8530 return f"{value} ON {on}" 8531 8532 index = self._index 8533 if self._match(TokenType.DEFAULT): 8534 default_value = self._parse_bitwise() 8535 if self._match_text_seq("ON", on): 8536 return default_value 8537 8538 self._retreat(index) 8539 8540 return None 8541 8542 @t.overload 8543 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8544 8545 @t.overload 8546 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8547 8548 def _parse_json_object(self, agg=False): 8549 star = self._parse_star() 8550 expressions = ( 8551 [star] 8552 if star 8553 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8554 ) 8555 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8556 8557 unique_keys = None 8558 if self._match_text_seq("WITH", "UNIQUE"): 8559 unique_keys = True 8560 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8561 unique_keys = False 8562 8563 self._match_text_seq("KEYS") 8564 8565 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8566 self._parse_type() 8567 ) 8568 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8569 8570 return self.expression( 8571 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8572 expressions=expressions, 8573 null_handling=null_handling, 8574 unique_keys=unique_keys, 8575 return_type=return_type, 8576 encoding=encoding, 8577 ) 8578 ) 8579 8580 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8581 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8582 if not self._match_text_seq("NESTED"): 8583 this = self._parse_id_var() 8584 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8585 kind = self._parse_types(allow_identifiers=False) 8586 nested = None 8587 else: 8588 this = None 8589 ordinality = None 8590 kind = None 8591 nested = True 8592 8593 format_json = self._match_text_seq("FORMAT", "JSON") 8594 path = self._match_text_seq("PATH") and self._parse_string() 8595 nested_schema = nested and self._parse_json_schema() 8596 8597 return self.expression( 8598 exp.JSONColumnDef( 8599 this=this, 8600 kind=kind, 8601 path=path, 8602 nested_schema=nested_schema, 8603 ordinality=ordinality, 8604 format_json=format_json, 8605 ) 8606 ) 8607 8608 def _parse_json_schema(self) -> exp.JSONSchema: 8609 self._match_text_seq("COLUMNS") 8610 return self.expression( 8611 exp.JSONSchema( 8612 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8613 ) 8614 ) 8615 8616 def _parse_json_table(self) -> exp.JSONTable: 8617 this = self._parse_format_json(self._parse_bitwise()) 8618 path = self._match(TokenType.COMMA) and self._parse_string() 8619 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8620 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8621 schema = self._parse_json_schema() 8622 8623 return exp.JSONTable( 8624 this=this, 8625 schema=schema, 8626 path=path, 8627 error_handling=error_handling, 8628 empty_handling=empty_handling, 8629 ) 8630 8631 def _parse_match_against(self) -> exp.MatchAgainst: 8632 if self._match_text_seq("TABLE"): 8633 # parse SingleStore MATCH(TABLE ...) syntax 8634 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8635 expressions = [] 8636 table = self._parse_table() 8637 if table: 8638 expressions = [table] 8639 else: 8640 expressions = self._parse_csv(self._parse_column) 8641 8642 self._match_text_seq(")", "AGAINST", "(") 8643 8644 this = self._parse_string() 8645 8646 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8647 modifier = "IN NATURAL LANGUAGE MODE" 8648 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8649 modifier = f"{modifier} WITH QUERY EXPANSION" 8650 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8651 modifier = "IN BOOLEAN MODE" 8652 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8653 modifier = "WITH QUERY EXPANSION" 8654 else: 8655 modifier = None 8656 8657 return self.expression( 8658 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8659 ) 8660 8661 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8662 def _parse_open_json(self) -> exp.OpenJSON: 8663 this = self._parse_bitwise() 8664 path = self._match(TokenType.COMMA) and self._parse_string() 8665 8666 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8667 this = self._parse_field(any_token=True) 8668 kind = self._parse_types() 8669 path = self._parse_string() 8670 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8671 8672 return self.expression( 8673 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8674 ) 8675 8676 expressions = None 8677 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8678 self._match_l_paren() 8679 expressions = self._parse_csv(_parse_open_json_column_def) 8680 8681 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8682 8683 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8684 args = self._parse_csv(self._parse_bitwise) 8685 8686 if self._match(TokenType.IN): 8687 return self.expression( 8688 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8689 ) 8690 8691 if haystack_first: 8692 haystack = seq_get(args, 0) 8693 needle = seq_get(args, 1) 8694 else: 8695 haystack = seq_get(args, 1) 8696 needle = seq_get(args, 0) 8697 8698 return self.expression( 8699 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8700 ) 8701 8702 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8703 args = self._parse_csv(self._parse_table) 8704 return exp.JoinHint(this=func_name.upper(), expressions=args) 8705 8706 def _parse_substring(self) -> exp.Substring: 8707 # Postgres supports the form: substring(string [from int] [for int]) 8708 # (despite being undocumented, the reverse order also works) 8709 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8710 8711 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8712 8713 start, length = None, None 8714 8715 while self._curr: 8716 if self._match(TokenType.FROM): 8717 start = self._parse_bitwise() 8718 elif self._match(TokenType.FOR): 8719 if not start: 8720 start = exp.Literal.number(1) 8721 length = self._parse_bitwise() 8722 else: 8723 break 8724 8725 if start: 8726 args.append(start) 8727 if length: 8728 args.append(length) 8729 8730 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8731 8732 def _parse_trim(self) -> exp.Trim: 8733 # https://www.w3resource.com/sql/character-functions/trim.php 8734 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8735 8736 position = None 8737 collation = None 8738 expression = None 8739 8740 if self._match_texts(self.TRIM_TYPES): 8741 position = self._prev.text.upper() 8742 8743 this = self._parse_bitwise() 8744 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8745 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8746 expression = self._parse_bitwise() 8747 8748 if invert_order: 8749 this, expression = expression, this 8750 8751 if self._match(TokenType.COLLATE): 8752 collation = self._parse_bitwise() 8753 8754 return self.expression( 8755 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8756 ) 8757 8758 def _parse_window_clause(self) -> list[exp.Expr] | None: 8759 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8760 8761 def _parse_named_window(self) -> exp.Expr | None: 8762 return self._parse_window(self._parse_id_var(), alias=True) 8763 8764 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8765 if self._curr.token_type == TokenType.VAR: 8766 if self._match_text_seq("IGNORE", "NULLS"): 8767 return self.expression(exp.IgnoreNulls(this=this)) 8768 if self._match_text_seq("RESPECT", "NULLS"): 8769 return self.expression(exp.RespectNulls(this=this)) 8770 return this 8771 8772 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8773 if self._match(TokenType.HAVING): 8774 self._match_texts(("MAX", "MIN")) 8775 max = self._prev.text.upper() != "MIN" 8776 return self.expression( 8777 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8778 ) 8779 8780 return this 8781 8782 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8783 func = this 8784 comments = func.comments if isinstance(func, exp.Expr) else None 8785 8786 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8787 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8788 if self._match_text_seq("FROM", "FIRST"): 8789 this.set("from_first", True) 8790 elif self._match_text_seq("FROM", "LAST"): 8791 this.set("from_first", False) 8792 8793 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8794 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8795 if self._match_text_seq("WITHIN", "GROUP"): 8796 order = self._parse_wrapped(self._parse_order) 8797 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8798 8799 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8800 self._match(TokenType.WHERE) 8801 this = self.expression( 8802 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8803 ) 8804 self._match_r_paren() 8805 8806 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8807 # Some dialects choose to implement and some do not. 8808 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8809 8810 # There is some code above in _parse_lambda that handles 8811 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8812 8813 # The below changes handle 8814 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8815 8816 # Oracle allows both formats 8817 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8818 # and Snowflake chose to do the same for familiarity 8819 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8820 if isinstance(this, exp.AggFunc): 8821 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8822 8823 if ignore_respect and ignore_respect is not this: 8824 ignore_respect.replace(ignore_respect.this) 8825 this = self.expression(ignore_respect.__class__(this=this)) 8826 8827 this = self._parse_respect_or_ignore_nulls(this) 8828 8829 # bigquery select from window x AS (partition by ...) 8830 if alias: 8831 over = None 8832 self._match(TokenType.ALIAS) 8833 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8834 return this 8835 else: 8836 over = self._prev.text.upper() 8837 8838 if comments and isinstance(func, exp.Expr): 8839 func.pop_comments() 8840 8841 if not self._match(TokenType.L_PAREN): 8842 return self.expression( 8843 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8844 ) 8845 8846 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8847 8848 first: bool | None = True if self._match(TokenType.FIRST) else None 8849 if self._match_text_seq("LAST"): 8850 first = False 8851 8852 partition, order = self._parse_partition_and_order() 8853 kind = ( 8854 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8855 ) and self._prev.text 8856 8857 if kind: 8858 self._match(TokenType.BETWEEN) 8859 start = self._parse_window_spec() 8860 8861 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8862 exclude = ( 8863 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8864 if self._match_text_seq("EXCLUDE") 8865 else None 8866 ) 8867 8868 spec = self.expression( 8869 exp.WindowSpec( 8870 kind=kind, 8871 start=start["value"], 8872 start_side=start["side"], 8873 end=end.get("value"), 8874 end_side=end.get("side"), 8875 exclude=exclude, 8876 ) 8877 ) 8878 else: 8879 spec = None 8880 8881 self._match_r_paren() 8882 8883 window = self.expression( 8884 exp.Window( 8885 this=this, 8886 partition_by=partition, 8887 order=order, 8888 spec=spec, 8889 alias=window_alias, 8890 over=over, 8891 first=first, 8892 ), 8893 comments=comments, 8894 ) 8895 8896 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 8897 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 8898 return self._parse_window(window, alias=alias) 8899 8900 return window 8901 8902 def _parse_partition_and_order( 8903 self, 8904 ) -> tuple[list[exp.Expr], exp.Expr | None]: 8905 return self._parse_partition_by(), self._parse_order() 8906 8907 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 8908 self._match(TokenType.BETWEEN) 8909 8910 return { 8911 "value": ( 8912 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 8913 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 8914 or self._parse_bitwise() 8915 ), 8916 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 8917 } 8918 8919 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 8920 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 8921 # so this section tries to parse the clause version and if it fails, it treats the token 8922 # as an identifier (alias) 8923 if self._can_parse_limit_or_offset(): 8924 return this 8925 8926 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 8927 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 8928 if self._can_parse_named_window(): 8929 return this 8930 8931 any_token = self._match(TokenType.ALIAS) 8932 comments = self._prev_comments 8933 8934 if explicit and not any_token: 8935 return this 8936 8937 if self._match(TokenType.L_PAREN): 8938 aliases = self.expression( 8939 exp.Aliases( 8940 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 8941 ), 8942 comments=comments, 8943 ) 8944 self._match_r_paren(aliases) 8945 return aliases 8946 8947 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 8948 self.STRING_ALIASES and self._parse_string_as_identifier() 8949 ) 8950 8951 if alias: 8952 comments.extend(alias.pop_comments()) 8953 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 8954 column = this.this 8955 8956 # Moves the comment next to the alias in `expr /* comment */ AS alias` 8957 if not this.comments and column and column.comments: 8958 this.comments = column.pop_comments() 8959 8960 return this 8961 8962 def _parse_id_var( 8963 self, 8964 any_token: bool = True, 8965 tokens: t.Collection[TokenType] | None = None, 8966 ) -> exp.Expr | None: 8967 expression = self._parse_identifier() 8968 if not expression and ( 8969 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 8970 ): 8971 quoted = self._prev.token_type == TokenType.STRING 8972 expression = self._identifier_expression(quoted=quoted) 8973 8974 return expression 8975 8976 def _parse_string(self) -> exp.Expr | None: 8977 if self._match_set(self.STRING_PARSERS): 8978 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 8979 return self._parse_placeholder() 8980 8981 def _parse_string_as_identifier(self) -> exp.Identifier | None: 8982 if not self._match(TokenType.STRING): 8983 return None 8984 output = exp.to_identifier(self._prev.text, quoted=True) 8985 output.update_positions(self._prev) 8986 return output 8987 8988 def _parse_number(self) -> exp.Expr | None: 8989 if self._match_set(self.NUMERIC_PARSERS): 8990 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 8991 return self._parse_placeholder() 8992 8993 def _parse_identifier(self) -> exp.Expr | None: 8994 if self._match(TokenType.IDENTIFIER): 8995 return self._identifier_expression(quoted=True) 8996 return self._parse_placeholder() 8997 8998 def _parse_var( 8999 self, 9000 any_token: bool = False, 9001 tokens: t.Collection[TokenType] | None = None, 9002 upper: bool = False, 9003 ) -> exp.Expr | None: 9004 if ( 9005 (any_token and self._advance_any()) 9006 or self._match(TokenType.VAR) 9007 or (self._match_set(tokens) if tokens else False) 9008 ): 9009 return self.expression( 9010 exp.Var(this=self._prev.text.upper() if upper else self._prev.text) 9011 ) 9012 return self._parse_placeholder() 9013 9014 def _advance_any(self, ignore_reserved: bool = False) -> Token | None: 9015 if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS): 9016 self._advance() 9017 return self._prev 9018 return None 9019 9020 def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None: 9021 return self._parse_string() or self._parse_var(any_token=True, upper=upper) 9022 9023 def _parse_primary_or_var(self) -> exp.Expr | None: 9024 return self._parse_primary() or self._parse_var(any_token=True) 9025 9026 def _parse_null(self) -> exp.Expr | None: 9027 if self._match_set((TokenType.NULL, TokenType.UNKNOWN)): 9028 return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev) 9029 return self._parse_placeholder() 9030 9031 def _parse_boolean(self) -> exp.Expr | None: 9032 if self._match(TokenType.TRUE): 9033 return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev) 9034 if self._match(TokenType.FALSE): 9035 return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev) 9036 return self._parse_placeholder() 9037 9038 def _parse_star(self) -> exp.Expr | None: 9039 if self._match(TokenType.STAR): 9040 return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev) 9041 return self._parse_placeholder() 9042 9043 def _parse_parameter(self) -> exp.Parameter: 9044 this = self._parse_identifier() or self._parse_primary_or_var() 9045 return self.expression(exp.Parameter(this=this)) 9046 9047 def _parse_placeholder(self) -> exp.Expr | None: 9048 if self._match_set(self.PLACEHOLDER_PARSERS): 9049 placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self) 9050 if placeholder: 9051 return placeholder 9052 self._advance(-1) 9053 return None 9054 9055 def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None: 9056 if not self._match_texts(keywords): 9057 return None 9058 if self._match(TokenType.L_PAREN, advance=False): 9059 return self._parse_wrapped_csv(self._parse_expression) 9060 9061 expression = self._parse_alias(self._parse_disjunction(), explicit=True) 9062 return [expression] if expression else None 9063 9064 def _parse_csv( 9065 self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA 9066 ) -> list[T]: 9067 parse_result = parse_method() 9068 items = [parse_result] if parse_result is not None else [] 9069 9070 while self._match(sep): 9071 if isinstance(parse_result, exp.Expr): 9072 self._add_comments(parse_result) 9073 parse_result = parse_method() 9074 if parse_result is not None: 9075 items.append(parse_result) 9076 9077 return items 9078 9079 def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]: 9080 return self._parse_wrapped_csv(self._parse_id_var, optional=optional) 9081 9082 def _parse_wrapped_csv( 9083 self, 9084 parse_method: t.Callable[[], T | None], 9085 sep: TokenType = TokenType.COMMA, 9086 optional: bool = False, 9087 ) -> list[T]: 9088 return self._parse_wrapped( 9089 lambda: self._parse_csv(parse_method, sep=sep), optional=optional 9090 ) 9091 9092 def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T: 9093 wrapped = self._match(TokenType.L_PAREN) 9094 if not wrapped and not optional: 9095 self.raise_error("Expecting (") 9096 parse_result = parse_method() 9097 if wrapped: 9098 self._match_r_paren() 9099 return parse_result 9100 9101 def _parse_expressions(self) -> list[exp.Expr]: 9102 return self._parse_csv(self._parse_expression) 9103 9104 def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None: 9105 return ( 9106 self._parse_set_operations( 9107 self._parse_alias(self._parse_assignment(), explicit=True) 9108 if alias 9109 else self._parse_assignment() 9110 ) 9111 or self._parse_select() 9112 ) 9113 9114 def _parse_ddl_select(self) -> exp.Expr | None: 9115 return self._parse_query_modifiers( 9116 self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False)) 9117 ) 9118 9119 def _parse_transaction(self) -> exp.Transaction | exp.Command: 9120 this = None 9121 if self._match_texts(self.TRANSACTION_KIND): 9122 this = self._prev.text 9123 9124 self._match_texts(("TRANSACTION", "WORK")) 9125 9126 modes = [] 9127 while True: 9128 mode = [] 9129 while self._match(TokenType.VAR) or self._match(TokenType.NOT): 9130 mode.append(self._prev.text) 9131 9132 if mode: 9133 modes.append(" ".join(mode)) 9134 if not self._match(TokenType.COMMA): 9135 break 9136 9137 return self.expression(exp.Transaction(this=this, modes=modes)) 9138 9139 def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback: 9140 chain = None 9141 savepoint = None 9142 is_rollback = self._prev.token_type == TokenType.ROLLBACK 9143 9144 self._match_texts(("TRANSACTION", "WORK")) 9145 9146 if self._match_text_seq("TO"): 9147 self._match_text_seq("SAVEPOINT") 9148 savepoint = self._parse_id_var() 9149 9150 if self._match(TokenType.AND): 9151 chain = not self._match_text_seq("NO") 9152 self._match_text_seq("CHAIN") 9153 9154 if is_rollback: 9155 return self.expression(exp.Rollback(savepoint=savepoint)) 9156 9157 return self.expression(exp.Commit(chain=chain)) 9158 9159 def _parse_refresh(self) -> exp.Refresh | exp.Command: 9160 if self._match_text_seq("EXTERNAL", "TABLE"): 9161 kind = "EXTERNAL TABLE" 9162 elif self._match(TokenType.TABLE): 9163 kind = "TABLE" 9164 elif self._match_text_seq("MATERIALIZED", "VIEW"): 9165 kind = "MATERIALIZED VIEW" 9166 else: 9167 kind = "" 9168 9169 this = self._parse_string() or self._parse_table() 9170 if not kind and not isinstance(this, exp.Literal): 9171 return self._parse_as_command(self._prev) 9172 9173 return self.expression(exp.Refresh(this=this, kind=kind)) 9174 9175 def _parse_column_def_with_exists(self): 9176 start = self._index 9177 self._match(TokenType.COLUMN) 9178 9179 exists_column = self._parse_exists(not_=True) 9180 expression = self._parse_field_def() 9181 9182 if not isinstance(expression, exp.ColumnDef): 9183 self._retreat(start) 9184 return None 9185 9186 expression.set("exists", exists_column) 9187 9188 return expression 9189 9190 def _parse_add_column(self) -> exp.ColumnDef | None: 9191 if not self._prev.text.upper() == "ADD": 9192 return None 9193 9194 return self._parse_column_def_with_exists() 9195 9196 def _parse_drop_column(self) -> exp.Drop | exp.Command | None: 9197 if not self._match(TokenType.DROP): 9198 return None 9199 9200 kind = None 9201 if not self.ALTER_DROP_REQUIRES_COLUMN: 9202 # A bare `DROP [IF EXISTS] c [CASCADE | RESTRICT]` drops a column 9203 index = self._index 9204 self._parse_exists() 9205 if ( 9206 self._curr.token_type not in (TokenType.COLUMN, TokenType.CONSTRAINT) 9207 and self._match_set(self.ID_VAR_TOKENS) 9208 and ( 9209 not self._curr 9210 or self._match(TokenType.COMMA, advance=False) 9211 or self._match_texts(("CASCADE", "RESTRICT"), advance=False) 9212 ) 9213 ): 9214 kind = "COLUMN" 9215 self._retreat(index) 9216 9217 return self._parse_drop(kind=kind) 9218 9219 def _parse_alter_drop_action(self) -> exp.Expr | None: 9220 drop = self._parse_drop_column() 9221 9222 if ( 9223 isinstance(drop, exp.Drop) 9224 and drop.kind == "COLUMN" 9225 and not self.dialect.ALTER_TABLE_DROP_REQUIRED_FOR_EACH_COLUMN 9226 and self._match(TokenType.COMMA) 9227 ): 9228 drop.set("tables", [*drop.args["tables"], *self._parse_csv(self._parse_column)]) 9229 9230 return drop 9231 9232 # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html 9233 def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition: 9234 return self.expression( 9235 exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists) 9236 ) 9237 9238 def _parse_alter_table_add(self) -> list[exp.Expr]: 9239 def _parse_add_alteration() -> exp.Expr | None: 9240 self._match_text_seq("ADD") 9241 if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) or self._match_texts( 9242 self.ADD_CONSTRAINT_KEYWORDS, advance=False 9243 ): 9244 return self.expression( 9245 exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint)) 9246 ) 9247 9248 column_def = self._parse_add_column() 9249 if isinstance(column_def, exp.ColumnDef): 9250 return column_def 9251 9252 exists = self._parse_exists(not_=True) 9253 if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False): 9254 return self.expression( 9255 exp.AddPartition( 9256 exists=exists, 9257 this=self._parse_field(any_token=True), 9258 location=self._match_text_seq("LOCATION", advance=False) 9259 and self._parse_property(), 9260 ) 9261 ) 9262 9263 return None 9264 9265 if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and ( 9266 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 9267 or self._match_text_seq("COLUMNS") 9268 ): 9269 schema = self._parse_schema() 9270 9271 return ( 9272 ensure_list(schema) 9273 if schema 9274 else self._parse_csv(self._parse_column_def_with_exists) 9275 ) 9276 9277 return self._parse_csv(_parse_add_alteration) 9278 9279 def _parse_alter_table_alter(self) -> exp.Expr | None: 9280 if self._match_texts(self.ALTER_ALTER_PARSERS): 9281 return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self) 9282 9283 # Many dialects support the ALTER [COLUMN] syntax, so if there is no 9284 # keyword after ALTER we default to parsing this statement 9285 self._match(TokenType.COLUMN) 9286 exists = self._parse_exists() 9287 column = self._parse_field(any_token=True) 9288 9289 if self._match_pair(TokenType.DROP, TokenType.DEFAULT): 9290 return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None)) 9291 if self._match_pair(TokenType.SET, TokenType.DEFAULT): 9292 return self.expression( 9293 exp.AlterColumn( 9294 this=column, default=self._parse_disjunction(), exists=exists or None 9295 ) 9296 ) 9297 if self._match(TokenType.COMMENT): 9298 return self.expression( 9299 exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None) 9300 ) 9301 if self._match_text_seq("DROP", "NOT", "NULL"): 9302 return self.expression( 9303 exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None) 9304 ) 9305 if self._match_text_seq("SET", "NOT", "NULL"): 9306 return self.expression( 9307 exp.AlterColumn(this=column, allow_null=False, exists=exists or None) 9308 ) 9309 9310 if self._match_text_seq("SET", "VISIBLE"): 9311 return self.expression( 9312 exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None) 9313 ) 9314 if self._match_text_seq("SET", "INVISIBLE"): 9315 return self.expression( 9316 exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None) 9317 ) 9318 9319 self._match_text_seq("SET", "DATA") 9320 dtype = ( 9321 self._parse_types() 9322 if self._match_text_seq("TYPE") or not self.ALTER_COLUMN_TYPE_REQUIRES_KEYWORD 9323 else None 9324 ) 9325 if not dtype: 9326 return None 9327 9328 return self.expression( 9329 exp.AlterColumn( 9330 this=column, 9331 dtype=dtype, 9332 collate=self._match(TokenType.COLLATE) and self._parse_term(), 9333 using=self._match(TokenType.USING) and self._parse_disjunction(), 9334 exists=exists or None, 9335 ) 9336 ) 9337 9338 def _parse_alter_diststyle(self) -> exp.AlterDistStyle: 9339 if self._match_texts(("ALL", "EVEN", "AUTO")): 9340 return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper()))) 9341 9342 self._match_text_seq("KEY", "DISTKEY") 9343 return self.expression(exp.AlterDistStyle(this=self._parse_column())) 9344 9345 def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey: 9346 if compound: 9347 self._match_text_seq("SORTKEY") 9348 9349 if self._match(TokenType.L_PAREN, advance=False): 9350 return self.expression( 9351 exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound) 9352 ) 9353 9354 self._match_texts(("AUTO", "NONE")) 9355 return self.expression( 9356 exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound) 9357 ) 9358 9359 def _parse_alter_table_drop(self) -> list[exp.Expr]: 9360 index = self._index - 1 9361 9362 partition_exists = self._parse_exists() 9363 if self._match(TokenType.PARTITION, advance=False): 9364 return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists)) 9365 9366 self._retreat(index) 9367 return self._parse_csv(self._parse_alter_drop_action) 9368 9369 def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None: 9370 if self._match(TokenType.COLUMN) or ( 9371 not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False) 9372 ): 9373 exists = self._parse_exists() 9374 old_column = self._parse_column() 9375 to = self._match_text_seq("TO") 9376 new_column = self._parse_column() 9377 9378 if old_column is None or not to or new_column is None: 9379 return None 9380 9381 return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists)) 9382 9383 self._match_text_seq("TO") 9384 return self.expression(exp.AlterRename(this=self._parse_table(schema=True))) 9385 9386 def _parse_alter_table_set(self) -> exp.AlterSet: 9387 alter_set = self.expression(exp.AlterSet()) 9388 9389 if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq( 9390 "TABLE", "PROPERTIES" 9391 ): 9392 alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment)) 9393 elif self._match_text_seq("FILESTREAM_ON", advance=False): 9394 alter_set.set("expressions", [self._parse_assignment()]) 9395 elif self._match_texts(("LOGGED", "UNLOGGED")): 9396 alter_set.set("option", exp.var(self._prev.text.upper())) 9397 elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")): 9398 alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}")) 9399 elif self._match_text_seq("LOCATION"): 9400 alter_set.set("location", self._parse_field()) 9401 elif self._match_text_seq("ACCESS", "METHOD"): 9402 alter_set.set("access_method", self._parse_field()) 9403 elif self._match_text_seq("TABLESPACE"): 9404 alter_set.set("tablespace", self._parse_field()) 9405 elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"): 9406 alter_set.set("file_format", [self._parse_field()]) 9407 elif self._match_text_seq("STAGE_FILE_FORMAT"): 9408 alter_set.set("file_format", self._parse_wrapped_options()) 9409 elif self._match_text_seq("STAGE_COPY_OPTIONS"): 9410 alter_set.set("copy_options", self._parse_wrapped_options()) 9411 elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"): 9412 alter_set.set("tag", self._parse_csv(self._parse_assignment)) 9413 else: 9414 if self._match_text_seq("SERDE"): 9415 alter_set.set("serde", self._parse_field()) 9416 9417 properties = self._parse_wrapped(self._parse_properties, optional=True) 9418 alter_set.set("expressions", [properties]) 9419 9420 return alter_set 9421 9422 def _parse_alter_session(self) -> exp.AlterSession: 9423 """Parse ALTER SESSION SET/UNSET statements.""" 9424 if self._match(TokenType.SET): 9425 expressions = self._parse_csv(lambda: self._parse_set_item_assignment()) 9426 return self.expression(exp.AlterSession(expressions=expressions, unset=False)) 9427 9428 self._match_text_seq("UNSET") 9429 expressions = self._parse_csv( 9430 lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True))) 9431 ) 9432 return self.expression(exp.AlterSession(expressions=expressions, unset=True)) 9433 9434 def _parse_alter(self) -> exp.Alter | exp.Command: 9435 start = self._prev 9436 9437 iceberg = self._match_text_seq("ICEBERG") 9438 9439 alter_token = self._match_set(self.ALTERABLES) and self._prev 9440 if not alter_token: 9441 return self._parse_as_command(start) 9442 if iceberg and alter_token.token_type != TokenType.TABLE: 9443 return self._parse_as_command(start) 9444 9445 exists = self._parse_exists() 9446 only = self._match_text_seq("ONLY") 9447 9448 if alter_token.token_type == TokenType.SESSION: 9449 this = None 9450 check = None 9451 cluster = None 9452 parser = self.ALTER_PARSERS.get(alter_token.text.upper()) 9453 else: 9454 this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS) 9455 check = self._match_text_seq("WITH", "CHECK") 9456 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 9457 parser = ( 9458 self.ALTER_PARSERS[self._prev.text.upper()] 9459 if self._match_texts(self.ALTER_PARSERS) 9460 else None 9461 ) 9462 9463 options: list[exp.Expr] = [] 9464 if ( 9465 not parser 9466 and alter_token.token_type == TokenType.TABLE 9467 and not self.ALTER_TABLE_REQUIRES_ACTION 9468 ): 9469 # Table options may precede the actions, e.g. MySQL's ENGINE=InnoDB, ADD COLUMN a INT. 9470 # A clause the property parser rejects, like PARTITION BY RANGE ... MAXVALUE, stays a Command 9471 properties = self._try_parse(self._parse_properties) 9472 options = properties.expressions if properties else [] 9473 9474 actions = ensure_list(parser(self) if parser else None) 9475 if actions or options: 9476 # Actions of different kinds and table options may alternate, e.g. MySQL's 9477 # ADD c INT, AUTO_INCREMENT=5, ADD d INT. The comma before an item may have already 9478 # been consumed by the previous parser 9479 while self.ALTER_TABLE_MIXED_ACTIONS and ( 9480 self._match(TokenType.COMMA) or self._prev.token_type == TokenType.COMMA 9481 ): 9482 if not self._match_texts( 9483 self.PROPERTY_PARSERS, advance=False 9484 ) and self._match_texts(self.ALTER_PARSERS): 9485 parser = self.ALTER_PARSERS[self._prev.text.upper()] 9486 parsed = ensure_list(self._try_parse(lambda: parser(self))) 9487 if not parsed or any(isinstance(action, exp.Command) for action in parsed): 9488 return self._parse_as_command(start) 9489 9490 actions.extend(parsed) 9491 elif properties := self._try_parse(self._parse_properties): 9492 options.extend(properties.expressions) 9493 else: 9494 break 9495 9496 not_valid = self._match_text_seq("NOT", "VALID") 9497 if properties := self._try_parse(self._parse_properties): 9498 options.extend(properties.expressions) 9499 cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE") 9500 9501 if not self._curr: 9502 return self.expression( 9503 exp.Alter( 9504 this=this, 9505 kind=alter_token.text.upper(), 9506 exists=exists, 9507 actions=actions, 9508 only=only, 9509 options=options, 9510 cluster=cluster, 9511 not_valid=not_valid, 9512 check=check, 9513 cascade=cascade, 9514 iceberg=iceberg, 9515 ) 9516 ) 9517 9518 return self._parse_as_command(start) 9519 9520 def _parse_analyze(self) -> exp.Analyze | exp.Command: 9521 start = self._prev 9522 # https://duckdb.org/docs/sql/statements/analyze 9523 if not self._curr: 9524 return self.expression(exp.Analyze()) 9525 9526 options = [] 9527 while self._match_texts(self.ANALYZE_STYLES): 9528 if self._prev.text.upper() == "BUFFER_USAGE_LIMIT": 9529 options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}") 9530 else: 9531 options.append(self._prev.text.upper()) 9532 9533 tables: exp.Expr | list[exp.Expr] | None = None 9534 inner_expression: exp.Expr | None = None 9535 9536 kind = self._curr.text.upper() if self._curr else None 9537 9538 if self._match(TokenType.TABLE): 9539 tables = self._parse_csv(self._parse_table_parts) 9540 elif self._match(TokenType.INDEX): 9541 tables = self._parse_table_parts() 9542 elif self._match_text_seq("TABLES"): 9543 if self._match_set((TokenType.FROM, TokenType.IN)): 9544 kind = f"{kind} {self._prev.text.upper()}" 9545 tables = self._parse_table(schema=True, is_db_reference=True) 9546 elif self._match_text_seq("DATABASE"): 9547 tables = self._parse_table(schema=True, is_db_reference=True) 9548 elif self._match_text_seq("CLUSTER"): 9549 tables = self._parse_table() 9550 # Try matching inner expr keywords before fallback to parse table. 9551 elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9552 kind = None 9553 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9554 else: 9555 # Empty kind https://prestodb.io/docs/current/sql/analyze.html 9556 kind = None 9557 tables = self._parse_csv(self._parse_table_parts) 9558 9559 partition = self._try_parse(self._parse_partition) 9560 if not partition and self._match_texts(self.PARTITION_KEYWORDS): 9561 return self._parse_as_command(start) 9562 9563 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9564 if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq( 9565 "WITH", "ASYNC", "MODE" 9566 ): 9567 mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE" 9568 else: 9569 mode = None 9570 9571 if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS): 9572 inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self) 9573 9574 properties = self._parse_properties() 9575 return self.expression( 9576 exp.Analyze( 9577 kind=kind, 9578 tables=ensure_list(tables), 9579 mode=mode, 9580 partition=partition, 9581 properties=properties, 9582 expression=inner_expression, 9583 options=options, 9584 ) 9585 ) 9586 9587 # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html 9588 def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics: 9589 this = None 9590 kind = self._prev.text.upper() 9591 option = self._prev.text.upper() if self._match_text_seq("DELTA") else None 9592 expressions = [] 9593 9594 if not self._match_text_seq("STATISTICS"): 9595 self.raise_error("Expecting token STATISTICS") 9596 9597 if self._match_text_seq("NOSCAN"): 9598 this = "NOSCAN" 9599 elif self._match(TokenType.FOR): 9600 if self._match_text_seq("ALL", "COLUMNS"): 9601 this = "FOR ALL COLUMNS" 9602 if self._match_text_seq("COLUMNS"): 9603 this = "FOR COLUMNS" 9604 expressions = self._parse_csv(self._parse_column_reference) 9605 elif self._match_text_seq("SAMPLE"): 9606 sample = self._parse_number() 9607 expressions = [ 9608 self.expression( 9609 exp.AnalyzeSample( 9610 sample=sample, 9611 kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None, 9612 ) 9613 ) 9614 ] 9615 9616 return self.expression( 9617 exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions) 9618 ) 9619 9620 # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html 9621 def _parse_analyze_validate(self) -> exp.AnalyzeValidate: 9622 kind = None 9623 this = None 9624 expression: exp.Expr | None = None 9625 if self._match_text_seq("REF", "UPDATE"): 9626 kind = "REF" 9627 this = "UPDATE" 9628 if self._match_text_seq("SET", "DANGLING", "TO", "NULL"): 9629 this = "UPDATE SET DANGLING TO NULL" 9630 elif self._match_text_seq("STRUCTURE"): 9631 kind = "STRUCTURE" 9632 if self._match_text_seq("CASCADE", "FAST"): 9633 this = "CASCADE FAST" 9634 elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts( 9635 ("ONLINE", "OFFLINE") 9636 ): 9637 this = f"CASCADE COMPLETE {self._prev.text.upper()}" 9638 expression = self._parse_into() 9639 9640 return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression)) 9641 9642 def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None: 9643 this = self._prev.text.upper() 9644 if self._match_text_seq("COLUMNS"): 9645 return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}")) 9646 return None 9647 9648 def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None: 9649 kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None 9650 if self._match_text_seq("STATISTICS"): 9651 return self.expression(exp.AnalyzeDelete(kind=kind)) 9652 return None 9653 9654 def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None: 9655 if self._match_text_seq("CHAINED", "ROWS"): 9656 return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into())) 9657 return None 9658 9659 # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html 9660 def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram: 9661 this = self._prev.text.upper() 9662 expression: exp.Expr | None = None 9663 expressions = [] 9664 update_options = None 9665 9666 if self._match_text_seq("HISTOGRAM", "ON"): 9667 expressions = self._parse_csv(self._parse_column_reference) 9668 with_expressions = [] 9669 while self._match(TokenType.WITH): 9670 # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/ 9671 if self._match_texts(("SYNC", "ASYNC")): 9672 if self._match_text_seq("MODE", advance=False): 9673 with_expressions.append(f"{self._prev.text.upper()} MODE") 9674 self._advance() 9675 else: 9676 buckets = self._parse_number() 9677 if self._match_text_seq("BUCKETS"): 9678 with_expressions.append(f"{buckets} BUCKETS") 9679 if with_expressions: 9680 expression = self.expression(exp.AnalyzeWith(expressions=with_expressions)) 9681 9682 if self._match_texts(("MANUAL", "AUTO")) and self._match( 9683 TokenType.UPDATE, advance=False 9684 ): 9685 update_options = self._prev.text.upper() 9686 self._advance() 9687 elif self._match_text_seq("USING", "DATA"): 9688 expression = self.expression(exp.UsingData(this=self._parse_string())) 9689 9690 return self.expression( 9691 exp.AnalyzeHistogram( 9692 this=this, 9693 expressions=expressions, 9694 expression=expression, 9695 update_options=update_options, 9696 ) 9697 ) 9698 9699 def _parse_merge(self) -> exp.Merge: 9700 self._match(TokenType.INTO) 9701 target = self._parse_table() 9702 9703 if target and self._match(TokenType.ALIAS, advance=False): 9704 target.set("alias", self._parse_table_alias()) 9705 9706 self._match(TokenType.USING) 9707 using = self._parse_table() 9708 9709 return self.expression( 9710 exp.Merge( 9711 this=target, 9712 using=using, 9713 on=self._match(TokenType.ON) and self._parse_disjunction(), 9714 using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(), 9715 whens=self._parse_when_matched(), 9716 returning=self._parse_returning(), 9717 ) 9718 ) 9719 9720 def _parse_when_matched(self) -> exp.Whens: 9721 whens = [] 9722 9723 while self._match(TokenType.WHEN): 9724 matched = not self._match(TokenType.NOT) 9725 self._match_text_seq("MATCHED") 9726 source = ( 9727 False 9728 if self._match_text_seq("BY", "TARGET") 9729 else self._match_text_seq("BY", "SOURCE") 9730 ) 9731 condition = self._parse_disjunction() if self._match(TokenType.AND) else None 9732 9733 self._match(TokenType.THEN) 9734 9735 if self._match(TokenType.INSERT): 9736 this = self._parse_star() 9737 if this: 9738 then: exp.Expr | None = self.expression(exp.Insert(this=this)) 9739 else: 9740 then = self.expression( 9741 exp.Insert( 9742 this=exp.var("ROW") 9743 if self._match_text_seq("ROW") 9744 else self._parse_value(values=False), 9745 expression=self._match_text_seq("VALUES") and self._parse_value(), 9746 where=self._parse_where(), 9747 ) 9748 ) 9749 elif self._match(TokenType.UPDATE): 9750 expressions = self._parse_star() 9751 if expressions: 9752 then = self.expression(exp.Update(expressions=expressions)) 9753 else: 9754 then = self.expression( 9755 exp.Update( 9756 expressions=self._match(TokenType.SET) 9757 and self._parse_csv(self._parse_update_assignment), 9758 where=self._parse_where(), 9759 ) 9760 ) 9761 elif self._match(TokenType.DELETE): 9762 then = self.expression(exp.Var(this=self._prev.text)) 9763 else: 9764 then = self._parse_var_from_options(self.CONFLICT_ACTIONS) 9765 9766 whens.append( 9767 self.expression( 9768 exp.When(matched=matched, source=source, condition=condition, then=then) 9769 ) 9770 ) 9771 return self.expression(exp.Whens(expressions=whens)) 9772 9773 def _parse_show(self) -> exp.Expr | None: 9774 parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE) 9775 if parser: 9776 return parser(self) 9777 return self._parse_as_command(self._prev) 9778 9779 def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None: 9780 index = self._index 9781 9782 if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"): 9783 return self._parse_set_transaction(global_=kind == "GLOBAL") 9784 9785 left = self._parse_primary() or self._parse_column() 9786 assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS) 9787 9788 if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter): 9789 self._retreat(index) 9790 return None 9791 9792 right = self._parse_statement() or self._parse_id_var() 9793 if isinstance(right, (exp.Column, exp.Identifier)): 9794 right = exp.var(right.name) 9795 9796 this = self.expression(exp.EQ(this=left, expression=right)) 9797 return self.expression(exp.SetItem(this=this, kind=kind)) 9798 9799 def _parse_set_transaction(self, global_: bool = False) -> exp.Expr: 9800 self._match_text_seq("TRANSACTION") 9801 characteristics = self._parse_csv( 9802 lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS) 9803 ) 9804 return self.expression( 9805 exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_) 9806 ) 9807 9808 def _parse_set_item(self) -> exp.Expr | None: 9809 parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE) 9810 return parser(self) if parser else self._parse_set_item_assignment(kind=None) 9811 9812 def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command: 9813 index = self._index 9814 set_ = self.expression( 9815 exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag) 9816 ) 9817 9818 if self._curr: 9819 self._retreat(index) 9820 return self._parse_as_command(self._prev) 9821 9822 return set_ 9823 9824 def _parse_var_from_options( 9825 self, options: OPTIONS_TYPE, raise_unmatched: bool = True 9826 ) -> exp.Var | None: 9827 start = self._curr 9828 if not start: 9829 return None 9830 9831 option = start.text.upper() 9832 continuations = ( 9833 None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option) 9834 ) 9835 9836 index = self._index 9837 self._advance() 9838 for keywords in continuations or []: 9839 if isinstance(keywords, str): 9840 keywords = (keywords,) 9841 9842 if self._match_text_seq(*keywords): 9843 option = f"{option} {' '.join(keywords)}" 9844 break 9845 else: 9846 if continuations or continuations is None: 9847 if raise_unmatched: 9848 self.raise_error(f"Unknown option {option}") 9849 9850 self._retreat(index) 9851 return None 9852 9853 return exp.var(option) 9854 9855 def _parse_as_command(self, start: Token) -> exp.Command: 9856 while self._curr: 9857 self._advance() 9858 text = self._find_sql(start, self._prev) 9859 size = len(start.text) 9860 self._warn_unsupported() 9861 return exp.Command(this=text[:size], expression=text[size:]) 9862 9863 def _parse_dict_property(self, this: str) -> exp.DictProperty: 9864 settings = [] 9865 9866 self._match_l_paren() 9867 kind = self._parse_id_var() 9868 9869 if self._match(TokenType.L_PAREN): 9870 while True: 9871 key = self._parse_id_var() 9872 value = self._parse_function() or self._parse_primary_or_var() 9873 if not key and value is None: 9874 break 9875 settings.append(self.expression(exp.DictSubProperty(this=key, value=value))) 9876 self._match(TokenType.R_PAREN) 9877 9878 self._match_r_paren() 9879 9880 return self.expression( 9881 exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings) 9882 ) 9883 9884 def _parse_dict_range(self, this: str) -> exp.DictRange: 9885 self._match_l_paren() 9886 has_min = self._match_text_seq("MIN") 9887 if has_min: 9888 min = self._parse_var() or self._parse_primary() 9889 self._match_text_seq("MAX") 9890 max = self._parse_var() or self._parse_primary() 9891 else: 9892 max = self._parse_var() or self._parse_primary() 9893 min = exp.Literal.number(0) 9894 self._match_r_paren() 9895 return self.expression(exp.DictRange(this=this, min=min, max=max)) 9896 9897 def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None: 9898 index = self._index 9899 expression = self._parse_id_var() 9900 position = self._parse_id_var() if self._match(TokenType.COMMA) else None 9901 9902 if not self._match(TokenType.IN): 9903 self._retreat(index - 1) 9904 return None 9905 iterator = self._parse_column() 9906 condition = self._parse_disjunction() if self._match_text_seq("IF") else None 9907 return self.expression( 9908 exp.Comprehension( 9909 this=this, 9910 expression=expression, 9911 position=position, 9912 iterator=iterator, 9913 condition=condition, 9914 ) 9915 ) 9916 9917 def _parse_heredoc(self) -> exp.Heredoc | None: 9918 if self._match(TokenType.HEREDOC_STRING): 9919 return self.expression(exp.Heredoc(this=self._prev.text)) 9920 9921 if not self._match_text_seq("$"): 9922 return None 9923 9924 tags = ["$"] 9925 tag_text = None 9926 9927 if self._is_connected(): 9928 self._advance() 9929 tags.append(self._prev.text.upper()) 9930 else: 9931 self.raise_error("No closing $ found") 9932 9933 if tags[-1] != "$": 9934 if self._is_connected() and self._match_text_seq("$"): 9935 tag_text = tags[-1] 9936 tags.append("$") 9937 else: 9938 self.raise_error("No closing $ found") 9939 9940 heredoc_start = self._curr 9941 9942 while self._curr: 9943 if self._match_text_seq(*tags, advance=False): 9944 this = self._find_sql(heredoc_start, self._prev) 9945 self._advance(len(tags)) 9946 return self.expression(exp.Heredoc(this=this, tag=tag_text)) 9947 9948 self._advance() 9949 9950 self.raise_error(f"No closing {''.join(tags)} found") 9951 return None 9952 9953 def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None: 9954 if not self._curr: 9955 return None 9956 9957 index = self._index 9958 this = [] 9959 while True: 9960 # The current token might be multiple words 9961 curr = self._curr.text.upper() 9962 key = curr.split(" ") 9963 this.append(curr) 9964 9965 self._advance() 9966 result, trie = in_trie(trie, key) 9967 if result == TrieResult.FAILED: 9968 break 9969 9970 if result == TrieResult.EXISTS: 9971 subparser = parsers[" ".join(this)] 9972 return subparser 9973 9974 self._retreat(index) 9975 return None 9976 9977 def _match_l_paren(self, expression: exp.Expr | None = None) -> None: 9978 if not self._match(TokenType.L_PAREN, expression=expression): 9979 self.raise_error("Expecting (") 9980 9981 def _match_r_paren(self, expression: exp.Expr | None = None) -> None: 9982 if not self._match(TokenType.R_PAREN, expression=expression): 9983 self.raise_error("Expecting )") 9984 9985 def _replace_lambda( 9986 self, node: exp.Expr | None, expressions: list[exp.Expr] 9987 ) -> exp.Expr | None: 9988 if not node: 9989 return node 9990 9991 lambda_types = {e.name: e.args.get("to") or False for e in expressions} 9992 9993 for column in node.find_all(exp.Column): 9994 typ = lambda_types.get(column.parts[0].name) 9995 if typ is not None: 9996 dot_or_id = column.to_dot() if column.table else column.this 9997 9998 if typ: 9999 dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ)) 10000 10001 parent = column.parent 10002 10003 while isinstance(parent, exp.Dot): 10004 if not isinstance(parent.parent, exp.Dot): 10005 parent.replace(dot_or_id) 10006 break 10007 parent = parent.parent 10008 else: 10009 if column is node: 10010 node = dot_or_id 10011 else: 10012 column.replace(dot_or_id) 10013 return node 10014 10015 def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr: 10016 start = self._prev 10017 10018 # Not to be confused with TRUNCATE(number, decimals) function call 10019 if self._match(TokenType.L_PAREN): 10020 self._retreat(self._index - 2) 10021 return self._parse_function() 10022 10023 # Clickhouse supports TRUNCATE DATABASE as well 10024 is_database = self._match(TokenType.DATABASE) 10025 10026 self._match(TokenType.TABLE) 10027 10028 exists = self._parse_exists(not_=False) 10029 10030 expressions = self._parse_csv( 10031 lambda: self._parse_table(schema=True, is_db_reference=is_database) 10032 ) 10033 10034 cluster = self._parse_on_property() if self._match(TokenType.ON) else None 10035 10036 if self._match_text_seq("RESTART", "IDENTITY"): 10037 identity = "RESTART" 10038 elif self._match_text_seq("CONTINUE", "IDENTITY"): 10039 identity = "CONTINUE" 10040 else: 10041 identity = None 10042 10043 if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"): 10044 option = self._prev.text 10045 else: 10046 option = None 10047 10048 partition = self._parse_partition() 10049 10050 # Fallback case 10051 if self._curr: 10052 return self._parse_as_command(start) 10053 10054 return self.expression( 10055 exp.TruncateTable( 10056 expressions=expressions, 10057 is_database=is_database, 10058 exists=exists, 10059 cluster=cluster, 10060 identity=identity, 10061 option=option, 10062 partition=partition, 10063 ) 10064 ) 10065 10066 def _parse_indexed_column(self) -> exp.Expr | None: 10067 return self._parse_ordered(self._parse_opclass) 10068 10069 def _parse_with_operator(self) -> exp.Expr | None: 10070 this = self._parse_indexed_column() 10071 10072 if not self._match(TokenType.WITH): 10073 return this 10074 10075 op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS) 10076 10077 return self.expression(exp.WithOperator(this=this, op=op)) 10078 10079 def _parse_wrapped_options(self) -> list[exp.Expr]: 10080 self._match(TokenType.EQ) 10081 self._match(TokenType.L_PAREN) 10082 10083 opts: list[exp.Expr] = [] 10084 option: exp.Expr | list[exp.Expr] | None 10085 while self._curr and not self._match(TokenType.R_PAREN): 10086 if self._match_text_seq("FORMAT_NAME", "="): 10087 # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL 10088 option = self._parse_format_name() 10089 else: 10090 option = self._parse_property() 10091 10092 if option is None: 10093 self.raise_error("Unable to parse option") 10094 break 10095 10096 opts.extend(ensure_list(option)) 10097 10098 return opts 10099 10100 def _parse_copy_parameters(self) -> list[exp.CopyParameter]: 10101 sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None 10102 10103 options = [] 10104 while self._curr and not self._match(TokenType.R_PAREN, advance=False): 10105 option = self._parse_var(any_token=True) 10106 prev = self._prev.text.upper() 10107 10108 # Different dialects might separate options and values by white space, "=" and "AS" 10109 self._match(TokenType.EQ) 10110 self._match(TokenType.ALIAS) 10111 10112 param = self.expression(exp.CopyParameter(this=option)) 10113 10114 if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match( 10115 TokenType.L_PAREN, advance=False 10116 ): 10117 # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options 10118 param.set("expressions", self._parse_wrapped_options()) 10119 elif prev == "FILE_FORMAT": 10120 # T-SQL's external file format case 10121 param.set("expression", self._parse_field()) 10122 elif ( 10123 prev == "FORMAT" 10124 and self._prev.token_type == TokenType.ALIAS 10125 and self._match_texts(("AVRO", "JSON")) 10126 ): 10127 param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}")) 10128 param.set("expression", self._parse_field()) 10129 else: 10130 param.set("expression", self._parse_unquoted_field() or self._parse_bracket()) 10131 10132 options.append(param) 10133 10134 if sep: 10135 self._match(sep) 10136 10137 return options 10138 10139 def _parse_credentials(self) -> exp.Credentials | None: 10140 expr = self.expression(exp.Credentials()) 10141 10142 if self._match_text_seq("STORAGE_INTEGRATION", "="): 10143 expr.set("storage", self._parse_field()) 10144 if self._match_text_seq("CREDENTIALS"): 10145 # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string> 10146 creds = ( 10147 self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field() 10148 ) 10149 expr.set("credentials", creds) 10150 if self._match_text_seq("ENCRYPTION"): 10151 expr.set("encryption", self._parse_wrapped_options()) 10152 if self._match_text_seq("IAM_ROLE"): 10153 expr.set( 10154 "iam_role", 10155 exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(), 10156 ) 10157 if self._match_text_seq("REGION"): 10158 expr.set("region", self._parse_field()) 10159 10160 return expr 10161 10162 def _parse_file_location(self) -> exp.Expr | None: 10163 return self._parse_field() 10164 10165 def _parse_copy(self) -> exp.Copy | exp.Command: 10166 start = self._prev 10167 10168 self._match(TokenType.INTO) 10169 10170 this = ( 10171 self._parse_select(nested=True, parse_subquery_alias=False) 10172 if self._match(TokenType.L_PAREN, advance=False) 10173 else self._parse_table(schema=True) 10174 ) 10175 10176 kind = self._match(TokenType.FROM) or not self._match_text_seq("TO") 10177 10178 files = self._parse_csv(self._parse_file_location) 10179 if self._match(TokenType.EQ, advance=False): 10180 # Backtrack one token since we've consumed the lhs of a parameter assignment here. 10181 # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter 10182 # list via `_parse_wrapped(..)` below. 10183 self._advance(-1) 10184 files = [] 10185 10186 credentials = self._parse_credentials() 10187 10188 self._match_text_seq("WITH") 10189 10190 params = self._parse_wrapped(self._parse_copy_parameters, optional=True) 10191 10192 # Fallback case 10193 if self._curr: 10194 return self._parse_as_command(start) 10195 10196 return self.expression( 10197 exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params) 10198 ) 10199 10200 def _parse_normalize(self) -> exp.Normalize: 10201 return self.expression( 10202 exp.Normalize( 10203 this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var() 10204 ) 10205 ) 10206 10207 def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor: 10208 args = self._parse_csv(lambda: self._parse_lambda()) 10209 10210 this = seq_get(args, 0) 10211 decimals = seq_get(args, 1) 10212 10213 return expr_type( 10214 this=this, 10215 decimals=decimals, 10216 to=self._parse_var() if self._match_text_seq("TO") else None, 10217 ) 10218 10219 def _parse_star_ops(self) -> exp.Expr | None: 10220 star_token = self._prev 10221 10222 if self._match_text_seq("COLUMNS", "(", advance=False): 10223 this = self._parse_function() 10224 if isinstance(this, exp.Columns): 10225 this.set("unpack", True) 10226 return this 10227 10228 index = self._index 10229 ilike = self._parse_string() if self._match(TokenType.ILIKE) else None 10230 if not ilike: 10231 # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)` 10232 self._retreat(index) 10233 10234 return self.expression( 10235 exp.Star( 10236 ilike=ilike, 10237 except_=self._parse_star_op("EXCEPT", "EXCLUDE"), 10238 replace=self._parse_star_op("REPLACE"), 10239 rename=self._parse_star_op("RENAME"), 10240 ) 10241 ).update_positions(star_token) 10242 10243 def _parse_grant_privilege(self) -> exp.GrantPrivilege | None: 10244 privilege_parts = [] 10245 10246 # Keep consuming consecutive keywords until comma (end of this privilege) or ON 10247 # (end of privilege list) or L_PAREN (start of column list) are met 10248 while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False): 10249 privilege_parts.append(self._curr.text.upper()) 10250 self._advance() 10251 10252 if not privilege_parts: 10253 self.raise_error("Expected privilege") 10254 return None 10255 10256 this = exp.var(" ".join(privilege_parts)) 10257 expressions = ( 10258 self._parse_wrapped_csv(self._parse_column) 10259 if self._match(TokenType.L_PAREN, advance=False) 10260 else None 10261 ) 10262 10263 return self.expression(exp.GrantPrivilege(this=this, expressions=expressions)) 10264 10265 def _parse_grant_principal(self) -> exp.GrantPrincipal | None: 10266 kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper() 10267 principal = self._parse_id_var() 10268 10269 if not principal: 10270 return None 10271 10272 return self.expression(exp.GrantPrincipal(this=principal, kind=kind)) 10273 10274 def _parse_grant_revoke_common( 10275 self, 10276 ) -> tuple[list | None, str | None, exp.Expr | None]: 10277 privileges = self._parse_csv(self._parse_grant_privilege) 10278 10279 self._match(TokenType.ON) 10280 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 10281 10282 # Attempt to parse the securable e.g. MySQL allows names 10283 # such as "foo.*", "*.*" which are not easily parseable yet 10284 securable = self._try_parse(self._parse_table_parts) 10285 10286 return privileges, kind, securable 10287 10288 def _parse_grant(self) -> exp.Grant | exp.Command: 10289 start = self._prev 10290 10291 privileges, kind, securable = self._parse_grant_revoke_common() 10292 10293 if not securable or not self._match_text_seq("TO"): 10294 return self._parse_as_command(start) 10295 10296 principals = self._parse_csv(self._parse_grant_principal) 10297 10298 grant_option = self._match_text_seq("WITH", "GRANT", "OPTION") 10299 10300 if self._curr: 10301 return self._parse_as_command(start) 10302 10303 return self.expression( 10304 exp.Grant( 10305 privileges=privileges, 10306 kind=kind, 10307 securable=securable, 10308 principals=principals, 10309 grant_option=grant_option, 10310 ) 10311 ) 10312 10313 def _parse_revoke(self) -> exp.Revoke | exp.Command: 10314 start = self._prev 10315 10316 grant_option = self._match_text_seq("GRANT", "OPTION", "FOR") 10317 10318 privileges, kind, securable = self._parse_grant_revoke_common() 10319 10320 if not securable or not self._match_text_seq("FROM"): 10321 return self._parse_as_command(start) 10322 10323 principals = self._parse_csv(self._parse_grant_principal) 10324 10325 cascade = None 10326 if self._match_texts(("CASCADE", "RESTRICT")): 10327 cascade = self._prev.text.upper() 10328 10329 if self._curr: 10330 return self._parse_as_command(start) 10331 10332 return self.expression( 10333 exp.Revoke( 10334 privileges=privileges, 10335 kind=kind, 10336 securable=securable, 10337 principals=principals, 10338 grant_option=grant_option, 10339 cascade=cascade, 10340 ) 10341 ) 10342 10343 def _parse_overlay(self) -> exp.Overlay: 10344 def _parse_overlay_arg(text: str) -> exp.Expr | None: 10345 return ( 10346 self._parse_bitwise() 10347 if self._match(TokenType.COMMA) or self._match_text_seq(text) 10348 else None 10349 ) 10350 10351 return self.expression( 10352 exp.Overlay( 10353 this=self._parse_bitwise(), 10354 expression=_parse_overlay_arg("PLACING"), 10355 from_=_parse_overlay_arg("FROM"), 10356 for_=_parse_overlay_arg("FOR"), 10357 ) 10358 ) 10359 10360 def _parse_format_name(self) -> exp.Property: 10361 # Note: Although not specified in the docs, Snowflake does accept a string/identifier 10362 # for FILE_FORMAT = <format_name> 10363 return self.expression( 10364 exp.Property( 10365 this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts() 10366 ) 10367 ) 10368 10369 def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F: 10370 is_distinct = self._match(TokenType.DISTINCT) 10371 if not is_distinct: 10372 self._match(TokenType.ALL) 10373 10374 args = [self._parse_lambda()] 10375 if self._match(TokenType.COMMA): 10376 args.extend(self._parse_function_args()) 10377 10378 target = seq_get(args, distinct_index) 10379 if is_distinct and target: 10380 args[distinct_index] = self.expression(exp.Distinct(expressions=[target])) 10381 10382 return func.from_arg_list(args) 10383 10384 def _identifier_expression( 10385 self, token: Token | None = None, quoted: bool | None = None 10386 ) -> exp.Identifier: 10387 token = token or self._prev 10388 return self.expression(exp.Identifier(this=token.text, quoted=quoted), token) 10389 10390 def _build_pipe_cte( 10391 self, 10392 query: exp.Query, 10393 expressions: list[exp.Expr], 10394 alias_cte: exp.TableAlias | None = None, 10395 ) -> exp.Select: 10396 new_cte: str | exp.TableAlias | None 10397 if alias_cte: 10398 new_cte = alias_cte 10399 else: 10400 self._pipe_cte_counter += 1 10401 new_cte = f"__tmp{self._pipe_cte_counter}" 10402 10403 with_ = query.args.get("with_") 10404 ctes = with_.pop() if with_ else None 10405 10406 new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False) 10407 if ctes: 10408 new_select.set("with_", ctes) 10409 10410 return new_select.with_(new_cte, as_=query, copy=False) 10411 10412 def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select: 10413 select = self._parse_select(consume_pipe=False) 10414 if not select: 10415 return query 10416 10417 return self._build_pipe_cte( 10418 query=query.select(*select.expressions, append=False), expressions=[exp.Star()] 10419 ) 10420 10421 def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select: 10422 limit = self._parse_limit() 10423 offset = self._parse_offset() 10424 if isinstance(limit, exp.Limit): 10425 curr_limit = query.args.get("limit", limit) 10426 curr_is_limit_all = isinstance(curr_limit, exp.Limit) and curr_limit.is_limit_all 10427 10428 # LIMIT ALL means "no cap", so it's never tighter than a numeric limit. 10429 if limit.is_limit_all: 10430 should_replace = curr_is_limit_all 10431 else: 10432 should_replace = ( 10433 curr_is_limit_all or curr_limit.expression.to_py() >= limit.expression.to_py() 10434 ) 10435 10436 if should_replace: 10437 query.limit(limit, copy=False) 10438 if offset: 10439 curr_offset = query.args.get("offset") 10440 curr_offset = curr_offset.expression.to_py() if curr_offset else 0 10441 query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False) 10442 10443 return query 10444 10445 def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None: 10446 this = self._parse_disjunction() 10447 if self._match_text_seq("GROUP", "AND", advance=False): 10448 return this 10449 10450 this = self._parse_alias(this) 10451 10452 if self._match_set((TokenType.ASC, TokenType.DESC), advance=False): 10453 return self._parse_ordered(lambda: this) 10454 10455 return this 10456 10457 def _parse_pipe_syntax_aggregate_group_order_by( 10458 self, query: exp.Select, group_by_exists: bool = True 10459 ) -> exp.Select: 10460 expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields) 10461 aggregates_or_groups, orders = [], [] 10462 for element in expr: 10463 if isinstance(element, exp.Ordered): 10464 this = element.this 10465 if isinstance(this, exp.Alias): 10466 element.set("this", this.args["alias"]) 10467 orders.append(element) 10468 else: 10469 this = element 10470 aggregates_or_groups.append(this) 10471 10472 if group_by_exists: 10473 query.select( 10474 *aggregates_or_groups, *query.expressions, append=False, copy=False 10475 ).group_by( 10476 *[projection.args.get("alias", projection) for projection in aggregates_or_groups], 10477 copy=False, 10478 ) 10479 else: 10480 query.select(*aggregates_or_groups, append=False, copy=False) 10481 10482 if orders: 10483 return query.order_by(*orders, append=False, copy=False) 10484 10485 return query 10486 10487 def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select: 10488 self._match_text_seq("AGGREGATE") 10489 query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False) 10490 10491 if self._match(TokenType.GROUP_BY) or ( 10492 self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY) 10493 ): 10494 query = self._parse_pipe_syntax_aggregate_group_order_by(query) 10495 10496 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10497 10498 def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None: 10499 first_setop = self.parse_set_operation(this=query) 10500 if not first_setop: 10501 return None 10502 10503 def _parse_and_unwrap_query() -> exp.Expr | None: 10504 expr = self._parse_paren() 10505 return expr.assert_is(exp.Subquery).unnest() if expr else None 10506 10507 first_setop.this.pop() 10508 10509 setops = [ 10510 first_setop.expression.pop().assert_is(exp.Subquery).unnest(), 10511 *self._parse_csv(_parse_and_unwrap_query), 10512 ] 10513 10514 query = self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10515 with_ = query.args.get("with_") 10516 ctes = with_.pop() if with_ else None 10517 10518 if isinstance(first_setop, exp.Union): 10519 query = query.union(*setops, copy=False, **first_setop.args) 10520 elif isinstance(first_setop, exp.Except): 10521 query = query.except_(*setops, copy=False, **first_setop.args) 10522 else: 10523 query = query.intersect(*setops, copy=False, **first_setop.args) 10524 10525 query.set("with_", ctes) 10526 10527 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10528 10529 def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None: 10530 join = self._parse_join() 10531 if not join: 10532 return None 10533 10534 if isinstance(query, exp.Select): 10535 return query.join(join, copy=False) 10536 10537 return query 10538 10539 def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select: 10540 pivots = self._parse_pivots() 10541 if not pivots: 10542 return query 10543 10544 from_ = query.args.get("from_") 10545 if from_: 10546 from_.this.set("pivots", pivots) 10547 10548 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10549 10550 def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select: 10551 self._match_text_seq("EXTEND") 10552 query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False) 10553 return self._build_pipe_cte(query=query, expressions=[exp.Star()]) 10554 10555 def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select: 10556 sample = self._parse_table_sample() 10557 10558 with_ = query.args.get("with_") 10559 if with_: 10560 with_.expressions[-1].this.set("sample", sample) 10561 else: 10562 query.set("sample", sample) 10563 10564 return query 10565 10566 def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None: 10567 if isinstance(query, exp.Subquery): 10568 query = exp.select("*").from_(query, copy=False) 10569 10570 if not query.args.get("from_"): 10571 query = exp.select("*").from_(query.subquery(copy=False), copy=False) 10572 10573 while self._match(TokenType.PIPE_GT): 10574 start_index = self._index 10575 start_text = self._curr.text.upper() 10576 parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text) 10577 if not parser: 10578 # The set operators (UNION, etc) and the JOIN operator have a few common starting 10579 # keywords, making it tricky to disambiguate them without lookahead. The approach 10580 # here is to try and parse a set operation and if that fails, then try to parse a 10581 # join operator. If that fails as well, then the operator is not supported. 10582 parsed_query = self._parse_pipe_syntax_set_operator(query) 10583 parsed_query = parsed_query or self._parse_pipe_syntax_join(query) 10584 if not parsed_query: 10585 self._retreat(start_index) 10586 self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.") 10587 break 10588 query = parsed_query 10589 else: 10590 query = parser(self, query) 10591 10592 return query 10593 10594 def _parse_declareitem(self) -> exp.DeclareItem | None: 10595 self._match_texts(("VAR", "VARIABLE")) 10596 10597 vars = self._parse_csv(self._parse_id_var) 10598 if not vars: 10599 return None 10600 10601 self._match(TokenType.ALIAS) 10602 kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types() 10603 default = ( 10604 self._match(TokenType.DEFAULT) or self._match(TokenType.EQ) 10605 ) and self._parse_bitwise() 10606 10607 return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default)) 10608 10609 def _parse_declare(self) -> exp.Declare | exp.Command: 10610 start = self._prev 10611 replace = self._match_text_seq("OR", "REPLACE") 10612 expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem)) 10613 10614 if not expressions or self._curr: 10615 return self._parse_as_command(start) 10616 10617 return self.expression(exp.Declare(expressions=expressions, replace=replace)) 10618 10619 def build_cast(self, strict: bool, **kwargs) -> exp.Expr: 10620 exp_class = exp.Cast if strict else exp.TryCast 10621 10622 if exp_class == exp.TryCast: 10623 kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING 10624 10625 return self.expression(exp_class(**kwargs)) 10626 10627 def _parse_json_value(self) -> exp.JSONValue: 10628 this = self._parse_bitwise() 10629 self._match(TokenType.COMMA) 10630 path = self._parse_bitwise() 10631 10632 returning = self._match(TokenType.RETURNING) and self._parse_type() 10633 10634 return self.expression( 10635 exp.JSONValue( 10636 this=this, 10637 path=self.dialect.to_json_path(path), 10638 returning=returning, 10639 on_condition=self._parse_on_condition(), 10640 ) 10641 ) 10642 10643 def _parse_group_concat(self) -> exp.Expr | None: 10644 def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr: 10645 if isinstance(node, exp.Distinct) and len(node.expressions) > 1: 10646 concat_exprs = [ 10647 self.expression( 10648 exp.Concat( 10649 expressions=node.expressions, 10650 safe=True, 10651 coalesce=self.dialect.CONCAT_COALESCE, 10652 ) 10653 ) 10654 ] 10655 node.set("expressions", concat_exprs) 10656 return node 10657 if len(exprs) == 1: 10658 return exprs[0] 10659 return self.expression( 10660 exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE) 10661 ) 10662 10663 args = self._parse_csv(self._parse_lambda) 10664 10665 if args: 10666 order = args[-1] if isinstance(args[-1], exp.Order) else None 10667 10668 if order: 10669 # Order By is the last (or only) expression in the list and has consumed the 'expr' before it, 10670 # remove 'expr' from exp.Order and add it back to args 10671 args[-1] = order.this 10672 order.set("this", concat_exprs(order.this, args)) 10673 10674 this = order or concat_exprs(args[0], args) 10675 else: 10676 this = None 10677 10678 separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None 10679 10680 return self.expression(exp.GroupConcat(this=this, separator=separator)) 10681 10682 def _parse_initcap(self) -> exp.Initcap: 10683 expr = exp.Initcap.from_arg_list(self._parse_function_args()) 10684 10685 # attach dialect's default delimiters 10686 if expr.args.get("expression") is None: 10687 expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)) 10688 10689 return expr 10690 10691 def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None: 10692 if not self._match(TokenType.L_PAREN): 10693 self._retreat(self._index - 1) 10694 return None 10695 10696 op = "" 10697 while self._curr and not self._match(TokenType.R_PAREN): 10698 op += self._curr.text 10699 self._advance() 10700 10701 comments = self._prev_comments 10702 return self.expression( 10703 exp.Operator(this=this, operator=op, expression=self._parse_bitwise()), 10704 comments=comments, 10705 )
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: 154 array_exp.set("value_constructor", len(args) == 1 and isinstance(args[0], exp.Query)) 155 if dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS: 156 array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET) 157 158 return array_exp
161def build_convert_timezone( 162 args: BuilderArgs, default_source_tz: str | None = None 163) -> exp.ConvertTimezone | exp.Anonymous: 164 if len(args) == 2: 165 source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None 166 return exp.ConvertTimezone( 167 source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1) 168 ) 169 170 return exp.ConvertTimezone.from_arg_list(args)
173def build_trim(args: BuilderArgs, is_left: bool = True, reverse_args: bool = False) -> exp.Trim: 174 this, expression = seq_get(args, 0), seq_get(args, 1) 175 176 if expression and reverse_args: 177 this, expression = expression, this 178 179 return exp.Trim(this=this, expression=expression, position="LEADING" if is_left else "TRAILING")
196def build_array_append(args: BuilderArgs, dialect: Dialect) -> exp.ArrayAppend: 197 """ 198 Builds ArrayAppend with NULL propagation semantics based on the dialect configuration. 199 200 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 201 Others (DuckDB, PostgreSQL) create a new single-element array instead. 202 203 Args: 204 args: Function arguments [array, element] 205 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 206 207 Returns: 208 ArrayAppend expression with appropriate null_propagation flag 209 """ 210 return exp.ArrayAppend( 211 this=seq_get(args, 0), 212 expression=seq_get(args, 1), 213 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 214 )
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
217def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend: 218 """ 219 Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration. 220 221 Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. 222 Others (DuckDB, PostgreSQL) create a new single-element array instead. 223 224 Args: 225 args: Function arguments [array, element] 226 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 227 228 Returns: 229 ArrayPrepend expression with appropriate null_propagation flag 230 """ 231 return exp.ArrayPrepend( 232 this=seq_get(args, 0), 233 expression=seq_get(args, 1), 234 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 235 )
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
238def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat: 239 """ 240 Builds ArrayConcat with NULL propagation semantics based on the dialect configuration. 241 242 Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. 243 Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation. 244 245 Args: 246 args: Function arguments [array1, array2, ...] (variadic) 247 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 248 249 Returns: 250 ArrayConcat expression with appropriate null_propagation flag 251 """ 252 return exp.ArrayConcat( 253 this=seq_get(args, 0), 254 expressions=args[1:], 255 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 256 )
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
259def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove: 260 """ 261 Builds ArrayRemove with NULL propagation semantics based on the dialect configuration. 262 263 Some dialects (Snowflake) return NULL when the removal value is NULL. 264 Others (DuckDB) may return empty array due to NULL comparison semantics. 265 266 Args: 267 args: Function arguments [array, value_to_remove] 268 dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from 269 270 Returns: 271 ArrayRemove expression with appropriate null_propagation flag 272 """ 273 return exp.ArrayRemove( 274 this=seq_get(args, 0), 275 expression=seq_get(args, 1), 276 null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS, 277 )
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
308def build_json_extract_scalar( 309 self: Parser, this: exp.Expr, path: exp.Expr 310) -> exp.JSONExtractScalar: 311 return self.expression( 312 exp.JSONExtractScalar( 313 this=this, 314 expression=self.dialect.to_json_path(path), 315 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 316 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 317 ) 318 )
340class Parser: 341 """ 342 Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree. 343 344 Args: 345 error_level: The desired error level. 346 Default: ErrorLevel.IMMEDIATE 347 error_message_context: The amount of context to capture from a query string when displaying 348 the error message (in number of characters). 349 Default: 100 350 max_errors: Maximum number of error messages to include in a raised ParseError. 351 This is only relevant if error_level is ErrorLevel.RAISE. 352 Default: 3 353 max_nodes: Maximum number of AST nodes to prevent memory exhaustion. 354 Set to -1 (default) to disable the check. 355 """ 356 357 __slots__ = ( 358 "error_level", 359 "error_message_context", 360 "max_errors", 361 "max_nodes", 362 "dialect", 363 "sql", 364 "errors", 365 "_tokens", 366 "_index", 367 "_curr", 368 "_next", 369 "_prev", 370 "_prev_comments", 371 "_pipe_cte_counter", 372 "_chunks", 373 "_chunk_index", 374 "_tokens_size", 375 "_node_count", 376 ) 377 378 FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = { 379 **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()}, 380 **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce), 381 "ARRAY": lambda args, dialect: exp.Array(expressions=args), 382 "ARRAYAGG": 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_AGG": lambda args, dialect: exp.ArrayAgg( 386 this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None 387 ), 388 "ARRAY_APPEND": build_array_append, 389 "ARRAY_CAT": build_array_concat, 390 "ARRAY_CONCAT": build_array_concat, 391 "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args), 392 "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args), 393 "ARRAY_PREPEND": build_array_prepend, 394 "ARRAY_REMOVE": build_array_remove, 395 "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True), 396 "CONCAT": lambda args, dialect: exp.Concat( 397 expressions=args, 398 safe=not dialect.STRICT_STRING_CONCAT, 399 coalesce=dialect.CONCAT_COALESCE, 400 ), 401 "CONCAT_WS": lambda args, dialect: exp.ConcatWs( 402 expressions=args, 403 safe=not dialect.STRICT_STRING_CONCAT, 404 coalesce=dialect.CONCAT_WS_COALESCE, 405 ), 406 "CONVERT_TIMEZONE": build_convert_timezone, 407 "DATE_TO_DATE_STR": lambda args: exp.Cast( 408 this=seq_get(args, 0), 409 to=exp.DataType(this=exp.DType.TEXT), 410 ), 411 "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray( 412 start=seq_get(args, 0), 413 end=seq_get(args, 1), 414 step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")), 415 ), 416 "GENERATE_UUID": lambda args, dialect: exp.Uuid( 417 is_string=dialect.UUID_IS_STRING_TYPE or None 418 ), 419 "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)), 420 "GREATEST": lambda args, dialect: exp.Greatest( 421 this=seq_get(args, 0), 422 expressions=args[1:], 423 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 424 ), 425 "LEAST": lambda args, dialect: exp.Least( 426 this=seq_get(args, 0), 427 expressions=args[1:], 428 ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS, 429 ), 430 "HEX": build_hex, 431 "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract), 432 "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar), 433 "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar), 434 "JSON_KEYS": lambda args, dialect: exp.JSONKeys( 435 this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1)) 436 ), 437 "LIKE": build_like, 438 "LOG": build_logarithm, 439 "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)), 440 "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)), 441 "LOWER": build_lower, 442 "LPAD": lambda args: build_pad(args), 443 "LEFTPAD": lambda args: build_pad(args), 444 "LTRIM": lambda args: build_trim(args), 445 "MOD": build_mod, 446 "RIGHTPAD": lambda args: build_pad(args, is_left=False), 447 "RPAD": lambda args: build_pad(args, is_left=False), 448 "RTRIM": lambda args: build_trim(args, is_left=False), 449 "SCOPE_RESOLUTION": lambda args: ( 450 exp.ScopeResolution(expression=seq_get(args, 0)) 451 if len(args) != 2 452 else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1)) 453 ), 454 "STRPOS": exp.StrPosition.from_arg_list, 455 "CHARINDEX": lambda args: build_locate_strposition(args), 456 "INSTR": exp.StrPosition.from_arg_list, 457 "LOCATE": lambda args: build_locate_strposition(args), 458 "TIME_TO_TIME_STR": lambda args: exp.Cast( 459 this=seq_get(args, 0), 460 to=exp.DataType(this=exp.DType.TEXT), 461 ), 462 "TO_HEX": build_hex, 463 "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring( 464 this=exp.Cast( 465 this=seq_get(args, 0), 466 to=exp.DataType(this=exp.DType.TEXT), 467 ), 468 start=exp.Literal.number(1), 469 length=exp.Literal.number(10), 470 ), 471 "UNNEST": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))), 472 "UPPER": build_upper, 473 "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None), 474 "UUID_STRING": lambda args, dialect: exp.Uuid( 475 this=seq_get(args, 0), 476 name=seq_get(args, 1), 477 is_string=dialect.UUID_IS_STRING_TYPE or None, 478 ), 479 "VAR_MAP": build_var_map, 480 } 481 482 NO_PAREN_FUNCTIONS: t.ClassVar[dict] = { 483 TokenType.CURRENT_DATE: exp.CurrentDate, 484 TokenType.CURRENT_DATETIME: exp.CurrentDate, 485 TokenType.CURRENT_TIME: exp.CurrentTime, 486 TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp, 487 TokenType.CURRENT_USER: exp.CurrentUser, 488 TokenType.CURRENT_ROLE: exp.CurrentRole, 489 } 490 491 STRUCT_TYPE_TOKENS: t.ClassVar = { 492 TokenType.NESTED, 493 TokenType.OBJECT, 494 TokenType.STRUCT, 495 TokenType.UNION, 496 } 497 498 NESTED_TYPE_TOKENS: t.ClassVar = { 499 TokenType.ARRAY, 500 TokenType.LIST, 501 TokenType.LOWCARDINALITY, 502 TokenType.MAP, 503 TokenType.NULLABLE, 504 TokenType.RANGE, 505 *STRUCT_TYPE_TOKENS, 506 } 507 508 ENUM_TYPE_TOKENS: t.ClassVar = { 509 TokenType.DYNAMIC, 510 TokenType.ENUM, 511 TokenType.ENUM8, 512 TokenType.ENUM16, 513 } 514 515 AGGREGATE_TYPE_TOKENS: t.ClassVar = { 516 TokenType.AGGREGATEFUNCTION, 517 TokenType.SIMPLEAGGREGATEFUNCTION, 518 } 519 520 TYPE_TOKENS: t.ClassVar = { 521 TokenType.BIT, 522 TokenType.BOOLEAN, 523 TokenType.TINYINT, 524 TokenType.UTINYINT, 525 TokenType.SMALLINT, 526 TokenType.USMALLINT, 527 TokenType.INT, 528 TokenType.UINT, 529 TokenType.BIGINT, 530 TokenType.UBIGINT, 531 TokenType.BIGNUM, 532 TokenType.INT128, 533 TokenType.UINT128, 534 TokenType.INT256, 535 TokenType.UINT256, 536 TokenType.MEDIUMINT, 537 TokenType.UMEDIUMINT, 538 TokenType.FIXEDSTRING, 539 TokenType.FLOAT, 540 TokenType.DOUBLE, 541 TokenType.UDOUBLE, 542 TokenType.CHAR, 543 TokenType.NCHAR, 544 TokenType.VARCHAR, 545 TokenType.NVARCHAR, 546 TokenType.BPCHAR, 547 TokenType.TEXT, 548 TokenType.MEDIUMTEXT, 549 TokenType.LONGTEXT, 550 TokenType.BLOB, 551 TokenType.MEDIUMBLOB, 552 TokenType.LONGBLOB, 553 TokenType.BINARY, 554 TokenType.VARBINARY, 555 TokenType.JSON, 556 TokenType.JSONB, 557 TokenType.INTERVAL, 558 TokenType.TINYBLOB, 559 TokenType.TINYTEXT, 560 TokenType.TIME, 561 TokenType.TIMETZ, 562 TokenType.TIME_NS, 563 TokenType.TIMESTAMP, 564 TokenType.TIMESTAMP_S, 565 TokenType.TIMESTAMP_MS, 566 TokenType.TIMESTAMP_NS, 567 TokenType.TIMESTAMPTZ, 568 TokenType.TIMESTAMPLTZ, 569 TokenType.TIMESTAMPNTZ, 570 TokenType.DATETIME, 571 TokenType.DATETIME2, 572 TokenType.DATETIME64, 573 TokenType.SMALLDATETIME, 574 TokenType.DATE, 575 TokenType.DATE32, 576 TokenType.INT4RANGE, 577 TokenType.INT4MULTIRANGE, 578 TokenType.INT8RANGE, 579 TokenType.INT8MULTIRANGE, 580 TokenType.NUMRANGE, 581 TokenType.NUMMULTIRANGE, 582 TokenType.TSRANGE, 583 TokenType.TSMULTIRANGE, 584 TokenType.TSTZRANGE, 585 TokenType.TSTZMULTIRANGE, 586 TokenType.DATERANGE, 587 TokenType.DATEMULTIRANGE, 588 TokenType.DECIMAL, 589 TokenType.DECIMAL32, 590 TokenType.DECIMAL64, 591 TokenType.DECIMAL128, 592 TokenType.DECIMAL256, 593 TokenType.DECFLOAT, 594 TokenType.UDECIMAL, 595 TokenType.BIGDECIMAL, 596 TokenType.UUID, 597 TokenType.GEOGRAPHY, 598 TokenType.GEOGRAPHYPOINT, 599 TokenType.GEOMETRY, 600 TokenType.POINT, 601 TokenType.RING, 602 TokenType.LINESTRING, 603 TokenType.MULTILINESTRING, 604 TokenType.POLYGON, 605 TokenType.MULTIPOLYGON, 606 TokenType.HLLSKETCH, 607 TokenType.HSTORE, 608 TokenType.PSEUDO_TYPE, 609 TokenType.SUPER, 610 TokenType.SERIAL, 611 TokenType.SMALLSERIAL, 612 TokenType.BIGSERIAL, 613 TokenType.XML, 614 TokenType.YEAR, 615 TokenType.USERDEFINED, 616 TokenType.MONEY, 617 TokenType.SMALLMONEY, 618 TokenType.ROWVERSION, 619 TokenType.IMAGE, 620 TokenType.VARIANT, 621 TokenType.VECTOR, 622 TokenType.VOID, 623 TokenType.OBJECT, 624 TokenType.OBJECT_IDENTIFIER, 625 TokenType.INET, 626 TokenType.IPADDRESS, 627 TokenType.IPPREFIX, 628 TokenType.IPV4, 629 TokenType.IPV6, 630 TokenType.UNKNOWN, 631 TokenType.NOTHING, 632 TokenType.NULL, 633 TokenType.NAME, 634 TokenType.TDIGEST, 635 TokenType.DYNAMIC, 636 *ENUM_TYPE_TOKENS, 637 *NESTED_TYPE_TOKENS, 638 *AGGREGATE_TYPE_TOKENS, 639 } 640 641 SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = { 642 TokenType.BIGINT: TokenType.UBIGINT, 643 TokenType.INT: TokenType.UINT, 644 TokenType.MEDIUMINT: TokenType.UMEDIUMINT, 645 TokenType.SMALLINT: TokenType.USMALLINT, 646 TokenType.TINYINT: TokenType.UTINYINT, 647 TokenType.DECIMAL: TokenType.UDECIMAL, 648 TokenType.DOUBLE: TokenType.UDOUBLE, 649 } 650 651 SUBQUERY_PREDICATES: t.ClassVar = { 652 TokenType.ANY: exp.Any, 653 TokenType.ALL: exp.All, 654 TokenType.EXISTS: exp.Exists, 655 TokenType.SOME: exp.Any, 656 } 657 658 SUBQUERY_TOKENS: t.ClassVar = { 659 TokenType.SELECT, 660 TokenType.WITH, 661 TokenType.FROM, 662 } 663 664 RESERVED_TOKENS: t.ClassVar = { 665 *Tokenizer.SINGLE_TOKENS.values(), 666 TokenType.SELECT, 667 } - {TokenType.IDENTIFIER} 668 669 # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers, 670 # string literals), so they must never be treated as keywords when matching by text 671 TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset( 672 { 673 TokenType.BIT_STRING, 674 TokenType.BYTE_STRING, 675 TokenType.HEREDOC_STRING, 676 TokenType.HEX_STRING, 677 TokenType.IDENTIFIER, 678 TokenType.NATIONAL_STRING, 679 TokenType.RAW_STRING, 680 TokenType.STRING, 681 TokenType.UNICODE_STRING, 682 } 683 ) 684 685 DB_CREATABLES: t.ClassVar = { 686 TokenType.DATABASE, 687 TokenType.DICTIONARY, 688 TokenType.FILE_FORMAT, 689 TokenType.MODEL, 690 TokenType.NAMESPACE, 691 TokenType.SCHEMA, 692 TokenType.SEMANTIC_VIEW, 693 TokenType.SEQUENCE, 694 TokenType.SINK, 695 TokenType.SOURCE, 696 TokenType.STAGE, 697 TokenType.STORAGE_INTEGRATION, 698 TokenType.STREAMLIT, 699 TokenType.TABLE, 700 TokenType.TAG, 701 TokenType.VIEW, 702 TokenType.WAREHOUSE, 703 } 704 705 CREATABLES: t.ClassVar = { 706 TokenType.COLUMN, 707 TokenType.CONSTRAINT, 708 TokenType.FOREIGN_KEY, 709 TokenType.FUNCTION, 710 TokenType.INDEX, 711 TokenType.PROCEDURE, 712 TokenType.TRIGGER, 713 TokenType.TYPE, 714 *DB_CREATABLES, 715 } 716 717 TRIGGER_EVENTS: t.ClassVar = { 718 TokenType.INSERT, 719 TokenType.UPDATE, 720 TokenType.DELETE, 721 TokenType.TRUNCATE, 722 } 723 724 ALTERABLES: t.ClassVar = { 725 TokenType.INDEX, 726 TokenType.TABLE, 727 TokenType.VIEW, 728 TokenType.SESSION, 729 } 730 731 # Tokens that can represent identifiers 732 ID_VAR_TOKENS: t.ClassVar[set] = { 733 TokenType.ALL, 734 TokenType.ANALYZE, 735 TokenType.ATTACH, 736 TokenType.VAR, 737 TokenType.ANTI, 738 TokenType.APPLY, 739 TokenType.ASC, 740 TokenType.ASOF, 741 TokenType.AUTO_INCREMENT, 742 TokenType.BEGIN, 743 TokenType.BPCHAR, 744 TokenType.CACHE, 745 TokenType.CASE, 746 TokenType.COLLATE, 747 TokenType.COMMAND, 748 TokenType.COMMENT, 749 TokenType.COMMIT, 750 TokenType.CONSTRAINT, 751 TokenType.COPY, 752 TokenType.CUBE, 753 TokenType.CURRENT_SCHEMA, 754 TokenType.DECLARE, 755 TokenType.DEFAULT, 756 TokenType.DELETE, 757 TokenType.DESC, 758 TokenType.DESCRIBE, 759 TokenType.DETACH, 760 TokenType.DICTIONARY, 761 TokenType.DIV, 762 TokenType.END, 763 TokenType.EXECUTE, 764 TokenType.EXPORT, 765 TokenType.ESCAPE, 766 TokenType.FALSE, 767 TokenType.FIRST, 768 TokenType.FILE, 769 TokenType.FILTER, 770 TokenType.FINAL, 771 TokenType.FORMAT, 772 TokenType.FULL, 773 TokenType.GET, 774 TokenType.IDENTIFIER, 775 TokenType.INOUT, 776 TokenType.IS, 777 TokenType.ISNULL, 778 TokenType.INTERVAL, 779 TokenType.KEEP, 780 TokenType.KILL, 781 TokenType.LEFT, 782 TokenType.LIMIT, 783 TokenType.LOAD, 784 TokenType.LOCK, 785 TokenType.MATCH, 786 TokenType.MERGE, 787 TokenType.NATURAL, 788 TokenType.NEXT, 789 TokenType.OFFSET, 790 TokenType.OPERATOR, 791 TokenType.ORDINALITY, 792 TokenType.OUT, 793 TokenType.OVER, 794 TokenType.OVERLAPS, 795 TokenType.OVERWRITE, 796 TokenType.PARTITION, 797 TokenType.PERCENT, 798 TokenType.PIVOT, 799 TokenType.PROJECTION, 800 TokenType.PRAGMA, 801 TokenType.PUT, 802 TokenType.RANGE, 803 TokenType.RECURSIVE, 804 TokenType.REFERENCES, 805 TokenType.REFRESH, 806 TokenType.RENAME, 807 TokenType.REPLACE, 808 TokenType.RIGHT, 809 TokenType.ROLLUP, 810 TokenType.ROW, 811 TokenType.ROWS, 812 TokenType.SEMI, 813 TokenType.SET, 814 TokenType.SETTINGS, 815 TokenType.SHOW, 816 TokenType.STREAM, 817 TokenType.STREAMLIT, 818 TokenType.TEMPORARY, 819 TokenType.TOP, 820 TokenType.TRUE, 821 TokenType.TRUNCATE, 822 TokenType.UNIQUE, 823 TokenType.UNNEST, 824 TokenType.UNPIVOT, 825 TokenType.UPDATE, 826 TokenType.USE, 827 TokenType.VOLATILE, 828 TokenType.WINDOW, 829 TokenType.CURRENT_CATALOG, 830 TokenType.LOCALTIME, 831 TokenType.LOCALTIMESTAMP, 832 TokenType.SESSION_USER, 833 TokenType.STRAIGHT_JOIN, 834 *ALTERABLES, 835 *CREATABLES, 836 *SUBQUERY_PREDICATES, 837 *TYPE_TOKENS, 838 *NO_PAREN_FUNCTIONS, 839 } - {TokenType.UNION} 840 841 TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - { 842 TokenType.ANTI, 843 TokenType.ASOF, 844 TokenType.FULL, 845 TokenType.LEFT, 846 TokenType.LOCK, 847 TokenType.NATURAL, 848 TokenType.RIGHT, 849 TokenType.SEMI, 850 TokenType.WINDOW, 851 } 852 853 ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS 854 855 COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS 856 857 ARRAY_CONSTRUCTORS: t.ClassVar = { 858 "ARRAY": exp.Array, 859 "LIST": exp.List, 860 } 861 862 COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS} 863 864 UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET} 865 866 TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"} 867 868 # Tokens that indicate a simple column reference 869 IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER}) 870 871 BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE}) 872 873 # Postfix tokens that prevent the bare column fast path 874 COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 875 { 876 TokenType.L_PAREN, 877 TokenType.L_BRACKET, 878 TokenType.L_BRACE, 879 TokenType.COLON, 880 TokenType.JOIN_MARKER, 881 } 882 ) 883 884 TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset( 885 { 886 TokenType.L_PAREN, 887 TokenType.L_BRACKET, 888 TokenType.L_BRACE, 889 TokenType.PIVOT, 890 TokenType.UNPIVOT, 891 TokenType.TABLE_SAMPLE, 892 } 893 ) 894 895 FUNC_TOKENS: t.ClassVar = { 896 TokenType.COLLATE, 897 TokenType.COMMAND, 898 TokenType.CURRENT_DATE, 899 TokenType.CURRENT_DATETIME, 900 TokenType.CURRENT_SCHEMA, 901 TokenType.CURRENT_TIMESTAMP, 902 TokenType.CURRENT_TIME, 903 TokenType.CURRENT_USER, 904 TokenType.CURRENT_CATALOG, 905 TokenType.DECLARE, 906 TokenType.FILTER, 907 TokenType.FIRST, 908 TokenType.FORMAT, 909 TokenType.GET, 910 TokenType.GLOB, 911 TokenType.IDENTIFIER, 912 TokenType.INDEX, 913 TokenType.ISNULL, 914 TokenType.ILIKE, 915 TokenType.INSERT, 916 TokenType.LIKE, 917 TokenType.LOCALTIME, 918 TokenType.LOCALTIMESTAMP, 919 TokenType.MERGE, 920 TokenType.NEXT, 921 TokenType.OFFSET, 922 TokenType.PRIMARY_KEY, 923 TokenType.RANGE, 924 TokenType.REPLACE, 925 TokenType.RLIKE, 926 TokenType.ROW, 927 TokenType.SESSION_USER, 928 TokenType.UNNEST, 929 TokenType.VAR, 930 TokenType.LEFT, 931 TokenType.RIGHT, 932 TokenType.SEQUENCE, 933 TokenType.DATE, 934 TokenType.DATETIME, 935 TokenType.TABLE, 936 TokenType.TIMESTAMP, 937 TokenType.TIMESTAMPTZ, 938 TokenType.TRUNCATE, 939 TokenType.UTC_DATE, 940 TokenType.UTC_TIME, 941 TokenType.UTC_TIMESTAMP, 942 TokenType.WINDOW, 943 TokenType.XOR, 944 *TYPE_TOKENS, 945 *SUBQUERY_PREDICATES, 946 } 947 948 CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 949 TokenType.AND: exp.And, 950 } 951 952 ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 953 TokenType.COLON_EQ: exp.PropertyEQ, 954 } 955 956 DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = { 957 TokenType.OR: exp.Or, 958 } 959 960 EQUALITY: t.ClassVar = { 961 TokenType.EQ: exp.EQ, 962 TokenType.NEQ: exp.NEQ, 963 TokenType.NULLSAFE_EQ: exp.NullSafeEQ, 964 } 965 966 COMPARISON: t.ClassVar = { 967 TokenType.GT: exp.GT, 968 TokenType.GTE: exp.GTE, 969 TokenType.LT: exp.LT, 970 TokenType.LTE: exp.LTE, 971 } 972 973 BITWISE: t.ClassVar = { 974 TokenType.AMP: exp.BitwiseAnd, 975 TokenType.CARET: exp.BitwiseXor, 976 TokenType.PIPE: exp.BitwiseOr, 977 } 978 979 TERM: t.ClassVar = { 980 TokenType.DASH: exp.Sub, 981 TokenType.PLUS: exp.Add, 982 TokenType.COLLATE: exp.Collate, 983 } 984 985 FACTOR: t.ClassVar = { 986 TokenType.DIV: exp.IntDiv, 987 TokenType.LR_ARROW: exp.Distance, 988 TokenType.LLRR_ARROW: exp.DistanceNd, 989 TokenType.MOD: exp.Mod, 990 TokenType.SLASH: exp.Div, 991 TokenType.STAR: exp.Mul, 992 } 993 994 EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {} 995 996 TIMES: t.ClassVar = { 997 TokenType.TIME, 998 TokenType.TIMETZ, 999 } 1000 1001 TIMESTAMPS: t.ClassVar = { 1002 TokenType.TIMESTAMP, 1003 TokenType.TIMESTAMPNTZ, 1004 TokenType.TIMESTAMPTZ, 1005 TokenType.TIMESTAMPLTZ, 1006 *TIMES, 1007 } 1008 1009 SET_OPERATIONS: t.ClassVar = { 1010 TokenType.UNION, 1011 TokenType.INTERSECT, 1012 TokenType.EXCEPT, 1013 } 1014 1015 JOIN_METHODS: t.ClassVar = { 1016 TokenType.ASOF, 1017 TokenType.NATURAL, 1018 TokenType.POSITIONAL, 1019 } 1020 1021 JOIN_SIDES: t.ClassVar = { 1022 TokenType.LEFT, 1023 TokenType.RIGHT, 1024 TokenType.FULL, 1025 } 1026 1027 JOIN_KINDS: t.ClassVar = { 1028 TokenType.ANTI, 1029 TokenType.CROSS, 1030 TokenType.INNER, 1031 TokenType.OUTER, 1032 TokenType.SEMI, 1033 TokenType.STRAIGHT_JOIN, 1034 } 1035 1036 JOIN_HINTS: t.ClassVar[set[str]] = set() 1037 1038 # Tokens that unambiguously end a table reference on the fast path 1039 TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset( 1040 { 1041 TokenType.COMMA, 1042 TokenType.GROUP_BY, 1043 TokenType.HAVING, 1044 TokenType.JOIN, 1045 TokenType.LIMIT, 1046 TokenType.ON, 1047 TokenType.ORDER_BY, 1048 TokenType.R_PAREN, 1049 TokenType.SEMICOLON, 1050 TokenType.SENTINEL, 1051 TokenType.WHERE, 1052 *SET_OPERATIONS, 1053 *JOIN_KINDS, 1054 *JOIN_METHODS, 1055 *JOIN_SIDES, 1056 } 1057 ) 1058 1059 LAMBDAS: t.ClassVar = { 1060 TokenType.ARROW: lambda self, expressions: self.expression( 1061 exp.Lambda( 1062 this=self._replace_lambda( 1063 self._parse_disjunction(), 1064 expressions, 1065 ), 1066 expressions=expressions, 1067 ) 1068 ), 1069 TokenType.FARROW: lambda self, expressions: self.expression( 1070 exp.Kwarg( 1071 this=exp.var(expressions[0].name), 1072 expression=self._parse_disjunction() or self._parse_select(), 1073 ) 1074 ), 1075 } 1076 1077 # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake 1078 TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False 1079 1080 LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN}) 1081 1082 COLUMN_OPERATORS: t.ClassVar = { 1083 TokenType.DOT: None, 1084 TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)), 1085 TokenType.DCOLON: lambda self, this, to: self.build_cast( 1086 strict=self.STRICT_CAST, this=this, to=to 1087 ), 1088 TokenType.ARROW: lambda self, this, path: self.expression( 1089 exp.JSONExtract( 1090 this=this, 1091 expression=self.dialect.to_json_path(path), 1092 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1093 ) 1094 ), 1095 TokenType.DARROW: lambda self, this, path: self.expression( 1096 exp.JSONExtractScalar( 1097 this=this, 1098 expression=self.dialect.to_json_path(path), 1099 only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE, 1100 scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY, 1101 ) 1102 ), 1103 TokenType.HASH_ARROW: lambda self, this, path: self.expression( 1104 exp.JSONBExtract(this=this, expression=path) 1105 ), 1106 TokenType.DHASH_ARROW: lambda self, this, path: self.expression( 1107 exp.JSONBExtractScalar(this=this, expression=path) 1108 ), 1109 TokenType.PLACEHOLDER: lambda self, this, key: self.expression( 1110 exp.JSONBContainsTopKey(this=this, expression=key) 1111 ), 1112 } 1113 1114 # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator" 1115 # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs). 1116 JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {} 1117 1118 CAST_COLUMN_OPERATORS: t.ClassVar = { 1119 TokenType.DOTCOLON, 1120 TokenType.DCOLON, 1121 } 1122 1123 EXPRESSION_PARSERS: t.ClassVar = { 1124 exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY), 1125 exp.Column: lambda self: self._parse_column(), 1126 exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()), 1127 exp.Condition: lambda self: self._parse_disjunction(), 1128 exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True), 1129 exp.Expr: lambda self: self._parse_expression(), 1130 exp.From: lambda self: self._parse_from(joins=True), 1131 exp.GrantPrincipal: lambda self: self._parse_grant_principal(), 1132 exp.GrantPrivilege: lambda self: self._parse_grant_privilege(), 1133 exp.Group: lambda self: self._parse_group(), 1134 exp.Having: lambda self: self._parse_having(), 1135 exp.Hint: lambda self: self._parse_hint_body(), 1136 exp.Identifier: lambda self: self._parse_id_var(), 1137 exp.Join: lambda self: self._parse_join(), 1138 exp.Lambda: lambda self: self._parse_lambda(), 1139 exp.Lateral: lambda self: self._parse_lateral(), 1140 exp.Limit: lambda self: self._parse_limit(), 1141 exp.Offset: lambda self: self._parse_offset(), 1142 exp.Order: lambda self: self._parse_order(), 1143 exp.Ordered: lambda self: self._parse_ordered(), 1144 exp.Properties: lambda self: self._parse_properties(), 1145 exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(), 1146 exp.Qualify: lambda self: self._parse_qualify(), 1147 exp.Returning: lambda self: self._parse_returning(), 1148 exp.Select: lambda self: self._parse_select(), 1149 exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY), 1150 exp.Table: lambda self: self._parse_table_parts(), 1151 exp.TableAlias: lambda self: self._parse_table_alias(), 1152 exp.Tuple: lambda self: self._parse_value(values=False), 1153 exp.Whens: lambda self: self._parse_when_matched(), 1154 exp.Where: lambda self: self._parse_where(), 1155 exp.Window: lambda self: self._parse_named_window(), 1156 exp.With: lambda self: self._parse_with(), 1157 } 1158 1159 STATEMENT_PARSERS: t.ClassVar = { 1160 TokenType.ALTER: lambda self: self._parse_alter(), 1161 TokenType.ANALYZE: lambda self: self._parse_analyze(), 1162 TokenType.BEGIN: lambda self: self._parse_transaction(), 1163 TokenType.CACHE: lambda self: self._parse_cache(), 1164 TokenType.COMMENT: lambda self: self._parse_comment(), 1165 TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(), 1166 TokenType.COPY: lambda self: self._parse_copy(), 1167 TokenType.CREATE: lambda self: self._parse_create(), 1168 TokenType.DECLARE: lambda self: self._parse_declare(), 1169 TokenType.DELETE: lambda self: self._parse_delete(), 1170 TokenType.DESC: lambda self: self._parse_describe(), 1171 TokenType.DESCRIBE: lambda self: self._parse_describe(), 1172 TokenType.DROP: lambda self: self._parse_drop(), 1173 TokenType.GRANT: lambda self: self._parse_grant(), 1174 TokenType.REVOKE: lambda self: self._parse_revoke(), 1175 TokenType.INSERT: lambda self: self._parse_insert(), 1176 TokenType.KILL: lambda self: self._parse_kill(), 1177 TokenType.LOAD: lambda self: self._parse_load(), 1178 TokenType.MERGE: lambda self: self._parse_merge(), 1179 TokenType.PIVOT: lambda self: self._parse_simplified_pivot(), 1180 TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())), 1181 TokenType.REFRESH: lambda self: self._parse_refresh(), 1182 TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(), 1183 TokenType.SET: lambda self: self._parse_set(), 1184 TokenType.TRUNCATE: lambda self: self._parse_truncate_table(), 1185 TokenType.UNCACHE: lambda self: self._parse_uncache(), 1186 TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True), 1187 TokenType.UPDATE: lambda self: self._parse_update(), 1188 TokenType.USE: lambda self: self._parse_use(), 1189 TokenType.SEMICOLON: lambda self: exp.Semicolon(), 1190 } 1191 1192 UNARY_PARSERS: t.ClassVar = { 1193 TokenType.PLUS: lambda self: self._parse_unary(), # Unary + is handled as a no-op 1194 TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())), 1195 TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())), 1196 TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())), 1197 TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())), 1198 TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())), 1199 } 1200 1201 STRING_PARSERS: t.ClassVar = { 1202 TokenType.HEREDOC_STRING: lambda self, token: self.expression( 1203 exp.RawString(this=token.text), token 1204 ), 1205 TokenType.NATIONAL_STRING: lambda self, token: self.expression( 1206 exp.National(this=token.text), token 1207 ), 1208 TokenType.RAW_STRING: lambda self, token: self.expression( 1209 exp.RawString(this=token.text), token 1210 ), 1211 TokenType.STRING: lambda self, token: self.expression( 1212 exp.Literal(this=token.text, is_string=True), token 1213 ), 1214 TokenType.UNICODE_STRING: lambda self, token: self.expression( 1215 exp.UnicodeString( 1216 this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string() 1217 ), 1218 token, 1219 ), 1220 } 1221 1222 NUMERIC_PARSERS: t.ClassVar = { 1223 TokenType.BIT_STRING: lambda self, token: self.expression( 1224 exp.BitString(this=token.text), token 1225 ), 1226 TokenType.BYTE_STRING: lambda self, token: self.expression( 1227 exp.ByteString( 1228 this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None 1229 ), 1230 token, 1231 ), 1232 TokenType.HEX_STRING: lambda self, token: self.expression( 1233 exp.HexString( 1234 this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None 1235 ), 1236 token, 1237 ), 1238 TokenType.NUMBER: lambda self, token: self.expression( 1239 exp.Literal(this=token.text, is_string=False), token 1240 ), 1241 } 1242 1243 PRIMARY_PARSERS: t.ClassVar = { 1244 **STRING_PARSERS, 1245 **NUMERIC_PARSERS, 1246 TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token), 1247 TokenType.NULL: lambda self, _: self.expression(exp.Null()), 1248 TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)), 1249 TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)), 1250 TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(), 1251 TokenType.STAR: lambda self, _: self._parse_star_ops(), 1252 } 1253 1254 PLACEHOLDER_PARSERS: t.ClassVar = { 1255 TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()), 1256 TokenType.PARAMETER: lambda self: self._parse_parameter(), 1257 TokenType.COLON: lambda self: ( 1258 self.expression(exp.Placeholder(this=self._prev.text)) 1259 if self._match_set(self.COLON_PLACEHOLDER_TOKENS) 1260 else None 1261 ), 1262 } 1263 1264 RANGE_PARSERS: t.ClassVar = { 1265 TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll), 1266 TokenType.BETWEEN: lambda self, this: self._parse_between(this), 1267 TokenType.GLOB: binary_range_parser(exp.Glob), 1268 TokenType.ILIKE: binary_range_parser(exp.ILike), 1269 TokenType.IN: lambda self, this: self._parse_in(this), 1270 TokenType.IRLIKE: binary_range_parser(exp.RegexpILike), 1271 TokenType.IS: lambda self, this: self._parse_is(this), 1272 TokenType.LIKE: binary_range_parser(exp.Like), 1273 TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy), 1274 TokenType.OVERLAPS: binary_range_parser(exp.Overlaps), 1275 TokenType.RLIKE: binary_range_parser(exp.RegexpLike), 1276 TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo), 1277 TokenType.FOR: lambda self, this: self._parse_comprehension(this), 1278 TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys), 1279 TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys), 1280 TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath), 1281 TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists), 1282 TokenType.ADJACENT: binary_range_parser(exp.Adjacent), 1283 TokenType.OPERATOR: lambda self, this: self._parse_operator(this), 1284 TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft), 1285 TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight), 1286 TokenType.CARET_AT: binary_range_parser(exp.StartsWith), 1287 } 1288 1289 PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = { 1290 "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query), 1291 "AS": lambda self, query: self._build_pipe_cte( 1292 query, [exp.Star()], self._parse_table_alias() 1293 ), 1294 "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False), 1295 "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query), 1296 "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query), 1297 "ORDER BY": lambda self, query: query.order_by( 1298 self._parse_order(), append=False, copy=False 1299 ), 1300 "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1301 "SELECT": lambda self, query: self._parse_pipe_syntax_select(query), 1302 "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query), 1303 "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query), 1304 "WHERE": lambda self, query: query.where(self._parse_where(), copy=False), 1305 } 1306 1307 PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1308 "ALLOWED_VALUES": lambda self: self.expression( 1309 exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary)) 1310 ), 1311 "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty), 1312 "AUTO": lambda self: self._parse_auto_property(), 1313 "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty), 1314 "BACKUP": lambda self: self.expression( 1315 exp.BackupProperty(this=self._parse_var(any_token=True)) 1316 ), 1317 "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(), 1318 "CALLED": lambda self: self._parse_called_on_null_input_property(), 1319 "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs), 1320 "CHECKSUM": lambda self: self._parse_checksum(), 1321 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1322 "CLUSTERED": lambda self: self._parse_clustered_by(), 1323 "COLLATE": lambda self, **kwargs: self._parse_property_assignment( 1324 exp.CollateProperty, **kwargs 1325 ), 1326 "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty), 1327 "CONTAINS": lambda self: self._parse_contains_property(), 1328 "COPY": lambda self: self._parse_copy_property(), 1329 "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs), 1330 "DATA_DELETION": lambda self: self._parse_data_deletion_property(), 1331 "DEFINER": lambda self: self._parse_definer(), 1332 "DETERMINISTIC": lambda self: self.expression( 1333 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1334 ), 1335 "DISTRIBUTED": lambda self: self._parse_distributed_property(), 1336 "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty), 1337 "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()), 1338 "DISTKEY": lambda self: self._parse_distkey(), 1339 "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty), 1340 "EMPTY": lambda self: self.expression(exp.EmptyProperty()), 1341 "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty), 1342 "ENVIRONMENT": lambda self: self.expression( 1343 exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment)) 1344 ), 1345 "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty), 1346 "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty), 1347 "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()), 1348 "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs), 1349 "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1350 "FREESPACE": lambda self: self._parse_freespace(), 1351 "GLOBAL": lambda self: self.expression(exp.GlobalProperty()), 1352 "HEAP": lambda self: self.expression(exp.HeapProperty()), 1353 "ICEBERG": lambda self: self.expression(exp.IcebergProperty()), 1354 "IMMUTABLE": lambda self: self.expression( 1355 exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE")) 1356 ), 1357 "INHERITS": lambda self: self.expression( 1358 exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table)) 1359 ), 1360 "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())), 1361 "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs), 1362 "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty), 1363 "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"), 1364 "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"), 1365 "LIKE": lambda self: self._parse_create_like(), 1366 "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty), 1367 "LOCK": lambda self: self._parse_locking(), 1368 "LOCKING": lambda self: self._parse_locking(), 1369 "LOG": lambda self, **kwargs: self._parse_log(**kwargs), 1370 "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()), 1371 "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs), 1372 "MODIFIES": lambda self: self._parse_modifies_property(), 1373 "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)), 1374 "NO": lambda self: self._parse_no_property(), 1375 "ON": lambda self: self._parse_on_property(), 1376 "ORDER BY": lambda self: self._parse_order(skip_order_token=True), 1377 "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())), 1378 "PARTITION": lambda self: self._parse_partitioned_of(), 1379 "PARTITION BY": lambda self: self._parse_partitioned_by(), 1380 "PARTITIONED BY": lambda self: self._parse_partitioned_by(), 1381 "PARTITIONED_BY": lambda self: self._parse_partitioned_by(), 1382 "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True), 1383 "RANGE": lambda self: self._parse_dict_range(this="RANGE"), 1384 "READS": lambda self: self._parse_reads_property(), 1385 "REMOTE": lambda self: self._parse_remote_with_connection(), 1386 "RETURNS": lambda self: self._parse_returns(), 1387 "STRICT": lambda self: self.expression(exp.StrictProperty()), 1388 "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()), 1389 "ROW": lambda self: self._parse_row(), 1390 "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty), 1391 "SAMPLE": lambda self: self.expression( 1392 exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise()) 1393 ), 1394 "SECURE": lambda self: self.expression(exp.SecureProperty()), 1395 "SECURITY": lambda self: self._parse_sql_security(), 1396 "SQL SECURITY": lambda self: self._parse_sql_security(), 1397 "SET": lambda self: self.expression(exp.SetProperty(multi=False)), 1398 "SETTINGS": lambda self: self._parse_settings_property(), 1399 "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty), 1400 "SORTKEY": lambda self: self._parse_sortkey(), 1401 "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"), 1402 "STABLE": lambda self: self.expression( 1403 exp.StabilityProperty(this=exp.Literal.string("STABLE")) 1404 ), 1405 "STORED": lambda self: self._parse_stored(), 1406 "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(), 1407 "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(), 1408 "TEMP": lambda self: self.expression(exp.TemporaryProperty()), 1409 "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()), 1410 "TO": lambda self: self._parse_to_table(), 1411 "TRANSIENT": lambda self: self.expression(exp.TransientProperty()), 1412 "TRANSFORM": lambda self: self.expression( 1413 exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression)) 1414 ), 1415 "TTL": lambda self: self._parse_ttl(), 1416 "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty), 1417 "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()), 1418 "VOLATILE": lambda self: self._parse_volatile_property(), 1419 "WITH": lambda self: self._parse_with_property(), 1420 } 1421 1422 CONSTRAINT_PARSERS: t.ClassVar = { 1423 "AUTOINCREMENT": lambda self: self._parse_auto_increment(), 1424 "AUTO_INCREMENT": lambda self: self._parse_auto_increment(), 1425 "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)), 1426 "CHECK": lambda self: self._parse_check_constraint(), 1427 "COLLATE": lambda self: self.expression( 1428 exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column()) 1429 ), 1430 "COMMENT": lambda self: self.expression( 1431 exp.CommentColumnConstraint(this=self._parse_string()) 1432 ), 1433 "COMPRESS": lambda self: self._parse_compress(), 1434 "CLUSTERED": lambda self: self.expression( 1435 exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1436 ), 1437 "NONCLUSTERED": lambda self: self.expression( 1438 exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered)) 1439 ), 1440 "DEFAULT": lambda self: self.expression( 1441 exp.DefaultColumnConstraint(this=self._parse_bitwise()) 1442 ), 1443 "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())), 1444 "EPHEMERAL": lambda self: self.expression( 1445 exp.EphemeralColumnConstraint(this=self._parse_bitwise()) 1446 ), 1447 "EXCLUDE": lambda self: self.expression( 1448 exp.ExcludeColumnConstraint(this=self._parse_index_params()) 1449 ), 1450 "FOREIGN KEY": lambda self: self._parse_foreign_key(), 1451 "FORMAT": lambda self: self.expression( 1452 exp.DateFormatColumnConstraint(this=self._parse_var_or_string()) 1453 ), 1454 "GENERATED": lambda self: self._parse_generated_as_identity(), 1455 "IDENTITY": lambda self: self._parse_auto_increment(), 1456 "INLINE": lambda self: self._parse_inline(), 1457 "LIKE": lambda self: self._parse_create_like(), 1458 "NOT": lambda self: self._parse_not_constraint(), 1459 "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)), 1460 "ON": lambda self: ( 1461 ( 1462 self._match(TokenType.UPDATE) 1463 and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function())) 1464 ) 1465 or self.expression(exp.OnProperty(this=self._parse_id_var())) 1466 ), 1467 "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())), 1468 "PERIOD": lambda self: self._parse_period_for_system_time(), 1469 "PRIMARY KEY": lambda self: self._parse_primary_key(), 1470 "REFERENCES": lambda self: self._parse_references(match=False), 1471 "TITLE": lambda self: self.expression( 1472 exp.TitleColumnConstraint(this=self._parse_var_or_string()) 1473 ), 1474 "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])), 1475 "UNIQUE": lambda self: self._parse_unique(), 1476 "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()), 1477 "WITH": lambda self: self.expression( 1478 exp.Properties(expressions=self._parse_wrapped_properties()) 1479 ), 1480 "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1481 "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(), 1482 } 1483 1484 def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None: 1485 if not self._match(TokenType.L_PAREN, advance=False): 1486 # Partitioning by bucket or truncate follows the syntax: 1487 # PARTITION BY (BUCKET(..) | TRUNCATE(..)) 1488 # If we don't have parenthesis after each keyword, we should instead parse this as an identifier 1489 self._retreat(self._index - 1) 1490 return None 1491 1492 klass = ( 1493 exp.PartitionedByBucket 1494 if self._prev.text.upper() == "BUCKET" 1495 else exp.PartitionByTruncate 1496 ) 1497 1498 args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column()) 1499 this, expression = seq_get(args, 0), seq_get(args, 1) 1500 1501 if isinstance(this, exp.Literal): 1502 # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order 1503 # - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)` 1504 # - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)` 1505 # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)` 1506 # 1507 # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning 1508 # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties 1509 this, expression = expression, this 1510 1511 return self.expression(klass(this=this, expression=expression)) 1512 1513 ALTER_PARSERS: t.ClassVar = { 1514 "ADD": lambda self: self._parse_alter_table_add(), 1515 "AS": lambda self: self._parse_select(), 1516 "ALTER": lambda self: self._parse_alter_table_alter(), 1517 "CLUSTER BY": lambda self: self._parse_cluster_property(), 1518 "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())), 1519 "DROP": lambda self: self._parse_alter_table_drop(), 1520 "RENAME": lambda self: self._parse_alter_table_rename(), 1521 "SET": lambda self: self._parse_alter_table_set(), 1522 "SWAP": lambda self: self.expression( 1523 exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True)) 1524 ), 1525 } 1526 1527 ALTER_ALTER_PARSERS: t.ClassVar = { 1528 "DISTKEY": lambda self: self._parse_alter_diststyle(), 1529 "DISTSTYLE": lambda self: self._parse_alter_diststyle(), 1530 "SORTKEY": lambda self: self._parse_alter_sortkey(), 1531 "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True), 1532 } 1533 1534 SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = { 1535 "CHECK", 1536 "EXCLUDE", 1537 "FOREIGN KEY", 1538 "LIKE", 1539 "PERIOD", 1540 "PRIMARY KEY", 1541 "UNIQUE", 1542 "BUCKET", 1543 "TRUNCATE", 1544 } 1545 1546 NO_PAREN_FUNCTION_PARSERS: t.ClassVar = { 1547 "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())), 1548 "CASE": lambda self: self._parse_case(), 1549 "CONNECT_BY_ROOT": lambda self: self.expression( 1550 exp.ConnectByRoot(this=self._parse_column()) 1551 ), 1552 "IF": lambda self: self._parse_if(), 1553 } 1554 1555 INVALID_FUNC_NAME_TOKENS: t.ClassVar = { 1556 TokenType.IDENTIFIER, 1557 TokenType.STRING, 1558 } 1559 1560 FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"} 1561 1562 KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice) 1563 1564 FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = { 1565 **{ 1566 name: lambda self: self._parse_distinct_arg_function(exp.ArgMax) 1567 for name in exp.ArgMax.sql_names() 1568 }, 1569 **{ 1570 name: lambda self: self._parse_distinct_arg_function(exp.ArgMin) 1571 for name in exp.ArgMin.sql_names() 1572 }, 1573 "CAST": lambda self: self._parse_cast(self.STRICT_CAST), 1574 "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil), 1575 "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST), 1576 "CHAR": lambda self: self._parse_char(), 1577 "CHR": lambda self: self._parse_char(), 1578 "DECODE": lambda self: self._parse_decode(), 1579 "EXTRACT": lambda self: self._parse_extract(), 1580 "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor), 1581 "GAP_FILL": lambda self: self._parse_gap_fill(), 1582 "INITCAP": lambda self: self._parse_initcap(), 1583 "JSON_OBJECT": lambda self: self._parse_json_object(), 1584 "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True), 1585 "JSON_TABLE": lambda self: self._parse_json_table(), 1586 "MATCH": lambda self: self._parse_match_against(), 1587 "NORMALIZE": lambda self: self._parse_normalize(), 1588 "OPENJSON": lambda self: self._parse_open_json(), 1589 "OVERLAY": lambda self: self._parse_overlay(), 1590 "POSITION": lambda self: self._parse_position(), 1591 "SAFE_CAST": lambda self: self._parse_cast(False, safe=True), 1592 "STRING_AGG": lambda self: self._parse_string_agg(), 1593 "SUBSTRING": lambda self: self._parse_substring(), 1594 "TRIM": lambda self: self._parse_trim(), 1595 "TRY_CAST": lambda self: self._parse_cast(False, safe=True), 1596 "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True), 1597 "WEIGHT_STRING": lambda self: self._parse_weight_string(), 1598 "XMLELEMENT": lambda self: self._parse_xml_element(), 1599 "XMLTABLE": lambda self: self._parse_xml_table(), 1600 } 1601 1602 QUERY_MODIFIER_PARSERS: t.ClassVar = { 1603 TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()), 1604 TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()), 1605 TokenType.WHERE: lambda self: ("where", self._parse_where()), 1606 TokenType.GROUP_BY: lambda self: ("group", self._parse_group()), 1607 TokenType.HAVING: lambda self: ("having", self._parse_having()), 1608 TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()), 1609 TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()), 1610 TokenType.ORDER_BY: lambda self: ("order", self._parse_order()), 1611 TokenType.LIMIT: lambda self: ("limit", self._parse_limit()), 1612 TokenType.FETCH: lambda self: ("limit", self._parse_limit()), 1613 TokenType.OFFSET: lambda self: ("offset", self._parse_offset()), 1614 TokenType.FOR: lambda self: ("locks", self._parse_locks()), 1615 TokenType.LOCK: lambda self: ("locks", self._parse_locks()), 1616 TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1617 TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)), 1618 TokenType.CLUSTER_BY: lambda self: ( 1619 "cluster", 1620 self._parse_cluster(), 1621 ), 1622 TokenType.DISTRIBUTE_BY: lambda self: ( 1623 "distribute", 1624 self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY), 1625 ), 1626 TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)), 1627 TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)), 1628 } 1629 QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS) 1630 1631 SET_PARSERS: t.ClassVar = { 1632 "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"), 1633 "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"), 1634 "SESSION": lambda self: self._parse_set_item_assignment("SESSION"), 1635 "TRANSACTION": lambda self: self._parse_set_transaction(), 1636 } 1637 1638 SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {} 1639 1640 TYPE_LITERAL_PARSERS: t.ClassVar = { 1641 exp.DType.JSON: lambda self, this, _: self.expression( 1642 exp.ParseJSON(this=this, is_literal=True) 1643 ), 1644 } 1645 1646 TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {} 1647 1648 DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN} 1649 1650 PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE} 1651 1652 TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"} 1653 TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = { 1654 "ISOLATION": ( 1655 ("LEVEL", "REPEATABLE", "READ"), 1656 ("LEVEL", "READ", "COMMITTED"), 1657 ("LEVEL", "READ", "UNCOMITTED"), 1658 ("LEVEL", "SERIALIZABLE"), 1659 ), 1660 "READ": ("WRITE", "ONLY"), 1661 } 1662 1663 CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = { 1664 **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()), 1665 "DO": ("NOTHING", "UPDATE"), 1666 } 1667 1668 TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = { 1669 "INSTEAD": (("OF",),), 1670 "BEFORE": tuple(), 1671 "AFTER": tuple(), 1672 } 1673 1674 TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = { 1675 "NOT": (("DEFERRABLE",),), 1676 "DEFERRABLE": tuple(), 1677 } 1678 1679 CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = { 1680 "SCALE": ("EXTEND", "NOEXTEND"), 1681 "SHARD": ("EXTEND", "NOEXTEND"), 1682 "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"), 1683 **dict.fromkeys( 1684 ( 1685 "SESSION", 1686 "GLOBAL", 1687 "KEEP", 1688 "NOKEEP", 1689 "ORDER", 1690 "NOORDER", 1691 "NOCACHE", 1692 "CYCLE", 1693 "NOCYCLE", 1694 "NOMINVALUE", 1695 "NOMAXVALUE", 1696 "NOSCALE", 1697 "NOSHARD", 1698 ), 1699 tuple(), 1700 ), 1701 } 1702 1703 ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")} 1704 1705 USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1706 ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple() 1707 ) 1708 1709 CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",)) 1710 1711 SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1712 "TYPE": ("EVOLUTION",), 1713 **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()), 1714 } 1715 1716 PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {} 1717 1718 EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys( 1719 ("CALLER", "SELF", "OWNER"), tuple() 1720 ) 1721 1722 KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1723 "NOT": ("ENFORCED",), 1724 "MATCH": ( 1725 "FULL", 1726 "PARTIAL", 1727 "SIMPLE", 1728 ), 1729 "INITIALLY": ("DEFERRED", "IMMEDIATE"), 1730 "USING": ( 1731 "BTREE", 1732 "HASH", 1733 ), 1734 **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()), 1735 } 1736 1737 WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = { 1738 "NO": ("OTHERS",), 1739 "CURRENT": ("ROW",), 1740 **dict.fromkeys(("GROUP", "TIES"), tuple()), 1741 } 1742 1743 INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"} 1744 1745 CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"} 1746 # Time travel clause prefixes, mapped to whether they pin a timestamp or a version 1747 VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = { 1748 ("FOR", "SYSTEM_TIME"): "TIMESTAMP", 1749 ("FOR", "SYSTEM", "TIME"): "TIMESTAMP", 1750 ("FOR", "TIMESTAMP"): "TIMESTAMP", 1751 ("FOR", "VERSION"): "VERSION", 1752 ("TIMESTAMP", "AS", "OF"): "TIMESTAMP", 1753 ("VERSION", "AS", "OF"): "VERSION", 1754 } 1755 1756 HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"} 1757 HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"} 1758 1759 OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"} 1760 1761 OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN} 1762 1763 TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE} 1764 1765 VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"} 1766 1767 WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS} 1768 WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER} 1769 WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"} 1770 1771 JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS} 1772 1773 FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT} 1774 1775 ADD_CONSTRAINT_TOKENS: t.ClassVar = { 1776 TokenType.CONSTRAINT, 1777 TokenType.FOREIGN_KEY, 1778 TokenType.INDEX, 1779 TokenType.KEY, 1780 TokenType.PRIMARY_KEY, 1781 TokenType.UNIQUE, 1782 } 1783 1784 # Constraint kinds that start an ALTER ... ADD action but aren't tokens of their own 1785 ADD_CONSTRAINT_KEYWORDS: t.ClassVar[set[str]] = set() 1786 1787 DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT} 1788 1789 UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS 1790 1791 SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT} 1792 1793 COPY_INTO_VARLEN_OPTIONS: t.ClassVar = { 1794 "FILE_FORMAT", 1795 "COPY_OPTIONS", 1796 "FORMAT_OPTIONS", 1797 "CREDENTIAL", 1798 } 1799 1800 IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"} 1801 1802 ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {} 1803 1804 ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"} 1805 1806 PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN} 1807 1808 # The style options for the DESCRIBE statement 1809 DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"} 1810 1811 SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"} 1812 1813 # The style options for the ANALYZE statement 1814 ANALYZE_STYLES: t.ClassVar = { 1815 "BUFFER_USAGE_LIMIT", 1816 "FULL", 1817 "LOCAL", 1818 "NO_WRITE_TO_BINLOG", 1819 "SAMPLE", 1820 "SKIP_LOCKED", 1821 "VERBOSE", 1822 } 1823 1824 ANALYZE_EXPRESSION_PARSERS: t.ClassVar = { 1825 "ALL": lambda self: self._parse_analyze_columns(), 1826 "COMPUTE": lambda self: self._parse_analyze_statistics(), 1827 "DELETE": lambda self: self._parse_analyze_delete(), 1828 "DROP": lambda self: self._parse_analyze_histogram(), 1829 "ESTIMATE": lambda self: self._parse_analyze_statistics(), 1830 "LIST": lambda self: self._parse_analyze_list(), 1831 "PREDICATE": lambda self: self._parse_analyze_columns(), 1832 "UPDATE": lambda self: self._parse_analyze_histogram(), 1833 "VALIDATE": lambda self: self._parse_analyze_validate(), 1834 } 1835 1836 PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"} 1837 1838 AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET) 1839 1840 OPERATION_MODIFIERS: t.ClassVar[set[str]] = set() 1841 1842 RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"} 1843 1844 SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"} 1845 1846 MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values) 1847 1848 STRICT_CAST: t.ClassVar = True 1849 1850 PREFIXED_PIVOT_COLUMNS: t.ClassVar = False 1851 IDENTIFY_PIVOT_STRINGS: t.ClassVar = False 1852 # Whether an UNPIVOT outputs its value column(s) before the name column 1853 UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False 1854 # Controls when an aggregation's name is included in a pivoted column's name: 1855 # "agg_name_if_aliased" - only for aggregations that carry an explicit alias 1856 # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations 1857 # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only) 1858 PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased" 1859 1860 LOG_DEFAULTS_TO_LN: t.ClassVar = False 1861 1862 # Whether the table sample clause expects CSV syntax 1863 TABLESAMPLE_CSV: t.ClassVar = False 1864 1865 # The default method used for table sampling 1866 DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None 1867 1868 # Whether the SET command needs a delimiter (e.g. "=") for assignments 1869 SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True 1870 1871 # Whether the TRIM function expects the characters to trim as its first argument 1872 TRIM_PATTERN_FIRST: t.ClassVar = False 1873 1874 # Whether string aliases are supported `SELECT COUNT(*) 'count'` 1875 STRING_ALIASES: t.ClassVar = False 1876 1877 # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand) 1878 MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True 1879 SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"} 1880 1881 # Whether to parse IF statements that aren't followed by a left parenthesis as commands 1882 NO_PAREN_IF_COMMANDS: t.ClassVar = True 1883 1884 # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres) 1885 JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False 1886 1887 # Whether the `:` operator is used to extract a value from a VARIANT column 1888 COLON_IS_VARIANT_EXTRACT: t.ClassVar = False 1889 1890 # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged 1891 # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks) 1892 COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True 1893 1894 # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause. 1895 # If this is True and '(' is not found, the keyword will be treated as an identifier 1896 VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True 1897 1898 # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift) 1899 SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False 1900 1901 # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery) 1902 SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False 1903 1904 # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS 1905 INTERVAL_SPANS: t.ClassVar = True 1906 1907 # Whether a PARTITION clause can follow a table reference 1908 SUPPORTS_PARTITION_SELECTION: t.ClassVar = False 1909 1910 # Whether the `name AS expr` schema/column constraint requires parentheses around `expr` 1911 WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True 1912 1913 # Whether the 'AS' keyword is optional in the CTE definition syntax 1914 OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True 1915 1916 # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword 1917 ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True 1918 1919 # Whether dropping a column with an ALTER statement requires the presence of the COLUMN keyword 1920 ALTER_DROP_REQUIRES_COLUMN: t.ClassVar = True 1921 1922 # Whether Alter statements are allowed to contain Partition specifications 1923 ALTER_TABLE_PARTITIONS: t.ClassVar = False 1924 1925 # Whether an ALTER can combine different actions, e.g. ADD COLUMN a INT, DROP COLUMN b 1926 ALTER_TABLE_MIXED_ACTIONS: t.ClassVar = False 1927 1928 # Whether an ALTER TABLE needs an action, e.g. MySQL's ALTER TABLE t ENGINE=InnoDB has none 1929 ALTER_TABLE_REQUIRES_ACTION: t.ClassVar = True 1930 1931 # Whether changing a column's type with ALTER COLUMN requires the TYPE keyword 1932 ALTER_COLUMN_TYPE_REQUIRES_KEYWORD: t.ClassVar = False 1933 1934 # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree. 1935 # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is 1936 # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such 1937 # as BigQuery, where all joins have the same precedence. 1938 JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False 1939 1940 # Whether TIMESTAMP <literal> can produce a zone-aware timestamp 1941 ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False 1942 1943 # Whether map literals support arbitrary expressions as keys. 1944 # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB). 1945 # When False, keys are typically restricted to identifiers. 1946 MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False 1947 1948 # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this 1949 # is true for Snowflake but not for BigQuery which can also process strings 1950 JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False 1951 1952 # Dialects like Databricks support JOINS without join criteria 1953 # Adding an ON TRUE, makes transpilation semantically correct for other dialects 1954 ADD_JOIN_ON_TRUE: t.ClassVar = False 1955 1956 # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]' 1957 # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND` 1958 SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False 1959 1960 # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them 1961 # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation. 1962 ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False 1963 1964 # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause, 1965 # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake) 1966 SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False 1967 1968 # Type names that denote a different type when they're quoted, so quoting has to be 1969 # preserved instead of resolving them into the built-in type of the same name. These 1970 # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR 1971 QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set() 1972 1973 SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS) 1974 SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS) 1975 1976 def __init__( 1977 self, 1978 error_level: ErrorLevel | None = None, 1979 error_message_context: int = 100, 1980 max_errors: int = 3, 1981 max_nodes: int = -1, 1982 dialect: DialectType = None, 1983 ): 1984 self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE 1985 self.error_message_context: int = error_message_context 1986 self.max_errors: int = max_errors 1987 self.max_nodes: int = max_nodes 1988 self.dialect: t.Any = _resolve_dialect(dialect) 1989 self.sql: str = "" 1990 self.errors: list[ParseError] = [] 1991 self._tokens: list[Token] = [] 1992 self._tokens_size: i64 = 0 1993 self._index: i64 = 0 1994 self._curr: Token = SENTINEL_NONE 1995 self._next: Token = SENTINEL_NONE 1996 self._prev: Token = SENTINEL_NONE 1997 self._prev_comments: list[str] = [] 1998 self._pipe_cte_counter: int = 0 1999 self._chunks: list[list[Token]] = [] 2000 self._chunk_index: i64 = 0 2001 self._node_count: int = 0 2002 2003 def reset(self) -> None: 2004 self.sql = "" 2005 self.errors = [] 2006 self._tokens = [] 2007 self._tokens_size = 0 2008 self._index = 0 2009 self._curr = SENTINEL_NONE 2010 self._next = SENTINEL_NONE 2011 self._prev = SENTINEL_NONE 2012 self._prev_comments = [] 2013 self._pipe_cte_counter = 0 2014 self._chunks = [] 2015 self._chunk_index = 0 2016 self._node_count = 0 2017 2018 def _advance(self, times: i64 = 1) -> None: 2019 index = self._index + times 2020 self._index = index 2021 tokens = self._tokens 2022 size = self._tokens_size 2023 self._curr = tokens[index] if index < size else SENTINEL_NONE 2024 self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE 2025 2026 if index > 0: 2027 prev = tokens[index - 1] 2028 self._prev = prev 2029 self._prev_comments = prev.comments 2030 else: 2031 self._prev = SENTINEL_NONE 2032 self._prev_comments = [] 2033 2034 def _advance_chunk(self) -> None: 2035 self._index = -1 2036 self._tokens = self._chunks[self._chunk_index] 2037 self._tokens_size = i64(len(self._tokens)) 2038 self._chunk_index += 1 2039 self._advance() 2040 2041 def _retreat(self, index: i64) -> None: 2042 if index != self._index: 2043 self._advance(index - self._index) 2044 2045 def _add_comments(self, expression: exp.Expr | None) -> None: 2046 if expression and self._prev_comments: 2047 expression.add_comments(self._prev_comments) 2048 self._prev_comments = [] 2049 2050 def _match( 2051 self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None 2052 ) -> bool: 2053 if self._curr.token_type == token_type: 2054 if advance: 2055 self._advance() 2056 self._add_comments(expression) 2057 return True 2058 return False 2059 2060 def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool: 2061 if self._curr.token_type in types: 2062 if advance: 2063 self._advance() 2064 return True 2065 return False 2066 2067 def _match_pair( 2068 self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True 2069 ) -> bool: 2070 if self._curr.token_type == token_type_a and self._next.token_type == token_type_b: 2071 if advance: 2072 self._advance(2) 2073 return True 2074 return False 2075 2076 def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool: 2077 if ( 2078 self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS 2079 and self._curr.text.upper() in texts 2080 ): 2081 if advance: 2082 self._advance() 2083 return True 2084 return False 2085 2086 def _match_text_seq(self, *texts: str, advance: bool = True) -> bool: 2087 index = self._index 2088 excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS 2089 for text in texts: 2090 if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text: 2091 self._advance() 2092 else: 2093 self._retreat(index) 2094 return False 2095 2096 if not advance: 2097 self._retreat(index) 2098 2099 return True 2100 2101 def _is_connected(self) -> bool: 2102 prev = self._prev 2103 curr = self._curr 2104 return bool(prev and curr and prev.end + 1 == curr.start) 2105 2106 def _find_sql(self, start: Token, end: Token) -> str: 2107 return self.sql[start.start : end.end + 1] 2108 2109 def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None: 2110 token = token or self._curr or self._prev or Token.string("") 2111 formatted_sql, start_context, highlight, end_context = highlight_sql( 2112 sql=self.sql, 2113 positions=[(token.start, token.end)], 2114 context_length=self.error_message_context, 2115 ) 2116 formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n {formatted_sql}" 2117 2118 error = ParseError.new( 2119 formatted_message, 2120 description=message, 2121 line=token.line, 2122 col=token.col, 2123 start_context=start_context, 2124 highlight=highlight, 2125 end_context=end_context, 2126 ) 2127 2128 if self.error_level == ErrorLevel.IMMEDIATE: 2129 raise error 2130 2131 self.errors.append(error) 2132 2133 def validate_expression(self, expression: E, args: list | None = None) -> E: 2134 if self.max_nodes > -1: 2135 self._node_count += 1 2136 if self._node_count > self.max_nodes: 2137 self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded") 2138 if self.error_level != ErrorLevel.IGNORE: 2139 for error_message in expression.error_messages(args): 2140 self.raise_error(error_message) 2141 return expression 2142 2143 def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None: 2144 index = self._index 2145 error_level = self.error_level 2146 this: T | None = None 2147 2148 self.error_level = ErrorLevel.IMMEDIATE 2149 try: 2150 this = parse_method() 2151 except ParseError: 2152 this = None 2153 finally: 2154 if not this or retreat: 2155 self._retreat(index) 2156 self.error_level = error_level 2157 2158 return this 2159 2160 def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]: 2161 """ 2162 Parses a list of tokens and returns a list of syntax trees, one tree 2163 per parsed SQL statement. 2164 2165 Args: 2166 raw_tokens: The list of tokens. 2167 sql: The original SQL string. 2168 2169 Returns: 2170 The list of the produced syntax trees. 2171 """ 2172 return self._parse( 2173 parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql 2174 ) 2175 2176 def parse_into( 2177 self, 2178 expression_types: exp.IntoType, 2179 raw_tokens: list[Token], 2180 sql: str | None = None, 2181 ) -> list[exp.Expr | None]: 2182 """ 2183 Parses a list of tokens into a given Expr type. If a collection of Expr 2184 types is given instead, this method will try to parse the token list into each one 2185 of them, stopping at the first for which the parsing succeeds. 2186 2187 Args: 2188 expression_types: The expression type(s) to try and parse the token list into. 2189 raw_tokens: The list of tokens. 2190 sql: The original SQL string, used to produce helpful debug messages. 2191 2192 Returns: 2193 The target Expr. 2194 """ 2195 errors = [] 2196 for expression_type in ensure_list(expression_types): 2197 parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type)) 2198 if not parser: 2199 raise TypeError(f"No parser registered for {expression_type}") 2200 2201 try: 2202 return self._parse(parser, raw_tokens, sql) 2203 except ParseError as e: 2204 e.errors[0]["into_expression"] = expression_type 2205 errors.append(e) 2206 2207 raise ParseError( 2208 f"Failed to parse '{sql or raw_tokens}' into {expression_types}", 2209 errors=merge_errors(errors), 2210 ) from errors[-1] 2211 2212 def check_errors(self) -> None: 2213 """Logs or raises any found errors, depending on the chosen error level setting.""" 2214 if self.error_level == ErrorLevel.WARN: 2215 for error in self.errors: 2216 logger.error(str(error)) 2217 elif self.error_level == ErrorLevel.RAISE and self.errors: 2218 raise ParseError( 2219 concat_messages(self.errors, self.max_errors), 2220 errors=merge_errors(self.errors), 2221 ) 2222 2223 def expression( 2224 self, 2225 instance: E, 2226 token: Token | None = None, 2227 comments: list[str] | None = None, 2228 ) -> E: 2229 if token: 2230 instance.update_positions(token) 2231 instance.add_comments(comments) if comments else self._add_comments(instance) 2232 if not instance.is_primitive: 2233 instance = self.validate_expression(instance) 2234 return instance 2235 2236 def _parse_batch_statements( 2237 self, 2238 parse_method: t.Callable[[Parser], exp.Expr | None], 2239 sep_first_statement: bool = True, 2240 ) -> list[exp.Expr | None]: 2241 expressions = [] 2242 2243 # Chunkification binds if/while statements with the first statement of the body 2244 if sep_first_statement: 2245 self._match(TokenType.BEGIN) 2246 expressions.append(parse_method(self)) 2247 2248 chunks_length = len(self._chunks) 2249 while self._chunk_index < chunks_length: 2250 self._advance_chunk() 2251 2252 if self._match(TokenType.ELSE, advance=False): 2253 return expressions 2254 2255 if expressions and not self._next and self._match(TokenType.END): 2256 expressions.append(exp.EndStatement()) 2257 continue 2258 2259 expressions.append(parse_method(self)) 2260 2261 if self._index < self._tokens_size: 2262 self.raise_error("Invalid expression / Unexpected token") 2263 2264 self.check_errors() 2265 2266 return expressions 2267 2268 def _parse( 2269 self, 2270 parse_method: t.Callable[[Parser], exp.Expr | None], 2271 raw_tokens: list[Token], 2272 sql: str | None = None, 2273 ) -> list[exp.Expr | None]: 2274 self.reset() 2275 self.sql = sql or "" 2276 2277 total = len(raw_tokens) 2278 chunks: list[list[Token]] = [[]] 2279 2280 for i, token in enumerate(raw_tokens): 2281 if token.token_type == TokenType.SEMICOLON: 2282 if token.comments: 2283 chunks.append([token]) 2284 2285 if i < total - 1: 2286 chunks.append([]) 2287 else: 2288 chunks[-1].append(token) 2289 2290 self._chunks = chunks 2291 2292 return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False) 2293 2294 def _warn_unsupported(self) -> None: 2295 if self._tokens_size <= 1: 2296 return 2297 2298 # We use _find_sql because self.sql may comprise multiple chunks, and we're only 2299 # interested in emitting a warning for the one being currently processed. 2300 sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context] 2301 2302 logger.warning( 2303 f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'." 2304 ) 2305 2306 def _parse_command(self) -> exp.Command: 2307 self._warn_unsupported() 2308 comments = self._prev_comments 2309 return self.expression( 2310 exp.Command(this=self._prev.text.upper(), expression=self._parse_string()), 2311 comments=comments, 2312 ) 2313 2314 def _parse_comment(self, allow_exists: bool = True) -> exp.Expr: 2315 start = self._prev 2316 exists = self._parse_exists() if allow_exists else None 2317 2318 self._match(TokenType.ON) 2319 2320 materialized = self._match_text_seq("MATERIALIZED") 2321 kind = self._match_set(self.CREATABLES) and self._prev 2322 if not kind: 2323 return self._parse_as_command(start) 2324 2325 if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2326 this = self._parse_user_defined_function(kind=kind.token_type) 2327 elif kind.token_type == TokenType.TABLE: 2328 this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS) 2329 elif kind.token_type == TokenType.COLUMN: 2330 this = self._parse_column() 2331 else: 2332 this = self._parse_table_parts(schema=True) 2333 2334 self._match(TokenType.IS) 2335 2336 return self.expression( 2337 exp.Comment( 2338 this=this, 2339 kind=kind.text, 2340 expression=self._parse_string(), 2341 exists=exists, 2342 materialized=materialized, 2343 ) 2344 ) 2345 2346 def _parse_to_table( 2347 self, 2348 ) -> exp.ToTableProperty: 2349 table = self._parse_table_parts(schema=True) 2350 return self.expression(exp.ToTableProperty(this=table)) 2351 2352 # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl 2353 def _parse_ttl(self) -> exp.Expr: 2354 def _parse_ttl_action() -> exp.Expr | None: 2355 this = self._parse_bitwise() 2356 2357 if self._match_text_seq("DELETE"): 2358 return self.expression(exp.MergeTreeTTLAction(this=this, delete=True)) 2359 if self._match_text_seq("RECOMPRESS"): 2360 return self.expression( 2361 exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise()) 2362 ) 2363 if self._match_text_seq("TO", "DISK"): 2364 return self.expression( 2365 exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string()) 2366 ) 2367 if self._match_text_seq("TO", "VOLUME"): 2368 return self.expression( 2369 exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string()) 2370 ) 2371 2372 return this 2373 2374 expressions = self._parse_csv(_parse_ttl_action) 2375 where = self._parse_where() 2376 group = self._parse_group() 2377 2378 aggregates = None 2379 if group and self._match(TokenType.SET): 2380 aggregates = self._parse_csv(self._parse_set_item) 2381 2382 return self.expression( 2383 exp.MergeTreeTTL( 2384 expressions=expressions, where=where, group=group, aggregates=aggregates 2385 ) 2386 ) 2387 2388 def _parse_condition(self) -> exp.Expr | None: 2389 return self._parse_wrapped(parse_method=self._parse_expression, optional=True) 2390 2391 def _parse_block(self) -> exp.Block: 2392 return self.expression( 2393 exp.Block( 2394 expressions=self._parse_batch_statements( 2395 parse_method=lambda self: self._parse_statement() 2396 ) 2397 ) 2398 ) 2399 2400 def _parse_whileblock(self) -> exp.WhileBlock: 2401 return self.expression( 2402 exp.WhileBlock(this=self._parse_condition(), body=self._parse_block()) 2403 ) 2404 2405 def _parse_statement(self) -> exp.Expr | None: 2406 if not self._curr: 2407 return None 2408 2409 if self._match_set(self.STATEMENT_PARSERS): 2410 comments = self._prev_comments 2411 stmt = self.STATEMENT_PARSERS[self._prev.token_type](self) 2412 stmt.add_comments(comments, prepend=True) 2413 return stmt 2414 2415 if self._match_set(self.dialect.tokenizer_class.COMMANDS): 2416 return self._parse_command() 2417 2418 if self._match_text_seq("WHILE"): 2419 return self._parse_whileblock() 2420 2421 expression = self._parse_expression() 2422 expression = self._parse_set_operations(expression) if expression else self._parse_select() 2423 2424 if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False): 2425 expression = self._parse_pipe_syntax_query(expression) 2426 2427 return self._parse_query_modifiers(expression) 2428 2429 def _parse_drop(self, exists: bool = False, kind: str | None = None) -> exp.Drop | exp.Command: 2430 start = self._prev 2431 2432 if not kind: 2433 temporary = self._match(TokenType.TEMPORARY) 2434 materialized = self._match_text_seq("MATERIALIZED") 2435 iceberg = self._match_text_seq("ICEBERG") 2436 kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None 2437 else: 2438 temporary = False 2439 materialized = False 2440 iceberg = False 2441 2442 if not kind or (iceberg and kind != "TABLE"): 2443 return self._parse_as_command(start) 2444 2445 concurrently = ( 2446 kind == "INDEX" 2447 and self._next.token_type in self.ID_VAR_TOKENS 2448 and self._match_text_seq("CONCURRENTLY") 2449 ) 2450 2451 if_exists = exists or self._parse_exists() 2452 2453 tables: exp.Expr | list[exp.Expr] | None 2454 if kind == "COLUMN": 2455 # A dropped column named after a keyword, like `any` or `charset`, is only a name 2456 name = ( 2457 None 2458 if self._curr.text.upper() in self.NO_PAREN_FUNCTION_PARSERS 2459 else self._parse_column() 2460 ) or self._parse_id_var() 2461 tables = ( 2462 self.expression(exp.Column(this=name)) if isinstance(name, exp.Identifier) else name 2463 ) 2464 elif kind in ("TABLE", "VIEW"): 2465 tables = self._parse_csv(lambda: self._parse_table_parts(schema=True)) 2466 else: 2467 tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA") 2468 2469 cluster: exp.Expr | None = None 2470 if self._match(TokenType.ON): 2471 if kind == "INDEX": 2472 cluster = self.expression(exp.OnProperty(this=self._parse_table_parts())) 2473 else: 2474 cluster = self._parse_on_property() 2475 2476 if self._match(TokenType.L_PAREN, advance=False): 2477 expressions = self._parse_wrapped_csv(self._parse_types) 2478 else: 2479 expressions = None 2480 2481 cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper() 2482 2483 return self.expression( 2484 exp.Drop( 2485 exists=if_exists, 2486 tables=ensure_list(tables), 2487 expressions=expressions, 2488 kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind, 2489 temporary=temporary, 2490 materialized=materialized, 2491 cascade=cascade_or_restrict == "CASCADE", 2492 restrict=cascade_or_restrict == "RESTRICT", 2493 constraints=self._match_text_seq("CONSTRAINTS"), 2494 purge=self._match_text_seq("PURGE"), 2495 cluster=cluster, 2496 concurrently=concurrently, 2497 sync=self._match_text_seq("SYNC"), 2498 iceberg=iceberg, 2499 force=self._match_text_seq("FORCE"), 2500 ) 2501 ) 2502 2503 def _parse_exists(self, not_: bool = False) -> bool | None: 2504 if not_: 2505 return self._match_text_seq("IF", "NOT", "EXISTS") 2506 return self._match_text_seq("IF", "EXISTS") 2507 2508 def _parse_create(self) -> exp.Create | exp.Command: 2509 # Note: this can't be None because we've matched a statement parser 2510 start = self._prev 2511 2512 replace = ( 2513 start.token_type == TokenType.REPLACE 2514 or self._match_pair(TokenType.OR, TokenType.REPLACE) 2515 or self._match_pair(TokenType.OR, TokenType.ALTER) 2516 ) 2517 refresh = self._match_pair(TokenType.OR, TokenType.REFRESH) 2518 2519 unique = self._match(TokenType.UNIQUE) 2520 2521 if self._match_text_seq("CLUSTERED", "COLUMNSTORE"): 2522 clustered = True 2523 elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq( 2524 "COLUMNSTORE" 2525 ): 2526 clustered = False 2527 else: 2528 clustered = None 2529 2530 if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False): 2531 self._advance() 2532 2533 properties = None 2534 create_token = self._match_set(self.CREATABLES) and self._prev 2535 2536 if not create_token: 2537 # exp.Properties.Location.POST_CREATE 2538 properties = self._parse_properties() 2539 create_token = self._match_set(self.CREATABLES) and self._prev 2540 2541 if not properties or not create_token: 2542 return self._parse_as_command(start) 2543 2544 create_token_type = t.cast(Token, create_token).token_type 2545 2546 concurrently = self._match_text_seq("CONCURRENTLY") 2547 exists = self._parse_exists(not_=True) 2548 this = None 2549 expression: exp.Expr | None = None 2550 indexes = None 2551 no_schema_binding = None 2552 begin = None 2553 clone = None 2554 2555 def extend_props(temp_props: exp.Properties | None) -> None: 2556 nonlocal properties 2557 if properties and temp_props: 2558 properties.expressions.extend(temp_props.expressions) 2559 elif temp_props: 2560 properties = temp_props 2561 2562 if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE): 2563 this = self._parse_user_defined_function(kind=create_token_type) 2564 2565 # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature) 2566 extend_props(self._parse_properties()) 2567 2568 expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None 2569 2570 if ( 2571 not expression 2572 and create_token_type == TokenType.FUNCTION 2573 and isinstance(this, exp.UserDefinedFunction) 2574 and this.args.get("wrapped") 2575 ): 2576 pre_table_index = self._index 2577 is_table = self._match(TokenType.TABLE) 2578 2579 expression = self._parse_expression() 2580 overload_mode = bool( 2581 expression 2582 and self._curr.token_type == TokenType.COMMA 2583 and self._next.token_type == TokenType.L_PAREN 2584 ) 2585 if not overload_mode: 2586 self._retreat(pre_table_index) 2587 is_table = False 2588 expression = None 2589 else: 2590 is_table = False 2591 overload_mode = False 2592 2593 extend_props(self._parse_function_properties()) 2594 2595 if not expression: 2596 if self._match(TokenType.COMMAND): 2597 expression = self._parse_as_command(self._prev) 2598 else: 2599 begin = self._match(TokenType.BEGIN) 2600 return_ = self._match_text_seq("RETURN") 2601 2602 if self._match(TokenType.STRING, advance=False): 2603 # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property 2604 # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement 2605 expression = self._parse_string() 2606 extend_props(self._parse_properties()) 2607 else: 2608 expression = ( 2609 self._parse_user_defined_function_expression() 2610 if create_token_type == TokenType.FUNCTION 2611 else self._parse_block() 2612 ) 2613 2614 if return_: 2615 expression = self.expression(exp.Return(this=expression)) 2616 2617 if overload_mode and expression: 2618 expression = self._parse_macro_overloads( 2619 t.cast(exp.UserDefinedFunction, this), expression, is_table 2620 ) 2621 elif create_token_type == TokenType.INDEX: 2622 # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c) 2623 if not self._match(TokenType.ON): 2624 index = self._parse_id_var() 2625 anonymous = False 2626 else: 2627 index = None 2628 anonymous = True 2629 2630 this = self._parse_index(index=index, anonymous=anonymous) 2631 elif ( 2632 create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER) 2633 ) or create_token_type == TokenType.TRIGGER: 2634 if is_constraint := (create_token_type == TokenType.CONSTRAINT): 2635 create_token = self._prev 2636 2637 trigger_name = self._parse_id_var() 2638 if not trigger_name: 2639 return self._parse_as_command(start) 2640 2641 timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False) 2642 timing = timing_var.this if timing_var else None 2643 if not timing: 2644 return self._parse_as_command(start) 2645 2646 events = self._parse_trigger_events() 2647 if not self._match(TokenType.ON): 2648 self.raise_error("Expected ON in trigger definition") 2649 2650 table = self._parse_table_parts() 2651 referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None 2652 deferrable, initially = self._parse_trigger_deferrable() 2653 referencing = self._parse_trigger_referencing() 2654 for_each = self._parse_trigger_for_each() 2655 when = self._match_text_seq("WHEN") and self._parse_wrapped( 2656 self._parse_disjunction, optional=True 2657 ) 2658 execute = self._parse_trigger_execute() 2659 2660 if execute is None: 2661 return self._parse_as_command(start) 2662 2663 trigger_props = self.expression( 2664 exp.TriggerProperties( 2665 table=table, 2666 timing=timing, 2667 events=events, 2668 execute=execute, 2669 constraint=is_constraint, 2670 referenced_table=referenced_table, 2671 deferrable=deferrable, 2672 initially=initially, 2673 referencing=referencing, 2674 for_each=for_each, 2675 when=when, 2676 ) 2677 ) 2678 2679 this = trigger_name 2680 extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else [])) 2681 elif create_token_type == TokenType.TYPE: 2682 this = self._parse_table_parts(schema=True) 2683 if not this or not self._match(TokenType.ALIAS): 2684 return self._parse_as_command(start) 2685 2686 if self._match(TokenType.ENUM): 2687 expression = exp.DataType( 2688 this=exp.DType.ENUM, 2689 expressions=self._parse_wrapped_csv(self._parse_string), 2690 ) 2691 elif self._match(TokenType.L_PAREN, advance=False): 2692 expression = self._parse_schema() 2693 else: 2694 return self._parse_as_command(start) 2695 elif create_token_type in self.DB_CREATABLES: 2696 table_parts = self._parse_table_parts( 2697 schema=True, is_db_reference=create_token_type == TokenType.SCHEMA 2698 ) 2699 2700 # exp.Properties.Location.POST_NAME 2701 self._match(TokenType.COMMA) 2702 extend_props(self._parse_properties(before=True)) 2703 2704 this = self._parse_schema(this=table_parts) 2705 2706 # exp.Properties.Location.POST_SCHEMA and POST_WITH 2707 extend_props(self._parse_properties()) 2708 2709 has_alias = self._match(TokenType.ALIAS) 2710 if not self._match_set(self.DDL_SELECT_TOKENS, advance=False): 2711 # exp.Properties.Location.POST_ALIAS 2712 extend_props(self._parse_properties()) 2713 2714 if create_token_type == TokenType.SEQUENCE: 2715 expression = self._parse_types() 2716 props = self._parse_properties() 2717 if props: 2718 sequence_props = exp.SequenceProperties() 2719 options = [] 2720 for prop in props: 2721 if isinstance(prop, exp.SequenceProperties): 2722 for arg, value in prop.args.items(): 2723 if arg == "options": 2724 options.extend(value) 2725 else: 2726 sequence_props.set(arg, value) 2727 prop.pop() 2728 2729 if options: 2730 sequence_props.set("options", options) 2731 2732 props.append("expressions", sequence_props) 2733 extend_props(props) 2734 else: 2735 expression = self._parse_ddl_select() 2736 2737 # Some dialects also support using a table as an alias instead of a SELECT. 2738 # Here we fallback to this as an alternative. 2739 if not expression and has_alias: 2740 expression = self._try_parse(self._parse_table_parts) 2741 2742 if create_token_type == TokenType.TABLE: 2743 # exp.Properties.Location.POST_EXPRESSION 2744 extend_props(self._parse_properties()) 2745 2746 indexes = [] 2747 while True: 2748 index = self._parse_index() 2749 2750 # exp.Properties.Location.POST_INDEX 2751 extend_props(self._parse_properties()) 2752 if not index: 2753 break 2754 else: 2755 self._match(TokenType.COMMA) 2756 indexes.append(index) 2757 elif create_token_type == TokenType.VIEW: 2758 if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"): 2759 no_schema_binding = True 2760 elif create_token_type in (TokenType.SINK, TokenType.SOURCE): 2761 extend_props(self._parse_properties()) 2762 2763 shallow = self._match_text_seq("SHALLOW") 2764 2765 if self._match_texts(self.CLONE_KEYWORDS): 2766 copy = self._prev.text.lower() == "copy" 2767 clone = self.expression( 2768 exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy) 2769 ) 2770 2771 if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False): 2772 return self._parse_as_command(start) 2773 2774 create_kind_text = create_token.text.upper() 2775 return self.expression( 2776 exp.Create( 2777 this=this, 2778 kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text, 2779 replace=replace, 2780 refresh=refresh, 2781 unique=unique, 2782 expression=expression, 2783 exists=exists, 2784 properties=properties, 2785 indexes=indexes, 2786 no_schema_binding=no_schema_binding, 2787 begin=begin, 2788 clone=clone, 2789 concurrently=concurrently, 2790 clustered=clustered, 2791 ) 2792 ) 2793 2794 def _parse_sequence_properties(self) -> exp.SequenceProperties | None: 2795 seq = exp.SequenceProperties() 2796 2797 options = [] 2798 index = self._index 2799 2800 while self._curr: 2801 self._match(TokenType.COMMA) 2802 if self._match_text_seq("INCREMENT"): 2803 self._match_text_seq("BY") 2804 self._match_text_seq("=") 2805 seq.set("increment", self._parse_term()) 2806 elif self._match_text_seq("MINVALUE"): 2807 seq.set("minvalue", self._parse_term()) 2808 elif self._match_text_seq("MAXVALUE"): 2809 seq.set("maxvalue", self._parse_term()) 2810 elif self._match_text_seq("START"): 2811 self._match_text_seq("WITH") 2812 self._match_text_seq("=") 2813 seq.set("start", self._parse_term()) 2814 elif self._match_text_seq("CACHE"): 2815 # T-SQL allows empty CACHE which is initialized dynamically 2816 seq.set("cache", self._parse_number() or True) 2817 elif self._match_text_seq("OWNED", "BY"): 2818 # "OWNED BY NONE" is the default 2819 seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column()) 2820 else: 2821 opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False) 2822 if opt: 2823 options.append(opt) 2824 else: 2825 break 2826 2827 seq.set("options", options if options else None) 2828 return None if self._index == index else seq 2829 2830 def _parse_trigger_events(self) -> list[exp.TriggerEvent]: 2831 events = [] 2832 2833 while True: 2834 event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper() 2835 2836 if not event_type: 2837 self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)") 2838 2839 columns = ( 2840 self._parse_csv(self._parse_column) 2841 if event_type == "UPDATE" and self._match_text_seq("OF") 2842 else None 2843 ) 2844 2845 events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns))) 2846 2847 if not self._match(TokenType.OR): 2848 break 2849 2850 return events 2851 2852 def _parse_trigger_deferrable( 2853 self, 2854 ) -> tuple[str | None, str | None]: 2855 deferrable_var = self._parse_var_from_options( 2856 self.TRIGGER_DEFERRABLE, raise_unmatched=False 2857 ) 2858 deferrable = deferrable_var.this if deferrable_var else None 2859 2860 initially = None 2861 if deferrable and self._match_text_seq("INITIALLY"): 2862 initially = ( 2863 self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None 2864 ) 2865 2866 return deferrable, initially 2867 2868 def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None: 2869 if not self._match_text_seq(keyword): 2870 return None 2871 if not self._match_text_seq("TABLE"): 2872 self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause") 2873 self._match_text_seq("AS") 2874 return self._parse_id_var() 2875 2876 def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None: 2877 if not self._match_text_seq("REFERENCING"): 2878 return None 2879 2880 old_alias = None 2881 new_alias = None 2882 2883 while True: 2884 if alias := self._parse_trigger_referencing_clause("OLD"): 2885 if old_alias is not None: 2886 self.raise_error("Duplicate OLD clause in REFERENCING") 2887 old_alias = alias 2888 elif alias := self._parse_trigger_referencing_clause("NEW"): 2889 if new_alias is not None: 2890 self.raise_error("Duplicate NEW clause in REFERENCING") 2891 new_alias = alias 2892 else: 2893 break 2894 2895 if old_alias is None and new_alias is None: 2896 self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE") 2897 2898 return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias)) 2899 2900 def _parse_trigger_for_each(self) -> str | None: 2901 if not self._match_text_seq("FOR", "EACH"): 2902 return None 2903 2904 return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None 2905 2906 def _parse_trigger_execute(self) -> exp.TriggerExecute | None: 2907 if not self._match(TokenType.EXECUTE): 2908 return None 2909 2910 if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)): 2911 self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE") 2912 2913 func_call = self._parse_column() 2914 return self.expression(exp.TriggerExecute(this=func_call)) 2915 2916 def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None: 2917 # only used for teradata currently 2918 self._match(TokenType.COMMA) 2919 2920 kwargs = { 2921 "no": self._match_text_seq("NO"), 2922 "dual": self._match_text_seq("DUAL"), 2923 "before": self._match_text_seq("BEFORE"), 2924 "default": self._match_text_seq("DEFAULT"), 2925 "local": (self._match_text_seq("LOCAL") and "LOCAL") 2926 or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"), 2927 "after": self._match_text_seq("AFTER"), 2928 "minimum": self._match_texts(("MIN", "MINIMUM")), 2929 "maximum": self._match_texts(("MAX", "MAXIMUM")), 2930 } 2931 2932 if self._match_texts(self.PROPERTY_PARSERS): 2933 parser = self.PROPERTY_PARSERS[self._prev.text.upper()] 2934 try: 2935 return parser(self, **{k: v for k, v in kwargs.items() if v}) 2936 except TypeError: 2937 self.raise_error(f"Cannot parse property '{self._prev.text}'") 2938 2939 if self._match_pair(TokenType.CHAR, TokenType.SET): 2940 return self._parse_character_set(default=bool(kwargs["default"])) 2941 2942 return None 2943 2944 def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]: 2945 return self._parse_wrapped_csv(self._parse_property) 2946 2947 def _parse_property(self) -> exp.Expr | list[exp.Expr] | None: 2948 if self._match_texts(self.PROPERTY_PARSERS): 2949 return self.PROPERTY_PARSERS[self._prev.text.upper()](self) 2950 2951 if self._match_pair(TokenType.CHAR, TokenType.SET): 2952 return self._parse_character_set() 2953 2954 if self._match(TokenType.DEFAULT): 2955 if self._match_texts(self.PROPERTY_PARSERS): 2956 return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True) 2957 2958 if self._match_pair(TokenType.CHAR, TokenType.SET): 2959 return self._parse_character_set(default=True) 2960 2961 if self._match_text_seq("COMPOUND", "SORTKEY"): 2962 return self._parse_sortkey(compound=True) 2963 2964 if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"): 2965 return self.expression(exp.ParameterStyleProperty(this="PANDAS")) 2966 2967 if self._match_text_seq("NOT", "DETERMINISTIC"): 2968 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 2969 2970 index = self._index 2971 2972 seq_props = self._parse_sequence_properties() 2973 if seq_props: 2974 return seq_props 2975 2976 self._retreat(index) 2977 return self._parse_key_value_property() 2978 2979 def _parse_key_value_property( 2980 self, parse_value: t.Callable[[], exp.Expr | None] | None = None 2981 ) -> exp.Property | None: 2982 index = self._index 2983 key = self._parse_column() 2984 2985 if not self._match(TokenType.EQ): 2986 self._retreat(index) 2987 return None 2988 2989 # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise 2990 if isinstance(key, exp.Column): 2991 key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name) 2992 2993 value = ( 2994 parse_value() 2995 if parse_value 2996 else self._parse_bitwise() or self._parse_var(any_token=True) 2997 ) 2998 2999 # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier()) 3000 if isinstance(value, exp.Column): 3001 value = exp.var(value.name) 3002 3003 return self.expression(exp.Property(this=key, value=value)) 3004 3005 def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty: 3006 if self._match_text_seq("BY"): 3007 return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string())) 3008 3009 self._match(TokenType.ALIAS) 3010 input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None 3011 output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None 3012 3013 return self.expression( 3014 exp.FileFormatProperty( 3015 this=( 3016 self.expression( 3017 exp.InputOutputFormat( 3018 input_format=input_format, output_format=output_format 3019 ) 3020 ) 3021 if input_format or output_format 3022 else self._parse_var_or_string() or self._parse_number() or self._parse_id_var() 3023 ), 3024 hive_format=True, 3025 ) 3026 ) 3027 3028 def _parse_unquoted_field(self) -> exp.Expr | None: 3029 field = self._parse_field() 3030 if isinstance(field, exp.Identifier) and not field.quoted: 3031 field = exp.var(field) 3032 3033 return field 3034 3035 def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E: 3036 self._match(TokenType.EQ) 3037 self._match(TokenType.ALIAS) 3038 3039 return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs)) 3040 3041 def _parse_properties(self, before: bool | None = None) -> exp.Properties | None: 3042 properties = [] 3043 while True: 3044 if before: 3045 prop = self._parse_property_before() 3046 else: 3047 prop = self._parse_property() 3048 if not prop: 3049 break 3050 for p in ensure_list(prop): 3051 properties.append(p) 3052 3053 self._match(TokenType.COMMA, expression=properties[-1]) 3054 3055 if properties: 3056 return self.expression(exp.Properties(expressions=properties)) 3057 3058 return None 3059 3060 def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty: 3061 return self.expression( 3062 exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION")) 3063 ) 3064 3065 def _parse_sql_security(self) -> exp.SqlSecurityProperty: 3066 return self.expression( 3067 exp.SqlSecurityProperty( 3068 this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper() 3069 ) 3070 ) 3071 3072 def _parse_settings_property(self) -> exp.SettingsProperty: 3073 return self.expression( 3074 exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment)) 3075 ) 3076 3077 def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None: 3078 if not self._match_text_seq("ON", "NULL", "INPUT"): 3079 self._retreat(self._index - 1) 3080 return None 3081 3082 return self.expression(exp.CalledOnNullInputProperty()) 3083 3084 def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty: 3085 if self._index >= 2: 3086 pre_volatile_token = self._tokens[self._index - 2] 3087 else: 3088 pre_volatile_token = None 3089 3090 if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS: 3091 return exp.VolatileProperty() 3092 3093 return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE"))) 3094 3095 def _parse_retention_period(self) -> exp.Var: 3096 # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...} 3097 number = self._parse_number() 3098 number_str = f"{number} " if number else "" 3099 unit = self._parse_var(any_token=True) 3100 return exp.var(f"{number_str}{unit}") 3101 3102 def _parse_system_versioning_property( 3103 self, with_: bool = False 3104 ) -> exp.WithSystemVersioningProperty: 3105 self._match(TokenType.EQ) 3106 prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_)) 3107 3108 if self._match_text_seq("OFF"): 3109 prop.set("on", False) 3110 return prop 3111 3112 self._match(TokenType.ON) 3113 if self._match(TokenType.L_PAREN): 3114 while self._curr and not self._match(TokenType.R_PAREN): 3115 if self._match_text_seq("HISTORY_TABLE", "="): 3116 prop.set("this", self._parse_table_parts()) 3117 elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="): 3118 prop.set("data_consistency", self._advance_any() and self._prev.text.upper()) 3119 elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="): 3120 prop.set("retention_period", self._parse_retention_period()) 3121 3122 self._match(TokenType.COMMA) 3123 3124 return prop 3125 3126 def _parse_data_deletion_property(self) -> exp.DataDeletionProperty: 3127 self._match(TokenType.EQ) 3128 on = self._match_text_seq("ON") or not self._match_text_seq("OFF") 3129 prop = self.expression(exp.DataDeletionProperty(on=on)) 3130 3131 if self._match(TokenType.L_PAREN): 3132 while self._curr and not self._match(TokenType.R_PAREN): 3133 if self._match_text_seq("FILTER_COLUMN", "="): 3134 prop.set("filter_column", self._parse_column()) 3135 elif self._match_text_seq("RETENTION_PERIOD", "="): 3136 prop.set("retention_period", self._parse_retention_period()) 3137 3138 self._match(TokenType.COMMA) 3139 3140 return prop 3141 3142 def _parse_distributed_property(self) -> exp.DistributedByProperty: 3143 kind = "HASH" 3144 expressions: list[exp.Expr] | None = None 3145 if self._match_text_seq("BY", "HASH"): 3146 expressions = self._parse_wrapped_csv(self._parse_id_var) 3147 elif self._match_text_seq("BY", "RANDOM"): 3148 kind = "RANDOM" 3149 3150 # If the BUCKETS keyword is not present, the number of buckets is AUTO 3151 buckets: exp.Expr | None = None 3152 if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"): 3153 buckets = self._parse_number() 3154 3155 return self.expression( 3156 exp.DistributedByProperty( 3157 expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order() 3158 ) 3159 ) 3160 3161 def _parse_composite_key_property(self, expr_type: type[E]) -> E: 3162 self._match_text_seq("KEY") 3163 expressions = self._parse_wrapped_id_vars() 3164 return self.expression(expr_type(expressions=expressions)) 3165 3166 def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]: 3167 if self._match_text_seq("(", "SYSTEM_VERSIONING"): 3168 prop = self._parse_system_versioning_property(with_=True) 3169 self._match_r_paren() 3170 return prop 3171 3172 if self._match(TokenType.L_PAREN, advance=False): 3173 result: list[exp.Expr] = [] 3174 for i in self._parse_wrapped_properties(): 3175 result.extend(i) if isinstance(i, list) else result.append(i) 3176 return result 3177 3178 if self._match_text_seq("JOURNAL"): 3179 return self._parse_withjournaltable() 3180 3181 if self._match_texts(self.VIEW_ATTRIBUTES): 3182 return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper())) 3183 3184 if self._match_text_seq("DATA"): 3185 return self._parse_withdata(no=False) 3186 elif self._match_text_seq("NO", "DATA"): 3187 return self._parse_withdata(no=True) 3188 3189 if self._match(TokenType.SERDE_PROPERTIES, advance=False): 3190 return self._parse_serde_properties(with_=True) 3191 3192 if self._match(TokenType.SCHEMA): 3193 return self.expression( 3194 exp.WithSchemaBindingProperty( 3195 this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS) 3196 ) 3197 ) 3198 3199 if self._match_texts(self.PROCEDURE_OPTIONS, advance=False): 3200 return self.expression( 3201 exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option)) 3202 ) 3203 3204 if not self._next: 3205 return None 3206 3207 return self._parse_withisolatedloading() 3208 3209 def _parse_procedure_option(self) -> exp.Expr | None: 3210 if self._match_text_seq("EXECUTE", "AS"): 3211 return self.expression( 3212 exp.ExecuteAsProperty( 3213 this=self._parse_var_from_options( 3214 self.EXECUTE_AS_OPTIONS, raise_unmatched=False 3215 ) 3216 or self._parse_string() 3217 ) 3218 ) 3219 3220 return self._parse_var_from_options(self.PROCEDURE_OPTIONS) 3221 3222 # https://dev.mysql.com/doc/refman/8.0/en/create-view.html 3223 def _parse_definer(self) -> exp.DefinerProperty | None: 3224 self._match(TokenType.EQ) 3225 3226 user = self._parse_id_var() 3227 self._match(TokenType.PARAMETER) 3228 host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text) 3229 3230 if not user or not host: 3231 return None 3232 3233 return exp.DefinerProperty(this=f"{user}@{host}") 3234 3235 def _parse_withjournaltable(self) -> exp.WithJournalTableProperty: 3236 self._match(TokenType.TABLE) 3237 self._match(TokenType.EQ) 3238 return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts())) 3239 3240 def _parse_log(self, no: bool = False) -> exp.LogProperty: 3241 return self.expression(exp.LogProperty(no=no)) 3242 3243 def _parse_journal(self, **kwargs) -> exp.JournalProperty: 3244 return self.expression(exp.JournalProperty(**kwargs)) 3245 3246 def _parse_checksum(self) -> exp.ChecksumProperty: 3247 self._match(TokenType.EQ) 3248 3249 on = None 3250 if self._match(TokenType.ON): 3251 on = True 3252 elif self._match_text_seq("OFF"): 3253 on = False 3254 3255 return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT))) 3256 3257 def _parse_cluster(self) -> exp.Cluster: 3258 self._match(TokenType.CLUSTER_BY) 3259 return self.expression( 3260 exp.Cluster( 3261 expressions=self._parse_csv(self._parse_column), 3262 ) 3263 ) 3264 3265 def _parse_cluster_property(self) -> exp.ClusterProperty: 3266 return self.expression( 3267 exp.ClusterProperty( 3268 expressions=self._parse_wrapped_csv(self._parse_column), 3269 ) 3270 ) 3271 3272 def _parse_clustered_by(self) -> exp.ClusteredByProperty: 3273 self._match_text_seq("BY") 3274 3275 self._match_l_paren() 3276 expressions = self._parse_csv(self._parse_column) 3277 self._match_r_paren() 3278 3279 if self._match_text_seq("SORTED", "BY"): 3280 self._match_l_paren() 3281 sorted_by = self._parse_csv(self._parse_ordered) 3282 self._match_r_paren() 3283 else: 3284 sorted_by = None 3285 3286 self._match(TokenType.INTO) 3287 buckets = self._parse_number() 3288 self._match_text_seq("BUCKETS") 3289 3290 return self.expression( 3291 exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets) 3292 ) 3293 3294 def _parse_copy_property(self) -> exp.CopyGrantsProperty | None: 3295 if not self._match_text_seq("GRANTS"): 3296 self._retreat(self._index - 1) 3297 return None 3298 3299 return self.expression(exp.CopyGrantsProperty()) 3300 3301 def _parse_freespace(self) -> exp.FreespaceProperty: 3302 self._match(TokenType.EQ) 3303 return self.expression( 3304 exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT)) 3305 ) 3306 3307 def _parse_mergeblockratio( 3308 self, no: bool = False, default: bool = False 3309 ) -> exp.MergeBlockRatioProperty: 3310 if self._match(TokenType.EQ): 3311 return self.expression( 3312 exp.MergeBlockRatioProperty( 3313 this=self._parse_number(), percent=self._match(TokenType.PERCENT) 3314 ) 3315 ) 3316 3317 return self.expression(exp.MergeBlockRatioProperty(no=no, default=default)) 3318 3319 def _parse_datablocksize( 3320 self, 3321 default: bool | None = None, 3322 minimum: bool | None = None, 3323 maximum: bool | None = None, 3324 ) -> exp.DataBlocksizeProperty: 3325 self._match(TokenType.EQ) 3326 size = self._parse_number() 3327 3328 units = None 3329 if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")): 3330 units = self._prev.text 3331 3332 return self.expression( 3333 exp.DataBlocksizeProperty( 3334 size=size, units=units, default=default, minimum=minimum, maximum=maximum 3335 ) 3336 ) 3337 3338 def _parse_blockcompression(self) -> exp.BlockCompressionProperty: 3339 self._match(TokenType.EQ) 3340 always = self._match_text_seq("ALWAYS") 3341 manual = self._match_text_seq("MANUAL") 3342 never = self._match_text_seq("NEVER") 3343 default = self._match_text_seq("DEFAULT") 3344 3345 autotemp = None 3346 if self._match_text_seq("AUTOTEMP"): 3347 autotemp = self._parse_schema() 3348 3349 return self.expression( 3350 exp.BlockCompressionProperty( 3351 always=always, manual=manual, never=never, default=default, autotemp=autotemp 3352 ) 3353 ) 3354 3355 def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None: 3356 index = self._index 3357 no = self._match_text_seq("NO") 3358 concurrent = self._match_text_seq("CONCURRENT") 3359 3360 if not self._match_text_seq("ISOLATED", "LOADING"): 3361 self._retreat(index) 3362 return None 3363 3364 target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False) 3365 return self.expression( 3366 exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target) 3367 ) 3368 3369 def _parse_locking(self) -> exp.LockingProperty: 3370 if self._match(TokenType.TABLE): 3371 kind = "TABLE" 3372 elif self._match(TokenType.VIEW): 3373 kind = "VIEW" 3374 elif self._match(TokenType.ROW): 3375 kind = "ROW" 3376 elif self._match_text_seq("DATABASE"): 3377 kind = "DATABASE" 3378 else: 3379 kind = None 3380 3381 if kind in ("DATABASE", "TABLE", "VIEW"): 3382 this = self._parse_table_parts() 3383 else: 3384 this = None 3385 3386 if self._match(TokenType.FOR): 3387 for_or_in = "FOR" 3388 elif self._match(TokenType.IN): 3389 for_or_in = "IN" 3390 else: 3391 for_or_in = None 3392 3393 if self._match_text_seq("ACCESS"): 3394 lock_type = "ACCESS" 3395 elif self._match_texts(("EXCL", "EXCLUSIVE")): 3396 lock_type = "EXCLUSIVE" 3397 elif self._match_text_seq("SHARE"): 3398 lock_type = "SHARE" 3399 elif self._match_text_seq("READ"): 3400 lock_type = "READ" 3401 elif self._match_text_seq("WRITE"): 3402 lock_type = "WRITE" 3403 elif self._match_text_seq("CHECKSUM"): 3404 lock_type = "CHECKSUM" 3405 else: 3406 lock_type = None 3407 3408 override = self._match_text_seq("OVERRIDE") 3409 3410 return self.expression( 3411 exp.LockingProperty( 3412 this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override 3413 ) 3414 ) 3415 3416 def _parse_partition_by(self) -> list[exp.Expr]: 3417 if self._match(TokenType.PARTITION_BY): 3418 return self._parse_csv(self._parse_disjunction) 3419 return [] 3420 3421 def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec: 3422 def _parse_partition_bound_expr() -> exp.Expr | None: 3423 if self._match_text_seq("MINVALUE"): 3424 return exp.var("MINVALUE") 3425 if self._match_text_seq("MAXVALUE"): 3426 return exp.var("MAXVALUE") 3427 return self._parse_bitwise() 3428 3429 this: exp.Expr | list[exp.Expr] | None = None 3430 expression = None 3431 from_expressions = None 3432 to_expressions = None 3433 3434 if self._match(TokenType.IN): 3435 this = self._parse_wrapped_csv(self._parse_bitwise) 3436 elif self._match(TokenType.FROM): 3437 from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3438 self._match_text_seq("TO") 3439 to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr) 3440 elif self._match_text_seq("WITH", "(", "MODULUS"): 3441 this = self._parse_number() 3442 self._match_text_seq(",", "REMAINDER") 3443 expression = self._parse_number() 3444 self._match_r_paren() 3445 else: 3446 self.raise_error("Failed to parse partition bound spec.") 3447 3448 return self.expression( 3449 exp.PartitionBoundSpec( 3450 this=this, 3451 expression=expression, 3452 from_expressions=from_expressions, 3453 to_expressions=to_expressions, 3454 ) 3455 ) 3456 3457 # https://www.postgresql.org/docs/current/sql-createtable.html 3458 def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None: 3459 if not self._match_text_seq("OF"): 3460 self._retreat(self._index - 1) 3461 return None 3462 3463 this = self._parse_table(schema=True) 3464 3465 if self._match(TokenType.DEFAULT): 3466 expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT") 3467 elif self._match_text_seq("FOR", "VALUES"): 3468 expression = self._parse_partition_bound_spec() 3469 else: 3470 self.raise_error("Expecting either DEFAULT or FOR VALUES clause.") 3471 3472 return self.expression(exp.PartitionedOfProperty(this=this, expression=expression)) 3473 3474 def _parse_partitioned_by(self) -> exp.PartitionedByProperty: 3475 self._match(TokenType.EQ) 3476 return self.expression( 3477 exp.PartitionedByProperty( 3478 this=self._parse_schema() or self._parse_bracket(self._parse_field()) 3479 ) 3480 ) 3481 3482 def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty: 3483 if self._match_text_seq("AND", "STATISTICS"): 3484 statistics = True 3485 elif self._match_text_seq("AND", "NO", "STATISTICS"): 3486 statistics = False 3487 else: 3488 statistics = None 3489 3490 return self.expression(exp.WithDataProperty(no=no, statistics=statistics)) 3491 3492 def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None: 3493 if self._match_text_seq("SQL"): 3494 return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL")) 3495 return None 3496 3497 def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None: 3498 if self._match_text_seq("SQL", "DATA"): 3499 return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA")) 3500 return None 3501 3502 def _parse_no_property(self) -> exp.Expr | None: 3503 if self._match_text_seq("PRIMARY", "INDEX"): 3504 return exp.NoPrimaryIndexProperty() 3505 if self._match_text_seq("SQL"): 3506 return self.expression(exp.SqlReadWriteProperty(this="NO SQL")) 3507 return None 3508 3509 def _parse_on_property(self) -> exp.Expr | None: 3510 if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"): 3511 return exp.OnCommitProperty() 3512 if self._match_text_seq("COMMIT", "DELETE", "ROWS"): 3513 return exp.OnCommitProperty(delete=True) 3514 return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var()))) 3515 3516 def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None: 3517 if self._match_text_seq("SQL", "DATA"): 3518 return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA")) 3519 return None 3520 3521 def _parse_distkey(self) -> exp.DistKeyProperty: 3522 return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var))) 3523 3524 def _parse_create_like(self) -> exp.LikeProperty | None: 3525 table = self._parse_table(schema=True) 3526 3527 options = [] 3528 while self._match_texts(("INCLUDING", "EXCLUDING")): 3529 this = self._prev.text.upper() 3530 3531 id_var = self._parse_id_var() 3532 if not id_var: 3533 return None 3534 3535 options.append( 3536 self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper()))) 3537 ) 3538 3539 return self.expression(exp.LikeProperty(this=table, expressions=options)) 3540 3541 def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty: 3542 return self.expression( 3543 exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound) 3544 ) 3545 3546 def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty: 3547 self._match(TokenType.EQ) 3548 return self.expression( 3549 exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default) 3550 ) 3551 3552 def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty: 3553 self._match_text_seq("WITH", "CONNECTION") 3554 return self.expression( 3555 exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts()) 3556 ) 3557 3558 def _parse_returns(self) -> exp.ReturnsProperty: 3559 value: exp.Expr | None 3560 null = None 3561 is_table = self._match(TokenType.TABLE) 3562 3563 if is_table: 3564 if self._match(TokenType.LT): 3565 value = self.expression( 3566 exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types)) 3567 ) 3568 if not self._match(TokenType.GT): 3569 self.raise_error("Expecting >") 3570 else: 3571 value = self._parse_schema(exp.var("TABLE")) 3572 elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"): 3573 null = True 3574 value = None 3575 else: 3576 value = self._parse_types() 3577 3578 return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null)) 3579 3580 def _parse_describe(self) -> exp.Describe: 3581 kind = self._prev.text if self._match_set(self.CREATABLES) else None 3582 style: str | None = ( 3583 self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None 3584 ) 3585 if self._match(TokenType.DOT): 3586 style = None 3587 self._retreat(self._index - 2) 3588 3589 format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None 3590 3591 if self._match_set(self.STATEMENT_PARSERS, advance=False): 3592 this = self._parse_statement() 3593 else: 3594 this = self._parse_table(schema=True) 3595 3596 properties = self._parse_properties() 3597 expressions = properties.expressions if properties else None 3598 partition = self._parse_partition() 3599 return self.expression( 3600 exp.Describe( 3601 this=this, 3602 style=style, 3603 kind=kind, 3604 expressions=expressions, 3605 partition=partition, 3606 format=format, 3607 as_json=self._match_text_seq("AS", "JSON"), 3608 ) 3609 ) 3610 3611 def _parse_multitable_inserts( 3612 self, comments: list[str] | None, kind: str | None = None 3613 ) -> exp.MultitableInserts: 3614 kind = kind or self._prev.text.upper() 3615 expressions = [] 3616 3617 def parse_conditional_insert() -> exp.ConditionalInsert | None: 3618 if self._match(TokenType.WHEN): 3619 expression = self._parse_disjunction() 3620 self._match(TokenType.THEN) 3621 else: 3622 expression = None 3623 3624 else_ = self._match(TokenType.ELSE) 3625 3626 if not self._match(TokenType.INTO): 3627 return None 3628 3629 return self.expression( 3630 exp.ConditionalInsert( 3631 this=self.expression( 3632 exp.Insert( 3633 this=self._parse_table(schema=True), 3634 expression=self._parse_derived_table_values(), 3635 ) 3636 ), 3637 expression=expression, 3638 else_=else_, 3639 ) 3640 ) 3641 3642 expression = parse_conditional_insert() 3643 while expression is not None: 3644 expressions.append(expression) 3645 expression = parse_conditional_insert() 3646 3647 return self.expression( 3648 exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()), 3649 comments=comments, 3650 ) 3651 3652 def _parse_insert(self) -> exp.Insert | exp.MultitableInserts: 3653 comments: list[str] = [] 3654 hint = self._parse_hint() 3655 overwrite = self._match(TokenType.OVERWRITE) 3656 ignore = self._match(TokenType.IGNORE) 3657 local = self._match_text_seq("LOCAL") 3658 alternative = None 3659 is_function = None 3660 3661 if self._match_text_seq("DIRECTORY"): 3662 this: exp.Expr | None = self.expression( 3663 exp.Directory( 3664 this=self._parse_var_or_string(), 3665 local=local, 3666 row_format=self._parse_row_format(match_row=True), 3667 ) 3668 ) 3669 else: 3670 if self._match_set((TokenType.FIRST, TokenType.ALL)): 3671 comments += ensure_list(self._prev_comments) 3672 return self._parse_multitable_inserts(comments) 3673 3674 # Oracle's conditional form defaults to ALL when the keyword is omitted 3675 if self._match(TokenType.WHEN, advance=False): 3676 return self._parse_multitable_inserts(comments, kind="ALL") 3677 3678 if self._match(TokenType.OR): 3679 alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text 3680 3681 self._match(TokenType.INTO) 3682 comments += ensure_list(self._prev_comments) 3683 self._match(TokenType.TABLE) 3684 is_function = self._match(TokenType.FUNCTION) 3685 3686 this = self._parse_function() if is_function else self._parse_insert_table() 3687 3688 # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant 3689 set_values = None 3690 if self._match(TokenType.SET): 3691 columns = [] 3692 values = [] 3693 3694 def _parse_set_assignment() -> exp.Expr | None: 3695 target = self._parse_column() 3696 if isinstance(target, exp.Column) and self._match(TokenType.EQ): 3697 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 3698 value: exp.Expr | None = exp.var(self._prev.text.upper()) 3699 else: 3700 value = self._parse_disjunction() 3701 3702 if value: 3703 columns.append(target.this) 3704 values.append(value) 3705 return value 3706 3707 self.raise_error("Expected column assignment in INSERT ... SET") 3708 return None 3709 3710 self._parse_csv(_parse_set_assignment) 3711 3712 this = self.expression(exp.Schema(this=this, expressions=columns)) 3713 set_values = self.expression( 3714 exp.Values( 3715 expressions=[exp.Tuple(expressions=values)], 3716 alias=self._parse_table_alias(), 3717 ) 3718 ) 3719 3720 returning = self._parse_returning() # TSQL allows RETURNING before source 3721 3722 stored = self._match_text_seq("STORED") and self._parse_stored() 3723 by_name = self._match_text_seq("BY", "NAME") 3724 exists = self._parse_exists() 3725 replace_where = None 3726 replace_using = None 3727 3728 if self._match(TokenType.REPLACE): 3729 if self._match(TokenType.WHERE): 3730 replace_where = self._parse_disjunction() 3731 elif self._match(TokenType.USING): 3732 replace_using = self._parse_using_identifiers() 3733 3734 return self.expression( 3735 exp.Insert( 3736 hint=hint, 3737 is_function=is_function, 3738 this=this, 3739 stored=stored, 3740 by_name=by_name, 3741 exists=exists, 3742 where=replace_where, 3743 using=replace_using, 3744 partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(), 3745 settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(), 3746 default=self._match_text_seq("DEFAULT", "VALUES"), 3747 expression=set_values 3748 or self._parse_derived_table_values(allow_value_synonym=True) 3749 or self._parse_ddl_select(), 3750 conflict=self._parse_on_conflict(), 3751 returning=returning or self._parse_returning(), 3752 overwrite=overwrite, 3753 alternative=alternative, 3754 ignore=ignore, 3755 source=self._match(TokenType.TABLE) and self._parse_table(), 3756 ), 3757 comments=comments, 3758 ) 3759 3760 def _parse_insert_table(self) -> exp.Expr | None: 3761 this = self._parse_table(schema=True, parse_partition=True) 3762 if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False): 3763 this.set("alias", self._parse_table_alias()) 3764 return this 3765 3766 def _parse_kill(self) -> exp.Kill: 3767 kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None 3768 3769 return self.expression(exp.Kill(this=self._parse_primary(), kind=kind)) 3770 3771 def _parse_on_conflict(self) -> exp.OnConflict | None: 3772 conflict = self._match_text_seq("ON", "CONFLICT") 3773 duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY") 3774 3775 if not conflict and not duplicate: 3776 return None 3777 3778 conflict_keys = None 3779 constraint = None 3780 3781 if conflict: 3782 if self._match_text_seq("ON", "CONSTRAINT"): 3783 constraint = self._parse_id_var() 3784 elif self._match(TokenType.L_PAREN): 3785 conflict_keys = self._parse_csv(self._parse_indexed_column) 3786 self._match_r_paren() 3787 3788 index_predicate = self._parse_where() 3789 3790 action = self._parse_var_from_options(self.CONFLICT_ACTIONS) 3791 if self._prev.token_type == TokenType.UPDATE: 3792 self._match(TokenType.SET) 3793 expressions = self._parse_csv(self._parse_update_assignment) 3794 else: 3795 expressions = None 3796 3797 return self.expression( 3798 exp.OnConflict( 3799 duplicate=duplicate, 3800 expressions=expressions, 3801 action=action, 3802 conflict_keys=conflict_keys, 3803 index_predicate=index_predicate, 3804 constraint=constraint, 3805 where=self._parse_where(), 3806 ) 3807 ) 3808 3809 def _parse_returning(self) -> exp.Returning | None: 3810 if not self._match(TokenType.RETURNING): 3811 return None 3812 return self.expression( 3813 exp.Returning( 3814 expressions=self._parse_csv(self._parse_expression), 3815 into=self._match(TokenType.INTO) and self._parse_table_part(), 3816 ) 3817 ) 3818 3819 def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3820 if not self._match(TokenType.FORMAT): 3821 return None 3822 return self._parse_row_format() 3823 3824 def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None: 3825 index = self._index 3826 with_ = with_ or self._match_text_seq("WITH") 3827 3828 if not self._match(TokenType.SERDE_PROPERTIES): 3829 self._retreat(index) 3830 return None 3831 return self.expression( 3832 exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_) 3833 ) 3834 3835 def _parse_row_format( 3836 self, match_row: bool = False 3837 ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None: 3838 if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT): 3839 return None 3840 3841 if self._match_text_seq("SERDE"): 3842 this = self._parse_string() 3843 3844 serde_properties = self._parse_serde_properties() 3845 3846 return self.expression( 3847 exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties) 3848 ) 3849 3850 self._match_text_seq("DELIMITED") 3851 3852 kwargs = {} 3853 3854 if self._match_text_seq("FIELDS", "TERMINATED", "BY"): 3855 kwargs["fields"] = self._parse_string() 3856 if self._match_text_seq("ESCAPED", "BY"): 3857 kwargs["escaped"] = self._parse_string() 3858 if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"): 3859 kwargs["collection_items"] = self._parse_string() 3860 if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"): 3861 kwargs["map_keys"] = self._parse_string() 3862 if self._match_text_seq("LINES", "TERMINATED", "BY"): 3863 kwargs["lines"] = self._parse_string() 3864 if self._match_text_seq("NULL", "DEFINED", "AS"): 3865 kwargs["null"] = self._parse_string() 3866 3867 return self.expression(exp.RowFormatDelimitedProperty(**kwargs)) # type: ignore 3868 3869 def _parse_load(self) -> exp.LoadData | exp.Command: 3870 if self._match_text_seq("DATA"): 3871 local = self._match_text_seq("LOCAL") 3872 self._match_text_seq("INPATH") 3873 inpath = self._parse_string() 3874 overwrite = self._match(TokenType.OVERWRITE) 3875 temp: bool | None = None 3876 if self._match(TokenType.INTO): 3877 temp = self._match(TokenType.TEMPORARY) 3878 self._match(TokenType.TABLE) 3879 3880 return self.expression( 3881 exp.LoadData( 3882 this=self._parse_table(schema=True), 3883 local=local, 3884 overwrite=overwrite, 3885 temp=temp, 3886 inpath=inpath, 3887 files=self._match_text_seq("FROM", "FILES") 3888 and exp.Properties(expressions=self._parse_wrapped_properties()), 3889 partition=self._parse_partition(), 3890 input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(), 3891 serde=self._match_text_seq("SERDE") and self._parse_string(), 3892 ) 3893 ) 3894 return self._parse_as_command(self._prev) 3895 3896 def _parse_delete(self) -> exp.Delete: 3897 hint = self._parse_hint() 3898 3899 # This handles MySQL's "Multiple-Table Syntax" 3900 # https://dev.mysql.com/doc/refman/8.0/en/delete.html 3901 tables = None 3902 if not self._match(TokenType.FROM, advance=False): 3903 tables = self._parse_csv(self._parse_table) or None 3904 3905 returning = self._parse_returning() 3906 3907 return self.expression( 3908 exp.Delete( 3909 hint=hint, 3910 tables=tables, 3911 this=self._match(TokenType.FROM) and self._parse_table(joins=True), 3912 using=self._match(TokenType.USING) 3913 and self._parse_csv(lambda: self._parse_table(joins=True)), 3914 cluster=self._match(TokenType.ON) and self._parse_on_property(), 3915 where=self._parse_where(), 3916 returning=returning or self._parse_returning(), 3917 order=self._parse_order(), 3918 limit=self._parse_limit(), 3919 ) 3920 ) 3921 3922 def _parse_update_assignment(self) -> exp.Expr | None: 3923 this = self._parse_comparison() 3924 if self._match(TokenType.EQ): 3925 comments = self._prev_comments 3926 this = self.expression( 3927 exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments 3928 ) 3929 return this 3930 3931 def _parse_update(self) -> exp.Update: 3932 hint = self._parse_hint() 3933 kwargs: dict[str, object] = { 3934 "hint": hint, 3935 "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS), 3936 } 3937 while self._curr: 3938 if self._match(TokenType.SET): 3939 kwargs["expressions"] = self._parse_csv(self._parse_update_assignment) 3940 elif self._match(TokenType.RETURNING, advance=False): 3941 kwargs["returning"] = self._parse_returning() 3942 elif self._match(TokenType.FROM, advance=False): 3943 from_ = self._parse_from(joins=True) 3944 table = from_.this if from_ else None 3945 if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False): 3946 table.set("joins", list(self._parse_joins()) or None) 3947 3948 kwargs["from_"] = from_ 3949 elif self._match(TokenType.WHERE, advance=False): 3950 kwargs["where"] = self._parse_where() 3951 elif self._match(TokenType.ORDER_BY, advance=False): 3952 kwargs["order"] = self._parse_order() 3953 elif self._match(TokenType.LIMIT, advance=False): 3954 kwargs["limit"] = self._parse_limit() 3955 else: 3956 break 3957 3958 return self.expression(exp.Update(**kwargs)) 3959 3960 def _parse_use(self) -> exp.Use: 3961 return self.expression( 3962 exp.Use( 3963 kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False), 3964 this=self._parse_table(schema=False), 3965 ) 3966 ) 3967 3968 def _parse_uncache(self) -> exp.Uncache: 3969 if not self._match(TokenType.TABLE): 3970 self.raise_error("Expecting TABLE after UNCACHE") 3971 3972 return self.expression( 3973 exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True)) 3974 ) 3975 3976 def _parse_cache(self) -> exp.Cache: 3977 lazy = self._match_text_seq("LAZY") 3978 self._match(TokenType.TABLE) 3979 table = self._parse_table(schema=True) 3980 3981 options = [] 3982 if self._match_text_seq("OPTIONS"): 3983 self._match_l_paren() 3984 k = self._parse_string() 3985 self._match(TokenType.EQ) 3986 v = self._parse_string() 3987 options = [k, v] 3988 self._match_r_paren() 3989 3990 self._match(TokenType.ALIAS) 3991 return self.expression( 3992 exp.Cache( 3993 this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True) 3994 ) 3995 ) 3996 3997 def _parse_partition(self) -> exp.Partition | None: 3998 if not self._match_texts(self.PARTITION_KEYWORDS): 3999 return None 4000 4001 return self.expression( 4002 exp.Partition( 4003 subpartition=self._prev.text.upper() == "SUBPARTITION", 4004 expressions=self._parse_wrapped_csv(self._parse_disjunction), 4005 ) 4006 ) 4007 4008 def _parse_value(self, values: bool = True) -> exp.Tuple | None: 4009 def _parse_value_expression() -> exp.Expr | None: 4010 if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT): 4011 return exp.var(self._prev.text.upper()) 4012 return self._parse_expression() 4013 4014 if self._match(TokenType.L_PAREN): 4015 expressions = self._parse_csv(_parse_value_expression) 4016 self._match_r_paren() 4017 return self.expression(exp.Tuple(expressions=expressions)) 4018 4019 # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows. 4020 expression = self._parse_expression() 4021 if expression: 4022 return self.expression(exp.Tuple(expressions=[expression])) 4023 return None 4024 4025 def _parse_projections( 4026 self, 4027 ) -> tuple[list[exp.Expr], list[exp.Expr] | None]: 4028 return self._parse_expressions(), None 4029 4030 def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None: 4031 if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)): 4032 this: exp.Expr | None = self._parse_simplified_pivot( 4033 is_unpivot=self._prev.token_type == TokenType.UNPIVOT 4034 ) 4035 else: 4036 # Support parentheses for duckdb FROM-first syntax 4037 this = ( 4038 self._parse_table(consume_pipe=True) 4039 if table and not self._match(TokenType.FROM, advance=False) 4040 else self._parse_select(nested=True, parse_set_operation=False) 4041 ) 4042 4043 # Transform exp.Values into a exp.Table to pass through parse_query_modifiers 4044 # in case a modifier (e.g. join) is following 4045 if table and isinstance(this, exp.Values) and this.alias: 4046 alias = this.args["alias"].pop() 4047 this = exp.Table(this=this, alias=alias) 4048 4049 this = self._parse_query_modifiers(self._parse_set_operations(this)) 4050 4051 return this 4052 4053 def _parse_select( 4054 self, 4055 nested: bool = False, 4056 table: bool = False, 4057 parse_subquery_alias: bool = True, 4058 parse_set_operation: bool = True, 4059 consume_pipe: bool = True, 4060 ) -> exp.Expr | None: 4061 query = self._parse_select_query( 4062 nested=nested, 4063 table=table, 4064 parse_subquery_alias=parse_subquery_alias, 4065 parse_set_operation=parse_set_operation, 4066 ) 4067 4068 if consume_pipe and self._match(TokenType.PIPE_GT, advance=False): 4069 if isinstance(query, exp.Query): 4070 query = self._parse_pipe_syntax_query(query) 4071 query = query.subquery(copy=False) if query and table else query 4072 4073 return query 4074 4075 def _parse_select_query( 4076 self, 4077 nested: bool = False, 4078 table: bool = False, 4079 parse_subquery_alias: bool = True, 4080 parse_set_operation: bool = True, 4081 ) -> exp.Expr | None: 4082 cte = self._parse_with() 4083 4084 if cte: 4085 this = self._parse_statement() 4086 4087 if not this: 4088 self.raise_error("Failed to parse any statement following CTE") 4089 return cte 4090 4091 while isinstance(this, exp.Subquery) and this.is_wrapper: 4092 this = this.this 4093 4094 assert this is not None 4095 if "with_" in this.arg_types: 4096 if inner_cte := this.args.get("with_"): 4097 cte.set("expressions", cte.expressions + inner_cte.expressions) 4098 if inner_cte.args.get("recursive"): 4099 cte.set("recursive", True) 4100 this.set("with_", cte) 4101 else: 4102 self.raise_error(f"{this.key} does not support CTE") 4103 this = cte 4104 4105 return this 4106 4107 # duckdb supports leading with FROM x 4108 from_ = ( 4109 self._parse_from(joins=True, consume_pipe=True) 4110 if self._match(TokenType.FROM, advance=False) 4111 else None 4112 ) 4113 laterals = list(iter(self._parse_lateral, None)) if from_ else None 4114 4115 # Hive supports multi-table inserts: FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 4116 if from_ and self._match(TokenType.INSERT, advance=False): 4117 source = from_.this 4118 source.set("laterals", laterals or None) 4119 4120 inserts = [] 4121 while self._match(TokenType.INSERT, advance=False): 4122 inserts.append(self._parse_statement()) 4123 4124 return self.expression( 4125 exp.MultitableInserts(expressions=inserts, source=source), 4126 comments=from_.comments, 4127 ) 4128 4129 if self._match(TokenType.SELECT): 4130 comments = self._prev_comments 4131 4132 hint = self._parse_hint() 4133 4134 if self._next and not self._next.token_type == TokenType.DOT: 4135 all_ = self._match(TokenType.ALL) 4136 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4137 else: 4138 all_, matched_distinct = None, False 4139 4140 kind = ( 4141 self._prev.text.upper() 4142 if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE")) 4143 else None 4144 ) 4145 4146 distinct: exp.Expr | None = ( 4147 self.expression( 4148 exp.Distinct( 4149 on=self._parse_value(values=False) if self._match(TokenType.ON) else None 4150 ) 4151 ) 4152 if matched_distinct 4153 else None 4154 ) 4155 4156 operation_modifiers = [] 4157 while self._curr and self._match_texts(self.OPERATION_MODIFIERS): 4158 operation_modifiers.append(exp.var(self._prev.text.upper())) 4159 4160 limit = self._parse_limit(top=True) 4161 4162 # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ... 4163 if limit and not matched_distinct and not all_: 4164 matched_distinct = self._match_set(self.DISTINCT_TOKENS) 4165 if matched_distinct: 4166 distinct = self.expression( 4167 exp.Distinct( 4168 on=self._parse_value(values=False) 4169 if self._match(TokenType.ON) 4170 else None 4171 ) 4172 ) 4173 else: 4174 all_ = self._match(TokenType.ALL) 4175 4176 if all_ and distinct: 4177 self.raise_error("Cannot specify both ALL and DISTINCT after SELECT") 4178 4179 projections, exclude = self._parse_projections() 4180 4181 this = self.expression( 4182 exp.Select( 4183 kind=kind, 4184 hint=hint, 4185 distinct=distinct, 4186 expressions=projections, 4187 limit=limit, 4188 exclude=exclude, 4189 operation_modifiers=operation_modifiers or None, 4190 ) 4191 ) 4192 this.comments = comments 4193 4194 into = self._parse_into() 4195 if into: 4196 this.set("into", into) 4197 4198 if not from_: 4199 from_ = self._parse_from() 4200 4201 if from_: 4202 this.set("from_", from_) 4203 4204 this = self._parse_query_modifiers(this) 4205 elif (table or nested) and self._match(TokenType.L_PAREN): 4206 comments = self._prev_comments 4207 this = self._parse_wrapped_select(table=table) 4208 4209 if this: 4210 this.add_comments(comments, prepend=True) 4211 4212 # We return early here so that the UNION isn't attached to the subquery by the 4213 # following call to _parse_set_operations, but instead becomes the parent node 4214 self._match_r_paren() 4215 return self._parse_subquery(this, parse_alias=parse_subquery_alias) 4216 elif self._match(TokenType.VALUES, advance=False): 4217 this = self._parse_derived_table_values() 4218 elif from_: 4219 this = exp.select("*").from_(from_.this, copy=False) 4220 this = self._parse_query_modifiers(this) 4221 elif self._match(TokenType.SUMMARIZE): 4222 table = self._match(TokenType.TABLE) 4223 this = self._parse_select() or self._parse_string() or self._parse_table() 4224 return self.expression(exp.Summarize(this=this, table=table)) 4225 elif self._match(TokenType.DESCRIBE): 4226 this = self._parse_describe() 4227 else: 4228 this = None 4229 4230 if laterals and isinstance(this, exp.Select): 4231 this.set("laterals", [*laterals, *(this.args.get("laterals") or [])]) 4232 4233 return self._parse_set_operations(this) if parse_set_operation else this 4234 4235 def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None: 4236 self._match_text_seq("SEARCH") 4237 4238 kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper() 4239 4240 if not kind: 4241 return None 4242 4243 self._match_text_seq("FIRST", "BY") 4244 4245 return self.expression( 4246 exp.RecursiveWithSearch( 4247 kind=kind, 4248 this=self._parse_id_var(), 4249 expression=self._match_text_seq("SET") and self._parse_id_var(), 4250 to=self._match_text_seq("TO") and self._parse_bitwise(), 4251 default=self._match(TokenType.DEFAULT) and self._parse_bitwise(), 4252 using=self._match_text_seq("USING") and self._parse_id_var(), 4253 ) 4254 ) 4255 4256 def _parse_with(self, skip_with_token: bool = False) -> exp.With | None: 4257 if not skip_with_token and not self._match(TokenType.WITH): 4258 return None 4259 4260 comments = self._prev_comments 4261 recursive = self._match(TokenType.RECURSIVE) 4262 4263 last_comments = None 4264 expressions = [] 4265 udfs = [] 4266 while True: 4267 cte = self._parse_cte() 4268 if cte: 4269 if isinstance(cte, exp.FunctionSpecification): 4270 udfs.append(cte) 4271 else: 4272 expressions.append(cte) 4273 4274 if last_comments: 4275 cte.add_comments(last_comments) 4276 4277 if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH): 4278 break 4279 else: 4280 self._match(TokenType.WITH) 4281 recursive = self._match(TokenType.RECURSIVE) or recursive 4282 4283 last_comments = self._prev_comments 4284 4285 search = self._parse_recursive_with_search() 4286 4287 # A SEARCH clause can be followed by a CYCLE clause 4288 cycle = ( 4289 self._parse_recursive_with_search() 4290 if search 4291 and search.args.get("kind") != "CYCLE" 4292 and self._match_text_seq("CYCLE", advance=False) 4293 else None 4294 ) 4295 4296 return self.expression( 4297 exp.With( 4298 expressions=expressions, 4299 recursive=recursive or None, 4300 search=search, 4301 cycle=cycle, 4302 udfs=udfs or None, 4303 ), 4304 comments=comments, 4305 ) 4306 4307 def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None: 4308 index = self._index 4309 4310 alias = self._parse_table_alias(self.ID_VAR_TOKENS) 4311 if not alias or not alias.this: 4312 self.raise_error("Expected CTE to have alias") 4313 4314 key_expressions = ( 4315 self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None 4316 ) 4317 4318 if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE: 4319 self._retreat(index) 4320 return None 4321 4322 comments = self._prev_comments 4323 4324 if self._match_text_seq("NOT", "MATERIALIZED"): 4325 materialized = False 4326 elif self._match_text_seq("MATERIALIZED"): 4327 materialized = True 4328 else: 4329 materialized = None 4330 4331 cte = self.expression( 4332 exp.CTE( 4333 this=self._parse_wrapped(self._parse_statement), 4334 alias=alias, 4335 materialized=materialized, 4336 key_expressions=key_expressions, 4337 ), 4338 comments=comments, 4339 ) 4340 4341 values = cte.this 4342 if isinstance(values, exp.Values): 4343 cte.set("this", self._values_to_select(values)) 4344 4345 return cte 4346 4347 def _values_to_select(self, values: exp.Values) -> exp.Select: 4348 if values.alias: 4349 return exp.select("*").from_(values) 4350 return exp.select("*").from_(exp.alias_(values, "_values", table=True)) 4351 4352 def _parse_table_alias( 4353 self, alias_tokens: t.Collection[TokenType] | None = None 4354 ) -> exp.TableAlias | None: 4355 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 4356 # so this section tries to parse the clause version and if it fails, it treats the token 4357 # as an identifier (alias) 4358 if self._can_parse_limit_or_offset(): 4359 return None 4360 4361 # START is never treated as an implicit alias when followed by WITH, since that 4362 # would swallow the beginning of a START WITH ... CONNECT BY clause 4363 if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH": 4364 return None 4365 4366 any_token = self._match(TokenType.ALIAS) 4367 alias = ( 4368 self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 4369 or self._parse_string_as_identifier() 4370 ) 4371 4372 index = self._index 4373 if self._match(TokenType.L_PAREN): 4374 columns = self._parse_csv(self._parse_function_parameter) 4375 self._match_r_paren() if columns else self._retreat(index) 4376 else: 4377 columns = None 4378 4379 if not alias and not columns: 4380 return None 4381 4382 table_alias = self.expression(exp.TableAlias(this=alias, columns=columns)) 4383 4384 # We bubble up comments from the Identifier to the TableAlias 4385 if isinstance(alias, exp.Identifier): 4386 table_alias.add_comments(alias.pop_comments()) 4387 4388 return table_alias 4389 4390 def _parse_subquery( 4391 self, this: exp.Expr | None, parse_alias: bool = True 4392 ) -> exp.Subquery | None: 4393 if not this: 4394 return None 4395 4396 return self.expression( 4397 exp.Subquery( 4398 this=this, 4399 pivots=self._parse_pivots(), 4400 alias=self._parse_table_alias() if parse_alias else None, 4401 sample=self._parse_table_sample(), 4402 ) 4403 ) 4404 4405 def _implicit_unnests_to_explicit(self, this: E) -> E: 4406 from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm 4407 4408 refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name} 4409 for i, join in enumerate(this.args.get("joins") or []): 4410 table = join.this 4411 normalized_table = table.copy() 4412 normalized_table.meta["maybe_column"] = True 4413 normalized_table = _norm(normalized_table, dialect=self.dialect) 4414 4415 if isinstance(table, exp.Table) and not join.args.get("on"): 4416 if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs: 4417 table_as_column = table.to_column() 4418 unnest = exp.Unnest(expressions=[table_as_column]) 4419 4420 # Table.to_column creates a parent Alias node that we want to convert to 4421 # a TableAlias and attach to the Unnest, so it matches the parser's output 4422 if isinstance(table.args.get("alias"), exp.TableAlias): 4423 table_as_column.replace(table_as_column.this) 4424 exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False) 4425 4426 table.replace(unnest) 4427 4428 refs.add(normalized_table.alias_or_name) 4429 4430 return this 4431 4432 @t.overload 4433 def _parse_query_modifiers(self, this: E) -> E: ... 4434 4435 @t.overload 4436 def _parse_query_modifiers(self, this: None) -> None: ... 4437 4438 def _parse_query_modifiers(self, this): 4439 if isinstance(this, self.MODIFIABLES): 4440 for join in self._parse_joins(): 4441 this.append("joins", join) 4442 for lateral in iter(self._parse_lateral, None): 4443 this.append("laterals", lateral) 4444 4445 while True: 4446 if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False): 4447 modifier_token = self._curr 4448 4449 # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result 4450 # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union 4451 if ( 4452 modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH) 4453 and (limit := this.args.get("limit")) 4454 and limit.meta.get("top") 4455 ): 4456 break 4457 4458 parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type] 4459 key, expression = parser(self) 4460 4461 if expression: 4462 if this.args.get(key): 4463 self.raise_error( 4464 f"Found multiple '{modifier_token.text.upper()}' clauses", 4465 token=modifier_token, 4466 ) 4467 4468 this.set(key, expression) 4469 if key == "limit": 4470 offset = expression.args.get("offset") 4471 expression.set("offset", None) 4472 4473 if offset: 4474 if this.args.get("offset"): 4475 self.raise_error( 4476 "Found multiple 'OFFSET' clauses", token=modifier_token 4477 ) 4478 4479 offset = exp.Offset(expression=offset) 4480 this.set("offset", offset) 4481 4482 limit_by_expressions = expression.expressions 4483 expression.set("expressions", None) 4484 offset.set("expressions", limit_by_expressions) 4485 continue 4486 4487 if self._curr.text.upper() == "START": 4488 modifier_token = self._curr 4489 connect = self._parse_connect() 4490 if connect: 4491 if this.args.get("connect"): 4492 self.raise_error( 4493 "Found multiple 'START WITH' clauses", token=modifier_token 4494 ) 4495 4496 this.set("connect", connect) 4497 continue 4498 break 4499 4500 if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"): 4501 this = self._implicit_unnests_to_explicit(this) 4502 4503 return this 4504 4505 def _parse_hint_fallback_to_string(self) -> exp.Hint | None: 4506 start = self._curr 4507 while self._curr: 4508 self._advance() 4509 4510 end = self._tokens[self._index - 1] 4511 return exp.Hint(expressions=[self._find_sql(start, end)]) 4512 4513 def _parse_hint_function_call(self) -> exp.Expr | None: 4514 return self._parse_function_call() 4515 4516 def _parse_hint_body(self) -> exp.Hint | None: 4517 start_index = self._index 4518 should_fallback_to_string = False 4519 4520 hints = [] 4521 try: 4522 for hint in iter( 4523 lambda: self._parse_csv( 4524 lambda: self._parse_hint_function_call() or self._parse_var(upper=True), 4525 ), 4526 [], 4527 ): 4528 hints.extend(hint) 4529 except ParseError: 4530 should_fallback_to_string = True 4531 4532 if should_fallback_to_string or self._curr: 4533 self._retreat(start_index) 4534 return self._parse_hint_fallback_to_string() 4535 4536 return self.expression(exp.Hint(expressions=hints)) 4537 4538 def _parse_hint(self) -> exp.Hint | None: 4539 if self._match(TokenType.HINT) and self._prev_comments: 4540 return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect) 4541 4542 return None 4543 4544 def _parse_into(self) -> exp.Into | None: 4545 if not self._match(TokenType.INTO): 4546 return None 4547 4548 temp = self._match(TokenType.TEMPORARY) 4549 unlogged = self._match_text_seq("UNLOGGED") 4550 self._match(TokenType.TABLE) 4551 4552 return self.expression( 4553 exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged) 4554 ) 4555 4556 def _parse_from( 4557 self, 4558 joins: bool = False, 4559 skip_from_token: bool = False, 4560 consume_pipe: bool = False, 4561 ) -> exp.From | None: 4562 if not skip_from_token and not self._match(TokenType.FROM): 4563 return None 4564 4565 comments = self._prev_comments 4566 return self.expression( 4567 exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)), 4568 comments=comments, 4569 ) 4570 4571 def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure: 4572 return self.expression( 4573 exp.MatchRecognizeMeasure( 4574 window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(), 4575 this=self._parse_expression(), 4576 ) 4577 ) 4578 4579 def _parse_match_recognize(self) -> exp.MatchRecognize | None: 4580 if not self._match(TokenType.MATCH_RECOGNIZE): 4581 return None 4582 4583 self._match_l_paren() 4584 4585 partition = self._parse_partition_by() 4586 order = self._parse_order() 4587 4588 measures = ( 4589 self._parse_csv(self._parse_match_recognize_measure) 4590 if self._match_text_seq("MEASURES") 4591 else None 4592 ) 4593 4594 if self._match_text_seq("ONE", "ROW", "PER", "MATCH"): 4595 rows = exp.var("ONE ROW PER MATCH") 4596 elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"): 4597 text = "ALL ROWS PER MATCH" 4598 if self._match_text_seq("SHOW", "EMPTY", "MATCHES"): 4599 text += " SHOW EMPTY MATCHES" 4600 elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"): 4601 text += " OMIT EMPTY MATCHES" 4602 elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"): 4603 text += " WITH UNMATCHED ROWS" 4604 rows = exp.var(text) 4605 else: 4606 rows = None 4607 4608 if self._match_text_seq("AFTER", "MATCH", "SKIP"): 4609 text = "AFTER MATCH SKIP" 4610 if self._match_text_seq("PAST", "LAST", "ROW"): 4611 text += " PAST LAST ROW" 4612 elif self._match_text_seq("TO", "NEXT", "ROW"): 4613 text += " TO NEXT ROW" 4614 elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"): 4615 direction = self._prev.text.upper() 4616 pattern_var = self._advance_any() 4617 if not pattern_var: 4618 self.raise_error( 4619 f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}" 4620 ) 4621 text += f" TO {direction} {pattern_var.text if pattern_var else ''}" 4622 after = exp.var(text) 4623 else: 4624 after = None 4625 4626 if self._match_text_seq("PATTERN"): 4627 self._match_l_paren() 4628 4629 if not self._curr: 4630 self.raise_error("Expecting )", self._curr) 4631 4632 paren = 1 4633 start = self._curr 4634 4635 while self._curr and paren > 0: 4636 if self._curr.token_type == TokenType.L_PAREN: 4637 paren += 1 4638 if self._curr.token_type == TokenType.R_PAREN: 4639 paren -= 1 4640 4641 end = self._prev 4642 self._advance() 4643 4644 if paren > 0: 4645 self.raise_error("Expecting )", self._curr) 4646 4647 pattern = exp.var(self._find_sql(start, end)) 4648 else: 4649 pattern = None 4650 4651 define = ( 4652 self._parse_csv(self._parse_name_as_expression) 4653 if self._match_text_seq("DEFINE") 4654 else None 4655 ) 4656 4657 self._match_r_paren() 4658 4659 return self.expression( 4660 exp.MatchRecognize( 4661 partition_by=partition, 4662 order=order, 4663 measures=measures, 4664 rows=rows, 4665 after=after, 4666 pattern=pattern, 4667 define=define, 4668 alias=self._parse_table_alias(), 4669 ) 4670 ) 4671 4672 def _parse_lateral(self) -> exp.Lateral | None: 4673 cross_apply: bool | None = None 4674 if self._match_pair(TokenType.CROSS, TokenType.APPLY): 4675 cross_apply = True 4676 elif self._match_pair(TokenType.OUTER, TokenType.APPLY): 4677 cross_apply = False 4678 4679 if cross_apply is not None: 4680 this = self._parse_select(table=True) 4681 view = None 4682 outer = None 4683 elif self._match(TokenType.LATERAL): 4684 this = self._parse_select(table=True) 4685 view = self._match(TokenType.VIEW) 4686 outer = self._match(TokenType.OUTER) 4687 else: 4688 return None 4689 4690 if not this: 4691 this = ( 4692 self._parse_unnest() 4693 or self._parse_function() 4694 or self._parse_id_var(any_token=False) 4695 ) 4696 4697 while self._match(TokenType.DOT): 4698 this = exp.Dot( 4699 this=this, 4700 expression=self._parse_function() or self._parse_id_var(any_token=False), 4701 ) 4702 4703 ordinality: bool | None = None 4704 4705 if view: 4706 table = self._parse_id_var(any_token=False) 4707 columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else [] 4708 table_alias: exp.TableAlias | None = self.expression( 4709 exp.TableAlias(this=table, columns=columns) 4710 ) 4711 elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias: 4712 # We move the alias from the lateral's child node to the lateral itself 4713 table_alias = this.args["alias"].pop() 4714 else: 4715 ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 4716 table_alias = self._parse_table_alias() 4717 4718 return self.expression( 4719 exp.Lateral( 4720 this=this, 4721 view=view, 4722 outer=outer, 4723 alias=table_alias, 4724 cross_apply=cross_apply, 4725 ordinality=ordinality, 4726 ) 4727 ) 4728 4729 def _parse_stream(self) -> exp.Stream | None: 4730 index = self._index 4731 if self._match(TokenType.STREAM): 4732 if this := self._try_parse(self._parse_table): 4733 return self.expression(exp.Stream(this=this)) 4734 self._retreat(index) 4735 return None 4736 4737 def _parse_join_parts( 4738 self, 4739 ) -> tuple[Token | None, Token | None, Token | None]: 4740 return ( 4741 self._prev if self._match_set(self.JOIN_METHODS) else None, 4742 self._prev if self._match_set(self.JOIN_SIDES) else None, 4743 self._prev if self._match_set(self.JOIN_KINDS) else None, 4744 ) 4745 4746 def _parse_using_identifiers(self) -> list[exp.Expr]: 4747 def _parse_column_as_identifier() -> exp.Expr | None: 4748 this = self._parse_column() 4749 if isinstance(this, exp.Column): 4750 return this.this 4751 return this 4752 4753 return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True) 4754 4755 def _parse_join( 4756 self, 4757 skip_join_token: bool = False, 4758 parse_bracket: bool = False, 4759 alias_tokens: t.Collection[TokenType] | None = None, 4760 ) -> exp.Join | None: 4761 if self._match(TokenType.COMMA): 4762 table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens)) 4763 cross_join = self.expression(exp.Join(this=table)) if table else None 4764 4765 if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE: 4766 cross_join.set("kind", "CROSS") 4767 4768 return cross_join 4769 4770 index = self._index 4771 method, side, kind = self._parse_join_parts() 4772 directed = self._match_text_seq("DIRECTED") 4773 hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None 4774 join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN) 4775 join_comments = self._prev_comments 4776 4777 if not skip_join_token and not join: 4778 self._retreat(index) 4779 kind = None 4780 method = None 4781 side = None 4782 4783 outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False) 4784 cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False) 4785 4786 if not skip_join_token and not join and not outer_apply and not cross_apply: 4787 return None 4788 4789 kwargs: dict[str, t.Any] = { 4790 "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4791 } 4792 if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA): 4793 kwargs["expressions"] = self._parse_csv( 4794 lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens) 4795 ) 4796 4797 if method: 4798 kwargs["method"] = method.text.upper() 4799 if side: 4800 kwargs["side"] = side.text.upper() 4801 if kind: 4802 kwargs["kind"] = kind.text.upper() 4803 if hint: 4804 kwargs["hint"] = hint 4805 4806 if self._match(TokenType.MATCH_CONDITION): 4807 kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison) 4808 4809 if self._match(TokenType.ON): 4810 kwargs["on"] = self._parse_disjunction() 4811 elif self._match(TokenType.USING): 4812 kwargs["using"] = self._parse_using_identifiers() 4813 elif ( 4814 not method 4815 and not (outer_apply or cross_apply) 4816 and not isinstance(kwargs["this"], exp.Unnest) 4817 and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY)) 4818 ): 4819 index = self._index 4820 joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens)) 4821 4822 if joins and self._match(TokenType.ON): 4823 kwargs["on"] = self._parse_disjunction() 4824 elif joins and self._match(TokenType.USING): 4825 kwargs["using"] = self._parse_using_identifiers() 4826 else: 4827 joins = None 4828 self._retreat(index) 4829 4830 kwargs["this"].set("joins", joins if joins else None) 4831 4832 kwargs["pivots"] = self._parse_pivots() 4833 4834 comments = [c for token in (method, side, kind) if token for c in token.comments] 4835 comments = (join_comments or []) + comments 4836 4837 if ( 4838 self.ADD_JOIN_ON_TRUE 4839 and not kwargs.get("on") 4840 and not kwargs.get("using") 4841 and not kwargs.get("method") 4842 and kwargs.get("kind") in (None, "INNER", "OUTER") 4843 ): 4844 kwargs["on"] = exp.true() 4845 4846 if directed: 4847 kwargs["directed"] = directed 4848 4849 return self.expression(exp.Join(**kwargs), comments=comments) 4850 4851 def _parse_opclass(self) -> exp.Expr | None: 4852 this = self._parse_disjunction() 4853 4854 if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False): 4855 return this 4856 4857 if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False): 4858 return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts())) 4859 4860 return this 4861 4862 def _parse_index_params(self) -> exp.IndexParameters: 4863 using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None 4864 4865 if self._match(TokenType.L_PAREN, advance=False): 4866 columns = self._parse_wrapped_csv(self._parse_with_operator) 4867 else: 4868 columns = None 4869 4870 include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None 4871 partition_by = self._parse_partition_by() 4872 with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties() 4873 tablespace = ( 4874 self._parse_var(any_token=True) 4875 if self._match_text_seq("USING", "INDEX", "TABLESPACE") 4876 else None 4877 ) 4878 where = self._parse_where() 4879 4880 on = self._parse_field() if self._match(TokenType.ON) else None 4881 4882 return self.expression( 4883 exp.IndexParameters( 4884 using=using, 4885 columns=columns, 4886 include=include, 4887 partition_by=partition_by, 4888 where=where, 4889 with_storage=with_storage, 4890 tablespace=tablespace, 4891 on=on, 4892 ) 4893 ) 4894 4895 def _parse_index( 4896 self, index: exp.Expr | None = None, anonymous: bool = False 4897 ) -> exp.Index | None: 4898 if index or anonymous: 4899 unique = None 4900 primary = None 4901 amp = None 4902 4903 self._match(TokenType.ON) 4904 self._match(TokenType.TABLE) # hive 4905 table = self._parse_table_parts(schema=True) 4906 else: 4907 unique = self._match(TokenType.UNIQUE) 4908 primary = self._match_text_seq("PRIMARY") 4909 amp = self._match_text_seq("AMP") 4910 4911 if not self._match(TokenType.INDEX): 4912 return None 4913 4914 index = self._parse_id_var() 4915 table = None 4916 4917 params = self._parse_index_params() 4918 4919 return self.expression( 4920 exp.Index( 4921 this=index, table=table, unique=unique, primary=primary, amp=amp, params=params 4922 ) 4923 ) 4924 4925 def _parse_table_hints(self) -> list[exp.Expr] | None: 4926 hints: list[exp.Expr] = [] 4927 if self._match_pair(TokenType.WITH, TokenType.L_PAREN): 4928 # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16 4929 hints.append( 4930 self.expression( 4931 exp.WithTableHint( 4932 expressions=self._parse_csv( 4933 lambda: self._parse_function() or self._parse_var(any_token=True) 4934 ) 4935 ) 4936 ) 4937 ) 4938 self._match_r_paren() 4939 else: 4940 # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html 4941 while self._match_set(self.TABLE_INDEX_HINT_TOKENS): 4942 hint = exp.IndexTableHint(this=self._prev.text.upper()) 4943 4944 self._match_set((TokenType.INDEX, TokenType.KEY)) 4945 if self._match(TokenType.FOR): 4946 hint.set("target", self._advance_any() and self._prev.text.upper()) 4947 4948 hint.set("expressions", self._parse_wrapped_id_vars()) 4949 hints.append(hint) 4950 4951 return hints or None 4952 4953 def _parse_table_part(self, schema: bool = False) -> exp.Expr | None: 4954 return ( 4955 (not schema and self._parse_function(optional_parens=False)) 4956 or self._parse_id_var(any_token=False) 4957 or self._parse_string_as_identifier() 4958 or self._parse_placeholder() 4959 ) 4960 4961 def _parse_table_parts_fast(self) -> exp.Table | None: 4962 index = self._index 4963 parts: list[exp.Identifier] | None = None 4964 all_comments: list[str] | None = None 4965 4966 while self._match_set(self.IDENTIFIER_TOKENS): 4967 token = self._prev 4968 comments = self._prev_comments 4969 4970 has_dot = self._match(TokenType.DOT) 4971 curr_tt = self._curr.token_type 4972 4973 if not has_dot: 4974 if curr_tt in self.TABLE_POSTFIX_TOKENS: 4975 self._retreat(index) 4976 return None 4977 elif curr_tt not in self.IDENTIFIER_TOKENS: 4978 self._retreat(index) 4979 return None 4980 4981 if parts is None: 4982 parts = [] 4983 4984 if comments: 4985 if all_comments is None: 4986 all_comments = [] 4987 all_comments.extend(comments) 4988 self._prev_comments = [] 4989 4990 parts.append( 4991 self.expression( 4992 exp.Identifier( 4993 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 4994 ), 4995 token, 4996 ) 4997 ) 4998 4999 if not has_dot: 5000 break 5001 5002 if parts is None: 5003 return None 5004 5005 n = len(parts) 5006 5007 if n == 1: 5008 table: exp.Table = exp.Table(this=parts[0]) 5009 elif n == 2: 5010 table = exp.Table(this=parts[1], db=parts[0]) 5011 elif n >= 3: 5012 this: exp.Identifier | exp.Dot = parts[2] 5013 for i in range(3, n): 5014 this = exp.Dot(this=this, expression=parts[i]) 5015 5016 table = exp.Table(this=this, db=parts[1], catalog=parts[0]) 5017 5018 if table is None: 5019 self._retreat(index) 5020 elif all_comments: 5021 table.add_comments(all_comments) 5022 return table 5023 5024 def _parse_table_parts( 5025 self, 5026 schema: bool = False, 5027 is_db_reference: bool = False, 5028 wildcard: bool = False, 5029 fast: bool = False, 5030 ) -> exp.Table | exp.Dot | None: 5031 if fast: 5032 return self._parse_table_parts_fast() 5033 5034 catalog: exp.Expr | str | None = None 5035 db: exp.Expr | str | None = None 5036 table: exp.Expr | str | None = self._parse_table_part(schema=schema) 5037 5038 while self._match(TokenType.DOT): 5039 if catalog: 5040 # This allows nesting the table in arbitrarily many dot expressions if needed 5041 table = self.expression( 5042 exp.Dot(this=table, expression=self._parse_table_part(schema=schema)) 5043 ) 5044 else: 5045 catalog = db 5046 db = table 5047 # "" used for tsql FROM a..b case 5048 table = self._parse_table_part(schema=schema) or "" 5049 5050 if ( 5051 wildcard 5052 and self._is_connected() 5053 and (isinstance(table, exp.Identifier) or not table) 5054 and self._match(TokenType.STAR) 5055 ): 5056 if isinstance(table, exp.Identifier): 5057 table.args["this"] += "*" 5058 else: 5059 table = exp.Identifier(this="*") 5060 5061 if is_db_reference: 5062 catalog = db 5063 db = table 5064 table = None 5065 5066 if not table and not is_db_reference: 5067 self.raise_error(f"Expected table name but got {self._curr}") 5068 if not db and is_db_reference: 5069 self.raise_error(f"Expected database name but got {self._curr}") 5070 5071 table = self.expression(exp.Table(this=table, db=db, catalog=catalog)) 5072 5073 # Bubble up comments from identifier parts to the Table 5074 comments = [] 5075 for part in table.parts: 5076 if part_comments := part.pop_comments(): 5077 comments.extend(part_comments) 5078 if comments: 5079 table.add_comments(comments) 5080 5081 changes = self._parse_changes() 5082 if changes: 5083 table.set("changes", changes) 5084 5085 at_before = self._parse_historical_data() 5086 if at_before: 5087 table.set("when", at_before) 5088 5089 pivots = self._parse_pivots() 5090 if pivots: 5091 table.set("pivots", pivots) 5092 5093 return table 5094 5095 def _parse_table( 5096 self, 5097 schema: bool = False, 5098 joins: bool = False, 5099 alias_tokens: t.Collection[TokenType] | None = None, 5100 parse_bracket: bool = False, 5101 is_db_reference: bool = False, 5102 parse_partition: bool = False, 5103 consume_pipe: bool = False, 5104 ) -> exp.Expr | None: 5105 if not schema and not is_db_reference and not consume_pipe and not joins: 5106 index = self._index 5107 table = self._parse_table_parts(fast=True) 5108 5109 if table is not None: 5110 curr_tt = self._curr.token_type 5111 next_tt = self._next.token_type 5112 5113 fast_terminators = self.TABLE_TERMINATORS 5114 5115 # only return the table if we're sure there are no other operators 5116 # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT 5117 if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION: 5118 return table 5119 5120 postfix_tokens = self.TABLE_POSTFIX_TOKENS 5121 5122 if curr_tt not in postfix_tokens and next_tt not in postfix_tokens: 5123 if alias := self._parse_table_alias( 5124 alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS 5125 ): 5126 table.set("alias", alias) 5127 5128 if self._curr.token_type in fast_terminators: 5129 return table 5130 5131 self._retreat(index) 5132 5133 if stream := self._parse_stream(): 5134 return stream 5135 5136 if lateral := self._parse_lateral(): 5137 return lateral 5138 5139 if unnest := self._parse_unnest(): 5140 return unnest 5141 5142 if values := self._parse_derived_table_values(): 5143 return values 5144 5145 if subquery := self._parse_select(table=True, consume_pipe=consume_pipe): 5146 if not subquery.args.get("pivots"): 5147 subquery.set("pivots", self._parse_pivots()) 5148 if joins: 5149 for join in self._parse_joins(): 5150 subquery.append("joins", join) 5151 return subquery 5152 5153 bracket = parse_bracket and self._parse_bracket(None) 5154 bracket = self.expression(exp.Table(this=bracket)) if bracket else None 5155 5156 rows_from_tables = ( 5157 self._parse_wrapped_csv(self._parse_table) 5158 if self._match_text_seq("ROWS", "FROM") 5159 else None 5160 ) 5161 rows_from = ( 5162 self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None 5163 ) 5164 5165 only = self._match(TokenType.ONLY) 5166 5167 this = t.cast( 5168 exp.Expr, 5169 bracket 5170 or rows_from 5171 or self._parse_bracket( 5172 self._parse_table_parts(schema=schema, is_db_reference=is_db_reference) 5173 ), 5174 ) 5175 5176 if only: 5177 this.set("only", only) 5178 5179 # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context 5180 self._match(TokenType.STAR) 5181 5182 parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION 5183 if parse_partition and self._match(TokenType.PARTITION, advance=False): 5184 this.set("partition", self._parse_partition()) 5185 5186 if schema: 5187 return self._parse_schema(this=this) 5188 5189 if self.dialect.ALIAS_POST_VERSION: 5190 this.set("version", self._parse_version()) 5191 5192 if self.dialect.ALIAS_POST_TABLESAMPLE: 5193 this.set("sample", self._parse_table_sample()) 5194 5195 alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS) 5196 if alias: 5197 this.set("alias", alias) 5198 5199 # DuckDB requires the time-travel clause to come after the alias, e.g. 5200 # SELECT * FROM t AS a AT (VERSION => 1) 5201 if isinstance(this, exp.Table) and not this.args.get("when"): 5202 this.set("when", self._parse_historical_data()) 5203 5204 if self._match(TokenType.INDEXED_BY): 5205 this.set("indexed", self._parse_table_parts()) 5206 elif self._match_text_seq("NOT", "INDEXED"): 5207 this.set("indexed", False) 5208 5209 if isinstance(this, exp.Table) and self._match_text_seq("AT"): 5210 return self.expression( 5211 exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var()) 5212 ) 5213 5214 this.set("hints", self._parse_table_hints()) 5215 5216 if not this.args.get("pivots"): 5217 this.set("pivots", self._parse_pivots()) 5218 5219 if not self.dialect.ALIAS_POST_TABLESAMPLE: 5220 this.set("sample", self._parse_table_sample()) 5221 5222 if not self.dialect.ALIAS_POST_VERSION: 5223 this.set("version", self._parse_version()) 5224 5225 if joins: 5226 for join in self._parse_joins(alias_tokens=alias_tokens): 5227 this.append("joins", join) 5228 5229 if self._match_pair(TokenType.WITH, TokenType.ORDINALITY): 5230 this.set("ordinality", True) 5231 this.set("alias", self._parse_table_alias()) 5232 5233 # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we 5234 # hoist the table args onto the latter and return it instead 5235 if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows): 5236 table_from_rows = this.this 5237 for arg in exp.TableFromRows.arg_types: 5238 if arg != "this": 5239 table_from_rows.set(arg, this.args.get(arg)) 5240 5241 this = table_from_rows 5242 5243 return this 5244 5245 def _parse_version(self) -> exp.Version | None: 5246 for phrase, this in self.VERSION_PHRASES.items(): 5247 if self._match_text_seq(*phrase): 5248 break 5249 else: 5250 return None 5251 5252 if self._match_set((TokenType.FROM, TokenType.BETWEEN)): 5253 kind = self._prev.text.upper() 5254 start = self._parse_bitwise() 5255 self._match_texts(("TO", "AND")) 5256 end = self._parse_bitwise() 5257 expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end])) 5258 elif self._match_text_seq("CONTAINED", "IN"): 5259 kind = "CONTAINED IN" 5260 expression = self.expression( 5261 exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise)) 5262 ) 5263 elif self._match(TokenType.ALL): 5264 kind = "ALL" 5265 expression = None 5266 else: 5267 self._match_text_seq("AS", "OF") 5268 kind = "AS OF" 5269 expression = self._parse_type() 5270 5271 return self.expression(exp.Version(this=this, expression=expression, kind=kind)) 5272 5273 def _parse_historical_data(self) -> exp.HistoricalData | None: 5274 # https://docs.snowflake.com/en/sql-reference/constructs/at-before 5275 index = self._index 5276 historical_data = None 5277 if self._match_texts(self.HISTORICAL_DATA_PREFIX): 5278 this = self._prev.text.upper() 5279 kind = ( 5280 self._match(TokenType.L_PAREN) 5281 and self._match_texts(self.HISTORICAL_DATA_KIND) 5282 and self._prev.text.upper() 5283 ) 5284 expression = self._match(TokenType.FARROW) and self._parse_bitwise() 5285 5286 if expression: 5287 self._match_r_paren() 5288 historical_data = self.expression( 5289 exp.HistoricalData(this=this, kind=kind, expression=expression) 5290 ) 5291 else: 5292 self._retreat(index) 5293 5294 return historical_data 5295 5296 def _parse_changes(self) -> exp.Changes | None: 5297 if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"): 5298 return None 5299 5300 information = self._parse_var(any_token=True) 5301 self._match_r_paren() 5302 5303 return self.expression( 5304 exp.Changes( 5305 information=information, 5306 at_before=self._parse_historical_data(), 5307 end=self._parse_historical_data(), 5308 ) 5309 ) 5310 5311 def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None: 5312 if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False): 5313 return None 5314 5315 self._advance() 5316 5317 expressions = self._parse_wrapped_csv(self._parse_equality) 5318 offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY) 5319 5320 alias = self._parse_table_alias() if with_alias else None 5321 5322 if alias: 5323 if self.dialect.UNNEST_COLUMN_ONLY: 5324 if alias.args.get("columns"): 5325 self.raise_error("Unexpected extra column alias in unnest.") 5326 5327 alias.set("columns", [alias.this]) 5328 alias.set("this", None) 5329 5330 columns = alias.args.get("columns") or [] 5331 if offset and len(expressions) < len(columns): 5332 offset = columns.pop() 5333 5334 if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET): 5335 self._match(TokenType.ALIAS) 5336 offset = self._parse_id_var( 5337 any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS 5338 ) or exp.to_identifier("offset") 5339 5340 return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset)) 5341 5342 def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None: 5343 is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES) 5344 if not is_derived and not ( 5345 # ClickHouse's `FORMAT Values` is equivalent to `VALUES` 5346 self._match_text_seq("VALUES") 5347 or self._match_text_seq("FORMAT", "VALUES") 5348 # MySQL accepts VALUE as a synonym for VALUES in INSERT statements. 5349 or (allow_value_synonym and self._match_text_seq("VALUE")) 5350 ): 5351 return None 5352 5353 expressions = self._parse_csv(self._parse_value) 5354 alias = self._parse_table_alias() 5355 5356 if is_derived: 5357 self._match_r_paren() 5358 5359 return self.expression( 5360 exp.Values(expressions=expressions, alias=alias or self._parse_table_alias()) 5361 ) 5362 5363 def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None: 5364 if not self._match(TokenType.TABLE_SAMPLE) and not ( 5365 as_modifier and self._match_text_seq("USING", "SAMPLE") 5366 ): 5367 return None 5368 5369 bucket_numerator = None 5370 bucket_denominator = None 5371 bucket_field = None 5372 percent = None 5373 size = None 5374 seed = None 5375 5376 method = self._parse_var(tokens=(TokenType.ROW,), upper=True) 5377 matched_l_paren = self._match(TokenType.L_PAREN) 5378 5379 if self.TABLESAMPLE_CSV: 5380 num = None 5381 expressions = self._parse_csv(self._parse_primary) 5382 else: 5383 expressions = None 5384 num = ( 5385 self._parse_factor(parse_mod=False) 5386 if self._match(TokenType.NUMBER, advance=False) 5387 else self._parse_primary() or self._parse_placeholder() 5388 ) 5389 5390 if self._match_text_seq("BUCKET"): 5391 bucket_numerator = self._parse_number() 5392 self._match_text_seq("OUT", "OF") 5393 bucket_denominator = bucket_denominator = self._parse_number() 5394 self._match(TokenType.ON) 5395 bucket_field = self._parse_field() 5396 elif self._match_set((TokenType.PERCENT, TokenType.MOD)): 5397 percent = num 5398 elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 5399 size = num 5400 else: 5401 percent = num 5402 5403 if matched_l_paren: 5404 self._match_r_paren() 5405 5406 if self._match(TokenType.L_PAREN): 5407 method = self._parse_var(upper=True) 5408 seed = self._match(TokenType.COMMA) and self._parse_number() 5409 self._match_r_paren() 5410 elif self._match_texts(("SEED", "REPEATABLE")): 5411 seed = self._parse_wrapped(self._parse_number) 5412 5413 if not method and self.DEFAULT_SAMPLING_METHOD: 5414 method = exp.var(self.DEFAULT_SAMPLING_METHOD) 5415 5416 return self.expression( 5417 exp.TableSample( 5418 expressions=expressions, 5419 method=method, 5420 bucket_numerator=bucket_numerator, 5421 bucket_denominator=bucket_denominator, 5422 bucket_field=bucket_field, 5423 percent=percent, 5424 size=size, 5425 seed=seed, 5426 ) 5427 ) 5428 5429 def _parse_pivots(self) -> list[exp.Pivot] | None: 5430 if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT): 5431 return None 5432 return list(iter(self._parse_pivot, None)) or None 5433 5434 def _parse_joins( 5435 self, alias_tokens: t.Collection[TokenType] | None = None 5436 ) -> t.Iterator[exp.Join]: 5437 return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None) 5438 5439 def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None: 5440 if not self._match(TokenType.INTO): 5441 return None 5442 5443 return self.expression( 5444 exp.UnpivotColumns( 5445 this=self._match_text_seq("NAME") and self._parse_column(), 5446 expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column), 5447 ) 5448 ) 5449 5450 # https://duckdb.org/docs/sql/statements/pivot 5451 def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot: 5452 def _parse_on() -> exp.Expr | None: 5453 this = self._parse_bitwise() 5454 5455 if self._match(TokenType.IN): 5456 # PIVOT ... ON col IN (row_val1, row_val2) 5457 return self._parse_in(this) 5458 if self._match(TokenType.ALIAS, advance=False): 5459 # UNPIVOT ... ON (col1, col2, col3) AS row_val 5460 return self._parse_alias(this) 5461 5462 return this 5463 5464 this = self._parse_table() 5465 expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on) 5466 into = self._parse_unpivot_columns() 5467 using = self._match(TokenType.USING) and self._parse_csv( 5468 lambda: self._parse_alias(self._parse_column()) 5469 ) 5470 group = self._parse_group() 5471 5472 return self.expression( 5473 exp.Pivot( 5474 this=this, 5475 expressions=expressions, 5476 using=using, 5477 group=group, 5478 unpivot=is_unpivot, 5479 into=into, 5480 ) 5481 ) 5482 5483 def _parse_pivot_in(self) -> exp.In: 5484 def _parse_aliased_expression() -> exp.Expr | None: 5485 this = self._parse_select_or_expression() 5486 5487 self._match(TokenType.ALIAS) 5488 alias = self._parse_bitwise() 5489 if alias: 5490 if isinstance(alias, exp.Column) and not alias.db: 5491 alias = alias.this 5492 return self.expression(exp.PivotAlias(this=this, alias=alias)) 5493 5494 return this 5495 5496 value = self._parse_column() 5497 5498 if not self._match(TokenType.IN): 5499 self.raise_error("Expecting IN") 5500 5501 if self._match(TokenType.L_PAREN): 5502 if self._match(TokenType.ANY): 5503 exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order())) 5504 else: 5505 exprs = self._parse_csv(_parse_aliased_expression) 5506 self._match_r_paren() 5507 return self.expression(exp.In(this=value, expressions=exprs)) 5508 5509 return self.expression(exp.In(this=value, field=self._parse_id_var())) 5510 5511 def _parse_pivot_aggregation(self) -> exp.Expr | None: 5512 func = self._parse_function() 5513 if not func: 5514 if self._prev.token_type == TokenType.COMMA: 5515 return None 5516 self.raise_error("Expecting an aggregation function in PIVOT") 5517 5518 return self._parse_alias(func) 5519 5520 def _parse_pivot(self) -> exp.Pivot | None: 5521 index = self._index 5522 include_nulls = None 5523 5524 if self._match(TokenType.PIVOT): 5525 unpivot = False 5526 elif self._match(TokenType.UNPIVOT): 5527 unpivot = True 5528 5529 # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax 5530 if self._match_text_seq("INCLUDE", "NULLS"): 5531 include_nulls = True 5532 elif self._match_text_seq("EXCLUDE", "NULLS"): 5533 include_nulls = False 5534 else: 5535 return None 5536 5537 expressions = [] 5538 5539 if not self._match(TokenType.L_PAREN): 5540 self._retreat(index) 5541 return None 5542 5543 if unpivot: 5544 expressions = self._parse_csv(self._parse_column) 5545 else: 5546 expressions = self._parse_csv(self._parse_pivot_aggregation) 5547 5548 if not expressions: 5549 self.raise_error("Failed to parse PIVOT's aggregation list") 5550 5551 if not self._match(TokenType.FOR): 5552 self.raise_error("Expecting FOR") 5553 5554 fields = [] 5555 while True: 5556 field = self._try_parse(self._parse_pivot_in) 5557 if not field: 5558 break 5559 fields.append(field) 5560 5561 default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped( 5562 self._parse_bitwise 5563 ) 5564 5565 group = self._parse_group() 5566 5567 self._match_r_paren() 5568 5569 pivot = self.expression( 5570 exp.Pivot( 5571 expressions=expressions, 5572 fields=fields, 5573 unpivot=unpivot, 5574 include_nulls=include_nulls, 5575 default_on_null=default_on_null, 5576 group=group, 5577 ) 5578 ) 5579 5580 if unpivot: 5581 pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions]) 5582 for pivot_field in pivot.fields: 5583 if isinstance(pivot_field, exp.In): 5584 pivot_field.set("this", _unpivot_target(pivot_field.this)) 5585 5586 pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST) 5587 5588 if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False): 5589 pivot.set("alias", self._parse_table_alias()) 5590 5591 if not unpivot: 5592 names = self._pivot_column_names(t.cast(list[exp.Expr], expressions)) 5593 5594 columns: list[exp.Expr] = [] 5595 all_fields = [] 5596 for pivot_field in pivot.fields: 5597 pivot_field_expressions = pivot_field.expressions 5598 5599 # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case. 5600 if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny): 5601 continue 5602 5603 all_fields.append( 5604 [ 5605 # An explicit `<field> AS <alias>` names the output column directly, 5606 # so it wins over the dialect's string-identifying convention 5607 fld.sql() 5608 if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias) 5609 else fld.alias_or_name 5610 for fld in pivot_field_expressions 5611 ] 5612 ) 5613 5614 if all_fields: 5615 if names: 5616 all_fields.append(names) 5617 5618 # Generate all possible combinations of the pivot columns 5619 # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US')) 5620 # generates the product between [[2000, 2010], ['NL', 'US'], ['total']] 5621 for fld_parts_tuple in itertools.product(*all_fields): 5622 fld_parts = list(fld_parts_tuple) 5623 5624 if names and self.PREFIXED_PIVOT_COLUMNS: 5625 # Move the "name" to the front of the list 5626 fld_parts.insert(0, fld_parts.pop(-1)) 5627 5628 columns.append(exp.to_identifier("_".join(fld_parts))) 5629 5630 pivot.set("columns", columns) 5631 pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS) 5632 pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS) 5633 pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING) 5634 5635 return pivot 5636 5637 def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]: 5638 return [agg.alias for agg in aggregations if agg.alias] 5639 5640 def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None: 5641 if not skip_where_token and not self._match(TokenType.PREWHERE): 5642 return None 5643 5644 comments = self._prev_comments 5645 return self.expression( 5646 exp.PreWhere(this=self._parse_disjunction()), 5647 comments=comments, 5648 ) 5649 5650 def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None: 5651 if not skip_where_token and not self._match(TokenType.WHERE): 5652 return None 5653 5654 comments = self._prev_comments 5655 return self.expression( 5656 exp.Where(this=self._parse_disjunction()), 5657 comments=comments, 5658 ) 5659 5660 def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None: 5661 if not skip_group_by_token and not self._match(TokenType.GROUP_BY): 5662 return None 5663 comments = self._prev_comments 5664 5665 elements: dict[str, t.Any] = defaultdict(list) 5666 5667 if self._match(TokenType.ALL): 5668 elements["all"] = True 5669 elif self._match(TokenType.DISTINCT): 5670 elements["all"] = False 5671 5672 while True: 5673 # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW, 5674 # which are also valid identifiers 5675 if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False): 5676 break 5677 5678 elements["expressions"].extend( 5679 self._parse_csv( 5680 lambda: ( 5681 self._parse_grouping_sets() 5682 or self._parse_cube_or_rollup() 5683 or self._parse_disjunction() 5684 ) 5685 ) 5686 ) 5687 5688 before_with_index = self._index 5689 5690 if self._match(TokenType.WITH) and ( 5691 cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True) 5692 ): 5693 key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube" 5694 elements[key].append(cube_or_rollup) 5695 elif grouping_sets := self._parse_grouping_sets(): 5696 # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...) 5697 elements["grouping_sets"].append(grouping_sets) 5698 break 5699 elif self._match_text_seq("TOTALS"): 5700 elements["totals"] = True # type: ignore 5701 5702 if before_with_index <= self._index <= before_with_index + 1: 5703 self._retreat(before_with_index) 5704 break 5705 5706 return self.expression(exp.Group(**elements), comments=comments) # type: ignore 5707 5708 def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None: 5709 if self._match(TokenType.CUBE): 5710 kind: type[exp.Cube | exp.Rollup] = exp.Cube 5711 elif self._match(TokenType.ROLLUP): 5712 kind = exp.Rollup 5713 else: 5714 return None 5715 5716 return self.expression( 5717 kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise)) 5718 ) 5719 5720 def _parse_grouping_sets(self) -> exp.GroupingSets | None: 5721 if self._match(TokenType.GROUPING_SETS): 5722 return self.expression( 5723 exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set)) 5724 ) 5725 return None 5726 5727 def _parse_grouping_set(self) -> exp.Expr | None: 5728 return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise() 5729 5730 def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None: 5731 if not skip_having_token and not self._match(TokenType.HAVING): 5732 return None 5733 comments = self._prev_comments 5734 return self.expression( 5735 exp.Having(this=self._parse_disjunction()), 5736 comments=comments, 5737 ) 5738 5739 def _parse_qualify(self) -> exp.Qualify | None: 5740 if not self._match(TokenType.QUALIFY): 5741 return None 5742 return self.expression(exp.Qualify(this=self._parse_disjunction())) 5743 5744 def _parse_connect_with_prior(self) -> exp.Expr | None: 5745 self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression( 5746 exp.Prior(this=self._parse_bitwise()) 5747 ) 5748 connect = self._parse_disjunction() 5749 self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR") 5750 return connect 5751 5752 def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None: 5753 if skip_start_token: 5754 start = None 5755 elif self._match_text_seq("START", "WITH"): 5756 start = self._parse_disjunction() 5757 else: 5758 return None 5759 5760 self._match(TokenType.CONNECT_BY) 5761 nocycle = self._match_text_seq("NOCYCLE") 5762 connect = self._parse_connect_with_prior() 5763 5764 if not start and self._match_text_seq("START", "WITH"): 5765 start = self._parse_disjunction() 5766 5767 return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle)) 5768 5769 def _parse_name_as_expression(self) -> exp.Expr | None: 5770 this = self._parse_id_var(any_token=True) 5771 if self._match(TokenType.ALIAS): 5772 this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction())) 5773 return this 5774 5775 def _parse_interpolate(self) -> list[exp.Expr] | None: 5776 if self._match_text_seq("INTERPOLATE"): 5777 return self._parse_wrapped_csv(self._parse_name_as_expression) 5778 return None 5779 5780 def _parse_order( 5781 self, this: exp.Expr | None = None, skip_order_token: bool = False 5782 ) -> exp.Expr | None: 5783 siblings = None 5784 if not skip_order_token and not self._match(TokenType.ORDER_BY): 5785 if not self._match(TokenType.ORDER_SIBLINGS_BY): 5786 return this 5787 5788 siblings = True 5789 5790 comments = self._prev_comments 5791 return self.expression( 5792 exp.Order( 5793 this=this, 5794 expressions=self._parse_csv(self._parse_ordered), 5795 siblings=siblings, 5796 ), 5797 comments=comments, 5798 ) 5799 5800 def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None: 5801 if not self._match(token): 5802 return None 5803 return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered))) 5804 5805 def _parse_ordered( 5806 self, parse_method: t.Callable[[], exp.Expr | None] | None = None 5807 ) -> exp.Ordered | None: 5808 this = parse_method() if parse_method else self._parse_disjunction() 5809 if not this: 5810 return None 5811 5812 if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL: 5813 this = exp.var("ALL") 5814 5815 asc = self._match(TokenType.ASC) 5816 desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None) 5817 5818 is_nulls_first = self._match_text_seq("NULLS", "FIRST") 5819 is_nulls_last = self._match_text_seq("NULLS", "LAST") 5820 5821 nulls_first = is_nulls_first or False 5822 explicitly_null_ordered = is_nulls_first or is_nulls_last 5823 5824 if ( 5825 not explicitly_null_ordered 5826 and ( 5827 (not desc and self.dialect.NULL_ORDERING == "nulls_are_small") 5828 or (desc and self.dialect.NULL_ORDERING != "nulls_are_small") 5829 ) 5830 and self.dialect.NULL_ORDERING != "nulls_are_last" 5831 ): 5832 nulls_first = True 5833 5834 if self._match_text_seq("WITH", "FILL"): 5835 with_fill = self.expression( 5836 exp.WithFill( 5837 from_=self._match(TokenType.FROM) and self._parse_bitwise(), 5838 to=self._match_text_seq("TO") and self._parse_bitwise(), 5839 step=self._match_text_seq("STEP") and self._parse_bitwise(), 5840 interpolate=self._parse_interpolate(), 5841 ) 5842 ) 5843 else: 5844 with_fill = None 5845 5846 return self.expression( 5847 exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill) 5848 ) 5849 5850 def _parse_limit_options(self) -> exp.LimitOptions | None: 5851 percent = self._match_set((TokenType.PERCENT, TokenType.MOD)) 5852 rows = self._match_texts(("ROW", "ROWS")) 5853 self._match_text_seq("ONLY") 5854 with_ties = self._match_text_seq("WITH", "TIES") 5855 5856 if not (percent or rows or with_ties): 5857 return None 5858 5859 return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties)) 5860 5861 def _parse_limit( 5862 self, 5863 this: exp.Expr | None = None, 5864 top: bool = False, 5865 skip_limit_token: bool = False, 5866 ) -> exp.Expr | None: 5867 if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT): 5868 comments = self._prev_comments 5869 if top: 5870 limit_paren = self._match(TokenType.L_PAREN) 5871 expression = ( 5872 self._parse_term() or self._parse_select() 5873 if limit_paren 5874 else self._parse_number() 5875 ) 5876 5877 if limit_paren: 5878 self._match_r_paren() 5879 5880 else: 5881 if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): 5882 expression = exp.var("ALL") 5883 else: 5884 expression = self._parse_term(parse_mod=False) 5885 limit_options = self._parse_limit_options() 5886 5887 if self._match(TokenType.COMMA): 5888 offset = expression 5889 expression = self._parse_term() 5890 else: 5891 offset = None 5892 5893 limit_exp = self.expression( 5894 exp.Limit( 5895 this=this, 5896 expression=expression, 5897 offset=offset, 5898 limit_options=limit_options, 5899 expressions=self._parse_limit_by(), 5900 ), 5901 comments=comments, 5902 ) 5903 5904 if top: 5905 limit_exp.meta["top"] = True 5906 5907 return limit_exp 5908 5909 if self._match(TokenType.FETCH): 5910 direction = ( 5911 self._prev.text.upper() 5912 if self._match_set((TokenType.FIRST, TokenType.NEXT)) 5913 else "FIRST" 5914 ) 5915 5916 count = ( 5917 None 5918 if self._match_texts(("ROW", "ROWS"), advance=False) 5919 else self._parse_field(tokens=self.FETCH_TOKENS) 5920 ) 5921 5922 return self.expression( 5923 exp.Fetch( 5924 direction=direction, count=count, limit_options=self._parse_limit_options() 5925 ) 5926 ) 5927 5928 return this 5929 5930 def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None: 5931 if not self._match(TokenType.OFFSET): 5932 return this 5933 5934 count = self._parse_term() 5935 self._match_texts(("ROW", "ROWS")) 5936 5937 return self.expression( 5938 exp.Offset(this=this, expression=count, expressions=self._parse_limit_by()) 5939 ) 5940 5941 def _can_parse_limit_or_offset(self) -> bool: 5942 if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False): 5943 return False 5944 5945 index = self._index 5946 result = bool( 5947 self._try_parse(self._parse_limit, retreat=True) 5948 or self._try_parse(self._parse_offset, retreat=True) 5949 ) 5950 self._retreat(index) 5951 5952 # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset 5953 if self._next.token_type == TokenType.MATCH_CONDITION: 5954 result = False 5955 5956 return result 5957 5958 def _can_parse_named_window(self) -> bool: 5959 # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias. 5960 # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`. 5961 if not self._match(TokenType.WINDOW, advance=False): 5962 return False 5963 5964 name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None 5965 if name is None or name.token_type not in self.ID_VAR_TOKENS: 5966 return False 5967 5968 alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None 5969 if alias_tok is None or alias_tok.token_type != TokenType.ALIAS: 5970 return False 5971 5972 body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None 5973 return body is not None and body.token_type == TokenType.L_PAREN 5974 5975 def _parse_limit_by(self) -> list[exp.Expr] | None: 5976 return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None 5977 5978 def _parse_locks(self) -> list[exp.Lock]: 5979 locks = [] 5980 while True: 5981 update, key = None, None 5982 if self._match_text_seq("FOR", "UPDATE"): 5983 update = True 5984 elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq( 5985 "LOCK", "IN", "SHARE", "MODE" 5986 ): 5987 update = False 5988 elif self._match_text_seq("FOR", "KEY", "SHARE"): 5989 update, key = False, True 5990 elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"): 5991 update, key = True, True 5992 else: 5993 break 5994 5995 expressions = None 5996 if self._match_text_seq("OF"): 5997 expressions = self._parse_csv(lambda: self._parse_table(schema=True)) 5998 5999 wait: bool | exp.Expr | None = None 6000 if self._match_text_seq("NOWAIT"): 6001 wait = True 6002 elif self._match_text_seq("WAIT"): 6003 wait = self._parse_primary() 6004 elif self._match_text_seq("SKIP", "LOCKED"): 6005 wait = False 6006 6007 locks.append( 6008 self.expression( 6009 exp.Lock(update=update, expressions=expressions, wait=wait, key=key) 6010 ) 6011 ) 6012 6013 return locks 6014 6015 def parse_set_operation( 6016 self, this: exp.Expr | None, consume_pipe: bool = False 6017 ) -> exp.Expr | None: 6018 start = self._index 6019 _, side_token, kind_token = self._parse_join_parts() 6020 6021 side = side_token.text if side_token else None 6022 kind = kind_token.text if kind_token else None 6023 6024 if not self._match_set(self.SET_OPERATIONS): 6025 self._retreat(start) 6026 return None 6027 6028 token_type = self._prev.token_type 6029 6030 if token_type == TokenType.UNION: 6031 operation: type[exp.SetOperation] = exp.Union 6032 elif token_type == TokenType.EXCEPT: 6033 operation = exp.Except 6034 else: 6035 operation = exp.Intersect 6036 6037 comments = self._prev.comments 6038 6039 if self._match(TokenType.DISTINCT): 6040 distinct: bool | None = True 6041 elif self._match(TokenType.ALL): 6042 distinct = False 6043 else: 6044 distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation] 6045 if distinct is None: 6046 self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}") 6047 6048 by_name = ( 6049 self._match_text_seq("BY", "NAME") 6050 or self._match_text_seq("STRICT", "CORRESPONDING") 6051 or None 6052 ) 6053 if self._match_text_seq("CORRESPONDING"): 6054 by_name = True 6055 if not side and not kind: 6056 kind = "INNER" 6057 6058 on_column_list = None 6059 if by_name and self._match_texts(("ON", "BY")): 6060 on_column_list = self._parse_wrapped_csv(self._parse_column) 6061 6062 expression = self._parse_select( 6063 nested=True, parse_set_operation=False, consume_pipe=consume_pipe 6064 ) 6065 6066 # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization 6067 # in _parse_cte and so that alias pushdown can reach into set operation branches 6068 if isinstance(this, exp.Values): 6069 this = self._values_to_select(this) 6070 if isinstance(expression, exp.Values): 6071 expression = self._values_to_select(expression) 6072 6073 if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery): 6074 subquery = this.this 6075 subquery.set("alias", exp.TableAlias(this=this.args["alias"])) 6076 subquery.add_comments(this.pop_comments()) 6077 this = subquery 6078 6079 return self.expression( 6080 operation( 6081 this=this, 6082 distinct=distinct, 6083 by_name=by_name, 6084 expression=expression, 6085 side=side, 6086 kind=kind, 6087 on=on_column_list, 6088 ), 6089 comments=comments, 6090 ) 6091 6092 def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None: 6093 while this: 6094 setop = self.parse_set_operation(this) 6095 if not setop: 6096 break 6097 this = setop 6098 6099 if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP: 6100 expression = this.expression 6101 6102 if expression: 6103 for arg in self.SET_OP_MODIFIERS: 6104 expr = expression.args.get(arg) 6105 if expr and not (arg == "limit" and expr.meta.get("top")): 6106 expression.set(arg, None) 6107 this.set(arg, expr) 6108 6109 # A trailing LIMIT/FETCH can coexist with TOP on the final operand. 6110 if self._curr.token_type in (TokenType.LIMIT, TokenType.FETCH): 6111 this = self._parse_query_modifiers(this) 6112 6113 return this 6114 6115 def _parse_expression(self) -> exp.Expr | None: 6116 return self._parse_alias(self._parse_assignment()) 6117 6118 def _parse_assignment(self) -> exp.Expr | None: 6119 this = self._parse_disjunction() 6120 if not this and self._next.token_type in self.ASSIGNMENT: 6121 # This allows us to parse <non-identifier token> := <expr> 6122 this = exp.column( 6123 t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text) 6124 ) 6125 6126 while self._match_set(self.ASSIGNMENT): 6127 if isinstance(this, exp.Column) and len(this.parts) == 1: 6128 this = this.this 6129 6130 comments = self._prev_comments 6131 this = self.expression( 6132 self.ASSIGNMENT[self._prev.token_type]( 6133 this=this, expression=self._parse_assignment() 6134 ), 6135 comments=comments, 6136 ) 6137 6138 return this 6139 6140 def _parse_disjunction(self) -> exp.Expr | None: 6141 this = self._parse_conjunction() 6142 while self._match_set(self.DISJUNCTION): 6143 comments = self._prev_comments 6144 this = self.expression( 6145 self.DISJUNCTION[self._prev.token_type]( 6146 this=this, expression=self._parse_conjunction() 6147 ), 6148 comments=comments, 6149 ) 6150 return this 6151 6152 def _parse_conjunction(self) -> exp.Expr | None: 6153 this = self._parse_equality() 6154 while self._match_set(self.CONJUNCTION): 6155 comments = self._prev_comments 6156 this = self.expression( 6157 self.CONJUNCTION[self._prev.token_type]( 6158 this=this, expression=self._parse_equality() 6159 ), 6160 comments=comments, 6161 ) 6162 return this 6163 6164 def _parse_equality(self) -> exp.Expr | None: 6165 this = self._parse_comparison() 6166 while self._match_set(self.EQUALITY): 6167 comments = self._prev_comments 6168 this = self.expression( 6169 self.EQUALITY[self._prev.token_type]( 6170 this=this, expression=self._parse_comparison() 6171 ), 6172 comments=comments, 6173 ) 6174 return this 6175 6176 def _parse_comparison(self) -> exp.Expr | None: 6177 this = self._parse_range() 6178 while self._match_set(self.COMPARISON): 6179 comments = self._prev_comments 6180 this = self.expression( 6181 self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()), 6182 comments=comments, 6183 ) 6184 return this 6185 6186 def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6187 this = this or self._parse_bitwise() 6188 6189 while True: 6190 negate = self._match(TokenType.NOT) 6191 if self._match_set(self.RANGE_PARSERS): 6192 expression = self.RANGE_PARSERS[self._prev.token_type](self, this) 6193 if not expression: 6194 return this 6195 6196 this = expression 6197 elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)): 6198 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6199 elif self._match(TokenType.NOTNULL): 6200 # Postgres supports ISNULL and NOTNULL for conditions. 6201 # https://blog.andreiavram.ro/postgresql-null-composite-type/ 6202 if self.dialect.NORMALIZE_NOT_NULL: 6203 this = self.expression(exp.Is(this=this, expression=exp.Null())) 6204 this = self.expression(exp.Not(this=this)) 6205 else: 6206 this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True)) 6207 else: 6208 if negate: 6209 self._retreat(self._index - 1) 6210 break 6211 6212 if negate: 6213 this = self._negate_range(this) 6214 if self._curr and ( 6215 self._curr.token_type == TokenType.NOT 6216 or self._curr.token_type in self.RANGE_PARSERS 6217 ): 6218 this = self.expression(exp.Paren(this=this)) 6219 6220 return this 6221 6222 def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None: 6223 if not this: 6224 return this 6225 6226 expression = this.this if isinstance(this, exp.Escape) else this 6227 if isinstance(expression, (exp.Like, exp.ILike)): 6228 expression.set("negate", True) 6229 return this 6230 6231 return self.expression(exp.Not(this=this)) 6232 6233 def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None: 6234 index = self._index - 1 6235 negate = self._match(TokenType.NOT) 6236 6237 if self._match_text_seq("DISTINCT", "FROM"): 6238 klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ 6239 return self.expression(klass(this=this, expression=self._parse_bitwise())) 6240 6241 if self._match(TokenType.JSON): 6242 kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper() 6243 6244 if self._match_text_seq("WITH"): 6245 _with = True 6246 elif self._match_text_seq("WITHOUT"): 6247 _with = False 6248 else: 6249 _with = None 6250 6251 unique = self._match(TokenType.UNIQUE) 6252 self._match_text_seq("KEYS") 6253 expression: exp.Expr | None = self.expression( 6254 exp.JSON(this=kind, with_=_with, unique=unique) 6255 ) 6256 else: 6257 expression = self._parse_null() or self._parse_bitwise() 6258 if not expression: 6259 self._retreat(index) 6260 return None 6261 6262 if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL: 6263 this = self.expression(exp.Is(this=this, expression=expression, negate=True)) 6264 else: 6265 this = self.expression(exp.Is(this=this, expression=expression)) 6266 this = self.expression(exp.Not(this=this)) if negate else this 6267 6268 return self._parse_column_ops(this) 6269 6270 def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In: 6271 unnest = self._parse_unnest(with_alias=False) 6272 if unnest: 6273 this = self.expression(exp.In(this=this, unnest=unnest)) 6274 elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)): 6275 matched_l_paren = self._prev.token_type == TokenType.L_PAREN 6276 expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias)) 6277 6278 if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query): 6279 this = self.expression( 6280 exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False)) 6281 ) 6282 else: 6283 this = self.expression(exp.In(this=this, expressions=expressions)) 6284 6285 if matched_l_paren: 6286 self._match_r_paren(this) 6287 elif not self._match(TokenType.R_BRACKET, expression=this): 6288 self.raise_error("Expecting ]") 6289 else: 6290 this = self.expression(exp.In(this=this, field=self._parse_column())) 6291 6292 return this 6293 6294 def _parse_between(self, this: exp.Expr | None) -> exp.Between: 6295 symmetric = None 6296 if self._match_text_seq("SYMMETRIC"): 6297 symmetric = True 6298 elif self._match_text_seq("ASYMMETRIC"): 6299 symmetric = False 6300 6301 low = self._parse_bitwise() 6302 self._match(TokenType.AND) 6303 high = self._parse_bitwise() 6304 6305 return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric)) 6306 6307 def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None: 6308 if not self._match(TokenType.ESCAPE): 6309 return this 6310 return self.expression( 6311 exp.Escape(this=this, expression=self._parse_string() or self._parse_null()) 6312 ) 6313 6314 def _parse_interval_span( 6315 self, this: exp.Expr, parse_function_unit: bool = True 6316 ) -> exp.Interval: 6317 # handle day-time format interval span with omitted units: 6318 # INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`> 6319 interval_span_units_omitted = None 6320 if ( 6321 this 6322 and this.is_string 6323 and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT 6324 and exp.INTERVAL_DAY_TIME_RE.match(this.name) 6325 ): 6326 index = self._index 6327 6328 # Var "TO" Var 6329 first_unit = self._parse_var(any_token=True, upper=True) 6330 second_unit = None 6331 if first_unit and self._match_text_seq("TO"): 6332 second_unit = self._parse_var(any_token=True, upper=True) 6333 6334 interval_span_units_omitted = not (first_unit and second_unit) 6335 6336 self._retreat(index) 6337 6338 unit_index = self._index 6339 if interval_span_units_omitted: 6340 unit = None 6341 else: 6342 # Only attempt to parse a unit if the current token can actually be one, so that a 6343 # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x) 6344 is_unit = self._curr is not None and ( 6345 self._curr.token_type == TokenType.VAR 6346 or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS 6347 ) 6348 unit = self._parse_function() if parse_function_unit and is_unit else None 6349 if not unit and is_unit: 6350 unit = self._parse_var(any_token=True, upper=True) 6351 6352 # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse 6353 # each INTERVAL expression into this canonical form so it's easy to transpile 6354 if this and this.is_number: 6355 try: 6356 this = exp.Literal.string(this.to_py()) 6357 except ValueError: 6358 self.raise_error(f"Invalid numeric interval literal: {this.name!r}") 6359 elif this and this.is_string: 6360 parts = exp.INTERVAL_STRING_RE.findall(this.name) 6361 if parts and unit: 6362 # Unconsume the eagerly-parsed unit, since the real unit was part of the string 6363 unit = None 6364 self._retreat(unit_index) 6365 6366 if len(parts) == 1: 6367 this = exp.Literal.string(parts[0][0]) 6368 unit = self.expression(exp.Var(this=parts[0][1].upper())) 6369 6370 if self.INTERVAL_SPANS and self._match_text_seq("TO"): 6371 unit = self.expression( 6372 exp.IntervalSpan( 6373 this=unit, 6374 expression=self._parse_function() 6375 or self._parse_var(any_token=True, upper=True), 6376 ) 6377 ) 6378 6379 return self.expression(exp.Interval(this=this, unit=unit)) 6380 6381 def _parse_interval( 6382 self, require_interval: bool = True, parse_function_unit: bool = True 6383 ) -> exp.Add | exp.Interval | None: 6384 index = self._index 6385 6386 if not self._match(TokenType.INTERVAL) and require_interval: 6387 return None 6388 6389 if self._match(TokenType.STRING, advance=False): 6390 this = self._parse_primary() 6391 else: 6392 this = self._parse_term() 6393 6394 if not this or ( 6395 isinstance(this, exp.Column) 6396 and not this.table 6397 and not this.this.quoted 6398 and self._curr 6399 and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS 6400 ): 6401 self._retreat(index) 6402 return None 6403 6404 interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit) 6405 6406 index = self._index 6407 self._match(TokenType.PLUS) 6408 6409 # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals 6410 if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False): 6411 return self.expression( 6412 exp.Add( 6413 this=interval, 6414 expression=self._parse_interval(False, parse_function_unit=parse_function_unit), 6415 ) 6416 ) 6417 6418 self._retreat(index) 6419 return interval 6420 6421 def _parse_bitwise(self) -> exp.Expr | None: 6422 this = self._parse_term() 6423 6424 while True: 6425 if self._match_set(self.BITWISE): 6426 this = self.expression( 6427 self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term()) 6428 ) 6429 elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE): 6430 this = self.expression( 6431 exp.DPipe( 6432 this=this, 6433 expression=self._parse_term(), 6434 safe=not self.dialect.STRICT_STRING_CONCAT, 6435 ) 6436 ) 6437 elif self._match(TokenType.DQMARK): 6438 this = self.expression( 6439 exp.Coalesce(this=this, expressions=ensure_list(self._parse_term())) 6440 ) 6441 elif self._match_pair(TokenType.LT, TokenType.LT): 6442 this = self.expression( 6443 exp.BitwiseLeftShift(this=this, expression=self._parse_term()) 6444 ) 6445 elif self._match_pair(TokenType.GT, TokenType.GT): 6446 this = self.expression( 6447 exp.BitwiseRightShift(this=this, expression=self._parse_term()) 6448 ) 6449 elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS): 6450 this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term()) 6451 else: 6452 break 6453 6454 return this 6455 6456 def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None: 6457 this = self._parse_factor(parse_mod=parse_mod) 6458 6459 while self._match_set(self.TERM): 6460 klass = self.TERM[self._prev.token_type] 6461 comments = self._prev_comments 6462 expression = self._parse_factor(parse_mod=parse_mod) 6463 6464 this = self.expression(klass(this=this, expression=expression), comments=comments) 6465 6466 if isinstance(this, exp.Collate): 6467 self._normalize_collate(this) 6468 6469 return this 6470 6471 def _normalize_collate(self, collate: exp.Collate) -> None: 6472 expr = collate.expression 6473 6474 # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise 6475 # fallback to Identifier / Var 6476 if isinstance(expr, exp.Column) and len(expr.parts) == 1: 6477 ident = expr.this 6478 if isinstance(ident, exp.Identifier): 6479 collate.set("expression", ident if ident.quoted else exp.var(ident.name)) 6480 6481 def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None: 6482 parse_method = self._parse_factor_operand 6483 this = self._parse_at_time_zone(parse_method()) 6484 6485 while self._match_set(self.FACTOR, advance=False): 6486 if not parse_mod and self._curr.token_type == TokenType.MOD: 6487 break 6488 6489 self._advance() 6490 klass = self.FACTOR[self._prev.token_type] 6491 comments = self._prev_comments 6492 expression = parse_method() 6493 6494 if not expression and klass is exp.IntDiv and self._prev.text.isalpha(): 6495 self._retreat(self._index - 1) 6496 return this 6497 6498 this = self.expression(klass(this=this, expression=expression), comments=comments) 6499 6500 if isinstance(this, exp.Div): 6501 this.set("typed", self.dialect.TYPED_DIVISION) 6502 this.set("safe", self.dialect.SAFE_DIVISION) 6503 6504 return this 6505 6506 def _parse_factor_operand(self) -> exp.Expr | None: 6507 return self._parse_exponent() if self.EXPONENT else self._parse_unary() 6508 6509 def _parse_exponent(self) -> exp.Expr | None: 6510 this = self._parse_unary() 6511 while self._match_set(self.EXPONENT): 6512 comments = self._prev_comments 6513 this = self.expression( 6514 self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()), 6515 comments=comments, 6516 ) 6517 return this 6518 6519 def _parse_unary(self) -> exp.Expr | None: 6520 if self._match_set(self.UNARY_PARSERS): 6521 return self.UNARY_PARSERS[self._prev.token_type](self) 6522 return self._parse_type() 6523 6524 def _parse_type( 6525 self, parse_interval: bool = True, fallback_to_identifier: bool = False 6526 ) -> exp.Expr | None: 6527 if not fallback_to_identifier and (atom := self._parse_atom()) is not None: 6528 return atom 6529 6530 if interval := parse_interval and self._parse_interval(): 6531 return self._parse_column_ops(interval) 6532 6533 index = self._index 6534 data_type = self._parse_types(check_func=True, allow_identifiers=False) 6535 6536 # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g. 6537 # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>) 6538 if isinstance(data_type, exp.Cast): 6539 # This constructor can contain ops directly after it, for instance struct unnesting: 6540 # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).* 6541 return self._parse_column_ops(data_type) 6542 6543 if data_type: 6544 index2 = self._index 6545 this = self._parse_primary() 6546 6547 if isinstance(this, exp.Literal): 6548 literal = this.name 6549 this = self._parse_column_ops(this) 6550 6551 if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal): 6552 if data_type.is_type(exp.DType.TIMESTAMP): 6553 data_type = exp.DType.TIMESTAMPTZ.into_expr() 6554 elif data_type.is_type(exp.DType.TIME): 6555 data_type = exp.DType.TIMETZ.into_expr() 6556 6557 parser = self.TYPE_LITERAL_PARSERS.get(data_type.this) 6558 if parser: 6559 return parser(self, this, data_type) 6560 6561 return self.expression(exp.Cast(this=this, to=data_type)) 6562 6563 # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0) 6564 # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 ) 6565 # 6566 # If the index difference here is greater than 1, that means the parser itself must have 6567 # consumed additional tokens such as the DECIMAL scale and precision in the above example. 6568 # 6569 # If it's not greater than 1, then it must be 1, because we've consumed at least the type 6570 # keyword, meaning that the expressions arg of the DataType must have gotten set by a 6571 # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to 6572 # DECIMAL(38, 0)) in order to facilitate the data type's transpilation. 6573 # 6574 # In these cases, we don't really want to return the converted type, but instead retreat 6575 # and try to parse a Column or Identifier in the section below. 6576 if data_type.expressions and index2 - index > 1: 6577 self._retreat(index2) 6578 return self._parse_column_ops(data_type) 6579 6580 self._retreat(index) 6581 6582 if fallback_to_identifier: 6583 return self._parse_id_var() 6584 6585 return self._parse_column() 6586 6587 def _parse_type_size(self) -> exp.DataTypeParam | None: 6588 this = self._parse_type() 6589 if not this: 6590 return None 6591 6592 if isinstance(this, exp.Column) and not this.table: 6593 this = exp.var(this.name.upper()) 6594 6595 return self.expression( 6596 exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True)) 6597 ) 6598 6599 def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None: 6600 type_name = identifier.name 6601 6602 while self._match(TokenType.DOT): 6603 type_name = f"{type_name}.{self._advance_any() and self._prev.text}" 6604 6605 return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True) 6606 6607 def _parse_types( 6608 self, 6609 check_func: bool = False, 6610 schema: bool = False, 6611 allow_identifiers: bool = True, 6612 with_collation: bool = False, 6613 ) -> exp.Expr | None: 6614 index = self._index 6615 this: exp.Expr | None = None 6616 6617 if self._match_set(self.TYPE_TOKENS): 6618 type_token = self._prev.token_type 6619 else: 6620 type_token = None 6621 identifier = allow_identifiers and self._parse_id_var( 6622 any_token=False, tokens=(TokenType.VAR,) 6623 ) 6624 if isinstance(identifier, exp.Identifier): 6625 if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE: 6626 this = exp.DataType.build(identifier, udt=True) 6627 else: 6628 try: 6629 tokens = self.dialect.tokenize(identifier.name) 6630 except TokenError: 6631 tokens = None 6632 6633 if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS: 6634 if len(tokens) > 1: 6635 return exp.DataType.from_str(identifier.name, dialect=self.dialect) 6636 elif self.dialect.SUPPORTS_USER_DEFINED_TYPES: 6637 this = self._parse_user_defined_type(identifier) 6638 else: 6639 self._retreat(self._index - 1) 6640 return None 6641 else: 6642 return None 6643 6644 if type_token == TokenType.PSEUDO_TYPE: 6645 return self.expression(exp.PseudoType(this=self._prev.text.upper())) 6646 6647 if type_token == TokenType.OBJECT_IDENTIFIER: 6648 return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper())) 6649 6650 # https://materialize.com/docs/sql/types/map/ 6651 if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET): 6652 key_type = self._parse_types( 6653 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6654 ) 6655 if not self._match(TokenType.FARROW): 6656 self._retreat(index) 6657 return None 6658 6659 value_type = self._parse_types( 6660 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6661 ) 6662 if not self._match(TokenType.R_BRACKET): 6663 self._retreat(index) 6664 return None 6665 6666 return exp.DataType( 6667 this=exp.DType.MAP, 6668 expressions=[key_type, value_type], 6669 nested=True, 6670 ) 6671 6672 nested = type_token in self.NESTED_TYPE_TOKENS 6673 is_struct = type_token in self.STRUCT_TYPE_TOKENS 6674 is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS 6675 expressions = None 6676 maybe_func = False 6677 6678 if self._match(TokenType.L_PAREN): 6679 if is_struct: 6680 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6681 elif nested: 6682 expressions = self._parse_csv( 6683 lambda: self._parse_types( 6684 check_func=check_func, schema=schema, allow_identifiers=allow_identifiers 6685 ) 6686 ) 6687 if type_token == TokenType.NULLABLE and len(expressions) == 1: 6688 this = expressions[0] 6689 this.set("nullable", True) 6690 self._match_r_paren() 6691 return this 6692 elif type_token in self.ENUM_TYPE_TOKENS: 6693 expressions = self._parse_csv(self._parse_equality) 6694 elif type_token == TokenType.JSON: 6695 # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value) 6696 # https://clickhouse.com/docs/sql-reference/data-types/newjson 6697 expressions = self._parse_csv(self._parse_json_type_arg) 6698 elif is_aggregate: 6699 func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var( 6700 any_token=False, tokens=(TokenType.VAR, TokenType.ANY) 6701 ) 6702 if not func_or_ident: 6703 return None 6704 expressions = [func_or_ident] 6705 if self._match(TokenType.COMMA): 6706 expressions.extend( 6707 self._parse_csv( 6708 lambda: self._parse_types( 6709 check_func=check_func, 6710 schema=schema, 6711 allow_identifiers=allow_identifiers, 6712 ) 6713 ) 6714 ) 6715 else: 6716 expressions = self._parse_csv(self._parse_type_size) 6717 6718 # https://docs.snowflake.com/en/sql-reference/data-types-vector 6719 if type_token == TokenType.VECTOR and len(expressions) == 2: 6720 expressions = self._parse_vector_expressions(expressions) 6721 6722 if not self._match(TokenType.R_PAREN): 6723 self._retreat(index) 6724 return None 6725 6726 maybe_func = True 6727 6728 values: list[exp.Expr] | None = None 6729 6730 if nested and self._match(TokenType.LT): 6731 if is_struct: 6732 expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True)) 6733 else: 6734 expressions = self._parse_csv( 6735 lambda: self._parse_types( 6736 check_func=check_func, 6737 schema=schema, 6738 allow_identifiers=allow_identifiers, 6739 with_collation=True, 6740 ) 6741 ) 6742 6743 if not self._match(TokenType.GT): 6744 self.raise_error("Expecting >") 6745 6746 if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)): 6747 values = self._parse_csv(self._parse_disjunction) 6748 if not values and is_struct: 6749 values = None 6750 self._retreat(self._index - 1) 6751 else: 6752 self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN)) 6753 6754 if type_token in self.TIMESTAMPS: 6755 if self._match_text_seq("WITH", "TIME", "ZONE"): 6756 maybe_func = False 6757 tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ 6758 this = exp.DataType(this=tz_type, expressions=expressions) 6759 elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"): 6760 maybe_func = False 6761 this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions) 6762 elif self._match_text_seq("WITHOUT", "TIME", "ZONE"): 6763 maybe_func = False 6764 elif type_token == TokenType.INTERVAL: 6765 if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS: 6766 unit = self._parse_var(upper=True) 6767 if self._match_text_seq("TO"): 6768 unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True)) 6769 6770 this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit)))) 6771 else: 6772 this = self.expression(exp.DataType(this=exp.DType.INTERVAL)) 6773 elif type_token == TokenType.VOID: 6774 this = exp.DataType(this=exp.DType.NULL) 6775 6776 if maybe_func and check_func: 6777 index2 = self._index 6778 peek = self._parse_string() 6779 6780 if not peek: 6781 self._retreat(index) 6782 return None 6783 6784 self._retreat(index2) 6785 6786 if not this: 6787 assert type_token is not None 6788 if self._match_text_seq("UNSIGNED"): 6789 unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token) 6790 if not unsigned_type_token: 6791 self.raise_error(f"Cannot convert {type_token.name} to unsigned.") 6792 6793 type_token = unsigned_type_token or type_token 6794 6795 # NULLABLE without parentheses can be a column (Presto/Trino) 6796 if type_token == TokenType.NULLABLE and not expressions: 6797 self._retreat(index) 6798 return None 6799 6800 this = exp.DataType( 6801 this=exp.DType[type_token.name], 6802 expressions=expressions, 6803 nested=nested, 6804 ) 6805 6806 # Empty arrays/structs are allowed 6807 if values is not None: 6808 cls = exp.Struct if is_struct else exp.Array 6809 this = exp.cast(cls(expressions=values), this, copy=False) 6810 6811 elif expressions: 6812 this.set("expressions", expressions) 6813 6814 # https://materialize.com/docs/sql/types/list/#type-name 6815 while self._match(TokenType.LIST): 6816 this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True) 6817 6818 index = self._index 6819 6820 # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3] 6821 matched_array = self._match(TokenType.ARRAY) 6822 6823 while self._curr: 6824 datatype_token = self._prev.token_type 6825 matched_l_bracket = self._match(TokenType.L_BRACKET) 6826 6827 if (not matched_l_bracket and not matched_array) or ( 6828 datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET) 6829 ): 6830 # Postgres allows casting empty arrays such as ARRAY[]::INT[], 6831 # not to be confused with the fixed size array parsing 6832 break 6833 6834 matched_array = False 6835 values = self._parse_csv(self._parse_disjunction) or None 6836 if ( 6837 values 6838 and not schema 6839 and ( 6840 not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS 6841 or datatype_token == TokenType.ARRAY 6842 or not self._match(TokenType.R_BRACKET, advance=False) 6843 ) 6844 ): 6845 # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB 6846 # 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 6847 self._retreat(index) 6848 break 6849 6850 this = exp.DataType( 6851 this=exp.DType.ARRAY, expressions=[this], values=values, nested=True 6852 ) 6853 self._match(TokenType.R_BRACKET) 6854 6855 if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType): 6856 converter = self.TYPE_CONVERTERS.get(this.this) 6857 if converter: 6858 this = converter(t.cast(exp.DataType, this)) 6859 6860 if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE): 6861 this.set("collate", self._parse_identifier() or self._parse_column()) 6862 6863 return this 6864 6865 def _parse_json_type_arg(self) -> exp.Expr | None: 6866 """Parse a single argument to ClickHouse's JSON type.""" 6867 6868 # SKIP col or SKIP REGEXP 'pattern' 6869 if self._match_text_seq("SKIP"): 6870 regexp = self._match(TokenType.RLIKE) 6871 arg = self._parse_column() 6872 if isinstance(arg, exp.Column): 6873 arg = arg.to_dot() 6874 return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg)) 6875 6876 param_or_col = self._parse_column() 6877 if not isinstance(param_or_col, exp.Column): 6878 return None 6879 6880 # Parameter: name=value (e.g., max_dynamic_paths=2) 6881 if len(param_or_col.parts) == 1 and self._match(TokenType.EQ): 6882 param = param_or_col.name 6883 value = self._parse_primary() 6884 return self.expression(exp.EQ(this=exp.var(param), expression=value)) 6885 6886 # Column type hint: col_name Type 6887 col = param_or_col.to_dot() 6888 kind = self._parse_types(check_func=False, allow_identifiers=False) 6889 return self.expression(exp.ColumnDef(this=col, kind=kind)) 6890 6891 def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]: 6892 return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]] 6893 6894 def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None: 6895 index = self._index 6896 6897 if ( 6898 self._curr 6899 and self._next 6900 and self._curr.token_type in self.TYPE_TOKENS 6901 and self._next.token_type in self.TYPE_TOKENS 6902 ): 6903 # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a 6904 # type token. Without this, the list will be parsed as a type and we'll eventually crash 6905 this = self._parse_id_var() 6906 else: 6907 this = ( 6908 self._parse_type(parse_interval=False, fallback_to_identifier=True) 6909 or self._parse_id_var() 6910 ) 6911 6912 self._match(TokenType.COLON) 6913 6914 if ( 6915 type_required 6916 and not isinstance(this, exp.DataType) 6917 and not self._match_set(self.TYPE_TOKENS, advance=False) 6918 ): 6919 self._retreat(index) 6920 return self._parse_types() 6921 6922 return self._parse_column_def(this) 6923 6924 def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None: 6925 if not self._match_text_seq("AT", "TIME", "ZONE"): 6926 return this 6927 return self._parse_at_time_zone( 6928 self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary())) 6929 ) 6930 6931 def _parse_atom(self) -> exp.Expr | None: 6932 if ( 6933 self._curr.token_type in self.IDENTIFIER_TOKENS 6934 and (column := self._parse_column()) is not None 6935 ): 6936 return column 6937 6938 token = self._curr 6939 token_type = token.token_type 6940 6941 if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)): 6942 return None 6943 6944 next_type = self._next.token_type 6945 6946 if ( 6947 next_type in self.COLUMN_OPERATORS 6948 or next_type in self.COLUMN_POSTFIX_TOKENS 6949 or (token_type == TokenType.STRING and next_type == TokenType.STRING) 6950 ): 6951 return None 6952 6953 self._advance() 6954 return primary_parser(self, token) 6955 6956 def _parse_column(self) -> exp.Expr | None: 6957 column: exp.Expr | None = self._parse_column_parts_fast() 6958 if column is None: 6959 this = self._parse_column_reference() 6960 if not this: 6961 this = self._parse_bracket(this) 6962 column = self._parse_column_ops(this) if this else this 6963 6964 if column: 6965 if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS and self._match(TokenType.JOIN_MARKER): 6966 column.set("join_mark", True) 6967 if self.COLON_IS_VARIANT_EXTRACT: 6968 column = self._parse_colon_as_variant_extract(column) 6969 6970 return column 6971 6972 def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None: 6973 """Fast path for simple column and dot references (a, a.b, ...). 6974 6975 Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks 6976 that nothing complex follows. If it does, retreats and returns None so 6977 the slow path can handle it. For >4 parts, wraps in exp.Dot nodes. 6978 """ 6979 index = self._index 6980 parts: list[exp.Identifier] | None = None 6981 all_comments: list[str] | None = None 6982 6983 while self._match_set(self.IDENTIFIER_TOKENS): 6984 token = self._prev 6985 comments = self._prev_comments 6986 6987 if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS: 6988 self._retreat(index) 6989 return None 6990 6991 has_dot = self._match(TokenType.DOT) 6992 curr_tt = self._curr.token_type 6993 6994 if not has_dot: 6995 if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS: 6996 self._retreat(index) 6997 return None 6998 elif curr_tt not in self.IDENTIFIER_TOKENS: 6999 self._retreat(index) 7000 return None 7001 7002 if parts is None: 7003 parts = [] 7004 7005 if comments: 7006 if all_comments is None: 7007 all_comments = [] 7008 all_comments.extend(comments) 7009 self._prev_comments = [] 7010 7011 parts.append( 7012 self.expression( 7013 exp.Identifier( 7014 this=token.text, quoted=token.token_type == TokenType.IDENTIFIER 7015 ), 7016 token, 7017 ) 7018 ) 7019 7020 if not has_dot: 7021 break 7022 7023 if parts is None: 7024 return None 7025 7026 n = len(parts) 7027 7028 if n == 1: 7029 column: exp.Column | exp.Dot = exp.Column(this=parts[0]) 7030 elif n == 2: 7031 column = exp.Column(this=parts[1], table=parts[0]) 7032 elif n == 3: 7033 column = exp.Column(this=parts[2], table=parts[1], db=parts[0]) 7034 else: 7035 column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0]) 7036 7037 for i in range(4, n): 7038 column = exp.Dot(this=column, expression=parts[i]) 7039 7040 if all_comments: 7041 column.add_comments(all_comments) 7042 7043 return column 7044 7045 def _parse_column_reference(self) -> exp.Expr | None: 7046 this = self._parse_field() 7047 if ( 7048 not this 7049 and self._match(TokenType.VALUES, advance=False) 7050 and self.VALUES_FOLLOWED_BY_PAREN 7051 and (not self._next or self._next.token_type != TokenType.L_PAREN) 7052 ): 7053 this = self._parse_id_var() 7054 7055 if isinstance(this, exp.Identifier): 7056 # We bubble up comments from the Identifier to the Column 7057 this = self.expression(exp.Column(this=this), comments=this.pop_comments()) 7058 7059 return this 7060 7061 def _build_json_extract( 7062 self, 7063 this: exp.Expr | None, 7064 path_parts: list[exp.JSONPathPart], 7065 ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]: 7066 if len(path_parts) > 1: 7067 this = self.expression( 7068 exp.JSONExtract( 7069 this=this, 7070 expression=exp.JSONPath(expressions=path_parts), 7071 variant_extract=True, 7072 requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION, 7073 ) 7074 ) 7075 path_parts = [exp.JSONPathRoot()] 7076 7077 return this, path_parts 7078 7079 def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None: 7080 path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()] 7081 7082 while self._match(TokenType.COLON): 7083 if not self.COLON_CHAIN_IS_SINGLE_EXTRACT: 7084 this, path_parts = self._build_json_extract(this, path_parts) 7085 7086 key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7087 7088 if key: 7089 quoted = isinstance(key, exp.Identifier) and key.quoted 7090 path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted)) 7091 7092 while True: 7093 if self._match(TokenType.DOT): 7094 next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,)) 7095 7096 if next_key: 7097 quoted = isinstance(next_key, exp.Identifier) and next_key.quoted 7098 path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted)) 7099 elif self._match(TokenType.L_BRACKET): 7100 bracket_expr = self._parse_bracket_key_value() 7101 7102 if not self._match(TokenType.R_BRACKET): 7103 self.raise_error("Expected ]") 7104 7105 if bracket_expr: 7106 if bracket_expr.is_string: 7107 path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True)) 7108 elif bracket_expr.is_star: 7109 path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard())) 7110 elif bracket_expr.is_number: 7111 path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py())) 7112 else: 7113 this, path_parts = self._build_json_extract(this, path_parts) 7114 7115 this = self.expression( 7116 exp.Bracket( 7117 this=this, expressions=[bracket_expr], json_access=True 7118 ), 7119 ) 7120 7121 elif self._match(TokenType.DCOLON): 7122 this, path_parts = self._build_json_extract(this, path_parts) 7123 7124 cast_type = self._parse_types() 7125 if cast_type: 7126 this = self.expression(exp.Cast(this=this, to=cast_type)) 7127 else: 7128 self.raise_error("Expected type after '::'") 7129 else: 7130 break 7131 7132 this, _ = self._build_json_extract(this, path_parts) 7133 7134 return this 7135 7136 def _parse_dcolon(self) -> exp.Expr | None: 7137 return self._parse_types() 7138 7139 def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None: 7140 while self._curr.token_type in self.BRACKETS: 7141 this = self._parse_bracket(this) 7142 7143 column_operators = self.COLUMN_OPERATORS 7144 cast_column_operators = self.CAST_COLUMN_OPERATORS 7145 while self._curr: 7146 op_token = self._curr.token_type 7147 7148 if op_token not in column_operators: 7149 break 7150 op = column_operators[op_token] 7151 self._advance() 7152 7153 if op_token in cast_column_operators: 7154 field = self._parse_dcolon() 7155 if not field: 7156 self.raise_error("Expected type") 7157 elif op and self._curr: 7158 field = self._parse_column_reference() or self._parse_bitwise() 7159 if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False): 7160 field = self._parse_column_ops(field) 7161 else: 7162 dot = self._is_connected() and self._prev.token_type == TokenType.DOT 7163 field = self._parse_field(any_token=True, anonymous_func=True) 7164 7165 # In t.true, t.null we should produce an Identifier node 7166 if dot and isinstance(field, (exp.Null, exp.Boolean)): 7167 field = self.expression( 7168 exp.Identifier(this=self._prev.text), 7169 comments=field.comments, 7170 ) 7171 7172 # Function calls can be qualified, e.g., x.y.FOO() 7173 # This converts the final AST to a series of Dots leading to the function call 7174 # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules 7175 if isinstance(field, (exp.Func, exp.Window)) and this: 7176 this = this.transform( 7177 lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n 7178 ) 7179 7180 if op: 7181 this = op(self, this, field) 7182 elif isinstance(this, exp.Column) and not this.args.get("catalog"): 7183 this = self.expression( 7184 exp.Column( 7185 this=field, 7186 table=this.this, 7187 db=this.args.get("table"), 7188 catalog=this.args.get("db"), 7189 ), 7190 comments=this.comments, 7191 ) 7192 elif isinstance(field, exp.Window): 7193 # Move the exp.Dot's to the window's function 7194 window_func = self.expression(exp.Dot(this=this, expression=field.this)) 7195 field.set("this", window_func) 7196 this = field 7197 else: 7198 this = self.expression(exp.Dot(this=this, expression=field)) 7199 7200 if field and field.comments: 7201 t.cast(exp.Expr, this).add_comments(field.pop_comments()) 7202 7203 this = self._parse_bracket(this) 7204 7205 return this 7206 7207 def _parse_paren(self) -> exp.Expr | None: 7208 if not self._match(TokenType.L_PAREN): 7209 return None 7210 7211 comments = self._prev_comments 7212 query = self._parse_select() 7213 7214 if query: 7215 expressions = [query] 7216 else: 7217 expressions = self._parse_expressions() 7218 7219 this = seq_get(expressions, 0) 7220 7221 if not this and self._match(TokenType.R_PAREN, advance=False): 7222 this = self.expression(exp.Tuple()) 7223 elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA: 7224 this = self.expression(exp.Tuple(expressions=expressions)) 7225 elif isinstance(this, exp.UNWRAPPED_QUERIES): 7226 this = self._parse_subquery(this=this, parse_alias=False) 7227 elif isinstance(this, (exp.Subquery, exp.Values)): 7228 this = self._parse_subquery( 7229 this=self._parse_query_modifiers(self._parse_set_operations(this)), 7230 parse_alias=False, 7231 ) 7232 else: 7233 this = self.expression(exp.Paren(this=this)) 7234 7235 if this: 7236 this.add_comments(comments) 7237 7238 self._match_r_paren(expression=this) 7239 7240 if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc): 7241 return self._parse_window(this) 7242 7243 return this 7244 7245 def _parse_primary(self) -> exp.Expr | None: 7246 if self._match_set(self.PRIMARY_PARSERS): 7247 token_type = self._prev.token_type 7248 primary = self.PRIMARY_PARSERS[token_type](self, self._prev) 7249 7250 if token_type == TokenType.STRING: 7251 expressions = [primary] 7252 while self._match(TokenType.STRING, advance=False): 7253 if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED: 7254 self.raise_error( 7255 "Adjacent string literals need to be separated by whitespace or comments" 7256 ) 7257 7258 self._advance() 7259 expressions.append(exp.Literal.string(self._prev.text)) 7260 7261 if len(expressions) > 1: 7262 return self.expression( 7263 exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE) 7264 ) 7265 7266 return primary 7267 7268 if self._match_pair(TokenType.DOT, TokenType.NUMBER): 7269 return exp.Literal.number(f"0.{self._prev.text}") 7270 7271 return self._parse_paren() 7272 7273 def _parse_field( 7274 self, 7275 any_token: bool = False, 7276 tokens: t.Collection[TokenType] | None = None, 7277 anonymous_func: bool = False, 7278 ) -> exp.Expr | None: 7279 after_dot = ( 7280 self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT 7281 ) 7282 7283 if anonymous_func: 7284 field = ( 7285 self._parse_function(anonymous=anonymous_func, any_token=any_token) 7286 or self._parse_primary() 7287 ) 7288 else: 7289 field = self._parse_primary() or self._parse_function( 7290 anonymous=anonymous_func, any_token=any_token 7291 ) 7292 7293 field = field or self._parse_id_var(any_token=any_token, tokens=tokens) 7294 7295 if after_dot and isinstance(field, exp.Literal) and field.is_number: 7296 name = field.name 7297 if self._is_connected() and self._parse_var(any_token=True): 7298 name += self._prev.text 7299 7300 field = exp.Identifier(this=name, quoted=True).update_positions(field) 7301 7302 return field 7303 7304 def _parse_function( 7305 self, 7306 functions: dict[str, t.Callable] | None = None, 7307 anonymous: bool = False, 7308 optional_parens: bool = True, 7309 any_token: bool = False, 7310 ) -> exp.Expr | None: 7311 # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this) 7312 # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences 7313 fn_syntax = False 7314 if ( 7315 self._match(TokenType.L_BRACE, advance=False) 7316 and self._next 7317 and self._next.text.upper() == "FN" 7318 ): 7319 self._advance(2) 7320 fn_syntax = True 7321 7322 func = self._parse_function_call( 7323 functions=functions, 7324 anonymous=anonymous, 7325 optional_parens=optional_parens, 7326 any_token=any_token, 7327 ) 7328 7329 if fn_syntax: 7330 self._match(TokenType.R_BRACE) 7331 7332 return func 7333 7334 def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]: 7335 return self._parse_csv(lambda: self._parse_lambda(alias=alias)) 7336 7337 def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren: 7338 args = self._parse_function_args(alias=False) 7339 if not args: 7340 self.raise_error("Expected at least one argument") 7341 7342 # Wrapped so the connector keeps its precedence in the parent context 7343 return exp.Paren(this=connector(*args, copy=False)) 7344 7345 def _parse_function_call( 7346 self, 7347 functions: dict[str, t.Callable] | None = None, 7348 anonymous: bool = False, 7349 optional_parens: bool = True, 7350 any_token: bool = False, 7351 ) -> exp.Expr | None: 7352 if not self._curr: 7353 return None 7354 7355 comments = self._curr.comments 7356 prev = self._prev 7357 token = self._curr 7358 token_type = self._curr.token_type 7359 this: str | exp.Expr = self._curr.text 7360 upper = self._curr.text.upper() 7361 7362 after_dot = prev.token_type == TokenType.DOT 7363 parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper) 7364 if ( 7365 optional_parens 7366 and parser 7367 and token_type not in self.INVALID_FUNC_NAME_TOKENS 7368 and not after_dot 7369 ): 7370 self._advance() 7371 return self._parse_window(parser(self)) 7372 7373 if self._next.token_type != TokenType.L_PAREN: 7374 if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot: 7375 self._advance() 7376 return self.expression(self.NO_PAREN_FUNCTIONS[token_type]()) 7377 7378 return None 7379 7380 if any_token: 7381 if token_type in self.RESERVED_TOKENS: 7382 return None 7383 elif token_type not in self.FUNC_TOKENS: 7384 return None 7385 7386 self._advance(2) 7387 7388 parser = self.FUNCTION_PARSERS.get(upper) 7389 if parser and not anonymous: 7390 result = parser(self) 7391 else: 7392 subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type) 7393 7394 if subquery_predicate: 7395 expr = None 7396 if self._curr.token_type in self.SUBQUERY_TOKENS: 7397 expr = self._parse_select() 7398 self._match_r_paren() 7399 elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE): 7400 # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like 7401 # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren 7402 self._advance(-1) 7403 expr = self._parse_bitwise() 7404 7405 if expr: 7406 return self.expression(subquery_predicate(this=expr), comments=comments) 7407 7408 if functions is None: 7409 functions = self.FUNCTIONS 7410 7411 function = functions.get(upper) 7412 known_function = function and not anonymous 7413 7414 alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS 7415 args = self._parse_function_args(alias) 7416 7417 post_func_comments = self._curr.comments if self._curr else None 7418 if known_function and post_func_comments: 7419 # If the user-inputted comment "/* sqlglot.anonymous */" is following the function 7420 # call we'll construct it as exp.Anonymous, even if it's "known" 7421 if any( 7422 comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS) 7423 for comment in post_func_comments 7424 ): 7425 known_function = False 7426 7427 if alias and known_function: 7428 args = self._kv_to_prop_eq(args) 7429 7430 if known_function: 7431 func_builder = t.cast(t.Callable, function) 7432 7433 # mypyc compiled functions don't have __code__, so we use 7434 # try/except to check if func_builder accepts 'dialect'. 7435 try: 7436 func = func_builder(args) 7437 except TypeError: 7438 func = func_builder(args, dialect=self.dialect) 7439 7440 func = self.validate_expression(func, args) 7441 if name_key := self.dialect.ORIGINAL_NAME_META_KEY: 7442 func.meta[name_key] = this 7443 7444 result = func 7445 else: 7446 if token_type == TokenType.IDENTIFIER: 7447 this = exp.Identifier(this=this, quoted=True).update_positions(token) 7448 7449 result = self.expression(exp.Anonymous(this=this, expressions=args)) 7450 7451 result = result.update_positions(token) 7452 7453 if isinstance(result, exp.Expr): 7454 result.add_comments(comments) 7455 7456 if parser: 7457 self._match(TokenType.R_PAREN, expression=result) 7458 else: 7459 self._match_r_paren(result) 7460 return self._parse_window(result) 7461 7462 def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr: 7463 return expression 7464 7465 def _kv_to_prop_eq( 7466 self, expressions: list[exp.Expr], parse_map: bool = False 7467 ) -> list[exp.Expr]: 7468 transformed = [] 7469 7470 for index, e in enumerate(expressions): 7471 if isinstance(e, self.KEY_VALUE_DEFINITIONS): 7472 if isinstance(e, exp.Alias): 7473 e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this)) 7474 7475 if not isinstance(e, exp.PropertyEQ): 7476 e = self.expression( 7477 exp.PropertyEQ( 7478 this=e.this if parse_map else exp.to_identifier(e.this.name), 7479 expression=e.expression, 7480 ) 7481 ) 7482 7483 if isinstance(e.this, exp.Column): 7484 e.this.replace(e.this.this) 7485 else: 7486 e = self._to_prop_eq(e, index) 7487 7488 transformed.append(e) 7489 7490 return transformed 7491 7492 def _parse_function_properties(self) -> exp.Properties | None: 7493 # Skip the generic `key = value` fallback in _parse_property since this 7494 # runs post-AS where a function body like `name = expr` can be misread 7495 # as a property. 7496 properties = [] 7497 while True: 7498 if self._match_texts(self.PROPERTY_PARSERS): 7499 keyword = self._prev.text.upper() 7500 prop = self.PROPERTY_PARSERS[keyword](self) 7501 elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS): 7502 keyword = self._prev.text.upper() 7503 prop = self.PROPERTY_PARSERS[keyword](self, default=True) 7504 else: 7505 break 7506 if not prop: 7507 self.raise_error(f"Failed to parse property '{keyword}'") 7508 break 7509 for p in ensure_list(prop): 7510 properties.append(p) 7511 7512 return self.expression(exp.Properties(expressions=properties)) if properties else None 7513 7514 def _parse_user_defined_function_expression(self) -> exp.Expr | None: 7515 return self._parse_statement() 7516 7517 def _parse_function_parameter(self) -> exp.Expr | None: 7518 return self._parse_column_def(this=self._parse_id_var(), computed_column=False) 7519 7520 def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None: 7521 this = self._parse_table_parts(schema=True) 7522 7523 if not self._match(TokenType.L_PAREN): 7524 return this 7525 7526 expressions = self._parse_csv(self._parse_function_parameter) 7527 self._match_r_paren() 7528 return self.expression( 7529 exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True) 7530 ) 7531 7532 def _parse_macro_overloads( 7533 self, 7534 this: exp.UserDefinedFunction, 7535 first_body: exp.Expr, 7536 first_is_table: bool = False, 7537 ) -> exp.MacroOverloads: 7538 overloads = [ 7539 self.expression( 7540 exp.MacroOverload( 7541 this=first_body, 7542 expressions=this.expressions or None, 7543 is_table=first_is_table, 7544 ) 7545 ) 7546 ] 7547 this.set("expressions", None) 7548 this.set("wrapped", False) 7549 7550 while self._match(TokenType.COMMA): 7551 if not self._match(TokenType.L_PAREN): 7552 break 7553 7554 params = self._parse_csv(self._parse_function_parameter) 7555 self._match_r_paren() 7556 7557 if not self._match(TokenType.ALIAS): 7558 break 7559 7560 is_table = self._match(TokenType.TABLE) 7561 body = self._parse_expression() 7562 macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table) 7563 overloads.append(self.expression(macro)) 7564 7565 return self.expression(exp.MacroOverloads(expressions=overloads)) 7566 7567 def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier: 7568 literal = self._parse_primary() 7569 if literal: 7570 return self.expression(exp.Introducer(this=token.text, expression=literal), token) 7571 7572 return self._identifier_expression(token) 7573 7574 def _parse_session_parameter(self) -> exp.SessionParameter: 7575 kind = None 7576 this = self._parse_id_var() or self._parse_primary() 7577 7578 if this and self._match(TokenType.DOT): 7579 kind = this.name 7580 this = self._parse_var() or self._parse_primary() 7581 7582 return self.expression(exp.SessionParameter(this=this, kind=kind)) 7583 7584 def _parse_lambda_arg(self) -> exp.Expr | None: 7585 return self._parse_id_var() 7586 7587 def _parse_lambda(self, alias: bool = False) -> exp.Expr | None: 7588 next_token_type = self._next.token_type 7589 7590 # Fast path: simple atom (column, literal, null, bool) followed by , or ) 7591 if ( 7592 next_token_type in self.LAMBDA_ARG_TERMINATORS 7593 and (atom := self._parse_atom()) is not None 7594 ): 7595 return atom 7596 7597 index = self._index 7598 7599 if self._match(TokenType.L_PAREN): 7600 expressions = t.cast( 7601 list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg) 7602 ) 7603 7604 if not self._match(TokenType.R_PAREN): 7605 self._retreat(index) 7606 elif self._match_set(self.LAMBDAS): 7607 return self.LAMBDAS[self._prev.token_type](self, expressions) 7608 else: 7609 self._retreat(index) 7610 elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS: 7611 expressions = [self._parse_lambda_arg()] 7612 7613 if self._match_set(self.LAMBDAS): 7614 return self.LAMBDAS[self._prev.token_type](self, expressions) 7615 7616 self._retreat(index) 7617 7618 this: exp.Expr | None 7619 7620 if self._match(TokenType.DISTINCT): 7621 this = self.expression( 7622 exp.Distinct(expressions=self._parse_csv(self._parse_disjunction)) 7623 ) 7624 else: 7625 self._match(TokenType.ALL) # ALL is the default/no-op aggregate modifier (SQL-92) 7626 this = self._parse_select_or_expression(alias=alias) 7627 7628 return self._parse_limit( 7629 self._parse_respect_or_ignore_nulls( 7630 self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this))) 7631 ) 7632 ) 7633 7634 def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None: 7635 index = self._index 7636 if not self._match(TokenType.L_PAREN): 7637 return this 7638 7639 # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>), 7640 # expr can be of both types 7641 if self._match_set(self.SELECT_START_TOKENS): 7642 self._retreat(index) 7643 return this 7644 args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def()) 7645 self._match_r_paren() 7646 return self.expression(exp.Schema(this=this, expressions=args)) 7647 7648 def _parse_field_def(self) -> exp.Expr | None: 7649 return self._parse_column_def(self._parse_field(any_token=True)) 7650 7651 def _parse_column_def( 7652 self, this: exp.Expr | None, computed_column: bool = True 7653 ) -> exp.Expr | None: 7654 # column defs are not really columns, they're identifiers 7655 if isinstance(this, exp.Column): 7656 this = this.this 7657 7658 if not computed_column: 7659 self._match(TokenType.ALIAS) 7660 7661 kind = self._parse_types(schema=True) 7662 7663 if self._match_text_seq("FOR", "ORDINALITY"): 7664 return self.expression(exp.ColumnDef(this=this, ordinality=True)) 7665 7666 constraints: list[exp.Expr] = [] 7667 7668 if (not kind and self._match(TokenType.ALIAS)) or self._match_texts( 7669 ("ALIAS", "MATERIALIZED") 7670 ): 7671 # Match storage before _parse_types so STORED is not treated as a data type 7672 # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`). 7673 persisted = self._prev.text.upper() == "MATERIALIZED" 7674 expression = self._parse_disjunction() 7675 if not persisted: 7676 if self._match_text_seq("PERSISTED"): 7677 persisted = True 7678 elif self._match_texts(("STORED", "VIRTUAL")): 7679 persisted = self._prev.text.upper() == "STORED" 7680 constraint_kind = exp.ComputedColumnConstraint( 7681 this=expression, 7682 persisted=persisted, 7683 data_type=exp.Var(this="AUTO") 7684 if self._match_text_seq("AUTO") 7685 else self._parse_types(), 7686 not_null=self._match_pair(TokenType.NOT, TokenType.NULL), 7687 ) 7688 constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind))) 7689 elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False): 7690 in_out_constraint = self.expression( 7691 exp.InOutColumnConstraint( 7692 input_=self._match(TokenType.IN), output=self._match(TokenType.OUT) 7693 ) 7694 ) 7695 constraints.append(in_out_constraint) 7696 kind = self._parse_types() 7697 elif ( 7698 kind 7699 and self._match(TokenType.ALIAS, advance=False) 7700 and ( 7701 not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT 7702 or self._next.token_type == TokenType.L_PAREN 7703 ) 7704 ): 7705 self._advance() 7706 constraints.append( 7707 self.expression( 7708 exp.ColumnConstraint( 7709 kind=exp.ComputedColumnConstraint( 7710 this=self._parse_disjunction(), 7711 persisted=self._match_texts(("STORED", "VIRTUAL")) 7712 and self._prev.text.upper() == "STORED", 7713 ) 7714 ) 7715 ) 7716 ) 7717 7718 while True: 7719 constraint = self._parse_column_constraint() 7720 if not constraint: 7721 break 7722 constraints.append(constraint) 7723 7724 if not kind and not constraints: 7725 return this 7726 7727 position = None 7728 if self._match_texts(("FIRST", "AFTER")): 7729 pos = self._prev.text 7730 position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos)) 7731 7732 return self.expression( 7733 exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position) 7734 ) 7735 7736 def _parse_auto_increment( 7737 self, 7738 ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint: 7739 start = None 7740 increment = None 7741 order = None 7742 7743 if self._match(TokenType.L_PAREN, advance=False): 7744 args = self._parse_wrapped_csv(self._parse_bitwise) 7745 start = seq_get(args, 0) 7746 increment = seq_get(args, 1) 7747 7748 # The remaining parts form an unordered bag and any of them can be omitted, in which 7749 # case the engine falls back to its own default, so they're parsed independently. 7750 while True: 7751 if self._match_text_seq("START"): 7752 start = self._parse_bitwise() 7753 elif self._match_text_seq("INCREMENT"): 7754 increment = self._parse_bitwise() 7755 elif self._match_text_seq("ORDER"): 7756 order = True 7757 elif self._match_text_seq("NOORDER"): 7758 order = False 7759 else: 7760 break 7761 7762 if start or increment or order is not None: 7763 return exp.GeneratedAsIdentityColumnConstraint( 7764 start=start, increment=increment, this=False, order=order 7765 ) 7766 7767 return exp.AutoIncrementColumnConstraint() 7768 7769 def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None: 7770 if not self._match(TokenType.L_PAREN, advance=False): 7771 return None 7772 7773 return self.expression( 7774 exp.CheckColumnConstraint( 7775 this=self._parse_wrapped(self._parse_assignment), 7776 enforced=self._match_text_seq("ENFORCED"), 7777 ) 7778 ) 7779 7780 def _parse_auto_property(self) -> exp.AutoRefreshProperty | None: 7781 if not self._match_text_seq("REFRESH"): 7782 self._retreat(self._index - 1) 7783 return None 7784 return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True))) 7785 7786 def _parse_compress(self) -> exp.CompressColumnConstraint: 7787 if self._match(TokenType.L_PAREN, advance=False): 7788 return self.expression( 7789 exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise)) 7790 ) 7791 7792 return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise())) 7793 7794 def _parse_generated_as_identity( 7795 self, 7796 ) -> ( 7797 exp.GeneratedAsIdentityColumnConstraint 7798 | exp.ComputedColumnConstraint 7799 | exp.GeneratedAsRowColumnConstraint 7800 ): 7801 if self._match_text_seq("BY", "DEFAULT"): 7802 on_null = self._match_pair(TokenType.ON, TokenType.NULL) 7803 this = self.expression( 7804 exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null) 7805 ) 7806 else: 7807 self._match_text_seq("ALWAYS") 7808 this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True)) 7809 7810 self._match(TokenType.ALIAS) 7811 7812 if self._match_text_seq("ROW"): 7813 start = self._match_text_seq("START") 7814 if not start: 7815 self._match(TokenType.END) 7816 hidden = self._match_text_seq("HIDDEN") 7817 return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden)) 7818 7819 identity = self._match_text_seq("IDENTITY") 7820 7821 if self._match(TokenType.L_PAREN): 7822 if self._match_text_seq("START", "WITH"): 7823 this.set("start", self._parse_bitwise()) 7824 if self._match_text_seq("INCREMENT", "BY"): 7825 this.set("increment", self._parse_bitwise()) 7826 if self._match_text_seq("MINVALUE"): 7827 this.set("minvalue", self._parse_bitwise()) 7828 if self._match_text_seq("MAXVALUE"): 7829 this.set("maxvalue", self._parse_bitwise()) 7830 7831 if self._match_text_seq("CYCLE"): 7832 this.set("cycle", True) 7833 elif self._match_text_seq("NO", "CYCLE"): 7834 this.set("cycle", False) 7835 7836 if not identity: 7837 this.set("expression", self._parse_range()) 7838 elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False): 7839 args = self._parse_csv(self._parse_bitwise) 7840 this.set("start", seq_get(args, 0)) 7841 this.set("increment", seq_get(args, 1)) 7842 7843 self._match_r_paren() 7844 7845 return this 7846 7847 def _parse_inline(self) -> exp.InlineLengthColumnConstraint: 7848 self._match_text_seq("LENGTH") 7849 return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise())) 7850 7851 def _parse_not_constraint(self) -> exp.Expr | None: 7852 if self._match_text_seq("NULL"): 7853 return self.expression(exp.NotNullColumnConstraint()) 7854 if self._match_text_seq("CASESPECIFIC"): 7855 return self.expression(exp.CaseSpecificColumnConstraint(not_=True)) 7856 if self._match_text_seq("FOR", "REPLICATION"): 7857 return self.expression(exp.NotForReplicationColumnConstraint()) 7858 7859 # Unconsume the `NOT` token 7860 self._retreat(self._index - 1) 7861 return None 7862 7863 def _parse_column_constraint(self) -> exp.Expr | None: 7864 this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None 7865 7866 procedure_option_follows = ( 7867 self._match(TokenType.WITH, advance=False) 7868 and self._next 7869 and self._next.text.upper() in self.PROCEDURE_OPTIONS 7870 ) 7871 7872 index = self._index 7873 if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS): 7874 constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self) 7875 if not constraint: 7876 self._retreat(index) 7877 return None 7878 7879 return self.expression(exp.ColumnConstraint(this=this, kind=constraint)) 7880 7881 if self._match(TokenType.CHARACTER_SET) or self._match_pair(TokenType.CHAR, TokenType.SET): 7882 return self.expression( 7883 exp.ColumnConstraint( 7884 this=this, 7885 kind=self.expression( 7886 exp.CharacterSetColumnConstraint(this=self._parse_var_or_string()) 7887 ), 7888 ) 7889 ) 7890 7891 return this 7892 7893 def _parse_constraint(self) -> exp.Expr | None: 7894 if not self._match(TokenType.CONSTRAINT): 7895 return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS) 7896 7897 return self.expression( 7898 exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints()) 7899 ) 7900 7901 def _parse_unnamed_constraints(self) -> list[exp.Expr]: 7902 constraints = [] 7903 while True: 7904 constraint = self._parse_unnamed_constraint() or self._parse_function() 7905 if not constraint: 7906 break 7907 constraints.append(constraint) 7908 7909 return constraints 7910 7911 def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None: 7912 index = self._index 7913 7914 if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts( 7915 constraints or self.CONSTRAINT_PARSERS 7916 ): 7917 return None 7918 7919 constraint_key = self._prev.text.upper() 7920 if constraint_key not in self.CONSTRAINT_PARSERS: 7921 self.raise_error(f"No parser found for schema constraint {constraint_key}.") 7922 7923 result = self.CONSTRAINT_PARSERS[constraint_key](self) 7924 if not result: 7925 self._retreat(index) 7926 7927 return result 7928 7929 def _parse_unique_key(self) -> exp.Expr | None: 7930 if ( 7931 self._curr 7932 and self._curr.token_type != TokenType.IDENTIFIER 7933 and self._curr.text.upper() in self.CONSTRAINT_PARSERS 7934 ): 7935 return None 7936 return self._parse_id_var(any_token=False) 7937 7938 def _parse_unique(self) -> exp.UniqueColumnConstraint: 7939 self._match_texts(("KEY", "INDEX")) 7940 return self.expression( 7941 exp.UniqueColumnConstraint( 7942 nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"), 7943 this=self._parse_schema(self._parse_unique_key()), 7944 index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text, 7945 on_conflict=self._parse_on_conflict(), 7946 options=self._parse_key_constraint_options(), 7947 ) 7948 ) 7949 7950 def _parse_key_constraint_options(self) -> list[str]: 7951 options = [] 7952 while True: 7953 if not self._curr: 7954 break 7955 7956 if self._match(TokenType.ON): 7957 action = None 7958 on = self._advance_any() and self._prev.text 7959 7960 if self._match_text_seq("NO", "ACTION"): 7961 action = "NO ACTION" 7962 elif self._match_text_seq("CASCADE"): 7963 action = "CASCADE" 7964 elif self._match_text_seq("RESTRICT"): 7965 action = "RESTRICT" 7966 elif self._match_pair(TokenType.SET, TokenType.NULL): 7967 action = "SET NULL" 7968 elif self._match_pair(TokenType.SET, TokenType.DEFAULT): 7969 action = "SET DEFAULT" 7970 else: 7971 self.raise_error("Invalid key constraint") 7972 7973 options.append(f"ON {on} {action}") 7974 else: 7975 var = self._parse_var_from_options( 7976 self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False 7977 ) 7978 if not var: 7979 break 7980 options.append(var.name) 7981 7982 return options 7983 7984 def _parse_references(self, match: bool = True) -> exp.Reference | None: 7985 if match and not self._match(TokenType.REFERENCES): 7986 return None 7987 7988 expressions: list | None = None 7989 this = self._parse_table(schema=True) 7990 options = self._parse_key_constraint_options() 7991 return self.expression(exp.Reference(this=this, expressions=expressions, options=options)) 7992 7993 def _parse_foreign_key(self) -> exp.ForeignKey: 7994 expressions = ( 7995 self._parse_wrapped_id_vars() 7996 if not self._match(TokenType.REFERENCES, advance=False) 7997 else None 7998 ) 7999 reference = self._parse_references() 8000 on_options = {} 8001 8002 while self._match(TokenType.ON): 8003 if not self._match_set((TokenType.DELETE, TokenType.UPDATE)): 8004 self.raise_error("Expected DELETE or UPDATE") 8005 8006 kind = self._prev.text.lower() 8007 8008 if self._match_text_seq("NO", "ACTION"): 8009 action = "NO ACTION" 8010 elif self._match(TokenType.SET): 8011 self._match_set((TokenType.NULL, TokenType.DEFAULT)) 8012 action = "SET " + self._prev.text.upper() 8013 else: 8014 self._advance() 8015 action = self._prev.text.upper() 8016 8017 on_options[kind] = action 8018 8019 return self.expression( 8020 exp.ForeignKey( 8021 expressions=expressions, 8022 reference=reference, 8023 options=self._parse_key_constraint_options(), 8024 **on_options, 8025 ) 8026 ) 8027 8028 def _parse_primary_key_part(self) -> exp.Expr | None: 8029 return self._parse_field() 8030 8031 def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None: 8032 if not self._match_text_seq("FOR", "SYSTEM_TIME"): 8033 self._retreat(self._index - 1) 8034 return None 8035 8036 id_vars = self._parse_wrapped_id_vars() 8037 return self.expression( 8038 exp.PeriodForSystemTimeConstraint( 8039 this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1) 8040 ) 8041 ) 8042 8043 def _parse_primary_key( 8044 self, 8045 wrapped_optional: bool = False, 8046 in_props: bool = False, 8047 named_primary_key: bool = False, 8048 ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey: 8049 desc = ( 8050 self._prev.token_type == TokenType.DESC 8051 if self._match_set((TokenType.ASC, TokenType.DESC)) 8052 else None 8053 ) 8054 8055 this = None 8056 if ( 8057 named_primary_key 8058 and self._curr.text.upper() not in self.CONSTRAINT_PARSERS 8059 and self._next 8060 and self._next.token_type == TokenType.L_PAREN 8061 ): 8062 this = self._parse_id_var() 8063 8064 if not in_props and not self._match(TokenType.L_PAREN, advance=False): 8065 return self.expression( 8066 exp.PrimaryKeyColumnConstraint( 8067 desc=desc, options=self._parse_key_constraint_options() 8068 ) 8069 ) 8070 8071 expressions = self._parse_wrapped_csv( 8072 self._parse_primary_key_part, optional=wrapped_optional 8073 ) 8074 8075 return self.expression( 8076 exp.PrimaryKey( 8077 this=this, 8078 expressions=expressions, 8079 include=self._parse_index_params(), 8080 options=self._parse_key_constraint_options(), 8081 ) 8082 ) 8083 8084 def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None: 8085 return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True)) 8086 8087 def _parse_odbc_datetime_literal(self) -> exp.Expr: 8088 """ 8089 Parses a datetime column in ODBC format. We parse the column into the corresponding 8090 types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the 8091 same as we did for `DATE('yyyy-mm-dd')`. 8092 8093 Reference: 8094 https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals 8095 """ 8096 self._match(TokenType.VAR) 8097 exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()] 8098 expression = self.expression(exp_class(this=self._parse_string())) 8099 if not self._match(TokenType.R_BRACE): 8100 self.raise_error("Expected }") 8101 return expression 8102 8103 def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None: 8104 if not self._match_set(self.BRACKETS): 8105 return this 8106 8107 if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: 8108 map_token = seq_get(self._tokens, self._index - 2) 8109 parse_map = map_token is not None and map_token.text.upper() == "MAP" 8110 else: 8111 parse_map = False 8112 8113 bracket_kind = self._prev.token_type 8114 if ( 8115 bracket_kind == TokenType.L_BRACE 8116 and self._curr 8117 and self._curr.token_type == TokenType.VAR 8118 and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS 8119 ): 8120 return self._parse_odbc_datetime_literal() 8121 8122 expressions = self._parse_csv( 8123 lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE) 8124 ) 8125 8126 if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET): 8127 self.raise_error("Expected ]") 8128 elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE): 8129 self.raise_error("Expected }") 8130 8131 # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs 8132 if bracket_kind == TokenType.L_BRACE: 8133 this = self.expression( 8134 exp.Struct( 8135 expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map) 8136 ) 8137 ) 8138 elif not this: 8139 this = build_array_constructor( 8140 exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect 8141 ) 8142 else: 8143 constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper()) 8144 if constructor_type: 8145 return build_array_constructor( 8146 constructor_type, 8147 args=expressions, 8148 bracket_kind=bracket_kind, 8149 dialect=self.dialect, 8150 ) 8151 8152 expressions = apply_index_offset( 8153 this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect 8154 ) 8155 this = self.expression( 8156 exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments() 8157 ) 8158 8159 self._add_comments(this) 8160 return self._parse_bracket(this) 8161 8162 def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None: 8163 if not self._match(TokenType.COLON): 8164 return this 8165 8166 if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False): 8167 self._advance() 8168 end: exp.Expr | None = -exp.Literal.number("1") 8169 else: 8170 end = self._parse_assignment() 8171 step = self._parse_unary() if self._match(TokenType.COLON) else None 8172 return self.expression(exp.Slice(this=this, expression=end, step=step)) 8173 8174 def _parse_case(self) -> exp.Expr | None: 8175 if self._match(TokenType.DOT, advance=False): 8176 # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake 8177 self._retreat(self._index - 1) 8178 return None 8179 8180 ifs = [] 8181 default = None 8182 8183 comments = self._prev_comments 8184 expression = self._parse_disjunction() 8185 8186 while self._match(TokenType.WHEN): 8187 this = self._parse_disjunction() 8188 self._match(TokenType.THEN) 8189 then = self._parse_disjunction() 8190 ifs.append(self.expression(exp.If(this=this, true=then))) 8191 8192 if self._match(TokenType.ELSE): 8193 default = self._parse_disjunction() 8194 8195 if not self._match(TokenType.END): 8196 if isinstance(default, exp.Interval) and default.this.sql().upper() == "END": 8197 default = exp.column("interval") 8198 else: 8199 self.raise_error("Expected END after CASE", self._prev) 8200 8201 return self.expression( 8202 exp.Case(this=expression, ifs=ifs, default=default), comments=comments 8203 ) 8204 8205 def _parse_if(self) -> exp.Expr | None: 8206 if self._match(TokenType.L_PAREN): 8207 args = self._parse_csv( 8208 lambda: self._parse_alias(self._parse_assignment(), explicit=True) 8209 ) 8210 this = self.validate_expression(exp.If.from_arg_list(args), args) 8211 self._match_r_paren() 8212 else: 8213 index = self._index - 1 8214 8215 if self.NO_PAREN_IF_COMMANDS and index == 0: 8216 return self._parse_as_command(self._prev) 8217 8218 condition = self._parse_disjunction() 8219 8220 if not condition or not self._match(TokenType.THEN): 8221 self._retreat(index) 8222 return None 8223 8224 true = self._parse_disjunction() 8225 false = self._parse_disjunction() if self._match(TokenType.ELSE) else None 8226 self._match(TokenType.END) 8227 this = self.expression(exp.If(this=condition, true=true, false=false)) 8228 8229 return this 8230 8231 def _parse_next_value_for(self) -> exp.Expr | None: 8232 if not self._match_text_seq("VALUE", "FOR"): 8233 self._retreat(self._index - 1) 8234 return None 8235 8236 return self.expression( 8237 exp.NextValueFor( 8238 this=self._parse_column(), 8239 order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order), 8240 ) 8241 ) 8242 8243 def _parse_extract(self) -> exp.Extract | exp.Anonymous: 8244 this = self._parse_function() or self._parse_var_or_string(upper=True) 8245 8246 if self._match(TokenType.FROM): 8247 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8248 8249 if not self._match(TokenType.COMMA): 8250 self.raise_error("Expected FROM or comma after EXTRACT", self._prev) 8251 8252 return self.expression(exp.Extract(this=this, expression=self._parse_bitwise())) 8253 8254 def _parse_gap_fill(self) -> exp.GapFill: 8255 self._match(TokenType.TABLE) 8256 this = self._parse_table() 8257 8258 self._match(TokenType.COMMA) 8259 args = [this, *self._parse_csv(self._parse_lambda)] 8260 8261 gap_fill = exp.GapFill.from_arg_list(args) 8262 return self.validate_expression(gap_fill, args) 8263 8264 def _parse_char(self) -> exp.Chr: 8265 return self.expression( 8266 exp.Chr( 8267 expressions=self._parse_csv(self._parse_assignment), 8268 charset=self._match(TokenType.USING) and self._parse_charset_name(), 8269 ) 8270 ) 8271 8272 def _parse_charset_name(self) -> exp.Expr | None: 8273 """ 8274 Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting 8275 for specific name shapes override this. 8276 """ 8277 return self._parse_var( 8278 tokens={TokenType.BINARY, TokenType.IDENTIFIER}, 8279 ) 8280 8281 def _parse_weight_string(self) -> exp.WeightString | exp.Anonymous: 8282 name = self._tokens[self._index - 2].text 8283 args = self._parse_csv(self._parse_assignment) 8284 8285 # MySQL also accepts an undocumented WEIGHT_STRING(str, n, n, n) form 8286 if len(args) != 1: 8287 return self.expression(exp.Anonymous(this=name, expressions=args)) 8288 8289 return self.expression( 8290 exp.WeightString(this=args[0], to=self._match(TokenType.ALIAS) and self._parse_types()) 8291 ) 8292 8293 def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr: 8294 this = self._parse_assignment() 8295 8296 if not self._match(TokenType.ALIAS): 8297 if self._match(TokenType.COMMA): 8298 return self.expression(exp.CastToStrType(this=this, to=self._parse_string())) 8299 8300 self.raise_error("Expected AS after CAST") 8301 8302 fmt = None 8303 to = self._parse_types(with_collation=True) 8304 8305 default = None 8306 if self._match(TokenType.DEFAULT): 8307 default = self._parse_bitwise() 8308 self._match_text_seq("ON", "CONVERSION", "ERROR") 8309 8310 if self._match_set((TokenType.FORMAT, TokenType.COMMA)): 8311 fmt_string = self._parse_wrapped(self._parse_string, optional=True) 8312 fmt = self._parse_at_time_zone(fmt_string) 8313 8314 if not to: 8315 to = exp.DType.UNKNOWN.into_expr() 8316 if to.this in exp.DataType.TEMPORAL_TYPES: 8317 this = self.expression( 8318 (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)( 8319 this=this, 8320 format=exp.Literal.string( 8321 format_time( 8322 fmt_string.this if fmt_string else "", 8323 self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING, 8324 self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE, 8325 ) 8326 ), 8327 safe=safe, 8328 ) 8329 ) 8330 8331 if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime): 8332 this.set("zone", fmt.args["zone"]) 8333 return this 8334 elif not to: 8335 self.raise_error("Expected TYPE after CAST") 8336 elif isinstance(to, exp.Identifier): 8337 to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True) 8338 else: 8339 to = self._parse_char_charset(to) 8340 8341 return self.build_cast( 8342 strict=strict, 8343 this=this, 8344 to=to, 8345 format=fmt, 8346 safe=safe, 8347 action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False), 8348 default=default, 8349 ) 8350 8351 def _parse_string_agg(self) -> exp.GroupConcat: 8352 if self._match(TokenType.DISTINCT): 8353 args: list[exp.Expr | None] = [ 8354 self.expression(exp.Distinct(expressions=[self._parse_disjunction()])) 8355 ] 8356 if self._match(TokenType.COMMA): 8357 args.extend(self._parse_csv(self._parse_disjunction)) 8358 else: 8359 args = self._parse_csv(self._parse_disjunction) # type: ignore 8360 8361 if self._match_text_seq("ON", "OVERFLOW"): 8362 # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior]) 8363 if self._match_text_seq("ERROR"): 8364 on_overflow: exp.Expr | None = exp.var("ERROR") 8365 else: 8366 self._match_text_seq("TRUNCATE") 8367 on_overflow = self.expression( 8368 exp.OverflowTruncateBehavior( 8369 this=self._parse_string(), 8370 with_count=( 8371 self._match_text_seq("WITH", "COUNT") 8372 or not self._match_text_seq("WITHOUT", "COUNT") 8373 ), 8374 ) 8375 ) 8376 else: 8377 on_overflow = None 8378 8379 index = self._index 8380 if not self._match(TokenType.R_PAREN) and args: 8381 # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]]) 8382 # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n]) 8383 # The order is parsed through `this` as a canonicalization for WITHIN GROUPs 8384 args[0] = self._parse_limit(this=self._parse_order(this=args[0])) 8385 return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1))) 8386 8387 # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]). 8388 # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that 8389 # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them. 8390 if not self._match_text_seq("WITHIN", "GROUP"): 8391 self._retreat(index) 8392 return self.validate_expression(exp.GroupConcat.from_arg_list(args), args) 8393 8394 # The corresponding match_r_paren will be called in parse_function (caller) 8395 self._match_l_paren() 8396 8397 return self.expression( 8398 exp.GroupConcat( 8399 this=self._parse_order(this=seq_get(args, 0)), 8400 separator=seq_get(args, 1), 8401 on_overflow=on_overflow, 8402 ) 8403 ) 8404 8405 def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None: 8406 this = self._parse_bitwise() 8407 8408 if self._match(TokenType.USING): 8409 to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name()) 8410 elif self._match(TokenType.COMMA): 8411 to = self._parse_char_charset(self._parse_types()) 8412 else: 8413 to = None 8414 8415 return self.build_cast(strict=strict, this=this, to=to, safe=safe) 8416 8417 def _parse_char_charset(self, to: exp.Expr | None) -> exp.Expr | None: 8418 # MySQL: CHAR[(N)] CHARACTER SET charset_name, the length is kept 8419 if ( 8420 isinstance(to, exp.DataType) 8421 and to.this == exp.DType.CHAR 8422 and (self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")) 8423 ): 8424 to.set("this", exp.DType.CHARACTER_SET) 8425 to.set("kind", self._parse_var_or_string()) 8426 return to 8427 8428 def _parse_xml_element(self) -> exp.XMLElement: 8429 if self._match_text_seq("EVALNAME"): 8430 evalname = True 8431 this = self._parse_bitwise() 8432 else: 8433 evalname = None 8434 self._match_text_seq("NAME") 8435 this = self._parse_id_var() 8436 8437 return self.expression( 8438 exp.XMLElement( 8439 this=this, 8440 expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise), 8441 evalname=evalname, 8442 ) 8443 ) 8444 8445 def _parse_xml_table(self) -> exp.XMLTable: 8446 namespaces = None 8447 passing = None 8448 columns = None 8449 8450 if self._match_text_seq("XMLNAMESPACES", "("): 8451 namespaces = self._parse_xml_namespace() 8452 self._match_text_seq(")", ",") 8453 8454 this = self._parse_string() 8455 8456 if self._match_text_seq("PASSING"): 8457 # The BY VALUE keywords are optional and are provided for semantic clarity 8458 self._match_text_seq("BY", "VALUE") 8459 passing = self._parse_csv(self._parse_column) 8460 8461 by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF") 8462 8463 if self._match_text_seq("COLUMNS"): 8464 columns = self._parse_csv(self._parse_field_def) 8465 8466 return self.expression( 8467 exp.XMLTable( 8468 this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref 8469 ) 8470 ) 8471 8472 def _parse_xml_namespace(self) -> list[exp.XMLNamespace]: 8473 namespaces = [] 8474 8475 while True: 8476 if self._match(TokenType.DEFAULT): 8477 uri = self._parse_string() 8478 else: 8479 uri = self._parse_alias(self._parse_string()) 8480 namespaces.append(self.expression(exp.XMLNamespace(this=uri))) 8481 if not self._match(TokenType.COMMA): 8482 break 8483 8484 return namespaces 8485 8486 def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None: 8487 args = self._parse_csv(self._parse_disjunction) 8488 8489 if len(args) < 3: 8490 return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1))) 8491 8492 return self.expression(exp.DecodeCase(expressions=args)) 8493 8494 def _parse_json_key_value(self) -> exp.JSONKeyValue | None: 8495 self._match_text_seq("KEY") 8496 key = self._parse_column() 8497 self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS) 8498 self._match_text_seq("VALUE") 8499 value = self._parse_bitwise() 8500 8501 if not key and not value: 8502 return None 8503 return self.expression(exp.JSONKeyValue(this=key, expression=value)) 8504 8505 def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None: 8506 if not this or not self._match_text_seq("FORMAT", "JSON"): 8507 return this 8508 8509 return self.expression(exp.FormatJson(this=this)) 8510 8511 def _parse_on_condition(self) -> exp.OnCondition | None: 8512 # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS) 8513 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR: 8514 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8515 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8516 else: 8517 error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS) 8518 empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS) 8519 8520 null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS) 8521 8522 if not empty and not error and not null: 8523 return None 8524 8525 return self.expression(exp.OnCondition(empty=empty, error=error, null=null)) 8526 8527 def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None: 8528 # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL) 8529 for value in values: 8530 if self._match_text_seq(value, "ON", on): 8531 return f"{value} ON {on}" 8532 8533 index = self._index 8534 if self._match(TokenType.DEFAULT): 8535 default_value = self._parse_bitwise() 8536 if self._match_text_seq("ON", on): 8537 return default_value 8538 8539 self._retreat(index) 8540 8541 return None 8542 8543 @t.overload 8544 def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ... 8545 8546 @t.overload 8547 def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ... 8548 8549 def _parse_json_object(self, agg=False): 8550 star = self._parse_star() 8551 expressions = ( 8552 [star] 8553 if star 8554 else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value())) 8555 ) 8556 null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT") 8557 8558 unique_keys = None 8559 if self._match_text_seq("WITH", "UNIQUE"): 8560 unique_keys = True 8561 elif self._match_text_seq("WITHOUT", "UNIQUE"): 8562 unique_keys = False 8563 8564 self._match_text_seq("KEYS") 8565 8566 return_type = self._match_text_seq("RETURNING") and self._parse_format_json( 8567 self._parse_type() 8568 ) 8569 encoding = self._match_text_seq("ENCODING") and self._parse_var() 8570 8571 return self.expression( 8572 (exp.JSONObjectAgg if agg else exp.JSONObject)( 8573 expressions=expressions, 8574 null_handling=null_handling, 8575 unique_keys=unique_keys, 8576 return_type=return_type, 8577 encoding=encoding, 8578 ) 8579 ) 8580 8581 # Note: this is currently incomplete; it only implements the "JSON_value_column" part 8582 def _parse_json_column_def(self) -> exp.JSONColumnDef: 8583 if not self._match_text_seq("NESTED"): 8584 this = self._parse_id_var() 8585 ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY) 8586 kind = self._parse_types(allow_identifiers=False) 8587 nested = None 8588 else: 8589 this = None 8590 ordinality = None 8591 kind = None 8592 nested = True 8593 8594 format_json = self._match_text_seq("FORMAT", "JSON") 8595 path = self._match_text_seq("PATH") and self._parse_string() 8596 nested_schema = nested and self._parse_json_schema() 8597 8598 return self.expression( 8599 exp.JSONColumnDef( 8600 this=this, 8601 kind=kind, 8602 path=path, 8603 nested_schema=nested_schema, 8604 ordinality=ordinality, 8605 format_json=format_json, 8606 ) 8607 ) 8608 8609 def _parse_json_schema(self) -> exp.JSONSchema: 8610 self._match_text_seq("COLUMNS") 8611 return self.expression( 8612 exp.JSONSchema( 8613 expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True) 8614 ) 8615 ) 8616 8617 def _parse_json_table(self) -> exp.JSONTable: 8618 this = self._parse_format_json(self._parse_bitwise()) 8619 path = self._match(TokenType.COMMA) and self._parse_string() 8620 error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL") 8621 empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL") 8622 schema = self._parse_json_schema() 8623 8624 return exp.JSONTable( 8625 this=this, 8626 schema=schema, 8627 path=path, 8628 error_handling=error_handling, 8629 empty_handling=empty_handling, 8630 ) 8631 8632 def _parse_match_against(self) -> exp.MatchAgainst: 8633 if self._match_text_seq("TABLE"): 8634 # parse SingleStore MATCH(TABLE ...) syntax 8635 # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/ 8636 expressions = [] 8637 table = self._parse_table() 8638 if table: 8639 expressions = [table] 8640 else: 8641 expressions = self._parse_csv(self._parse_column) 8642 8643 self._match_text_seq(")", "AGAINST", "(") 8644 8645 this = self._parse_string() 8646 8647 if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"): 8648 modifier = "IN NATURAL LANGUAGE MODE" 8649 if self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8650 modifier = f"{modifier} WITH QUERY EXPANSION" 8651 elif self._match_text_seq("IN", "BOOLEAN", "MODE"): 8652 modifier = "IN BOOLEAN MODE" 8653 elif self._match_text_seq("WITH", "QUERY", "EXPANSION"): 8654 modifier = "WITH QUERY EXPANSION" 8655 else: 8656 modifier = None 8657 8658 return self.expression( 8659 exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier) 8660 ) 8661 8662 # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16 8663 def _parse_open_json(self) -> exp.OpenJSON: 8664 this = self._parse_bitwise() 8665 path = self._match(TokenType.COMMA) and self._parse_string() 8666 8667 def _parse_open_json_column_def() -> exp.OpenJSONColumnDef: 8668 this = self._parse_field(any_token=True) 8669 kind = self._parse_types() 8670 path = self._parse_string() 8671 as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON) 8672 8673 return self.expression( 8674 exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json) 8675 ) 8676 8677 expressions = None 8678 if self._match_pair(TokenType.R_PAREN, TokenType.WITH): 8679 self._match_l_paren() 8680 expressions = self._parse_csv(_parse_open_json_column_def) 8681 8682 return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions)) 8683 8684 def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition: 8685 args = self._parse_csv(self._parse_bitwise) 8686 8687 if self._match(TokenType.IN): 8688 return self.expression( 8689 exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0)) 8690 ) 8691 8692 if haystack_first: 8693 haystack = seq_get(args, 0) 8694 needle = seq_get(args, 1) 8695 else: 8696 haystack = seq_get(args, 1) 8697 needle = seq_get(args, 0) 8698 8699 return self.expression( 8700 exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2)) 8701 ) 8702 8703 def _parse_join_hint(self, func_name: str) -> exp.JoinHint: 8704 args = self._parse_csv(self._parse_table) 8705 return exp.JoinHint(this=func_name.upper(), expressions=args) 8706 8707 def _parse_substring(self) -> exp.Substring: 8708 # Postgres supports the form: substring(string [from int] [for int]) 8709 # (despite being undocumented, the reverse order also works) 8710 # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6 8711 8712 args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise)) 8713 8714 start, length = None, None 8715 8716 while self._curr: 8717 if self._match(TokenType.FROM): 8718 start = self._parse_bitwise() 8719 elif self._match(TokenType.FOR): 8720 if not start: 8721 start = exp.Literal.number(1) 8722 length = self._parse_bitwise() 8723 else: 8724 break 8725 8726 if start: 8727 args.append(start) 8728 if length: 8729 args.append(length) 8730 8731 return self.validate_expression(exp.Substring.from_arg_list(args), args) 8732 8733 def _parse_trim(self) -> exp.Trim: 8734 # https://www.w3resource.com/sql/character-functions/trim.php 8735 # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html 8736 8737 position = None 8738 collation = None 8739 expression = None 8740 8741 if self._match_texts(self.TRIM_TYPES): 8742 position = self._prev.text.upper() 8743 8744 this = self._parse_bitwise() 8745 if self._match_set((TokenType.FROM, TokenType.COMMA)): 8746 invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST 8747 expression = self._parse_bitwise() 8748 8749 if invert_order: 8750 this, expression = expression, this 8751 8752 if self._match(TokenType.COLLATE): 8753 collation = self._parse_bitwise() 8754 8755 return self.expression( 8756 exp.Trim(this=this, position=position, expression=expression, collation=collation) 8757 ) 8758 8759 def _parse_window_clause(self) -> list[exp.Expr] | None: 8760 return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None 8761 8762 def _parse_named_window(self) -> exp.Expr | None: 8763 return self._parse_window(self._parse_id_var(), alias=True) 8764 8765 def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None: 8766 if self._curr.token_type == TokenType.VAR: 8767 if self._match_text_seq("IGNORE", "NULLS"): 8768 return self.expression(exp.IgnoreNulls(this=this)) 8769 if self._match_text_seq("RESPECT", "NULLS"): 8770 return self.expression(exp.RespectNulls(this=this)) 8771 return this 8772 8773 def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None: 8774 if self._match(TokenType.HAVING): 8775 self._match_texts(("MAX", "MIN")) 8776 max = self._prev.text.upper() != "MIN" 8777 return self.expression( 8778 exp.HavingMax(this=this, expression=self._parse_column(), max=max) 8779 ) 8780 8781 return this 8782 8783 def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None: 8784 func = this 8785 comments = func.comments if isinstance(func, exp.Expr) else None 8786 8787 # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html 8788 if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue): 8789 if self._match_text_seq("FROM", "FIRST"): 8790 this.set("from_first", True) 8791 elif self._match_text_seq("FROM", "LAST"): 8792 this.set("from_first", False) 8793 8794 # T-SQL allows the OVER (...) syntax after WITHIN GROUP. 8795 # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16 8796 if self._match_text_seq("WITHIN", "GROUP"): 8797 order = self._parse_wrapped(self._parse_order) 8798 this = self.expression(exp.WithinGroup(this=this, expression=order)) 8799 8800 if self._match_pair(TokenType.FILTER, TokenType.L_PAREN): 8801 self._match(TokenType.WHERE) 8802 this = self.expression( 8803 exp.Filter(this=this, expression=self._parse_where(skip_where_token=True)) 8804 ) 8805 self._match_r_paren() 8806 8807 # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER 8808 # Some dialects choose to implement and some do not. 8809 # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html 8810 8811 # There is some code above in _parse_lambda that handles 8812 # SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ... 8813 8814 # The below changes handle 8815 # SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ... 8816 8817 # Oracle allows both formats 8818 # (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html) 8819 # and Snowflake chose to do the same for familiarity 8820 # https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes 8821 if isinstance(this, exp.AggFunc): 8822 ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls) 8823 8824 if ignore_respect and ignore_respect is not this: 8825 ignore_respect.replace(ignore_respect.this) 8826 this = self.expression(ignore_respect.__class__(this=this)) 8827 8828 this = self._parse_respect_or_ignore_nulls(this) 8829 8830 # bigquery select from window x AS (partition by ...) 8831 if alias: 8832 over = None 8833 self._match(TokenType.ALIAS) 8834 elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS): 8835 return this 8836 else: 8837 over = self._prev.text.upper() 8838 8839 if comments and isinstance(func, exp.Expr): 8840 func.pop_comments() 8841 8842 if not self._match(TokenType.L_PAREN): 8843 return self.expression( 8844 exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments 8845 ) 8846 8847 window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS) 8848 8849 first: bool | None = True if self._match(TokenType.FIRST) else None 8850 if self._match_text_seq("LAST"): 8851 first = False 8852 8853 partition, order = self._parse_partition_and_order() 8854 kind = ( 8855 self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS") 8856 ) and self._prev.text 8857 8858 if kind: 8859 self._match(TokenType.BETWEEN) 8860 start = self._parse_window_spec() 8861 8862 end = self._parse_window_spec() if self._match(TokenType.AND) else {} 8863 exclude = ( 8864 self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS) 8865 if self._match_text_seq("EXCLUDE") 8866 else None 8867 ) 8868 8869 spec = self.expression( 8870 exp.WindowSpec( 8871 kind=kind, 8872 start=start["value"], 8873 start_side=start["side"], 8874 end=end.get("value"), 8875 end_side=end.get("side"), 8876 exclude=exclude, 8877 ) 8878 ) 8879 else: 8880 spec = None 8881 8882 self._match_r_paren() 8883 8884 window = self.expression( 8885 exp.Window( 8886 this=this, 8887 partition_by=partition, 8888 order=order, 8889 spec=spec, 8890 alias=window_alias, 8891 over=over, 8892 first=first, 8893 ), 8894 comments=comments, 8895 ) 8896 8897 # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...) 8898 if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False): 8899 return self._parse_window(window, alias=alias) 8900 8901 return window 8902 8903 def _parse_partition_and_order( 8904 self, 8905 ) -> tuple[list[exp.Expr], exp.Expr | None]: 8906 return self._parse_partition_by(), self._parse_order() 8907 8908 def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]: 8909 self._match(TokenType.BETWEEN) 8910 8911 return { 8912 "value": ( 8913 (self._match_text_seq("UNBOUNDED") and "UNBOUNDED") 8914 or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW") 8915 or self._parse_bitwise() 8916 ), 8917 "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None, 8918 } 8919 8920 def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None: 8921 # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses) 8922 # so this section tries to parse the clause version and if it fails, it treats the token 8923 # as an identifier (alias) 8924 if self._can_parse_limit_or_offset(): 8925 return this 8926 8927 # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the 8928 # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it. 8929 if self._can_parse_named_window(): 8930 return this 8931 8932 any_token = self._match(TokenType.ALIAS) 8933 comments = self._prev_comments 8934 8935 if explicit and not any_token: 8936 return this 8937 8938 if self._match(TokenType.L_PAREN): 8939 aliases = self.expression( 8940 exp.Aliases( 8941 this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token)) 8942 ), 8943 comments=comments, 8944 ) 8945 self._match_r_paren(aliases) 8946 return aliases 8947 8948 alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or ( 8949 self.STRING_ALIASES and self._parse_string_as_identifier() 8950 ) 8951 8952 if alias: 8953 comments.extend(alias.pop_comments()) 8954 this = self.expression(exp.Alias(this=this, alias=alias), comments=comments) 8955 column = this.this 8956 8957 # Moves the comment next to the alias in `expr /* comment */ AS alias` 8958 if not this.comments and column and column.comments: 8959 this.comments = column.pop_comments() 8960 8961 return this 8962 8963 def _parse_id_var( 8964 self, 8965 any_token: bool = True, 8966 tokens: t.Collection[TokenType] | None = None, 8967 ) -> exp.Expr | None: 8968 expression = self._parse_identifier() 8969 if not expression and ( 8970 (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS) 8971 ): 8972 quoted = self._prev.token_type == TokenType.STRING 8973 expression = self._identifier_expression(quoted=quoted) 8974 8975 return expression 8976 8977 def _parse_string(self) -> exp.Expr | None: 8978 if self._match_set(self.STRING_PARSERS): 8979 return self.STRING_PARSERS[self._prev.token_type](self, self._prev) 8980 return self._parse_placeholder() 8981 8982 def _parse_string_as_identifier(self) -> exp.Identifier | None: 8983 if not self._match(TokenType.STRING): 8984 return None 8985 output = exp.to_identifier(self._prev.text, quoted=True) 8986 output.update_positions(self._prev) 8987 return output 8988 8989 def _parse_number(self) -> exp.Expr | None: 8990 if self._match_set(self.NUMERIC_PARSERS): 8991 return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev) 8992