Edit on GitHub

sqlglot.parser

    1from __future__ import annotations
    2
    3import itertools
    4import logging
    5import re
    6import typing as t
    7from builtins import type as Type
    8from collections import defaultdict
    9from collections.abc import Sequence
   10
   11from sqlglot import exp
   12from sqlglot._typing import F
   13from sqlglot.errors import (
   14    ErrorLevel,
   15    ParseError,
   16    TokenError,
   17    concat_messages,
   18    highlight_sql,
   19    merge_errors,
   20)
   21from sqlglot.expressions import apply_index_offset
   22from sqlglot.helper import ensure_list, i64, seq_get
   23from sqlglot.optimizer.scope import find_in_scope
   24from sqlglot.time import format_time
   25from sqlglot.tokens import Token, Tokenizer, TokenType
   26from sqlglot.trie import TrieResult, in_trie, new_trie
   27
   28if t.TYPE_CHECKING:
   29    from re import Pattern
   30
   31    from sqlglot._typing import BuilderArgs, E
   32    from sqlglot.dialects.dialect import Dialect, DialectType
   33    from sqlglot.expressions import ExpOrStr
   34
   35    T = t.TypeVar("T")
   36    TCeilFloor = t.TypeVar("TCeilFloor", exp.Ceil, exp.Floor)
   37
   38logger = logging.getLogger("sqlglot")
   39
   40OPTIONS_TYPE = dict[str, Sequence[t.Union[Sequence[str], str]]]
   41
   42# Excludes bare strings, which are also collections of strings, so that a single keyword
   43# can't accidentally be matched with substring semantics (e.g. _match_texts("FOO"))
   44TEXTS_TYPE = t.Union[tuple[str, ...], list[str], t.AbstractSet[str], t.Mapping[str, t.Any]]
   45
   46# Used to detect alphabetical characters and +/- in timestamp literals
   47TIME_ZONE_RE: Pattern[str] = re.compile(r":.*?[a-zA-Z\+\-]")
   48
   49
   50def build_var_map(args: BuilderArgs) -> exp.StarMap | exp.VarMap:
   51    if len(args) == 1 and args[0].is_star:
   52        return exp.StarMap(this=args[0])
   53
   54    keys: list[ExpOrStr] = []
   55    values: list[ExpOrStr] = []
   56    for i in range(0, len(args), 2):
   57        keys.append(args[i])
   58        values.append(args[i + 1])
   59
   60    return exp.VarMap(keys=exp.array(*keys, copy=False), values=exp.array(*values, copy=False))
   61
   62
   63def build_like(args: BuilderArgs) -> exp.Escape | exp.Like:
   64    like = exp.Like(this=seq_get(args, 1), expression=seq_get(args, 0))
   65    return exp.Escape(this=like, expression=seq_get(args, 2)) if len(args) > 2 else like
   66
   67
   68def binary_range_parser(
   69    expr_type: Type[exp.Expr], reverse_args: bool = False
   70) -> t.Callable[[Parser, exp.Expr | None], exp.Expr | None]:
   71    def _parse_binary_range(self: Parser, this: exp.Expr | None) -> exp.Expr | None:
   72        expression = self._parse_bitwise()
   73        if reverse_args:
   74            this, expression = expression, this
   75        return self._parse_escape(self.expression(expr_type(this=this, expression=expression)))
   76
   77    return _parse_binary_range
   78
   79
   80def build_logarithm(args: BuilderArgs, dialect: Dialect) -> exp.Func:
   81    # Default argument order is base, expression
   82    this = seq_get(args, 0)
   83    expression = seq_get(args, 1)
   84
   85    if expression:
   86        if not dialect.LOG_BASE_FIRST:
   87            this, expression = expression, this
   88        return exp.Log(this=this, expression=expression)
   89
   90    return (exp.Ln if dialect.parser_class.LOG_DEFAULTS_TO_LN else exp.Log)(this=this)
   91
   92
   93def build_hex(args: BuilderArgs, dialect: Dialect) -> exp.Hex | exp.LowerHex:
   94    arg = seq_get(args, 0)
   95    return exp.LowerHex(this=arg) if dialect.HEX_LOWERCASE else exp.Hex(this=arg)
   96
   97
   98def build_lower(args: BuilderArgs) -> exp.Lower | exp.Hex:
   99    # LOWER(HEX(..)) can be simplified to LowerHex to simplify its transpilation
  100    arg = seq_get(args, 0)
  101    return exp.LowerHex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Lower(this=arg)
  102
  103
  104def build_upper(args: BuilderArgs) -> exp.Upper | exp.Hex:
  105    # UPPER(HEX(..)) can be simplified to Hex to simplify its transpilation
  106    arg = seq_get(args, 0)
  107    return exp.Hex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Upper(this=arg)
  108
  109
  110def build_extract_json_with_path(
  111    expr_type: Type[E],
  112) -> t.Callable[[BuilderArgs, Dialect], E]:
  113    def _builder(args: BuilderArgs, dialect: Dialect) -> E:
  114        expression = expr_type(
  115            this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1))
  116        )
  117        if len(args) > 2 and expr_type is exp.JSONExtract:
  118            expression.set("expressions", args[2:])
  119        if expr_type is exp.JSONExtractScalar:
  120            expression.set("scalar_only", dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY)
  121
  122        return expression
  123
  124    return _builder
  125
  126
  127def build_mod(args: BuilderArgs) -> exp.Mod:
  128    this = seq_get(args, 0)
  129    expression = seq_get(args, 1)
  130
  131    # Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7
  132    this = exp.Paren(this=this) if isinstance(this, exp.Binary) else this
  133    expression = exp.Paren(this=expression) if isinstance(expression, exp.Binary) else expression
  134
  135    return exp.Mod(this=this, expression=expression)
  136
  137
  138def build_pad(args: BuilderArgs, is_left: bool = True):
  139    return exp.Pad(
  140        this=seq_get(args, 0),
  141        expression=seq_get(args, 1),
  142        fill_pattern=seq_get(args, 2),
  143        is_left=is_left,
  144    )
  145
  146
  147def build_array_constructor(
  148    exp_class: Type[E], args: list[t.Any], bracket_kind: TokenType, dialect: Dialect
  149) -> exp.Expr:
  150    array_exp = exp_class(expressions=args)
  151
  152    if exp_class == exp.Array and dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS:
  153        array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET)
  154
  155    return array_exp
  156
  157
  158def build_convert_timezone(
  159    args: BuilderArgs, default_source_tz: str | None = None
  160) -> exp.ConvertTimezone | exp.Anonymous:
  161    if len(args) == 2:
  162        source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None
  163        return exp.ConvertTimezone(
  164            source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1)
  165        )
  166
  167    return exp.ConvertTimezone.from_arg_list(args)
  168
  169
  170def build_trim(args: BuilderArgs, is_left: bool = True, reverse_args: bool = False) -> exp.Trim:
  171    this, expression = seq_get(args, 0), seq_get(args, 1)
  172
  173    if expression and reverse_args:
  174        this, expression = expression, this
  175
  176    return exp.Trim(this=this, expression=expression, position="LEADING" if is_left else "TRAILING")
  177
  178
  179def build_coalesce(
  180    args: BuilderArgs, is_nvl: bool | None = None, is_null: bool | None = None
  181) -> exp.Coalesce:
  182    return exp.Coalesce(this=seq_get(args, 0), expressions=args[1:], is_nvl=is_nvl, is_null=is_null)
  183
  184
  185def build_locate_strposition(args: BuilderArgs) -> exp.StrPosition:
  186    return exp.StrPosition(
  187        this=seq_get(args, 1),
  188        substr=seq_get(args, 0),
  189        position=seq_get(args, 2),
  190    )
  191
  192
  193def build_array_append(args: BuilderArgs, dialect: Dialect) -> exp.ArrayAppend:
  194    """
  195    Builds ArrayAppend with NULL propagation semantics based on the dialect configuration.
  196
  197    Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL.
  198    Others (DuckDB, PostgreSQL) create a new single-element array instead.
  199
  200    Args:
  201        args: Function arguments [array, element]
  202        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
  203
  204    Returns:
  205        ArrayAppend expression with appropriate null_propagation flag
  206    """
  207    return exp.ArrayAppend(
  208        this=seq_get(args, 0),
  209        expression=seq_get(args, 1),
  210        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,
  211    )
  212
  213
  214def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend:
  215    """
  216    Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration.
  217
  218    Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL.
  219    Others (DuckDB, PostgreSQL) create a new single-element array instead.
  220
  221    Args:
  222        args: Function arguments [array, element]
  223        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
  224
  225    Returns:
  226        ArrayPrepend expression with appropriate null_propagation flag
  227    """
  228    return exp.ArrayPrepend(
  229        this=seq_get(args, 0),
  230        expression=seq_get(args, 1),
  231        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,
  232    )
  233
  234
  235def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat:
  236    """
  237    Builds ArrayConcat with NULL propagation semantics based on the dialect configuration.
  238
  239    Some dialects (Redshift, Snowflake) return NULL when any input array is NULL.
  240    Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation.
  241
  242    Args:
  243        args: Function arguments [array1, array2, ...] (variadic)
  244        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
  245
  246    Returns:
  247        ArrayConcat expression with appropriate null_propagation flag
  248    """
  249    return exp.ArrayConcat(
  250        this=seq_get(args, 0),
  251        expressions=args[1:],
  252        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,
  253    )
  254
  255
  256def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove:
  257    """
  258    Builds ArrayRemove with NULL propagation semantics based on the dialect configuration.
  259
  260    Some dialects (Snowflake) return NULL when the removal value is NULL.
  261    Others (DuckDB) may return empty array due to NULL comparison semantics.
  262
  263    Args:
  264        args: Function arguments [array, value_to_remove]
  265        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
  266
  267    Returns:
  268        ArrayRemove expression with appropriate null_propagation flag
  269    """
  270    return exp.ArrayRemove(
  271        this=seq_get(args, 0),
  272        expression=seq_get(args, 1),
  273        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,
  274    )
  275
  276
  277def _resolve_dialect(dialect: DialectType) -> Dialect:
  278    from sqlglot.dialects.dialect import Dialect
  279
  280    return Dialect.get_or_raise(dialect)
  281
  282
  283def _unpivot_target(expr: exp.Expr) -> exp.Expr:
  284    # UNPIVOT's pre-FOR values and FOR field are new output names, not column references.
  285    if isinstance(expr, exp.Column) and not expr.table:
  286        return expr.this
  287    if isinstance(expr, exp.Tuple):
  288        expr.set("expressions", [_unpivot_target(e) for e in expr.expressions])
  289    return expr
  290
  291
  292# Builders for the JSON `->` / `->>` / `#>` / `#>>` / `?` operators, shared between
  293# COLUMN_OPERATORS (accessor-tier dialects) and JSON_OPERATORS (Postgres/DuckDB's
  294# binary-operator tier).
  295def build_json_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONExtract:
  296    return self.expression(
  297        exp.JSONExtract(
  298            this=this,
  299            expression=self.dialect.to_json_path(path),
  300            only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
  301        )
  302    )
  303
  304
  305def build_json_extract_scalar(
  306    self: Parser, this: exp.Expr, path: exp.Expr
  307) -> exp.JSONExtractScalar:
  308    return self.expression(
  309        exp.JSONExtractScalar(
  310            this=this,
  311            expression=self.dialect.to_json_path(path),
  312            only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
  313            scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY,
  314        )
  315    )
  316
  317
  318def build_jsonb_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONBExtract:
  319    return self.expression(exp.JSONBExtract(this=this, expression=path))
  320
  321
  322def build_jsonb_extract_scalar(
  323    self: Parser, this: exp.Expr, path: exp.Expr
  324) -> exp.JSONBExtractScalar:
  325    return self.expression(exp.JSONBExtractScalar(this=this, expression=path))
  326
  327
  328def build_jsonb_contains_top_key(
  329    self: Parser, this: exp.Expr, key: exp.Expr
  330) -> exp.JSONBContainsTopKey:
  331    return self.expression(exp.JSONBContainsTopKey(this=this, expression=key))
  332
  333
  334SENTINEL_NONE: Token = Token(TokenType.SENTINEL, "SENTINEL")
  335
  336
  337class Parser:
  338    """
  339    Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.
  340
  341    Args:
  342        error_level: The desired error level.
  343            Default: ErrorLevel.IMMEDIATE
  344        error_message_context: The amount of context to capture from a query string when displaying
  345            the error message (in number of characters).
  346            Default: 100
  347        max_errors: Maximum number of error messages to include in a raised ParseError.
  348            This is only relevant if error_level is ErrorLevel.RAISE.
  349            Default: 3
  350        max_nodes: Maximum number of AST nodes to prevent memory exhaustion.
  351            Set to -1 (default) to disable the check.
  352    """
  353
  354    __slots__ = (
  355        "error_level",
  356        "error_message_context",
  357        "max_errors",
  358        "max_nodes",
  359        "dialect",
  360        "sql",
  361        "errors",
  362        "_tokens",
  363        "_index",
  364        "_curr",
  365        "_next",
  366        "_prev",
  367        "_prev_comments",
  368        "_pipe_cte_counter",
  369        "_chunks",
  370        "_chunk_index",
  371        "_tokens_size",
  372        "_node_count",
  373    )
  374
  375    FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = {
  376        **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()},
  377        **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce),
  378        "ARRAY": lambda args, dialect: exp.Array(expressions=args),
  379        "ARRAYAGG": lambda args, dialect: exp.ArrayAgg(
  380            this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None
  381        ),
  382        "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg(
  383            this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None
  384        ),
  385        "ARRAY_APPEND": build_array_append,
  386        "ARRAY_CAT": build_array_concat,
  387        "ARRAY_CONCAT": build_array_concat,
  388        "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args),
  389        "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args),
  390        "ARRAY_PREPEND": build_array_prepend,
  391        "ARRAY_REMOVE": build_array_remove,
  392        "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True),
  393        "CONCAT": lambda args, dialect: exp.Concat(
  394            expressions=args,
  395            safe=not dialect.STRICT_STRING_CONCAT,
  396            coalesce=dialect.CONCAT_COALESCE,
  397        ),
  398        "CONCAT_WS": lambda args, dialect: exp.ConcatWs(
  399            expressions=args,
  400            safe=not dialect.STRICT_STRING_CONCAT,
  401            coalesce=dialect.CONCAT_WS_COALESCE,
  402        ),
  403        "CONVERT_TIMEZONE": build_convert_timezone,
  404        "DATE_TO_DATE_STR": lambda args: exp.Cast(
  405            this=seq_get(args, 0),
  406            to=exp.DataType(this=exp.DType.TEXT),
  407        ),
  408        "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray(
  409            start=seq_get(args, 0),
  410            end=seq_get(args, 1),
  411            step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")),
  412        ),
  413        "GENERATE_UUID": lambda args, dialect: exp.Uuid(
  414            is_string=dialect.UUID_IS_STRING_TYPE or None
  415        ),
  416        "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)),
  417        "GREATEST": lambda args, dialect: exp.Greatest(
  418            this=seq_get(args, 0),
  419            expressions=args[1:],
  420            ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS,
  421        ),
  422        "LEAST": lambda args, dialect: exp.Least(
  423            this=seq_get(args, 0),
  424            expressions=args[1:],
  425            ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS,
  426        ),
  427        "HEX": build_hex,
  428        "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract),
  429        "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar),
  430        "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar),
  431        "JSON_KEYS": lambda args, dialect: exp.JSONKeys(
  432            this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1))
  433        ),
  434        "LIKE": build_like,
  435        "LOG": build_logarithm,
  436        "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)),
  437        "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)),
  438        "LOWER": build_lower,
  439        "LPAD": lambda args: build_pad(args),
  440        "LEFTPAD": lambda args: build_pad(args),
  441        "LTRIM": lambda args: build_trim(args),
  442        "MOD": build_mod,
  443        "RIGHTPAD": lambda args: build_pad(args, is_left=False),
  444        "RPAD": lambda args: build_pad(args, is_left=False),
  445        "RTRIM": lambda args: build_trim(args, is_left=False),
  446        "SCOPE_RESOLUTION": lambda args: (
  447            exp.ScopeResolution(expression=seq_get(args, 0))
  448            if len(args) != 2
  449            else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1))
  450        ),
  451        "STRPOS": exp.StrPosition.from_arg_list,
  452        "CHARINDEX": lambda args: build_locate_strposition(args),
  453        "INSTR": exp.StrPosition.from_arg_list,
  454        "LOCATE": lambda args: build_locate_strposition(args),
  455        "TIME_TO_TIME_STR": lambda args: exp.Cast(
  456            this=seq_get(args, 0),
  457            to=exp.DataType(this=exp.DType.TEXT),
  458        ),
  459        "TO_HEX": build_hex,
  460        "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring(
  461            this=exp.Cast(
  462                this=seq_get(args, 0),
  463                to=exp.DataType(this=exp.DType.TEXT),
  464            ),
  465            start=exp.Literal.number(1),
  466            length=exp.Literal.number(10),
  467        ),
  468        "UNNEST": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))),
  469        "UPPER": build_upper,
  470        "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None),
  471        "UUID_STRING": lambda args, dialect: exp.Uuid(
  472            this=seq_get(args, 0),
  473            name=seq_get(args, 1),
  474            is_string=dialect.UUID_IS_STRING_TYPE or None,
  475        ),
  476        "VAR_MAP": build_var_map,
  477    }
  478
  479    NO_PAREN_FUNCTIONS: t.ClassVar[dict] = {
  480        TokenType.CURRENT_DATE: exp.CurrentDate,
  481        TokenType.CURRENT_DATETIME: exp.CurrentDate,
  482        TokenType.CURRENT_TIME: exp.CurrentTime,
  483        TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp,
  484        TokenType.CURRENT_USER: exp.CurrentUser,
  485        TokenType.CURRENT_ROLE: exp.CurrentRole,
  486    }
  487
  488    STRUCT_TYPE_TOKENS: t.ClassVar = {
  489        TokenType.NESTED,
  490        TokenType.OBJECT,
  491        TokenType.STRUCT,
  492        TokenType.UNION,
  493    }
  494
  495    NESTED_TYPE_TOKENS: t.ClassVar = {
  496        TokenType.ARRAY,
  497        TokenType.LIST,
  498        TokenType.LOWCARDINALITY,
  499        TokenType.MAP,
  500        TokenType.NULLABLE,
  501        TokenType.RANGE,
  502        *STRUCT_TYPE_TOKENS,
  503    }
  504
  505    ENUM_TYPE_TOKENS: t.ClassVar = {
  506        TokenType.DYNAMIC,
  507        TokenType.ENUM,
  508        TokenType.ENUM8,
  509        TokenType.ENUM16,
  510    }
  511
  512    AGGREGATE_TYPE_TOKENS: t.ClassVar = {
  513        TokenType.AGGREGATEFUNCTION,
  514        TokenType.SIMPLEAGGREGATEFUNCTION,
  515    }
  516
  517    TYPE_TOKENS: t.ClassVar = {
  518        TokenType.BIT,
  519        TokenType.BOOLEAN,
  520        TokenType.TINYINT,
  521        TokenType.UTINYINT,
  522        TokenType.SMALLINT,
  523        TokenType.USMALLINT,
  524        TokenType.INT,
  525        TokenType.UINT,
  526        TokenType.BIGINT,
  527        TokenType.UBIGINT,
  528        TokenType.BIGNUM,
  529        TokenType.INT128,
  530        TokenType.UINT128,
  531        TokenType.INT256,
  532        TokenType.UINT256,
  533        TokenType.MEDIUMINT,
  534        TokenType.UMEDIUMINT,
  535        TokenType.FIXEDSTRING,
  536        TokenType.FLOAT,
  537        TokenType.DOUBLE,
  538        TokenType.UDOUBLE,
  539        TokenType.CHAR,
  540        TokenType.NCHAR,
  541        TokenType.VARCHAR,
  542        TokenType.NVARCHAR,
  543        TokenType.BPCHAR,
  544        TokenType.TEXT,
  545        TokenType.MEDIUMTEXT,
  546        TokenType.LONGTEXT,
  547        TokenType.BLOB,
  548        TokenType.MEDIUMBLOB,
  549        TokenType.LONGBLOB,
  550        TokenType.BINARY,
  551        TokenType.VARBINARY,
  552        TokenType.JSON,
  553        TokenType.JSONB,
  554        TokenType.INTERVAL,
  555        TokenType.TINYBLOB,
  556        TokenType.TINYTEXT,
  557        TokenType.TIME,
  558        TokenType.TIMETZ,
  559        TokenType.TIME_NS,
  560        TokenType.TIMESTAMP,
  561        TokenType.TIMESTAMP_S,
  562        TokenType.TIMESTAMP_MS,
  563        TokenType.TIMESTAMP_NS,
  564        TokenType.TIMESTAMPTZ,
  565        TokenType.TIMESTAMPLTZ,
  566        TokenType.TIMESTAMPNTZ,
  567        TokenType.DATETIME,
  568        TokenType.DATETIME2,
  569        TokenType.DATETIME64,
  570        TokenType.SMALLDATETIME,
  571        TokenType.DATE,
  572        TokenType.DATE32,
  573        TokenType.INT4RANGE,
  574        TokenType.INT4MULTIRANGE,
  575        TokenType.INT8RANGE,
  576        TokenType.INT8MULTIRANGE,
  577        TokenType.NUMRANGE,
  578        TokenType.NUMMULTIRANGE,
  579        TokenType.TSRANGE,
  580        TokenType.TSMULTIRANGE,
  581        TokenType.TSTZRANGE,
  582        TokenType.TSTZMULTIRANGE,
  583        TokenType.DATERANGE,
  584        TokenType.DATEMULTIRANGE,
  585        TokenType.DECIMAL,
  586        TokenType.DECIMAL32,
  587        TokenType.DECIMAL64,
  588        TokenType.DECIMAL128,
  589        TokenType.DECIMAL256,
  590        TokenType.DECFLOAT,
  591        TokenType.UDECIMAL,
  592        TokenType.BIGDECIMAL,
  593        TokenType.UUID,
  594        TokenType.GEOGRAPHY,
  595        TokenType.GEOGRAPHYPOINT,
  596        TokenType.GEOMETRY,
  597        TokenType.POINT,
  598        TokenType.RING,
  599        TokenType.LINESTRING,
  600        TokenType.MULTILINESTRING,
  601        TokenType.POLYGON,
  602        TokenType.MULTIPOLYGON,
  603        TokenType.HLLSKETCH,
  604        TokenType.HSTORE,
  605        TokenType.PSEUDO_TYPE,
  606        TokenType.SUPER,
  607        TokenType.SERIAL,
  608        TokenType.SMALLSERIAL,
  609        TokenType.BIGSERIAL,
  610        TokenType.XML,
  611        TokenType.YEAR,
  612        TokenType.USERDEFINED,
  613        TokenType.MONEY,
  614        TokenType.SMALLMONEY,
  615        TokenType.ROWVERSION,
  616        TokenType.IMAGE,
  617        TokenType.VARIANT,
  618        TokenType.VECTOR,
  619        TokenType.VOID,
  620        TokenType.OBJECT,
  621        TokenType.OBJECT_IDENTIFIER,
  622        TokenType.INET,
  623        TokenType.IPADDRESS,
  624        TokenType.IPPREFIX,
  625        TokenType.IPV4,
  626        TokenType.IPV6,
  627        TokenType.UNKNOWN,
  628        TokenType.NOTHING,
  629        TokenType.NULL,
  630        TokenType.NAME,
  631        TokenType.TDIGEST,
  632        TokenType.DYNAMIC,
  633        *ENUM_TYPE_TOKENS,
  634        *NESTED_TYPE_TOKENS,
  635        *AGGREGATE_TYPE_TOKENS,
  636    }
  637
  638    SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = {
  639        TokenType.BIGINT: TokenType.UBIGINT,
  640        TokenType.INT: TokenType.UINT,
  641        TokenType.MEDIUMINT: TokenType.UMEDIUMINT,
  642        TokenType.SMALLINT: TokenType.USMALLINT,
  643        TokenType.TINYINT: TokenType.UTINYINT,
  644        TokenType.DECIMAL: TokenType.UDECIMAL,
  645        TokenType.DOUBLE: TokenType.UDOUBLE,
  646    }
  647
  648    SUBQUERY_PREDICATES: t.ClassVar = {
  649        TokenType.ANY: exp.Any,
  650        TokenType.ALL: exp.All,
  651        TokenType.EXISTS: exp.Exists,
  652        TokenType.SOME: exp.Any,
  653    }
  654
  655    SUBQUERY_TOKENS: t.ClassVar = {
  656        TokenType.SELECT,
  657        TokenType.WITH,
  658        TokenType.FROM,
  659    }
  660
  661    RESERVED_TOKENS: t.ClassVar = {
  662        *Tokenizer.SINGLE_TOKENS.values(),
  663        TokenType.SELECT,
  664    } - {TokenType.IDENTIFIER}
  665
  666    # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers,
  667    # string literals), so they must never be treated as keywords when matching by text
  668    TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset(
  669        {
  670            TokenType.BIT_STRING,
  671            TokenType.BYTE_STRING,
  672            TokenType.HEREDOC_STRING,
  673            TokenType.HEX_STRING,
  674            TokenType.IDENTIFIER,
  675            TokenType.NATIONAL_STRING,
  676            TokenType.RAW_STRING,
  677            TokenType.STRING,
  678            TokenType.UNICODE_STRING,
  679        }
  680    )
  681
  682    DB_CREATABLES: t.ClassVar = {
  683        TokenType.DATABASE,
  684        TokenType.DICTIONARY,
  685        TokenType.FILE_FORMAT,
  686        TokenType.MODEL,
  687        TokenType.NAMESPACE,
  688        TokenType.SCHEMA,
  689        TokenType.SEMANTIC_VIEW,
  690        TokenType.SEQUENCE,
  691        TokenType.SINK,
  692        TokenType.SOURCE,
  693        TokenType.STAGE,
  694        TokenType.STORAGE_INTEGRATION,
  695        TokenType.STREAMLIT,
  696        TokenType.TABLE,
  697        TokenType.TAG,
  698        TokenType.VIEW,
  699        TokenType.WAREHOUSE,
  700    }
  701
  702    CREATABLES: t.ClassVar = {
  703        TokenType.COLUMN,
  704        TokenType.CONSTRAINT,
  705        TokenType.FOREIGN_KEY,
  706        TokenType.FUNCTION,
  707        TokenType.INDEX,
  708        TokenType.PROCEDURE,
  709        TokenType.TRIGGER,
  710        TokenType.TYPE,
  711        *DB_CREATABLES,
  712    }
  713
  714    TRIGGER_EVENTS: t.ClassVar = {
  715        TokenType.INSERT,
  716        TokenType.UPDATE,
  717        TokenType.DELETE,
  718        TokenType.TRUNCATE,
  719    }
  720
  721    ALTERABLES: t.ClassVar = {
  722        TokenType.INDEX,
  723        TokenType.TABLE,
  724        TokenType.VIEW,
  725        TokenType.SESSION,
  726    }
  727
  728    # Tokens that can represent identifiers
  729    ID_VAR_TOKENS: t.ClassVar[set] = {
  730        TokenType.ALL,
  731        TokenType.ANALYZE,
  732        TokenType.ATTACH,
  733        TokenType.VAR,
  734        TokenType.ANTI,
  735        TokenType.APPLY,
  736        TokenType.ASC,
  737        TokenType.ASOF,
  738        TokenType.AUTO_INCREMENT,
  739        TokenType.BEGIN,
  740        TokenType.BPCHAR,
  741        TokenType.CACHE,
  742        TokenType.CASE,
  743        TokenType.COLLATE,
  744        TokenType.COMMAND,
  745        TokenType.COMMENT,
  746        TokenType.COMMIT,
  747        TokenType.CONSTRAINT,
  748        TokenType.COPY,
  749        TokenType.CUBE,
  750        TokenType.CURRENT_SCHEMA,
  751        TokenType.DECLARE,
  752        TokenType.DEFAULT,
  753        TokenType.DELETE,
  754        TokenType.DESC,
  755        TokenType.DESCRIBE,
  756        TokenType.DETACH,
  757        TokenType.DICTIONARY,
  758        TokenType.DIV,
  759        TokenType.END,
  760        TokenType.EXECUTE,
  761        TokenType.EXPORT,
  762        TokenType.ESCAPE,
  763        TokenType.FALSE,
  764        TokenType.FIRST,
  765        TokenType.FILE,
  766        TokenType.FILTER,
  767        TokenType.FINAL,
  768        TokenType.FORMAT,
  769        TokenType.FULL,
  770        TokenType.GET,
  771        TokenType.IDENTIFIER,
  772        TokenType.INOUT,
  773        TokenType.IS,
  774        TokenType.ISNULL,
  775        TokenType.INTERVAL,
  776        TokenType.KEEP,
  777        TokenType.KILL,
  778        TokenType.LEFT,
  779        TokenType.LIMIT,
  780        TokenType.LOAD,
  781        TokenType.LOCK,
  782        TokenType.MATCH,
  783        TokenType.MERGE,
  784        TokenType.NATURAL,
  785        TokenType.NEXT,
  786        TokenType.OFFSET,
  787        TokenType.OPERATOR,
  788        TokenType.ORDINALITY,
  789        TokenType.OUT,
  790        TokenType.OVER,
  791        TokenType.OVERLAPS,
  792        TokenType.OVERWRITE,
  793        TokenType.PARTITION,
  794        TokenType.PERCENT,
  795        TokenType.PIVOT,
  796        TokenType.PROJECTION,
  797        TokenType.PRAGMA,
  798        TokenType.PUT,
  799        TokenType.RANGE,
  800        TokenType.RECURSIVE,
  801        TokenType.REFERENCES,
  802        TokenType.REFRESH,
  803        TokenType.RENAME,
  804        TokenType.REPLACE,
  805        TokenType.RIGHT,
  806        TokenType.ROLLUP,
  807        TokenType.ROW,
  808        TokenType.ROWS,
  809        TokenType.SEMI,
  810        TokenType.SET,
  811        TokenType.SETTINGS,
  812        TokenType.SHOW,
  813        TokenType.STREAM,
  814        TokenType.STREAMLIT,
  815        TokenType.TEMPORARY,
  816        TokenType.TOP,
  817        TokenType.TRUE,
  818        TokenType.TRUNCATE,
  819        TokenType.UNIQUE,
  820        TokenType.UNNEST,
  821        TokenType.UNPIVOT,
  822        TokenType.UPDATE,
  823        TokenType.USE,
  824        TokenType.VOLATILE,
  825        TokenType.WINDOW,
  826        TokenType.CURRENT_CATALOG,
  827        TokenType.LOCALTIME,
  828        TokenType.LOCALTIMESTAMP,
  829        TokenType.SESSION_USER,
  830        TokenType.STRAIGHT_JOIN,
  831        *ALTERABLES,
  832        *CREATABLES,
  833        *SUBQUERY_PREDICATES,
  834        *TYPE_TOKENS,
  835        *NO_PAREN_FUNCTIONS,
  836    } - {TokenType.UNION}
  837
  838    TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - {
  839        TokenType.ANTI,
  840        TokenType.ASOF,
  841        TokenType.FULL,
  842        TokenType.LEFT,
  843        TokenType.LOCK,
  844        TokenType.NATURAL,
  845        TokenType.RIGHT,
  846        TokenType.SEMI,
  847        TokenType.WINDOW,
  848    }
  849
  850    ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS
  851
  852    COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS
  853
  854    ARRAY_CONSTRUCTORS: t.ClassVar = {
  855        "ARRAY": exp.Array,
  856        "LIST": exp.List,
  857    }
  858
  859    COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS}
  860
  861    UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET}
  862
  863    TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"}
  864
  865    # Tokens that indicate a simple column reference
  866    IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER})
  867
  868    BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE})
  869
  870    # Postfix tokens that prevent the bare column fast path
  871    COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  872        {
  873            TokenType.L_PAREN,
  874            TokenType.L_BRACKET,
  875            TokenType.L_BRACE,
  876            TokenType.COLON,
  877            TokenType.JOIN_MARKER,
  878        }
  879    )
  880
  881    TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  882        {
  883            TokenType.L_PAREN,
  884            TokenType.L_BRACKET,
  885            TokenType.L_BRACE,
  886            TokenType.PIVOT,
  887            TokenType.UNPIVOT,
  888            TokenType.TABLE_SAMPLE,
  889        }
  890    )
  891
  892    FUNC_TOKENS: t.ClassVar = {
  893        TokenType.COLLATE,
  894        TokenType.COMMAND,
  895        TokenType.CURRENT_DATE,
  896        TokenType.CURRENT_DATETIME,
  897        TokenType.CURRENT_SCHEMA,
  898        TokenType.CURRENT_TIMESTAMP,
  899        TokenType.CURRENT_TIME,
  900        TokenType.CURRENT_USER,
  901        TokenType.CURRENT_CATALOG,
  902        TokenType.DECLARE,
  903        TokenType.FILTER,
  904        TokenType.FIRST,
  905        TokenType.FORMAT,
  906        TokenType.GET,
  907        TokenType.GLOB,
  908        TokenType.IDENTIFIER,
  909        TokenType.INDEX,
  910        TokenType.ISNULL,
  911        TokenType.ILIKE,
  912        TokenType.INSERT,
  913        TokenType.LIKE,
  914        TokenType.LOCALTIME,
  915        TokenType.LOCALTIMESTAMP,
  916        TokenType.MERGE,
  917        TokenType.NEXT,
  918        TokenType.OFFSET,
  919        TokenType.PRIMARY_KEY,
  920        TokenType.RANGE,
  921        TokenType.REPLACE,
  922        TokenType.RLIKE,
  923        TokenType.ROW,
  924        TokenType.SESSION_USER,
  925        TokenType.UNNEST,
  926        TokenType.VAR,
  927        TokenType.LEFT,
  928        TokenType.RIGHT,
  929        TokenType.SEQUENCE,
  930        TokenType.DATE,
  931        TokenType.DATETIME,
  932        TokenType.TABLE,
  933        TokenType.TIMESTAMP,
  934        TokenType.TIMESTAMPTZ,
  935        TokenType.TRUNCATE,
  936        TokenType.UTC_DATE,
  937        TokenType.UTC_TIME,
  938        TokenType.UTC_TIMESTAMP,
  939        TokenType.WINDOW,
  940        TokenType.XOR,
  941        *TYPE_TOKENS,
  942        *SUBQUERY_PREDICATES,
  943    }
  944
  945    CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  946        TokenType.AND: exp.And,
  947    }
  948
  949    ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  950        TokenType.COLON_EQ: exp.PropertyEQ,
  951    }
  952
  953    DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  954        TokenType.OR: exp.Or,
  955    }
  956
  957    EQUALITY: t.ClassVar = {
  958        TokenType.EQ: exp.EQ,
  959        TokenType.NEQ: exp.NEQ,
  960        TokenType.NULLSAFE_EQ: exp.NullSafeEQ,
  961    }
  962
  963    COMPARISON: t.ClassVar = {
  964        TokenType.GT: exp.GT,
  965        TokenType.GTE: exp.GTE,
  966        TokenType.LT: exp.LT,
  967        TokenType.LTE: exp.LTE,
  968    }
  969
  970    BITWISE: t.ClassVar = {
  971        TokenType.AMP: exp.BitwiseAnd,
  972        TokenType.CARET: exp.BitwiseXor,
  973        TokenType.PIPE: exp.BitwiseOr,
  974    }
  975
  976    TERM: t.ClassVar = {
  977        TokenType.DASH: exp.Sub,
  978        TokenType.PLUS: exp.Add,
  979        TokenType.COLLATE: exp.Collate,
  980    }
  981
  982    FACTOR: t.ClassVar = {
  983        TokenType.DIV: exp.IntDiv,
  984        TokenType.LR_ARROW: exp.Distance,
  985        TokenType.LLRR_ARROW: exp.DistanceNd,
  986        TokenType.MOD: exp.Mod,
  987        TokenType.SLASH: exp.Div,
  988        TokenType.STAR: exp.Mul,
  989    }
  990
  991    EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {}
  992
  993    TIMES: t.ClassVar = {
  994        TokenType.TIME,
  995        TokenType.TIMETZ,
  996    }
  997
  998    TIMESTAMPS: t.ClassVar = {
  999        TokenType.TIMESTAMP,
 1000        TokenType.TIMESTAMPNTZ,
 1001        TokenType.TIMESTAMPTZ,
 1002        TokenType.TIMESTAMPLTZ,
 1003        *TIMES,
 1004    }
 1005
 1006    SET_OPERATIONS: t.ClassVar = {
 1007        TokenType.UNION,
 1008        TokenType.INTERSECT,
 1009        TokenType.EXCEPT,
 1010    }
 1011
 1012    JOIN_METHODS: t.ClassVar = {
 1013        TokenType.ASOF,
 1014        TokenType.NATURAL,
 1015        TokenType.POSITIONAL,
 1016    }
 1017
 1018    JOIN_SIDES: t.ClassVar = {
 1019        TokenType.LEFT,
 1020        TokenType.RIGHT,
 1021        TokenType.FULL,
 1022    }
 1023
 1024    JOIN_KINDS: t.ClassVar = {
 1025        TokenType.ANTI,
 1026        TokenType.CROSS,
 1027        TokenType.INNER,
 1028        TokenType.OUTER,
 1029        TokenType.SEMI,
 1030        TokenType.STRAIGHT_JOIN,
 1031    }
 1032
 1033    JOIN_HINTS: t.ClassVar[set[str]] = set()
 1034
 1035    # Tokens that unambiguously end a table reference on the fast path
 1036    TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset(
 1037        {
 1038            TokenType.COMMA,
 1039            TokenType.GROUP_BY,
 1040            TokenType.HAVING,
 1041            TokenType.JOIN,
 1042            TokenType.LIMIT,
 1043            TokenType.ON,
 1044            TokenType.ORDER_BY,
 1045            TokenType.R_PAREN,
 1046            TokenType.SEMICOLON,
 1047            TokenType.SENTINEL,
 1048            TokenType.WHERE,
 1049            *SET_OPERATIONS,
 1050            *JOIN_KINDS,
 1051            *JOIN_METHODS,
 1052            *JOIN_SIDES,
 1053        }
 1054    )
 1055
 1056    LAMBDAS: t.ClassVar = {
 1057        TokenType.ARROW: lambda self, expressions: self.expression(
 1058            exp.Lambda(
 1059                this=self._replace_lambda(
 1060                    self._parse_disjunction(),
 1061                    expressions,
 1062                ),
 1063                expressions=expressions,
 1064            )
 1065        ),
 1066        TokenType.FARROW: lambda self, expressions: self.expression(
 1067            exp.Kwarg(
 1068                this=exp.var(expressions[0].name),
 1069                expression=self._parse_disjunction() or self._parse_select(),
 1070            )
 1071        ),
 1072    }
 1073
 1074    # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake
 1075    TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False
 1076
 1077    LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN})
 1078
 1079    COLUMN_OPERATORS: t.ClassVar = {
 1080        TokenType.DOT: None,
 1081        TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)),
 1082        TokenType.DCOLON: lambda self, this, to: self.build_cast(
 1083            strict=self.STRICT_CAST, this=this, to=to
 1084        ),
 1085        TokenType.ARROW: lambda self, this, path: self.expression(
 1086            exp.JSONExtract(
 1087                this=this,
 1088                expression=self.dialect.to_json_path(path),
 1089                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1090            )
 1091        ),
 1092        TokenType.DARROW: lambda self, this, path: self.expression(
 1093            exp.JSONExtractScalar(
 1094                this=this,
 1095                expression=self.dialect.to_json_path(path),
 1096                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1097                scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY,
 1098            )
 1099        ),
 1100        TokenType.HASH_ARROW: lambda self, this, path: self.expression(
 1101            exp.JSONBExtract(this=this, expression=path)
 1102        ),
 1103        TokenType.DHASH_ARROW: lambda self, this, path: self.expression(
 1104            exp.JSONBExtractScalar(this=this, expression=path)
 1105        ),
 1106        TokenType.PLACEHOLDER: lambda self, this, key: self.expression(
 1107            exp.JSONBContainsTopKey(this=this, expression=key)
 1108        ),
 1109    }
 1110
 1111    # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator"
 1112    # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs).
 1113    JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {}
 1114
 1115    CAST_COLUMN_OPERATORS: t.ClassVar = {
 1116        TokenType.DOTCOLON,
 1117        TokenType.DCOLON,
 1118    }
 1119
 1120    EXPRESSION_PARSERS: t.ClassVar = {
 1121        exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY),
 1122        exp.Column: lambda self: self._parse_column(),
 1123        exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()),
 1124        exp.Condition: lambda self: self._parse_disjunction(),
 1125        exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True),
 1126        exp.Expr: lambda self: self._parse_expression(),
 1127        exp.From: lambda self: self._parse_from(joins=True),
 1128        exp.GrantPrincipal: lambda self: self._parse_grant_principal(),
 1129        exp.GrantPrivilege: lambda self: self._parse_grant_privilege(),
 1130        exp.Group: lambda self: self._parse_group(),
 1131        exp.Having: lambda self: self._parse_having(),
 1132        exp.Hint: lambda self: self._parse_hint_body(),
 1133        exp.Identifier: lambda self: self._parse_id_var(),
 1134        exp.Join: lambda self: self._parse_join(),
 1135        exp.Lambda: lambda self: self._parse_lambda(),
 1136        exp.Lateral: lambda self: self._parse_lateral(),
 1137        exp.Limit: lambda self: self._parse_limit(),
 1138        exp.Offset: lambda self: self._parse_offset(),
 1139        exp.Order: lambda self: self._parse_order(),
 1140        exp.Ordered: lambda self: self._parse_ordered(),
 1141        exp.Properties: lambda self: self._parse_properties(),
 1142        exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(),
 1143        exp.Qualify: lambda self: self._parse_qualify(),
 1144        exp.Returning: lambda self: self._parse_returning(),
 1145        exp.Select: lambda self: self._parse_select(),
 1146        exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY),
 1147        exp.Table: lambda self: self._parse_table_parts(),
 1148        exp.TableAlias: lambda self: self._parse_table_alias(),
 1149        exp.Tuple: lambda self: self._parse_value(values=False),
 1150        exp.Whens: lambda self: self._parse_when_matched(),
 1151        exp.Where: lambda self: self._parse_where(),
 1152        exp.Window: lambda self: self._parse_named_window(),
 1153        exp.With: lambda self: self._parse_with(),
 1154    }
 1155
 1156    STATEMENT_PARSERS: t.ClassVar = {
 1157        TokenType.ALTER: lambda self: self._parse_alter(),
 1158        TokenType.ANALYZE: lambda self: self._parse_analyze(),
 1159        TokenType.BEGIN: lambda self: self._parse_transaction(),
 1160        TokenType.CACHE: lambda self: self._parse_cache(),
 1161        TokenType.COMMENT: lambda self: self._parse_comment(),
 1162        TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(),
 1163        TokenType.COPY: lambda self: self._parse_copy(),
 1164        TokenType.CREATE: lambda self: self._parse_create(),
 1165        TokenType.DECLARE: lambda self: self._parse_declare(),
 1166        TokenType.DELETE: lambda self: self._parse_delete(),
 1167        TokenType.DESC: lambda self: self._parse_describe(),
 1168        TokenType.DESCRIBE: lambda self: self._parse_describe(),
 1169        TokenType.DROP: lambda self: self._parse_drop(),
 1170        TokenType.GRANT: lambda self: self._parse_grant(),
 1171        TokenType.REVOKE: lambda self: self._parse_revoke(),
 1172        TokenType.INSERT: lambda self: self._parse_insert(),
 1173        TokenType.KILL: lambda self: self._parse_kill(),
 1174        TokenType.LOAD: lambda self: self._parse_load(),
 1175        TokenType.MERGE: lambda self: self._parse_merge(),
 1176        TokenType.PIVOT: lambda self: self._parse_simplified_pivot(),
 1177        TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())),
 1178        TokenType.REFRESH: lambda self: self._parse_refresh(),
 1179        TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(),
 1180        TokenType.SET: lambda self: self._parse_set(),
 1181        TokenType.TRUNCATE: lambda self: self._parse_truncate_table(),
 1182        TokenType.UNCACHE: lambda self: self._parse_uncache(),
 1183        TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True),
 1184        TokenType.UPDATE: lambda self: self._parse_update(),
 1185        TokenType.USE: lambda self: self._parse_use(),
 1186        TokenType.SEMICOLON: lambda self: exp.Semicolon(),
 1187    }
 1188
 1189    UNARY_PARSERS: t.ClassVar = {
 1190        TokenType.PLUS: lambda self: self._parse_unary(),  # Unary + is handled as a no-op
 1191        TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())),
 1192        TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())),
 1193        TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())),
 1194        TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())),
 1195        TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())),
 1196    }
 1197
 1198    STRING_PARSERS: t.ClassVar = {
 1199        TokenType.HEREDOC_STRING: lambda self, token: self.expression(
 1200            exp.RawString(this=token.text), token
 1201        ),
 1202        TokenType.NATIONAL_STRING: lambda self, token: self.expression(
 1203            exp.National(this=token.text), token
 1204        ),
 1205        TokenType.RAW_STRING: lambda self, token: self.expression(
 1206            exp.RawString(this=token.text), token
 1207        ),
 1208        TokenType.STRING: lambda self, token: self.expression(
 1209            exp.Literal(this=token.text, is_string=True), token
 1210        ),
 1211        TokenType.UNICODE_STRING: lambda self, token: self.expression(
 1212            exp.UnicodeString(
 1213                this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string()
 1214            ),
 1215            token,
 1216        ),
 1217    }
 1218
 1219    NUMERIC_PARSERS: t.ClassVar = {
 1220        TokenType.BIT_STRING: lambda self, token: self.expression(
 1221            exp.BitString(this=token.text), token
 1222        ),
 1223        TokenType.BYTE_STRING: lambda self, token: self.expression(
 1224            exp.ByteString(
 1225                this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None
 1226            ),
 1227            token,
 1228        ),
 1229        TokenType.HEX_STRING: lambda self, token: self.expression(
 1230            exp.HexString(
 1231                this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None
 1232            ),
 1233            token,
 1234        ),
 1235        TokenType.NUMBER: lambda self, token: self.expression(
 1236            exp.Literal(this=token.text, is_string=False), token
 1237        ),
 1238    }
 1239
 1240    PRIMARY_PARSERS: t.ClassVar = {
 1241        **STRING_PARSERS,
 1242        **NUMERIC_PARSERS,
 1243        TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token),
 1244        TokenType.NULL: lambda self, _: self.expression(exp.Null()),
 1245        TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)),
 1246        TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)),
 1247        TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(),
 1248        TokenType.STAR: lambda self, _: self._parse_star_ops(),
 1249    }
 1250
 1251    PLACEHOLDER_PARSERS: t.ClassVar = {
 1252        TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()),
 1253        TokenType.PARAMETER: lambda self: self._parse_parameter(),
 1254        TokenType.COLON: lambda self: (
 1255            self.expression(exp.Placeholder(this=self._prev.text))
 1256            if self._match_set(self.COLON_PLACEHOLDER_TOKENS)
 1257            else None
 1258        ),
 1259    }
 1260
 1261    RANGE_PARSERS: t.ClassVar = {
 1262        TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll),
 1263        TokenType.BETWEEN: lambda self, this: self._parse_between(this),
 1264        TokenType.GLOB: binary_range_parser(exp.Glob),
 1265        TokenType.ILIKE: binary_range_parser(exp.ILike),
 1266        TokenType.IN: lambda self, this: self._parse_in(this),
 1267        TokenType.IRLIKE: binary_range_parser(exp.RegexpILike),
 1268        TokenType.IS: lambda self, this: self._parse_is(this),
 1269        TokenType.LIKE: binary_range_parser(exp.Like),
 1270        TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy),
 1271        TokenType.OVERLAPS: binary_range_parser(exp.Overlaps),
 1272        TokenType.RLIKE: binary_range_parser(exp.RegexpLike),
 1273        TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo),
 1274        TokenType.FOR: lambda self, this: self._parse_comprehension(this),
 1275        TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys),
 1276        TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys),
 1277        TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath),
 1278        TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists),
 1279        TokenType.ADJACENT: binary_range_parser(exp.Adjacent),
 1280        TokenType.OPERATOR: lambda self, this: self._parse_operator(this),
 1281        TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft),
 1282        TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight),
 1283    }
 1284
 1285    PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = {
 1286        "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query),
 1287        "AS": lambda self, query: self._build_pipe_cte(
 1288            query, [exp.Star()], self._parse_table_alias()
 1289        ),
 1290        "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False),
 1291        "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query),
 1292        "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query),
 1293        "ORDER BY": lambda self, query: query.order_by(
 1294            self._parse_order(), append=False, copy=False
 1295        ),
 1296        "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1297        "SELECT": lambda self, query: self._parse_pipe_syntax_select(query),
 1298        "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query),
 1299        "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1300        "WHERE": lambda self, query: query.where(self._parse_where(), copy=False),
 1301    }
 1302
 1303    PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1304        "ALLOWED_VALUES": lambda self: self.expression(
 1305            exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary))
 1306        ),
 1307        "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty),
 1308        "AUTO": lambda self: self._parse_auto_property(),
 1309        "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty),
 1310        "BACKUP": lambda self: self.expression(
 1311            exp.BackupProperty(this=self._parse_var(any_token=True))
 1312        ),
 1313        "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(),
 1314        "CALLED": lambda self: self._parse_called_on_null_input_property(),
 1315        "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs),
 1316        "CHECKSUM": lambda self: self._parse_checksum(),
 1317        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1318        "CLUSTERED": lambda self: self._parse_clustered_by(),
 1319        "COLLATE": lambda self, **kwargs: self._parse_property_assignment(
 1320            exp.CollateProperty, **kwargs
 1321        ),
 1322        "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty),
 1323        "CONTAINS": lambda self: self._parse_contains_property(),
 1324        "COPY": lambda self: self._parse_copy_property(),
 1325        "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs),
 1326        "DATA_DELETION": lambda self: self._parse_data_deletion_property(),
 1327        "DEFINER": lambda self: self._parse_definer(),
 1328        "DETERMINISTIC": lambda self: self.expression(
 1329            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1330        ),
 1331        "DISTRIBUTED": lambda self: self._parse_distributed_property(),
 1332        "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty),
 1333        "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()),
 1334        "DISTKEY": lambda self: self._parse_distkey(),
 1335        "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty),
 1336        "EMPTY": lambda self: self.expression(exp.EmptyProperty()),
 1337        "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty),
 1338        "ENVIRONMENT": lambda self: self.expression(
 1339            exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment))
 1340        ),
 1341        "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty),
 1342        "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty),
 1343        "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()),
 1344        "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs),
 1345        "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1346        "FREESPACE": lambda self: self._parse_freespace(),
 1347        "GLOBAL": lambda self: self.expression(exp.GlobalProperty()),
 1348        "HEAP": lambda self: self.expression(exp.HeapProperty()),
 1349        "ICEBERG": lambda self: self.expression(exp.IcebergProperty()),
 1350        "IMMUTABLE": lambda self: self.expression(
 1351            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1352        ),
 1353        "INHERITS": lambda self: self.expression(
 1354            exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table))
 1355        ),
 1356        "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())),
 1357        "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs),
 1358        "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty),
 1359        "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"),
 1360        "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"),
 1361        "LIKE": lambda self: self._parse_create_like(),
 1362        "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty),
 1363        "LOCK": lambda self: self._parse_locking(),
 1364        "LOCKING": lambda self: self._parse_locking(),
 1365        "LOG": lambda self, **kwargs: self._parse_log(**kwargs),
 1366        "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()),
 1367        "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs),
 1368        "MODIFIES": lambda self: self._parse_modifies_property(),
 1369        "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)),
 1370        "NO": lambda self: self._parse_no_property(),
 1371        "ON": lambda self: self._parse_on_property(),
 1372        "ORDER BY": lambda self: self._parse_order(skip_order_token=True),
 1373        "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())),
 1374        "PARTITION": lambda self: self._parse_partitioned_of(),
 1375        "PARTITION BY": lambda self: self._parse_partitioned_by(),
 1376        "PARTITIONED BY": lambda self: self._parse_partitioned_by(),
 1377        "PARTITIONED_BY": lambda self: self._parse_partitioned_by(),
 1378        "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True),
 1379        "RANGE": lambda self: self._parse_dict_range(this="RANGE"),
 1380        "READS": lambda self: self._parse_reads_property(),
 1381        "REMOTE": lambda self: self._parse_remote_with_connection(),
 1382        "RETURNS": lambda self: self._parse_returns(),
 1383        "STRICT": lambda self: self.expression(exp.StrictProperty()),
 1384        "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()),
 1385        "ROW": lambda self: self._parse_row(),
 1386        "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty),
 1387        "SAMPLE": lambda self: self.expression(
 1388            exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise())
 1389        ),
 1390        "SECURE": lambda self: self.expression(exp.SecureProperty()),
 1391        "SECURITY": lambda self: self._parse_sql_security(),
 1392        "SQL SECURITY": lambda self: self._parse_sql_security(),
 1393        "SET": lambda self: self.expression(exp.SetProperty(multi=False)),
 1394        "SETTINGS": lambda self: self._parse_settings_property(),
 1395        "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty),
 1396        "SORTKEY": lambda self: self._parse_sortkey(),
 1397        "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"),
 1398        "STABLE": lambda self: self.expression(
 1399            exp.StabilityProperty(this=exp.Literal.string("STABLE"))
 1400        ),
 1401        "STORED": lambda self: self._parse_stored(),
 1402        "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(),
 1403        "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(),
 1404        "TEMP": lambda self: self.expression(exp.TemporaryProperty()),
 1405        "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()),
 1406        "TO": lambda self: self._parse_to_table(),
 1407        "TRANSIENT": lambda self: self.expression(exp.TransientProperty()),
 1408        "TRANSFORM": lambda self: self.expression(
 1409            exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression))
 1410        ),
 1411        "TTL": lambda self: self._parse_ttl(),
 1412        "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1413        "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()),
 1414        "VOLATILE": lambda self: self._parse_volatile_property(),
 1415        "WITH": lambda self: self._parse_with_property(),
 1416    }
 1417
 1418    CONSTRAINT_PARSERS: t.ClassVar = {
 1419        "AUTOINCREMENT": lambda self: self._parse_auto_increment(),
 1420        "AUTO_INCREMENT": lambda self: self._parse_auto_increment(),
 1421        "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)),
 1422        "CHECK": lambda self: self._parse_check_constraint(),
 1423        "COLLATE": lambda self: self.expression(
 1424            exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column())
 1425        ),
 1426        "COMMENT": lambda self: self.expression(
 1427            exp.CommentColumnConstraint(this=self._parse_string())
 1428        ),
 1429        "COMPRESS": lambda self: self._parse_compress(),
 1430        "CLUSTERED": lambda self: self.expression(
 1431            exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1432        ),
 1433        "NONCLUSTERED": lambda self: self.expression(
 1434            exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1435        ),
 1436        "DEFAULT": lambda self: self.expression(
 1437            exp.DefaultColumnConstraint(this=self._parse_bitwise())
 1438        ),
 1439        "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())),
 1440        "EPHEMERAL": lambda self: self.expression(
 1441            exp.EphemeralColumnConstraint(this=self._parse_bitwise())
 1442        ),
 1443        "EXCLUDE": lambda self: self.expression(
 1444            exp.ExcludeColumnConstraint(this=self._parse_index_params())
 1445        ),
 1446        "FOREIGN KEY": lambda self: self._parse_foreign_key(),
 1447        "FORMAT": lambda self: self.expression(
 1448            exp.DateFormatColumnConstraint(this=self._parse_var_or_string())
 1449        ),
 1450        "GENERATED": lambda self: self._parse_generated_as_identity(),
 1451        "IDENTITY": lambda self: self._parse_auto_increment(),
 1452        "INLINE": lambda self: self._parse_inline(),
 1453        "LIKE": lambda self: self._parse_create_like(),
 1454        "NOT": lambda self: self._parse_not_constraint(),
 1455        "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)),
 1456        "ON": lambda self: (
 1457            (
 1458                self._match(TokenType.UPDATE)
 1459                and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function()))
 1460            )
 1461            or self.expression(exp.OnProperty(this=self._parse_id_var()))
 1462        ),
 1463        "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())),
 1464        "PERIOD": lambda self: self._parse_period_for_system_time(),
 1465        "PRIMARY KEY": lambda self: self._parse_primary_key(),
 1466        "REFERENCES": lambda self: self._parse_references(match=False),
 1467        "TITLE": lambda self: self.expression(
 1468            exp.TitleColumnConstraint(this=self._parse_var_or_string())
 1469        ),
 1470        "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])),
 1471        "UNIQUE": lambda self: self._parse_unique(),
 1472        "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()),
 1473        "WITH": lambda self: self.expression(
 1474            exp.Properties(expressions=self._parse_wrapped_properties())
 1475        ),
 1476        "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1477        "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1478    }
 1479
 1480    def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None:
 1481        if not self._match(TokenType.L_PAREN, advance=False):
 1482            # Partitioning by bucket or truncate follows the syntax:
 1483            # PARTITION BY (BUCKET(..) | TRUNCATE(..))
 1484            # If we don't have parenthesis after each keyword, we should instead parse this as an identifier
 1485            self._retreat(self._index - 1)
 1486            return None
 1487
 1488        klass = (
 1489            exp.PartitionedByBucket
 1490            if self._prev.text.upper() == "BUCKET"
 1491            else exp.PartitionByTruncate
 1492        )
 1493
 1494        args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column())
 1495        this, expression = seq_get(args, 0), seq_get(args, 1)
 1496
 1497        if isinstance(this, exp.Literal):
 1498            # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order
 1499            #  - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)`
 1500            #  - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)`
 1501            # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)`
 1502            #
 1503            # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning
 1504            # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties
 1505            this, expression = expression, this
 1506
 1507        return self.expression(klass(this=this, expression=expression))
 1508
 1509    ALTER_PARSERS: t.ClassVar = {
 1510        "ADD": lambda self: self._parse_alter_table_add(),
 1511        "AS": lambda self: self._parse_select(),
 1512        "ALTER": lambda self: self._parse_alter_table_alter(),
 1513        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1514        "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())),
 1515        "DROP": lambda self: self._parse_alter_table_drop(),
 1516        "RENAME": lambda self: self._parse_alter_table_rename(),
 1517        "SET": lambda self: self._parse_alter_table_set(),
 1518        "SWAP": lambda self: self.expression(
 1519            exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True))
 1520        ),
 1521    }
 1522
 1523    ALTER_ALTER_PARSERS: t.ClassVar = {
 1524        "DISTKEY": lambda self: self._parse_alter_diststyle(),
 1525        "DISTSTYLE": lambda self: self._parse_alter_diststyle(),
 1526        "SORTKEY": lambda self: self._parse_alter_sortkey(),
 1527        "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True),
 1528    }
 1529
 1530    SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = {
 1531        "CHECK",
 1532        "EXCLUDE",
 1533        "FOREIGN KEY",
 1534        "LIKE",
 1535        "PERIOD",
 1536        "PRIMARY KEY",
 1537        "UNIQUE",
 1538        "BUCKET",
 1539        "TRUNCATE",
 1540    }
 1541
 1542    NO_PAREN_FUNCTION_PARSERS: t.ClassVar = {
 1543        "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())),
 1544        "CASE": lambda self: self._parse_case(),
 1545        "CONNECT_BY_ROOT": lambda self: self.expression(
 1546            exp.ConnectByRoot(this=self._parse_column())
 1547        ),
 1548        "IF": lambda self: self._parse_if(),
 1549    }
 1550
 1551    INVALID_FUNC_NAME_TOKENS: t.ClassVar = {
 1552        TokenType.IDENTIFIER,
 1553        TokenType.STRING,
 1554    }
 1555
 1556    FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"}
 1557
 1558    KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice)
 1559
 1560    FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1561        **{
 1562            name: lambda self: self._parse_distinct_arg_function(exp.ArgMax)
 1563            for name in exp.ArgMax.sql_names()
 1564        },
 1565        **{
 1566            name: lambda self: self._parse_distinct_arg_function(exp.ArgMin)
 1567            for name in exp.ArgMin.sql_names()
 1568        },
 1569        "CAST": lambda self: self._parse_cast(self.STRICT_CAST),
 1570        "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil),
 1571        "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST),
 1572        "CHAR": lambda self: self._parse_char(),
 1573        "CHR": lambda self: self._parse_char(),
 1574        "DECODE": lambda self: self._parse_decode(),
 1575        "EXTRACT": lambda self: self._parse_extract(),
 1576        "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor),
 1577        "GAP_FILL": lambda self: self._parse_gap_fill(),
 1578        "INITCAP": lambda self: self._parse_initcap(),
 1579        "JSON_OBJECT": lambda self: self._parse_json_object(),
 1580        "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True),
 1581        "JSON_TABLE": lambda self: self._parse_json_table(),
 1582        "MATCH": lambda self: self._parse_match_against(),
 1583        "NORMALIZE": lambda self: self._parse_normalize(),
 1584        "OPENJSON": lambda self: self._parse_open_json(),
 1585        "OVERLAY": lambda self: self._parse_overlay(),
 1586        "POSITION": lambda self: self._parse_position(),
 1587        "SAFE_CAST": lambda self: self._parse_cast(False, safe=True),
 1588        "STRING_AGG": lambda self: self._parse_string_agg(),
 1589        "SUBSTRING": lambda self: self._parse_substring(),
 1590        "TRIM": lambda self: self._parse_trim(),
 1591        "TRY_CAST": lambda self: self._parse_cast(False, safe=True),
 1592        "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True),
 1593        "XMLELEMENT": lambda self: self._parse_xml_element(),
 1594        "XMLTABLE": lambda self: self._parse_xml_table(),
 1595    }
 1596
 1597    QUERY_MODIFIER_PARSERS: t.ClassVar = {
 1598        TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()),
 1599        TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()),
 1600        TokenType.WHERE: lambda self: ("where", self._parse_where()),
 1601        TokenType.GROUP_BY: lambda self: ("group", self._parse_group()),
 1602        TokenType.HAVING: lambda self: ("having", self._parse_having()),
 1603        TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()),
 1604        TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()),
 1605        TokenType.ORDER_BY: lambda self: ("order", self._parse_order()),
 1606        TokenType.LIMIT: lambda self: ("limit", self._parse_limit()),
 1607        TokenType.FETCH: lambda self: ("limit", self._parse_limit()),
 1608        TokenType.OFFSET: lambda self: ("offset", self._parse_offset()),
 1609        TokenType.FOR: lambda self: ("locks", self._parse_locks()),
 1610        TokenType.LOCK: lambda self: ("locks", self._parse_locks()),
 1611        TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1612        TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1613        TokenType.CLUSTER_BY: lambda self: (
 1614            "cluster",
 1615            self._parse_cluster(),
 1616        ),
 1617        TokenType.DISTRIBUTE_BY: lambda self: (
 1618            "distribute",
 1619            self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY),
 1620        ),
 1621        TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)),
 1622        TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)),
 1623    }
 1624    QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS)
 1625
 1626    SET_PARSERS: t.ClassVar = {
 1627        "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"),
 1628        "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"),
 1629        "SESSION": lambda self: self._parse_set_item_assignment("SESSION"),
 1630        "TRANSACTION": lambda self: self._parse_set_transaction(),
 1631    }
 1632
 1633    SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {}
 1634
 1635    TYPE_LITERAL_PARSERS: t.ClassVar = {
 1636        exp.DType.JSON: lambda self, this, _: self.expression(
 1637            exp.ParseJSON(this=this, is_literal=True)
 1638        ),
 1639    }
 1640
 1641    TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {}
 1642
 1643    DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN}
 1644
 1645    PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE}
 1646
 1647    TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"}
 1648    TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = {
 1649        "ISOLATION": (
 1650            ("LEVEL", "REPEATABLE", "READ"),
 1651            ("LEVEL", "READ", "COMMITTED"),
 1652            ("LEVEL", "READ", "UNCOMITTED"),
 1653            ("LEVEL", "SERIALIZABLE"),
 1654        ),
 1655        "READ": ("WRITE", "ONLY"),
 1656    }
 1657
 1658    CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1659        **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()),
 1660        "DO": ("NOTHING", "UPDATE"),
 1661    }
 1662
 1663    TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = {
 1664        "INSTEAD": (("OF",),),
 1665        "BEFORE": tuple(),
 1666        "AFTER": tuple(),
 1667    }
 1668
 1669    TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = {
 1670        "NOT": (("DEFERRABLE",),),
 1671        "DEFERRABLE": tuple(),
 1672    }
 1673
 1674    CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = {
 1675        "SCALE": ("EXTEND", "NOEXTEND"),
 1676        "SHARD": ("EXTEND", "NOEXTEND"),
 1677        "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"),
 1678        **dict.fromkeys(
 1679            (
 1680                "SESSION",
 1681                "GLOBAL",
 1682                "KEEP",
 1683                "NOKEEP",
 1684                "ORDER",
 1685                "NOORDER",
 1686                "NOCACHE",
 1687                "CYCLE",
 1688                "NOCYCLE",
 1689                "NOMINVALUE",
 1690                "NOMAXVALUE",
 1691                "NOSCALE",
 1692                "NOSHARD",
 1693            ),
 1694            tuple(),
 1695        ),
 1696    }
 1697
 1698    ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")}
 1699
 1700    USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1701        ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple()
 1702    )
 1703
 1704    CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",))
 1705
 1706    SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1707        "TYPE": ("EVOLUTION",),
 1708        **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()),
 1709    }
 1710
 1711    PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {}
 1712
 1713    EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1714        ("CALLER", "SELF", "OWNER"), tuple()
 1715    )
 1716
 1717    KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1718        "NOT": ("ENFORCED",),
 1719        "MATCH": (
 1720            "FULL",
 1721            "PARTIAL",
 1722            "SIMPLE",
 1723        ),
 1724        "INITIALLY": ("DEFERRED", "IMMEDIATE"),
 1725        "USING": (
 1726            "BTREE",
 1727            "HASH",
 1728        ),
 1729        **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()),
 1730    }
 1731
 1732    WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1733        "NO": ("OTHERS",),
 1734        "CURRENT": ("ROW",),
 1735        **dict.fromkeys(("GROUP", "TIES"), tuple()),
 1736    }
 1737
 1738    INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"}
 1739
 1740    CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"}
 1741    # Time travel clause prefixes, mapped to whether they pin a timestamp or a version
 1742    VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = {
 1743        ("FOR", "SYSTEM_TIME"): "TIMESTAMP",
 1744        ("FOR", "SYSTEM", "TIME"): "TIMESTAMP",
 1745        ("FOR", "TIMESTAMP"): "TIMESTAMP",
 1746        ("FOR", "VERSION"): "VERSION",
 1747        ("TIMESTAMP", "AS", "OF"): "TIMESTAMP",
 1748        ("VERSION", "AS", "OF"): "VERSION",
 1749    }
 1750
 1751    HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"}
 1752    HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"}
 1753
 1754    OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"}
 1755
 1756    OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN}
 1757
 1758    TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE}
 1759
 1760    VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"}
 1761
 1762    WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS}
 1763    WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER}
 1764    WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"}
 1765
 1766    JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS}
 1767
 1768    FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT}
 1769
 1770    ADD_CONSTRAINT_TOKENS: t.ClassVar = {
 1771        TokenType.CONSTRAINT,
 1772        TokenType.FOREIGN_KEY,
 1773        TokenType.INDEX,
 1774        TokenType.KEY,
 1775        TokenType.PRIMARY_KEY,
 1776        TokenType.UNIQUE,
 1777    }
 1778
 1779    DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT}
 1780
 1781    UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS
 1782
 1783    SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT}
 1784
 1785    COPY_INTO_VARLEN_OPTIONS: t.ClassVar = {
 1786        "FILE_FORMAT",
 1787        "COPY_OPTIONS",
 1788        "FORMAT_OPTIONS",
 1789        "CREDENTIAL",
 1790    }
 1791
 1792    IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"}
 1793
 1794    ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {}
 1795
 1796    ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"}
 1797
 1798    PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN}
 1799
 1800    # The style options for the DESCRIBE statement
 1801    DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"}
 1802
 1803    SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"}
 1804
 1805    # The style options for the ANALYZE statement
 1806    ANALYZE_STYLES: t.ClassVar = {
 1807        "BUFFER_USAGE_LIMIT",
 1808        "FULL",
 1809        "LOCAL",
 1810        "NO_WRITE_TO_BINLOG",
 1811        "SAMPLE",
 1812        "SKIP_LOCKED",
 1813        "VERBOSE",
 1814    }
 1815
 1816    ANALYZE_EXPRESSION_PARSERS: t.ClassVar = {
 1817        "ALL": lambda self: self._parse_analyze_columns(),
 1818        "COMPUTE": lambda self: self._parse_analyze_statistics(),
 1819        "DELETE": lambda self: self._parse_analyze_delete(),
 1820        "DROP": lambda self: self._parse_analyze_histogram(),
 1821        "ESTIMATE": lambda self: self._parse_analyze_statistics(),
 1822        "LIST": lambda self: self._parse_analyze_list(),
 1823        "PREDICATE": lambda self: self._parse_analyze_columns(),
 1824        "UPDATE": lambda self: self._parse_analyze_histogram(),
 1825        "VALIDATE": lambda self: self._parse_analyze_validate(),
 1826    }
 1827
 1828    PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"}
 1829
 1830    AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET)
 1831
 1832    OPERATION_MODIFIERS: t.ClassVar[set[str]] = set()
 1833
 1834    RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"}
 1835
 1836    SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"}
 1837
 1838    MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values)
 1839
 1840    STRICT_CAST: t.ClassVar = True
 1841
 1842    PREFIXED_PIVOT_COLUMNS: t.ClassVar = False
 1843    IDENTIFY_PIVOT_STRINGS: t.ClassVar = False
 1844    # Whether an UNPIVOT outputs its value column(s) before the name column
 1845    UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False
 1846    # Controls when an aggregation's name is included in a pivoted column's name:
 1847    # "agg_name_if_aliased" - only for aggregations that carry an explicit alias
 1848    # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations
 1849    # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only)
 1850    PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased"
 1851
 1852    LOG_DEFAULTS_TO_LN: t.ClassVar = False
 1853
 1854    # Whether the table sample clause expects CSV syntax
 1855    TABLESAMPLE_CSV: t.ClassVar = False
 1856
 1857    # The default method used for table sampling
 1858    DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None
 1859
 1860    # Whether the SET command needs a delimiter (e.g. "=") for assignments
 1861    SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True
 1862
 1863    # Whether the TRIM function expects the characters to trim as its first argument
 1864    TRIM_PATTERN_FIRST: t.ClassVar = False
 1865
 1866    # Whether string aliases are supported `SELECT COUNT(*) 'count'`
 1867    STRING_ALIASES: t.ClassVar = False
 1868
 1869    # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand)
 1870    MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True
 1871    SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"}
 1872
 1873    # Whether to parse IF statements that aren't followed by a left parenthesis as commands
 1874    NO_PAREN_IF_COMMANDS: t.ClassVar = True
 1875
 1876    # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres)
 1877    JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False
 1878
 1879    # Whether the `:` operator is used to extract a value from a VARIANT column
 1880    COLON_IS_VARIANT_EXTRACT: t.ClassVar = False
 1881
 1882    # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged
 1883    # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks)
 1884    COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True
 1885
 1886    # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause.
 1887    # If this is True and '(' is not found, the keyword will be treated as an identifier
 1888    VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True
 1889
 1890    # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift)
 1891    SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False
 1892
 1893    # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery)
 1894    SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False
 1895
 1896    # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS
 1897    INTERVAL_SPANS: t.ClassVar = True
 1898
 1899    # Whether a PARTITION clause can follow a table reference
 1900    SUPPORTS_PARTITION_SELECTION: t.ClassVar = False
 1901
 1902    # Whether the `name AS expr` schema/column constraint requires parentheses around `expr`
 1903    WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True
 1904
 1905    # Whether the 'AS' keyword is optional in the CTE definition syntax
 1906    OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True
 1907
 1908    # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword
 1909    ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True
 1910
 1911    # Whether Alter statements are allowed to contain Partition specifications
 1912    ALTER_TABLE_PARTITIONS: t.ClassVar = False
 1913
 1914    # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree.
 1915    # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is
 1916    # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such
 1917    # as BigQuery, where all joins have the same precedence.
 1918    JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False
 1919
 1920    # Whether TIMESTAMP <literal> can produce a zone-aware timestamp
 1921    ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False
 1922
 1923    # Whether map literals support arbitrary expressions as keys.
 1924    # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB).
 1925    # When False, keys are typically restricted to identifiers.
 1926    MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False
 1927
 1928    # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this
 1929    # is true for Snowflake but not for BigQuery which can also process strings
 1930    JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False
 1931
 1932    # Dialects like Databricks support JOINS without join criteria
 1933    # Adding an ON TRUE, makes transpilation semantically correct for other dialects
 1934    ADD_JOIN_ON_TRUE: t.ClassVar = False
 1935
 1936    # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]'
 1937    # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND`
 1938    SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False
 1939
 1940    # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them
 1941    # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation.
 1942    ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False
 1943
 1944    # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause,
 1945    # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake)
 1946    SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False
 1947
 1948    # Type names that denote a different type when they're quoted, so quoting has to be
 1949    # preserved instead of resolving them into the built-in type of the same name. These
 1950    # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR
 1951    QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set()
 1952
 1953    SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS)
 1954    SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS)
 1955
 1956    def __init__(
 1957        self,
 1958        error_level: ErrorLevel | None = None,
 1959        error_message_context: int = 100,
 1960        max_errors: int = 3,
 1961        max_nodes: int = -1,
 1962        dialect: DialectType = None,
 1963    ):
 1964        self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE
 1965        self.error_message_context: int = error_message_context
 1966        self.max_errors: int = max_errors
 1967        self.max_nodes: int = max_nodes
 1968        self.dialect: t.Any = _resolve_dialect(dialect)
 1969        self.sql: str = ""
 1970        self.errors: list[ParseError] = []
 1971        self._tokens: list[Token] = []
 1972        self._tokens_size: i64 = 0
 1973        self._index: i64 = 0
 1974        self._curr: Token = SENTINEL_NONE
 1975        self._next: Token = SENTINEL_NONE
 1976        self._prev: Token = SENTINEL_NONE
 1977        self._prev_comments: list[str] = []
 1978        self._pipe_cte_counter: int = 0
 1979        self._chunks: list[list[Token]] = []
 1980        self._chunk_index: i64 = 0
 1981        self._node_count: int = 0
 1982
 1983    def reset(self) -> None:
 1984        self.sql = ""
 1985        self.errors = []
 1986        self._tokens = []
 1987        self._tokens_size = 0
 1988        self._index = 0
 1989        self._curr = SENTINEL_NONE
 1990        self._next = SENTINEL_NONE
 1991        self._prev = SENTINEL_NONE
 1992        self._prev_comments = []
 1993        self._pipe_cte_counter = 0
 1994        self._chunks = []
 1995        self._chunk_index = 0
 1996        self._node_count = 0
 1997
 1998    def _advance(self, times: i64 = 1) -> None:
 1999        index = self._index + times
 2000        self._index = index
 2001        tokens = self._tokens
 2002        size = self._tokens_size
 2003        self._curr = tokens[index] if index < size else SENTINEL_NONE
 2004        self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE
 2005
 2006        if index > 0:
 2007            prev = tokens[index - 1]
 2008            self._prev = prev
 2009            self._prev_comments = prev.comments
 2010        else:
 2011            self._prev = SENTINEL_NONE
 2012            self._prev_comments = []
 2013
 2014    def _advance_chunk(self) -> None:
 2015        self._index = -1
 2016        self._tokens = self._chunks[self._chunk_index]
 2017        self._tokens_size = i64(len(self._tokens))
 2018        self._chunk_index += 1
 2019        self._advance()
 2020
 2021    def _retreat(self, index: i64) -> None:
 2022        if index != self._index:
 2023            self._advance(index - self._index)
 2024
 2025    def _add_comments(self, expression: exp.Expr | None) -> None:
 2026        if expression and self._prev_comments:
 2027            expression.add_comments(self._prev_comments)
 2028            self._prev_comments = []
 2029
 2030    def _match(
 2031        self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None
 2032    ) -> bool:
 2033        if self._curr.token_type == token_type:
 2034            if advance:
 2035                self._advance()
 2036            self._add_comments(expression)
 2037            return True
 2038        return False
 2039
 2040    def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool:
 2041        if self._curr.token_type in types:
 2042            if advance:
 2043                self._advance()
 2044            return True
 2045        return False
 2046
 2047    def _match_pair(
 2048        self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True
 2049    ) -> bool:
 2050        if self._curr.token_type == token_type_a and self._next.token_type == token_type_b:
 2051            if advance:
 2052                self._advance(2)
 2053            return True
 2054        return False
 2055
 2056    def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool:
 2057        if (
 2058            self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS
 2059            and self._curr.text.upper() in texts
 2060        ):
 2061            if advance:
 2062                self._advance()
 2063            return True
 2064        return False
 2065
 2066    def _match_text_seq(self, *texts: str, advance: bool = True) -> bool:
 2067        index = self._index
 2068        excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS
 2069        for text in texts:
 2070            if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text:
 2071                self._advance()
 2072            else:
 2073                self._retreat(index)
 2074                return False
 2075
 2076        if not advance:
 2077            self._retreat(index)
 2078
 2079        return True
 2080
 2081    def _is_connected(self) -> bool:
 2082        prev = self._prev
 2083        curr = self._curr
 2084        return bool(prev and curr and prev.end + 1 == curr.start)
 2085
 2086    def _find_sql(self, start: Token, end: Token) -> str:
 2087        return self.sql[start.start : end.end + 1]
 2088
 2089    def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None:
 2090        token = token or self._curr or self._prev or Token.string("")
 2091        formatted_sql, start_context, highlight, end_context = highlight_sql(
 2092            sql=self.sql,
 2093            positions=[(token.start, token.end)],
 2094            context_length=self.error_message_context,
 2095        )
 2096        formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n  {formatted_sql}"
 2097
 2098        error = ParseError.new(
 2099            formatted_message,
 2100            description=message,
 2101            line=token.line,
 2102            col=token.col,
 2103            start_context=start_context,
 2104            highlight=highlight,
 2105            end_context=end_context,
 2106        )
 2107
 2108        if self.error_level == ErrorLevel.IMMEDIATE:
 2109            raise error
 2110
 2111        self.errors.append(error)
 2112
 2113    def validate_expression(self, expression: E, args: list | None = None) -> E:
 2114        if self.max_nodes > -1:
 2115            self._node_count += 1
 2116            if self._node_count > self.max_nodes:
 2117                self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded")
 2118        if self.error_level != ErrorLevel.IGNORE:
 2119            for error_message in expression.error_messages(args):
 2120                self.raise_error(error_message)
 2121        return expression
 2122
 2123    def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None:
 2124        index = self._index
 2125        error_level = self.error_level
 2126        this: T | None = None
 2127
 2128        self.error_level = ErrorLevel.IMMEDIATE
 2129        try:
 2130            this = parse_method()
 2131        except ParseError:
 2132            this = None
 2133        finally:
 2134            if not this or retreat:
 2135                self._retreat(index)
 2136            self.error_level = error_level
 2137
 2138        return this
 2139
 2140    def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]:
 2141        """
 2142        Parses a list of tokens and returns a list of syntax trees, one tree
 2143        per parsed SQL statement.
 2144
 2145        Args:
 2146            raw_tokens: The list of tokens.
 2147            sql: The original SQL string.
 2148
 2149        Returns:
 2150            The list of the produced syntax trees.
 2151        """
 2152        return self._parse(
 2153            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
 2154        )
 2155
 2156    def parse_into(
 2157        self,
 2158        expression_types: exp.IntoType,
 2159        raw_tokens: list[Token],
 2160        sql: str | None = None,
 2161    ) -> list[exp.Expr | None]:
 2162        """
 2163        Parses a list of tokens into a given Expr type. If a collection of Expr
 2164        types is given instead, this method will try to parse the token list into each one
 2165        of them, stopping at the first for which the parsing succeeds.
 2166
 2167        Args:
 2168            expression_types: The expression type(s) to try and parse the token list into.
 2169            raw_tokens: The list of tokens.
 2170            sql: The original SQL string, used to produce helpful debug messages.
 2171
 2172        Returns:
 2173            The target Expr.
 2174        """
 2175        errors = []
 2176        for expression_type in ensure_list(expression_types):
 2177            parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type))
 2178            if not parser:
 2179                raise TypeError(f"No parser registered for {expression_type}")
 2180
 2181            try:
 2182                return self._parse(parser, raw_tokens, sql)
 2183            except ParseError as e:
 2184                e.errors[0]["into_expression"] = expression_type
 2185                errors.append(e)
 2186
 2187        raise ParseError(
 2188            f"Failed to parse '{sql or raw_tokens}' into {expression_types}",
 2189            errors=merge_errors(errors),
 2190        ) from errors[-1]
 2191
 2192    def check_errors(self) -> None:
 2193        """Logs or raises any found errors, depending on the chosen error level setting."""
 2194        if self.error_level == ErrorLevel.WARN:
 2195            for error in self.errors:
 2196                logger.error(str(error))
 2197        elif self.error_level == ErrorLevel.RAISE and self.errors:
 2198            raise ParseError(
 2199                concat_messages(self.errors, self.max_errors),
 2200                errors=merge_errors(self.errors),
 2201            )
 2202
 2203    def expression(
 2204        self,
 2205        instance: E,
 2206        token: Token | None = None,
 2207        comments: list[str] | None = None,
 2208    ) -> E:
 2209        if token:
 2210            instance.update_positions(token)
 2211        instance.add_comments(comments) if comments else self._add_comments(instance)
 2212        if not instance.is_primitive:
 2213            instance = self.validate_expression(instance)
 2214        return instance
 2215
 2216    def _parse_batch_statements(
 2217        self,
 2218        parse_method: t.Callable[[Parser], exp.Expr | None],
 2219        sep_first_statement: bool = True,
 2220    ) -> list[exp.Expr | None]:
 2221        expressions = []
 2222
 2223        # Chunkification binds if/while statements with the first statement of the body
 2224        if sep_first_statement:
 2225            self._match(TokenType.BEGIN)
 2226            expressions.append(parse_method(self))
 2227
 2228        chunks_length = len(self._chunks)
 2229        while self._chunk_index < chunks_length:
 2230            self._advance_chunk()
 2231
 2232            if self._match(TokenType.ELSE, advance=False):
 2233                return expressions
 2234
 2235            if expressions and not self._next and self._match(TokenType.END):
 2236                expressions.append(exp.EndStatement())
 2237                continue
 2238
 2239            expressions.append(parse_method(self))
 2240
 2241            if self._index < self._tokens_size:
 2242                self.raise_error("Invalid expression / Unexpected token")
 2243
 2244            self.check_errors()
 2245
 2246        return expressions
 2247
 2248    def _parse(
 2249        self,
 2250        parse_method: t.Callable[[Parser], exp.Expr | None],
 2251        raw_tokens: list[Token],
 2252        sql: str | None = None,
 2253    ) -> list[exp.Expr | None]:
 2254        self.reset()
 2255        self.sql = sql or ""
 2256
 2257        total = len(raw_tokens)
 2258        chunks: list[list[Token]] = [[]]
 2259
 2260        for i, token in enumerate(raw_tokens):
 2261            if token.token_type == TokenType.SEMICOLON:
 2262                if token.comments:
 2263                    chunks.append([token])
 2264
 2265                if i < total - 1:
 2266                    chunks.append([])
 2267            else:
 2268                chunks[-1].append(token)
 2269
 2270        self._chunks = chunks
 2271
 2272        return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False)
 2273
 2274    def _warn_unsupported(self) -> None:
 2275        if self._tokens_size <= 1:
 2276            return
 2277
 2278        # We use _find_sql because self.sql may comprise multiple chunks, and we're only
 2279        # interested in emitting a warning for the one being currently processed.
 2280        sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context]
 2281
 2282        logger.warning(
 2283            f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'."
 2284        )
 2285
 2286    def _parse_command(self) -> exp.Command:
 2287        self._warn_unsupported()
 2288        comments = self._prev_comments
 2289        return self.expression(
 2290            exp.Command(this=self._prev.text.upper(), expression=self._parse_string()),
 2291            comments=comments,
 2292        )
 2293
 2294    def _parse_comment(self, allow_exists: bool = True) -> exp.Expr:
 2295        start = self._prev
 2296        exists = self._parse_exists() if allow_exists else None
 2297
 2298        self._match(TokenType.ON)
 2299
 2300        materialized = self._match_text_seq("MATERIALIZED")
 2301        kind = self._match_set(self.CREATABLES) and self._prev
 2302        if not kind:
 2303            return self._parse_as_command(start)
 2304
 2305        if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2306            this = self._parse_user_defined_function(kind=kind.token_type)
 2307        elif kind.token_type == TokenType.TABLE:
 2308            this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS)
 2309        elif kind.token_type == TokenType.COLUMN:
 2310            this = self._parse_column()
 2311        else:
 2312            this = self._parse_table_parts(schema=True)
 2313
 2314        self._match(TokenType.IS)
 2315
 2316        return self.expression(
 2317            exp.Comment(
 2318                this=this,
 2319                kind=kind.text,
 2320                expression=self._parse_string(),
 2321                exists=exists,
 2322                materialized=materialized,
 2323            )
 2324        )
 2325
 2326    def _parse_to_table(
 2327        self,
 2328    ) -> exp.ToTableProperty:
 2329        table = self._parse_table_parts(schema=True)
 2330        return self.expression(exp.ToTableProperty(this=table))
 2331
 2332    # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl
 2333    def _parse_ttl(self) -> exp.Expr:
 2334        def _parse_ttl_action() -> exp.Expr | None:
 2335            this = self._parse_bitwise()
 2336
 2337            if self._match_text_seq("DELETE"):
 2338                return self.expression(exp.MergeTreeTTLAction(this=this, delete=True))
 2339            if self._match_text_seq("RECOMPRESS"):
 2340                return self.expression(
 2341                    exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise())
 2342                )
 2343            if self._match_text_seq("TO", "DISK"):
 2344                return self.expression(
 2345                    exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string())
 2346                )
 2347            if self._match_text_seq("TO", "VOLUME"):
 2348                return self.expression(
 2349                    exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string())
 2350                )
 2351
 2352            return this
 2353
 2354        expressions = self._parse_csv(_parse_ttl_action)
 2355        where = self._parse_where()
 2356        group = self._parse_group()
 2357
 2358        aggregates = None
 2359        if group and self._match(TokenType.SET):
 2360            aggregates = self._parse_csv(self._parse_set_item)
 2361
 2362        return self.expression(
 2363            exp.MergeTreeTTL(
 2364                expressions=expressions, where=where, group=group, aggregates=aggregates
 2365            )
 2366        )
 2367
 2368    def _parse_condition(self) -> exp.Expr | None:
 2369        return self._parse_wrapped(parse_method=self._parse_expression, optional=True)
 2370
 2371    def _parse_block(self) -> exp.Block:
 2372        return self.expression(
 2373            exp.Block(
 2374                expressions=self._parse_batch_statements(
 2375                    parse_method=lambda self: self._parse_statement()
 2376                )
 2377            )
 2378        )
 2379
 2380    def _parse_whileblock(self) -> exp.WhileBlock:
 2381        return self.expression(
 2382            exp.WhileBlock(this=self._parse_condition(), body=self._parse_block())
 2383        )
 2384
 2385    def _parse_statement(self) -> exp.Expr | None:
 2386        if not self._curr:
 2387            return None
 2388
 2389        if self._match_set(self.STATEMENT_PARSERS):
 2390            comments = self._prev_comments
 2391            stmt = self.STATEMENT_PARSERS[self._prev.token_type](self)
 2392            stmt.add_comments(comments, prepend=True)
 2393            return stmt
 2394
 2395        if self._match_set(self.dialect.tokenizer_class.COMMANDS):
 2396            return self._parse_command()
 2397
 2398        if self._match_text_seq("WHILE"):
 2399            return self._parse_whileblock()
 2400
 2401        expression = self._parse_expression()
 2402        expression = self._parse_set_operations(expression) if expression else self._parse_select()
 2403
 2404        if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False):
 2405            expression = self._parse_pipe_syntax_query(expression)
 2406
 2407        return self._parse_query_modifiers(expression)
 2408
 2409    def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command:
 2410        start = self._prev
 2411        temporary = self._match(TokenType.TEMPORARY)
 2412        materialized = self._match_text_seq("MATERIALIZED")
 2413        iceberg = self._match_text_seq("ICEBERG")
 2414
 2415        kind = self._match_set(self.CREATABLES) and self._prev.text.upper()
 2416        if not kind or (iceberg and kind and kind != "TABLE"):
 2417            return self._parse_as_command(start)
 2418
 2419        concurrently = self._match_text_seq("CONCURRENTLY")
 2420        if_exists = exists or self._parse_exists()
 2421
 2422        tables: exp.Expr | list[exp.Expr] | None
 2423        if kind == "COLUMN":
 2424            tables = self._parse_column()
 2425        elif kind in ("TABLE", "VIEW"):
 2426            tables = self._parse_csv(lambda: self._parse_table_parts(schema=True))
 2427        else:
 2428            tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA")
 2429
 2430        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 2431
 2432        if self._match(TokenType.L_PAREN, advance=False):
 2433            expressions = self._parse_wrapped_csv(self._parse_types)
 2434        else:
 2435            expressions = None
 2436
 2437        cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper()
 2438
 2439        return self.expression(
 2440            exp.Drop(
 2441                exists=if_exists,
 2442                tables=ensure_list(tables),
 2443                expressions=expressions,
 2444                kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind,
 2445                temporary=temporary,
 2446                materialized=materialized,
 2447                cascade=cascade_or_restrict == "CASCADE",
 2448                restrict=cascade_or_restrict == "RESTRICT",
 2449                constraints=self._match_text_seq("CONSTRAINTS"),
 2450                purge=self._match_text_seq("PURGE"),
 2451                cluster=cluster,
 2452                concurrently=concurrently,
 2453                sync=self._match_text_seq("SYNC"),
 2454                iceberg=iceberg,
 2455                force=self._match_text_seq("FORCE"),
 2456            )
 2457        )
 2458
 2459    def _parse_exists(self, not_: bool = False) -> bool | None:
 2460        return (
 2461            self._match_text_seq("IF")
 2462            and (not not_ or self._match(TokenType.NOT))
 2463            and self._match(TokenType.EXISTS)
 2464        )
 2465
 2466    def _parse_create(self) -> exp.Create | exp.Command:
 2467        # Note: this can't be None because we've matched a statement parser
 2468        start = self._prev
 2469
 2470        replace = (
 2471            start.token_type == TokenType.REPLACE
 2472            or self._match_pair(TokenType.OR, TokenType.REPLACE)
 2473            or self._match_pair(TokenType.OR, TokenType.ALTER)
 2474        )
 2475        refresh = self._match_pair(TokenType.OR, TokenType.REFRESH)
 2476
 2477        unique = self._match(TokenType.UNIQUE)
 2478
 2479        if self._match_text_seq("CLUSTERED", "COLUMNSTORE"):
 2480            clustered = True
 2481        elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq(
 2482            "COLUMNSTORE"
 2483        ):
 2484            clustered = False
 2485        else:
 2486            clustered = None
 2487
 2488        if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False):
 2489            self._advance()
 2490
 2491        properties = None
 2492        create_token = self._match_set(self.CREATABLES) and self._prev
 2493
 2494        if not create_token:
 2495            # exp.Properties.Location.POST_CREATE
 2496            properties = self._parse_properties()
 2497            create_token = self._match_set(self.CREATABLES) and self._prev
 2498
 2499            if not properties or not create_token:
 2500                return self._parse_as_command(start)
 2501
 2502        create_token_type = t.cast(Token, create_token).token_type
 2503
 2504        concurrently = self._match_text_seq("CONCURRENTLY")
 2505        exists = self._parse_exists(not_=True)
 2506        this = None
 2507        expression: exp.Expr | None = None
 2508        indexes = None
 2509        no_schema_binding = None
 2510        begin = None
 2511        clone = None
 2512
 2513        def extend_props(temp_props: exp.Properties | None) -> None:
 2514            nonlocal properties
 2515            if properties and temp_props:
 2516                properties.expressions.extend(temp_props.expressions)
 2517            elif temp_props:
 2518                properties = temp_props
 2519
 2520        if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2521            this = self._parse_user_defined_function(kind=create_token_type)
 2522
 2523            # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature)
 2524            extend_props(self._parse_properties())
 2525
 2526            expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None
 2527
 2528            if (
 2529                not expression
 2530                and create_token_type == TokenType.FUNCTION
 2531                and isinstance(this, exp.UserDefinedFunction)
 2532                and this.args.get("wrapped")
 2533            ):
 2534                pre_table_index = self._index
 2535                is_table = self._match(TokenType.TABLE)
 2536
 2537                expression = self._parse_expression()
 2538                overload_mode = bool(
 2539                    expression
 2540                    and self._curr.token_type == TokenType.COMMA
 2541                    and self._next.token_type == TokenType.L_PAREN
 2542                )
 2543                if not overload_mode:
 2544                    self._retreat(pre_table_index)
 2545                    is_table = False
 2546                    expression = None
 2547            else:
 2548                is_table = False
 2549                overload_mode = False
 2550
 2551            extend_props(self._parse_function_properties())
 2552
 2553            if not expression:
 2554                if self._match(TokenType.COMMAND):
 2555                    expression = self._parse_as_command(self._prev)
 2556                else:
 2557                    begin = self._match(TokenType.BEGIN)
 2558                    return_ = self._match_text_seq("RETURN")
 2559
 2560                    if self._match(TokenType.STRING, advance=False):
 2561                        # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property
 2562                        # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement
 2563                        expression = self._parse_string()
 2564                        extend_props(self._parse_properties())
 2565                    else:
 2566                        expression = (
 2567                            self._parse_user_defined_function_expression()
 2568                            if create_token_type == TokenType.FUNCTION
 2569                            else self._parse_block()
 2570                        )
 2571
 2572                    if return_:
 2573                        expression = self.expression(exp.Return(this=expression))
 2574
 2575            if overload_mode and expression:
 2576                expression = self._parse_macro_overloads(
 2577                    t.cast(exp.UserDefinedFunction, this), expression, is_table
 2578                )
 2579        elif create_token_type == TokenType.INDEX:
 2580            # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c)
 2581            if not self._match(TokenType.ON):
 2582                index = self._parse_id_var()
 2583                anonymous = False
 2584            else:
 2585                index = None
 2586                anonymous = True
 2587
 2588            this = self._parse_index(index=index, anonymous=anonymous)
 2589        elif (
 2590            create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER)
 2591        ) or create_token_type == TokenType.TRIGGER:
 2592            if is_constraint := (create_token_type == TokenType.CONSTRAINT):
 2593                create_token = self._prev
 2594
 2595            trigger_name = self._parse_id_var()
 2596            if not trigger_name:
 2597                return self._parse_as_command(start)
 2598
 2599            timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False)
 2600            timing = timing_var.this if timing_var else None
 2601            if not timing:
 2602                return self._parse_as_command(start)
 2603
 2604            events = self._parse_trigger_events()
 2605            if not self._match(TokenType.ON):
 2606                self.raise_error("Expected ON in trigger definition")
 2607
 2608            table = self._parse_table_parts()
 2609            referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None
 2610            deferrable, initially = self._parse_trigger_deferrable()
 2611            referencing = self._parse_trigger_referencing()
 2612            for_each = self._parse_trigger_for_each()
 2613            when = self._match_text_seq("WHEN") and self._parse_wrapped(
 2614                self._parse_disjunction, optional=True
 2615            )
 2616            execute = self._parse_trigger_execute()
 2617
 2618            if execute is None:
 2619                return self._parse_as_command(start)
 2620
 2621            trigger_props = self.expression(
 2622                exp.TriggerProperties(
 2623                    table=table,
 2624                    timing=timing,
 2625                    events=events,
 2626                    execute=execute,
 2627                    constraint=is_constraint,
 2628                    referenced_table=referenced_table,
 2629                    deferrable=deferrable,
 2630                    initially=initially,
 2631                    referencing=referencing,
 2632                    for_each=for_each,
 2633                    when=when,
 2634                )
 2635            )
 2636
 2637            this = trigger_name
 2638            extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else []))
 2639        elif create_token_type == TokenType.TYPE:
 2640            this = self._parse_table_parts(schema=True)
 2641            if not this or not self._match(TokenType.ALIAS):
 2642                return self._parse_as_command(start)
 2643
 2644            if self._match(TokenType.ENUM):
 2645                expression = exp.DataType(
 2646                    this=exp.DType.ENUM,
 2647                    expressions=self._parse_wrapped_csv(self._parse_string),
 2648                )
 2649            elif self._match(TokenType.L_PAREN, advance=False):
 2650                expression = self._parse_schema()
 2651            else:
 2652                return self._parse_as_command(start)
 2653        elif create_token_type in self.DB_CREATABLES:
 2654            table_parts = self._parse_table_parts(
 2655                schema=True, is_db_reference=create_token_type == TokenType.SCHEMA
 2656            )
 2657
 2658            # exp.Properties.Location.POST_NAME
 2659            self._match(TokenType.COMMA)
 2660            extend_props(self._parse_properties(before=True))
 2661
 2662            this = self._parse_schema(this=table_parts)
 2663
 2664            # exp.Properties.Location.POST_SCHEMA and POST_WITH
 2665            extend_props(self._parse_properties())
 2666
 2667            has_alias = self._match(TokenType.ALIAS)
 2668            if not self._match_set(self.DDL_SELECT_TOKENS, advance=False):
 2669                # exp.Properties.Location.POST_ALIAS
 2670                extend_props(self._parse_properties())
 2671
 2672            if create_token_type == TokenType.SEQUENCE:
 2673                expression = self._parse_types()
 2674                props = self._parse_properties()
 2675                if props:
 2676                    sequence_props = exp.SequenceProperties()
 2677                    options = []
 2678                    for prop in props:
 2679                        if isinstance(prop, exp.SequenceProperties):
 2680                            for arg, value in prop.args.items():
 2681                                if arg == "options":
 2682                                    options.extend(value)
 2683                                else:
 2684                                    sequence_props.set(arg, value)
 2685                            prop.pop()
 2686
 2687                    if options:
 2688                        sequence_props.set("options", options)
 2689
 2690                    props.append("expressions", sequence_props)
 2691                    extend_props(props)
 2692            else:
 2693                expression = self._parse_ddl_select()
 2694
 2695                # Some dialects also support using a table as an alias instead of a SELECT.
 2696                # Here we fallback to this as an alternative.
 2697                if not expression and has_alias:
 2698                    expression = self._try_parse(self._parse_table_parts)
 2699
 2700            if create_token_type == TokenType.TABLE:
 2701                # exp.Properties.Location.POST_EXPRESSION
 2702                extend_props(self._parse_properties())
 2703
 2704                indexes = []
 2705                while True:
 2706                    index = self._parse_index()
 2707
 2708                    # exp.Properties.Location.POST_INDEX
 2709                    extend_props(self._parse_properties())
 2710                    if not index:
 2711                        break
 2712                    else:
 2713                        self._match(TokenType.COMMA)
 2714                        indexes.append(index)
 2715            elif create_token_type == TokenType.VIEW:
 2716                if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"):
 2717                    no_schema_binding = True
 2718            elif create_token_type in (TokenType.SINK, TokenType.SOURCE):
 2719                extend_props(self._parse_properties())
 2720
 2721            shallow = self._match_text_seq("SHALLOW")
 2722
 2723            if self._match_texts(self.CLONE_KEYWORDS):
 2724                copy = self._prev.text.lower() == "copy"
 2725                clone = self.expression(
 2726                    exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy)
 2727                )
 2728
 2729        if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False):
 2730            return self._parse_as_command(start)
 2731
 2732        create_kind_text = create_token.text.upper()
 2733        return self.expression(
 2734            exp.Create(
 2735                this=this,
 2736                kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text,
 2737                replace=replace,
 2738                refresh=refresh,
 2739                unique=unique,
 2740                expression=expression,
 2741                exists=exists,
 2742                properties=properties,
 2743                indexes=indexes,
 2744                no_schema_binding=no_schema_binding,
 2745                begin=begin,
 2746                clone=clone,
 2747                concurrently=concurrently,
 2748                clustered=clustered,
 2749            )
 2750        )
 2751
 2752    def _parse_sequence_properties(self) -> exp.SequenceProperties | None:
 2753        seq = exp.SequenceProperties()
 2754
 2755        options = []
 2756        index = self._index
 2757
 2758        while self._curr:
 2759            self._match(TokenType.COMMA)
 2760            if self._match_text_seq("INCREMENT"):
 2761                self._match_text_seq("BY")
 2762                self._match_text_seq("=")
 2763                seq.set("increment", self._parse_term())
 2764            elif self._match_text_seq("MINVALUE"):
 2765                seq.set("minvalue", self._parse_term())
 2766            elif self._match_text_seq("MAXVALUE"):
 2767                seq.set("maxvalue", self._parse_term())
 2768            elif self._match_text_seq("START"):
 2769                self._match_text_seq("WITH")
 2770                self._match_text_seq("=")
 2771                seq.set("start", self._parse_term())
 2772            elif self._match_text_seq("CACHE"):
 2773                # T-SQL allows empty CACHE which is initialized dynamically
 2774                seq.set("cache", self._parse_number() or True)
 2775            elif self._match_text_seq("OWNED", "BY"):
 2776                # "OWNED BY NONE" is the default
 2777                seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column())
 2778            else:
 2779                opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False)
 2780                if opt:
 2781                    options.append(opt)
 2782                else:
 2783                    break
 2784
 2785        seq.set("options", options if options else None)
 2786        return None if self._index == index else seq
 2787
 2788    def _parse_trigger_events(self) -> list[exp.TriggerEvent]:
 2789        events = []
 2790
 2791        while True:
 2792            event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper()
 2793
 2794            if not event_type:
 2795                self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)")
 2796
 2797            columns = (
 2798                self._parse_csv(self._parse_column)
 2799                if event_type == "UPDATE" and self._match_text_seq("OF")
 2800                else None
 2801            )
 2802
 2803            events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns)))
 2804
 2805            if not self._match(TokenType.OR):
 2806                break
 2807
 2808        return events
 2809
 2810    def _parse_trigger_deferrable(
 2811        self,
 2812    ) -> tuple[str | None, str | None]:
 2813        deferrable_var = self._parse_var_from_options(
 2814            self.TRIGGER_DEFERRABLE, raise_unmatched=False
 2815        )
 2816        deferrable = deferrable_var.this if deferrable_var else None
 2817
 2818        initially = None
 2819        if deferrable and self._match_text_seq("INITIALLY"):
 2820            initially = (
 2821                self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None
 2822            )
 2823
 2824        return deferrable, initially
 2825
 2826    def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None:
 2827        if not self._match_text_seq(keyword):
 2828            return None
 2829        if not self._match_text_seq("TABLE"):
 2830            self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause")
 2831        self._match_text_seq("AS")
 2832        return self._parse_id_var()
 2833
 2834    def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None:
 2835        if not self._match_text_seq("REFERENCING"):
 2836            return None
 2837
 2838        old_alias = None
 2839        new_alias = None
 2840
 2841        while True:
 2842            if alias := self._parse_trigger_referencing_clause("OLD"):
 2843                if old_alias is not None:
 2844                    self.raise_error("Duplicate OLD clause in REFERENCING")
 2845                old_alias = alias
 2846            elif alias := self._parse_trigger_referencing_clause("NEW"):
 2847                if new_alias is not None:
 2848                    self.raise_error("Duplicate NEW clause in REFERENCING")
 2849                new_alias = alias
 2850            else:
 2851                break
 2852
 2853        if old_alias is None and new_alias is None:
 2854            self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE")
 2855
 2856        return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias))
 2857
 2858    def _parse_trigger_for_each(self) -> str | None:
 2859        if not self._match_text_seq("FOR", "EACH"):
 2860            return None
 2861
 2862        return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None
 2863
 2864    def _parse_trigger_execute(self) -> exp.TriggerExecute | None:
 2865        if not self._match(TokenType.EXECUTE):
 2866            return None
 2867
 2868        if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)):
 2869            self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE")
 2870
 2871        func_call = self._parse_column()
 2872        return self.expression(exp.TriggerExecute(this=func_call))
 2873
 2874    def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None:
 2875        # only used for teradata currently
 2876        self._match(TokenType.COMMA)
 2877
 2878        kwargs = {
 2879            "no": self._match_text_seq("NO"),
 2880            "dual": self._match_text_seq("DUAL"),
 2881            "before": self._match_text_seq("BEFORE"),
 2882            "default": self._match_text_seq("DEFAULT"),
 2883            "local": (self._match_text_seq("LOCAL") and "LOCAL")
 2884            or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"),
 2885            "after": self._match_text_seq("AFTER"),
 2886            "minimum": self._match_texts(("MIN", "MINIMUM")),
 2887            "maximum": self._match_texts(("MAX", "MAXIMUM")),
 2888        }
 2889
 2890        if self._match_texts(self.PROPERTY_PARSERS):
 2891            parser = self.PROPERTY_PARSERS[self._prev.text.upper()]
 2892            try:
 2893                return parser(self, **{k: v for k, v in kwargs.items() if v})
 2894            except TypeError:
 2895                self.raise_error(f"Cannot parse property '{self._prev.text}'")
 2896
 2897        if self._match_text_seq("CHARACTER", "SET"):
 2898            return self._parse_character_set(default=bool(kwargs["default"]))
 2899
 2900        return None
 2901
 2902    def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]:
 2903        return self._parse_wrapped_csv(self._parse_property)
 2904
 2905    def _parse_property(self) -> exp.Expr | list[exp.Expr] | None:
 2906        if self._match_texts(self.PROPERTY_PARSERS):
 2907            return self.PROPERTY_PARSERS[self._prev.text.upper()](self)
 2908
 2909        if self._match_text_seq("CHARACTER", "SET"):
 2910            return self._parse_character_set()
 2911
 2912        if self._match(TokenType.DEFAULT):
 2913            if self._match_texts(self.PROPERTY_PARSERS):
 2914                return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True)
 2915
 2916            if self._match_text_seq("CHARACTER", "SET"):
 2917                return self._parse_character_set(default=True)
 2918
 2919        if self._match_text_seq("COMPOUND", "SORTKEY"):
 2920            return self._parse_sortkey(compound=True)
 2921
 2922        if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"):
 2923            return self.expression(exp.ParameterStyleProperty(this="PANDAS"))
 2924
 2925        if self._match_text_seq("NOT", "DETERMINISTIC"):
 2926            return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 2927
 2928        index = self._index
 2929
 2930        seq_props = self._parse_sequence_properties()
 2931        if seq_props:
 2932            return seq_props
 2933
 2934        self._retreat(index)
 2935        return self._parse_key_value_property()
 2936
 2937    def _parse_key_value_property(
 2938        self, parse_value: t.Callable[[], exp.Expr | None] | None = None
 2939    ) -> exp.Property | None:
 2940        index = self._index
 2941        key = self._parse_column()
 2942
 2943        if not self._match(TokenType.EQ):
 2944            self._retreat(index)
 2945            return None
 2946
 2947        # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise
 2948        if isinstance(key, exp.Column):
 2949            key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name)
 2950
 2951        value = (
 2952            parse_value()
 2953            if parse_value
 2954            else self._parse_bitwise() or self._parse_var(any_token=True)
 2955        )
 2956
 2957        # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier())
 2958        if isinstance(value, exp.Column):
 2959            value = exp.var(value.name)
 2960
 2961        return self.expression(exp.Property(this=key, value=value))
 2962
 2963    def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty:
 2964        if self._match_text_seq("BY"):
 2965            return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string()))
 2966
 2967        self._match(TokenType.ALIAS)
 2968        input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None
 2969        output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None
 2970
 2971        return self.expression(
 2972            exp.FileFormatProperty(
 2973                this=(
 2974                    self.expression(
 2975                        exp.InputOutputFormat(
 2976                            input_format=input_format, output_format=output_format
 2977                        )
 2978                    )
 2979                    if input_format or output_format
 2980                    else self._parse_var_or_string() or self._parse_number() or self._parse_id_var()
 2981                ),
 2982                hive_format=True,
 2983            )
 2984        )
 2985
 2986    def _parse_unquoted_field(self) -> exp.Expr | None:
 2987        field = self._parse_field()
 2988        if isinstance(field, exp.Identifier) and not field.quoted:
 2989            field = exp.var(field)
 2990
 2991        return field
 2992
 2993    def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E:
 2994        self._match(TokenType.EQ)
 2995        self._match(TokenType.ALIAS)
 2996
 2997        return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs))
 2998
 2999    def _parse_properties(self, before: bool | None = None) -> exp.Properties | None:
 3000        properties = []
 3001        while True:
 3002            if before:
 3003                prop = self._parse_property_before()
 3004            else:
 3005                prop = self._parse_property()
 3006            if not prop:
 3007                break
 3008            for p in ensure_list(prop):
 3009                properties.append(p)
 3010
 3011        if properties:
 3012            return self.expression(exp.Properties(expressions=properties))
 3013
 3014        return None
 3015
 3016    def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty:
 3017        return self.expression(
 3018            exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION"))
 3019        )
 3020
 3021    def _parse_sql_security(self) -> exp.SqlSecurityProperty:
 3022        return self.expression(
 3023            exp.SqlSecurityProperty(
 3024                this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper()
 3025            )
 3026        )
 3027
 3028    def _parse_settings_property(self) -> exp.SettingsProperty:
 3029        return self.expression(
 3030            exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment))
 3031        )
 3032
 3033    def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None:
 3034        if not self._match_text_seq("ON", "NULL", "INPUT"):
 3035            self._retreat(self._index - 1)
 3036            return None
 3037
 3038        return self.expression(exp.CalledOnNullInputProperty())
 3039
 3040    def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty:
 3041        if self._index >= 2:
 3042            pre_volatile_token = self._tokens[self._index - 2]
 3043        else:
 3044            pre_volatile_token = None
 3045
 3046        if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS:
 3047            return exp.VolatileProperty()
 3048
 3049        return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 3050
 3051    def _parse_retention_period(self) -> exp.Var:
 3052        # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...}
 3053        number = self._parse_number()
 3054        number_str = f"{number} " if number else ""
 3055        unit = self._parse_var(any_token=True)
 3056        return exp.var(f"{number_str}{unit}")
 3057
 3058    def _parse_system_versioning_property(
 3059        self, with_: bool = False
 3060    ) -> exp.WithSystemVersioningProperty:
 3061        self._match(TokenType.EQ)
 3062        prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_))
 3063
 3064        if self._match_text_seq("OFF"):
 3065            prop.set("on", False)
 3066            return prop
 3067
 3068        self._match(TokenType.ON)
 3069        if self._match(TokenType.L_PAREN):
 3070            while self._curr and not self._match(TokenType.R_PAREN):
 3071                if self._match_text_seq("HISTORY_TABLE", "="):
 3072                    prop.set("this", self._parse_table_parts())
 3073                elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="):
 3074                    prop.set("data_consistency", self._advance_any() and self._prev.text.upper())
 3075                elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="):
 3076                    prop.set("retention_period", self._parse_retention_period())
 3077
 3078                self._match(TokenType.COMMA)
 3079
 3080        return prop
 3081
 3082    def _parse_data_deletion_property(self) -> exp.DataDeletionProperty:
 3083        self._match(TokenType.EQ)
 3084        on = self._match_text_seq("ON") or not self._match_text_seq("OFF")
 3085        prop = self.expression(exp.DataDeletionProperty(on=on))
 3086
 3087        if self._match(TokenType.L_PAREN):
 3088            while self._curr and not self._match(TokenType.R_PAREN):
 3089                if self._match_text_seq("FILTER_COLUMN", "="):
 3090                    prop.set("filter_column", self._parse_column())
 3091                elif self._match_text_seq("RETENTION_PERIOD", "="):
 3092                    prop.set("retention_period", self._parse_retention_period())
 3093
 3094                self._match(TokenType.COMMA)
 3095
 3096        return prop
 3097
 3098    def _parse_distributed_property(self) -> exp.DistributedByProperty:
 3099        kind = "HASH"
 3100        expressions: list[exp.Expr] | None = None
 3101        if self._match_text_seq("BY", "HASH"):
 3102            expressions = self._parse_wrapped_csv(self._parse_id_var)
 3103        elif self._match_text_seq("BY", "RANDOM"):
 3104            kind = "RANDOM"
 3105
 3106        # If the BUCKETS keyword is not present, the number of buckets is AUTO
 3107        buckets: exp.Expr | None = None
 3108        if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"):
 3109            buckets = self._parse_number()
 3110
 3111        return self.expression(
 3112            exp.DistributedByProperty(
 3113                expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order()
 3114            )
 3115        )
 3116
 3117    def _parse_composite_key_property(self, expr_type: type[E]) -> E:
 3118        self._match_text_seq("KEY")
 3119        expressions = self._parse_wrapped_id_vars()
 3120        return self.expression(expr_type(expressions=expressions))
 3121
 3122    def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]:
 3123        if self._match_text_seq("(", "SYSTEM_VERSIONING"):
 3124            prop = self._parse_system_versioning_property(with_=True)
 3125            self._match_r_paren()
 3126            return prop
 3127
 3128        if self._match(TokenType.L_PAREN, advance=False):
 3129            result: list[exp.Expr] = []
 3130            for i in self._parse_wrapped_properties():
 3131                result.extend(i) if isinstance(i, list) else result.append(i)
 3132            return result
 3133
 3134        if self._match_text_seq("JOURNAL"):
 3135            return self._parse_withjournaltable()
 3136
 3137        if self._match_texts(self.VIEW_ATTRIBUTES):
 3138            return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper()))
 3139
 3140        if self._match_text_seq("DATA"):
 3141            return self._parse_withdata(no=False)
 3142        elif self._match_text_seq("NO", "DATA"):
 3143            return self._parse_withdata(no=True)
 3144
 3145        if self._match(TokenType.SERDE_PROPERTIES, advance=False):
 3146            return self._parse_serde_properties(with_=True)
 3147
 3148        if self._match(TokenType.SCHEMA):
 3149            return self.expression(
 3150                exp.WithSchemaBindingProperty(
 3151                    this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS)
 3152                )
 3153            )
 3154
 3155        if self._match_texts(self.PROCEDURE_OPTIONS, advance=False):
 3156            return self.expression(
 3157                exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option))
 3158            )
 3159
 3160        if not self._next:
 3161            return None
 3162
 3163        return self._parse_withisolatedloading()
 3164
 3165    def _parse_procedure_option(self) -> exp.Expr | None:
 3166        if self._match_text_seq("EXECUTE", "AS"):
 3167            return self.expression(
 3168                exp.ExecuteAsProperty(
 3169                    this=self._parse_var_from_options(
 3170                        self.EXECUTE_AS_OPTIONS, raise_unmatched=False
 3171                    )
 3172                    or self._parse_string()
 3173                )
 3174            )
 3175
 3176        return self._parse_var_from_options(self.PROCEDURE_OPTIONS)
 3177
 3178    # https://dev.mysql.com/doc/refman/8.0/en/create-view.html
 3179    def _parse_definer(self) -> exp.DefinerProperty | None:
 3180        self._match(TokenType.EQ)
 3181
 3182        user = self._parse_id_var()
 3183        self._match(TokenType.PARAMETER)
 3184        host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text)
 3185
 3186        if not user or not host:
 3187            return None
 3188
 3189        return exp.DefinerProperty(this=f"{user}@{host}")
 3190
 3191    def _parse_withjournaltable(self) -> exp.WithJournalTableProperty:
 3192        self._match(TokenType.TABLE)
 3193        self._match(TokenType.EQ)
 3194        return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts()))
 3195
 3196    def _parse_log(self, no: bool = False) -> exp.LogProperty:
 3197        return self.expression(exp.LogProperty(no=no))
 3198
 3199    def _parse_journal(self, **kwargs) -> exp.JournalProperty:
 3200        return self.expression(exp.JournalProperty(**kwargs))
 3201
 3202    def _parse_checksum(self) -> exp.ChecksumProperty:
 3203        self._match(TokenType.EQ)
 3204
 3205        on = None
 3206        if self._match(TokenType.ON):
 3207            on = True
 3208        elif self._match_text_seq("OFF"):
 3209            on = False
 3210
 3211        return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT)))
 3212
 3213    def _parse_cluster(self) -> exp.Cluster:
 3214        self._match(TokenType.CLUSTER_BY)
 3215        return self.expression(
 3216            exp.Cluster(
 3217                expressions=self._parse_csv(self._parse_column),
 3218            )
 3219        )
 3220
 3221    def _parse_cluster_property(self) -> exp.ClusterProperty:
 3222        return self.expression(
 3223            exp.ClusterProperty(
 3224                expressions=self._parse_wrapped_csv(self._parse_column),
 3225            )
 3226        )
 3227
 3228    def _parse_clustered_by(self) -> exp.ClusteredByProperty:
 3229        self._match_text_seq("BY")
 3230
 3231        self._match_l_paren()
 3232        expressions = self._parse_csv(self._parse_column)
 3233        self._match_r_paren()
 3234
 3235        if self._match_text_seq("SORTED", "BY"):
 3236            self._match_l_paren()
 3237            sorted_by = self._parse_csv(self._parse_ordered)
 3238            self._match_r_paren()
 3239        else:
 3240            sorted_by = None
 3241
 3242        self._match(TokenType.INTO)
 3243        buckets = self._parse_number()
 3244        self._match_text_seq("BUCKETS")
 3245
 3246        return self.expression(
 3247            exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets)
 3248        )
 3249
 3250    def _parse_copy_property(self) -> exp.CopyGrantsProperty | None:
 3251        if not self._match_text_seq("GRANTS"):
 3252            self._retreat(self._index - 1)
 3253            return None
 3254
 3255        return self.expression(exp.CopyGrantsProperty())
 3256
 3257    def _parse_freespace(self) -> exp.FreespaceProperty:
 3258        self._match(TokenType.EQ)
 3259        return self.expression(
 3260            exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT))
 3261        )
 3262
 3263    def _parse_mergeblockratio(
 3264        self, no: bool = False, default: bool = False
 3265    ) -> exp.MergeBlockRatioProperty:
 3266        if self._match(TokenType.EQ):
 3267            return self.expression(
 3268                exp.MergeBlockRatioProperty(
 3269                    this=self._parse_number(), percent=self._match(TokenType.PERCENT)
 3270                )
 3271            )
 3272
 3273        return self.expression(exp.MergeBlockRatioProperty(no=no, default=default))
 3274
 3275    def _parse_datablocksize(
 3276        self,
 3277        default: bool | None = None,
 3278        minimum: bool | None = None,
 3279        maximum: bool | None = None,
 3280    ) -> exp.DataBlocksizeProperty:
 3281        self._match(TokenType.EQ)
 3282        size = self._parse_number()
 3283
 3284        units = None
 3285        if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")):
 3286            units = self._prev.text
 3287
 3288        return self.expression(
 3289            exp.DataBlocksizeProperty(
 3290                size=size, units=units, default=default, minimum=minimum, maximum=maximum
 3291            )
 3292        )
 3293
 3294    def _parse_blockcompression(self) -> exp.BlockCompressionProperty:
 3295        self._match(TokenType.EQ)
 3296        always = self._match_text_seq("ALWAYS")
 3297        manual = self._match_text_seq("MANUAL")
 3298        never = self._match_text_seq("NEVER")
 3299        default = self._match_text_seq("DEFAULT")
 3300
 3301        autotemp = None
 3302        if self._match_text_seq("AUTOTEMP"):
 3303            autotemp = self._parse_schema()
 3304
 3305        return self.expression(
 3306            exp.BlockCompressionProperty(
 3307                always=always, manual=manual, never=never, default=default, autotemp=autotemp
 3308            )
 3309        )
 3310
 3311    def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None:
 3312        index = self._index
 3313        no = self._match_text_seq("NO")
 3314        concurrent = self._match_text_seq("CONCURRENT")
 3315
 3316        if not self._match_text_seq("ISOLATED", "LOADING"):
 3317            self._retreat(index)
 3318            return None
 3319
 3320        target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False)
 3321        return self.expression(
 3322            exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target)
 3323        )
 3324
 3325    def _parse_locking(self) -> exp.LockingProperty:
 3326        if self._match(TokenType.TABLE):
 3327            kind = "TABLE"
 3328        elif self._match(TokenType.VIEW):
 3329            kind = "VIEW"
 3330        elif self._match(TokenType.ROW):
 3331            kind = "ROW"
 3332        elif self._match_text_seq("DATABASE"):
 3333            kind = "DATABASE"
 3334        else:
 3335            kind = None
 3336
 3337        if kind in ("DATABASE", "TABLE", "VIEW"):
 3338            this = self._parse_table_parts()
 3339        else:
 3340            this = None
 3341
 3342        if self._match(TokenType.FOR):
 3343            for_or_in = "FOR"
 3344        elif self._match(TokenType.IN):
 3345            for_or_in = "IN"
 3346        else:
 3347            for_or_in = None
 3348
 3349        if self._match_text_seq("ACCESS"):
 3350            lock_type = "ACCESS"
 3351        elif self._match_texts(("EXCL", "EXCLUSIVE")):
 3352            lock_type = "EXCLUSIVE"
 3353        elif self._match_text_seq("SHARE"):
 3354            lock_type = "SHARE"
 3355        elif self._match_text_seq("READ"):
 3356            lock_type = "READ"
 3357        elif self._match_text_seq("WRITE"):
 3358            lock_type = "WRITE"
 3359        elif self._match_text_seq("CHECKSUM"):
 3360            lock_type = "CHECKSUM"
 3361        else:
 3362            lock_type = None
 3363
 3364        override = self._match_text_seq("OVERRIDE")
 3365
 3366        return self.expression(
 3367            exp.LockingProperty(
 3368                this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override
 3369            )
 3370        )
 3371
 3372    def _parse_partition_by(self) -> list[exp.Expr]:
 3373        if self._match(TokenType.PARTITION_BY):
 3374            return self._parse_csv(self._parse_disjunction)
 3375        return []
 3376
 3377    def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec:
 3378        def _parse_partition_bound_expr() -> exp.Expr | None:
 3379            if self._match_text_seq("MINVALUE"):
 3380                return exp.var("MINVALUE")
 3381            if self._match_text_seq("MAXVALUE"):
 3382                return exp.var("MAXVALUE")
 3383            return self._parse_bitwise()
 3384
 3385        this: exp.Expr | list[exp.Expr] | None = None
 3386        expression = None
 3387        from_expressions = None
 3388        to_expressions = None
 3389
 3390        if self._match(TokenType.IN):
 3391            this = self._parse_wrapped_csv(self._parse_bitwise)
 3392        elif self._match(TokenType.FROM):
 3393            from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3394            self._match_text_seq("TO")
 3395            to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3396        elif self._match_text_seq("WITH", "(", "MODULUS"):
 3397            this = self._parse_number()
 3398            self._match_text_seq(",", "REMAINDER")
 3399            expression = self._parse_number()
 3400            self._match_r_paren()
 3401        else:
 3402            self.raise_error("Failed to parse partition bound spec.")
 3403
 3404        return self.expression(
 3405            exp.PartitionBoundSpec(
 3406                this=this,
 3407                expression=expression,
 3408                from_expressions=from_expressions,
 3409                to_expressions=to_expressions,
 3410            )
 3411        )
 3412
 3413    # https://www.postgresql.org/docs/current/sql-createtable.html
 3414    def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None:
 3415        if not self._match_text_seq("OF"):
 3416            self._retreat(self._index - 1)
 3417            return None
 3418
 3419        this = self._parse_table(schema=True)
 3420
 3421        if self._match(TokenType.DEFAULT):
 3422            expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT")
 3423        elif self._match_text_seq("FOR", "VALUES"):
 3424            expression = self._parse_partition_bound_spec()
 3425        else:
 3426            self.raise_error("Expecting either DEFAULT or FOR VALUES clause.")
 3427
 3428        return self.expression(exp.PartitionedOfProperty(this=this, expression=expression))
 3429
 3430    def _parse_partitioned_by(self) -> exp.PartitionedByProperty:
 3431        self._match(TokenType.EQ)
 3432        return self.expression(
 3433            exp.PartitionedByProperty(
 3434                this=self._parse_schema() or self._parse_bracket(self._parse_field())
 3435            )
 3436        )
 3437
 3438    def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty:
 3439        if self._match_text_seq("AND", "STATISTICS"):
 3440            statistics = True
 3441        elif self._match_text_seq("AND", "NO", "STATISTICS"):
 3442            statistics = False
 3443        else:
 3444            statistics = None
 3445
 3446        return self.expression(exp.WithDataProperty(no=no, statistics=statistics))
 3447
 3448    def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None:
 3449        if self._match_text_seq("SQL"):
 3450            return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL"))
 3451        return None
 3452
 3453    def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None:
 3454        if self._match_text_seq("SQL", "DATA"):
 3455            return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA"))
 3456        return None
 3457
 3458    def _parse_no_property(self) -> exp.Expr | None:
 3459        if self._match_text_seq("PRIMARY", "INDEX"):
 3460            return exp.NoPrimaryIndexProperty()
 3461        if self._match_text_seq("SQL"):
 3462            return self.expression(exp.SqlReadWriteProperty(this="NO SQL"))
 3463        return None
 3464
 3465    def _parse_on_property(self) -> exp.Expr | None:
 3466        if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"):
 3467            return exp.OnCommitProperty()
 3468        if self._match_text_seq("COMMIT", "DELETE", "ROWS"):
 3469            return exp.OnCommitProperty(delete=True)
 3470        return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var())))
 3471
 3472    def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None:
 3473        if self._match_text_seq("SQL", "DATA"):
 3474            return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA"))
 3475        return None
 3476
 3477    def _parse_distkey(self) -> exp.DistKeyProperty:
 3478        return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var)))
 3479
 3480    def _parse_create_like(self) -> exp.LikeProperty | None:
 3481        table = self._parse_table(schema=True)
 3482
 3483        options = []
 3484        while self._match_texts(("INCLUDING", "EXCLUDING")):
 3485            this = self._prev.text.upper()
 3486
 3487            id_var = self._parse_id_var()
 3488            if not id_var:
 3489                return None
 3490
 3491            options.append(
 3492                self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper())))
 3493            )
 3494
 3495        return self.expression(exp.LikeProperty(this=table, expressions=options))
 3496
 3497    def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty:
 3498        return self.expression(
 3499            exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound)
 3500        )
 3501
 3502    def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty:
 3503        self._match(TokenType.EQ)
 3504        return self.expression(
 3505            exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default)
 3506        )
 3507
 3508    def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty:
 3509        self._match_text_seq("WITH", "CONNECTION")
 3510        return self.expression(
 3511            exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts())
 3512        )
 3513
 3514    def _parse_returns(self) -> exp.ReturnsProperty:
 3515        value: exp.Expr | None
 3516        null = None
 3517        is_table = self._match(TokenType.TABLE)
 3518
 3519        if is_table:
 3520            if self._match(TokenType.LT):
 3521                value = self.expression(
 3522                    exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types))
 3523                )
 3524                if not self._match(TokenType.GT):
 3525                    self.raise_error("Expecting >")
 3526            else:
 3527                value = self._parse_schema(exp.var("TABLE"))
 3528        elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"):
 3529            null = True
 3530            value = None
 3531        else:
 3532            value = self._parse_types()
 3533
 3534        return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null))
 3535
 3536    def _parse_describe(self) -> exp.Describe:
 3537        kind = self._prev.text if self._match_set(self.CREATABLES) else None
 3538        style: str | None = (
 3539            self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None
 3540        )
 3541        if self._match(TokenType.DOT):
 3542            style = None
 3543            self._retreat(self._index - 2)
 3544
 3545        format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None
 3546
 3547        if self._match_set(self.STATEMENT_PARSERS, advance=False):
 3548            this = self._parse_statement()
 3549        else:
 3550            this = self._parse_table(schema=True)
 3551
 3552        properties = self._parse_properties()
 3553        expressions = properties.expressions if properties else None
 3554        partition = self._parse_partition()
 3555        return self.expression(
 3556            exp.Describe(
 3557                this=this,
 3558                style=style,
 3559                kind=kind,
 3560                expressions=expressions,
 3561                partition=partition,
 3562                format=format,
 3563                as_json=self._match_text_seq("AS", "JSON"),
 3564            )
 3565        )
 3566
 3567    def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts:
 3568        kind = self._prev.text.upper()
 3569        expressions = []
 3570
 3571        def parse_conditional_insert() -> exp.ConditionalInsert | None:
 3572            if self._match(TokenType.WHEN):
 3573                expression = self._parse_disjunction()
 3574                self._match(TokenType.THEN)
 3575            else:
 3576                expression = None
 3577
 3578            else_ = self._match(TokenType.ELSE)
 3579
 3580            if not self._match(TokenType.INTO):
 3581                return None
 3582
 3583            return self.expression(
 3584                exp.ConditionalInsert(
 3585                    this=self.expression(
 3586                        exp.Insert(
 3587                            this=self._parse_table(schema=True),
 3588                            expression=self._parse_derived_table_values(),
 3589                        )
 3590                    ),
 3591                    expression=expression,
 3592                    else_=else_,
 3593                )
 3594            )
 3595
 3596        expression = parse_conditional_insert()
 3597        while expression is not None:
 3598            expressions.append(expression)
 3599            expression = parse_conditional_insert()
 3600
 3601        return self.expression(
 3602            exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()),
 3603            comments=comments,
 3604        )
 3605
 3606    def _parse_insert(self) -> exp.Insert | exp.MultitableInserts:
 3607        comments: list[str] = []
 3608        hint = self._parse_hint()
 3609        overwrite = self._match(TokenType.OVERWRITE)
 3610        ignore = self._match(TokenType.IGNORE)
 3611        local = self._match_text_seq("LOCAL")
 3612        alternative = None
 3613        is_function = None
 3614
 3615        if self._match_text_seq("DIRECTORY"):
 3616            this: exp.Expr | None = self.expression(
 3617                exp.Directory(
 3618                    this=self._parse_var_or_string(),
 3619                    local=local,
 3620                    row_format=self._parse_row_format(match_row=True),
 3621                )
 3622            )
 3623        else:
 3624            if self._match_set((TokenType.FIRST, TokenType.ALL)):
 3625                comments += ensure_list(self._prev_comments)
 3626                return self._parse_multitable_inserts(comments)
 3627
 3628            if self._match(TokenType.OR):
 3629                alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text
 3630
 3631            self._match(TokenType.INTO)
 3632            comments += ensure_list(self._prev_comments)
 3633            self._match(TokenType.TABLE)
 3634            is_function = self._match(TokenType.FUNCTION)
 3635
 3636            this = self._parse_function() if is_function else self._parse_insert_table()
 3637
 3638        # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant
 3639        set_values = None
 3640        if self._match(TokenType.SET):
 3641            columns = []
 3642            values = []
 3643
 3644            def _parse_set_assignment() -> exp.Expr | None:
 3645                target = self._parse_column()
 3646                if isinstance(target, exp.Column) and self._match(TokenType.EQ):
 3647                    if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3648                        value: exp.Expr | None = exp.var(self._prev.text.upper())
 3649                    else:
 3650                        value = self._parse_disjunction()
 3651
 3652                    if value:
 3653                        columns.append(target.this)
 3654                        values.append(value)
 3655                        return value
 3656
 3657                self.raise_error("Expected column assignment in INSERT ... SET")
 3658                return None
 3659
 3660            self._parse_csv(_parse_set_assignment)
 3661
 3662            this = self.expression(exp.Schema(this=this, expressions=columns))
 3663            set_values = self.expression(
 3664                exp.Values(
 3665                    expressions=[exp.Tuple(expressions=values)],
 3666                    alias=self._parse_table_alias(),
 3667                )
 3668            )
 3669
 3670        returning = self._parse_returning()  # TSQL allows RETURNING before source
 3671
 3672        stored = self._match_text_seq("STORED") and self._parse_stored()
 3673        by_name = self._match_text_seq("BY", "NAME")
 3674        exists = self._parse_exists()
 3675        replace_where = None
 3676        replace_using = None
 3677
 3678        if self._match(TokenType.REPLACE):
 3679            if self._match(TokenType.WHERE):
 3680                replace_where = self._parse_disjunction()
 3681            elif self._match(TokenType.USING):
 3682                replace_using = self._parse_using_identifiers()
 3683
 3684        return self.expression(
 3685            exp.Insert(
 3686                hint=hint,
 3687                is_function=is_function,
 3688                this=this,
 3689                stored=stored,
 3690                by_name=by_name,
 3691                exists=exists,
 3692                where=replace_where,
 3693                using=replace_using,
 3694                partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(),
 3695                settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(),
 3696                default=self._match_text_seq("DEFAULT", "VALUES"),
 3697                expression=set_values
 3698                or self._parse_derived_table_values(allow_value_synonym=True)
 3699                or self._parse_ddl_select(),
 3700                conflict=self._parse_on_conflict(),
 3701                returning=returning or self._parse_returning(),
 3702                overwrite=overwrite,
 3703                alternative=alternative,
 3704                ignore=ignore,
 3705                source=self._match(TokenType.TABLE) and self._parse_table(),
 3706            ),
 3707            comments=comments,
 3708        )
 3709
 3710    def _parse_insert_table(self) -> exp.Expr | None:
 3711        this = self._parse_table(schema=True, parse_partition=True)
 3712        if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False):
 3713            this.set("alias", self._parse_table_alias())
 3714        return this
 3715
 3716    def _parse_kill(self) -> exp.Kill:
 3717        kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None
 3718
 3719        return self.expression(exp.Kill(this=self._parse_primary(), kind=kind))
 3720
 3721    def _parse_on_conflict(self) -> exp.OnConflict | None:
 3722        conflict = self._match_text_seq("ON", "CONFLICT")
 3723        duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY")
 3724
 3725        if not conflict and not duplicate:
 3726            return None
 3727
 3728        conflict_keys = None
 3729        constraint = None
 3730
 3731        if conflict:
 3732            if self._match_text_seq("ON", "CONSTRAINT"):
 3733                constraint = self._parse_id_var()
 3734            elif self._match(TokenType.L_PAREN):
 3735                conflict_keys = self._parse_csv(self._parse_indexed_column)
 3736                self._match_r_paren()
 3737
 3738        index_predicate = self._parse_where()
 3739
 3740        action = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 3741        if self._prev.token_type == TokenType.UPDATE:
 3742            self._match(TokenType.SET)
 3743            expressions = self._parse_csv(self._parse_update_assignment)
 3744        else:
 3745            expressions = None
 3746
 3747        return self.expression(
 3748            exp.OnConflict(
 3749                duplicate=duplicate,
 3750                expressions=expressions,
 3751                action=action,
 3752                conflict_keys=conflict_keys,
 3753                index_predicate=index_predicate,
 3754                constraint=constraint,
 3755                where=self._parse_where(),
 3756            )
 3757        )
 3758
 3759    def _parse_returning(self) -> exp.Returning | None:
 3760        if not self._match(TokenType.RETURNING):
 3761            return None
 3762        return self.expression(
 3763            exp.Returning(
 3764                expressions=self._parse_csv(self._parse_expression),
 3765                into=self._match(TokenType.INTO) and self._parse_table_part(),
 3766            )
 3767        )
 3768
 3769    def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3770        if not self._match(TokenType.FORMAT):
 3771            return None
 3772        return self._parse_row_format()
 3773
 3774    def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None:
 3775        index = self._index
 3776        with_ = with_ or self._match_text_seq("WITH")
 3777
 3778        if not self._match(TokenType.SERDE_PROPERTIES):
 3779            self._retreat(index)
 3780            return None
 3781        return self.expression(
 3782            exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_)
 3783        )
 3784
 3785    def _parse_row_format(
 3786        self, match_row: bool = False
 3787    ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3788        if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT):
 3789            return None
 3790
 3791        if self._match_text_seq("SERDE"):
 3792            this = self._parse_string()
 3793
 3794            serde_properties = self._parse_serde_properties()
 3795
 3796            return self.expression(
 3797                exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties)
 3798            )
 3799
 3800        self._match_text_seq("DELIMITED")
 3801
 3802        kwargs = {}
 3803
 3804        if self._match_text_seq("FIELDS", "TERMINATED", "BY"):
 3805            kwargs["fields"] = self._parse_string()
 3806            if self._match_text_seq("ESCAPED", "BY"):
 3807                kwargs["escaped"] = self._parse_string()
 3808        if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"):
 3809            kwargs["collection_items"] = self._parse_string()
 3810        if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"):
 3811            kwargs["map_keys"] = self._parse_string()
 3812        if self._match_text_seq("LINES", "TERMINATED", "BY"):
 3813            kwargs["lines"] = self._parse_string()
 3814        if self._match_text_seq("NULL", "DEFINED", "AS"):
 3815            kwargs["null"] = self._parse_string()
 3816
 3817        return self.expression(exp.RowFormatDelimitedProperty(**kwargs))  # type: ignore
 3818
 3819    def _parse_load(self) -> exp.LoadData | exp.Command:
 3820        if self._match_text_seq("DATA"):
 3821            local = self._match_text_seq("LOCAL")
 3822            self._match_text_seq("INPATH")
 3823            inpath = self._parse_string()
 3824            overwrite = self._match(TokenType.OVERWRITE)
 3825            temp: bool | None = None
 3826            if self._match(TokenType.INTO):
 3827                temp = self._match(TokenType.TEMPORARY)
 3828                self._match(TokenType.TABLE)
 3829
 3830            return self.expression(
 3831                exp.LoadData(
 3832                    this=self._parse_table(schema=True),
 3833                    local=local,
 3834                    overwrite=overwrite,
 3835                    temp=temp,
 3836                    inpath=inpath,
 3837                    files=self._match_text_seq("FROM", "FILES")
 3838                    and exp.Properties(expressions=self._parse_wrapped_properties()),
 3839                    partition=self._parse_partition(),
 3840                    input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(),
 3841                    serde=self._match_text_seq("SERDE") and self._parse_string(),
 3842                )
 3843            )
 3844        return self._parse_as_command(self._prev)
 3845
 3846    def _parse_delete(self) -> exp.Delete:
 3847        hint = self._parse_hint()
 3848
 3849        # This handles MySQL's "Multiple-Table Syntax"
 3850        # https://dev.mysql.com/doc/refman/8.0/en/delete.html
 3851        tables = None
 3852        if not self._match(TokenType.FROM, advance=False):
 3853            tables = self._parse_csv(self._parse_table) or None
 3854
 3855        returning = self._parse_returning()
 3856
 3857        return self.expression(
 3858            exp.Delete(
 3859                hint=hint,
 3860                tables=tables,
 3861                this=self._match(TokenType.FROM) and self._parse_table(joins=True),
 3862                using=self._match(TokenType.USING)
 3863                and self._parse_csv(lambda: self._parse_table(joins=True)),
 3864                cluster=self._match(TokenType.ON) and self._parse_on_property(),
 3865                where=self._parse_where(),
 3866                returning=returning or self._parse_returning(),
 3867                order=self._parse_order(),
 3868                limit=self._parse_limit(),
 3869            )
 3870        )
 3871
 3872    def _parse_update_assignment(self) -> exp.Expr | None:
 3873        this = self._parse_comparison()
 3874        if self._match(TokenType.EQ):
 3875            comments = self._prev_comments
 3876            this = self.expression(
 3877                exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments
 3878            )
 3879        return this
 3880
 3881    def _parse_update(self) -> exp.Update:
 3882        hint = self._parse_hint()
 3883        kwargs: dict[str, object] = {
 3884            "hint": hint,
 3885            "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS),
 3886        }
 3887        while self._curr:
 3888            if self._match(TokenType.SET):
 3889                kwargs["expressions"] = self._parse_csv(self._parse_update_assignment)
 3890            elif self._match(TokenType.RETURNING, advance=False):
 3891                kwargs["returning"] = self._parse_returning()
 3892            elif self._match(TokenType.FROM, advance=False):
 3893                from_ = self._parse_from(joins=True)
 3894                table = from_.this if from_ else None
 3895                if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False):
 3896                    table.set("joins", list(self._parse_joins()) or None)
 3897
 3898                kwargs["from_"] = from_
 3899            elif self._match(TokenType.WHERE, advance=False):
 3900                kwargs["where"] = self._parse_where()
 3901            elif self._match(TokenType.ORDER_BY, advance=False):
 3902                kwargs["order"] = self._parse_order()
 3903            elif self._match(TokenType.LIMIT, advance=False):
 3904                kwargs["limit"] = self._parse_limit()
 3905            else:
 3906                break
 3907
 3908        return self.expression(exp.Update(**kwargs))
 3909
 3910    def _parse_use(self) -> exp.Use:
 3911        return self.expression(
 3912            exp.Use(
 3913                kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False),
 3914                this=self._parse_table(schema=False),
 3915            )
 3916        )
 3917
 3918    def _parse_uncache(self) -> exp.Uncache:
 3919        if not self._match(TokenType.TABLE):
 3920            self.raise_error("Expecting TABLE after UNCACHE")
 3921
 3922        return self.expression(
 3923            exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True))
 3924        )
 3925
 3926    def _parse_cache(self) -> exp.Cache:
 3927        lazy = self._match_text_seq("LAZY")
 3928        self._match(TokenType.TABLE)
 3929        table = self._parse_table(schema=True)
 3930
 3931        options = []
 3932        if self._match_text_seq("OPTIONS"):
 3933            self._match_l_paren()
 3934            k = self._parse_string()
 3935            self._match(TokenType.EQ)
 3936            v = self._parse_string()
 3937            options = [k, v]
 3938            self._match_r_paren()
 3939
 3940        self._match(TokenType.ALIAS)
 3941        return self.expression(
 3942            exp.Cache(
 3943                this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True)
 3944            )
 3945        )
 3946
 3947    def _parse_partition(self) -> exp.Partition | None:
 3948        if not self._match_texts(self.PARTITION_KEYWORDS):
 3949            return None
 3950
 3951        return self.expression(
 3952            exp.Partition(
 3953                subpartition=self._prev.text.upper() == "SUBPARTITION",
 3954                expressions=self._parse_wrapped_csv(self._parse_disjunction),
 3955            )
 3956        )
 3957
 3958    def _parse_value(self, values: bool = True) -> exp.Tuple | None:
 3959        def _parse_value_expression() -> exp.Expr | None:
 3960            if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3961                return exp.var(self._prev.text.upper())
 3962            return self._parse_expression()
 3963
 3964        if self._match(TokenType.L_PAREN):
 3965            expressions = self._parse_csv(_parse_value_expression)
 3966            self._match_r_paren()
 3967            return self.expression(exp.Tuple(expressions=expressions))
 3968
 3969        # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows.
 3970        expression = self._parse_expression()
 3971        if expression:
 3972            return self.expression(exp.Tuple(expressions=[expression]))
 3973        return None
 3974
 3975    def _parse_projections(
 3976        self,
 3977    ) -> tuple[list[exp.Expr], list[exp.Expr] | None]:
 3978        return self._parse_expressions(), None
 3979
 3980    def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None:
 3981        if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)):
 3982            this: exp.Expr | None = self._parse_simplified_pivot(
 3983                is_unpivot=self._prev.token_type == TokenType.UNPIVOT
 3984            )
 3985        elif self._match(TokenType.FROM):
 3986            from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True)
 3987            # Support parentheses for duckdb FROM-first syntax
 3988            select = self._parse_select(from_=from_)
 3989            if select:
 3990                if not select.args.get("from_"):
 3991                    select.set("from_", from_)
 3992                this = select
 3993            else:
 3994                this = exp.select("*").from_(t.cast(exp.From, from_))
 3995                this = self._parse_query_modifiers(self._parse_set_operations(this))
 3996        else:
 3997            this = (
 3998                self._parse_table(consume_pipe=True)
 3999                if table
 4000                else self._parse_select(nested=True, parse_set_operation=False)
 4001            )
 4002
 4003            # Transform exp.Values into a exp.Table to pass through parse_query_modifiers
 4004            # in case a modifier (e.g. join) is following
 4005            if table and isinstance(this, exp.Values) and this.alias:
 4006                alias = this.args["alias"].pop()
 4007                this = exp.Table(this=this, alias=alias)
 4008
 4009            this = self._parse_query_modifiers(self._parse_set_operations(this))
 4010
 4011        return this
 4012
 4013    def _parse_select(
 4014        self,
 4015        nested: bool = False,
 4016        table: bool = False,
 4017        parse_subquery_alias: bool = True,
 4018        parse_set_operation: bool = True,
 4019        consume_pipe: bool = True,
 4020        from_: exp.From | None = None,
 4021    ) -> exp.Expr | None:
 4022        query = self._parse_select_query(
 4023            nested=nested,
 4024            table=table,
 4025            parse_subquery_alias=parse_subquery_alias,
 4026            parse_set_operation=parse_set_operation,
 4027        )
 4028
 4029        if consume_pipe and self._match(TokenType.PIPE_GT, advance=False):
 4030            if not query and from_:
 4031                query = exp.select("*").from_(from_)
 4032            if isinstance(query, exp.Query):
 4033                query = self._parse_pipe_syntax_query(query)
 4034                query = query.subquery(copy=False) if query and table else query
 4035
 4036        return query
 4037
 4038    def _parse_select_query(
 4039        self,
 4040        nested: bool = False,
 4041        table: bool = False,
 4042        parse_subquery_alias: bool = True,
 4043        parse_set_operation: bool = True,
 4044    ) -> exp.Expr | None:
 4045        cte = self._parse_with()
 4046
 4047        if cte:
 4048            this = self._parse_statement()
 4049
 4050            if not this:
 4051                self.raise_error("Failed to parse any statement following CTE")
 4052                return cte
 4053
 4054            while isinstance(this, exp.Subquery) and this.is_wrapper:
 4055                this = this.this
 4056
 4057            assert this is not None
 4058            if "with_" in this.arg_types:
 4059                if inner_cte := this.args.get("with_"):
 4060                    cte.set("expressions", cte.expressions + inner_cte.expressions)
 4061                    if inner_cte.args.get("recursive"):
 4062                        cte.set("recursive", True)
 4063                this.set("with_", cte)
 4064            else:
 4065                self.raise_error(f"{this.key} does not support CTE")
 4066                this = cte
 4067
 4068            return this
 4069
 4070        # duckdb supports leading with FROM x
 4071        from_ = (
 4072            self._parse_from(joins=True, consume_pipe=True)
 4073            if self._match(TokenType.FROM, advance=False)
 4074            else None
 4075        )
 4076
 4077        if self._match(TokenType.SELECT):
 4078            comments = self._prev_comments
 4079
 4080            hint = self._parse_hint()
 4081
 4082            if self._next and not self._next.token_type == TokenType.DOT:
 4083                all_ = self._match(TokenType.ALL)
 4084                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4085            else:
 4086                all_, matched_distinct = None, False
 4087
 4088            kind = (
 4089                self._prev.text.upper()
 4090                if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE"))
 4091                else None
 4092            )
 4093
 4094            distinct: exp.Expr | None = (
 4095                self.expression(
 4096                    exp.Distinct(
 4097                        on=self._parse_value(values=False) if self._match(TokenType.ON) else None
 4098                    )
 4099                )
 4100                if matched_distinct
 4101                else None
 4102            )
 4103
 4104            operation_modifiers = []
 4105            while self._curr and self._match_texts(self.OPERATION_MODIFIERS):
 4106                operation_modifiers.append(exp.var(self._prev.text.upper()))
 4107
 4108            limit = self._parse_limit(top=True)
 4109
 4110            # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ...
 4111            if limit and not matched_distinct and not all_:
 4112                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4113                if matched_distinct:
 4114                    distinct = self.expression(
 4115                        exp.Distinct(
 4116                            on=self._parse_value(values=False)
 4117                            if self._match(TokenType.ON)
 4118                            else None
 4119                        )
 4120                    )
 4121                else:
 4122                    all_ = self._match(TokenType.ALL)
 4123
 4124            if all_ and distinct:
 4125                self.raise_error("Cannot specify both ALL and DISTINCT after SELECT")
 4126
 4127            projections, exclude = self._parse_projections()
 4128
 4129            this = self.expression(
 4130                exp.Select(
 4131                    kind=kind,
 4132                    hint=hint,
 4133                    distinct=distinct,
 4134                    expressions=projections,
 4135                    limit=limit,
 4136                    exclude=exclude,
 4137                    operation_modifiers=operation_modifiers or None,
 4138                )
 4139            )
 4140            this.comments = comments
 4141
 4142            into = self._parse_into()
 4143            if into:
 4144                this.set("into", into)
 4145
 4146            if not from_:
 4147                from_ = self._parse_from()
 4148
 4149            if from_:
 4150                this.set("from_", from_)
 4151
 4152            this = self._parse_query_modifiers(this)
 4153        elif (table or nested) and self._match(TokenType.L_PAREN):
 4154            comments = self._prev_comments
 4155            this = self._parse_wrapped_select(table=table)
 4156
 4157            if this:
 4158                this.add_comments(comments, prepend=True)
 4159
 4160            # We return early here so that the UNION isn't attached to the subquery by the
 4161            # following call to _parse_set_operations, but instead becomes the parent node
 4162            self._match_r_paren()
 4163            return self._parse_subquery(this, parse_alias=parse_subquery_alias)
 4164        elif self._match(TokenType.VALUES, advance=False):
 4165            this = self._parse_derived_table_values()
 4166        elif from_:
 4167            this = exp.select("*").from_(from_.this, copy=False)
 4168            this = self._parse_query_modifiers(this)
 4169        elif self._match(TokenType.SUMMARIZE):
 4170            table = self._match(TokenType.TABLE)
 4171            this = self._parse_select() or self._parse_string() or self._parse_table()
 4172            return self.expression(exp.Summarize(this=this, table=table))
 4173        elif self._match(TokenType.DESCRIBE):
 4174            this = self._parse_describe()
 4175        else:
 4176            this = None
 4177
 4178        return self._parse_set_operations(this) if parse_set_operation else this
 4179
 4180    def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None:
 4181        self._match_text_seq("SEARCH")
 4182
 4183        kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper()
 4184
 4185        if not kind:
 4186            return None
 4187
 4188        self._match_text_seq("FIRST", "BY")
 4189
 4190        return self.expression(
 4191            exp.RecursiveWithSearch(
 4192                kind=kind,
 4193                this=self._parse_id_var(),
 4194                expression=self._match_text_seq("SET") and self._parse_id_var(),
 4195                using=self._match_text_seq("USING") and self._parse_id_var(),
 4196            )
 4197        )
 4198
 4199    def _parse_with(self, skip_with_token: bool = False) -> exp.With | None:
 4200        if not skip_with_token and not self._match(TokenType.WITH):
 4201            return None
 4202
 4203        comments = self._prev_comments
 4204        recursive = self._match(TokenType.RECURSIVE)
 4205
 4206        last_comments = None
 4207        expressions = []
 4208        udfs = []
 4209        while True:
 4210            cte = self._parse_cte()
 4211            if cte:
 4212                if isinstance(cte, exp.FunctionSpecification):
 4213                    udfs.append(cte)
 4214                else:
 4215                    expressions.append(cte)
 4216
 4217                if last_comments:
 4218                    cte.add_comments(last_comments)
 4219
 4220            if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH):
 4221                break
 4222            else:
 4223                self._match(TokenType.WITH)
 4224                recursive = self._match(TokenType.RECURSIVE) or recursive
 4225
 4226            last_comments = self._prev_comments
 4227
 4228        return self.expression(
 4229            exp.With(
 4230                expressions=expressions,
 4231                recursive=recursive or None,
 4232                search=self._parse_recursive_with_search(),
 4233                udfs=udfs or None,
 4234            ),
 4235            comments=comments,
 4236        )
 4237
 4238    def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None:
 4239        index = self._index
 4240
 4241        alias = self._parse_table_alias(self.ID_VAR_TOKENS)
 4242        if not alias or not alias.this:
 4243            self.raise_error("Expected CTE to have alias")
 4244
 4245        key_expressions = (
 4246            self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None
 4247        )
 4248
 4249        if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE:
 4250            self._retreat(index)
 4251            return None
 4252
 4253        comments = self._prev_comments
 4254
 4255        if self._match_text_seq("NOT", "MATERIALIZED"):
 4256            materialized = False
 4257        elif self._match_text_seq("MATERIALIZED"):
 4258            materialized = True
 4259        else:
 4260            materialized = None
 4261
 4262        cte = self.expression(
 4263            exp.CTE(
 4264                this=self._parse_wrapped(self._parse_statement),
 4265                alias=alias,
 4266                materialized=materialized,
 4267                key_expressions=key_expressions,
 4268            ),
 4269            comments=comments,
 4270        )
 4271
 4272        values = cte.this
 4273        if isinstance(values, exp.Values):
 4274            cte.set("this", self._values_to_select(values))
 4275
 4276        return cte
 4277
 4278    def _values_to_select(self, values: exp.Values) -> exp.Select:
 4279        if values.alias:
 4280            return exp.select("*").from_(values)
 4281        return exp.select("*").from_(exp.alias_(values, "_values", table=True))
 4282
 4283    def _parse_table_alias(
 4284        self, alias_tokens: t.Collection[TokenType] | None = None
 4285    ) -> exp.TableAlias | None:
 4286        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 4287        # so this section tries to parse the clause version and if it fails, it treats the token
 4288        # as an identifier (alias)
 4289        if self._can_parse_limit_or_offset():
 4290            return None
 4291
 4292        # START is never treated as an implicit alias when followed by WITH, since that
 4293        # would swallow the beginning of a START WITH ... CONNECT BY clause
 4294        if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH":
 4295            return None
 4296
 4297        any_token = self._match(TokenType.ALIAS)
 4298        alias = (
 4299            self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 4300            or self._parse_string_as_identifier()
 4301        )
 4302
 4303        index = self._index
 4304        if self._match(TokenType.L_PAREN):
 4305            columns = self._parse_csv(self._parse_function_parameter)
 4306            self._match_r_paren() if columns else self._retreat(index)
 4307        else:
 4308            columns = None
 4309
 4310        if not alias and not columns:
 4311            return None
 4312
 4313        table_alias = self.expression(exp.TableAlias(this=alias, columns=columns))
 4314
 4315        # We bubble up comments from the Identifier to the TableAlias
 4316        if isinstance(alias, exp.Identifier):
 4317            table_alias.add_comments(alias.pop_comments())
 4318
 4319        return table_alias
 4320
 4321    def _parse_subquery(
 4322        self, this: exp.Expr | None, parse_alias: bool = True
 4323    ) -> exp.Subquery | None:
 4324        if not this:
 4325            return None
 4326
 4327        return self.expression(
 4328            exp.Subquery(
 4329                this=this,
 4330                pivots=self._parse_pivots(),
 4331                alias=self._parse_table_alias() if parse_alias else None,
 4332                sample=self._parse_table_sample(),
 4333            )
 4334        )
 4335
 4336    def _implicit_unnests_to_explicit(self, this: E) -> E:
 4337        from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm
 4338
 4339        refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name}
 4340        for i, join in enumerate(this.args.get("joins") or []):
 4341            table = join.this
 4342            normalized_table = table.copy()
 4343            normalized_table.meta["maybe_column"] = True
 4344            normalized_table = _norm(normalized_table, dialect=self.dialect)
 4345
 4346            if isinstance(table, exp.Table) and not join.args.get("on"):
 4347                if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs:
 4348                    table_as_column = table.to_column()
 4349                    unnest = exp.Unnest(expressions=[table_as_column])
 4350
 4351                    # Table.to_column creates a parent Alias node that we want to convert to
 4352                    # a TableAlias and attach to the Unnest, so it matches the parser's output
 4353                    if isinstance(table.args.get("alias"), exp.TableAlias):
 4354                        table_as_column.replace(table_as_column.this)
 4355                        exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False)
 4356
 4357                    table.replace(unnest)
 4358
 4359            refs.add(normalized_table.alias_or_name)
 4360
 4361        return this
 4362
 4363    @t.overload
 4364    def _parse_query_modifiers(self, this: E) -> E: ...
 4365
 4366    @t.overload
 4367    def _parse_query_modifiers(self, this: None) -> None: ...
 4368
 4369    def _parse_query_modifiers(self, this):
 4370        if isinstance(this, self.MODIFIABLES):
 4371            for join in self._parse_joins():
 4372                this.append("joins", join)
 4373            for lateral in iter(self._parse_lateral, None):
 4374                this.append("laterals", lateral)
 4375
 4376            while True:
 4377                if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):
 4378                    modifier_token = self._curr
 4379
 4380                    # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result
 4381                    # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union
 4382                    if (
 4383                        modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH)
 4384                        and (limit := this.args.get("limit"))
 4385                        and limit.meta.get("top")
 4386                    ):
 4387                        break
 4388
 4389                    parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type]
 4390                    key, expression = parser(self)
 4391
 4392                    if expression:
 4393                        if this.args.get(key):
 4394                            self.raise_error(
 4395                                f"Found multiple '{modifier_token.text.upper()}' clauses",
 4396                                token=modifier_token,
 4397                            )
 4398
 4399                        this.set(key, expression)
 4400                        if key == "limit":
 4401                            offset = expression.args.get("offset")
 4402                            expression.set("offset", None)
 4403
 4404                            if offset:
 4405                                if this.args.get("offset"):
 4406                                    self.raise_error(
 4407                                        "Found multiple 'OFFSET' clauses", token=modifier_token
 4408                                    )
 4409
 4410                                offset = exp.Offset(expression=offset)
 4411                                this.set("offset", offset)
 4412
 4413                                limit_by_expressions = expression.expressions
 4414                                expression.set("expressions", None)
 4415                                offset.set("expressions", limit_by_expressions)
 4416                        continue
 4417
 4418                if self._curr.text.upper() == "START":
 4419                    modifier_token = self._curr
 4420                    connect = self._parse_connect()
 4421                    if connect:
 4422                        if this.args.get("connect"):
 4423                            self.raise_error(
 4424                                "Found multiple 'START WITH' clauses", token=modifier_token
 4425                            )
 4426
 4427                        this.set("connect", connect)
 4428                        continue
 4429                break
 4430
 4431        if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"):
 4432            this = self._implicit_unnests_to_explicit(this)
 4433
 4434        return this
 4435
 4436    def _parse_hint_fallback_to_string(self) -> exp.Hint | None:
 4437        start = self._curr
 4438        while self._curr:
 4439            self._advance()
 4440
 4441        end = self._tokens[self._index - 1]
 4442        return exp.Hint(expressions=[self._find_sql(start, end)])
 4443
 4444    def _parse_hint_function_call(self) -> exp.Expr | None:
 4445        return self._parse_function_call()
 4446
 4447    def _parse_hint_body(self) -> exp.Hint | None:
 4448        start_index = self._index
 4449        should_fallback_to_string = False
 4450
 4451        hints = []
 4452        try:
 4453            for hint in iter(
 4454                lambda: self._parse_csv(
 4455                    lambda: self._parse_hint_function_call() or self._parse_var(upper=True),
 4456                ),
 4457                [],
 4458            ):
 4459                hints.extend(hint)
 4460        except ParseError:
 4461            should_fallback_to_string = True
 4462
 4463        if should_fallback_to_string or self._curr:
 4464            self._retreat(start_index)
 4465            return self._parse_hint_fallback_to_string()
 4466
 4467        return self.expression(exp.Hint(expressions=hints))
 4468
 4469    def _parse_hint(self) -> exp.Hint | None:
 4470        if self._match(TokenType.HINT) and self._prev_comments:
 4471            return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect)
 4472
 4473        return None
 4474
 4475    def _parse_into(self) -> exp.Into | None:
 4476        if not self._match(TokenType.INTO):
 4477            return None
 4478
 4479        temp = self._match(TokenType.TEMPORARY)
 4480        unlogged = self._match_text_seq("UNLOGGED")
 4481        self._match(TokenType.TABLE)
 4482
 4483        return self.expression(
 4484            exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged)
 4485        )
 4486
 4487    def _parse_from(
 4488        self,
 4489        joins: bool = False,
 4490        skip_from_token: bool = False,
 4491        consume_pipe: bool = False,
 4492    ) -> exp.From | None:
 4493        if not skip_from_token and not self._match(TokenType.FROM):
 4494            return None
 4495
 4496        comments = self._prev_comments
 4497        return self.expression(
 4498            exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)),
 4499            comments=comments,
 4500        )
 4501
 4502    def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure:
 4503        return self.expression(
 4504            exp.MatchRecognizeMeasure(
 4505                window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(),
 4506                this=self._parse_expression(),
 4507            )
 4508        )
 4509
 4510    def _parse_match_recognize(self) -> exp.MatchRecognize | None:
 4511        if not self._match(TokenType.MATCH_RECOGNIZE):
 4512            return None
 4513
 4514        self._match_l_paren()
 4515
 4516        partition = self._parse_partition_by()
 4517        order = self._parse_order()
 4518
 4519        measures = (
 4520            self._parse_csv(self._parse_match_recognize_measure)
 4521            if self._match_text_seq("MEASURES")
 4522            else None
 4523        )
 4524
 4525        if self._match_text_seq("ONE", "ROW", "PER", "MATCH"):
 4526            rows = exp.var("ONE ROW PER MATCH")
 4527        elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"):
 4528            text = "ALL ROWS PER MATCH"
 4529            if self._match_text_seq("SHOW", "EMPTY", "MATCHES"):
 4530                text += " SHOW EMPTY MATCHES"
 4531            elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"):
 4532                text += " OMIT EMPTY MATCHES"
 4533            elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"):
 4534                text += " WITH UNMATCHED ROWS"
 4535            rows = exp.var(text)
 4536        else:
 4537            rows = None
 4538
 4539        if self._match_text_seq("AFTER", "MATCH", "SKIP"):
 4540            text = "AFTER MATCH SKIP"
 4541            if self._match_text_seq("PAST", "LAST", "ROW"):
 4542                text += " PAST LAST ROW"
 4543            elif self._match_text_seq("TO", "NEXT", "ROW"):
 4544                text += " TO NEXT ROW"
 4545            elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"):
 4546                direction = self._prev.text.upper()
 4547                pattern_var = self._advance_any()
 4548                if not pattern_var:
 4549                    self.raise_error(
 4550                        f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}"
 4551                    )
 4552                text += f" TO {direction} {pattern_var.text if pattern_var else ''}"
 4553            after = exp.var(text)
 4554        else:
 4555            after = None
 4556
 4557        if self._match_text_seq("PATTERN"):
 4558            self._match_l_paren()
 4559
 4560            if not self._curr:
 4561                self.raise_error("Expecting )", self._curr)
 4562
 4563            paren = 1
 4564            start = self._curr
 4565
 4566            while self._curr and paren > 0:
 4567                if self._curr.token_type == TokenType.L_PAREN:
 4568                    paren += 1
 4569                if self._curr.token_type == TokenType.R_PAREN:
 4570                    paren -= 1
 4571
 4572                end = self._prev
 4573                self._advance()
 4574
 4575            if paren > 0:
 4576                self.raise_error("Expecting )", self._curr)
 4577
 4578            pattern = exp.var(self._find_sql(start, end))
 4579        else:
 4580            pattern = None
 4581
 4582        define = (
 4583            self._parse_csv(self._parse_name_as_expression)
 4584            if self._match_text_seq("DEFINE")
 4585            else None
 4586        )
 4587
 4588        self._match_r_paren()
 4589
 4590        return self.expression(
 4591            exp.MatchRecognize(
 4592                partition_by=partition,
 4593                order=order,
 4594                measures=measures,
 4595                rows=rows,
 4596                after=after,
 4597                pattern=pattern,
 4598                define=define,
 4599                alias=self._parse_table_alias(),
 4600            )
 4601        )
 4602
 4603    def _parse_lateral(self) -> exp.Lateral | None:
 4604        cross_apply: bool | None = None
 4605        if self._match_pair(TokenType.CROSS, TokenType.APPLY):
 4606            cross_apply = True
 4607        elif self._match_pair(TokenType.OUTER, TokenType.APPLY):
 4608            cross_apply = False
 4609
 4610        if cross_apply is not None:
 4611            this = self._parse_select(table=True)
 4612            view = None
 4613            outer = None
 4614        elif self._match(TokenType.LATERAL):
 4615            this = self._parse_select(table=True)
 4616            view = self._match(TokenType.VIEW)
 4617            outer = self._match(TokenType.OUTER)
 4618        else:
 4619            return None
 4620
 4621        if not this:
 4622            this = (
 4623                self._parse_unnest()
 4624                or self._parse_function()
 4625                or self._parse_id_var(any_token=False)
 4626            )
 4627
 4628            while self._match(TokenType.DOT):
 4629                this = exp.Dot(
 4630                    this=this,
 4631                    expression=self._parse_function() or self._parse_id_var(any_token=False),
 4632                )
 4633
 4634        ordinality: bool | None = None
 4635
 4636        if view:
 4637            table = self._parse_id_var(any_token=False)
 4638            columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else []
 4639            table_alias: exp.TableAlias | None = self.expression(
 4640                exp.TableAlias(this=table, columns=columns)
 4641            )
 4642        elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias:
 4643            # We move the alias from the lateral's child node to the lateral itself
 4644            table_alias = this.args["alias"].pop()
 4645        else:
 4646            ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 4647            table_alias = self._parse_table_alias()
 4648
 4649        return self.expression(
 4650            exp.Lateral(
 4651                this=this,
 4652                view=view,
 4653                outer=outer,
 4654                alias=table_alias,
 4655                cross_apply=cross_apply,
 4656                ordinality=ordinality,
 4657            )
 4658        )
 4659
 4660    def _parse_stream(self) -> exp.Stream | None:
 4661        index = self._index
 4662        if self._match(TokenType.STREAM):
 4663            if this := self._try_parse(self._parse_table):
 4664                return self.expression(exp.Stream(this=this))
 4665            self._retreat(index)
 4666        return None
 4667
 4668    def _parse_join_parts(
 4669        self,
 4670    ) -> tuple[Token | None, Token | None, Token | None]:
 4671        return (
 4672            self._prev if self._match_set(self.JOIN_METHODS) else None,
 4673            self._prev if self._match_set(self.JOIN_SIDES) else None,
 4674            self._prev if self._match_set(self.JOIN_KINDS) else None,
 4675        )
 4676
 4677    def _parse_using_identifiers(self) -> list[exp.Expr]:
 4678        def _parse_column_as_identifier() -> exp.Expr | None:
 4679            this = self._parse_column()
 4680            if isinstance(this, exp.Column):
 4681                return this.this
 4682            return this
 4683
 4684        return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True)
 4685
 4686    def _parse_join(
 4687        self,
 4688        skip_join_token: bool = False,
 4689        parse_bracket: bool = False,
 4690        alias_tokens: t.Collection[TokenType] | None = None,
 4691    ) -> exp.Join | None:
 4692        if self._match(TokenType.COMMA):
 4693            table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens))
 4694            cross_join = self.expression(exp.Join(this=table)) if table else None
 4695
 4696            if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE:
 4697                cross_join.set("kind", "CROSS")
 4698
 4699            return cross_join
 4700
 4701        index = self._index
 4702        method, side, kind = self._parse_join_parts()
 4703        directed = self._match_text_seq("DIRECTED")
 4704        hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None
 4705        join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN)
 4706        join_comments = self._prev_comments
 4707
 4708        if not skip_join_token and not join:
 4709            self._retreat(index)
 4710            kind = None
 4711            method = None
 4712            side = None
 4713
 4714        outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False)
 4715        cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False)
 4716
 4717        if not skip_join_token and not join and not outer_apply and not cross_apply:
 4718            return None
 4719
 4720        kwargs: dict[str, t.Any] = {
 4721            "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4722        }
 4723        if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA):
 4724            kwargs["expressions"] = self._parse_csv(
 4725                lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4726            )
 4727
 4728        if method:
 4729            kwargs["method"] = method.text.upper()
 4730        if side:
 4731            kwargs["side"] = side.text.upper()
 4732        if kind:
 4733            kwargs["kind"] = kind.text.upper()
 4734        if hint:
 4735            kwargs["hint"] = hint
 4736
 4737        if self._match(TokenType.MATCH_CONDITION):
 4738            kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison)
 4739
 4740        if self._match(TokenType.ON):
 4741            kwargs["on"] = self._parse_disjunction()
 4742        elif self._match(TokenType.USING):
 4743            kwargs["using"] = self._parse_using_identifiers()
 4744        elif (
 4745            not method
 4746            and not (outer_apply or cross_apply)
 4747            and not isinstance(kwargs["this"], exp.Unnest)
 4748            and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY))
 4749        ):
 4750            index = self._index
 4751            joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens))
 4752
 4753            if joins and self._match(TokenType.ON):
 4754                kwargs["on"] = self._parse_disjunction()
 4755            elif joins and self._match(TokenType.USING):
 4756                kwargs["using"] = self._parse_using_identifiers()
 4757            else:
 4758                joins = None
 4759                self._retreat(index)
 4760
 4761            kwargs["this"].set("joins", joins if joins else None)
 4762
 4763        kwargs["pivots"] = self._parse_pivots()
 4764
 4765        comments = [c for token in (method, side, kind) if token for c in token.comments]
 4766        comments = (join_comments or []) + comments
 4767
 4768        if (
 4769            self.ADD_JOIN_ON_TRUE
 4770            and not kwargs.get("on")
 4771            and not kwargs.get("using")
 4772            and not kwargs.get("method")
 4773            and kwargs.get("kind") in (None, "INNER", "OUTER")
 4774        ):
 4775            kwargs["on"] = exp.true()
 4776
 4777        if directed:
 4778            kwargs["directed"] = directed
 4779
 4780        return self.expression(exp.Join(**kwargs), comments=comments)
 4781
 4782    def _parse_opclass(self) -> exp.Expr | None:
 4783        this = self._parse_disjunction()
 4784
 4785        if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False):
 4786            return this
 4787
 4788        if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False):
 4789            return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts()))
 4790
 4791        return this
 4792
 4793    def _parse_index_params(self) -> exp.IndexParameters:
 4794        using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None
 4795
 4796        if self._match(TokenType.L_PAREN, advance=False):
 4797            columns = self._parse_wrapped_csv(self._parse_with_operator)
 4798        else:
 4799            columns = None
 4800
 4801        include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None
 4802        partition_by = self._parse_partition_by()
 4803        with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties()
 4804        tablespace = (
 4805            self._parse_var(any_token=True)
 4806            if self._match_text_seq("USING", "INDEX", "TABLESPACE")
 4807            else None
 4808        )
 4809        where = self._parse_where()
 4810
 4811        on = self._parse_field() if self._match(TokenType.ON) else None
 4812
 4813        return self.expression(
 4814            exp.IndexParameters(
 4815                using=using,
 4816                columns=columns,
 4817                include=include,
 4818                partition_by=partition_by,
 4819                where=where,
 4820                with_storage=with_storage,
 4821                tablespace=tablespace,
 4822                on=on,
 4823            )
 4824        )
 4825
 4826    def _parse_index(
 4827        self, index: exp.Expr | None = None, anonymous: bool = False
 4828    ) -> exp.Index | None:
 4829        if index or anonymous:
 4830            unique = None
 4831            primary = None
 4832            amp = None
 4833
 4834            self._match(TokenType.ON)
 4835            self._match(TokenType.TABLE)  # hive
 4836            table = self._parse_table_parts(schema=True)
 4837        else:
 4838            unique = self._match(TokenType.UNIQUE)
 4839            primary = self._match_text_seq("PRIMARY")
 4840            amp = self._match_text_seq("AMP")
 4841
 4842            if not self._match(TokenType.INDEX):
 4843                return None
 4844
 4845            index = self._parse_id_var()
 4846            table = None
 4847
 4848        params = self._parse_index_params()
 4849
 4850        return self.expression(
 4851            exp.Index(
 4852                this=index, table=table, unique=unique, primary=primary, amp=amp, params=params
 4853            )
 4854        )
 4855
 4856    def _parse_table_hints(self) -> list[exp.Expr] | None:
 4857        hints: list[exp.Expr] = []
 4858        if self._match_pair(TokenType.WITH, TokenType.L_PAREN):
 4859            # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16
 4860            hints.append(
 4861                self.expression(
 4862                    exp.WithTableHint(
 4863                        expressions=self._parse_csv(
 4864                            lambda: self._parse_function() or self._parse_var(any_token=True)
 4865                        )
 4866                    )
 4867                )
 4868            )
 4869            self._match_r_paren()
 4870        else:
 4871            # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
 4872            while self._match_set(self.TABLE_INDEX_HINT_TOKENS):
 4873                hint = exp.IndexTableHint(this=self._prev.text.upper())
 4874
 4875                self._match_set((TokenType.INDEX, TokenType.KEY))
 4876                if self._match(TokenType.FOR):
 4877                    hint.set("target", self._advance_any() and self._prev.text.upper())
 4878
 4879                hint.set("expressions", self._parse_wrapped_id_vars())
 4880                hints.append(hint)
 4881
 4882        return hints or None
 4883
 4884    def _parse_table_part(self, schema: bool = False) -> exp.Expr | None:
 4885        return (
 4886            (not schema and self._parse_function(optional_parens=False))
 4887            or self._parse_id_var(any_token=False)
 4888            or self._parse_string_as_identifier()
 4889            or self._parse_placeholder()
 4890        )
 4891
 4892    def _parse_table_parts_fast(self) -> exp.Table | None:
 4893        index = self._index
 4894        parts: list[exp.Identifier] | None = None
 4895        all_comments: list[str] | None = None
 4896
 4897        while self._match_set(self.IDENTIFIER_TOKENS):
 4898            token = self._prev
 4899            comments = self._prev_comments
 4900
 4901            has_dot = self._match(TokenType.DOT)
 4902            curr_tt = self._curr.token_type
 4903
 4904            if not has_dot:
 4905                if curr_tt in self.TABLE_POSTFIX_TOKENS:
 4906                    self._retreat(index)
 4907                    return None
 4908            elif curr_tt not in self.IDENTIFIER_TOKENS:
 4909                self._retreat(index)
 4910                return None
 4911
 4912            if parts is None:
 4913                parts = []
 4914
 4915            if comments:
 4916                if all_comments is None:
 4917                    all_comments = []
 4918                all_comments.extend(comments)
 4919                self._prev_comments = []
 4920
 4921            parts.append(
 4922                self.expression(
 4923                    exp.Identifier(
 4924                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 4925                    ),
 4926                    token,
 4927                )
 4928            )
 4929
 4930            if not has_dot:
 4931                break
 4932
 4933        if parts is None:
 4934            return None
 4935
 4936        n = len(parts)
 4937
 4938        if n == 1:
 4939            table: exp.Table = exp.Table(this=parts[0])
 4940        elif n == 2:
 4941            table = exp.Table(this=parts[1], db=parts[0])
 4942        elif n >= 3:
 4943            this: exp.Identifier | exp.Dot = parts[2]
 4944            for i in range(3, n):
 4945                this = exp.Dot(this=this, expression=parts[i])
 4946
 4947            table = exp.Table(this=this, db=parts[1], catalog=parts[0])
 4948
 4949        if table is None:
 4950            self._retreat(index)
 4951        elif all_comments:
 4952            table.add_comments(all_comments)
 4953        return table
 4954
 4955    def _parse_table_parts(
 4956        self,
 4957        schema: bool = False,
 4958        is_db_reference: bool = False,
 4959        wildcard: bool = False,
 4960        fast: bool = False,
 4961    ) -> exp.Table | exp.Dot | None:
 4962        if fast:
 4963            return self._parse_table_parts_fast()
 4964
 4965        catalog: exp.Expr | str | None = None
 4966        db: exp.Expr | str | None = None
 4967        table: exp.Expr | str | None = self._parse_table_part(schema=schema)
 4968
 4969        while self._match(TokenType.DOT):
 4970            if catalog:
 4971                # This allows nesting the table in arbitrarily many dot expressions if needed
 4972                table = self.expression(
 4973                    exp.Dot(this=table, expression=self._parse_table_part(schema=schema))
 4974                )
 4975            else:
 4976                catalog = db
 4977                db = table
 4978                # "" used for tsql FROM a..b case
 4979                table = self._parse_table_part(schema=schema) or ""
 4980
 4981        if (
 4982            wildcard
 4983            and self._is_connected()
 4984            and (isinstance(table, exp.Identifier) or not table)
 4985            and self._match(TokenType.STAR)
 4986        ):
 4987            if isinstance(table, exp.Identifier):
 4988                table.args["this"] += "*"
 4989            else:
 4990                table = exp.Identifier(this="*")
 4991
 4992        if is_db_reference:
 4993            catalog = db
 4994            db = table
 4995            table = None
 4996
 4997        if not table and not is_db_reference:
 4998            self.raise_error(f"Expected table name but got {self._curr}")
 4999        if not db and is_db_reference:
 5000            self.raise_error(f"Expected database name but got {self._curr}")
 5001
 5002        table = self.expression(exp.Table(this=table, db=db, catalog=catalog))
 5003
 5004        # Bubble up comments from identifier parts to the Table
 5005        comments = []
 5006        for part in table.parts:
 5007            if part_comments := part.pop_comments():
 5008                comments.extend(part_comments)
 5009        if comments:
 5010            table.add_comments(comments)
 5011
 5012        changes = self._parse_changes()
 5013        if changes:
 5014            table.set("changes", changes)
 5015
 5016        at_before = self._parse_historical_data()
 5017        if at_before:
 5018            table.set("when", at_before)
 5019
 5020        pivots = self._parse_pivots()
 5021        if pivots:
 5022            table.set("pivots", pivots)
 5023
 5024        return table
 5025
 5026    def _parse_table(
 5027        self,
 5028        schema: bool = False,
 5029        joins: bool = False,
 5030        alias_tokens: t.Collection[TokenType] | None = None,
 5031        parse_bracket: bool = False,
 5032        is_db_reference: bool = False,
 5033        parse_partition: bool = False,
 5034        consume_pipe: bool = False,
 5035    ) -> exp.Expr | None:
 5036        if not schema and not is_db_reference and not consume_pipe and not joins:
 5037            index = self._index
 5038            table = self._parse_table_parts(fast=True)
 5039
 5040            if table is not None:
 5041                curr_tt = self._curr.token_type
 5042                next_tt = self._next.token_type
 5043
 5044                fast_terminators = self.TABLE_TERMINATORS
 5045
 5046                # only return the table if we're sure there are no other operators
 5047                # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT
 5048                if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION:
 5049                    return table
 5050
 5051                postfix_tokens = self.TABLE_POSTFIX_TOKENS
 5052
 5053                if curr_tt not in postfix_tokens and next_tt not in postfix_tokens:
 5054                    if alias := self._parse_table_alias(
 5055                        alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS
 5056                    ):
 5057                        table.set("alias", alias)
 5058
 5059                    if self._curr.token_type in fast_terminators:
 5060                        return table
 5061
 5062                self._retreat(index)
 5063
 5064        if stream := self._parse_stream():
 5065            return stream
 5066
 5067        if lateral := self._parse_lateral():
 5068            return lateral
 5069
 5070        if unnest := self._parse_unnest():
 5071            return unnest
 5072
 5073        if values := self._parse_derived_table_values():
 5074            return values
 5075
 5076        if subquery := self._parse_select(table=True, consume_pipe=consume_pipe):
 5077            if not subquery.args.get("pivots"):
 5078                subquery.set("pivots", self._parse_pivots())
 5079            if joins:
 5080                for join in self._parse_joins():
 5081                    subquery.append("joins", join)
 5082            return subquery
 5083
 5084        bracket = parse_bracket and self._parse_bracket(None)
 5085        bracket = self.expression(exp.Table(this=bracket)) if bracket else None
 5086
 5087        rows_from_tables = (
 5088            self._parse_wrapped_csv(self._parse_table)
 5089            if self._match_text_seq("ROWS", "FROM")
 5090            else None
 5091        )
 5092        rows_from = (
 5093            self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None
 5094        )
 5095
 5096        only = self._match(TokenType.ONLY)
 5097
 5098        this = t.cast(
 5099            exp.Expr,
 5100            bracket
 5101            or rows_from
 5102            or self._parse_bracket(
 5103                self._parse_table_parts(schema=schema, is_db_reference=is_db_reference)
 5104            ),
 5105        )
 5106
 5107        if only:
 5108            this.set("only", only)
 5109
 5110        # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context
 5111        self._match(TokenType.STAR)
 5112
 5113        parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION
 5114        if parse_partition and self._match(TokenType.PARTITION, advance=False):
 5115            this.set("partition", self._parse_partition())
 5116
 5117        if schema:
 5118            return self._parse_schema(this=this)
 5119
 5120        if self.dialect.ALIAS_POST_VERSION:
 5121            this.set("version", self._parse_version())
 5122
 5123        if self.dialect.ALIAS_POST_TABLESAMPLE:
 5124            this.set("sample", self._parse_table_sample())
 5125
 5126        alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 5127        if alias:
 5128            this.set("alias", alias)
 5129
 5130            # DuckDB requires the time-travel clause to come after the alias, e.g.
 5131            # SELECT * FROM t AS a AT (VERSION => 1)
 5132            if isinstance(this, exp.Table) and not this.args.get("when"):
 5133                this.set("when", self._parse_historical_data())
 5134
 5135        if self._match(TokenType.INDEXED_BY):
 5136            this.set("indexed", self._parse_table_parts())
 5137        elif self._match_text_seq("NOT", "INDEXED"):
 5138            this.set("indexed", False)
 5139
 5140        if isinstance(this, exp.Table) and self._match_text_seq("AT"):
 5141            return self.expression(
 5142                exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var())
 5143            )
 5144
 5145        this.set("hints", self._parse_table_hints())
 5146
 5147        if not this.args.get("pivots"):
 5148            this.set("pivots", self._parse_pivots())
 5149
 5150        if not self.dialect.ALIAS_POST_TABLESAMPLE:
 5151            this.set("sample", self._parse_table_sample())
 5152
 5153        if not self.dialect.ALIAS_POST_VERSION:
 5154            this.set("version", self._parse_version())
 5155
 5156        if joins:
 5157            for join in self._parse_joins(alias_tokens=alias_tokens):
 5158                this.append("joins", join)
 5159
 5160        if self._match_pair(TokenType.WITH, TokenType.ORDINALITY):
 5161            this.set("ordinality", True)
 5162            this.set("alias", self._parse_table_alias())
 5163
 5164        # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we
 5165        # hoist the table args onto the latter and return it instead
 5166        if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows):
 5167            table_from_rows = this.this
 5168            for arg in exp.TableFromRows.arg_types:
 5169                if arg != "this":
 5170                    table_from_rows.set(arg, this.args.get(arg))
 5171
 5172            this = table_from_rows
 5173
 5174        return this
 5175
 5176    def _parse_version(self) -> exp.Version | None:
 5177        for phrase, this in self.VERSION_PHRASES.items():
 5178            if self._match_text_seq(*phrase):
 5179                break
 5180        else:
 5181            return None
 5182
 5183        if self._match_set((TokenType.FROM, TokenType.BETWEEN)):
 5184            kind = self._prev.text.upper()
 5185            start = self._parse_bitwise()
 5186            self._match_texts(("TO", "AND"))
 5187            end = self._parse_bitwise()
 5188            expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end]))
 5189        elif self._match_text_seq("CONTAINED", "IN"):
 5190            kind = "CONTAINED IN"
 5191            expression = self.expression(
 5192                exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise))
 5193            )
 5194        elif self._match(TokenType.ALL):
 5195            kind = "ALL"
 5196            expression = None
 5197        else:
 5198            self._match_text_seq("AS", "OF")
 5199            kind = "AS OF"
 5200            expression = self._parse_type()
 5201
 5202        return self.expression(exp.Version(this=this, expression=expression, kind=kind))
 5203
 5204    def _parse_historical_data(self) -> exp.HistoricalData | None:
 5205        # https://docs.snowflake.com/en/sql-reference/constructs/at-before
 5206        index = self._index
 5207        historical_data = None
 5208        if self._match_texts(self.HISTORICAL_DATA_PREFIX):
 5209            this = self._prev.text.upper()
 5210            kind = (
 5211                self._match(TokenType.L_PAREN)
 5212                and self._match_texts(self.HISTORICAL_DATA_KIND)
 5213                and self._prev.text.upper()
 5214            )
 5215            expression = self._match(TokenType.FARROW) and self._parse_bitwise()
 5216
 5217            if expression:
 5218                self._match_r_paren()
 5219                historical_data = self.expression(
 5220                    exp.HistoricalData(this=this, kind=kind, expression=expression)
 5221                )
 5222            else:
 5223                self._retreat(index)
 5224
 5225        return historical_data
 5226
 5227    def _parse_changes(self) -> exp.Changes | None:
 5228        if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"):
 5229            return None
 5230
 5231        information = self._parse_var(any_token=True)
 5232        self._match_r_paren()
 5233
 5234        return self.expression(
 5235            exp.Changes(
 5236                information=information,
 5237                at_before=self._parse_historical_data(),
 5238                end=self._parse_historical_data(),
 5239            )
 5240        )
 5241
 5242    def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None:
 5243        if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False):
 5244            return None
 5245
 5246        self._advance()
 5247
 5248        expressions = self._parse_wrapped_csv(self._parse_equality)
 5249        offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 5250
 5251        alias = self._parse_table_alias() if with_alias else None
 5252
 5253        if alias:
 5254            if self.dialect.UNNEST_COLUMN_ONLY:
 5255                if alias.args.get("columns"):
 5256                    self.raise_error("Unexpected extra column alias in unnest.")
 5257
 5258                alias.set("columns", [alias.this])
 5259                alias.set("this", None)
 5260
 5261            columns = alias.args.get("columns") or []
 5262            if offset and len(expressions) < len(columns):
 5263                offset = columns.pop()
 5264
 5265        if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET):
 5266            self._match(TokenType.ALIAS)
 5267            offset = self._parse_id_var(
 5268                any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS
 5269            ) or exp.to_identifier("offset")
 5270
 5271        return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset))
 5272
 5273    def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None:
 5274        is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES)
 5275        if not is_derived and not (
 5276            # ClickHouse's `FORMAT Values` is equivalent to `VALUES`
 5277            self._match_text_seq("VALUES")
 5278            or self._match_text_seq("FORMAT", "VALUES")
 5279            # MySQL accepts VALUE as a synonym for VALUES in INSERT statements.
 5280            or (allow_value_synonym and self._match_text_seq("VALUE"))
 5281        ):
 5282            return None
 5283
 5284        expressions = self._parse_csv(self._parse_value)
 5285        alias = self._parse_table_alias()
 5286
 5287        if is_derived:
 5288            self._match_r_paren()
 5289
 5290        return self.expression(
 5291            exp.Values(expressions=expressions, alias=alias or self._parse_table_alias())
 5292        )
 5293
 5294    def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None:
 5295        if not self._match(TokenType.TABLE_SAMPLE) and not (
 5296            as_modifier and self._match_text_seq("USING", "SAMPLE")
 5297        ):
 5298            return None
 5299
 5300        bucket_numerator = None
 5301        bucket_denominator = None
 5302        bucket_field = None
 5303        percent = None
 5304        size = None
 5305        seed = None
 5306
 5307        method = self._parse_var(tokens=(TokenType.ROW,), upper=True)
 5308        matched_l_paren = self._match(TokenType.L_PAREN)
 5309
 5310        if self.TABLESAMPLE_CSV:
 5311            num = None
 5312            expressions = self._parse_csv(self._parse_primary)
 5313        else:
 5314            expressions = None
 5315            num = (
 5316                self._parse_factor(parse_mod=False)
 5317                if self._match(TokenType.NUMBER, advance=False)
 5318                else self._parse_primary() or self._parse_placeholder()
 5319            )
 5320
 5321        if self._match_text_seq("BUCKET"):
 5322            bucket_numerator = self._parse_number()
 5323            self._match_text_seq("OUT", "OF")
 5324            bucket_denominator = bucket_denominator = self._parse_number()
 5325            self._match(TokenType.ON)
 5326            bucket_field = self._parse_field()
 5327        elif self._match_set((TokenType.PERCENT, TokenType.MOD)):
 5328            percent = num
 5329        elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
 5330            size = num
 5331        else:
 5332            percent = num
 5333
 5334        if matched_l_paren:
 5335            self._match_r_paren()
 5336
 5337        if self._match(TokenType.L_PAREN):
 5338            method = self._parse_var(upper=True)
 5339            seed = self._match(TokenType.COMMA) and self._parse_number()
 5340            self._match_r_paren()
 5341        elif self._match_texts(("SEED", "REPEATABLE")):
 5342            seed = self._parse_wrapped(self._parse_number)
 5343
 5344        if not method and self.DEFAULT_SAMPLING_METHOD:
 5345            method = exp.var(self.DEFAULT_SAMPLING_METHOD)
 5346
 5347        return self.expression(
 5348            exp.TableSample(
 5349                expressions=expressions,
 5350                method=method,
 5351                bucket_numerator=bucket_numerator,
 5352                bucket_denominator=bucket_denominator,
 5353                bucket_field=bucket_field,
 5354                percent=percent,
 5355                size=size,
 5356                seed=seed,
 5357            )
 5358        )
 5359
 5360    def _parse_pivots(self) -> list[exp.Pivot] | None:
 5361        if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT):
 5362            return None
 5363        return list(iter(self._parse_pivot, None)) or None
 5364
 5365    def _parse_joins(
 5366        self, alias_tokens: t.Collection[TokenType] | None = None
 5367    ) -> t.Iterator[exp.Join]:
 5368        return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None)
 5369
 5370    def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None:
 5371        if not self._match(TokenType.INTO):
 5372            return None
 5373
 5374        return self.expression(
 5375            exp.UnpivotColumns(
 5376                this=self._match_text_seq("NAME") and self._parse_column(),
 5377                expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column),
 5378            )
 5379        )
 5380
 5381    # https://duckdb.org/docs/sql/statements/pivot
 5382    def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot:
 5383        def _parse_on() -> exp.Expr | None:
 5384            this = self._parse_bitwise()
 5385
 5386            if self._match(TokenType.IN):
 5387                # PIVOT ... ON col IN (row_val1, row_val2)
 5388                return self._parse_in(this)
 5389            if self._match(TokenType.ALIAS, advance=False):
 5390                # UNPIVOT ... ON (col1, col2, col3) AS row_val
 5391                return self._parse_alias(this)
 5392
 5393            return this
 5394
 5395        this = self._parse_table()
 5396        expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on)
 5397        into = self._parse_unpivot_columns()
 5398        using = self._match(TokenType.USING) and self._parse_csv(
 5399            lambda: self._parse_alias(self._parse_column())
 5400        )
 5401        group = self._parse_group()
 5402
 5403        return self.expression(
 5404            exp.Pivot(
 5405                this=this,
 5406                expressions=expressions,
 5407                using=using,
 5408                group=group,
 5409                unpivot=is_unpivot,
 5410                into=into,
 5411            )
 5412        )
 5413
 5414    def _parse_pivot_in(self) -> exp.In:
 5415        def _parse_aliased_expression() -> exp.Expr | None:
 5416            this = self._parse_select_or_expression()
 5417
 5418            self._match(TokenType.ALIAS)
 5419            alias = self._parse_bitwise()
 5420            if alias:
 5421                if isinstance(alias, exp.Column) and not alias.db:
 5422                    alias = alias.this
 5423                return self.expression(exp.PivotAlias(this=this, alias=alias))
 5424
 5425            return this
 5426
 5427        value = self._parse_column()
 5428
 5429        if not self._match(TokenType.IN):
 5430            self.raise_error("Expecting IN")
 5431
 5432        if self._match(TokenType.L_PAREN):
 5433            if self._match(TokenType.ANY):
 5434                exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order()))
 5435            else:
 5436                exprs = self._parse_csv(_parse_aliased_expression)
 5437            self._match_r_paren()
 5438            return self.expression(exp.In(this=value, expressions=exprs))
 5439
 5440        return self.expression(exp.In(this=value, field=self._parse_id_var()))
 5441
 5442    def _parse_pivot_aggregation(self) -> exp.Expr | None:
 5443        func = self._parse_function()
 5444        if not func:
 5445            if self._prev.token_type == TokenType.COMMA:
 5446                return None
 5447            self.raise_error("Expecting an aggregation function in PIVOT")
 5448
 5449        return self._parse_alias(func)
 5450
 5451    def _parse_pivot(self) -> exp.Pivot | None:
 5452        index = self._index
 5453        include_nulls = None
 5454
 5455        if self._match(TokenType.PIVOT):
 5456            unpivot = False
 5457        elif self._match(TokenType.UNPIVOT):
 5458            unpivot = True
 5459
 5460            # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax
 5461            if self._match_text_seq("INCLUDE", "NULLS"):
 5462                include_nulls = True
 5463            elif self._match_text_seq("EXCLUDE", "NULLS"):
 5464                include_nulls = False
 5465        else:
 5466            return None
 5467
 5468        expressions = []
 5469
 5470        if not self._match(TokenType.L_PAREN):
 5471            self._retreat(index)
 5472            return None
 5473
 5474        if unpivot:
 5475            expressions = self._parse_csv(self._parse_column)
 5476        else:
 5477            expressions = self._parse_csv(self._parse_pivot_aggregation)
 5478
 5479        if not expressions:
 5480            self.raise_error("Failed to parse PIVOT's aggregation list")
 5481
 5482        if not self._match(TokenType.FOR):
 5483            self.raise_error("Expecting FOR")
 5484
 5485        fields = []
 5486        while True:
 5487            field = self._try_parse(self._parse_pivot_in)
 5488            if not field:
 5489                break
 5490            fields.append(field)
 5491
 5492        default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped(
 5493            self._parse_bitwise
 5494        )
 5495
 5496        group = self._parse_group()
 5497
 5498        self._match_r_paren()
 5499
 5500        pivot = self.expression(
 5501            exp.Pivot(
 5502                expressions=expressions,
 5503                fields=fields,
 5504                unpivot=unpivot,
 5505                include_nulls=include_nulls,
 5506                default_on_null=default_on_null,
 5507                group=group,
 5508            )
 5509        )
 5510
 5511        if unpivot:
 5512            pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions])
 5513            for pivot_field in pivot.fields:
 5514                if isinstance(pivot_field, exp.In):
 5515                    pivot_field.set("this", _unpivot_target(pivot_field.this))
 5516
 5517            pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST)
 5518
 5519        if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False):
 5520            pivot.set("alias", self._parse_table_alias())
 5521
 5522        if not unpivot:
 5523            names = self._pivot_column_names(t.cast(list[exp.Expr], expressions))
 5524
 5525            columns: list[exp.Expr] = []
 5526            all_fields = []
 5527            for pivot_field in pivot.fields:
 5528                pivot_field_expressions = pivot_field.expressions
 5529
 5530                # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case.
 5531                if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny):
 5532                    continue
 5533
 5534                all_fields.append(
 5535                    [
 5536                        # An explicit `<field> AS <alias>` names the output column directly,
 5537                        # so it wins over the dialect's string-identifying convention
 5538                        fld.sql()
 5539                        if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias)
 5540                        else fld.alias_or_name
 5541                        for fld in pivot_field_expressions
 5542                    ]
 5543                )
 5544
 5545            if all_fields:
 5546                if names:
 5547                    all_fields.append(names)
 5548
 5549                # Generate all possible combinations of the pivot columns
 5550                # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US'))
 5551                # generates the product between [[2000, 2010], ['NL', 'US'], ['total']]
 5552                for fld_parts_tuple in itertools.product(*all_fields):
 5553                    fld_parts = list(fld_parts_tuple)
 5554
 5555                    if names and self.PREFIXED_PIVOT_COLUMNS:
 5556                        # Move the "name" to the front of the list
 5557                        fld_parts.insert(0, fld_parts.pop(-1))
 5558
 5559                    columns.append(exp.to_identifier("_".join(fld_parts)))
 5560
 5561            pivot.set("columns", columns)
 5562            pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS)
 5563            pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS)
 5564            pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING)
 5565
 5566        return pivot
 5567
 5568    def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]:
 5569        return [agg.alias for agg in aggregations if agg.alias]
 5570
 5571    def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None:
 5572        if not skip_where_token and not self._match(TokenType.PREWHERE):
 5573            return None
 5574
 5575        comments = self._prev_comments
 5576        return self.expression(
 5577            exp.PreWhere(this=self._parse_disjunction()),
 5578            comments=comments,
 5579        )
 5580
 5581    def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None:
 5582        if not skip_where_token and not self._match(TokenType.WHERE):
 5583            return None
 5584
 5585        comments = self._prev_comments
 5586        return self.expression(
 5587            exp.Where(this=self._parse_disjunction()),
 5588            comments=comments,
 5589        )
 5590
 5591    def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None:
 5592        if not skip_group_by_token and not self._match(TokenType.GROUP_BY):
 5593            return None
 5594        comments = self._prev_comments
 5595
 5596        elements: dict[str, t.Any] = defaultdict(list)
 5597
 5598        if self._match(TokenType.ALL):
 5599            elements["all"] = True
 5600        elif self._match(TokenType.DISTINCT):
 5601            elements["all"] = False
 5602
 5603        while True:
 5604            # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW,
 5605            # which are also valid identifiers
 5606            if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False):
 5607                break
 5608
 5609            elements["expressions"].extend(
 5610                self._parse_csv(
 5611                    lambda: (
 5612                        self._parse_grouping_sets()
 5613                        or self._parse_cube_or_rollup()
 5614                        or self._parse_disjunction()
 5615                    )
 5616                )
 5617            )
 5618
 5619            before_with_index = self._index
 5620
 5621            if self._match(TokenType.WITH) and (
 5622                cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True)
 5623            ):
 5624                key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube"
 5625                elements[key].append(cube_or_rollup)
 5626            elif grouping_sets := self._parse_grouping_sets():
 5627                # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...)
 5628                elements["grouping_sets"].append(grouping_sets)
 5629                break
 5630            elif self._match_text_seq("TOTALS"):
 5631                elements["totals"] = True  # type: ignore
 5632
 5633            if before_with_index <= self._index <= before_with_index + 1:
 5634                self._retreat(before_with_index)
 5635                break
 5636
 5637        return self.expression(exp.Group(**elements), comments=comments)  # type: ignore
 5638
 5639    def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None:
 5640        if self._match(TokenType.CUBE):
 5641            kind: type[exp.Cube | exp.Rollup] = exp.Cube
 5642        elif self._match(TokenType.ROLLUP):
 5643            kind = exp.Rollup
 5644        else:
 5645            return None
 5646
 5647        return self.expression(
 5648            kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise))
 5649        )
 5650
 5651    def _parse_grouping_sets(self) -> exp.GroupingSets | None:
 5652        if self._match(TokenType.GROUPING_SETS):
 5653            return self.expression(
 5654                exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set))
 5655            )
 5656        return None
 5657
 5658    def _parse_grouping_set(self) -> exp.Expr | None:
 5659        return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise()
 5660
 5661    def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None:
 5662        if not skip_having_token and not self._match(TokenType.HAVING):
 5663            return None
 5664        comments = self._prev_comments
 5665        return self.expression(
 5666            exp.Having(this=self._parse_disjunction()),
 5667            comments=comments,
 5668        )
 5669
 5670    def _parse_qualify(self) -> exp.Qualify | None:
 5671        if not self._match(TokenType.QUALIFY):
 5672            return None
 5673        return self.expression(exp.Qualify(this=self._parse_disjunction()))
 5674
 5675    def _parse_connect_with_prior(self) -> exp.Expr | None:
 5676        self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression(
 5677            exp.Prior(this=self._parse_bitwise())
 5678        )
 5679        connect = self._parse_disjunction()
 5680        self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR")
 5681        return connect
 5682
 5683    def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None:
 5684        if skip_start_token:
 5685            start = None
 5686        elif self._match_text_seq("START", "WITH"):
 5687            start = self._parse_disjunction()
 5688        else:
 5689            return None
 5690
 5691        self._match(TokenType.CONNECT_BY)
 5692        nocycle = self._match_text_seq("NOCYCLE")
 5693        connect = self._parse_connect_with_prior()
 5694
 5695        if not start and self._match_text_seq("START", "WITH"):
 5696            start = self._parse_disjunction()
 5697
 5698        return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle))
 5699
 5700    def _parse_name_as_expression(self) -> exp.Expr | None:
 5701        this = self._parse_id_var(any_token=True)
 5702        if self._match(TokenType.ALIAS):
 5703            this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction()))
 5704        return this
 5705
 5706    def _parse_interpolate(self) -> list[exp.Expr] | None:
 5707        if self._match_text_seq("INTERPOLATE"):
 5708            return self._parse_wrapped_csv(self._parse_name_as_expression)
 5709        return None
 5710
 5711    def _parse_order(
 5712        self, this: exp.Expr | None = None, skip_order_token: bool = False
 5713    ) -> exp.Expr | None:
 5714        siblings = None
 5715        if not skip_order_token and not self._match(TokenType.ORDER_BY):
 5716            if not self._match(TokenType.ORDER_SIBLINGS_BY):
 5717                return this
 5718
 5719            siblings = True
 5720
 5721        comments = self._prev_comments
 5722        return self.expression(
 5723            exp.Order(
 5724                this=this,
 5725                expressions=self._parse_csv(self._parse_ordered),
 5726                siblings=siblings,
 5727            ),
 5728            comments=comments,
 5729        )
 5730
 5731    def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None:
 5732        if not self._match(token):
 5733            return None
 5734        return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered)))
 5735
 5736    def _parse_ordered(
 5737        self, parse_method: t.Callable[[], exp.Expr | None] | None = None
 5738    ) -> exp.Ordered | None:
 5739        this = parse_method() if parse_method else self._parse_disjunction()
 5740        if not this:
 5741            return None
 5742
 5743        if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL:
 5744            this = exp.var("ALL")
 5745
 5746        asc = self._match(TokenType.ASC)
 5747        desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None)
 5748
 5749        is_nulls_first = self._match_text_seq("NULLS", "FIRST")
 5750        is_nulls_last = self._match_text_seq("NULLS", "LAST")
 5751
 5752        nulls_first = is_nulls_first or False
 5753        explicitly_null_ordered = is_nulls_first or is_nulls_last
 5754
 5755        if (
 5756            not explicitly_null_ordered
 5757            and (
 5758                (not desc and self.dialect.NULL_ORDERING == "nulls_are_small")
 5759                or (desc and self.dialect.NULL_ORDERING != "nulls_are_small")
 5760            )
 5761            and self.dialect.NULL_ORDERING != "nulls_are_last"
 5762        ):
 5763            nulls_first = True
 5764
 5765        if self._match_text_seq("WITH", "FILL"):
 5766            with_fill = self.expression(
 5767                exp.WithFill(
 5768                    from_=self._match(TokenType.FROM) and self._parse_bitwise(),
 5769                    to=self._match_text_seq("TO") and self._parse_bitwise(),
 5770                    step=self._match_text_seq("STEP") and self._parse_bitwise(),
 5771                    interpolate=self._parse_interpolate(),
 5772                )
 5773            )
 5774        else:
 5775            with_fill = None
 5776
 5777        return self.expression(
 5778            exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill)
 5779        )
 5780
 5781    def _parse_limit_options(self) -> exp.LimitOptions | None:
 5782        percent = self._match_set((TokenType.PERCENT, TokenType.MOD))
 5783        rows = self._match_texts(("ROW", "ROWS"))
 5784        self._match_text_seq("ONLY")
 5785        with_ties = self._match_text_seq("WITH", "TIES")
 5786
 5787        if not (percent or rows or with_ties):
 5788            return None
 5789
 5790        return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties))
 5791
 5792    def _parse_limit(
 5793        self,
 5794        this: exp.Expr | None = None,
 5795        top: bool = False,
 5796        skip_limit_token: bool = False,
 5797    ) -> exp.Expr | None:
 5798        if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT):
 5799            comments = self._prev_comments
 5800            if top:
 5801                limit_paren = self._match(TokenType.L_PAREN)
 5802                expression = (
 5803                    self._parse_term() or self._parse_select()
 5804                    if limit_paren
 5805                    else self._parse_number()
 5806                )
 5807
 5808                if limit_paren:
 5809                    self._match_r_paren()
 5810
 5811            else:
 5812                if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL):
 5813                    return this
 5814
 5815                expression = self._parse_term(parse_mod=False)
 5816            limit_options = self._parse_limit_options()
 5817
 5818            if self._match(TokenType.COMMA):
 5819                offset = expression
 5820                expression = self._parse_term()
 5821            else:
 5822                offset = None
 5823
 5824            limit_exp = self.expression(
 5825                exp.Limit(
 5826                    this=this,
 5827                    expression=expression,
 5828                    offset=offset,
 5829                    limit_options=limit_options,
 5830                    expressions=self._parse_limit_by(),
 5831                ),
 5832                comments=comments,
 5833            )
 5834
 5835            if top:
 5836                limit_exp.meta["top"] = True
 5837
 5838            return limit_exp
 5839
 5840        if self._match(TokenType.FETCH):
 5841            direction = (
 5842                self._prev.text.upper()
 5843                if self._match_set((TokenType.FIRST, TokenType.NEXT))
 5844                else "FIRST"
 5845            )
 5846
 5847            count = (
 5848                None
 5849                if self._match_texts(("ROW", "ROWS"), advance=False)
 5850                else self._parse_field(tokens=self.FETCH_TOKENS)
 5851            )
 5852
 5853            return self.expression(
 5854                exp.Fetch(
 5855                    direction=direction, count=count, limit_options=self._parse_limit_options()
 5856                )
 5857            )
 5858
 5859        return this
 5860
 5861    def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None:
 5862        if not self._match(TokenType.OFFSET):
 5863            return this
 5864
 5865        count = self._parse_term()
 5866        self._match_texts(("ROW", "ROWS"))
 5867
 5868        return self.expression(
 5869            exp.Offset(this=this, expression=count, expressions=self._parse_limit_by())
 5870        )
 5871
 5872    def _can_parse_limit_or_offset(self) -> bool:
 5873        if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False):
 5874            return False
 5875
 5876        index = self._index
 5877        result = bool(
 5878            self._try_parse(self._parse_limit, retreat=True)
 5879            or self._try_parse(self._parse_offset, retreat=True)
 5880        )
 5881        self._retreat(index)
 5882
 5883        # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset
 5884        if self._next.token_type == TokenType.MATCH_CONDITION:
 5885            result = False
 5886
 5887        return result
 5888
 5889    def _can_parse_named_window(self) -> bool:
 5890        # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias.
 5891        # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`.
 5892        if not self._match(TokenType.WINDOW, advance=False):
 5893            return False
 5894
 5895        name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None
 5896        if name is None or name.token_type not in self.ID_VAR_TOKENS:
 5897            return False
 5898
 5899        alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None
 5900        if alias_tok is None or alias_tok.token_type != TokenType.ALIAS:
 5901            return False
 5902
 5903        body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None
 5904        return body is not None and body.token_type == TokenType.L_PAREN
 5905
 5906    def _parse_limit_by(self) -> list[exp.Expr] | None:
 5907        return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None
 5908
 5909    def _parse_locks(self) -> list[exp.Lock]:
 5910        locks = []
 5911        while True:
 5912            update, key = None, None
 5913            if self._match_text_seq("FOR", "UPDATE"):
 5914                update = True
 5915            elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq(
 5916                "LOCK", "IN", "SHARE", "MODE"
 5917            ):
 5918                update = False
 5919            elif self._match_text_seq("FOR", "KEY", "SHARE"):
 5920                update, key = False, True
 5921            elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"):
 5922                update, key = True, True
 5923            else:
 5924                break
 5925
 5926            expressions = None
 5927            if self._match_text_seq("OF"):
 5928                expressions = self._parse_csv(lambda: self._parse_table(schema=True))
 5929
 5930            wait: bool | exp.Expr | None = None
 5931            if self._match_text_seq("NOWAIT"):
 5932                wait = True
 5933            elif self._match_text_seq("WAIT"):
 5934                wait = self._parse_primary()
 5935            elif self._match_text_seq("SKIP", "LOCKED"):
 5936                wait = False
 5937
 5938            locks.append(
 5939                self.expression(
 5940                    exp.Lock(update=update, expressions=expressions, wait=wait, key=key)
 5941                )
 5942            )
 5943
 5944        return locks
 5945
 5946    def parse_set_operation(
 5947        self, this: exp.Expr | None, consume_pipe: bool = False
 5948    ) -> exp.Expr | None:
 5949        start = self._index
 5950        _, side_token, kind_token = self._parse_join_parts()
 5951
 5952        side = side_token.text if side_token else None
 5953        kind = kind_token.text if kind_token else None
 5954
 5955        if not self._match_set(self.SET_OPERATIONS):
 5956            self._retreat(start)
 5957            return None
 5958
 5959        token_type = self._prev.token_type
 5960
 5961        if token_type == TokenType.UNION:
 5962            operation: type[exp.SetOperation] = exp.Union
 5963        elif token_type == TokenType.EXCEPT:
 5964            operation = exp.Except
 5965        else:
 5966            operation = exp.Intersect
 5967
 5968        comments = self._prev.comments
 5969
 5970        if self._match(TokenType.DISTINCT):
 5971            distinct: bool | None = True
 5972        elif self._match(TokenType.ALL):
 5973            distinct = False
 5974        else:
 5975            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
 5976            if distinct is None:
 5977                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
 5978
 5979        by_name = (
 5980            self._match_text_seq("BY", "NAME")
 5981            or self._match_text_seq("STRICT", "CORRESPONDING")
 5982            or None
 5983        )
 5984        if self._match_text_seq("CORRESPONDING"):
 5985            by_name = True
 5986            if not side and not kind:
 5987                kind = "INNER"
 5988
 5989        on_column_list = None
 5990        if by_name and self._match_texts(("ON", "BY")):
 5991            on_column_list = self._parse_wrapped_csv(self._parse_column)
 5992
 5993        expression = self._parse_select(
 5994            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
 5995        )
 5996
 5997        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
 5998        # in _parse_cte and so that alias pushdown can reach into set operation branches
 5999        if isinstance(this, exp.Values):
 6000            this = self._values_to_select(this)
 6001        if isinstance(expression, exp.Values):
 6002            expression = self._values_to_select(expression)
 6003
 6004        if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery):
 6005            subquery = this.this
 6006            subquery.set("alias", exp.TableAlias(this=this.args["alias"]))
 6007            subquery.add_comments(this.pop_comments())
 6008            this = subquery
 6009
 6010        return self.expression(
 6011            operation(
 6012                this=this,
 6013                distinct=distinct,
 6014                by_name=by_name,
 6015                expression=expression,
 6016                side=side,
 6017                kind=kind,
 6018                on=on_column_list,
 6019            ),
 6020            comments=comments,
 6021        )
 6022
 6023    def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None:
 6024        while this:
 6025            setop = self.parse_set_operation(this)
 6026            if not setop:
 6027                break
 6028            this = setop
 6029
 6030        if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP:
 6031            expression = this.expression
 6032
 6033            if expression:
 6034                for arg in self.SET_OP_MODIFIERS:
 6035                    expr = expression.args.get(arg)
 6036                    if expr and not (arg == "limit" and expr.meta.get("top")):
 6037                        expression.set(arg, None)
 6038                        this.set(arg, expr)
 6039
 6040            # A trailing LIMIT/FETCH can coexist with TOP on the final operand.
 6041            if self._curr.token_type in (TokenType.LIMIT, TokenType.FETCH):
 6042                this = self._parse_query_modifiers(this)
 6043
 6044        return this
 6045
 6046    def _parse_expression(self) -> exp.Expr | None:
 6047        return self._parse_alias(self._parse_assignment())
 6048
 6049    def _parse_assignment(self) -> exp.Expr | None:
 6050        this = self._parse_disjunction()
 6051        if not this and self._next.token_type in self.ASSIGNMENT:
 6052            # This allows us to parse <non-identifier token> := <expr>
 6053            this = exp.column(
 6054                t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text)
 6055            )
 6056
 6057        while self._match_set(self.ASSIGNMENT):
 6058            if isinstance(this, exp.Column) and len(this.parts) == 1:
 6059                this = this.this
 6060
 6061            comments = self._prev_comments
 6062            this = self.expression(
 6063                self.ASSIGNMENT[self._prev.token_type](
 6064                    this=this, expression=self._parse_assignment()
 6065                ),
 6066                comments=comments,
 6067            )
 6068
 6069        return this
 6070
 6071    def _parse_disjunction(self) -> exp.Expr | None:
 6072        this = self._parse_conjunction()
 6073        while self._match_set(self.DISJUNCTION):
 6074            comments = self._prev_comments
 6075            this = self.expression(
 6076                self.DISJUNCTION[self._prev.token_type](
 6077                    this=this, expression=self._parse_conjunction()
 6078                ),
 6079                comments=comments,
 6080            )
 6081        return this
 6082
 6083    def _parse_conjunction(self) -> exp.Expr | None:
 6084        this = self._parse_equality()
 6085        while self._match_set(self.CONJUNCTION):
 6086            comments = self._prev_comments
 6087            this = self.expression(
 6088                self.CONJUNCTION[self._prev.token_type](
 6089                    this=this, expression=self._parse_equality()
 6090                ),
 6091                comments=comments,
 6092            )
 6093        return this
 6094
 6095    def _parse_equality(self) -> exp.Expr | None:
 6096        this = self._parse_comparison()
 6097        while self._match_set(self.EQUALITY):
 6098            comments = self._prev_comments
 6099            this = self.expression(
 6100                self.EQUALITY[self._prev.token_type](
 6101                    this=this, expression=self._parse_comparison()
 6102                ),
 6103                comments=comments,
 6104            )
 6105        return this
 6106
 6107    def _parse_comparison(self) -> exp.Expr | None:
 6108        this = self._parse_range()
 6109        while self._match_set(self.COMPARISON):
 6110            comments = self._prev_comments
 6111            this = self.expression(
 6112                self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()),
 6113                comments=comments,
 6114            )
 6115        return this
 6116
 6117    def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6118        this = this or self._parse_bitwise()
 6119
 6120        while True:
 6121            negate = self._match(TokenType.NOT)
 6122            if self._match_set(self.RANGE_PARSERS):
 6123                expression = self.RANGE_PARSERS[self._prev.token_type](self, this)
 6124                if not expression:
 6125                    return this
 6126
 6127                this = expression
 6128            elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)):
 6129                this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6130            elif self._match(TokenType.NOTNULL):
 6131                # Postgres supports ISNULL and NOTNULL for conditions.
 6132                # https://blog.andreiavram.ro/postgresql-null-composite-type/
 6133                if self.dialect.NORMALIZE_NOT_NULL:
 6134                    this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6135                    this = self.expression(exp.Not(this=this))
 6136                else:
 6137                    this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True))
 6138            else:
 6139                if negate:
 6140                    self._retreat(self._index - 1)
 6141                break
 6142
 6143            if negate:
 6144                this = self._negate_range(this)
 6145                if self._curr and (
 6146                    self._curr.token_type == TokenType.NOT
 6147                    or self._curr.token_type in self.RANGE_PARSERS
 6148                ):
 6149                    this = self.expression(exp.Paren(this=this))
 6150
 6151        return this
 6152
 6153    def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6154        if not this:
 6155            return this
 6156
 6157        expression = this.this if isinstance(this, exp.Escape) else this
 6158        if isinstance(expression, (exp.Like, exp.ILike)):
 6159            expression.set("negate", True)
 6160            return this
 6161
 6162        return self.expression(exp.Not(this=this))
 6163
 6164    def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None:
 6165        index = self._index - 1
 6166        negate = self._match(TokenType.NOT)
 6167
 6168        if self._match_text_seq("DISTINCT", "FROM"):
 6169            klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ
 6170            return self.expression(klass(this=this, expression=self._parse_bitwise()))
 6171
 6172        if self._match(TokenType.JSON):
 6173            kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper()
 6174
 6175            if self._match_text_seq("WITH"):
 6176                _with = True
 6177            elif self._match_text_seq("WITHOUT"):
 6178                _with = False
 6179            else:
 6180                _with = None
 6181
 6182            unique = self._match(TokenType.UNIQUE)
 6183            self._match_text_seq("KEYS")
 6184            expression: exp.Expr | None = self.expression(
 6185                exp.JSON(this=kind, with_=_with, unique=unique)
 6186            )
 6187        else:
 6188            expression = self._parse_null() or self._parse_bitwise()
 6189            if not expression:
 6190                self._retreat(index)
 6191                return None
 6192
 6193        if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL:
 6194            this = self.expression(exp.Is(this=this, expression=expression, negate=True))
 6195        else:
 6196            this = self.expression(exp.Is(this=this, expression=expression))
 6197            this = self.expression(exp.Not(this=this)) if negate else this
 6198
 6199        return self._parse_column_ops(this)
 6200
 6201    def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In:
 6202        unnest = self._parse_unnest(with_alias=False)
 6203        if unnest:
 6204            this = self.expression(exp.In(this=this, unnest=unnest))
 6205        elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)):
 6206            matched_l_paren = self._prev.token_type == TokenType.L_PAREN
 6207            expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias))
 6208
 6209            if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query):
 6210                this = self.expression(
 6211                    exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False))
 6212                )
 6213            else:
 6214                this = self.expression(exp.In(this=this, expressions=expressions))
 6215
 6216            if matched_l_paren:
 6217                self._match_r_paren(this)
 6218            elif not self._match(TokenType.R_BRACKET, expression=this):
 6219                self.raise_error("Expecting ]")
 6220        else:
 6221            this = self.expression(exp.In(this=this, field=self._parse_column()))
 6222
 6223        return this
 6224
 6225    def _parse_between(self, this: exp.Expr | None) -> exp.Between:
 6226        symmetric = None
 6227        if self._match_text_seq("SYMMETRIC"):
 6228            symmetric = True
 6229        elif self._match_text_seq("ASYMMETRIC"):
 6230            symmetric = False
 6231
 6232        low = self._parse_bitwise()
 6233        self._match(TokenType.AND)
 6234        high = self._parse_bitwise()
 6235
 6236        return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric))
 6237
 6238    def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None:
 6239        if not self._match(TokenType.ESCAPE):
 6240            return this
 6241        return self.expression(
 6242            exp.Escape(this=this, expression=self._parse_string() or self._parse_null())
 6243        )
 6244
 6245    def _parse_interval_span(
 6246        self, this: exp.Expr, parse_function_unit: bool = True
 6247    ) -> exp.Interval:
 6248        # handle day-time format interval span with omitted units:
 6249        #   INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`>
 6250        interval_span_units_omitted = None
 6251        if (
 6252            this
 6253            and this.is_string
 6254            and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
 6255            and exp.INTERVAL_DAY_TIME_RE.match(this.name)
 6256        ):
 6257            index = self._index
 6258
 6259            # Var "TO" Var
 6260            first_unit = self._parse_var(any_token=True, upper=True)
 6261            second_unit = None
 6262            if first_unit and self._match_text_seq("TO"):
 6263                second_unit = self._parse_var(any_token=True, upper=True)
 6264
 6265            interval_span_units_omitted = not (first_unit and second_unit)
 6266
 6267            self._retreat(index)
 6268
 6269        unit_index = self._index
 6270        if interval_span_units_omitted:
 6271            unit = None
 6272        else:
 6273            # Only attempt to parse a unit if the current token can actually be one, so that a
 6274            # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x)
 6275            is_unit = self._curr is not None and (
 6276                self._curr.token_type == TokenType.VAR
 6277                or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS
 6278            )
 6279            unit = self._parse_function() if parse_function_unit and is_unit else None
 6280            if not unit and is_unit:
 6281                unit = self._parse_var(any_token=True, upper=True)
 6282
 6283        # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse
 6284        # each INTERVAL expression into this canonical form so it's easy to transpile
 6285        if this and this.is_number:
 6286            try:
 6287                this = exp.Literal.string(this.to_py())
 6288            except ValueError:
 6289                self.raise_error(f"Invalid numeric interval literal: {this.name!r}")
 6290        elif this and this.is_string:
 6291            parts = exp.INTERVAL_STRING_RE.findall(this.name)
 6292            if parts and unit:
 6293                # Unconsume the eagerly-parsed unit, since the real unit was part of the string
 6294                unit = None
 6295                self._retreat(unit_index)
 6296
 6297            if len(parts) == 1:
 6298                this = exp.Literal.string(parts[0][0])
 6299                unit = self.expression(exp.Var(this=parts[0][1].upper()))
 6300
 6301        if self.INTERVAL_SPANS and self._match_text_seq("TO"):
 6302            unit = self.expression(
 6303                exp.IntervalSpan(
 6304                    this=unit,
 6305                    expression=self._parse_function()
 6306                    or self._parse_var(any_token=True, upper=True),
 6307                )
 6308            )
 6309
 6310        return self.expression(exp.Interval(this=this, unit=unit))
 6311
 6312    def _parse_interval(
 6313        self, require_interval: bool = True, parse_function_unit: bool = True
 6314    ) -> exp.Add | exp.Interval | None:
 6315        index = self._index
 6316
 6317        if not self._match(TokenType.INTERVAL) and require_interval:
 6318            return None
 6319
 6320        if self._match(TokenType.STRING, advance=False):
 6321            this = self._parse_primary()
 6322        else:
 6323            this = self._parse_term()
 6324
 6325        if not this or (
 6326            isinstance(this, exp.Column)
 6327            and not this.table
 6328            and not this.this.quoted
 6329            and self._curr
 6330            and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS
 6331        ):
 6332            self._retreat(index)
 6333            return None
 6334
 6335        interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit)
 6336
 6337        index = self._index
 6338        self._match(TokenType.PLUS)
 6339
 6340        # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals
 6341        if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False):
 6342            return self.expression(
 6343                exp.Add(
 6344                    this=interval,
 6345                    expression=self._parse_interval(False, parse_function_unit=parse_function_unit),
 6346                )
 6347            )
 6348
 6349        self._retreat(index)
 6350        return interval
 6351
 6352    def _parse_bitwise(self) -> exp.Expr | None:
 6353        this = self._parse_term()
 6354
 6355        while True:
 6356            if self._match_set(self.BITWISE):
 6357                this = self.expression(
 6358                    self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term())
 6359                )
 6360            elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE):
 6361                this = self.expression(
 6362                    exp.DPipe(
 6363                        this=this,
 6364                        expression=self._parse_term(),
 6365                        safe=not self.dialect.STRICT_STRING_CONCAT,
 6366                    )
 6367                )
 6368            elif self._match(TokenType.DQMARK):
 6369                this = self.expression(
 6370                    exp.Coalesce(this=this, expressions=ensure_list(self._parse_term()))
 6371                )
 6372            elif self._match_pair(TokenType.LT, TokenType.LT):
 6373                this = self.expression(
 6374                    exp.BitwiseLeftShift(this=this, expression=self._parse_term())
 6375                )
 6376            elif self._match_pair(TokenType.GT, TokenType.GT):
 6377                this = self.expression(
 6378                    exp.BitwiseRightShift(this=this, expression=self._parse_term())
 6379                )
 6380            elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS):
 6381                this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term())
 6382            else:
 6383                break
 6384
 6385        return this
 6386
 6387    def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None:
 6388        this = self._parse_factor(parse_mod=parse_mod)
 6389
 6390        while self._match_set(self.TERM):
 6391            klass = self.TERM[self._prev.token_type]
 6392            comments = self._prev_comments
 6393            expression = self._parse_factor(parse_mod=parse_mod)
 6394
 6395            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6396
 6397            if isinstance(this, exp.Collate):
 6398                self._normalize_collate(this)
 6399
 6400        return this
 6401
 6402    def _normalize_collate(self, collate: exp.Collate) -> None:
 6403        expr = collate.expression
 6404
 6405        # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise
 6406        # fallback to Identifier / Var
 6407        if isinstance(expr, exp.Column) and len(expr.parts) == 1:
 6408            ident = expr.this
 6409            if isinstance(ident, exp.Identifier):
 6410                collate.set("expression", ident if ident.quoted else exp.var(ident.name))
 6411
 6412    def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None:
 6413        parse_method = self._parse_factor_operand
 6414        this = self._parse_at_time_zone(parse_method())
 6415
 6416        while self._match_set(self.FACTOR, advance=False):
 6417            if not parse_mod and self._curr.token_type == TokenType.MOD:
 6418                break
 6419
 6420            self._advance()
 6421            klass = self.FACTOR[self._prev.token_type]
 6422            comments = self._prev_comments
 6423            expression = parse_method()
 6424
 6425            if not expression and klass is exp.IntDiv and self._prev.text.isalpha():
 6426                self._retreat(self._index - 1)
 6427                return this
 6428
 6429            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6430
 6431            if isinstance(this, exp.Div):
 6432                this.set("typed", self.dialect.TYPED_DIVISION)
 6433                this.set("safe", self.dialect.SAFE_DIVISION)
 6434
 6435        return this
 6436
 6437    def _parse_factor_operand(self) -> exp.Expr | None:
 6438        return self._parse_exponent() if self.EXPONENT else self._parse_unary()
 6439
 6440    def _parse_exponent(self) -> exp.Expr | None:
 6441        this = self._parse_unary()
 6442        while self._match_set(self.EXPONENT):
 6443            comments = self._prev_comments
 6444            this = self.expression(
 6445                self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()),
 6446                comments=comments,
 6447            )
 6448        return this
 6449
 6450    def _parse_unary(self) -> exp.Expr | None:
 6451        if self._match_set(self.UNARY_PARSERS):
 6452            return self.UNARY_PARSERS[self._prev.token_type](self)
 6453        return self._parse_type()
 6454
 6455    def _parse_type(
 6456        self, parse_interval: bool = True, fallback_to_identifier: bool = False
 6457    ) -> exp.Expr | None:
 6458        if not fallback_to_identifier and (atom := self._parse_atom()) is not None:
 6459            return atom
 6460
 6461        if interval := parse_interval and self._parse_interval():
 6462            return self._parse_column_ops(interval)
 6463
 6464        index = self._index
 6465        data_type = self._parse_types(check_func=True, allow_identifiers=False)
 6466
 6467        # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g.
 6468        # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>)
 6469        if isinstance(data_type, exp.Cast):
 6470            # This constructor can contain ops directly after it, for instance struct unnesting:
 6471            # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).*
 6472            return self._parse_column_ops(data_type)
 6473
 6474        if data_type:
 6475            index2 = self._index
 6476            this = self._parse_primary()
 6477
 6478            if isinstance(this, exp.Literal):
 6479                literal = this.name
 6480                this = self._parse_column_ops(this)
 6481
 6482                parser = self.TYPE_LITERAL_PARSERS.get(data_type.this)
 6483                if parser:
 6484                    return parser(self, this, data_type)
 6485
 6486                if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal):
 6487                    if data_type.is_type(exp.DType.TIMESTAMP):
 6488                        data_type = exp.DType.TIMESTAMPTZ.into_expr()
 6489                    elif data_type.is_type(exp.DType.TIME):
 6490                        data_type = exp.DType.TIMETZ.into_expr()
 6491
 6492                return self.expression(exp.Cast(this=this, to=data_type))
 6493
 6494            # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0)
 6495            # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 )
 6496            #
 6497            # If the index difference here is greater than 1, that means the parser itself must have
 6498            # consumed additional tokens such as the DECIMAL scale and precision in the above example.
 6499            #
 6500            # If it's not greater than 1, then it must be 1, because we've consumed at least the type
 6501            # keyword, meaning that the expressions arg of the DataType must have gotten set by a
 6502            # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to
 6503            # DECIMAL(38, 0)) in order to facilitate the data type's transpilation.
 6504            #
 6505            # In these cases, we don't really want to return the converted type, but instead retreat
 6506            # and try to parse a Column or Identifier in the section below.
 6507            if data_type.expressions and index2 - index > 1:
 6508                self._retreat(index2)
 6509                return self._parse_column_ops(data_type)
 6510
 6511            self._retreat(index)
 6512
 6513        if fallback_to_identifier:
 6514            return self._parse_id_var()
 6515
 6516        return self._parse_column()
 6517
 6518    def _parse_type_size(self) -> exp.DataTypeParam | None:
 6519        this = self._parse_type()
 6520        if not this:
 6521            return None
 6522
 6523        if isinstance(this, exp.Column) and not this.table:
 6524            this = exp.var(this.name.upper())
 6525
 6526        return self.expression(
 6527            exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True))
 6528        )
 6529
 6530    def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None:
 6531        type_name = identifier.name
 6532
 6533        while self._match(TokenType.DOT):
 6534            type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
 6535
 6536        return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True)
 6537
 6538    def _parse_types(
 6539        self,
 6540        check_func: bool = False,
 6541        schema: bool = False,
 6542        allow_identifiers: bool = True,
 6543        with_collation: bool = False,
 6544    ) -> exp.Expr | None:
 6545        index = self._index
 6546        this: exp.Expr | None = None
 6547
 6548        if self._match_set(self.TYPE_TOKENS):
 6549            type_token = self._prev.token_type
 6550        else:
 6551            type_token = None
 6552            identifier = allow_identifiers and self._parse_id_var(
 6553                any_token=False, tokens=(TokenType.VAR,)
 6554            )
 6555            if isinstance(identifier, exp.Identifier):
 6556                if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE:
 6557                    this = exp.DataType.build(identifier, udt=True)
 6558                else:
 6559                    try:
 6560                        tokens = self.dialect.tokenize(identifier.name)
 6561                    except TokenError:
 6562                        tokens = None
 6563
 6564                    if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS:
 6565                        if len(tokens) > 1:
 6566                            return exp.DataType.from_str(identifier.name, dialect=self.dialect)
 6567                    elif self.dialect.SUPPORTS_USER_DEFINED_TYPES:
 6568                        this = self._parse_user_defined_type(identifier)
 6569                    else:
 6570                        self._retreat(self._index - 1)
 6571                        return None
 6572            else:
 6573                return None
 6574
 6575        if type_token == TokenType.PSEUDO_TYPE:
 6576            return self.expression(exp.PseudoType(this=self._prev.text.upper()))
 6577
 6578        if type_token == TokenType.OBJECT_IDENTIFIER:
 6579            return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper()))
 6580
 6581        # https://materialize.com/docs/sql/types/map/
 6582        if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET):
 6583            key_type = self._parse_types(
 6584                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6585            )
 6586            if not self._match(TokenType.FARROW):
 6587                self._retreat(index)
 6588                return None
 6589
 6590            value_type = self._parse_types(
 6591                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6592            )
 6593            if not self._match(TokenType.R_BRACKET):
 6594                self._retreat(index)
 6595                return None
 6596
 6597            return exp.DataType(
 6598                this=exp.DType.MAP,
 6599                expressions=[key_type, value_type],
 6600                nested=True,
 6601            )
 6602
 6603        nested = type_token in self.NESTED_TYPE_TOKENS
 6604        is_struct = type_token in self.STRUCT_TYPE_TOKENS
 6605        is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS
 6606        expressions = None
 6607        maybe_func = False
 6608
 6609        if self._match(TokenType.L_PAREN):
 6610            if is_struct:
 6611                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6612            elif nested:
 6613                expressions = self._parse_csv(
 6614                    lambda: self._parse_types(
 6615                        check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6616                    )
 6617                )
 6618                if type_token == TokenType.NULLABLE and len(expressions) == 1:
 6619                    this = expressions[0]
 6620                    this.set("nullable", True)
 6621                    self._match_r_paren()
 6622                    return this
 6623            elif type_token in self.ENUM_TYPE_TOKENS:
 6624                expressions = self._parse_csv(self._parse_equality)
 6625            elif type_token == TokenType.JSON:
 6626                # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value)
 6627                # https://clickhouse.com/docs/sql-reference/data-types/newjson
 6628                expressions = self._parse_csv(self._parse_json_type_arg)
 6629            elif is_aggregate:
 6630                func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var(
 6631                    any_token=False, tokens=(TokenType.VAR, TokenType.ANY)
 6632                )
 6633                if not func_or_ident:
 6634                    return None
 6635                expressions = [func_or_ident]
 6636                if self._match(TokenType.COMMA):
 6637                    expressions.extend(
 6638                        self._parse_csv(
 6639                            lambda: self._parse_types(
 6640                                check_func=check_func,
 6641                                schema=schema,
 6642                                allow_identifiers=allow_identifiers,
 6643                            )
 6644                        )
 6645                    )
 6646            else:
 6647                expressions = self._parse_csv(self._parse_type_size)
 6648
 6649                # https://docs.snowflake.com/en/sql-reference/data-types-vector
 6650                if type_token == TokenType.VECTOR and len(expressions) == 2:
 6651                    expressions = self._parse_vector_expressions(expressions)
 6652
 6653            if not self._match(TokenType.R_PAREN):
 6654                self._retreat(index)
 6655                return None
 6656
 6657            maybe_func = True
 6658
 6659        values: list[exp.Expr] | None = None
 6660
 6661        if nested and self._match(TokenType.LT):
 6662            if is_struct:
 6663                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6664            else:
 6665                expressions = self._parse_csv(
 6666                    lambda: self._parse_types(
 6667                        check_func=check_func,
 6668                        schema=schema,
 6669                        allow_identifiers=allow_identifiers,
 6670                        with_collation=True,
 6671                    )
 6672                )
 6673
 6674            if not self._match(TokenType.GT):
 6675                self.raise_error("Expecting >")
 6676
 6677            if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)):
 6678                values = self._parse_csv(self._parse_disjunction)
 6679                if not values and is_struct:
 6680                    values = None
 6681                    self._retreat(self._index - 1)
 6682                else:
 6683                    self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN))
 6684
 6685        if type_token in self.TIMESTAMPS:
 6686            if self._match_text_seq("WITH", "TIME", "ZONE"):
 6687                maybe_func = False
 6688                tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ
 6689                this = exp.DataType(this=tz_type, expressions=expressions)
 6690            elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"):
 6691                maybe_func = False
 6692                this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions)
 6693            elif self._match_text_seq("WITHOUT", "TIME", "ZONE"):
 6694                maybe_func = False
 6695        elif type_token == TokenType.INTERVAL:
 6696            if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS:
 6697                unit = self._parse_var(upper=True)
 6698                if self._match_text_seq("TO"):
 6699                    unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True))
 6700
 6701                this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit))))
 6702            else:
 6703                this = self.expression(exp.DataType(this=exp.DType.INTERVAL))
 6704        elif type_token == TokenType.VOID:
 6705            this = exp.DataType(this=exp.DType.NULL)
 6706
 6707        if maybe_func and check_func:
 6708            index2 = self._index
 6709            peek = self._parse_string()
 6710
 6711            if not peek:
 6712                self._retreat(index)
 6713                return None
 6714
 6715            self._retreat(index2)
 6716
 6717        if not this:
 6718            assert type_token is not None
 6719            if self._match_text_seq("UNSIGNED"):
 6720                unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token)
 6721                if not unsigned_type_token:
 6722                    self.raise_error(f"Cannot convert {type_token.name} to unsigned.")
 6723
 6724                type_token = unsigned_type_token or type_token
 6725
 6726            # NULLABLE without parentheses can be a column (Presto/Trino)
 6727            if type_token == TokenType.NULLABLE and not expressions:
 6728                self._retreat(index)
 6729                return None
 6730
 6731            this = exp.DataType(
 6732                this=exp.DType[type_token.name],
 6733                expressions=expressions,
 6734                nested=nested,
 6735            )
 6736
 6737            # Empty arrays/structs are allowed
 6738            if values is not None:
 6739                cls = exp.Struct if is_struct else exp.Array
 6740                this = exp.cast(cls(expressions=values), this, copy=False)
 6741
 6742        elif expressions:
 6743            this.set("expressions", expressions)
 6744
 6745        # https://materialize.com/docs/sql/types/list/#type-name
 6746        while self._match(TokenType.LIST):
 6747            this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True)
 6748
 6749        index = self._index
 6750
 6751        # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3]
 6752        matched_array = self._match(TokenType.ARRAY)
 6753
 6754        while self._curr:
 6755            datatype_token = self._prev.token_type
 6756            matched_l_bracket = self._match(TokenType.L_BRACKET)
 6757
 6758            if (not matched_l_bracket and not matched_array) or (
 6759                datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET)
 6760            ):
 6761                # Postgres allows casting empty arrays such as ARRAY[]::INT[],
 6762                # not to be confused with the fixed size array parsing
 6763                break
 6764
 6765            matched_array = False
 6766            values = self._parse_csv(self._parse_disjunction) or None
 6767            if (
 6768                values
 6769                and not schema
 6770                and (
 6771                    not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS
 6772                    or datatype_token == TokenType.ARRAY
 6773                    or not self._match(TokenType.R_BRACKET, advance=False)
 6774                )
 6775            ):
 6776                # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB
 6777                # ARRAY[1] should retreat and instead be parsed into exp.Array in contrast to INT[x][y] which denotes a fixed-size array data type
 6778                self._retreat(index)
 6779                break
 6780
 6781            this = exp.DataType(
 6782                this=exp.DType.ARRAY, expressions=[this], values=values, nested=True
 6783            )
 6784            self._match(TokenType.R_BRACKET)
 6785
 6786        if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType):
 6787            converter = self.TYPE_CONVERTERS.get(this.this)
 6788            if converter:
 6789                this = converter(t.cast(exp.DataType, this))
 6790
 6791        if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE):
 6792            this.set("collate", self._parse_identifier() or self._parse_column())
 6793
 6794        return this
 6795
 6796    def _parse_json_type_arg(self) -> exp.Expr | None:
 6797        """Parse a single argument to ClickHouse's JSON type."""
 6798
 6799        # SKIP col or SKIP REGEXP 'pattern'
 6800        if self._match_text_seq("SKIP"):
 6801            regexp = self._match(TokenType.RLIKE)
 6802            arg = self._parse_column()
 6803            if isinstance(arg, exp.Column):
 6804                arg = arg.to_dot()
 6805            return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg))
 6806
 6807        param_or_col = self._parse_column()
 6808        if not isinstance(param_or_col, exp.Column):
 6809            return None
 6810
 6811        # Parameter: name=value (e.g., max_dynamic_paths=2)
 6812        if len(param_or_col.parts) == 1 and self._match(TokenType.EQ):
 6813            param = param_or_col.name
 6814            value = self._parse_primary()
 6815            return self.expression(exp.EQ(this=exp.var(param), expression=value))
 6816
 6817        # Column type hint: col_name Type
 6818        col = param_or_col.to_dot()
 6819        kind = self._parse_types(check_func=False, allow_identifiers=False)
 6820        return self.expression(exp.ColumnDef(this=col, kind=kind))
 6821
 6822    def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]:
 6823        return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]]
 6824
 6825    def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None:
 6826        index = self._index
 6827
 6828        if (
 6829            self._curr
 6830            and self._next
 6831            and self._curr.token_type in self.TYPE_TOKENS
 6832            and self._next.token_type in self.TYPE_TOKENS
 6833        ):
 6834            # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a
 6835            # type token. Without this, the list will be parsed as a type and we'll eventually crash
 6836            this = self._parse_id_var()
 6837        else:
 6838            this = (
 6839                self._parse_type(parse_interval=False, fallback_to_identifier=True)
 6840                or self._parse_id_var()
 6841            )
 6842
 6843        self._match(TokenType.COLON)
 6844
 6845        if (
 6846            type_required
 6847            and not isinstance(this, exp.DataType)
 6848            and not self._match_set(self.TYPE_TOKENS, advance=False)
 6849        ):
 6850            self._retreat(index)
 6851            return self._parse_types()
 6852
 6853        return self._parse_column_def(this)
 6854
 6855    def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None:
 6856        if not self._match_text_seq("AT", "TIME", "ZONE"):
 6857            return this
 6858        return self._parse_at_time_zone(
 6859            self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary()))
 6860        )
 6861
 6862    def _parse_atom(self) -> exp.Expr | None:
 6863        if (
 6864            self._curr.token_type in self.IDENTIFIER_TOKENS
 6865            and (column := self._parse_column()) is not None
 6866        ):
 6867            return column
 6868
 6869        token = self._curr
 6870        token_type = token.token_type
 6871
 6872        if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)):
 6873            return None
 6874
 6875        next_type = self._next.token_type
 6876
 6877        if (
 6878            next_type in self.COLUMN_OPERATORS
 6879            or next_type in self.COLUMN_POSTFIX_TOKENS
 6880            or (token_type == TokenType.STRING and next_type == TokenType.STRING)
 6881        ):
 6882            return None
 6883
 6884        self._advance()
 6885        return primary_parser(self, token)
 6886
 6887    def _parse_column(self) -> exp.Expr | None:
 6888        column: exp.Expr | None = self._parse_column_parts_fast()
 6889        if column is None:
 6890            this = self._parse_column_reference()
 6891            if not this:
 6892                this = self._parse_bracket(this)
 6893            column = self._parse_column_ops(this) if this else this
 6894
 6895        if column:
 6896            if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 6897                column.set("join_mark", self._match(TokenType.JOIN_MARKER))
 6898            if self.COLON_IS_VARIANT_EXTRACT:
 6899                column = self._parse_colon_as_variant_extract(column)
 6900
 6901        return column
 6902
 6903    def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None:
 6904        """Fast path for simple column and dot references (a, a.b, ...).
 6905
 6906        Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks
 6907        that nothing complex follows. If it does, retreats and returns None so
 6908        the slow path can handle it. For >4 parts, wraps in exp.Dot nodes.
 6909        """
 6910        index = self._index
 6911        parts: list[exp.Identifier] | None = None
 6912        all_comments: list[str] | None = None
 6913
 6914        while self._match_set(self.IDENTIFIER_TOKENS):
 6915            token = self._prev
 6916            comments = self._prev_comments
 6917
 6918            if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS:
 6919                self._retreat(index)
 6920                return None
 6921
 6922            has_dot = self._match(TokenType.DOT)
 6923            curr_tt = self._curr.token_type
 6924
 6925            if not has_dot:
 6926                if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS:
 6927                    self._retreat(index)
 6928                    return None
 6929            elif curr_tt not in self.IDENTIFIER_TOKENS:
 6930                self._retreat(index)
 6931                return None
 6932
 6933            if parts is None:
 6934                parts = []
 6935
 6936            if comments:
 6937                if all_comments is None:
 6938                    all_comments = []
 6939                all_comments.extend(comments)
 6940                self._prev_comments = []
 6941
 6942            parts.append(
 6943                self.expression(
 6944                    exp.Identifier(
 6945                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 6946                    ),
 6947                    token,
 6948                )
 6949            )
 6950
 6951            if not has_dot:
 6952                break
 6953
 6954        if parts is None:
 6955            return None
 6956
 6957        n = len(parts)
 6958
 6959        if n == 1:
 6960            column: exp.Column | exp.Dot = exp.Column(this=parts[0])
 6961        elif n == 2:
 6962            column = exp.Column(this=parts[1], table=parts[0])
 6963        elif n == 3:
 6964            column = exp.Column(this=parts[2], table=parts[1], db=parts[0])
 6965        else:
 6966            column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0])
 6967
 6968            for i in range(4, n):
 6969                column = exp.Dot(this=column, expression=parts[i])
 6970
 6971        if all_comments:
 6972            column.add_comments(all_comments)
 6973
 6974        return column
 6975
 6976    def _parse_column_reference(self) -> exp.Expr | None:
 6977        this = self._parse_field()
 6978        if (
 6979            not this
 6980            and self._match(TokenType.VALUES, advance=False)
 6981            and self.VALUES_FOLLOWED_BY_PAREN
 6982            and (not self._next or self._next.token_type != TokenType.L_PAREN)
 6983        ):
 6984            this = self._parse_id_var()
 6985
 6986        if isinstance(this, exp.Identifier):
 6987            # We bubble up comments from the Identifier to the Column
 6988            this = self.expression(exp.Column(this=this), comments=this.pop_comments())
 6989
 6990        return this
 6991
 6992    def _build_json_extract(
 6993        self,
 6994        this: exp.Expr | None,
 6995        path_parts: list[exp.JSONPathPart],
 6996    ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]:
 6997        if len(path_parts) > 1:
 6998            this = self.expression(
 6999                exp.JSONExtract(
 7000                    this=this,
 7001                    expression=exp.JSONPath(expressions=path_parts),
 7002                    variant_extract=True,
 7003                    requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION,
 7004                )
 7005            )
 7006            path_parts = [exp.JSONPathRoot()]
 7007
 7008        return this, path_parts
 7009
 7010    def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None:
 7011        path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()]
 7012
 7013        while self._match(TokenType.COLON):
 7014            if not self.COLON_CHAIN_IS_SINGLE_EXTRACT:
 7015                this, path_parts = self._build_json_extract(this, path_parts)
 7016
 7017            key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 7018
 7019            if key:
 7020                quoted = isinstance(key, exp.Identifier) and key.quoted
 7021                path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted))
 7022
 7023            while True:
 7024                if self._match(TokenType.DOT):
 7025                    next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 7026
 7027                    if next_key:
 7028                        quoted = isinstance(next_key, exp.Identifier) and next_key.quoted
 7029                        path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted))
 7030                elif self._match(TokenType.L_BRACKET):
 7031                    bracket_expr = self._parse_bracket_key_value()
 7032
 7033                    if not self._match(TokenType.R_BRACKET):
 7034                        self.raise_error("Expected ]")
 7035
 7036                    if bracket_expr:
 7037                        if bracket_expr.is_string:
 7038                            path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True))
 7039                        elif bracket_expr.is_star:
 7040                            path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard()))
 7041                        elif bracket_expr.is_number:
 7042                            path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py()))
 7043                        else:
 7044                            this, path_parts = self._build_json_extract(this, path_parts)
 7045
 7046                            this = self.expression(
 7047                                exp.Bracket(
 7048                                    this=this, expressions=[bracket_expr], json_access=True
 7049                                ),
 7050                            )
 7051
 7052                elif self._match(TokenType.DCOLON):
 7053                    this, path_parts = self._build_json_extract(this, path_parts)
 7054
 7055                    cast_type = self._parse_types()
 7056                    if cast_type:
 7057                        this = self.expression(exp.Cast(this=this, to=cast_type))
 7058                    else:
 7059                        self.raise_error("Expected type after '::'")
 7060                else:
 7061                    break
 7062
 7063        this, _ = self._build_json_extract(this, path_parts)
 7064
 7065        return this
 7066
 7067    def _parse_dcolon(self) -> exp.Expr | None:
 7068        return self._parse_types()
 7069
 7070    def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None:
 7071        while self._curr.token_type in self.BRACKETS:
 7072            this = self._parse_bracket(this)
 7073
 7074        column_operators = self.COLUMN_OPERATORS
 7075        cast_column_operators = self.CAST_COLUMN_OPERATORS
 7076        while self._curr:
 7077            op_token = self._curr.token_type
 7078
 7079            if op_token not in column_operators:
 7080                break
 7081            op = column_operators[op_token]
 7082            self._advance()
 7083
 7084            if op_token in cast_column_operators:
 7085                field = self._parse_dcolon()
 7086                if not field:
 7087                    self.raise_error("Expected type")
 7088            elif op and self._curr:
 7089                field = self._parse_column_reference() or self._parse_bitwise()
 7090                if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False):
 7091                    field = self._parse_column_ops(field)
 7092            else:
 7093                dot = self._is_connected() and self._prev.token_type == TokenType.DOT
 7094                field = self._parse_field(any_token=True, anonymous_func=True)
 7095
 7096                # In t.true, t.null we should produce an Identifier node
 7097                if dot and isinstance(field, (exp.Null, exp.Boolean)):
 7098                    field = self.expression(
 7099                        exp.Identifier(this=self._prev.text),
 7100                        comments=field.comments,
 7101                    )
 7102
 7103            # Function calls can be qualified, e.g., x.y.FOO()
 7104            # This converts the final AST to a series of Dots leading to the function call
 7105            # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules
 7106            if isinstance(field, (exp.Func, exp.Window)) and this:
 7107                this = this.transform(
 7108                    lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n
 7109                )
 7110
 7111            if op:
 7112                this = op(self, this, field)
 7113            elif isinstance(this, exp.Column) and not this.args.get("catalog"):
 7114                this = self.expression(
 7115                    exp.Column(
 7116                        this=field,
 7117                        table=this.this,
 7118                        db=this.args.get("table"),
 7119                        catalog=this.args.get("db"),
 7120                    ),
 7121                    comments=this.comments,
 7122                )
 7123            elif isinstance(field, exp.Window):
 7124                # Move the exp.Dot's to the window's function
 7125                window_func = self.expression(exp.Dot(this=this, expression=field.this))
 7126                field.set("this", window_func)
 7127                this = field
 7128            else:
 7129                this = self.expression(exp.Dot(this=this, expression=field))
 7130
 7131            if field and field.comments:
 7132                t.cast(exp.Expr, this).add_comments(field.pop_comments())
 7133
 7134            this = self._parse_bracket(this)
 7135
 7136        return this
 7137
 7138    def _parse_paren(self) -> exp.Expr | None:
 7139        if not self._match(TokenType.L_PAREN):
 7140            return None
 7141
 7142        comments = self._prev_comments
 7143        query = self._parse_select()
 7144
 7145        if query:
 7146            expressions = [query]
 7147        else:
 7148            expressions = self._parse_expressions()
 7149
 7150        this = seq_get(expressions, 0)
 7151
 7152        if not this and self._match(TokenType.R_PAREN, advance=False):
 7153            this = self.expression(exp.Tuple())
 7154        elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA:
 7155            this = self.expression(exp.Tuple(expressions=expressions))
 7156        elif isinstance(this, exp.UNWRAPPED_QUERIES):
 7157            this = self._parse_subquery(this=this, parse_alias=False)
 7158        elif isinstance(this, (exp.Subquery, exp.Values)):
 7159            this = self._parse_subquery(
 7160                this=self._parse_query_modifiers(self._parse_set_operations(this)),
 7161                parse_alias=False,
 7162            )
 7163        else:
 7164            this = self.expression(exp.Paren(this=this))
 7165
 7166        if this:
 7167            this.add_comments(comments)
 7168
 7169        self._match_r_paren(expression=this)
 7170
 7171        if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc):
 7172            return self._parse_window(this)
 7173
 7174        return this
 7175
 7176    def _parse_primary(self) -> exp.Expr | None:
 7177        if self._match_set(self.PRIMARY_PARSERS):
 7178            token_type = self._prev.token_type
 7179            primary = self.PRIMARY_PARSERS[token_type](self, self._prev)
 7180
 7181            if token_type == TokenType.STRING:
 7182                expressions = [primary]
 7183                while self._match(TokenType.STRING, advance=False):
 7184                    if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED:
 7185                        self.raise_error(
 7186                            "Adjacent string literals need to be separated by whitespace or comments"
 7187                        )
 7188
 7189                    self._advance()
 7190                    expressions.append(exp.Literal.string(self._prev.text))
 7191
 7192                if len(expressions) > 1:
 7193                    return self.expression(
 7194                        exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE)
 7195                    )
 7196
 7197            return primary
 7198
 7199        if self._match_pair(TokenType.DOT, TokenType.NUMBER):
 7200            return exp.Literal.number(f"0.{self._prev.text}")
 7201
 7202        return self._parse_paren()
 7203
 7204    def _parse_field(
 7205        self,
 7206        any_token: bool = False,
 7207        tokens: t.Collection[TokenType] | None = None,
 7208        anonymous_func: bool = False,
 7209    ) -> exp.Expr | None:
 7210        after_dot = (
 7211            self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT
 7212        )
 7213
 7214        if anonymous_func:
 7215            field = (
 7216                self._parse_function(anonymous=anonymous_func, any_token=any_token)
 7217                or self._parse_primary()
 7218            )
 7219        else:
 7220            field = self._parse_primary() or self._parse_function(
 7221                anonymous=anonymous_func, any_token=any_token
 7222            )
 7223
 7224        field = field or self._parse_id_var(any_token=any_token, tokens=tokens)
 7225
 7226        if after_dot and isinstance(field, exp.Literal) and field.is_number:
 7227            name = field.name
 7228            if self._is_connected() and self._parse_var(any_token=True):
 7229                name += self._prev.text
 7230
 7231            field = exp.Identifier(this=name, quoted=True).update_positions(field)
 7232
 7233        return field
 7234
 7235    def _parse_function(
 7236        self,
 7237        functions: dict[str, t.Callable] | None = None,
 7238        anonymous: bool = False,
 7239        optional_parens: bool = True,
 7240        any_token: bool = False,
 7241    ) -> exp.Expr | None:
 7242        # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this)
 7243        # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences
 7244        fn_syntax = False
 7245        if (
 7246            self._match(TokenType.L_BRACE, advance=False)
 7247            and self._next
 7248            and self._next.text.upper() == "FN"
 7249        ):
 7250            self._advance(2)
 7251            fn_syntax = True
 7252
 7253        func = self._parse_function_call(
 7254            functions=functions,
 7255            anonymous=anonymous,
 7256            optional_parens=optional_parens,
 7257            any_token=any_token,
 7258        )
 7259
 7260        if fn_syntax:
 7261            self._match(TokenType.R_BRACE)
 7262
 7263        return func
 7264
 7265    def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]:
 7266        return self._parse_csv(lambda: self._parse_lambda(alias=alias))
 7267
 7268    def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren:
 7269        args = self._parse_function_args(alias=False)
 7270        if not args:
 7271            self.raise_error("Expected at least one argument")
 7272
 7273        # Wrapped so the connector keeps its precedence in the parent context
 7274        return exp.Paren(this=connector(*args, copy=False))
 7275
 7276    def _parse_function_call(
 7277        self,
 7278        functions: dict[str, t.Callable] | None = None,
 7279        anonymous: bool = False,
 7280        optional_parens: bool = True,
 7281        any_token: bool = False,
 7282    ) -> exp.Expr | None:
 7283        if not self._curr:
 7284            return None
 7285
 7286        comments = self._curr.comments
 7287        prev = self._prev
 7288        token = self._curr
 7289        token_type = self._curr.token_type
 7290        this: str | exp.Expr = self._curr.text
 7291        upper = self._curr.text.upper()
 7292
 7293        after_dot = prev.token_type == TokenType.DOT
 7294        parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper)
 7295        if (
 7296            optional_parens
 7297            and parser
 7298            and token_type not in self.INVALID_FUNC_NAME_TOKENS
 7299            and not after_dot
 7300        ):
 7301            self._advance()
 7302            return self._parse_window(parser(self))
 7303
 7304        if self._next.token_type != TokenType.L_PAREN:
 7305            if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot:
 7306                self._advance()
 7307                return self.expression(self.NO_PAREN_FUNCTIONS[token_type]())
 7308
 7309            return None
 7310
 7311        if any_token:
 7312            if token_type in self.RESERVED_TOKENS:
 7313                return None
 7314        elif token_type not in self.FUNC_TOKENS:
 7315            return None
 7316
 7317        self._advance(2)
 7318
 7319        parser = self.FUNCTION_PARSERS.get(upper)
 7320        if parser and not anonymous:
 7321            result = parser(self)
 7322        else:
 7323            subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type)
 7324
 7325            if subquery_predicate:
 7326                expr = None
 7327                if self._curr.token_type in self.SUBQUERY_TOKENS:
 7328                    expr = self._parse_select()
 7329                    self._match_r_paren()
 7330                elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE):
 7331                    # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like
 7332                    # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren
 7333                    self._advance(-1)
 7334                    expr = self._parse_bitwise()
 7335
 7336                if expr:
 7337                    return self.expression(subquery_predicate(this=expr), comments=comments)
 7338
 7339            if functions is None:
 7340                functions = self.FUNCTIONS
 7341
 7342            function = functions.get(upper)
 7343            known_function = function and not anonymous
 7344
 7345            alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS
 7346            args = self._parse_function_args(alias)
 7347
 7348            post_func_comments = self._curr.comments if self._curr else None
 7349            if known_function and post_func_comments:
 7350                # If the user-inputted comment "/* sqlglot.anonymous */" is following the function
 7351                # call we'll construct it as exp.Anonymous, even if it's "known"
 7352                if any(
 7353                    comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS)
 7354                    for comment in post_func_comments
 7355                ):
 7356                    known_function = False
 7357
 7358            if alias and known_function:
 7359                args = self._kv_to_prop_eq(args)
 7360
 7361            if known_function:
 7362                func_builder = t.cast(t.Callable, function)
 7363
 7364                # mypyc compiled functions don't have __code__, so we use
 7365                # try/except to check if func_builder accepts 'dialect'.
 7366                try:
 7367                    func = func_builder(args)
 7368                except TypeError:
 7369                    func = func_builder(args, dialect=self.dialect)
 7370
 7371                func = self.validate_expression(func, args)
 7372                if self.dialect.PRESERVE_ORIGINAL_NAMES:
 7373                    func.meta["name"] = this
 7374
 7375                result = func
 7376            else:
 7377                if token_type == TokenType.IDENTIFIER:
 7378                    this = exp.Identifier(this=this, quoted=True).update_positions(token)
 7379
 7380                result = self.expression(exp.Anonymous(this=this, expressions=args))
 7381
 7382            result = result.update_positions(token)
 7383
 7384        if isinstance(result, exp.Expr):
 7385            result.add_comments(comments)
 7386
 7387        if parser:
 7388            self._match(TokenType.R_PAREN, expression=result)
 7389        else:
 7390            self._match_r_paren(result)
 7391        return self._parse_window(result)
 7392
 7393    def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr:
 7394        return expression
 7395
 7396    def _kv_to_prop_eq(
 7397        self, expressions: list[exp.Expr], parse_map: bool = False
 7398    ) -> list[exp.Expr]:
 7399        transformed = []
 7400
 7401        for index, e in enumerate(expressions):
 7402            if isinstance(e, self.KEY_VALUE_DEFINITIONS):
 7403                if isinstance(e, exp.Alias):
 7404                    e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this))
 7405
 7406                if not isinstance(e, exp.PropertyEQ):
 7407                    e = self.expression(
 7408                        exp.PropertyEQ(
 7409                            this=e.this if parse_map else exp.to_identifier(e.this.name),
 7410                            expression=e.expression,
 7411                        )
 7412                    )
 7413
 7414                if isinstance(e.this, exp.Column):
 7415                    e.this.replace(e.this.this)
 7416            else:
 7417                e = self._to_prop_eq(e, index)
 7418
 7419            transformed.append(e)
 7420
 7421        return transformed
 7422
 7423    def _parse_function_properties(self) -> exp.Properties | None:
 7424        # Skip the generic `key = value` fallback in _parse_property since this
 7425        # runs post-AS where a function body like `name = expr` can be misread
 7426        # as a property.
 7427        properties = []
 7428        while True:
 7429            if self._match_texts(self.PROPERTY_PARSERS):
 7430                keyword = self._prev.text.upper()
 7431                prop = self.PROPERTY_PARSERS[keyword](self)
 7432            elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS):
 7433                keyword = self._prev.text.upper()
 7434                prop = self.PROPERTY_PARSERS[keyword](self, default=True)
 7435            else:
 7436                break
 7437            if not prop:
 7438                self.raise_error(f"Failed to parse property '{keyword}'")
 7439                break
 7440            for p in ensure_list(prop):
 7441                properties.append(p)
 7442
 7443        return self.expression(exp.Properties(expressions=properties)) if properties else None
 7444
 7445    def _parse_user_defined_function_expression(self) -> exp.Expr | None:
 7446        return self._parse_statement()
 7447
 7448    def _parse_function_parameter(self) -> exp.Expr | None:
 7449        return self._parse_column_def(this=self._parse_id_var(), computed_column=False)
 7450
 7451    def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None:
 7452        this = self._parse_table_parts(schema=True)
 7453
 7454        if not self._match(TokenType.L_PAREN):
 7455            return this
 7456
 7457        expressions = self._parse_csv(self._parse_function_parameter)
 7458        self._match_r_paren()
 7459        return self.expression(
 7460            exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True)
 7461        )
 7462
 7463    def _parse_macro_overloads(
 7464        self,
 7465        this: exp.UserDefinedFunction,
 7466        first_body: exp.Expr,
 7467        first_is_table: bool = False,
 7468    ) -> exp.MacroOverloads:
 7469        overloads = [
 7470            self.expression(
 7471                exp.MacroOverload(
 7472                    this=first_body,
 7473                    expressions=this.expressions or None,
 7474                    is_table=first_is_table,
 7475                )
 7476            )
 7477        ]
 7478        this.set("expressions", None)
 7479        this.set("wrapped", False)
 7480
 7481        while self._match(TokenType.COMMA):
 7482            if not self._match(TokenType.L_PAREN):
 7483                break
 7484
 7485            params = self._parse_csv(self._parse_function_parameter)
 7486            self._match_r_paren()
 7487
 7488            if not self._match(TokenType.ALIAS):
 7489                break
 7490
 7491            is_table = self._match(TokenType.TABLE)
 7492            body = self._parse_expression()
 7493            macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table)
 7494            overloads.append(self.expression(macro))
 7495
 7496        return self.expression(exp.MacroOverloads(expressions=overloads))
 7497
 7498    def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier:
 7499        literal = self._parse_primary()
 7500        if literal:
 7501            return self.expression(exp.Introducer(this=token.text, expression=literal), token)
 7502
 7503        return self._identifier_expression(token)
 7504
 7505    def _parse_session_parameter(self) -> exp.SessionParameter:
 7506        kind = None
 7507        this = self._parse_id_var() or self._parse_primary()
 7508
 7509        if this and self._match(TokenType.DOT):
 7510            kind = this.name
 7511            this = self._parse_var() or self._parse_primary()
 7512
 7513        return self.expression(exp.SessionParameter(this=this, kind=kind))
 7514
 7515    def _parse_lambda_arg(self) -> exp.Expr | None:
 7516        return self._parse_id_var()
 7517
 7518    def _parse_lambda(self, alias: bool = False) -> exp.Expr | None:
 7519        next_token_type = self._next.token_type
 7520
 7521        # Fast path: simple atom (column, literal, null, bool) followed by , or )
 7522        if (
 7523            next_token_type in self.LAMBDA_ARG_TERMINATORS
 7524            and (atom := self._parse_atom()) is not None
 7525        ):
 7526            return atom
 7527
 7528        index = self._index
 7529
 7530        if self._match(TokenType.L_PAREN):
 7531            expressions = t.cast(
 7532                list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg)
 7533            )
 7534
 7535            if not self._match(TokenType.R_PAREN):
 7536                self._retreat(index)
 7537            elif self._match_set(self.LAMBDAS):
 7538                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7539            else:
 7540                self._retreat(index)
 7541        elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS:
 7542            expressions = [self._parse_lambda_arg()]
 7543
 7544            if self._match_set(self.LAMBDAS):
 7545                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7546
 7547            self._retreat(index)
 7548
 7549        this: exp.Expr | None
 7550
 7551        if self._match(TokenType.DISTINCT):
 7552            this = self.expression(
 7553                exp.Distinct(expressions=self._parse_csv(self._parse_disjunction))
 7554            )
 7555        else:
 7556            self._match(TokenType.ALL)  # ALL is the default/no-op aggregate modifier (SQL-92)
 7557            this = self._parse_select_or_expression(alias=alias)
 7558
 7559        return self._parse_limit(
 7560            self._parse_respect_or_ignore_nulls(
 7561                self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this)))
 7562            )
 7563        )
 7564
 7565    def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7566        index = self._index
 7567        if not self._match(TokenType.L_PAREN):
 7568            return this
 7569
 7570        # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>),
 7571        # expr can be of both types
 7572        if self._match_set(self.SELECT_START_TOKENS):
 7573            self._retreat(index)
 7574            return this
 7575        args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def())
 7576        self._match_r_paren()
 7577        return self.expression(exp.Schema(this=this, expressions=args))
 7578
 7579    def _parse_field_def(self) -> exp.Expr | None:
 7580        return self._parse_column_def(self._parse_field(any_token=True))
 7581
 7582    def _parse_column_def(
 7583        self, this: exp.Expr | None, computed_column: bool = True
 7584    ) -> exp.Expr | None:
 7585        # column defs are not really columns, they're identifiers
 7586        if isinstance(this, exp.Column):
 7587            this = this.this
 7588
 7589        if not computed_column:
 7590            self._match(TokenType.ALIAS)
 7591
 7592        kind = self._parse_types(schema=True)
 7593
 7594        if self._match_text_seq("FOR", "ORDINALITY"):
 7595            return self.expression(exp.ColumnDef(this=this, ordinality=True))
 7596
 7597        constraints: list[exp.Expr] = []
 7598
 7599        if (not kind and self._match(TokenType.ALIAS)) or self._match_texts(
 7600            ("ALIAS", "MATERIALIZED")
 7601        ):
 7602            # Match storage before _parse_types so STORED is not treated as a data type
 7603            # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`).
 7604            persisted = self._prev.text.upper() == "MATERIALIZED"
 7605            expression = self._parse_disjunction()
 7606            if not persisted:
 7607                if self._match_text_seq("PERSISTED"):
 7608                    persisted = True
 7609                elif self._match_texts(("STORED", "VIRTUAL")):
 7610                    persisted = self._prev.text.upper() == "STORED"
 7611            constraint_kind = exp.ComputedColumnConstraint(
 7612                this=expression,
 7613                persisted=persisted,
 7614                data_type=exp.Var(this="AUTO")
 7615                if self._match_text_seq("AUTO")
 7616                else self._parse_types(),
 7617                not_null=self._match_pair(TokenType.NOT, TokenType.NULL),
 7618            )
 7619            constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind)))
 7620        elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False):
 7621            in_out_constraint = self.expression(
 7622                exp.InOutColumnConstraint(
 7623                    input_=self._match(TokenType.IN), output=self._match(TokenType.OUT)
 7624                )
 7625            )
 7626            constraints.append(in_out_constraint)
 7627            kind = self._parse_types()
 7628        elif (
 7629            kind
 7630            and self._match(TokenType.ALIAS, advance=False)
 7631            and (
 7632                not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
 7633                or self._next.token_type == TokenType.L_PAREN
 7634            )
 7635        ):
 7636            self._advance()
 7637            constraints.append(
 7638                self.expression(
 7639                    exp.ColumnConstraint(
 7640                        kind=exp.ComputedColumnConstraint(
 7641                            this=self._parse_disjunction(),
 7642                            persisted=self._match_texts(("STORED", "VIRTUAL"))
 7643                            and self._prev.text.upper() == "STORED",
 7644                        )
 7645                    )
 7646                )
 7647            )
 7648
 7649        while True:
 7650            constraint = self._parse_column_constraint()
 7651            if not constraint:
 7652                break
 7653            constraints.append(constraint)
 7654
 7655        if not kind and not constraints:
 7656            return this
 7657
 7658        position = None
 7659        if self._match_texts(("FIRST", "AFTER")):
 7660            pos = self._prev.text
 7661            position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos))
 7662
 7663        return self.expression(
 7664            exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position)
 7665        )
 7666
 7667    def _parse_auto_increment(
 7668        self,
 7669    ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint:
 7670        start = None
 7671        increment = None
 7672        order = None
 7673
 7674        if self._match(TokenType.L_PAREN, advance=False):
 7675            args = self._parse_wrapped_csv(self._parse_bitwise)
 7676            start = seq_get(args, 0)
 7677            increment = seq_get(args, 1)
 7678
 7679        # The remaining parts form an unordered bag and any of them can be omitted, in which
 7680        # case the engine falls back to its own default, so they're parsed independently.
 7681        while True:
 7682            if self._match_text_seq("START"):
 7683                start = self._parse_bitwise()
 7684            elif self._match_text_seq("INCREMENT"):
 7685                increment = self._parse_bitwise()
 7686            elif self._match_text_seq("ORDER"):
 7687                order = True
 7688            elif self._match_text_seq("NOORDER"):
 7689                order = False
 7690            else:
 7691                break
 7692
 7693        if start or increment or order is not None:
 7694            return exp.GeneratedAsIdentityColumnConstraint(
 7695                start=start, increment=increment, this=False, order=order
 7696            )
 7697
 7698        return exp.AutoIncrementColumnConstraint()
 7699
 7700    def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None:
 7701        if not self._match(TokenType.L_PAREN, advance=False):
 7702            return None
 7703
 7704        return self.expression(
 7705            exp.CheckColumnConstraint(
 7706                this=self._parse_wrapped(self._parse_assignment),
 7707                enforced=self._match_text_seq("ENFORCED"),
 7708            )
 7709        )
 7710
 7711    def _parse_auto_property(self) -> exp.AutoRefreshProperty | None:
 7712        if not self._match_text_seq("REFRESH"):
 7713            self._retreat(self._index - 1)
 7714            return None
 7715        return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True)))
 7716
 7717    def _parse_compress(self) -> exp.CompressColumnConstraint:
 7718        if self._match(TokenType.L_PAREN, advance=False):
 7719            return self.expression(
 7720                exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise))
 7721            )
 7722
 7723        return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise()))
 7724
 7725    def _parse_generated_as_identity(
 7726        self,
 7727    ) -> (
 7728        exp.GeneratedAsIdentityColumnConstraint
 7729        | exp.ComputedColumnConstraint
 7730        | exp.GeneratedAsRowColumnConstraint
 7731    ):
 7732        if self._match_text_seq("BY", "DEFAULT"):
 7733            on_null = self._match_pair(TokenType.ON, TokenType.NULL)
 7734            this = self.expression(
 7735                exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null)
 7736            )
 7737        else:
 7738            self._match_text_seq("ALWAYS")
 7739            this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True))
 7740
 7741        self._match(TokenType.ALIAS)
 7742
 7743        if self._match_text_seq("ROW"):
 7744            start = self._match_text_seq("START")
 7745            if not start:
 7746                self._match(TokenType.END)
 7747            hidden = self._match_text_seq("HIDDEN")
 7748            return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden))
 7749
 7750        identity = self._match_text_seq("IDENTITY")
 7751
 7752        if self._match(TokenType.L_PAREN):
 7753            if self._match_text_seq("START", "WITH"):
 7754                this.set("start", self._parse_bitwise())
 7755            if self._match_text_seq("INCREMENT", "BY"):
 7756                this.set("increment", self._parse_bitwise())
 7757            if self._match_text_seq("MINVALUE"):
 7758                this.set("minvalue", self._parse_bitwise())
 7759            if self._match_text_seq("MAXVALUE"):
 7760                this.set("maxvalue", self._parse_bitwise())
 7761
 7762            if self._match_text_seq("CYCLE"):
 7763                this.set("cycle", True)
 7764            elif self._match_text_seq("NO", "CYCLE"):
 7765                this.set("cycle", False)
 7766
 7767            if not identity:
 7768                this.set("expression", self._parse_range())
 7769            elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False):
 7770                args = self._parse_csv(self._parse_bitwise)
 7771                this.set("start", seq_get(args, 0))
 7772                this.set("increment", seq_get(args, 1))
 7773
 7774            self._match_r_paren()
 7775
 7776        return this
 7777
 7778    def _parse_inline(self) -> exp.InlineLengthColumnConstraint:
 7779        self._match_text_seq("LENGTH")
 7780        return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise()))
 7781
 7782    def _parse_not_constraint(self) -> exp.Expr | None:
 7783        if self._match_text_seq("NULL"):
 7784            return self.expression(exp.NotNullColumnConstraint())
 7785        if self._match_text_seq("CASESPECIFIC"):
 7786            return self.expression(exp.CaseSpecificColumnConstraint(not_=True))
 7787        if self._match_text_seq("FOR", "REPLICATION"):
 7788            return self.expression(exp.NotForReplicationColumnConstraint())
 7789
 7790        # Unconsume the `NOT` token
 7791        self._retreat(self._index - 1)
 7792        return None
 7793
 7794    def _parse_column_constraint(self) -> exp.Expr | None:
 7795        this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None
 7796
 7797        procedure_option_follows = (
 7798            self._match(TokenType.WITH, advance=False)
 7799            and self._next
 7800            and self._next.text.upper() in self.PROCEDURE_OPTIONS
 7801        )
 7802
 7803        index = self._index
 7804        if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS):
 7805            constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self)
 7806            if not constraint:
 7807                self._retreat(index)
 7808                return None
 7809
 7810            return self.expression(exp.ColumnConstraint(this=this, kind=constraint))
 7811
 7812        if self._match_text_seq("CHARACTER", "SET"):
 7813            return self.expression(
 7814                exp.ColumnConstraint(
 7815                    this=this,
 7816                    kind=self.expression(
 7817                        exp.CharacterSetColumnConstraint(this=self._parse_var_or_string())
 7818                    ),
 7819                )
 7820            )
 7821
 7822        return this
 7823
 7824    def _parse_constraint(self) -> exp.Expr | None:
 7825        if not self._match(TokenType.CONSTRAINT):
 7826            return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS)
 7827
 7828        return self.expression(
 7829            exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints())
 7830        )
 7831
 7832    def _parse_unnamed_constraints(self) -> list[exp.Expr]:
 7833        constraints = []
 7834        while True:
 7835            constraint = self._parse_unnamed_constraint() or self._parse_function()
 7836            if not constraint:
 7837                break
 7838            constraints.append(constraint)
 7839
 7840        return constraints
 7841
 7842    def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None:
 7843        index = self._index
 7844
 7845        if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts(
 7846            constraints or self.CONSTRAINT_PARSERS
 7847        ):
 7848            return None
 7849
 7850        constraint_key = self._prev.text.upper()
 7851        if constraint_key not in self.CONSTRAINT_PARSERS:
 7852            self.raise_error(f"No parser found for schema constraint {constraint_key}.")
 7853
 7854        result = self.CONSTRAINT_PARSERS[constraint_key](self)
 7855        if not result:
 7856            self._retreat(index)
 7857
 7858        return result
 7859
 7860    def _parse_unique_key(self) -> exp.Expr | None:
 7861        if (
 7862            self._curr
 7863            and self._curr.token_type != TokenType.IDENTIFIER
 7864            and self._curr.text.upper() in self.CONSTRAINT_PARSERS
 7865        ):
 7866            return None
 7867        return self._parse_id_var(any_token=False)
 7868
 7869    def _parse_unique(self) -> exp.UniqueColumnConstraint:
 7870        self._match_texts(("KEY", "INDEX"))
 7871        return self.expression(
 7872            exp.UniqueColumnConstraint(
 7873                nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"),
 7874                this=self._parse_schema(self._parse_unique_key()),
 7875                index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text,
 7876                on_conflict=self._parse_on_conflict(),
 7877                options=self._parse_key_constraint_options(),
 7878            )
 7879        )
 7880
 7881    def _parse_key_constraint_options(self) -> list[str]:
 7882        options = []
 7883        while True:
 7884            if not self._curr:
 7885                break
 7886
 7887            if self._match(TokenType.ON):
 7888                action = None
 7889                on = self._advance_any() and self._prev.text
 7890
 7891                if self._match_text_seq("NO", "ACTION"):
 7892                    action = "NO ACTION"
 7893                elif self._match_text_seq("CASCADE"):
 7894                    action = "CASCADE"
 7895                elif self._match_text_seq("RESTRICT"):
 7896                    action = "RESTRICT"
 7897                elif self._match_pair(TokenType.SET, TokenType.NULL):
 7898                    action = "SET NULL"
 7899                elif self._match_pair(TokenType.SET, TokenType.DEFAULT):
 7900                    action = "SET DEFAULT"
 7901                else:
 7902                    self.raise_error("Invalid key constraint")
 7903
 7904                options.append(f"ON {on} {action}")
 7905            else:
 7906                var = self._parse_var_from_options(
 7907                    self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False
 7908                )
 7909                if not var:
 7910                    break
 7911                options.append(var.name)
 7912
 7913        return options
 7914
 7915    def _parse_references(self, match: bool = True) -> exp.Reference | None:
 7916        if match and not self._match(TokenType.REFERENCES):
 7917            return None
 7918
 7919        expressions: list | None = None
 7920        this = self._parse_table(schema=True)
 7921        options = self._parse_key_constraint_options()
 7922        return self.expression(exp.Reference(this=this, expressions=expressions, options=options))
 7923
 7924    def _parse_foreign_key(self) -> exp.ForeignKey:
 7925        expressions = (
 7926            self._parse_wrapped_id_vars()
 7927            if not self._match(TokenType.REFERENCES, advance=False)
 7928            else None
 7929        )
 7930        reference = self._parse_references()
 7931        on_options = {}
 7932
 7933        while self._match(TokenType.ON):
 7934            if not self._match_set((TokenType.DELETE, TokenType.UPDATE)):
 7935                self.raise_error("Expected DELETE or UPDATE")
 7936
 7937            kind = self._prev.text.lower()
 7938
 7939            if self._match_text_seq("NO", "ACTION"):
 7940                action = "NO ACTION"
 7941            elif self._match(TokenType.SET):
 7942                self._match_set((TokenType.NULL, TokenType.DEFAULT))
 7943                action = "SET " + self._prev.text.upper()
 7944            else:
 7945                self._advance()
 7946                action = self._prev.text.upper()
 7947
 7948            on_options[kind] = action
 7949
 7950        return self.expression(
 7951            exp.ForeignKey(
 7952                expressions=expressions,
 7953                reference=reference,
 7954                options=self._parse_key_constraint_options(),
 7955                **on_options,
 7956            )
 7957        )
 7958
 7959    def _parse_primary_key_part(self) -> exp.Expr | None:
 7960        return self._parse_field()
 7961
 7962    def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None:
 7963        if not self._match_text_seq("FOR", "SYSTEM_TIME"):
 7964            self._retreat(self._index - 1)
 7965            return None
 7966
 7967        id_vars = self._parse_wrapped_id_vars()
 7968        return self.expression(
 7969            exp.PeriodForSystemTimeConstraint(
 7970                this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1)
 7971            )
 7972        )
 7973
 7974    def _parse_primary_key(
 7975        self,
 7976        wrapped_optional: bool = False,
 7977        in_props: bool = False,
 7978        named_primary_key: bool = False,
 7979    ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:
 7980        desc = (
 7981            self._prev.token_type == TokenType.DESC
 7982            if self._match_set((TokenType.ASC, TokenType.DESC))
 7983            else None
 7984        )
 7985
 7986        this = None
 7987        if (
 7988            named_primary_key
 7989            and self._curr.text.upper() not in self.CONSTRAINT_PARSERS
 7990            and self._next
 7991            and self._next.token_type == TokenType.L_PAREN
 7992        ):
 7993            this = self._parse_id_var()
 7994
 7995        if not in_props and not self._match(TokenType.L_PAREN, advance=False):
 7996            return self.expression(
 7997                exp.PrimaryKeyColumnConstraint(
 7998                    desc=desc, options=self._parse_key_constraint_options()
 7999                )
 8000            )
 8001
 8002        expressions = self._parse_wrapped_csv(
 8003            self._parse_primary_key_part, optional=wrapped_optional
 8004        )
 8005
 8006        return self.expression(
 8007            exp.PrimaryKey(
 8008                this=this,
 8009                expressions=expressions,
 8010                include=self._parse_index_params(),
 8011                options=self._parse_key_constraint_options(),
 8012            )
 8013        )
 8014
 8015    def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None:
 8016        return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True))
 8017
 8018    def _parse_odbc_datetime_literal(self) -> exp.Expr:
 8019        """
 8020        Parses a datetime column in ODBC format. We parse the column into the corresponding
 8021        types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the
 8022        same as we did for `DATE('yyyy-mm-dd')`.
 8023
 8024        Reference:
 8025        https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals
 8026        """
 8027        self._match(TokenType.VAR)
 8028        exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()]
 8029        expression = self.expression(exp_class(this=self._parse_string()))
 8030        if not self._match(TokenType.R_BRACE):
 8031            self.raise_error("Expected }")
 8032        return expression
 8033
 8034    def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None:
 8035        if not self._match_set(self.BRACKETS):
 8036            return this
 8037
 8038        if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS:
 8039            map_token = seq_get(self._tokens, self._index - 2)
 8040            parse_map = map_token is not None and map_token.text.upper() == "MAP"
 8041        else:
 8042            parse_map = False
 8043
 8044        bracket_kind = self._prev.token_type
 8045        if (
 8046            bracket_kind == TokenType.L_BRACE
 8047            and self._curr
 8048            and self._curr.token_type == TokenType.VAR
 8049            and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS
 8050        ):
 8051            return self._parse_odbc_datetime_literal()
 8052
 8053        expressions = self._parse_csv(
 8054            lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE)
 8055        )
 8056
 8057        if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET):
 8058            self.raise_error("Expected ]")
 8059        elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE):
 8060            self.raise_error("Expected }")
 8061
 8062        # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs
 8063        if bracket_kind == TokenType.L_BRACE:
 8064            this = self.expression(
 8065                exp.Struct(
 8066                    expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map)
 8067                )
 8068            )
 8069        elif not this:
 8070            this = build_array_constructor(
 8071                exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect
 8072            )
 8073        else:
 8074            constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper())
 8075            if constructor_type:
 8076                return build_array_constructor(
 8077                    constructor_type,
 8078                    args=expressions,
 8079                    bracket_kind=bracket_kind,
 8080                    dialect=self.dialect,
 8081                )
 8082
 8083            expressions = apply_index_offset(
 8084                this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect
 8085            )
 8086            this = self.expression(
 8087                exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments()
 8088            )
 8089
 8090        self._add_comments(this)
 8091        return self._parse_bracket(this)
 8092
 8093    def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None:
 8094        if not self._match(TokenType.COLON):
 8095            return this
 8096
 8097        if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False):
 8098            self._advance()
 8099            end: exp.Expr | None = -exp.Literal.number("1")
 8100        else:
 8101            end = self._parse_assignment()
 8102        step = self._parse_unary() if self._match(TokenType.COLON) else None
 8103        return self.expression(exp.Slice(this=this, expression=end, step=step))
 8104
 8105    def _parse_case(self) -> exp.Expr | None:
 8106        if self._match(TokenType.DOT, advance=False):
 8107            # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake
 8108            self._retreat(self._index - 1)
 8109            return None
 8110
 8111        ifs = []
 8112        default = None
 8113
 8114        comments = self._prev_comments
 8115        expression = self._parse_disjunction()
 8116
 8117        while self._match(TokenType.WHEN):
 8118            this = self._parse_disjunction()
 8119            self._match(TokenType.THEN)
 8120            then = self._parse_disjunction()
 8121            ifs.append(self.expression(exp.If(this=this, true=then)))
 8122
 8123        if self._match(TokenType.ELSE):
 8124            default = self._parse_disjunction()
 8125
 8126        if not self._match(TokenType.END):
 8127            if isinstance(default, exp.Interval) and default.this.sql().upper() == "END":
 8128                default = exp.column("interval")
 8129            else:
 8130                self.raise_error("Expected END after CASE", self._prev)
 8131
 8132        return self.expression(
 8133            exp.Case(this=expression, ifs=ifs, default=default), comments=comments
 8134        )
 8135
 8136    def _parse_if(self) -> exp.Expr | None:
 8137        if self._match(TokenType.L_PAREN):
 8138            args = self._parse_csv(
 8139                lambda: self._parse_alias(self._parse_assignment(), explicit=True)
 8140            )
 8141            this = self.validate_expression(exp.If.from_arg_list(args), args)
 8142            self._match_r_paren()
 8143        else:
 8144            index = self._index - 1
 8145
 8146            if self.NO_PAREN_IF_COMMANDS and index == 0:
 8147                return self._parse_as_command(self._prev)
 8148
 8149            condition = self._parse_disjunction()
 8150
 8151            if not condition:
 8152                self._retreat(index)
 8153                return None
 8154
 8155            self._match(TokenType.THEN)
 8156            true = self._parse_disjunction()
 8157            false = self._parse_disjunction() if self._match(TokenType.ELSE) else None
 8158            self._match(TokenType.END)
 8159            this = self.expression(exp.If(this=condition, true=true, false=false))
 8160
 8161        return this
 8162
 8163    def _parse_next_value_for(self) -> exp.Expr | None:
 8164        if not self._match_text_seq("VALUE", "FOR"):
 8165            self._retreat(self._index - 1)
 8166            return None
 8167
 8168        return self.expression(
 8169            exp.NextValueFor(
 8170                this=self._parse_column(),
 8171                order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order),
 8172            )
 8173        )
 8174
 8175    def _parse_extract(self) -> exp.Extract | exp.Anonymous:
 8176        this = self._parse_function() or self._parse_var_or_string(upper=True)
 8177
 8178        if self._match(TokenType.FROM):
 8179            return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8180
 8181        if not self._match(TokenType.COMMA):
 8182            self.raise_error("Expected FROM or comma after EXTRACT", self._prev)
 8183
 8184        return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8185
 8186    def _parse_gap_fill(self) -> exp.GapFill:
 8187        self._match(TokenType.TABLE)
 8188        this = self._parse_table()
 8189
 8190        self._match(TokenType.COMMA)
 8191        args = [this, *self._parse_csv(self._parse_lambda)]
 8192
 8193        gap_fill = exp.GapFill.from_arg_list(args)
 8194        return self.validate_expression(gap_fill, args)
 8195
 8196    def _parse_char(self) -> exp.Chr:
 8197        return self.expression(
 8198            exp.Chr(
 8199                expressions=self._parse_csv(self._parse_assignment),
 8200                charset=self._match(TokenType.USING) and self._parse_charset_name(),
 8201            )
 8202        )
 8203
 8204    def _parse_charset_name(self) -> exp.Expr | None:
 8205        """
 8206        Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting
 8207        for specific name shapes override this.
 8208        """
 8209        return self._parse_var(
 8210            tokens={TokenType.BINARY, TokenType.IDENTIFIER},
 8211        )
 8212
 8213    def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr:
 8214        this = self._parse_assignment()
 8215
 8216        if not self._match(TokenType.ALIAS):
 8217            if self._match(TokenType.COMMA):
 8218                return self.expression(exp.CastToStrType(this=this, to=self._parse_string()))
 8219
 8220            self.raise_error("Expected AS after CAST")
 8221
 8222        fmt = None
 8223        to = self._parse_types(with_collation=True)
 8224
 8225        default = None
 8226        if self._match(TokenType.DEFAULT):
 8227            default = self._parse_bitwise()
 8228            self._match_text_seq("ON", "CONVERSION", "ERROR")
 8229
 8230        if self._match_set((TokenType.FORMAT, TokenType.COMMA)):
 8231            fmt_string = self._parse_wrapped(self._parse_string, optional=True)
 8232            fmt = self._parse_at_time_zone(fmt_string)
 8233
 8234            if not to:
 8235                to = exp.DType.UNKNOWN.into_expr()
 8236            if to.this in exp.DataType.TEMPORAL_TYPES:
 8237                this = self.expression(
 8238                    (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)(
 8239                        this=this,
 8240                        format=exp.Literal.string(
 8241                            format_time(
 8242                                fmt_string.this if fmt_string else "",
 8243                                self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING,
 8244                                self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE,
 8245                            )
 8246                        ),
 8247                        safe=safe,
 8248                    )
 8249                )
 8250
 8251                if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime):
 8252                    this.set("zone", fmt.args["zone"])
 8253                return this
 8254        elif not to:
 8255            self.raise_error("Expected TYPE after CAST")
 8256        elif isinstance(to, exp.Identifier):
 8257            to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True)
 8258        elif to.this == exp.DType.CHAR and (
 8259            self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")
 8260        ):
 8261            to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string())
 8262
 8263        return self.build_cast(
 8264            strict=strict,
 8265            this=this,
 8266            to=to,
 8267            format=fmt,
 8268            safe=safe,
 8269            action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False),
 8270            default=default,
 8271        )
 8272
 8273    def _parse_string_agg(self) -> exp.GroupConcat:
 8274        if self._match(TokenType.DISTINCT):
 8275            args: list[exp.Expr | None] = [
 8276                self.expression(exp.Distinct(expressions=[self._parse_disjunction()]))
 8277            ]
 8278            if self._match(TokenType.COMMA):
 8279                args.extend(self._parse_csv(self._parse_disjunction))
 8280        else:
 8281            args = self._parse_csv(self._parse_disjunction)  # type: ignore
 8282
 8283        if self._match_text_seq("ON", "OVERFLOW"):
 8284            # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior])
 8285            if self._match_text_seq("ERROR"):
 8286                on_overflow: exp.Expr | None = exp.var("ERROR")
 8287            else:
 8288                self._match_text_seq("TRUNCATE")
 8289                on_overflow = self.expression(
 8290                    exp.OverflowTruncateBehavior(
 8291                        this=self._parse_string(),
 8292                        with_count=(
 8293                            self._match_text_seq("WITH", "COUNT")
 8294                            or not self._match_text_seq("WITHOUT", "COUNT")
 8295                        ),
 8296                    )
 8297                )
 8298        else:
 8299            on_overflow = None
 8300
 8301        index = self._index
 8302        if not self._match(TokenType.R_PAREN) and args:
 8303            # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]])
 8304            # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n])
 8305            # The order is parsed through `this` as a canonicalization for WITHIN GROUPs
 8306            args[0] = self._parse_limit(this=self._parse_order(this=args[0]))
 8307            return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1)))
 8308
 8309        # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]).
 8310        # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that
 8311        # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them.
 8312        if not self._match_text_seq("WITHIN", "GROUP"):
 8313            self._retreat(index)
 8314            return self.validate_expression(exp.GroupConcat.from_arg_list(args), args)
 8315
 8316        # The corresponding match_r_paren will be called in parse_function (caller)
 8317        self._match_l_paren()
 8318
 8319        return self.expression(
 8320            exp.GroupConcat(
 8321                this=self._parse_order(this=seq_get(args, 0)),
 8322                separator=seq_get(args, 1),
 8323                on_overflow=on_overflow,
 8324            )
 8325        )
 8326
 8327    def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None:
 8328        this = self._parse_bitwise()
 8329
 8330        if self._match(TokenType.USING):
 8331            to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name())
 8332        elif self._match(TokenType.COMMA):
 8333            to = self._parse_types()
 8334        else:
 8335            to = None
 8336
 8337        return self.build_cast(strict=strict, this=this, to=to, safe=safe)
 8338
 8339    def _parse_xml_element(self) -> exp.XMLElement:
 8340        if self._match_text_seq("EVALNAME"):
 8341            evalname = True
 8342            this = self._parse_bitwise()
 8343        else:
 8344            evalname = None
 8345            self._match_text_seq("NAME")
 8346            this = self._parse_id_var()
 8347
 8348        return self.expression(
 8349            exp.XMLElement(
 8350                this=this,
 8351                expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise),
 8352                evalname=evalname,
 8353            )
 8354        )
 8355
 8356    def _parse_xml_table(self) -> exp.XMLTable:
 8357        namespaces = None
 8358        passing = None
 8359        columns = None
 8360
 8361        if self._match_text_seq("XMLNAMESPACES", "("):
 8362            namespaces = self._parse_xml_namespace()
 8363            self._match_text_seq(")", ",")
 8364
 8365        this = self._parse_string()
 8366
 8367        if self._match_text_seq("PASSING"):
 8368            # The BY VALUE keywords are optional and are provided for semantic clarity
 8369            self._match_text_seq("BY", "VALUE")
 8370            passing = self._parse_csv(self._parse_column)
 8371
 8372        by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF")
 8373
 8374        if self._match_text_seq("COLUMNS"):
 8375            columns = self._parse_csv(self._parse_field_def)
 8376
 8377        return self.expression(
 8378            exp.XMLTable(
 8379                this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref
 8380            )
 8381        )
 8382
 8383    def _parse_xml_namespace(self) -> list[exp.XMLNamespace]:
 8384        namespaces = []
 8385
 8386        while True:
 8387            if self._match(TokenType.DEFAULT):
 8388                uri = self._parse_string()
 8389            else:
 8390                uri = self._parse_alias(self._parse_string())
 8391            namespaces.append(self.expression(exp.XMLNamespace(this=uri)))
 8392            if not self._match(TokenType.COMMA):
 8393                break
 8394
 8395        return namespaces
 8396
 8397    def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None:
 8398        args = self._parse_csv(self._parse_disjunction)
 8399
 8400        if len(args) < 3:
 8401            return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1)))
 8402
 8403        return self.expression(exp.DecodeCase(expressions=args))
 8404
 8405    def _parse_json_key_value(self) -> exp.JSONKeyValue | None:
 8406        self._match_text_seq("KEY")
 8407        key = self._parse_column()
 8408        self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS)
 8409        self._match_text_seq("VALUE")
 8410        value = self._parse_bitwise()
 8411
 8412        if not key and not value:
 8413            return None
 8414        return self.expression(exp.JSONKeyValue(this=key, expression=value))
 8415
 8416    def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None:
 8417        if not this or not self._match_text_seq("FORMAT", "JSON"):
 8418            return this
 8419
 8420        return self.expression(exp.FormatJson(this=this))
 8421
 8422    def _parse_on_condition(self) -> exp.OnCondition | None:
 8423        # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS)
 8424        if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR:
 8425            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8426            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8427        else:
 8428            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8429            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8430
 8431        null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS)
 8432
 8433        if not empty and not error and not null:
 8434            return None
 8435
 8436        return self.expression(exp.OnCondition(empty=empty, error=error, null=null))
 8437
 8438    def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None:
 8439        # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL)
 8440        for value in values:
 8441            if self._match_text_seq(value, "ON", on):
 8442                return f"{value} ON {on}"
 8443
 8444        index = self._index
 8445        if self._match(TokenType.DEFAULT):
 8446            default_value = self._parse_bitwise()
 8447            if self._match_text_seq("ON", on):
 8448                return default_value
 8449
 8450            self._retreat(index)
 8451
 8452        return None
 8453
 8454    @t.overload
 8455    def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ...
 8456
 8457    @t.overload
 8458    def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ...
 8459
 8460    def _parse_json_object(self, agg=False):
 8461        star = self._parse_star()
 8462        expressions = (
 8463            [star]
 8464            if star
 8465            else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value()))
 8466        )
 8467        null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT")
 8468
 8469        unique_keys = None
 8470        if self._match_text_seq("WITH", "UNIQUE"):
 8471            unique_keys = True
 8472        elif self._match_text_seq("WITHOUT", "UNIQUE"):
 8473            unique_keys = False
 8474
 8475        self._match_text_seq("KEYS")
 8476
 8477        return_type = self._match_text_seq("RETURNING") and self._parse_format_json(
 8478            self._parse_type()
 8479        )
 8480        encoding = self._match_text_seq("ENCODING") and self._parse_var()
 8481
 8482        return self.expression(
 8483            (exp.JSONObjectAgg if agg else exp.JSONObject)(
 8484                expressions=expressions,
 8485                null_handling=null_handling,
 8486                unique_keys=unique_keys,
 8487                return_type=return_type,
 8488                encoding=encoding,
 8489            )
 8490        )
 8491
 8492    # Note: this is currently incomplete; it only implements the "JSON_value_column" part
 8493    def _parse_json_column_def(self) -> exp.JSONColumnDef:
 8494        if not self._match_text_seq("NESTED"):
 8495            this = self._parse_id_var()
 8496            ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY)
 8497            kind = self._parse_types(allow_identifiers=False)
 8498            nested = None
 8499        else:
 8500            this = None
 8501            ordinality = None
 8502            kind = None
 8503            nested = True
 8504
 8505        format_json = self._match_text_seq("FORMAT", "JSON")
 8506        path = self._match_text_seq("PATH") and self._parse_string()
 8507        nested_schema = nested and self._parse_json_schema()
 8508
 8509        return self.expression(
 8510            exp.JSONColumnDef(
 8511                this=this,
 8512                kind=kind,
 8513                path=path,
 8514                nested_schema=nested_schema,
 8515                ordinality=ordinality,
 8516                format_json=format_json,
 8517            )
 8518        )
 8519
 8520    def _parse_json_schema(self) -> exp.JSONSchema:
 8521        self._match_text_seq("COLUMNS")
 8522        return self.expression(
 8523            exp.JSONSchema(
 8524                expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True)
 8525            )
 8526        )
 8527
 8528    def _parse_json_table(self) -> exp.JSONTable:
 8529        this = self._parse_format_json(self._parse_bitwise())
 8530        path = self._match(TokenType.COMMA) and self._parse_string()
 8531        error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL")
 8532        empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL")
 8533        schema = self._parse_json_schema()
 8534
 8535        return exp.JSONTable(
 8536            this=this,
 8537            schema=schema,
 8538            path=path,
 8539            error_handling=error_handling,
 8540            empty_handling=empty_handling,
 8541        )
 8542
 8543    def _parse_match_against(self) -> exp.MatchAgainst:
 8544        if self._match_text_seq("TABLE"):
 8545            # parse SingleStore MATCH(TABLE ...) syntax
 8546            # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/
 8547            expressions = []
 8548            table = self._parse_table()
 8549            if table:
 8550                expressions = [table]
 8551        else:
 8552            expressions = self._parse_csv(self._parse_column)
 8553
 8554        self._match_text_seq(")", "AGAINST", "(")
 8555
 8556        this = self._parse_string()
 8557
 8558        if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"):
 8559            modifier = "IN NATURAL LANGUAGE MODE"
 8560            if self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8561                modifier = f"{modifier} WITH QUERY EXPANSION"
 8562        elif self._match_text_seq("IN", "BOOLEAN", "MODE"):
 8563            modifier = "IN BOOLEAN MODE"
 8564        elif self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8565            modifier = "WITH QUERY EXPANSION"
 8566        else:
 8567            modifier = None
 8568
 8569        return self.expression(
 8570            exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier)
 8571        )
 8572
 8573    # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16
 8574    def _parse_open_json(self) -> exp.OpenJSON:
 8575        this = self._parse_bitwise()
 8576        path = self._match(TokenType.COMMA) and self._parse_string()
 8577
 8578        def _parse_open_json_column_def() -> exp.OpenJSONColumnDef:
 8579            this = self._parse_field(any_token=True)
 8580            kind = self._parse_types()
 8581            path = self._parse_string()
 8582            as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON)
 8583
 8584            return self.expression(
 8585                exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json)
 8586            )
 8587
 8588        expressions = None
 8589        if self._match_pair(TokenType.R_PAREN, TokenType.WITH):
 8590            self._match_l_paren()
 8591            expressions = self._parse_csv(_parse_open_json_column_def)
 8592
 8593        return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions))
 8594
 8595    def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition:
 8596        args = self._parse_csv(self._parse_bitwise)
 8597
 8598        if self._match(TokenType.IN):
 8599            return self.expression(
 8600                exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0))
 8601            )
 8602
 8603        if haystack_first:
 8604            haystack = seq_get(args, 0)
 8605            needle = seq_get(args, 1)
 8606        else:
 8607            haystack = seq_get(args, 1)
 8608            needle = seq_get(args, 0)
 8609
 8610        return self.expression(
 8611            exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2))
 8612        )
 8613
 8614    def _parse_join_hint(self, func_name: str) -> exp.JoinHint:
 8615        args = self._parse_csv(self._parse_table)
 8616        return exp.JoinHint(this=func_name.upper(), expressions=args)
 8617
 8618    def _parse_substring(self) -> exp.Substring:
 8619        # Postgres supports the form: substring(string [from int] [for int])
 8620        # (despite being undocumented, the reverse order also works)
 8621        # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6
 8622
 8623        args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise))
 8624
 8625        start, length = None, None
 8626
 8627        while self._curr:
 8628            if self._match(TokenType.FROM):
 8629                start = self._parse_bitwise()
 8630            elif self._match(TokenType.FOR):
 8631                if not start:
 8632                    start = exp.Literal.number(1)
 8633                length = self._parse_bitwise()
 8634            else:
 8635                break
 8636
 8637        if start:
 8638            args.append(start)
 8639        if length:
 8640            args.append(length)
 8641
 8642        return self.validate_expression(exp.Substring.from_arg_list(args), args)
 8643
 8644    def _parse_trim(self) -> exp.Trim:
 8645        # https://www.w3resource.com/sql/character-functions/trim.php
 8646        # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html
 8647
 8648        position = None
 8649        collation = None
 8650        expression = None
 8651
 8652        if self._match_texts(self.TRIM_TYPES):
 8653            position = self._prev.text.upper()
 8654
 8655        this = self._parse_bitwise()
 8656        if self._match_set((TokenType.FROM, TokenType.COMMA)):
 8657            invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST
 8658            expression = self._parse_bitwise()
 8659
 8660            if invert_order:
 8661                this, expression = expression, this
 8662
 8663        if self._match(TokenType.COLLATE):
 8664            collation = self._parse_bitwise()
 8665
 8666        return self.expression(
 8667            exp.Trim(this=this, position=position, expression=expression, collation=collation)
 8668        )
 8669
 8670    def _parse_window_clause(self) -> list[exp.Expr] | None:
 8671        return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None
 8672
 8673    def _parse_named_window(self) -> exp.Expr | None:
 8674        return self._parse_window(self._parse_id_var(), alias=True)
 8675
 8676    def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None:
 8677        if self._curr.token_type == TokenType.VAR:
 8678            if self._match_text_seq("IGNORE", "NULLS"):
 8679                return self.expression(exp.IgnoreNulls(this=this))
 8680            if self._match_text_seq("RESPECT", "NULLS"):
 8681                return self.expression(exp.RespectNulls(this=this))
 8682        return this
 8683
 8684    def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None:
 8685        if self._match(TokenType.HAVING):
 8686            self._match_texts(("MAX", "MIN"))
 8687            max = self._prev.text.upper() != "MIN"
 8688            return self.expression(
 8689                exp.HavingMax(this=this, expression=self._parse_column(), max=max)
 8690            )
 8691
 8692        return this
 8693
 8694    def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None:
 8695        func = this
 8696        comments = func.comments if isinstance(func, exp.Expr) else None
 8697
 8698        # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html
 8699        if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue):
 8700            if self._match_text_seq("FROM", "FIRST"):
 8701                this.set("from_first", True)
 8702            elif self._match_text_seq("FROM", "LAST"):
 8703                this.set("from_first", False)
 8704
 8705        # T-SQL allows the OVER (...) syntax after WITHIN GROUP.
 8706        # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16
 8707        if self._match_text_seq("WITHIN", "GROUP"):
 8708            order = self._parse_wrapped(self._parse_order)
 8709            this = self.expression(exp.WithinGroup(this=this, expression=order))
 8710
 8711        if self._match_pair(TokenType.FILTER, TokenType.L_PAREN):
 8712            self._match(TokenType.WHERE)
 8713            this = self.expression(
 8714                exp.Filter(this=this, expression=self._parse_where(skip_where_token=True))
 8715            )
 8716            self._match_r_paren()
 8717
 8718        # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER
 8719        # Some dialects choose to implement and some do not.
 8720        # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html
 8721
 8722        # There is some code above in _parse_lambda that handles
 8723        #   SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ...
 8724
 8725        # The below changes handle
 8726        #   SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ...
 8727
 8728        # Oracle allows both formats
 8729        #   (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html)
 8730        #   and Snowflake chose to do the same for familiarity
 8731        #   https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes
 8732        if isinstance(this, exp.AggFunc):
 8733            ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls)
 8734
 8735            if ignore_respect and ignore_respect is not this:
 8736                ignore_respect.replace(ignore_respect.this)
 8737                this = self.expression(ignore_respect.__class__(this=this))
 8738
 8739        this = self._parse_respect_or_ignore_nulls(this)
 8740
 8741        # bigquery select from window x AS (partition by ...)
 8742        if alias:
 8743            over = None
 8744            self._match(TokenType.ALIAS)
 8745        elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS):
 8746            return this
 8747        else:
 8748            over = self._prev.text.upper()
 8749
 8750        if comments and isinstance(func, exp.Expr):
 8751            func.pop_comments()
 8752
 8753        if not self._match(TokenType.L_PAREN):
 8754            return self.expression(
 8755                exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments
 8756            )
 8757
 8758        window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS)
 8759
 8760        first: bool | None = True if self._match(TokenType.FIRST) else None
 8761        if self._match_text_seq("LAST"):
 8762            first = False
 8763
 8764        partition, order = self._parse_partition_and_order()
 8765        kind = (
 8766            self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS")
 8767        ) and self._prev.text
 8768
 8769        if kind:
 8770            self._match(TokenType.BETWEEN)
 8771            start = self._parse_window_spec()
 8772
 8773            end = self._parse_window_spec() if self._match(TokenType.AND) else {}
 8774            exclude = (
 8775                self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS)
 8776                if self._match_text_seq("EXCLUDE")
 8777                else None
 8778            )
 8779
 8780            spec = self.expression(
 8781                exp.WindowSpec(
 8782                    kind=kind,
 8783                    start=start["value"],
 8784                    start_side=start["side"],
 8785                    end=end.get("value"),
 8786                    end_side=end.get("side"),
 8787                    exclude=exclude,
 8788                )
 8789            )
 8790        else:
 8791            spec = None
 8792
 8793        self._match_r_paren()
 8794
 8795        window = self.expression(
 8796            exp.Window(
 8797                this=this,
 8798                partition_by=partition,
 8799                order=order,
 8800                spec=spec,
 8801                alias=window_alias,
 8802                over=over,
 8803                first=first,
 8804            ),
 8805            comments=comments,
 8806        )
 8807
 8808        # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...)
 8809        if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False):
 8810            return self._parse_window(window, alias=alias)
 8811
 8812        return window
 8813
 8814    def _parse_partition_and_order(
 8815        self,
 8816    ) -> tuple[list[exp.Expr], exp.Expr | None]:
 8817        return self._parse_partition_by(), self._parse_order()
 8818
 8819    def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]:
 8820        self._match(TokenType.BETWEEN)
 8821
 8822        return {
 8823            "value": (
 8824                (self._match_text_seq("UNBOUNDED") and "UNBOUNDED")
 8825                or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW")
 8826                or self._parse_bitwise()
 8827            ),
 8828            "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None,
 8829        }
 8830
 8831    def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None:
 8832        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 8833        # so this section tries to parse the clause version and if it fails, it treats the token
 8834        # as an identifier (alias)
 8835        if self._can_parse_limit_or_offset():
 8836            return this
 8837
 8838        # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the
 8839        # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it.
 8840        if self._can_parse_named_window():
 8841            return this
 8842
 8843        any_token = self._match(TokenType.ALIAS)
 8844        comments = self._prev_comments
 8845
 8846        if explicit and not any_token:
 8847            return this
 8848
 8849        if self._match(TokenType.L_PAREN):
 8850            aliases = self.expression(
 8851                exp.Aliases(
 8852                    this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token))
 8853                ),
 8854                comments=comments,
 8855            )
 8856            self._match_r_paren(aliases)
 8857            return aliases
 8858
 8859        alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or (
 8860            self.STRING_ALIASES and self._parse_string_as_identifier()
 8861        )
 8862
 8863        if alias:
 8864            comments.extend(alias.pop_comments())
 8865            this = self.expression(exp.Alias(this=this, alias=alias), comments=comments)
 8866            column = this.this
 8867
 8868            # Moves the comment next to the alias in `expr /* comment */ AS alias`
 8869            if not this.comments and column and column.comments:
 8870                this.comments = column.pop_comments()
 8871
 8872        return this
 8873
 8874    def _parse_id_var(
 8875        self,
 8876        any_token: bool = True,
 8877        tokens: t.Collection[TokenType] | None = None,
 8878    ) -> exp.Expr | None:
 8879        expression = self._parse_identifier()
 8880        if not expression and (
 8881            (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS)
 8882        ):
 8883            quoted = self._prev.token_type == TokenType.STRING
 8884            expression = self._identifier_expression(quoted=quoted)
 8885
 8886        return expression
 8887
 8888    def _parse_string(self) -> exp.Expr | None:
 8889        if self._match_set(self.STRING_PARSERS):
 8890            return self.STRING_PARSERS[self._prev.token_type](self, self._prev)
 8891        return self._parse_placeholder()
 8892
 8893    def _parse_string_as_identifier(self) -> exp.Identifier | None:
 8894        if not self._match(TokenType.STRING):
 8895            return None
 8896        output = exp.to_identifier(self._prev.text, quoted=True)
 8897        output.update_positions(self._prev)
 8898        return output
 8899
 8900    def _parse_number(self) -> exp.Expr | None:
 8901        if self._match_set(self.NUMERIC_PARSERS):
 8902            return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev)
 8903        return self._parse_placeholder()
 8904
 8905    def _parse_identifier(self) -> exp.Expr | None:
 8906        if self._match(TokenType.IDENTIFIER):
 8907            return self._identifier_expression(quoted=True)
 8908        return self._parse_placeholder()
 8909
 8910    def _parse_var(
 8911        self,
 8912        any_token: bool = False,
 8913        tokens: t.Collection[TokenType] | None = None,
 8914        upper: bool = False,
 8915    ) -> exp.Expr | None:
 8916        if (
 8917            (any_token and self._advance_any())
 8918            or self._match(TokenType.VAR)
 8919            or (self._match_set(tokens) if tokens else False)
 8920        ):
 8921            return self.expression(
 8922                exp.Var(this=self._prev.text.upper() if upper else self._prev.text)
 8923            )
 8924        return self._parse_placeholder()
 8925
 8926    def _advance_any(self, ignore_reserved: bool = False) -> Token | None:
 8927        if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS):
 8928            self._advance()
 8929            return self._prev
 8930        return None
 8931
 8932    def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None:
 8933        return self._parse_string() or self._parse_var(any_token=True, upper=upper)
 8934
 8935    def _parse_primary_or_var(self) -> exp.Expr | None:
 8936        return self._parse_primary() or self._parse_var(any_token=True)
 8937
 8938    def _parse_null(self) -> exp.Expr | None:
 8939        if self._match_set((TokenType.NULL, TokenType.UNKNOWN)):
 8940            return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev)
 8941        return self._parse_placeholder()
 8942
 8943    def _parse_boolean(self) -> exp.Expr | None:
 8944        if self._match(TokenType.TRUE):
 8945            return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev)
 8946        if self._match(TokenType.FALSE):
 8947            return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev)
 8948        return self._parse_placeholder()
 8949
 8950    def _parse_star(self) -> exp.Expr | None:
 8951        if self._match(TokenType.STAR):
 8952            return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev)
 8953        return self._parse_placeholder()
 8954
 8955    def _parse_parameter(self) -> exp.Parameter:
 8956        this = self._parse_identifier() or self._parse_primary_or_var()
 8957        return self.expression(exp.Parameter(this=this))
 8958
 8959    def _parse_placeholder(self) -> exp.Expr | None:
 8960        if self._match_set(self.PLACEHOLDER_PARSERS):
 8961            placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self)
 8962            if placeholder:
 8963                return placeholder
 8964            self._advance(-1)
 8965        return None
 8966
 8967    def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None:
 8968        if not self._match_texts(keywords):
 8969            return None
 8970        if self._match(TokenType.L_PAREN, advance=False):
 8971            return self._parse_wrapped_csv(self._parse_expression)
 8972
 8973        expression = self._parse_alias(self._parse_disjunction(), explicit=True)
 8974        return [expression] if expression else None
 8975
 8976    def _parse_csv(
 8977        self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA
 8978    ) -> list[T]:
 8979        parse_result = parse_method()
 8980        items = [parse_result] if parse_result is not None else []
 8981
 8982        while self._match(sep):
 8983            if isinstance(parse_result, exp.Expr):
 8984                self._add_comments(parse_result)
 8985            parse_result = parse_method()
 8986            if parse_result is not None:
 8987                items.append(parse_result)
 8988
 8989        return items
 8990
 8991    def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]:
 8992        return self._parse_wrapped_csv(self._parse_id_var, optional=optional)
 8993
 8994    def _parse_wrapped_csv(
 8995        self,
 8996        parse_method: t.Callable[[], T | None],
 8997        sep: TokenType = TokenType.COMMA,
 8998        optional: bool = False,
 8999    ) -> list[T]:
 9000        return self._parse_wrapped(
 9001            lambda: self._parse_csv(parse_method, sep=sep), optional=optional
 9002        )
 9003
 9004    def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T:
 9005        wrapped = self._match(TokenType.L_PAREN)
 9006        if not wrapped and not optional:
 9007            self.raise_error("Expecting (")
 9008        parse_result = parse_method()
 9009        if wrapped:
 9010            self._match_r_paren()
 9011        return parse_result
 9012
 9013    def _parse_expressions(self) -> list[exp.Expr]:
 9014        return self._parse_csv(self._parse_expression)
 9015
 9016    def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None:
 9017        return (
 9018            self._parse_set_operations(
 9019                self._parse_alias(self._parse_assignment(), explicit=True)
 9020                if alias
 9021                else self._parse_assignment()
 9022            )
 9023            or self._parse_select()
 9024        )
 9025
 9026    def _parse_ddl_select(self) -> exp.Expr | None:
 9027        return self._parse_query_modifiers(
 9028            self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False))
 9029        )
 9030
 9031    def _parse_transaction(self) -> exp.Transaction | exp.Command:
 9032        this = None
 9033        if self._match_texts(self.TRANSACTION_KIND):
 9034            this = self._prev.text
 9035
 9036        self._match_texts(("TRANSACTION", "WORK"))
 9037
 9038        modes = []
 9039        while True:
 9040            mode = []
 9041            while self._match(TokenType.VAR) or self._match(TokenType.NOT):
 9042                mode.append(self._prev.text)
 9043
 9044            if mode:
 9045                modes.append(" ".join(mode))
 9046            if not self._match(TokenType.COMMA):
 9047                break
 9048
 9049        return self.expression(exp.Transaction(this=this, modes=modes))
 9050
 9051    def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback:
 9052        chain = None
 9053        savepoint = None
 9054        is_rollback = self._prev.token_type == TokenType.ROLLBACK
 9055
 9056        self._match_texts(("TRANSACTION", "WORK"))
 9057
 9058        if self._match_text_seq("TO"):
 9059            self._match_text_seq("SAVEPOINT")
 9060            savepoint = self._parse_id_var()
 9061
 9062        if self._match(TokenType.AND):
 9063            chain = not self._match_text_seq("NO")
 9064            self._match_text_seq("CHAIN")
 9065
 9066        if is_rollback:
 9067            return self.expression(exp.Rollback(savepoint=savepoint))
 9068
 9069        return self.expression(exp.Commit(chain=chain))
 9070
 9071    def _parse_refresh(self) -> exp.Refresh | exp.Command:
 9072        if self._match_text_seq("EXTERNAL", "TABLE"):
 9073            kind = "EXTERNAL TABLE"
 9074        elif self._match(TokenType.TABLE):
 9075            kind = "TABLE"
 9076        elif self._match_text_seq("MATERIALIZED", "VIEW"):
 9077            kind = "MATERIALIZED VIEW"
 9078        else:
 9079            kind = ""
 9080
 9081        this = self._parse_string() or self._parse_table()
 9082        if not kind and not isinstance(this, exp.Literal):
 9083            return self._parse_as_command(self._prev)
 9084
 9085        return self.expression(exp.Refresh(this=this, kind=kind))
 9086
 9087    def _parse_column_def_with_exists(self):
 9088        start = self._index
 9089        self._match(TokenType.COLUMN)
 9090
 9091        exists_column = self._parse_exists(not_=True)
 9092        expression = self._parse_field_def()
 9093
 9094        if not isinstance(expression, exp.ColumnDef):
 9095            self._retreat(start)
 9096            return None
 9097
 9098        expression.set("exists", exists_column)
 9099
 9100        return expression
 9101
 9102    def _parse_add_column(self) -> exp.ColumnDef | None:
 9103        if not self._prev.text.upper() == "ADD":
 9104            return None
 9105
 9106        return self._parse_column_def_with_exists()
 9107
 9108    def _parse_drop_column(self) -> exp.Drop | exp.Command | None:
 9109        drop = self._parse_drop() if self._match(TokenType.DROP) else None
 9110        if drop and not isinstance(drop, exp.Command):
 9111            drop.set("kind", drop.args.get("kind", "COLUMN"))
 9112        return drop
 9113
 9114    def _parse_alter_drop_action(self) -> exp.Expr | None:
 9115        return self._parse_drop_column()
 9116
 9117    # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html
 9118    def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition:
 9119        return self.expression(
 9120            exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists)
 9121        )
 9122
 9123    def _parse_alter_table_add(self) -> list[exp.Expr]:
 9124        def _parse_add_alteration() -> exp.Expr | None:
 9125            self._match_text_seq("ADD")
 9126            if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False):
 9127                return self.expression(
 9128                    exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint))
 9129                )
 9130
 9131            column_def = self._parse_add_column()
 9132            if isinstance(column_def, exp.ColumnDef):
 9133                return column_def
 9134
 9135            exists = self._parse_exists(not_=True)
 9136            if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False):
 9137                return self.expression(
 9138                    exp.AddPartition(
 9139                        exists=exists,
 9140                        this=self._parse_field(any_token=True),
 9141                        location=self._match_text_seq("LOCATION", advance=False)
 9142                        and self._parse_property(),
 9143                    )
 9144                )
 9145
 9146            return None
 9147
 9148        if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and (
 9149            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
 9150            or self._match_text_seq("COLUMNS")
 9151        ):
 9152            schema = self._parse_schema()
 9153
 9154            return (
 9155                ensure_list(schema)
 9156                if schema
 9157                else self._parse_csv(self._parse_column_def_with_exists)
 9158            )
 9159
 9160        return self._parse_csv(_parse_add_alteration)
 9161
 9162    def _parse_alter_table_alter(self) -> exp.Expr | None:
 9163        if self._match_texts(self.ALTER_ALTER_PARSERS):
 9164            return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self)
 9165
 9166        # Many dialects support the ALTER [COLUMN] syntax, so if there is no
 9167        # keyword after ALTER we default to parsing this statement
 9168        self._match(TokenType.COLUMN)
 9169        exists = self._parse_exists()
 9170        column = self._parse_field(any_token=True)
 9171
 9172        if self._match_pair(TokenType.DROP, TokenType.DEFAULT):
 9173            return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None))
 9174        if self._match_pair(TokenType.SET, TokenType.DEFAULT):
 9175            return self.expression(
 9176                exp.AlterColumn(
 9177                    this=column, default=self._parse_disjunction(), exists=exists or None
 9178                )
 9179            )
 9180        if self._match(TokenType.COMMENT):
 9181            return self.expression(
 9182                exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None)
 9183            )
 9184        if self._match_text_seq("DROP", "NOT", "NULL"):
 9185            return self.expression(
 9186                exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None)
 9187            )
 9188        if self._match_text_seq("SET", "NOT", "NULL"):
 9189            return self.expression(
 9190                exp.AlterColumn(this=column, allow_null=False, exists=exists or None)
 9191            )
 9192
 9193        if self._match_text_seq("SET", "VISIBLE"):
 9194            return self.expression(
 9195                exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None)
 9196            )
 9197        if self._match_text_seq("SET", "INVISIBLE"):
 9198            return self.expression(
 9199                exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None)
 9200            )
 9201
 9202        self._match_text_seq("SET", "DATA")
 9203        self._match_text_seq("TYPE")
 9204        return self.expression(
 9205            exp.AlterColumn(
 9206                this=column,
 9207                dtype=self._parse_types(),
 9208                collate=self._match(TokenType.COLLATE) and self._parse_term(),
 9209                using=self._match(TokenType.USING) and self._parse_disjunction(),
 9210                exists=exists or None,
 9211            )
 9212        )
 9213
 9214    def _parse_alter_diststyle(self) -> exp.AlterDistStyle:
 9215        if self._match_texts(("ALL", "EVEN", "AUTO")):
 9216            return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper())))
 9217
 9218        self._match_text_seq("KEY", "DISTKEY")
 9219        return self.expression(exp.AlterDistStyle(this=self._parse_column()))
 9220
 9221    def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey:
 9222        if compound:
 9223            self._match_text_seq("SORTKEY")
 9224
 9225        if self._match(TokenType.L_PAREN, advance=False):
 9226            return self.expression(
 9227                exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound)
 9228            )
 9229
 9230        self._match_texts(("AUTO", "NONE"))
 9231        return self.expression(
 9232            exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound)
 9233        )
 9234
 9235    def _parse_alter_table_drop(self) -> list[exp.Expr]:
 9236        index = self._index - 1
 9237
 9238        partition_exists = self._parse_exists()
 9239        if self._match(TokenType.PARTITION, advance=False):
 9240            return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists))
 9241
 9242        self._retreat(index)
 9243        return self._parse_csv(self._parse_alter_drop_action)
 9244
 9245    def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None:
 9246        if self._match(TokenType.COLUMN) or (
 9247            not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False)
 9248        ):
 9249            exists = self._parse_exists()
 9250            old_column = self._parse_column()
 9251            to = self._match_text_seq("TO")
 9252            new_column = self._parse_column()
 9253
 9254            if old_column is None or not to or new_column is None:
 9255                return None
 9256
 9257            return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists))
 9258
 9259        self._match_text_seq("TO")
 9260        return self.expression(exp.AlterRename(this=self._parse_table(schema=True)))
 9261
 9262    def _parse_alter_table_set(self) -> exp.AlterSet:
 9263        alter_set = self.expression(exp.AlterSet())
 9264
 9265        if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq(
 9266            "TABLE", "PROPERTIES"
 9267        ):
 9268            alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment))
 9269        elif self._match_text_seq("FILESTREAM_ON", advance=False):
 9270            alter_set.set("expressions", [self._parse_assignment()])
 9271        elif self._match_texts(("LOGGED", "UNLOGGED")):
 9272            alter_set.set("option", exp.var(self._prev.text.upper()))
 9273        elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")):
 9274            alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}"))
 9275        elif self._match_text_seq("LOCATION"):
 9276            alter_set.set("location", self._parse_field())
 9277        elif self._match_text_seq("ACCESS", "METHOD"):
 9278            alter_set.set("access_method", self._parse_field())
 9279        elif self._match_text_seq("TABLESPACE"):
 9280            alter_set.set("tablespace", self._parse_field())
 9281        elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"):
 9282            alter_set.set("file_format", [self._parse_field()])
 9283        elif self._match_text_seq("STAGE_FILE_FORMAT"):
 9284            alter_set.set("file_format", self._parse_wrapped_options())
 9285        elif self._match_text_seq("STAGE_COPY_OPTIONS"):
 9286            alter_set.set("copy_options", self._parse_wrapped_options())
 9287        elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"):
 9288            alter_set.set("tag", self._parse_csv(self._parse_assignment))
 9289        else:
 9290            if self._match_text_seq("SERDE"):
 9291                alter_set.set("serde", self._parse_field())
 9292
 9293            properties = self._parse_wrapped(self._parse_properties, optional=True)
 9294            alter_set.set("expressions", [properties])
 9295
 9296        return alter_set
 9297
 9298    def _parse_alter_session(self) -> exp.AlterSession:
 9299        """Parse ALTER SESSION SET/UNSET statements."""
 9300        if self._match(TokenType.SET):
 9301            expressions = self._parse_csv(lambda: self._parse_set_item_assignment())
 9302            return self.expression(exp.AlterSession(expressions=expressions, unset=False))
 9303
 9304        self._match_text_seq("UNSET")
 9305        expressions = self._parse_csv(
 9306            lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True)))
 9307        )
 9308        return self.expression(exp.AlterSession(expressions=expressions, unset=True))
 9309
 9310    def _parse_alter(self) -> exp.Alter | exp.Command:
 9311        start = self._prev
 9312
 9313        iceberg = self._match_text_seq("ICEBERG")
 9314
 9315        alter_token = self._match_set(self.ALTERABLES) and self._prev
 9316        if not alter_token:
 9317            return self._parse_as_command(start)
 9318        if iceberg and alter_token.token_type != TokenType.TABLE:
 9319            return self._parse_as_command(start)
 9320
 9321        exists = self._parse_exists()
 9322        only = self._match_text_seq("ONLY")
 9323
 9324        if alter_token.token_type == TokenType.SESSION:
 9325            this = None
 9326            check = None
 9327            cluster = None
 9328        else:
 9329            this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS)
 9330            check = self._match_text_seq("WITH", "CHECK")
 9331            cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9332
 9333            if self._next:
 9334                self._advance()
 9335
 9336        parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None
 9337        if parser:
 9338            actions = ensure_list(parser(self))
 9339            not_valid = self._match_text_seq("NOT", "VALID")
 9340            options = self._parse_csv(self._parse_property)
 9341            cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE")
 9342
 9343            if not self._curr and actions:
 9344                return self.expression(
 9345                    exp.Alter(
 9346                        this=this,
 9347                        kind=alter_token.text.upper(),
 9348                        exists=exists,
 9349                        actions=actions,
 9350                        only=only,
 9351                        options=options,
 9352                        cluster=cluster,
 9353                        not_valid=not_valid,
 9354                        check=check,
 9355                        cascade=cascade,
 9356                        iceberg=iceberg,
 9357                    )
 9358                )
 9359
 9360        return self._parse_as_command(start)
 9361
 9362    def _parse_analyze(self) -> exp.Analyze | exp.Command:
 9363        start = self._prev
 9364        # https://duckdb.org/docs/sql/statements/analyze
 9365        if not self._curr:
 9366            return self.expression(exp.Analyze())
 9367
 9368        options = []
 9369        while self._match_texts(self.ANALYZE_STYLES):
 9370            if self._prev.text.upper() == "BUFFER_USAGE_LIMIT":
 9371                options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}")
 9372            else:
 9373                options.append(self._prev.text.upper())
 9374
 9375        tables: exp.Expr | list[exp.Expr] | None = None
 9376        inner_expression: exp.Expr | None = None
 9377
 9378        kind = self._curr.text.upper() if self._curr else None
 9379
 9380        if self._match(TokenType.TABLE):
 9381            tables = self._parse_csv(self._parse_table_parts)
 9382        elif self._match(TokenType.INDEX):
 9383            tables = self._parse_table_parts()
 9384        elif self._match_text_seq("TABLES"):
 9385            if self._match_set((TokenType.FROM, TokenType.IN)):
 9386                kind = f"{kind} {self._prev.text.upper()}"
 9387                tables = self._parse_table(schema=True, is_db_reference=True)
 9388        elif self._match_text_seq("DATABASE"):
 9389            tables = self._parse_table(schema=True, is_db_reference=True)
 9390        elif self._match_text_seq("CLUSTER"):
 9391            tables = self._parse_table()
 9392        # Try matching inner expr keywords before fallback to parse table.
 9393        elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9394            kind = None
 9395            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9396        else:
 9397            # Empty kind  https://prestodb.io/docs/current/sql/analyze.html
 9398            kind = None
 9399            tables = self._parse_csv(self._parse_table_parts)
 9400
 9401        partition = self._try_parse(self._parse_partition)
 9402        if not partition and self._match_texts(self.PARTITION_KEYWORDS):
 9403            return self._parse_as_command(start)
 9404
 9405        # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9406        if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq(
 9407            "WITH", "ASYNC", "MODE"
 9408        ):
 9409            mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE"
 9410        else:
 9411            mode = None
 9412
 9413        if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9414            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9415
 9416        properties = self._parse_properties()
 9417        return self.expression(
 9418            exp.Analyze(
 9419                kind=kind,
 9420                tables=ensure_list(tables),
 9421                mode=mode,
 9422                partition=partition,
 9423                properties=properties,
 9424                expression=inner_expression,
 9425                options=options,
 9426            )
 9427        )
 9428
 9429    # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html
 9430    def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics:
 9431        this = None
 9432        kind = self._prev.text.upper()
 9433        option = self._prev.text.upper() if self._match_text_seq("DELTA") else None
 9434        expressions = []
 9435
 9436        if not self._match_text_seq("STATISTICS"):
 9437            self.raise_error("Expecting token STATISTICS")
 9438
 9439        if self._match_text_seq("NOSCAN"):
 9440            this = "NOSCAN"
 9441        elif self._match(TokenType.FOR):
 9442            if self._match_text_seq("ALL", "COLUMNS"):
 9443                this = "FOR ALL COLUMNS"
 9444            if self._match_text_seq("COLUMNS"):
 9445                this = "FOR COLUMNS"
 9446                expressions = self._parse_csv(self._parse_column_reference)
 9447        elif self._match_text_seq("SAMPLE"):
 9448            sample = self._parse_number()
 9449            expressions = [
 9450                self.expression(
 9451                    exp.AnalyzeSample(
 9452                        sample=sample,
 9453                        kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None,
 9454                    )
 9455                )
 9456            ]
 9457
 9458        return self.expression(
 9459            exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions)
 9460        )
 9461
 9462    # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html
 9463    def _parse_analyze_validate(self) -> exp.AnalyzeValidate:
 9464        kind = None
 9465        this = None
 9466        expression: exp.Expr | None = None
 9467        if self._match_text_seq("REF", "UPDATE"):
 9468            kind = "REF"
 9469            this = "UPDATE"
 9470            if self._match_text_seq("SET", "DANGLING", "TO", "NULL"):
 9471                this = "UPDATE SET DANGLING TO NULL"
 9472        elif self._match_text_seq("STRUCTURE"):
 9473            kind = "STRUCTURE"
 9474            if self._match_text_seq("CASCADE", "FAST"):
 9475                this = "CASCADE FAST"
 9476            elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts(
 9477                ("ONLINE", "OFFLINE")
 9478            ):
 9479                this = f"CASCADE COMPLETE {self._prev.text.upper()}"
 9480                expression = self._parse_into()
 9481
 9482        return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression))
 9483
 9484    def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None:
 9485        this = self._prev.text.upper()
 9486        if self._match_text_seq("COLUMNS"):
 9487            return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}"))
 9488        return None
 9489
 9490    def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None:
 9491        kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None
 9492        if self._match_text_seq("STATISTICS"):
 9493            return self.expression(exp.AnalyzeDelete(kind=kind))
 9494        return None
 9495
 9496    def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None:
 9497        if self._match_text_seq("CHAINED", "ROWS"):
 9498            return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into()))
 9499        return None
 9500
 9501    # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html
 9502    def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram:
 9503        this = self._prev.text.upper()
 9504        expression: exp.Expr | None = None
 9505        expressions = []
 9506        update_options = None
 9507
 9508        if self._match_text_seq("HISTOGRAM", "ON"):
 9509            expressions = self._parse_csv(self._parse_column_reference)
 9510            with_expressions = []
 9511            while self._match(TokenType.WITH):
 9512                # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9513                if self._match_texts(("SYNC", "ASYNC")):
 9514                    if self._match_text_seq("MODE", advance=False):
 9515                        with_expressions.append(f"{self._prev.text.upper()} MODE")
 9516                        self._advance()
 9517                else:
 9518                    buckets = self._parse_number()
 9519                    if self._match_text_seq("BUCKETS"):
 9520                        with_expressions.append(f"{buckets} BUCKETS")
 9521            if with_expressions:
 9522                expression = self.expression(exp.AnalyzeWith(expressions=with_expressions))
 9523
 9524            if self._match_texts(("MANUAL", "AUTO")) and self._match(
 9525                TokenType.UPDATE, advance=False
 9526            ):
 9527                update_options = self._prev.text.upper()
 9528                self._advance()
 9529            elif self._match_text_seq("USING", "DATA"):
 9530                expression = self.expression(exp.UsingData(this=self._parse_string()))
 9531
 9532        return self.expression(
 9533            exp.AnalyzeHistogram(
 9534                this=this,
 9535                expressions=expressions,
 9536                expression=expression,
 9537                update_options=update_options,
 9538            )
 9539        )
 9540
 9541    def _parse_merge(self) -> exp.Merge:
 9542        self._match(TokenType.INTO)
 9543        target = self._parse_table()
 9544
 9545        if target and self._match(TokenType.ALIAS, advance=False):
 9546            target.set("alias", self._parse_table_alias())
 9547
 9548        self._match(TokenType.USING)
 9549        using = self._parse_table()
 9550
 9551        return self.expression(
 9552            exp.Merge(
 9553                this=target,
 9554                using=using,
 9555                on=self._match(TokenType.ON) and self._parse_disjunction(),
 9556                using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(),
 9557                whens=self._parse_when_matched(),
 9558                returning=self._parse_returning(),
 9559            )
 9560        )
 9561
 9562    def _parse_when_matched(self) -> exp.Whens:
 9563        whens = []
 9564
 9565        while self._match(TokenType.WHEN):
 9566            matched = not self._match(TokenType.NOT)
 9567            self._match_text_seq("MATCHED")
 9568            source = (
 9569                False
 9570                if self._match_text_seq("BY", "TARGET")
 9571                else self._match_text_seq("BY", "SOURCE")
 9572            )
 9573            condition = self._parse_disjunction() if self._match(TokenType.AND) else None
 9574
 9575            self._match(TokenType.THEN)
 9576
 9577            if self._match(TokenType.INSERT):
 9578                this = self._parse_star()
 9579                if this:
 9580                    then: exp.Expr | None = self.expression(exp.Insert(this=this))
 9581                else:
 9582                    then = self.expression(
 9583                        exp.Insert(
 9584                            this=exp.var("ROW")
 9585                            if self._match_text_seq("ROW")
 9586                            else self._parse_value(values=False),
 9587                            expression=self._match_text_seq("VALUES") and self._parse_value(),
 9588                            where=self._parse_where(),
 9589                        )
 9590                    )
 9591            elif self._match(TokenType.UPDATE):
 9592                expressions = self._parse_star()
 9593                if expressions:
 9594                    then = self.expression(exp.Update(expressions=expressions))
 9595                else:
 9596                    then = self.expression(
 9597                        exp.Update(
 9598                            expressions=self._match(TokenType.SET)
 9599                            and self._parse_csv(self._parse_update_assignment),
 9600                            where=self._parse_where(),
 9601                        )
 9602                    )
 9603            elif self._match(TokenType.DELETE):
 9604                then = self.expression(exp.Var(this=self._prev.text))
 9605            else:
 9606                then = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 9607
 9608            whens.append(
 9609                self.expression(
 9610                    exp.When(matched=matched, source=source, condition=condition, then=then)
 9611                )
 9612            )
 9613        return self.expression(exp.Whens(expressions=whens))
 9614
 9615    def _parse_show(self) -> exp.Expr | None:
 9616        parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE)
 9617        if parser:
 9618            return parser(self)
 9619        return self._parse_as_command(self._prev)
 9620
 9621    def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None:
 9622        index = self._index
 9623
 9624        if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"):
 9625            return self._parse_set_transaction(global_=kind == "GLOBAL")
 9626
 9627        left = self._parse_primary() or self._parse_column()
 9628        assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS)
 9629
 9630        if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter):
 9631            self._retreat(index)
 9632            return None
 9633
 9634        right = self._parse_statement() or self._parse_id_var()
 9635        if isinstance(right, (exp.Column, exp.Identifier)):
 9636            right = exp.var(right.name)
 9637
 9638        this = self.expression(exp.EQ(this=left, expression=right))
 9639        return self.expression(exp.SetItem(this=this, kind=kind))
 9640
 9641    def _parse_set_transaction(self, global_: bool = False) -> exp.Expr:
 9642        self._match_text_seq("TRANSACTION")
 9643        characteristics = self._parse_csv(
 9644            lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS)
 9645        )
 9646        return self.expression(
 9647            exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_)
 9648        )
 9649
 9650    def _parse_set_item(self) -> exp.Expr | None:
 9651        parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE)
 9652        return parser(self) if parser else self._parse_set_item_assignment(kind=None)
 9653
 9654    def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command:
 9655        index = self._index
 9656        set_ = self.expression(
 9657            exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag)
 9658        )
 9659
 9660        if self._curr:
 9661            self._retreat(index)
 9662            return self._parse_as_command(self._prev)
 9663
 9664        return set_
 9665
 9666    def _parse_var_from_options(
 9667        self, options: OPTIONS_TYPE, raise_unmatched: bool = True
 9668    ) -> exp.Var | None:
 9669        start = self._curr
 9670        if not start:
 9671            return None
 9672
 9673        option = start.text.upper()
 9674        continuations = (
 9675            None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option)
 9676        )
 9677
 9678        index = self._index
 9679        self._advance()
 9680        for keywords in continuations or []:
 9681            if isinstance(keywords, str):
 9682                keywords = (keywords,)
 9683
 9684            if self._match_text_seq(*keywords):
 9685                option = f"{option} {' '.join(keywords)}"
 9686                break
 9687        else:
 9688            if continuations or continuations is None:
 9689                if raise_unmatched:
 9690                    self.raise_error(f"Unknown option {option}")
 9691
 9692                self._retreat(index)
 9693                return None
 9694
 9695        return exp.var(option)
 9696
 9697    def _parse_as_command(self, start: Token) -> exp.Command:
 9698        while self._curr:
 9699            self._advance()
 9700        text = self._find_sql(start, self._prev)
 9701        size = len(start.text)
 9702        self._warn_unsupported()
 9703        return exp.Command(this=text[:size], expression=text[size:])
 9704
 9705    def _parse_dict_property(self, this: str) -> exp.DictProperty:
 9706        settings = []
 9707
 9708        self._match_l_paren()
 9709        kind = self._parse_id_var()
 9710
 9711        if self._match(TokenType.L_PAREN):
 9712            while True:
 9713                key = self._parse_id_var()
 9714                value = self._parse_function() or self._parse_primary_or_var()
 9715                if not key and value is None:
 9716                    break
 9717                settings.append(self.expression(exp.DictSubProperty(this=key, value=value)))
 9718            self._match(TokenType.R_PAREN)
 9719
 9720        self._match_r_paren()
 9721
 9722        return self.expression(
 9723            exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings)
 9724        )
 9725
 9726    def _parse_dict_range(self, this: str) -> exp.DictRange:
 9727        self._match_l_paren()
 9728        has_min = self._match_text_seq("MIN")
 9729        if has_min:
 9730            min = self._parse_var() or self._parse_primary()
 9731            self._match_text_seq("MAX")
 9732            max = self._parse_var() or self._parse_primary()
 9733        else:
 9734            max = self._parse_var() or self._parse_primary()
 9735            min = exp.Literal.number(0)
 9736        self._match_r_paren()
 9737        return self.expression(exp.DictRange(this=this, min=min, max=max))
 9738
 9739    def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None:
 9740        index = self._index
 9741        expression = self._parse_column()
 9742        position = self._match(TokenType.COMMA) and self._parse_column()
 9743
 9744        if not self._match(TokenType.IN):
 9745            self._retreat(index - 1)
 9746            return None
 9747        iterator = self._parse_column()
 9748        condition = self._parse_disjunction() if self._match_text_seq("IF") else None
 9749        return self.expression(
 9750            exp.Comprehension(
 9751                this=this,
 9752                expression=expression,
 9753                position=position,
 9754                iterator=iterator,
 9755                condition=condition,
 9756            )
 9757        )
 9758
 9759    def _parse_heredoc(self) -> exp.Heredoc | None:
 9760        if self._match(TokenType.HEREDOC_STRING):
 9761            return self.expression(exp.Heredoc(this=self._prev.text))
 9762
 9763        if not self._match_text_seq("$"):
 9764            return None
 9765
 9766        tags = ["$"]
 9767        tag_text = None
 9768
 9769        if self._is_connected():
 9770            self._advance()
 9771            tags.append(self._prev.text.upper())
 9772        else:
 9773            self.raise_error("No closing $ found")
 9774
 9775        if tags[-1] != "$":
 9776            if self._is_connected() and self._match_text_seq("$"):
 9777                tag_text = tags[-1]
 9778                tags.append("$")
 9779            else:
 9780                self.raise_error("No closing $ found")
 9781
 9782        heredoc_start = self._curr
 9783
 9784        while self._curr:
 9785            if self._match_text_seq(*tags, advance=False):
 9786                this = self._find_sql(heredoc_start, self._prev)
 9787                self._advance(len(tags))
 9788                return self.expression(exp.Heredoc(this=this, tag=tag_text))
 9789
 9790            self._advance()
 9791
 9792        self.raise_error(f"No closing {''.join(tags)} found")
 9793        return None
 9794
 9795    def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None:
 9796        if not self._curr:
 9797            return None
 9798
 9799        index = self._index
 9800        this = []
 9801        while True:
 9802            # The current token might be multiple words
 9803            curr = self._curr.text.upper()
 9804            key = curr.split(" ")
 9805            this.append(curr)
 9806
 9807            self._advance()
 9808            result, trie = in_trie(trie, key)
 9809            if result == TrieResult.FAILED:
 9810                break
 9811
 9812            if result == TrieResult.EXISTS:
 9813                subparser = parsers[" ".join(this)]
 9814                return subparser
 9815
 9816        self._retreat(index)
 9817        return None
 9818
 9819    def _match_l_paren(self, expression: exp.Expr | None = None) -> None:
 9820        if not self._match(TokenType.L_PAREN, expression=expression):
 9821            self.raise_error("Expecting (")
 9822
 9823    def _match_r_paren(self, expression: exp.Expr | None = None) -> None:
 9824        if not self._match(TokenType.R_PAREN, expression=expression):
 9825            self.raise_error("Expecting )")
 9826
 9827    def _replace_lambda(
 9828        self, node: exp.Expr | None, expressions: list[exp.Expr]
 9829    ) -> exp.Expr | None:
 9830        if not node:
 9831            return node
 9832
 9833        lambda_types = {e.name: e.args.get("to") or False for e in expressions}
 9834
 9835        for column in node.find_all(exp.Column):
 9836            typ = lambda_types.get(column.parts[0].name)
 9837            if typ is not None:
 9838                dot_or_id = column.to_dot() if column.table else column.this
 9839
 9840                if typ:
 9841                    dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ))
 9842
 9843                parent = column.parent
 9844
 9845                while isinstance(parent, exp.Dot):
 9846                    if not isinstance(parent.parent, exp.Dot):
 9847                        parent.replace(dot_or_id)
 9848                        break
 9849                    parent = parent.parent
 9850                else:
 9851                    if column is node:
 9852                        node = dot_or_id
 9853                    else:
 9854                        column.replace(dot_or_id)
 9855        return node
 9856
 9857    def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr:
 9858        start = self._prev
 9859
 9860        # Not to be confused with TRUNCATE(number, decimals) function call
 9861        if self._match(TokenType.L_PAREN):
 9862            self._retreat(self._index - 2)
 9863            return self._parse_function()
 9864
 9865        # Clickhouse supports TRUNCATE DATABASE as well
 9866        is_database = self._match(TokenType.DATABASE)
 9867
 9868        self._match(TokenType.TABLE)
 9869
 9870        exists = self._parse_exists(not_=False)
 9871
 9872        expressions = self._parse_csv(
 9873            lambda: self._parse_table(schema=True, is_db_reference=is_database)
 9874        )
 9875
 9876        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9877
 9878        if self._match_text_seq("RESTART", "IDENTITY"):
 9879            identity = "RESTART"
 9880        elif self._match_text_seq("CONTINUE", "IDENTITY"):
 9881            identity = "CONTINUE"
 9882        else:
 9883            identity = None
 9884
 9885        if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"):
 9886            option = self._prev.text
 9887        else:
 9888            option = None
 9889
 9890        partition = self._parse_partition()
 9891
 9892        # Fallback case
 9893        if self._curr:
 9894            return self._parse_as_command(start)
 9895
 9896        return self.expression(
 9897            exp.TruncateTable(
 9898                expressions=expressions,
 9899                is_database=is_database,
 9900                exists=exists,
 9901                cluster=cluster,
 9902                identity=identity,
 9903                option=option,
 9904                partition=partition,
 9905            )
 9906        )
 9907
 9908    def _parse_indexed_column(self) -> exp.Expr | None:
 9909        return self._parse_ordered(self._parse_opclass)
 9910
 9911    def _parse_with_operator(self) -> exp.Expr | None:
 9912        this = self._parse_indexed_column()
 9913
 9914        if not self._match(TokenType.WITH):
 9915            return this
 9916
 9917        op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS)
 9918
 9919        return self.expression(exp.WithOperator(this=this, op=op))
 9920
 9921    def _parse_wrapped_options(self) -> list[exp.Expr]:
 9922        self._match(TokenType.EQ)
 9923        self._match(TokenType.L_PAREN)
 9924
 9925        opts: list[exp.Expr] = []
 9926        option: exp.Expr | list[exp.Expr] | None
 9927        while self._curr and not self._match(TokenType.R_PAREN):
 9928            if self._match_text_seq("FORMAT_NAME", "="):
 9929                # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL
 9930                option = self._parse_format_name()
 9931            else:
 9932                option = self._parse_property()
 9933
 9934            if option is None:
 9935                self.raise_error("Unable to parse option")
 9936                break
 9937
 9938            opts.extend(ensure_list(option))
 9939
 9940        return opts
 9941
 9942    def _parse_copy_parameters(self) -> list[exp.CopyParameter]:
 9943        sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None
 9944
 9945        options = []
 9946        while self._curr and not self._match(TokenType.R_PAREN, advance=False):
 9947            option = self._parse_var(any_token=True)
 9948            prev = self._prev.text.upper()
 9949
 9950            # Different dialects might separate options and values by white space, "=" and "AS"
 9951            self._match(TokenType.EQ)
 9952            self._match(TokenType.ALIAS)
 9953
 9954            param = self.expression(exp.CopyParameter(this=option))
 9955
 9956            if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match(
 9957                TokenType.L_PAREN, advance=False
 9958            ):
 9959                # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options
 9960                param.set("expressions", self._parse_wrapped_options())
 9961            elif prev == "FILE_FORMAT":
 9962                # T-SQL's external file format case
 9963                param.set("expression", self._parse_field())
 9964            elif (
 9965                prev == "FORMAT"
 9966                and self._prev.token_type == TokenType.ALIAS
 9967                and self._match_texts(("AVRO", "JSON"))
 9968            ):
 9969                param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}"))
 9970                param.set("expression", self._parse_field())
 9971            else:
 9972                param.set("expression", self._parse_unquoted_field() or self._parse_bracket())
 9973
 9974            options.append(param)
 9975
 9976            if sep:
 9977                self._match(sep)
 9978
 9979        return options
 9980
 9981    def _parse_credentials(self) -> exp.Credentials | None:
 9982        expr = self.expression(exp.Credentials())
 9983
 9984        if self._match_text_seq("STORAGE_INTEGRATION", "="):
 9985            expr.set("storage", self._parse_field())
 9986        if self._match_text_seq("CREDENTIALS"):
 9987            # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string>
 9988            creds = (
 9989                self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field()
 9990            )
 9991            expr.set("credentials", creds)
 9992        if self._match_text_seq("ENCRYPTION"):
 9993            expr.set("encryption", self._parse_wrapped_options())
 9994        if self._match_text_seq("IAM_ROLE"):
 9995            expr.set(
 9996                "iam_role",
 9997                exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(),
 9998            )
 9999        if self._match_text_seq("REGION"):
10000            expr.set("region", self._parse_field())
10001
10002        return expr
10003
10004    def _parse_file_location(self) -> exp.Expr | None:
10005        return self._parse_field()
10006
10007    def _parse_copy(self) -> exp.Copy | exp.Command:
10008        start = self._prev
10009
10010        self._match(TokenType.INTO)
10011
10012        this = (
10013            self._parse_select(nested=True, parse_subquery_alias=False)
10014            if self._match(TokenType.L_PAREN, advance=False)
10015            else self._parse_table(schema=True)
10016        )
10017
10018        kind = self._match(TokenType.FROM) or not self._match_text_seq("TO")
10019
10020        files = self._parse_csv(self._parse_file_location)
10021        if self._match(TokenType.EQ, advance=False):
10022            # Backtrack one token since we've consumed the lhs of a parameter assignment here.
10023            # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter
10024            # list via `_parse_wrapped(..)` below.
10025            self._advance(-1)
10026            files = []
10027
10028        credentials = self._parse_credentials()
10029
10030        self._match_text_seq("WITH")
10031
10032        params = self._parse_wrapped(self._parse_copy_parameters, optional=True)
10033
10034        # Fallback case
10035        if self._curr:
10036            return self._parse_as_command(start)
10037
10038        return self.expression(
10039            exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params)
10040        )
10041
10042    def _parse_normalize(self) -> exp.Normalize:
10043        return self.expression(
10044            exp.Normalize(
10045                this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var()
10046            )
10047        )
10048
10049    def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor:
10050        args = self._parse_csv(lambda: self._parse_lambda())
10051
10052        this = seq_get(args, 0)
10053        decimals = seq_get(args, 1)
10054
10055        return expr_type(
10056            this=this,
10057            decimals=decimals,
10058            to=self._parse_var() if self._match_text_seq("TO") else None,
10059        )
10060
10061    def _parse_star_ops(self) -> exp.Expr | None:
10062        star_token = self._prev
10063
10064        if self._match_text_seq("COLUMNS", "(", advance=False):
10065            this = self._parse_function()
10066            if isinstance(this, exp.Columns):
10067                this.set("unpack", True)
10068            return this
10069
10070        index = self._index
10071        ilike = self._parse_string() if self._match(TokenType.ILIKE) else None
10072        if not ilike:
10073            # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)`
10074            self._retreat(index)
10075
10076        return self.expression(
10077            exp.Star(
10078                ilike=ilike,
10079                except_=self._parse_star_op("EXCEPT", "EXCLUDE"),
10080                replace=self._parse_star_op("REPLACE"),
10081                rename=self._parse_star_op("RENAME"),
10082            )
10083        ).update_positions(star_token)
10084
10085    def _parse_grant_privilege(self) -> exp.GrantPrivilege | None:
10086        privilege_parts = []
10087
10088        # Keep consuming consecutive keywords until comma (end of this privilege) or ON
10089        # (end of privilege list) or L_PAREN (start of column list) are met
10090        while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False):
10091            privilege_parts.append(self._curr.text.upper())
10092            self._advance()
10093
10094        if not privilege_parts:
10095            self.raise_error("Expected privilege")
10096            return None
10097
10098        this = exp.var(" ".join(privilege_parts))
10099        expressions = (
10100            self._parse_wrapped_csv(self._parse_column)
10101            if self._match(TokenType.L_PAREN, advance=False)
10102            else None
10103        )
10104
10105        return self.expression(exp.GrantPrivilege(this=this, expressions=expressions))
10106
10107    def _parse_grant_principal(self) -> exp.GrantPrincipal | None:
10108        kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper()
10109        principal = self._parse_id_var()
10110
10111        if not principal:
10112            return None
10113
10114        return self.expression(exp.GrantPrincipal(this=principal, kind=kind))
10115
10116    def _parse_grant_revoke_common(
10117        self,
10118    ) -> tuple[list | None, str | None, exp.Expr | None]:
10119        privileges = self._parse_csv(self._parse_grant_privilege)
10120
10121        self._match(TokenType.ON)
10122        kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None
10123
10124        # Attempt to parse the securable e.g. MySQL allows names
10125        # such as "foo.*", "*.*" which are not easily parseable yet
10126        securable = self._try_parse(self._parse_table_parts)
10127
10128        return privileges, kind, securable
10129
10130    def _parse_grant(self) -> exp.Grant | exp.Command:
10131        start = self._prev
10132
10133        privileges, kind, securable = self._parse_grant_revoke_common()
10134
10135        if not securable or not self._match_text_seq("TO"):
10136            return self._parse_as_command(start)
10137
10138        principals = self._parse_csv(self._parse_grant_principal)
10139
10140        grant_option = self._match_text_seq("WITH", "GRANT", "OPTION")
10141
10142        if self._curr:
10143            return self._parse_as_command(start)
10144
10145        return self.expression(
10146            exp.Grant(
10147                privileges=privileges,
10148                kind=kind,
10149                securable=securable,
10150                principals=principals,
10151                grant_option=grant_option,
10152            )
10153        )
10154
10155    def _parse_revoke(self) -> exp.Revoke | exp.Command:
10156        start = self._prev
10157
10158        grant_option = self._match_text_seq("GRANT", "OPTION", "FOR")
10159
10160        privileges, kind, securable = self._parse_grant_revoke_common()
10161
10162        if not securable or not self._match_text_seq("FROM"):
10163            return self._parse_as_command(start)
10164
10165        principals = self._parse_csv(self._parse_grant_principal)
10166
10167        cascade = None
10168        if self._match_texts(("CASCADE", "RESTRICT")):
10169            cascade = self._prev.text.upper()
10170
10171        if self._curr:
10172            return self._parse_as_command(start)
10173
10174        return self.expression(
10175            exp.Revoke(
10176                privileges=privileges,
10177                kind=kind,
10178                securable=securable,
10179                principals=principals,
10180                grant_option=grant_option,
10181                cascade=cascade,
10182            )
10183        )
10184
10185    def _parse_overlay(self) -> exp.Overlay:
10186        def _parse_overlay_arg(text: str) -> exp.Expr | None:
10187            return (
10188                self._parse_bitwise()
10189                if self._match(TokenType.COMMA) or self._match_text_seq(text)
10190                else None
10191            )
10192
10193        return self.expression(
10194            exp.Overlay(
10195                this=self._parse_bitwise(),
10196                expression=_parse_overlay_arg("PLACING"),
10197                from_=_parse_overlay_arg("FROM"),
10198                for_=_parse_overlay_arg("FOR"),
10199            )
10200        )
10201
10202    def _parse_format_name(self) -> exp.Property:
10203        # Note: Although not specified in the docs, Snowflake does accept a string/identifier
10204        # for FILE_FORMAT = <format_name>
10205        return self.expression(
10206            exp.Property(
10207                this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts()
10208            )
10209        )
10210
10211    def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F:
10212        is_distinct = self._match(TokenType.DISTINCT)
10213        if not is_distinct:
10214            self._match(TokenType.ALL)
10215
10216        args = [self._parse_lambda()]
10217        if self._match(TokenType.COMMA):
10218            args.extend(self._parse_function_args())
10219
10220        target = seq_get(args, distinct_index)
10221        if is_distinct and target:
10222            args[distinct_index] = self.expression(exp.Distinct(expressions=[target]))
10223
10224        return func.from_arg_list(args)
10225
10226    def _identifier_expression(
10227        self, token: Token | None = None, quoted: bool | None = None
10228    ) -> exp.Identifier:
10229        token = token or self._prev
10230        return self.expression(exp.Identifier(this=token.text, quoted=quoted), token)
10231
10232    def _build_pipe_cte(
10233        self,
10234        query: exp.Query,
10235        expressions: list[exp.Expr],
10236        alias_cte: exp.TableAlias | None = None,
10237    ) -> exp.Select:
10238        new_cte: str | exp.TableAlias | None
10239        if alias_cte:
10240            new_cte = alias_cte
10241        else:
10242            self._pipe_cte_counter += 1
10243            new_cte = f"__tmp{self._pipe_cte_counter}"
10244
10245        with_ = query.args.get("with_")
10246        ctes = with_.pop() if with_ else None
10247
10248        new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False)
10249        if ctes:
10250            new_select.set("with_", ctes)
10251
10252        return new_select.with_(new_cte, as_=query, copy=False)
10253
10254    def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select:
10255        select = self._parse_select(consume_pipe=False)
10256        if not select:
10257            return query
10258
10259        return self._build_pipe_cte(
10260            query=query.select(*select.expressions, append=False), expressions=[exp.Star()]
10261        )
10262
10263    def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select:
10264        limit = self._parse_limit()
10265        offset = self._parse_offset()
10266        if limit:
10267            curr_limit = query.args.get("limit", limit)
10268            if curr_limit.expression.to_py() >= limit.expression.to_py():
10269                query.limit(limit, copy=False)
10270        if offset:
10271            curr_offset = query.args.get("offset")
10272            curr_offset = curr_offset.expression.to_py() if curr_offset else 0
10273            query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False)
10274
10275        return query
10276
10277    def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None:
10278        this = self._parse_disjunction()
10279        if self._match_text_seq("GROUP", "AND", advance=False):
10280            return this
10281
10282        this = self._parse_alias(this)
10283
10284        if self._match_set((TokenType.ASC, TokenType.DESC), advance=False):
10285            return self._parse_ordered(lambda: this)
10286
10287        return this
10288
10289    def _parse_pipe_syntax_aggregate_group_order_by(
10290        self, query: exp.Select, group_by_exists: bool = True
10291    ) -> exp.Select:
10292        expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields)
10293        aggregates_or_groups, orders = [], []
10294        for element in expr:
10295            if isinstance(element, exp.Ordered):
10296                this = element.this
10297                if isinstance(this, exp.Alias):
10298                    element.set("this", this.args["alias"])
10299                orders.append(element)
10300            else:
10301                this = element
10302            aggregates_or_groups.append(this)
10303
10304        if group_by_exists:
10305            query.select(
10306                *aggregates_or_groups, *query.expressions, append=False, copy=False
10307            ).group_by(
10308                *[projection.args.get("alias", projection) for projection in aggregates_or_groups],
10309                copy=False,
10310            )
10311        else:
10312            query.select(*aggregates_or_groups, append=False, copy=False)
10313
10314        if orders:
10315            return query.order_by(*orders, append=False, copy=False)
10316
10317        return query
10318
10319    def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select:
10320        self._match_text_seq("AGGREGATE")
10321        query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False)
10322
10323        if self._match(TokenType.GROUP_BY) or (
10324            self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY)
10325        ):
10326            query = self._parse_pipe_syntax_aggregate_group_order_by(query)
10327
10328        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10329
10330    def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None:
10331        first_setop = self.parse_set_operation(this=query)
10332        if not first_setop:
10333            return None
10334
10335        def _parse_and_unwrap_query() -> exp.Expr | None:
10336            expr = self._parse_paren()
10337            return expr.assert_is(exp.Subquery).unnest() if expr else None
10338
10339        first_setop.this.pop()
10340
10341        setops = [
10342            first_setop.expression.pop().assert_is(exp.Subquery).unnest(),
10343            *self._parse_csv(_parse_and_unwrap_query),
10344        ]
10345
10346        query = self._build_pipe_cte(query=query, expressions=[exp.Star()])
10347        with_ = query.args.get("with_")
10348        ctes = with_.pop() if with_ else None
10349
10350        if isinstance(first_setop, exp.Union):
10351            query = query.union(*setops, copy=False, **first_setop.args)
10352        elif isinstance(first_setop, exp.Except):
10353            query = query.except_(*setops, copy=False, **first_setop.args)
10354        else:
10355            query = query.intersect(*setops, copy=False, **first_setop.args)
10356
10357        query.set("with_", ctes)
10358
10359        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10360
10361    def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None:
10362        join = self._parse_join()
10363        if not join:
10364            return None
10365
10366        if isinstance(query, exp.Select):
10367            return query.join(join, copy=False)
10368
10369        return query
10370
10371    def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select:
10372        pivots = self._parse_pivots()
10373        if not pivots:
10374            return query
10375
10376        from_ = query.args.get("from_")
10377        if from_:
10378            from_.this.set("pivots", pivots)
10379
10380        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10381
10382    def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select:
10383        self._match_text_seq("EXTEND")
10384        query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False)
10385        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10386
10387    def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select:
10388        sample = self._parse_table_sample()
10389
10390        with_ = query.args.get("with_")
10391        if with_:
10392            with_.expressions[-1].this.set("sample", sample)
10393        else:
10394            query.set("sample", sample)
10395
10396        return query
10397
10398    def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None:
10399        if isinstance(query, exp.Subquery):
10400            query = exp.select("*").from_(query, copy=False)
10401
10402        if not query.args.get("from_"):
10403            query = exp.select("*").from_(query.subquery(copy=False), copy=False)
10404
10405        while self._match(TokenType.PIPE_GT):
10406            start_index = self._index
10407            start_text = self._curr.text.upper()
10408            parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text)
10409            if not parser:
10410                # The set operators (UNION, etc) and the JOIN operator have a few common starting
10411                # keywords, making it tricky to disambiguate them without lookahead. The approach
10412                # here is to try and parse a set operation and if that fails, then try to parse a
10413                # join operator. If that fails as well, then the operator is not supported.
10414                parsed_query = self._parse_pipe_syntax_set_operator(query)
10415                parsed_query = parsed_query or self._parse_pipe_syntax_join(query)
10416                if not parsed_query:
10417                    self._retreat(start_index)
10418                    self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.")
10419                    break
10420                query = parsed_query
10421            else:
10422                query = parser(self, query)
10423
10424        return query
10425
10426    def _parse_declareitem(self) -> exp.DeclareItem | None:
10427        self._match_texts(("VAR", "VARIABLE"))
10428
10429        vars = self._parse_csv(self._parse_id_var)
10430        if not vars:
10431            return None
10432
10433        self._match(TokenType.ALIAS)
10434        kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types()
10435        default = (
10436            self._match(TokenType.DEFAULT) or self._match(TokenType.EQ)
10437        ) and self._parse_bitwise()
10438
10439        return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default))
10440
10441    def _parse_declare(self) -> exp.Declare | exp.Command:
10442        start = self._prev
10443        replace = self._match_text_seq("OR", "REPLACE")
10444        expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem))
10445
10446        if not expressions or self._curr:
10447            return self._parse_as_command(start)
10448
10449        return self.expression(exp.Declare(expressions=expressions, replace=replace))
10450
10451    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10452        exp_class = exp.Cast if strict else exp.TryCast
10453
10454        if exp_class == exp.TryCast:
10455            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10456
10457        return self.expression(exp_class(**kwargs))
10458
10459    def _parse_json_value(self) -> exp.JSONValue:
10460        this = self._parse_bitwise()
10461        self._match(TokenType.COMMA)
10462        path = self._parse_bitwise()
10463
10464        returning = self._match(TokenType.RETURNING) and self._parse_type()
10465
10466        return self.expression(
10467            exp.JSONValue(
10468                this=this,
10469                path=self.dialect.to_json_path(path),
10470                returning=returning,
10471                on_condition=self._parse_on_condition(),
10472            )
10473        )
10474
10475    def _parse_group_concat(self) -> exp.Expr | None:
10476        def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr:
10477            if isinstance(node, exp.Distinct) and len(node.expressions) > 1:
10478                concat_exprs = [
10479                    self.expression(
10480                        exp.Concat(
10481                            expressions=node.expressions,
10482                            safe=True,
10483                            coalesce=self.dialect.CONCAT_COALESCE,
10484                        )
10485                    )
10486                ]
10487                node.set("expressions", concat_exprs)
10488                return node
10489            if len(exprs) == 1:
10490                return exprs[0]
10491            return self.expression(
10492                exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE)
10493            )
10494
10495        args = self._parse_csv(self._parse_lambda)
10496
10497        if args:
10498            order = args[-1] if isinstance(args[-1], exp.Order) else None
10499
10500            if order:
10501                # Order By is the last (or only) expression in the list and has consumed the 'expr' before it,
10502                # remove 'expr' from exp.Order and add it back to args
10503                args[-1] = order.this
10504                order.set("this", concat_exprs(order.this, args))
10505
10506            this = order or concat_exprs(args[0], args)
10507        else:
10508            this = None
10509
10510        separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None
10511
10512        return self.expression(exp.GroupConcat(this=this, separator=separator))
10513
10514    def _parse_initcap(self) -> exp.Initcap:
10515        expr = exp.Initcap.from_arg_list(self._parse_function_args())
10516
10517        # attach dialect's default delimiters
10518        if expr.args.get("expression") is None:
10519            expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS))
10520
10521        return expr
10522
10523    def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None:
10524        if not self._match(TokenType.L_PAREN):
10525            self._retreat(self._index - 1)
10526            return None
10527
10528        op = ""
10529        while self._curr and not self._match(TokenType.R_PAREN):
10530            op += self._curr.text
10531            self._advance()
10532
10533        comments = self._prev_comments
10534        return self.expression(
10535            exp.Operator(this=this, operator=op, expression=self._parse_bitwise()),
10536            comments=comments,
10537        )
logger = <Logger sqlglot (WARNING)>
OPTIONS_TYPE = dict[str, collections.abc.Sequence[typing.Union[collections.abc.Sequence[str], str]]]
TEXTS_TYPE = typing.Union[tuple[str, ...], list[str], typing.AbstractSet[str], typing.Mapping[str, typing.Any]]
TIME_ZONE_RE: re.Pattern[str] = re.compile(':.*?[a-zA-Z\\+\\-]')
def build_var_map( args: Sequence[typing.Any]) -> sqlglot.expressions.array.StarMap | sqlglot.expressions.array.VarMap:
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))
def build_like( args: Sequence[typing.Any]) -> sqlglot.expressions.core.Escape | sqlglot.expressions.core.Like:
64def build_like(args: BuilderArgs) -> exp.Escape | exp.Like:
65    like = exp.Like(this=seq_get(args, 1), expression=seq_get(args, 0))
66    return exp.Escape(this=like, expression=seq_get(args, 2)) if len(args) > 2 else like
def binary_range_parser( expr_type: type[sqlglot.expressions.core.Expr], reverse_args: bool = False) -> Callable[[Parser, sqlglot.expressions.core.Expr | None], sqlglot.expressions.core.Expr | None]:
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
def build_logarithm( args: Sequence[typing.Any], dialect: sqlglot.dialects.Dialect) -> sqlglot.expressions.core.Func:
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)
def build_hex( args: Sequence[typing.Any], dialect: sqlglot.dialects.Dialect) -> sqlglot.expressions.string.Hex | sqlglot.expressions.string.LowerHex:
94def build_hex(args: BuilderArgs, dialect: Dialect) -> exp.Hex | exp.LowerHex:
95    arg = seq_get(args, 0)
96    return exp.LowerHex(this=arg) if dialect.HEX_LOWERCASE else exp.Hex(this=arg)
def build_lower( args: Sequence[typing.Any]) -> sqlglot.expressions.string.Lower | sqlglot.expressions.string.Hex:
 99def build_lower(args: BuilderArgs) -> exp.Lower | exp.Hex:
100    # LOWER(HEX(..)) can be simplified to LowerHex to simplify its transpilation
101    arg = seq_get(args, 0)
102    return exp.LowerHex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Lower(this=arg)
def build_upper( args: Sequence[typing.Any]) -> sqlglot.expressions.string.Upper | sqlglot.expressions.string.Hex:
105def build_upper(args: BuilderArgs) -> exp.Upper | exp.Hex:
106    # UPPER(HEX(..)) can be simplified to Hex to simplify its transpilation
107    arg = seq_get(args, 0)
108    return exp.Hex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Upper(this=arg)
def build_extract_json_with_path( expr_type: type[~E]) -> Callable[[Sequence[Any], sqlglot.dialects.Dialect], ~E]:
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
def build_mod(args: Sequence[typing.Any]) -> sqlglot.expressions.core.Mod:
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)
def build_pad(args: Sequence[typing.Any], is_left: bool = True):
139def build_pad(args: BuilderArgs, is_left: bool = True):
140    return exp.Pad(
141        this=seq_get(args, 0),
142        expression=seq_get(args, 1),
143        fill_pattern=seq_get(args, 2),
144        is_left=is_left,
145    )
def build_array_constructor( exp_class: type[~E], args: list[typing.Any], bracket_kind: sqlglot.tokenizer_core.TokenType, dialect: sqlglot.dialects.Dialect) -> sqlglot.expressions.core.Expr:
148def build_array_constructor(
149    exp_class: Type[E], args: list[t.Any], bracket_kind: TokenType, dialect: Dialect
150) -> exp.Expr:
151    array_exp = exp_class(expressions=args)
152
153    if exp_class == exp.Array and dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS:
154        array_exp.set("bracket_notation", bracket_kind == TokenType.L_BRACKET)
155
156    return array_exp
def build_convert_timezone( args: Sequence[typing.Any], default_source_tz: str | None = None) -> sqlglot.expressions.temporal.ConvertTimezone | sqlglot.expressions.core.Anonymous:
159def build_convert_timezone(
160    args: BuilderArgs, default_source_tz: str | None = None
161) -> exp.ConvertTimezone | exp.Anonymous:
162    if len(args) == 2:
163        source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None
164        return exp.ConvertTimezone(
165            source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1)
166        )
167
168    return exp.ConvertTimezone.from_arg_list(args)
def build_trim( args: Sequence[typing.Any], is_left: bool = True, reverse_args: bool = False) -> sqlglot.expressions.string.Trim:
171def build_trim(args: BuilderArgs, is_left: bool = True, reverse_args: bool = False) -> exp.Trim:
172    this, expression = seq_get(args, 0), seq_get(args, 1)
173
174    if expression and reverse_args:
175        this, expression = expression, this
176
177    return exp.Trim(this=this, expression=expression, position="LEADING" if is_left else "TRAILING")
def build_coalesce( args: Sequence[typing.Any], is_nvl: bool | None = None, is_null: bool | None = None) -> sqlglot.expressions.functions.Coalesce:
180def build_coalesce(
181    args: BuilderArgs, is_nvl: bool | None = None, is_null: bool | None = None
182) -> exp.Coalesce:
183    return exp.Coalesce(this=seq_get(args, 0), expressions=args[1:], is_nvl=is_nvl, is_null=is_null)
def build_locate_strposition(args: Sequence[typing.Any]) -> sqlglot.expressions.string.StrPosition:
186def build_locate_strposition(args: BuilderArgs) -> exp.StrPosition:
187    return exp.StrPosition(
188        this=seq_get(args, 1),
189        substr=seq_get(args, 0),
190        position=seq_get(args, 2),
191    )
def build_array_append( args: Sequence[typing.Any], dialect: sqlglot.dialects.Dialect) -> sqlglot.expressions.array.ArrayAppend:
194def build_array_append(args: BuilderArgs, dialect: Dialect) -> exp.ArrayAppend:
195    """
196    Builds ArrayAppend with NULL propagation semantics based on the dialect configuration.
197
198    Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL.
199    Others (DuckDB, PostgreSQL) create a new single-element array instead.
200
201    Args:
202        args: Function arguments [array, element]
203        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
204
205    Returns:
206        ArrayAppend expression with appropriate null_propagation flag
207    """
208    return exp.ArrayAppend(
209        this=seq_get(args, 0),
210        expression=seq_get(args, 1),
211        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,
212    )

Builds ArrayAppend with NULL propagation semantics based on the dialect configuration.

Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. Others (DuckDB, PostgreSQL) create a new single-element array instead.

Arguments:
  • args: Function arguments [array, element]
  • dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:

ArrayAppend expression with appropriate null_propagation flag

def build_array_prepend( args: Sequence[typing.Any], dialect: sqlglot.dialects.Dialect) -> sqlglot.expressions.array.ArrayPrepend:
215def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend:
216    """
217    Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration.
218
219    Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL.
220    Others (DuckDB, PostgreSQL) create a new single-element array instead.
221
222    Args:
223        args: Function arguments [array, element]
224        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
225
226    Returns:
227        ArrayPrepend expression with appropriate null_propagation flag
228    """
229    return exp.ArrayPrepend(
230        this=seq_get(args, 0),
231        expression=seq_get(args, 1),
232        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,
233    )

Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration.

Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL. Others (DuckDB, PostgreSQL) create a new single-element array instead.

Arguments:
  • args: Function arguments [array, element]
  • dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:

ArrayPrepend expression with appropriate null_propagation flag

def build_array_concat( args: Sequence[typing.Any], dialect: sqlglot.dialects.Dialect) -> sqlglot.expressions.array.ArrayConcat:
236def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat:
237    """
238    Builds ArrayConcat with NULL propagation semantics based on the dialect configuration.
239
240    Some dialects (Redshift, Snowflake) return NULL when any input array is NULL.
241    Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation.
242
243    Args:
244        args: Function arguments [array1, array2, ...] (variadic)
245        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
246
247    Returns:
248        ArrayConcat expression with appropriate null_propagation flag
249    """
250    return exp.ArrayConcat(
251        this=seq_get(args, 0),
252        expressions=args[1:],
253        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,
254    )

Builds ArrayConcat with NULL propagation semantics based on the dialect configuration.

Some dialects (Redshift, Snowflake) return NULL when any input array is NULL. Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation.

Arguments:
  • args: Function arguments [array1, array2, ...] (variadic)
  • dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:

ArrayConcat expression with appropriate null_propagation flag

def build_array_remove( args: Sequence[typing.Any], dialect: sqlglot.dialects.Dialect) -> sqlglot.expressions.array.ArrayRemove:
257def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove:
258    """
259    Builds ArrayRemove with NULL propagation semantics based on the dialect configuration.
260
261    Some dialects (Snowflake) return NULL when the removal value is NULL.
262    Others (DuckDB) may return empty array due to NULL comparison semantics.
263
264    Args:
265        args: Function arguments [array, value_to_remove]
266        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
267
268    Returns:
269        ArrayRemove expression with appropriate null_propagation flag
270    """
271    return exp.ArrayRemove(
272        this=seq_get(args, 0),
273        expression=seq_get(args, 1),
274        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,
275    )

Builds ArrayRemove with NULL propagation semantics based on the dialect configuration.

Some dialects (Snowflake) return NULL when the removal value is NULL. Others (DuckDB) may return empty array due to NULL comparison semantics.

Arguments:
  • args: Function arguments [array, value_to_remove]
  • dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
Returns:

ArrayRemove expression with appropriate null_propagation flag

296def build_json_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONExtract:
297    return self.expression(
298        exp.JSONExtract(
299            this=this,
300            expression=self.dialect.to_json_path(path),
301            only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
302        )
303    )
def build_json_extract_scalar( self: Parser, this: sqlglot.expressions.core.Expr, path: sqlglot.expressions.core.Expr) -> sqlglot.expressions.json.JSONExtractScalar:
306def build_json_extract_scalar(
307    self: Parser, this: exp.Expr, path: exp.Expr
308) -> exp.JSONExtractScalar:
309    return self.expression(
310        exp.JSONExtractScalar(
311            this=this,
312            expression=self.dialect.to_json_path(path),
313            only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
314            scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY,
315        )
316    )
319def build_jsonb_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONBExtract:
320    return self.expression(exp.JSONBExtract(this=this, expression=path))
def build_jsonb_extract_scalar( self: Parser, this: sqlglot.expressions.core.Expr, path: sqlglot.expressions.core.Expr) -> sqlglot.expressions.json.JSONBExtractScalar:
323def build_jsonb_extract_scalar(
324    self: Parser, this: exp.Expr, path: exp.Expr
325) -> exp.JSONBExtractScalar:
326    return self.expression(exp.JSONBExtractScalar(this=this, expression=path))
def build_jsonb_contains_top_key( self: Parser, this: sqlglot.expressions.core.Expr, key: sqlglot.expressions.core.Expr) -> sqlglot.expressions.json.JSONBContainsTopKey:
329def build_jsonb_contains_top_key(
330    self: Parser, this: exp.Expr, key: exp.Expr
331) -> exp.JSONBContainsTopKey:
332    return self.expression(exp.JSONBContainsTopKey(this=this, expression=key))
SENTINEL_NONE: sqlglot.tokenizer_core.Token = <Token token_type: TokenType.SENTINEL, text: SENTINEL, line: 1, col: 1, start: 0, end: 0, comments: []>
class Parser:
  338class Parser:
  339    """
  340    Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.
  341
  342    Args:
  343        error_level: The desired error level.
  344            Default: ErrorLevel.IMMEDIATE
  345        error_message_context: The amount of context to capture from a query string when displaying
  346            the error message (in number of characters).
  347            Default: 100
  348        max_errors: Maximum number of error messages to include in a raised ParseError.
  349            This is only relevant if error_level is ErrorLevel.RAISE.
  350            Default: 3
  351        max_nodes: Maximum number of AST nodes to prevent memory exhaustion.
  352            Set to -1 (default) to disable the check.
  353    """
  354
  355    __slots__ = (
  356        "error_level",
  357        "error_message_context",
  358        "max_errors",
  359        "max_nodes",
  360        "dialect",
  361        "sql",
  362        "errors",
  363        "_tokens",
  364        "_index",
  365        "_curr",
  366        "_next",
  367        "_prev",
  368        "_prev_comments",
  369        "_pipe_cte_counter",
  370        "_chunks",
  371        "_chunk_index",
  372        "_tokens_size",
  373        "_node_count",
  374    )
  375
  376    FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = {
  377        **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()},
  378        **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce),
  379        "ARRAY": lambda args, dialect: exp.Array(expressions=args),
  380        "ARRAYAGG": lambda args, dialect: exp.ArrayAgg(
  381            this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None
  382        ),
  383        "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg(
  384            this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None
  385        ),
  386        "ARRAY_APPEND": build_array_append,
  387        "ARRAY_CAT": build_array_concat,
  388        "ARRAY_CONCAT": build_array_concat,
  389        "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args),
  390        "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args),
  391        "ARRAY_PREPEND": build_array_prepend,
  392        "ARRAY_REMOVE": build_array_remove,
  393        "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True),
  394        "CONCAT": lambda args, dialect: exp.Concat(
  395            expressions=args,
  396            safe=not dialect.STRICT_STRING_CONCAT,
  397            coalesce=dialect.CONCAT_COALESCE,
  398        ),
  399        "CONCAT_WS": lambda args, dialect: exp.ConcatWs(
  400            expressions=args,
  401            safe=not dialect.STRICT_STRING_CONCAT,
  402            coalesce=dialect.CONCAT_WS_COALESCE,
  403        ),
  404        "CONVERT_TIMEZONE": build_convert_timezone,
  405        "DATE_TO_DATE_STR": lambda args: exp.Cast(
  406            this=seq_get(args, 0),
  407            to=exp.DataType(this=exp.DType.TEXT),
  408        ),
  409        "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray(
  410            start=seq_get(args, 0),
  411            end=seq_get(args, 1),
  412            step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")),
  413        ),
  414        "GENERATE_UUID": lambda args, dialect: exp.Uuid(
  415            is_string=dialect.UUID_IS_STRING_TYPE or None
  416        ),
  417        "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)),
  418        "GREATEST": lambda args, dialect: exp.Greatest(
  419            this=seq_get(args, 0),
  420            expressions=args[1:],
  421            ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS,
  422        ),
  423        "LEAST": lambda args, dialect: exp.Least(
  424            this=seq_get(args, 0),
  425            expressions=args[1:],
  426            ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS,
  427        ),
  428        "HEX": build_hex,
  429        "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract),
  430        "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar),
  431        "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar),
  432        "JSON_KEYS": lambda args, dialect: exp.JSONKeys(
  433            this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1))
  434        ),
  435        "LIKE": build_like,
  436        "LOG": build_logarithm,
  437        "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)),
  438        "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)),
  439        "LOWER": build_lower,
  440        "LPAD": lambda args: build_pad(args),
  441        "LEFTPAD": lambda args: build_pad(args),
  442        "LTRIM": lambda args: build_trim(args),
  443        "MOD": build_mod,
  444        "RIGHTPAD": lambda args: build_pad(args, is_left=False),
  445        "RPAD": lambda args: build_pad(args, is_left=False),
  446        "RTRIM": lambda args: build_trim(args, is_left=False),
  447        "SCOPE_RESOLUTION": lambda args: (
  448            exp.ScopeResolution(expression=seq_get(args, 0))
  449            if len(args) != 2
  450            else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1))
  451        ),
  452        "STRPOS": exp.StrPosition.from_arg_list,
  453        "CHARINDEX": lambda args: build_locate_strposition(args),
  454        "INSTR": exp.StrPosition.from_arg_list,
  455        "LOCATE": lambda args: build_locate_strposition(args),
  456        "TIME_TO_TIME_STR": lambda args: exp.Cast(
  457            this=seq_get(args, 0),
  458            to=exp.DataType(this=exp.DType.TEXT),
  459        ),
  460        "TO_HEX": build_hex,
  461        "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring(
  462            this=exp.Cast(
  463                this=seq_get(args, 0),
  464                to=exp.DataType(this=exp.DType.TEXT),
  465            ),
  466            start=exp.Literal.number(1),
  467            length=exp.Literal.number(10),
  468        ),
  469        "UNNEST": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))),
  470        "UPPER": build_upper,
  471        "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None),
  472        "UUID_STRING": lambda args, dialect: exp.Uuid(
  473            this=seq_get(args, 0),
  474            name=seq_get(args, 1),
  475            is_string=dialect.UUID_IS_STRING_TYPE or None,
  476        ),
  477        "VAR_MAP": build_var_map,
  478    }
  479
  480    NO_PAREN_FUNCTIONS: t.ClassVar[dict] = {
  481        TokenType.CURRENT_DATE: exp.CurrentDate,
  482        TokenType.CURRENT_DATETIME: exp.CurrentDate,
  483        TokenType.CURRENT_TIME: exp.CurrentTime,
  484        TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp,
  485        TokenType.CURRENT_USER: exp.CurrentUser,
  486        TokenType.CURRENT_ROLE: exp.CurrentRole,
  487    }
  488
  489    STRUCT_TYPE_TOKENS: t.ClassVar = {
  490        TokenType.NESTED,
  491        TokenType.OBJECT,
  492        TokenType.STRUCT,
  493        TokenType.UNION,
  494    }
  495
  496    NESTED_TYPE_TOKENS: t.ClassVar = {
  497        TokenType.ARRAY,
  498        TokenType.LIST,
  499        TokenType.LOWCARDINALITY,
  500        TokenType.MAP,
  501        TokenType.NULLABLE,
  502        TokenType.RANGE,
  503        *STRUCT_TYPE_TOKENS,
  504    }
  505
  506    ENUM_TYPE_TOKENS: t.ClassVar = {
  507        TokenType.DYNAMIC,
  508        TokenType.ENUM,
  509        TokenType.ENUM8,
  510        TokenType.ENUM16,
  511    }
  512
  513    AGGREGATE_TYPE_TOKENS: t.ClassVar = {
  514        TokenType.AGGREGATEFUNCTION,
  515        TokenType.SIMPLEAGGREGATEFUNCTION,
  516    }
  517
  518    TYPE_TOKENS: t.ClassVar = {
  519        TokenType.BIT,
  520        TokenType.BOOLEAN,
  521        TokenType.TINYINT,
  522        TokenType.UTINYINT,
  523        TokenType.SMALLINT,
  524        TokenType.USMALLINT,
  525        TokenType.INT,
  526        TokenType.UINT,
  527        TokenType.BIGINT,
  528        TokenType.UBIGINT,
  529        TokenType.BIGNUM,
  530        TokenType.INT128,
  531        TokenType.UINT128,
  532        TokenType.INT256,
  533        TokenType.UINT256,
  534        TokenType.MEDIUMINT,
  535        TokenType.UMEDIUMINT,
  536        TokenType.FIXEDSTRING,
  537        TokenType.FLOAT,
  538        TokenType.DOUBLE,
  539        TokenType.UDOUBLE,
  540        TokenType.CHAR,
  541        TokenType.NCHAR,
  542        TokenType.VARCHAR,
  543        TokenType.NVARCHAR,
  544        TokenType.BPCHAR,
  545        TokenType.TEXT,
  546        TokenType.MEDIUMTEXT,
  547        TokenType.LONGTEXT,
  548        TokenType.BLOB,
  549        TokenType.MEDIUMBLOB,
  550        TokenType.LONGBLOB,
  551        TokenType.BINARY,
  552        TokenType.VARBINARY,
  553        TokenType.JSON,
  554        TokenType.JSONB,
  555        TokenType.INTERVAL,
  556        TokenType.TINYBLOB,
  557        TokenType.TINYTEXT,
  558        TokenType.TIME,
  559        TokenType.TIMETZ,
  560        TokenType.TIME_NS,
  561        TokenType.TIMESTAMP,
  562        TokenType.TIMESTAMP_S,
  563        TokenType.TIMESTAMP_MS,
  564        TokenType.TIMESTAMP_NS,
  565        TokenType.TIMESTAMPTZ,
  566        TokenType.TIMESTAMPLTZ,
  567        TokenType.TIMESTAMPNTZ,
  568        TokenType.DATETIME,
  569        TokenType.DATETIME2,
  570        TokenType.DATETIME64,
  571        TokenType.SMALLDATETIME,
  572        TokenType.DATE,
  573        TokenType.DATE32,
  574        TokenType.INT4RANGE,
  575        TokenType.INT4MULTIRANGE,
  576        TokenType.INT8RANGE,
  577        TokenType.INT8MULTIRANGE,
  578        TokenType.NUMRANGE,
  579        TokenType.NUMMULTIRANGE,
  580        TokenType.TSRANGE,
  581        TokenType.TSMULTIRANGE,
  582        TokenType.TSTZRANGE,
  583        TokenType.TSTZMULTIRANGE,
  584        TokenType.DATERANGE,
  585        TokenType.DATEMULTIRANGE,
  586        TokenType.DECIMAL,
  587        TokenType.DECIMAL32,
  588        TokenType.DECIMAL64,
  589        TokenType.DECIMAL128,
  590        TokenType.DECIMAL256,
  591        TokenType.DECFLOAT,
  592        TokenType.UDECIMAL,
  593        TokenType.BIGDECIMAL,
  594        TokenType.UUID,
  595        TokenType.GEOGRAPHY,
  596        TokenType.GEOGRAPHYPOINT,
  597        TokenType.GEOMETRY,
  598        TokenType.POINT,
  599        TokenType.RING,
  600        TokenType.LINESTRING,
  601        TokenType.MULTILINESTRING,
  602        TokenType.POLYGON,
  603        TokenType.MULTIPOLYGON,
  604        TokenType.HLLSKETCH,
  605        TokenType.HSTORE,
  606        TokenType.PSEUDO_TYPE,
  607        TokenType.SUPER,
  608        TokenType.SERIAL,
  609        TokenType.SMALLSERIAL,
  610        TokenType.BIGSERIAL,
  611        TokenType.XML,
  612        TokenType.YEAR,
  613        TokenType.USERDEFINED,
  614        TokenType.MONEY,
  615        TokenType.SMALLMONEY,
  616        TokenType.ROWVERSION,
  617        TokenType.IMAGE,
  618        TokenType.VARIANT,
  619        TokenType.VECTOR,
  620        TokenType.VOID,
  621        TokenType.OBJECT,
  622        TokenType.OBJECT_IDENTIFIER,
  623        TokenType.INET,
  624        TokenType.IPADDRESS,
  625        TokenType.IPPREFIX,
  626        TokenType.IPV4,
  627        TokenType.IPV6,
  628        TokenType.UNKNOWN,
  629        TokenType.NOTHING,
  630        TokenType.NULL,
  631        TokenType.NAME,
  632        TokenType.TDIGEST,
  633        TokenType.DYNAMIC,
  634        *ENUM_TYPE_TOKENS,
  635        *NESTED_TYPE_TOKENS,
  636        *AGGREGATE_TYPE_TOKENS,
  637    }
  638
  639    SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = {
  640        TokenType.BIGINT: TokenType.UBIGINT,
  641        TokenType.INT: TokenType.UINT,
  642        TokenType.MEDIUMINT: TokenType.UMEDIUMINT,
  643        TokenType.SMALLINT: TokenType.USMALLINT,
  644        TokenType.TINYINT: TokenType.UTINYINT,
  645        TokenType.DECIMAL: TokenType.UDECIMAL,
  646        TokenType.DOUBLE: TokenType.UDOUBLE,
  647    }
  648
  649    SUBQUERY_PREDICATES: t.ClassVar = {
  650        TokenType.ANY: exp.Any,
  651        TokenType.ALL: exp.All,
  652        TokenType.EXISTS: exp.Exists,
  653        TokenType.SOME: exp.Any,
  654    }
  655
  656    SUBQUERY_TOKENS: t.ClassVar = {
  657        TokenType.SELECT,
  658        TokenType.WITH,
  659        TokenType.FROM,
  660    }
  661
  662    RESERVED_TOKENS: t.ClassVar = {
  663        *Tokenizer.SINGLE_TOKENS.values(),
  664        TokenType.SELECT,
  665    } - {TokenType.IDENTIFIER}
  666
  667    # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers,
  668    # string literals), so they must never be treated as keywords when matching by text
  669    TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset(
  670        {
  671            TokenType.BIT_STRING,
  672            TokenType.BYTE_STRING,
  673            TokenType.HEREDOC_STRING,
  674            TokenType.HEX_STRING,
  675            TokenType.IDENTIFIER,
  676            TokenType.NATIONAL_STRING,
  677            TokenType.RAW_STRING,
  678            TokenType.STRING,
  679            TokenType.UNICODE_STRING,
  680        }
  681    )
  682
  683    DB_CREATABLES: t.ClassVar = {
  684        TokenType.DATABASE,
  685        TokenType.DICTIONARY,
  686        TokenType.FILE_FORMAT,
  687        TokenType.MODEL,
  688        TokenType.NAMESPACE,
  689        TokenType.SCHEMA,
  690        TokenType.SEMANTIC_VIEW,
  691        TokenType.SEQUENCE,
  692        TokenType.SINK,
  693        TokenType.SOURCE,
  694        TokenType.STAGE,
  695        TokenType.STORAGE_INTEGRATION,
  696        TokenType.STREAMLIT,
  697        TokenType.TABLE,
  698        TokenType.TAG,
  699        TokenType.VIEW,
  700        TokenType.WAREHOUSE,
  701    }
  702
  703    CREATABLES: t.ClassVar = {
  704        TokenType.COLUMN,
  705        TokenType.CONSTRAINT,
  706        TokenType.FOREIGN_KEY,
  707        TokenType.FUNCTION,
  708        TokenType.INDEX,
  709        TokenType.PROCEDURE,
  710        TokenType.TRIGGER,
  711        TokenType.TYPE,
  712        *DB_CREATABLES,
  713    }
  714
  715    TRIGGER_EVENTS: t.ClassVar = {
  716        TokenType.INSERT,
  717        TokenType.UPDATE,
  718        TokenType.DELETE,
  719        TokenType.TRUNCATE,
  720    }
  721
  722    ALTERABLES: t.ClassVar = {
  723        TokenType.INDEX,
  724        TokenType.TABLE,
  725        TokenType.VIEW,
  726        TokenType.SESSION,
  727    }
  728
  729    # Tokens that can represent identifiers
  730    ID_VAR_TOKENS: t.ClassVar[set] = {
  731        TokenType.ALL,
  732        TokenType.ANALYZE,
  733        TokenType.ATTACH,
  734        TokenType.VAR,
  735        TokenType.ANTI,
  736        TokenType.APPLY,
  737        TokenType.ASC,
  738        TokenType.ASOF,
  739        TokenType.AUTO_INCREMENT,
  740        TokenType.BEGIN,
  741        TokenType.BPCHAR,
  742        TokenType.CACHE,
  743        TokenType.CASE,
  744        TokenType.COLLATE,
  745        TokenType.COMMAND,
  746        TokenType.COMMENT,
  747        TokenType.COMMIT,
  748        TokenType.CONSTRAINT,
  749        TokenType.COPY,
  750        TokenType.CUBE,
  751        TokenType.CURRENT_SCHEMA,
  752        TokenType.DECLARE,
  753        TokenType.DEFAULT,
  754        TokenType.DELETE,
  755        TokenType.DESC,
  756        TokenType.DESCRIBE,
  757        TokenType.DETACH,
  758        TokenType.DICTIONARY,
  759        TokenType.DIV,
  760        TokenType.END,
  761        TokenType.EXECUTE,
  762        TokenType.EXPORT,
  763        TokenType.ESCAPE,
  764        TokenType.FALSE,
  765        TokenType.FIRST,
  766        TokenType.FILE,
  767        TokenType.FILTER,
  768        TokenType.FINAL,
  769        TokenType.FORMAT,
  770        TokenType.FULL,
  771        TokenType.GET,
  772        TokenType.IDENTIFIER,
  773        TokenType.INOUT,
  774        TokenType.IS,
  775        TokenType.ISNULL,
  776        TokenType.INTERVAL,
  777        TokenType.KEEP,
  778        TokenType.KILL,
  779        TokenType.LEFT,
  780        TokenType.LIMIT,
  781        TokenType.LOAD,
  782        TokenType.LOCK,
  783        TokenType.MATCH,
  784        TokenType.MERGE,
  785        TokenType.NATURAL,
  786        TokenType.NEXT,
  787        TokenType.OFFSET,
  788        TokenType.OPERATOR,
  789        TokenType.ORDINALITY,
  790        TokenType.OUT,
  791        TokenType.OVER,
  792        TokenType.OVERLAPS,
  793        TokenType.OVERWRITE,
  794        TokenType.PARTITION,
  795        TokenType.PERCENT,
  796        TokenType.PIVOT,
  797        TokenType.PROJECTION,
  798        TokenType.PRAGMA,
  799        TokenType.PUT,
  800        TokenType.RANGE,
  801        TokenType.RECURSIVE,
  802        TokenType.REFERENCES,
  803        TokenType.REFRESH,
  804        TokenType.RENAME,
  805        TokenType.REPLACE,
  806        TokenType.RIGHT,
  807        TokenType.ROLLUP,
  808        TokenType.ROW,
  809        TokenType.ROWS,
  810        TokenType.SEMI,
  811        TokenType.SET,
  812        TokenType.SETTINGS,
  813        TokenType.SHOW,
  814        TokenType.STREAM,
  815        TokenType.STREAMLIT,
  816        TokenType.TEMPORARY,
  817        TokenType.TOP,
  818        TokenType.TRUE,
  819        TokenType.TRUNCATE,
  820        TokenType.UNIQUE,
  821        TokenType.UNNEST,
  822        TokenType.UNPIVOT,
  823        TokenType.UPDATE,
  824        TokenType.USE,
  825        TokenType.VOLATILE,
  826        TokenType.WINDOW,
  827        TokenType.CURRENT_CATALOG,
  828        TokenType.LOCALTIME,
  829        TokenType.LOCALTIMESTAMP,
  830        TokenType.SESSION_USER,
  831        TokenType.STRAIGHT_JOIN,
  832        *ALTERABLES,
  833        *CREATABLES,
  834        *SUBQUERY_PREDICATES,
  835        *TYPE_TOKENS,
  836        *NO_PAREN_FUNCTIONS,
  837    } - {TokenType.UNION}
  838
  839    TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - {
  840        TokenType.ANTI,
  841        TokenType.ASOF,
  842        TokenType.FULL,
  843        TokenType.LEFT,
  844        TokenType.LOCK,
  845        TokenType.NATURAL,
  846        TokenType.RIGHT,
  847        TokenType.SEMI,
  848        TokenType.WINDOW,
  849    }
  850
  851    ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS
  852
  853    COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS
  854
  855    ARRAY_CONSTRUCTORS: t.ClassVar = {
  856        "ARRAY": exp.Array,
  857        "LIST": exp.List,
  858    }
  859
  860    COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS}
  861
  862    UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET}
  863
  864    TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"}
  865
  866    # Tokens that indicate a simple column reference
  867    IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER})
  868
  869    BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE})
  870
  871    # Postfix tokens that prevent the bare column fast path
  872    COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  873        {
  874            TokenType.L_PAREN,
  875            TokenType.L_BRACKET,
  876            TokenType.L_BRACE,
  877            TokenType.COLON,
  878            TokenType.JOIN_MARKER,
  879        }
  880    )
  881
  882    TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  883        {
  884            TokenType.L_PAREN,
  885            TokenType.L_BRACKET,
  886            TokenType.L_BRACE,
  887            TokenType.PIVOT,
  888            TokenType.UNPIVOT,
  889            TokenType.TABLE_SAMPLE,
  890        }
  891    )
  892
  893    FUNC_TOKENS: t.ClassVar = {
  894        TokenType.COLLATE,
  895        TokenType.COMMAND,
  896        TokenType.CURRENT_DATE,
  897        TokenType.CURRENT_DATETIME,
  898        TokenType.CURRENT_SCHEMA,
  899        TokenType.CURRENT_TIMESTAMP,
  900        TokenType.CURRENT_TIME,
  901        TokenType.CURRENT_USER,
  902        TokenType.CURRENT_CATALOG,
  903        TokenType.DECLARE,
  904        TokenType.FILTER,
  905        TokenType.FIRST,
  906        TokenType.FORMAT,
  907        TokenType.GET,
  908        TokenType.GLOB,
  909        TokenType.IDENTIFIER,
  910        TokenType.INDEX,
  911        TokenType.ISNULL,
  912        TokenType.ILIKE,
  913        TokenType.INSERT,
  914        TokenType.LIKE,
  915        TokenType.LOCALTIME,
  916        TokenType.LOCALTIMESTAMP,
  917        TokenType.MERGE,
  918        TokenType.NEXT,
  919        TokenType.OFFSET,
  920        TokenType.PRIMARY_KEY,
  921        TokenType.RANGE,
  922        TokenType.REPLACE,
  923        TokenType.RLIKE,
  924        TokenType.ROW,
  925        TokenType.SESSION_USER,
  926        TokenType.UNNEST,
  927        TokenType.VAR,
  928        TokenType.LEFT,
  929        TokenType.RIGHT,
  930        TokenType.SEQUENCE,
  931        TokenType.DATE,
  932        TokenType.DATETIME,
  933        TokenType.TABLE,
  934        TokenType.TIMESTAMP,
  935        TokenType.TIMESTAMPTZ,
  936        TokenType.TRUNCATE,
  937        TokenType.UTC_DATE,
  938        TokenType.UTC_TIME,
  939        TokenType.UTC_TIMESTAMP,
  940        TokenType.WINDOW,
  941        TokenType.XOR,
  942        *TYPE_TOKENS,
  943        *SUBQUERY_PREDICATES,
  944    }
  945
  946    CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  947        TokenType.AND: exp.And,
  948    }
  949
  950    ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  951        TokenType.COLON_EQ: exp.PropertyEQ,
  952    }
  953
  954    DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  955        TokenType.OR: exp.Or,
  956    }
  957
  958    EQUALITY: t.ClassVar = {
  959        TokenType.EQ: exp.EQ,
  960        TokenType.NEQ: exp.NEQ,
  961        TokenType.NULLSAFE_EQ: exp.NullSafeEQ,
  962    }
  963
  964    COMPARISON: t.ClassVar = {
  965        TokenType.GT: exp.GT,
  966        TokenType.GTE: exp.GTE,
  967        TokenType.LT: exp.LT,
  968        TokenType.LTE: exp.LTE,
  969    }
  970
  971    BITWISE: t.ClassVar = {
  972        TokenType.AMP: exp.BitwiseAnd,
  973        TokenType.CARET: exp.BitwiseXor,
  974        TokenType.PIPE: exp.BitwiseOr,
  975    }
  976
  977    TERM: t.ClassVar = {
  978        TokenType.DASH: exp.Sub,
  979        TokenType.PLUS: exp.Add,
  980        TokenType.COLLATE: exp.Collate,
  981    }
  982
  983    FACTOR: t.ClassVar = {
  984        TokenType.DIV: exp.IntDiv,
  985        TokenType.LR_ARROW: exp.Distance,
  986        TokenType.LLRR_ARROW: exp.DistanceNd,
  987        TokenType.MOD: exp.Mod,
  988        TokenType.SLASH: exp.Div,
  989        TokenType.STAR: exp.Mul,
  990    }
  991
  992    EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {}
  993
  994    TIMES: t.ClassVar = {
  995        TokenType.TIME,
  996        TokenType.TIMETZ,
  997    }
  998
  999    TIMESTAMPS: t.ClassVar = {
 1000        TokenType.TIMESTAMP,
 1001        TokenType.TIMESTAMPNTZ,
 1002        TokenType.TIMESTAMPTZ,
 1003        TokenType.TIMESTAMPLTZ,
 1004        *TIMES,
 1005    }
 1006
 1007    SET_OPERATIONS: t.ClassVar = {
 1008        TokenType.UNION,
 1009        TokenType.INTERSECT,
 1010        TokenType.EXCEPT,
 1011    }
 1012
 1013    JOIN_METHODS: t.ClassVar = {
 1014        TokenType.ASOF,
 1015        TokenType.NATURAL,
 1016        TokenType.POSITIONAL,
 1017    }
 1018
 1019    JOIN_SIDES: t.ClassVar = {
 1020        TokenType.LEFT,
 1021        TokenType.RIGHT,
 1022        TokenType.FULL,
 1023    }
 1024
 1025    JOIN_KINDS: t.ClassVar = {
 1026        TokenType.ANTI,
 1027        TokenType.CROSS,
 1028        TokenType.INNER,
 1029        TokenType.OUTER,
 1030        TokenType.SEMI,
 1031        TokenType.STRAIGHT_JOIN,
 1032    }
 1033
 1034    JOIN_HINTS: t.ClassVar[set[str]] = set()
 1035
 1036    # Tokens that unambiguously end a table reference on the fast path
 1037    TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset(
 1038        {
 1039            TokenType.COMMA,
 1040            TokenType.GROUP_BY,
 1041            TokenType.HAVING,
 1042            TokenType.JOIN,
 1043            TokenType.LIMIT,
 1044            TokenType.ON,
 1045            TokenType.ORDER_BY,
 1046            TokenType.R_PAREN,
 1047            TokenType.SEMICOLON,
 1048            TokenType.SENTINEL,
 1049            TokenType.WHERE,
 1050            *SET_OPERATIONS,
 1051            *JOIN_KINDS,
 1052            *JOIN_METHODS,
 1053            *JOIN_SIDES,
 1054        }
 1055    )
 1056
 1057    LAMBDAS: t.ClassVar = {
 1058        TokenType.ARROW: lambda self, expressions: self.expression(
 1059            exp.Lambda(
 1060                this=self._replace_lambda(
 1061                    self._parse_disjunction(),
 1062                    expressions,
 1063                ),
 1064                expressions=expressions,
 1065            )
 1066        ),
 1067        TokenType.FARROW: lambda self, expressions: self.expression(
 1068            exp.Kwarg(
 1069                this=exp.var(expressions[0].name),
 1070                expression=self._parse_disjunction() or self._parse_select(),
 1071            )
 1072        ),
 1073    }
 1074
 1075    # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake
 1076    TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False
 1077
 1078    LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN})
 1079
 1080    COLUMN_OPERATORS: t.ClassVar = {
 1081        TokenType.DOT: None,
 1082        TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)),
 1083        TokenType.DCOLON: lambda self, this, to: self.build_cast(
 1084            strict=self.STRICT_CAST, this=this, to=to
 1085        ),
 1086        TokenType.ARROW: lambda self, this, path: self.expression(
 1087            exp.JSONExtract(
 1088                this=this,
 1089                expression=self.dialect.to_json_path(path),
 1090                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1091            )
 1092        ),
 1093        TokenType.DARROW: lambda self, this, path: self.expression(
 1094            exp.JSONExtractScalar(
 1095                this=this,
 1096                expression=self.dialect.to_json_path(path),
 1097                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1098                scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY,
 1099            )
 1100        ),
 1101        TokenType.HASH_ARROW: lambda self, this, path: self.expression(
 1102            exp.JSONBExtract(this=this, expression=path)
 1103        ),
 1104        TokenType.DHASH_ARROW: lambda self, this, path: self.expression(
 1105            exp.JSONBExtractScalar(this=this, expression=path)
 1106        ),
 1107        TokenType.PLACEHOLDER: lambda self, this, key: self.expression(
 1108            exp.JSONBContainsTopKey(this=this, expression=key)
 1109        ),
 1110    }
 1111
 1112    # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator"
 1113    # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs).
 1114    JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {}
 1115
 1116    CAST_COLUMN_OPERATORS: t.ClassVar = {
 1117        TokenType.DOTCOLON,
 1118        TokenType.DCOLON,
 1119    }
 1120
 1121    EXPRESSION_PARSERS: t.ClassVar = {
 1122        exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY),
 1123        exp.Column: lambda self: self._parse_column(),
 1124        exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()),
 1125        exp.Condition: lambda self: self._parse_disjunction(),
 1126        exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True),
 1127        exp.Expr: lambda self: self._parse_expression(),
 1128        exp.From: lambda self: self._parse_from(joins=True),
 1129        exp.GrantPrincipal: lambda self: self._parse_grant_principal(),
 1130        exp.GrantPrivilege: lambda self: self._parse_grant_privilege(),
 1131        exp.Group: lambda self: self._parse_group(),
 1132        exp.Having: lambda self: self._parse_having(),
 1133        exp.Hint: lambda self: self._parse_hint_body(),
 1134        exp.Identifier: lambda self: self._parse_id_var(),
 1135        exp.Join: lambda self: self._parse_join(),
 1136        exp.Lambda: lambda self: self._parse_lambda(),
 1137        exp.Lateral: lambda self: self._parse_lateral(),
 1138        exp.Limit: lambda self: self._parse_limit(),
 1139        exp.Offset: lambda self: self._parse_offset(),
 1140        exp.Order: lambda self: self._parse_order(),
 1141        exp.Ordered: lambda self: self._parse_ordered(),
 1142        exp.Properties: lambda self: self._parse_properties(),
 1143        exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(),
 1144        exp.Qualify: lambda self: self._parse_qualify(),
 1145        exp.Returning: lambda self: self._parse_returning(),
 1146        exp.Select: lambda self: self._parse_select(),
 1147        exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY),
 1148        exp.Table: lambda self: self._parse_table_parts(),
 1149        exp.TableAlias: lambda self: self._parse_table_alias(),
 1150        exp.Tuple: lambda self: self._parse_value(values=False),
 1151        exp.Whens: lambda self: self._parse_when_matched(),
 1152        exp.Where: lambda self: self._parse_where(),
 1153        exp.Window: lambda self: self._parse_named_window(),
 1154        exp.With: lambda self: self._parse_with(),
 1155    }
 1156
 1157    STATEMENT_PARSERS: t.ClassVar = {
 1158        TokenType.ALTER: lambda self: self._parse_alter(),
 1159        TokenType.ANALYZE: lambda self: self._parse_analyze(),
 1160        TokenType.BEGIN: lambda self: self._parse_transaction(),
 1161        TokenType.CACHE: lambda self: self._parse_cache(),
 1162        TokenType.COMMENT: lambda self: self._parse_comment(),
 1163        TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(),
 1164        TokenType.COPY: lambda self: self._parse_copy(),
 1165        TokenType.CREATE: lambda self: self._parse_create(),
 1166        TokenType.DECLARE: lambda self: self._parse_declare(),
 1167        TokenType.DELETE: lambda self: self._parse_delete(),
 1168        TokenType.DESC: lambda self: self._parse_describe(),
 1169        TokenType.DESCRIBE: lambda self: self._parse_describe(),
 1170        TokenType.DROP: lambda self: self._parse_drop(),
 1171        TokenType.GRANT: lambda self: self._parse_grant(),
 1172        TokenType.REVOKE: lambda self: self._parse_revoke(),
 1173        TokenType.INSERT: lambda self: self._parse_insert(),
 1174        TokenType.KILL: lambda self: self._parse_kill(),
 1175        TokenType.LOAD: lambda self: self._parse_load(),
 1176        TokenType.MERGE: lambda self: self._parse_merge(),
 1177        TokenType.PIVOT: lambda self: self._parse_simplified_pivot(),
 1178        TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())),
 1179        TokenType.REFRESH: lambda self: self._parse_refresh(),
 1180        TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(),
 1181        TokenType.SET: lambda self: self._parse_set(),
 1182        TokenType.TRUNCATE: lambda self: self._parse_truncate_table(),
 1183        TokenType.UNCACHE: lambda self: self._parse_uncache(),
 1184        TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True),
 1185        TokenType.UPDATE: lambda self: self._parse_update(),
 1186        TokenType.USE: lambda self: self._parse_use(),
 1187        TokenType.SEMICOLON: lambda self: exp.Semicolon(),
 1188    }
 1189
 1190    UNARY_PARSERS: t.ClassVar = {
 1191        TokenType.PLUS: lambda self: self._parse_unary(),  # Unary + is handled as a no-op
 1192        TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())),
 1193        TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())),
 1194        TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())),
 1195        TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())),
 1196        TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())),
 1197    }
 1198
 1199    STRING_PARSERS: t.ClassVar = {
 1200        TokenType.HEREDOC_STRING: lambda self, token: self.expression(
 1201            exp.RawString(this=token.text), token
 1202        ),
 1203        TokenType.NATIONAL_STRING: lambda self, token: self.expression(
 1204            exp.National(this=token.text), token
 1205        ),
 1206        TokenType.RAW_STRING: lambda self, token: self.expression(
 1207            exp.RawString(this=token.text), token
 1208        ),
 1209        TokenType.STRING: lambda self, token: self.expression(
 1210            exp.Literal(this=token.text, is_string=True), token
 1211        ),
 1212        TokenType.UNICODE_STRING: lambda self, token: self.expression(
 1213            exp.UnicodeString(
 1214                this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string()
 1215            ),
 1216            token,
 1217        ),
 1218    }
 1219
 1220    NUMERIC_PARSERS: t.ClassVar = {
 1221        TokenType.BIT_STRING: lambda self, token: self.expression(
 1222            exp.BitString(this=token.text), token
 1223        ),
 1224        TokenType.BYTE_STRING: lambda self, token: self.expression(
 1225            exp.ByteString(
 1226                this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None
 1227            ),
 1228            token,
 1229        ),
 1230        TokenType.HEX_STRING: lambda self, token: self.expression(
 1231            exp.HexString(
 1232                this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None
 1233            ),
 1234            token,
 1235        ),
 1236        TokenType.NUMBER: lambda self, token: self.expression(
 1237            exp.Literal(this=token.text, is_string=False), token
 1238        ),
 1239    }
 1240
 1241    PRIMARY_PARSERS: t.ClassVar = {
 1242        **STRING_PARSERS,
 1243        **NUMERIC_PARSERS,
 1244        TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token),
 1245        TokenType.NULL: lambda self, _: self.expression(exp.Null()),
 1246        TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)),
 1247        TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)),
 1248        TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(),
 1249        TokenType.STAR: lambda self, _: self._parse_star_ops(),
 1250    }
 1251
 1252    PLACEHOLDER_PARSERS: t.ClassVar = {
 1253        TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()),
 1254        TokenType.PARAMETER: lambda self: self._parse_parameter(),
 1255        TokenType.COLON: lambda self: (
 1256            self.expression(exp.Placeholder(this=self._prev.text))
 1257            if self._match_set(self.COLON_PLACEHOLDER_TOKENS)
 1258            else None
 1259        ),
 1260    }
 1261
 1262    RANGE_PARSERS: t.ClassVar = {
 1263        TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll),
 1264        TokenType.BETWEEN: lambda self, this: self._parse_between(this),
 1265        TokenType.GLOB: binary_range_parser(exp.Glob),
 1266        TokenType.ILIKE: binary_range_parser(exp.ILike),
 1267        TokenType.IN: lambda self, this: self._parse_in(this),
 1268        TokenType.IRLIKE: binary_range_parser(exp.RegexpILike),
 1269        TokenType.IS: lambda self, this: self._parse_is(this),
 1270        TokenType.LIKE: binary_range_parser(exp.Like),
 1271        TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy),
 1272        TokenType.OVERLAPS: binary_range_parser(exp.Overlaps),
 1273        TokenType.RLIKE: binary_range_parser(exp.RegexpLike),
 1274        TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo),
 1275        TokenType.FOR: lambda self, this: self._parse_comprehension(this),
 1276        TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys),
 1277        TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys),
 1278        TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath),
 1279        TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists),
 1280        TokenType.ADJACENT: binary_range_parser(exp.Adjacent),
 1281        TokenType.OPERATOR: lambda self, this: self._parse_operator(this),
 1282        TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft),
 1283        TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight),
 1284    }
 1285
 1286    PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = {
 1287        "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query),
 1288        "AS": lambda self, query: self._build_pipe_cte(
 1289            query, [exp.Star()], self._parse_table_alias()
 1290        ),
 1291        "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False),
 1292        "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query),
 1293        "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query),
 1294        "ORDER BY": lambda self, query: query.order_by(
 1295            self._parse_order(), append=False, copy=False
 1296        ),
 1297        "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1298        "SELECT": lambda self, query: self._parse_pipe_syntax_select(query),
 1299        "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query),
 1300        "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1301        "WHERE": lambda self, query: query.where(self._parse_where(), copy=False),
 1302    }
 1303
 1304    PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1305        "ALLOWED_VALUES": lambda self: self.expression(
 1306            exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary))
 1307        ),
 1308        "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty),
 1309        "AUTO": lambda self: self._parse_auto_property(),
 1310        "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty),
 1311        "BACKUP": lambda self: self.expression(
 1312            exp.BackupProperty(this=self._parse_var(any_token=True))
 1313        ),
 1314        "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(),
 1315        "CALLED": lambda self: self._parse_called_on_null_input_property(),
 1316        "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs),
 1317        "CHECKSUM": lambda self: self._parse_checksum(),
 1318        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1319        "CLUSTERED": lambda self: self._parse_clustered_by(),
 1320        "COLLATE": lambda self, **kwargs: self._parse_property_assignment(
 1321            exp.CollateProperty, **kwargs
 1322        ),
 1323        "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty),
 1324        "CONTAINS": lambda self: self._parse_contains_property(),
 1325        "COPY": lambda self: self._parse_copy_property(),
 1326        "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs),
 1327        "DATA_DELETION": lambda self: self._parse_data_deletion_property(),
 1328        "DEFINER": lambda self: self._parse_definer(),
 1329        "DETERMINISTIC": lambda self: self.expression(
 1330            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1331        ),
 1332        "DISTRIBUTED": lambda self: self._parse_distributed_property(),
 1333        "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty),
 1334        "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()),
 1335        "DISTKEY": lambda self: self._parse_distkey(),
 1336        "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty),
 1337        "EMPTY": lambda self: self.expression(exp.EmptyProperty()),
 1338        "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty),
 1339        "ENVIRONMENT": lambda self: self.expression(
 1340            exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment))
 1341        ),
 1342        "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty),
 1343        "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty),
 1344        "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()),
 1345        "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs),
 1346        "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1347        "FREESPACE": lambda self: self._parse_freespace(),
 1348        "GLOBAL": lambda self: self.expression(exp.GlobalProperty()),
 1349        "HEAP": lambda self: self.expression(exp.HeapProperty()),
 1350        "ICEBERG": lambda self: self.expression(exp.IcebergProperty()),
 1351        "IMMUTABLE": lambda self: self.expression(
 1352            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1353        ),
 1354        "INHERITS": lambda self: self.expression(
 1355            exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table))
 1356        ),
 1357        "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())),
 1358        "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs),
 1359        "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty),
 1360        "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"),
 1361        "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"),
 1362        "LIKE": lambda self: self._parse_create_like(),
 1363        "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty),
 1364        "LOCK": lambda self: self._parse_locking(),
 1365        "LOCKING": lambda self: self._parse_locking(),
 1366        "LOG": lambda self, **kwargs: self._parse_log(**kwargs),
 1367        "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()),
 1368        "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs),
 1369        "MODIFIES": lambda self: self._parse_modifies_property(),
 1370        "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)),
 1371        "NO": lambda self: self._parse_no_property(),
 1372        "ON": lambda self: self._parse_on_property(),
 1373        "ORDER BY": lambda self: self._parse_order(skip_order_token=True),
 1374        "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())),
 1375        "PARTITION": lambda self: self._parse_partitioned_of(),
 1376        "PARTITION BY": lambda self: self._parse_partitioned_by(),
 1377        "PARTITIONED BY": lambda self: self._parse_partitioned_by(),
 1378        "PARTITIONED_BY": lambda self: self._parse_partitioned_by(),
 1379        "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True),
 1380        "RANGE": lambda self: self._parse_dict_range(this="RANGE"),
 1381        "READS": lambda self: self._parse_reads_property(),
 1382        "REMOTE": lambda self: self._parse_remote_with_connection(),
 1383        "RETURNS": lambda self: self._parse_returns(),
 1384        "STRICT": lambda self: self.expression(exp.StrictProperty()),
 1385        "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()),
 1386        "ROW": lambda self: self._parse_row(),
 1387        "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty),
 1388        "SAMPLE": lambda self: self.expression(
 1389            exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise())
 1390        ),
 1391        "SECURE": lambda self: self.expression(exp.SecureProperty()),
 1392        "SECURITY": lambda self: self._parse_sql_security(),
 1393        "SQL SECURITY": lambda self: self._parse_sql_security(),
 1394        "SET": lambda self: self.expression(exp.SetProperty(multi=False)),
 1395        "SETTINGS": lambda self: self._parse_settings_property(),
 1396        "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty),
 1397        "SORTKEY": lambda self: self._parse_sortkey(),
 1398        "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"),
 1399        "STABLE": lambda self: self.expression(
 1400            exp.StabilityProperty(this=exp.Literal.string("STABLE"))
 1401        ),
 1402        "STORED": lambda self: self._parse_stored(),
 1403        "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(),
 1404        "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(),
 1405        "TEMP": lambda self: self.expression(exp.TemporaryProperty()),
 1406        "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()),
 1407        "TO": lambda self: self._parse_to_table(),
 1408        "TRANSIENT": lambda self: self.expression(exp.TransientProperty()),
 1409        "TRANSFORM": lambda self: self.expression(
 1410            exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression))
 1411        ),
 1412        "TTL": lambda self: self._parse_ttl(),
 1413        "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1414        "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()),
 1415        "VOLATILE": lambda self: self._parse_volatile_property(),
 1416        "WITH": lambda self: self._parse_with_property(),
 1417    }
 1418
 1419    CONSTRAINT_PARSERS: t.ClassVar = {
 1420        "AUTOINCREMENT": lambda self: self._parse_auto_increment(),
 1421        "AUTO_INCREMENT": lambda self: self._parse_auto_increment(),
 1422        "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)),
 1423        "CHECK": lambda self: self._parse_check_constraint(),
 1424        "COLLATE": lambda self: self.expression(
 1425            exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column())
 1426        ),
 1427        "COMMENT": lambda self: self.expression(
 1428            exp.CommentColumnConstraint(this=self._parse_string())
 1429        ),
 1430        "COMPRESS": lambda self: self._parse_compress(),
 1431        "CLUSTERED": lambda self: self.expression(
 1432            exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1433        ),
 1434        "NONCLUSTERED": lambda self: self.expression(
 1435            exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1436        ),
 1437        "DEFAULT": lambda self: self.expression(
 1438            exp.DefaultColumnConstraint(this=self._parse_bitwise())
 1439        ),
 1440        "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())),
 1441        "EPHEMERAL": lambda self: self.expression(
 1442            exp.EphemeralColumnConstraint(this=self._parse_bitwise())
 1443        ),
 1444        "EXCLUDE": lambda self: self.expression(
 1445            exp.ExcludeColumnConstraint(this=self._parse_index_params())
 1446        ),
 1447        "FOREIGN KEY": lambda self: self._parse_foreign_key(),
 1448        "FORMAT": lambda self: self.expression(
 1449            exp.DateFormatColumnConstraint(this=self._parse_var_or_string())
 1450        ),
 1451        "GENERATED": lambda self: self._parse_generated_as_identity(),
 1452        "IDENTITY": lambda self: self._parse_auto_increment(),
 1453        "INLINE": lambda self: self._parse_inline(),
 1454        "LIKE": lambda self: self._parse_create_like(),
 1455        "NOT": lambda self: self._parse_not_constraint(),
 1456        "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)),
 1457        "ON": lambda self: (
 1458            (
 1459                self._match(TokenType.UPDATE)
 1460                and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function()))
 1461            )
 1462            or self.expression(exp.OnProperty(this=self._parse_id_var()))
 1463        ),
 1464        "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())),
 1465        "PERIOD": lambda self: self._parse_period_for_system_time(),
 1466        "PRIMARY KEY": lambda self: self._parse_primary_key(),
 1467        "REFERENCES": lambda self: self._parse_references(match=False),
 1468        "TITLE": lambda self: self.expression(
 1469            exp.TitleColumnConstraint(this=self._parse_var_or_string())
 1470        ),
 1471        "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])),
 1472        "UNIQUE": lambda self: self._parse_unique(),
 1473        "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()),
 1474        "WITH": lambda self: self.expression(
 1475            exp.Properties(expressions=self._parse_wrapped_properties())
 1476        ),
 1477        "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1478        "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1479    }
 1480
 1481    def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None:
 1482        if not self._match(TokenType.L_PAREN, advance=False):
 1483            # Partitioning by bucket or truncate follows the syntax:
 1484            # PARTITION BY (BUCKET(..) | TRUNCATE(..))
 1485            # If we don't have parenthesis after each keyword, we should instead parse this as an identifier
 1486            self._retreat(self._index - 1)
 1487            return None
 1488
 1489        klass = (
 1490            exp.PartitionedByBucket
 1491            if self._prev.text.upper() == "BUCKET"
 1492            else exp.PartitionByTruncate
 1493        )
 1494
 1495        args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column())
 1496        this, expression = seq_get(args, 0), seq_get(args, 1)
 1497
 1498        if isinstance(this, exp.Literal):
 1499            # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order
 1500            #  - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)`
 1501            #  - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)`
 1502            # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)`
 1503            #
 1504            # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning
 1505            # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties
 1506            this, expression = expression, this
 1507
 1508        return self.expression(klass(this=this, expression=expression))
 1509
 1510    ALTER_PARSERS: t.ClassVar = {
 1511        "ADD": lambda self: self._parse_alter_table_add(),
 1512        "AS": lambda self: self._parse_select(),
 1513        "ALTER": lambda self: self._parse_alter_table_alter(),
 1514        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1515        "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())),
 1516        "DROP": lambda self: self._parse_alter_table_drop(),
 1517        "RENAME": lambda self: self._parse_alter_table_rename(),
 1518        "SET": lambda self: self._parse_alter_table_set(),
 1519        "SWAP": lambda self: self.expression(
 1520            exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True))
 1521        ),
 1522    }
 1523
 1524    ALTER_ALTER_PARSERS: t.ClassVar = {
 1525        "DISTKEY": lambda self: self._parse_alter_diststyle(),
 1526        "DISTSTYLE": lambda self: self._parse_alter_diststyle(),
 1527        "SORTKEY": lambda self: self._parse_alter_sortkey(),
 1528        "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True),
 1529    }
 1530
 1531    SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = {
 1532        "CHECK",
 1533        "EXCLUDE",
 1534        "FOREIGN KEY",
 1535        "LIKE",
 1536        "PERIOD",
 1537        "PRIMARY KEY",
 1538        "UNIQUE",
 1539        "BUCKET",
 1540        "TRUNCATE",
 1541    }
 1542
 1543    NO_PAREN_FUNCTION_PARSERS: t.ClassVar = {
 1544        "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())),
 1545        "CASE": lambda self: self._parse_case(),
 1546        "CONNECT_BY_ROOT": lambda self: self.expression(
 1547            exp.ConnectByRoot(this=self._parse_column())
 1548        ),
 1549        "IF": lambda self: self._parse_if(),
 1550    }
 1551
 1552    INVALID_FUNC_NAME_TOKENS: t.ClassVar = {
 1553        TokenType.IDENTIFIER,
 1554        TokenType.STRING,
 1555    }
 1556
 1557    FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"}
 1558
 1559    KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice)
 1560
 1561    FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1562        **{
 1563            name: lambda self: self._parse_distinct_arg_function(exp.ArgMax)
 1564            for name in exp.ArgMax.sql_names()
 1565        },
 1566        **{
 1567            name: lambda self: self._parse_distinct_arg_function(exp.ArgMin)
 1568            for name in exp.ArgMin.sql_names()
 1569        },
 1570        "CAST": lambda self: self._parse_cast(self.STRICT_CAST),
 1571        "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil),
 1572        "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST),
 1573        "CHAR": lambda self: self._parse_char(),
 1574        "CHR": lambda self: self._parse_char(),
 1575        "DECODE": lambda self: self._parse_decode(),
 1576        "EXTRACT": lambda self: self._parse_extract(),
 1577        "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor),
 1578        "GAP_FILL": lambda self: self._parse_gap_fill(),
 1579        "INITCAP": lambda self: self._parse_initcap(),
 1580        "JSON_OBJECT": lambda self: self._parse_json_object(),
 1581        "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True),
 1582        "JSON_TABLE": lambda self: self._parse_json_table(),
 1583        "MATCH": lambda self: self._parse_match_against(),
 1584        "NORMALIZE": lambda self: self._parse_normalize(),
 1585        "OPENJSON": lambda self: self._parse_open_json(),
 1586        "OVERLAY": lambda self: self._parse_overlay(),
 1587        "POSITION": lambda self: self._parse_position(),
 1588        "SAFE_CAST": lambda self: self._parse_cast(False, safe=True),
 1589        "STRING_AGG": lambda self: self._parse_string_agg(),
 1590        "SUBSTRING": lambda self: self._parse_substring(),
 1591        "TRIM": lambda self: self._parse_trim(),
 1592        "TRY_CAST": lambda self: self._parse_cast(False, safe=True),
 1593        "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True),
 1594        "XMLELEMENT": lambda self: self._parse_xml_element(),
 1595        "XMLTABLE": lambda self: self._parse_xml_table(),
 1596    }
 1597
 1598    QUERY_MODIFIER_PARSERS: t.ClassVar = {
 1599        TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()),
 1600        TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()),
 1601        TokenType.WHERE: lambda self: ("where", self._parse_where()),
 1602        TokenType.GROUP_BY: lambda self: ("group", self._parse_group()),
 1603        TokenType.HAVING: lambda self: ("having", self._parse_having()),
 1604        TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()),
 1605        TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()),
 1606        TokenType.ORDER_BY: lambda self: ("order", self._parse_order()),
 1607        TokenType.LIMIT: lambda self: ("limit", self._parse_limit()),
 1608        TokenType.FETCH: lambda self: ("limit", self._parse_limit()),
 1609        TokenType.OFFSET: lambda self: ("offset", self._parse_offset()),
 1610        TokenType.FOR: lambda self: ("locks", self._parse_locks()),
 1611        TokenType.LOCK: lambda self: ("locks", self._parse_locks()),
 1612        TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1613        TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1614        TokenType.CLUSTER_BY: lambda self: (
 1615            "cluster",
 1616            self._parse_cluster(),
 1617        ),
 1618        TokenType.DISTRIBUTE_BY: lambda self: (
 1619            "distribute",
 1620            self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY),
 1621        ),
 1622        TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)),
 1623        TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)),
 1624    }
 1625    QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS)
 1626
 1627    SET_PARSERS: t.ClassVar = {
 1628        "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"),
 1629        "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"),
 1630        "SESSION": lambda self: self._parse_set_item_assignment("SESSION"),
 1631        "TRANSACTION": lambda self: self._parse_set_transaction(),
 1632    }
 1633
 1634    SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {}
 1635
 1636    TYPE_LITERAL_PARSERS: t.ClassVar = {
 1637        exp.DType.JSON: lambda self, this, _: self.expression(
 1638            exp.ParseJSON(this=this, is_literal=True)
 1639        ),
 1640    }
 1641
 1642    TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {}
 1643
 1644    DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN}
 1645
 1646    PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE}
 1647
 1648    TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"}
 1649    TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = {
 1650        "ISOLATION": (
 1651            ("LEVEL", "REPEATABLE", "READ"),
 1652            ("LEVEL", "READ", "COMMITTED"),
 1653            ("LEVEL", "READ", "UNCOMITTED"),
 1654            ("LEVEL", "SERIALIZABLE"),
 1655        ),
 1656        "READ": ("WRITE", "ONLY"),
 1657    }
 1658
 1659    CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1660        **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()),
 1661        "DO": ("NOTHING", "UPDATE"),
 1662    }
 1663
 1664    TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = {
 1665        "INSTEAD": (("OF",),),
 1666        "BEFORE": tuple(),
 1667        "AFTER": tuple(),
 1668    }
 1669
 1670    TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = {
 1671        "NOT": (("DEFERRABLE",),),
 1672        "DEFERRABLE": tuple(),
 1673    }
 1674
 1675    CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = {
 1676        "SCALE": ("EXTEND", "NOEXTEND"),
 1677        "SHARD": ("EXTEND", "NOEXTEND"),
 1678        "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"),
 1679        **dict.fromkeys(
 1680            (
 1681                "SESSION",
 1682                "GLOBAL",
 1683                "KEEP",
 1684                "NOKEEP",
 1685                "ORDER",
 1686                "NOORDER",
 1687                "NOCACHE",
 1688                "CYCLE",
 1689                "NOCYCLE",
 1690                "NOMINVALUE",
 1691                "NOMAXVALUE",
 1692                "NOSCALE",
 1693                "NOSHARD",
 1694            ),
 1695            tuple(),
 1696        ),
 1697    }
 1698
 1699    ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")}
 1700
 1701    USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1702        ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple()
 1703    )
 1704
 1705    CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",))
 1706
 1707    SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1708        "TYPE": ("EVOLUTION",),
 1709        **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()),
 1710    }
 1711
 1712    PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {}
 1713
 1714    EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1715        ("CALLER", "SELF", "OWNER"), tuple()
 1716    )
 1717
 1718    KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1719        "NOT": ("ENFORCED",),
 1720        "MATCH": (
 1721            "FULL",
 1722            "PARTIAL",
 1723            "SIMPLE",
 1724        ),
 1725        "INITIALLY": ("DEFERRED", "IMMEDIATE"),
 1726        "USING": (
 1727            "BTREE",
 1728            "HASH",
 1729        ),
 1730        **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()),
 1731    }
 1732
 1733    WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1734        "NO": ("OTHERS",),
 1735        "CURRENT": ("ROW",),
 1736        **dict.fromkeys(("GROUP", "TIES"), tuple()),
 1737    }
 1738
 1739    INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"}
 1740
 1741    CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"}
 1742    # Time travel clause prefixes, mapped to whether they pin a timestamp or a version
 1743    VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = {
 1744        ("FOR", "SYSTEM_TIME"): "TIMESTAMP",
 1745        ("FOR", "SYSTEM", "TIME"): "TIMESTAMP",
 1746        ("FOR", "TIMESTAMP"): "TIMESTAMP",
 1747        ("FOR", "VERSION"): "VERSION",
 1748        ("TIMESTAMP", "AS", "OF"): "TIMESTAMP",
 1749        ("VERSION", "AS", "OF"): "VERSION",
 1750    }
 1751
 1752    HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"}
 1753    HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"}
 1754
 1755    OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"}
 1756
 1757    OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN}
 1758
 1759    TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE}
 1760
 1761    VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"}
 1762
 1763    WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS}
 1764    WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER}
 1765    WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"}
 1766
 1767    JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS}
 1768
 1769    FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT}
 1770
 1771    ADD_CONSTRAINT_TOKENS: t.ClassVar = {
 1772        TokenType.CONSTRAINT,
 1773        TokenType.FOREIGN_KEY,
 1774        TokenType.INDEX,
 1775        TokenType.KEY,
 1776        TokenType.PRIMARY_KEY,
 1777        TokenType.UNIQUE,
 1778    }
 1779
 1780    DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT}
 1781
 1782    UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS
 1783
 1784    SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT}
 1785
 1786    COPY_INTO_VARLEN_OPTIONS: t.ClassVar = {
 1787        "FILE_FORMAT",
 1788        "COPY_OPTIONS",
 1789        "FORMAT_OPTIONS",
 1790        "CREDENTIAL",
 1791    }
 1792
 1793    IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"}
 1794
 1795    ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {}
 1796
 1797    ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"}
 1798
 1799    PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN}
 1800
 1801    # The style options for the DESCRIBE statement
 1802    DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"}
 1803
 1804    SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"}
 1805
 1806    # The style options for the ANALYZE statement
 1807    ANALYZE_STYLES: t.ClassVar = {
 1808        "BUFFER_USAGE_LIMIT",
 1809        "FULL",
 1810        "LOCAL",
 1811        "NO_WRITE_TO_BINLOG",
 1812        "SAMPLE",
 1813        "SKIP_LOCKED",
 1814        "VERBOSE",
 1815    }
 1816
 1817    ANALYZE_EXPRESSION_PARSERS: t.ClassVar = {
 1818        "ALL": lambda self: self._parse_analyze_columns(),
 1819        "COMPUTE": lambda self: self._parse_analyze_statistics(),
 1820        "DELETE": lambda self: self._parse_analyze_delete(),
 1821        "DROP": lambda self: self._parse_analyze_histogram(),
 1822        "ESTIMATE": lambda self: self._parse_analyze_statistics(),
 1823        "LIST": lambda self: self._parse_analyze_list(),
 1824        "PREDICATE": lambda self: self._parse_analyze_columns(),
 1825        "UPDATE": lambda self: self._parse_analyze_histogram(),
 1826        "VALIDATE": lambda self: self._parse_analyze_validate(),
 1827    }
 1828
 1829    PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"}
 1830
 1831    AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET)
 1832
 1833    OPERATION_MODIFIERS: t.ClassVar[set[str]] = set()
 1834
 1835    RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"}
 1836
 1837    SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"}
 1838
 1839    MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values)
 1840
 1841    STRICT_CAST: t.ClassVar = True
 1842
 1843    PREFIXED_PIVOT_COLUMNS: t.ClassVar = False
 1844    IDENTIFY_PIVOT_STRINGS: t.ClassVar = False
 1845    # Whether an UNPIVOT outputs its value column(s) before the name column
 1846    UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False
 1847    # Controls when an aggregation's name is included in a pivoted column's name:
 1848    # "agg_name_if_aliased" - only for aggregations that carry an explicit alias
 1849    # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations
 1850    # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only)
 1851    PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased"
 1852
 1853    LOG_DEFAULTS_TO_LN: t.ClassVar = False
 1854
 1855    # Whether the table sample clause expects CSV syntax
 1856    TABLESAMPLE_CSV: t.ClassVar = False
 1857
 1858    # The default method used for table sampling
 1859    DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None
 1860
 1861    # Whether the SET command needs a delimiter (e.g. "=") for assignments
 1862    SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True
 1863
 1864    # Whether the TRIM function expects the characters to trim as its first argument
 1865    TRIM_PATTERN_FIRST: t.ClassVar = False
 1866
 1867    # Whether string aliases are supported `SELECT COUNT(*) 'count'`
 1868    STRING_ALIASES: t.ClassVar = False
 1869
 1870    # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand)
 1871    MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True
 1872    SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"}
 1873
 1874    # Whether to parse IF statements that aren't followed by a left parenthesis as commands
 1875    NO_PAREN_IF_COMMANDS: t.ClassVar = True
 1876
 1877    # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres)
 1878    JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False
 1879
 1880    # Whether the `:` operator is used to extract a value from a VARIANT column
 1881    COLON_IS_VARIANT_EXTRACT: t.ClassVar = False
 1882
 1883    # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged
 1884    # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks)
 1885    COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True
 1886
 1887    # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause.
 1888    # If this is True and '(' is not found, the keyword will be treated as an identifier
 1889    VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True
 1890
 1891    # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift)
 1892    SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False
 1893
 1894    # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery)
 1895    SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False
 1896
 1897    # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS
 1898    INTERVAL_SPANS: t.ClassVar = True
 1899
 1900    # Whether a PARTITION clause can follow a table reference
 1901    SUPPORTS_PARTITION_SELECTION: t.ClassVar = False
 1902
 1903    # Whether the `name AS expr` schema/column constraint requires parentheses around `expr`
 1904    WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True
 1905
 1906    # Whether the 'AS' keyword is optional in the CTE definition syntax
 1907    OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True
 1908
 1909    # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword
 1910    ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True
 1911
 1912    # Whether Alter statements are allowed to contain Partition specifications
 1913    ALTER_TABLE_PARTITIONS: t.ClassVar = False
 1914
 1915    # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree.
 1916    # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is
 1917    # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such
 1918    # as BigQuery, where all joins have the same precedence.
 1919    JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False
 1920
 1921    # Whether TIMESTAMP <literal> can produce a zone-aware timestamp
 1922    ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False
 1923
 1924    # Whether map literals support arbitrary expressions as keys.
 1925    # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB).
 1926    # When False, keys are typically restricted to identifiers.
 1927    MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False
 1928
 1929    # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this
 1930    # is true for Snowflake but not for BigQuery which can also process strings
 1931    JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False
 1932
 1933    # Dialects like Databricks support JOINS without join criteria
 1934    # Adding an ON TRUE, makes transpilation semantically correct for other dialects
 1935    ADD_JOIN_ON_TRUE: t.ClassVar = False
 1936
 1937    # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]'
 1938    # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND`
 1939    SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False
 1940
 1941    # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them
 1942    # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation.
 1943    ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False
 1944
 1945    # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause,
 1946    # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake)
 1947    SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False
 1948
 1949    # Type names that denote a different type when they're quoted, so quoting has to be
 1950    # preserved instead of resolving them into the built-in type of the same name. These
 1951    # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR
 1952    QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set()
 1953
 1954    SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS)
 1955    SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS)
 1956
 1957    def __init__(
 1958        self,
 1959        error_level: ErrorLevel | None = None,
 1960        error_message_context: int = 100,
 1961        max_errors: int = 3,
 1962        max_nodes: int = -1,
 1963        dialect: DialectType = None,
 1964    ):
 1965        self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE
 1966        self.error_message_context: int = error_message_context
 1967        self.max_errors: int = max_errors
 1968        self.max_nodes: int = max_nodes
 1969        self.dialect: t.Any = _resolve_dialect(dialect)
 1970        self.sql: str = ""
 1971        self.errors: list[ParseError] = []
 1972        self._tokens: list[Token] = []
 1973        self._tokens_size: i64 = 0
 1974        self._index: i64 = 0
 1975        self._curr: Token = SENTINEL_NONE
 1976        self._next: Token = SENTINEL_NONE
 1977        self._prev: Token = SENTINEL_NONE
 1978        self._prev_comments: list[str] = []
 1979        self._pipe_cte_counter: int = 0
 1980        self._chunks: list[list[Token]] = []
 1981        self._chunk_index: i64 = 0
 1982        self._node_count: int = 0
 1983
 1984    def reset(self) -> None:
 1985        self.sql = ""
 1986        self.errors = []
 1987        self._tokens = []
 1988        self._tokens_size = 0
 1989        self._index = 0
 1990        self._curr = SENTINEL_NONE
 1991        self._next = SENTINEL_NONE
 1992        self._prev = SENTINEL_NONE
 1993        self._prev_comments = []
 1994        self._pipe_cte_counter = 0
 1995        self._chunks = []
 1996        self._chunk_index = 0
 1997        self._node_count = 0
 1998
 1999    def _advance(self, times: i64 = 1) -> None:
 2000        index = self._index + times
 2001        self._index = index
 2002        tokens = self._tokens
 2003        size = self._tokens_size
 2004        self._curr = tokens[index] if index < size else SENTINEL_NONE
 2005        self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE
 2006
 2007        if index > 0:
 2008            prev = tokens[index - 1]
 2009            self._prev = prev
 2010            self._prev_comments = prev.comments
 2011        else:
 2012            self._prev = SENTINEL_NONE
 2013            self._prev_comments = []
 2014
 2015    def _advance_chunk(self) -> None:
 2016        self._index = -1
 2017        self._tokens = self._chunks[self._chunk_index]
 2018        self._tokens_size = i64(len(self._tokens))
 2019        self._chunk_index += 1
 2020        self._advance()
 2021
 2022    def _retreat(self, index: i64) -> None:
 2023        if index != self._index:
 2024            self._advance(index - self._index)
 2025
 2026    def _add_comments(self, expression: exp.Expr | None) -> None:
 2027        if expression and self._prev_comments:
 2028            expression.add_comments(self._prev_comments)
 2029            self._prev_comments = []
 2030
 2031    def _match(
 2032        self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None
 2033    ) -> bool:
 2034        if self._curr.token_type == token_type:
 2035            if advance:
 2036                self._advance()
 2037            self._add_comments(expression)
 2038            return True
 2039        return False
 2040
 2041    def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool:
 2042        if self._curr.token_type in types:
 2043            if advance:
 2044                self._advance()
 2045            return True
 2046        return False
 2047
 2048    def _match_pair(
 2049        self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True
 2050    ) -> bool:
 2051        if self._curr.token_type == token_type_a and self._next.token_type == token_type_b:
 2052            if advance:
 2053                self._advance(2)
 2054            return True
 2055        return False
 2056
 2057    def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool:
 2058        if (
 2059            self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS
 2060            and self._curr.text.upper() in texts
 2061        ):
 2062            if advance:
 2063                self._advance()
 2064            return True
 2065        return False
 2066
 2067    def _match_text_seq(self, *texts: str, advance: bool = True) -> bool:
 2068        index = self._index
 2069        excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS
 2070        for text in texts:
 2071            if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text:
 2072                self._advance()
 2073            else:
 2074                self._retreat(index)
 2075                return False
 2076
 2077        if not advance:
 2078            self._retreat(index)
 2079
 2080        return True
 2081
 2082    def _is_connected(self) -> bool:
 2083        prev = self._prev
 2084        curr = self._curr
 2085        return bool(prev and curr and prev.end + 1 == curr.start)
 2086
 2087    def _find_sql(self, start: Token, end: Token) -> str:
 2088        return self.sql[start.start : end.end + 1]
 2089
 2090    def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None:
 2091        token = token or self._curr or self._prev or Token.string("")
 2092        formatted_sql, start_context, highlight, end_context = highlight_sql(
 2093            sql=self.sql,
 2094            positions=[(token.start, token.end)],
 2095            context_length=self.error_message_context,
 2096        )
 2097        formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n  {formatted_sql}"
 2098
 2099        error = ParseError.new(
 2100            formatted_message,
 2101            description=message,
 2102            line=token.line,
 2103            col=token.col,
 2104            start_context=start_context,
 2105            highlight=highlight,
 2106            end_context=end_context,
 2107        )
 2108
 2109        if self.error_level == ErrorLevel.IMMEDIATE:
 2110            raise error
 2111
 2112        self.errors.append(error)
 2113
 2114    def validate_expression(self, expression: E, args: list | None = None) -> E:
 2115        if self.max_nodes > -1:
 2116            self._node_count += 1
 2117            if self._node_count > self.max_nodes:
 2118                self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded")
 2119        if self.error_level != ErrorLevel.IGNORE:
 2120            for error_message in expression.error_messages(args):
 2121                self.raise_error(error_message)
 2122        return expression
 2123
 2124    def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None:
 2125        index = self._index
 2126        error_level = self.error_level
 2127        this: T | None = None
 2128
 2129        self.error_level = ErrorLevel.IMMEDIATE
 2130        try:
 2131            this = parse_method()
 2132        except ParseError:
 2133            this = None
 2134        finally:
 2135            if not this or retreat:
 2136                self._retreat(index)
 2137            self.error_level = error_level
 2138
 2139        return this
 2140
 2141    def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]:
 2142        """
 2143        Parses a list of tokens and returns a list of syntax trees, one tree
 2144        per parsed SQL statement.
 2145
 2146        Args:
 2147            raw_tokens: The list of tokens.
 2148            sql: The original SQL string.
 2149
 2150        Returns:
 2151            The list of the produced syntax trees.
 2152        """
 2153        return self._parse(
 2154            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
 2155        )
 2156
 2157    def parse_into(
 2158        self,
 2159        expression_types: exp.IntoType,
 2160        raw_tokens: list[Token],
 2161        sql: str | None = None,
 2162    ) -> list[exp.Expr | None]:
 2163        """
 2164        Parses a list of tokens into a given Expr type. If a collection of Expr
 2165        types is given instead, this method will try to parse the token list into each one
 2166        of them, stopping at the first for which the parsing succeeds.
 2167
 2168        Args:
 2169            expression_types: The expression type(s) to try and parse the token list into.
 2170            raw_tokens: The list of tokens.
 2171            sql: The original SQL string, used to produce helpful debug messages.
 2172
 2173        Returns:
 2174            The target Expr.
 2175        """
 2176        errors = []
 2177        for expression_type in ensure_list(expression_types):
 2178            parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type))
 2179            if not parser:
 2180                raise TypeError(f"No parser registered for {expression_type}")
 2181
 2182            try:
 2183                return self._parse(parser, raw_tokens, sql)
 2184            except ParseError as e:
 2185                e.errors[0]["into_expression"] = expression_type
 2186                errors.append(e)
 2187
 2188        raise ParseError(
 2189            f"Failed to parse '{sql or raw_tokens}' into {expression_types}",
 2190            errors=merge_errors(errors),
 2191        ) from errors[-1]
 2192
 2193    def check_errors(self) -> None:
 2194        """Logs or raises any found errors, depending on the chosen error level setting."""
 2195        if self.error_level == ErrorLevel.WARN:
 2196            for error in self.errors:
 2197                logger.error(str(error))
 2198        elif self.error_level == ErrorLevel.RAISE and self.errors:
 2199            raise ParseError(
 2200                concat_messages(self.errors, self.max_errors),
 2201                errors=merge_errors(self.errors),
 2202            )
 2203
 2204    def expression(
 2205        self,
 2206        instance: E,
 2207        token: Token | None = None,
 2208        comments: list[str] | None = None,
 2209    ) -> E:
 2210        if token:
 2211            instance.update_positions(token)
 2212        instance.add_comments(comments) if comments else self._add_comments(instance)
 2213        if not instance.is_primitive:
 2214            instance = self.validate_expression(instance)
 2215        return instance
 2216
 2217    def _parse_batch_statements(
 2218        self,
 2219        parse_method: t.Callable[[Parser], exp.Expr | None],
 2220        sep_first_statement: bool = True,
 2221    ) -> list[exp.Expr | None]:
 2222        expressions = []
 2223
 2224        # Chunkification binds if/while statements with the first statement of the body
 2225        if sep_first_statement:
 2226            self._match(TokenType.BEGIN)
 2227            expressions.append(parse_method(self))
 2228
 2229        chunks_length = len(self._chunks)
 2230        while self._chunk_index < chunks_length:
 2231            self._advance_chunk()
 2232
 2233            if self._match(TokenType.ELSE, advance=False):
 2234                return expressions
 2235
 2236            if expressions and not self._next and self._match(TokenType.END):
 2237                expressions.append(exp.EndStatement())
 2238                continue
 2239
 2240            expressions.append(parse_method(self))
 2241
 2242            if self._index < self._tokens_size:
 2243                self.raise_error("Invalid expression / Unexpected token")
 2244
 2245            self.check_errors()
 2246
 2247        return expressions
 2248
 2249    def _parse(
 2250        self,
 2251        parse_method: t.Callable[[Parser], exp.Expr | None],
 2252        raw_tokens: list[Token],
 2253        sql: str | None = None,
 2254    ) -> list[exp.Expr | None]:
 2255        self.reset()
 2256        self.sql = sql or ""
 2257
 2258        total = len(raw_tokens)
 2259        chunks: list[list[Token]] = [[]]
 2260
 2261        for i, token in enumerate(raw_tokens):
 2262            if token.token_type == TokenType.SEMICOLON:
 2263                if token.comments:
 2264                    chunks.append([token])
 2265
 2266                if i < total - 1:
 2267                    chunks.append([])
 2268            else:
 2269                chunks[-1].append(token)
 2270
 2271        self._chunks = chunks
 2272
 2273        return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False)
 2274
 2275    def _warn_unsupported(self) -> None:
 2276        if self._tokens_size <= 1:
 2277            return
 2278
 2279        # We use _find_sql because self.sql may comprise multiple chunks, and we're only
 2280        # interested in emitting a warning for the one being currently processed.
 2281        sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context]
 2282
 2283        logger.warning(
 2284            f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'."
 2285        )
 2286
 2287    def _parse_command(self) -> exp.Command:
 2288        self._warn_unsupported()
 2289        comments = self._prev_comments
 2290        return self.expression(
 2291            exp.Command(this=self._prev.text.upper(), expression=self._parse_string()),
 2292            comments=comments,
 2293        )
 2294
 2295    def _parse_comment(self, allow_exists: bool = True) -> exp.Expr:
 2296        start = self._prev
 2297        exists = self._parse_exists() if allow_exists else None
 2298
 2299        self._match(TokenType.ON)
 2300
 2301        materialized = self._match_text_seq("MATERIALIZED")
 2302        kind = self._match_set(self.CREATABLES) and self._prev
 2303        if not kind:
 2304            return self._parse_as_command(start)
 2305
 2306        if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2307            this = self._parse_user_defined_function(kind=kind.token_type)
 2308        elif kind.token_type == TokenType.TABLE:
 2309            this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS)
 2310        elif kind.token_type == TokenType.COLUMN:
 2311            this = self._parse_column()
 2312        else:
 2313            this = self._parse_table_parts(schema=True)
 2314
 2315        self._match(TokenType.IS)
 2316
 2317        return self.expression(
 2318            exp.Comment(
 2319                this=this,
 2320                kind=kind.text,
 2321                expression=self._parse_string(),
 2322                exists=exists,
 2323                materialized=materialized,
 2324            )
 2325        )
 2326
 2327    def _parse_to_table(
 2328        self,
 2329    ) -> exp.ToTableProperty:
 2330        table = self._parse_table_parts(schema=True)
 2331        return self.expression(exp.ToTableProperty(this=table))
 2332
 2333    # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl
 2334    def _parse_ttl(self) -> exp.Expr:
 2335        def _parse_ttl_action() -> exp.Expr | None:
 2336            this = self._parse_bitwise()
 2337
 2338            if self._match_text_seq("DELETE"):
 2339                return self.expression(exp.MergeTreeTTLAction(this=this, delete=True))
 2340            if self._match_text_seq("RECOMPRESS"):
 2341                return self.expression(
 2342                    exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise())
 2343                )
 2344            if self._match_text_seq("TO", "DISK"):
 2345                return self.expression(
 2346                    exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string())
 2347                )
 2348            if self._match_text_seq("TO", "VOLUME"):
 2349                return self.expression(
 2350                    exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string())
 2351                )
 2352
 2353            return this
 2354
 2355        expressions = self._parse_csv(_parse_ttl_action)
 2356        where = self._parse_where()
 2357        group = self._parse_group()
 2358
 2359        aggregates = None
 2360        if group and self._match(TokenType.SET):
 2361            aggregates = self._parse_csv(self._parse_set_item)
 2362
 2363        return self.expression(
 2364            exp.MergeTreeTTL(
 2365                expressions=expressions, where=where, group=group, aggregates=aggregates
 2366            )
 2367        )
 2368
 2369    def _parse_condition(self) -> exp.Expr | None:
 2370        return self._parse_wrapped(parse_method=self._parse_expression, optional=True)
 2371
 2372    def _parse_block(self) -> exp.Block:
 2373        return self.expression(
 2374            exp.Block(
 2375                expressions=self._parse_batch_statements(
 2376                    parse_method=lambda self: self._parse_statement()
 2377                )
 2378            )
 2379        )
 2380
 2381    def _parse_whileblock(self) -> exp.WhileBlock:
 2382        return self.expression(
 2383            exp.WhileBlock(this=self._parse_condition(), body=self._parse_block())
 2384        )
 2385
 2386    def _parse_statement(self) -> exp.Expr | None:
 2387        if not self._curr:
 2388            return None
 2389
 2390        if self._match_set(self.STATEMENT_PARSERS):
 2391            comments = self._prev_comments
 2392            stmt = self.STATEMENT_PARSERS[self._prev.token_type](self)
 2393            stmt.add_comments(comments, prepend=True)
 2394            return stmt
 2395
 2396        if self._match_set(self.dialect.tokenizer_class.COMMANDS):
 2397            return self._parse_command()
 2398
 2399        if self._match_text_seq("WHILE"):
 2400            return self._parse_whileblock()
 2401
 2402        expression = self._parse_expression()
 2403        expression = self._parse_set_operations(expression) if expression else self._parse_select()
 2404
 2405        if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False):
 2406            expression = self._parse_pipe_syntax_query(expression)
 2407
 2408        return self._parse_query_modifiers(expression)
 2409
 2410    def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command:
 2411        start = self._prev
 2412        temporary = self._match(TokenType.TEMPORARY)
 2413        materialized = self._match_text_seq("MATERIALIZED")
 2414        iceberg = self._match_text_seq("ICEBERG")
 2415
 2416        kind = self._match_set(self.CREATABLES) and self._prev.text.upper()
 2417        if not kind or (iceberg and kind and kind != "TABLE"):
 2418            return self._parse_as_command(start)
 2419
 2420        concurrently = self._match_text_seq("CONCURRENTLY")
 2421        if_exists = exists or self._parse_exists()
 2422
 2423        tables: exp.Expr | list[exp.Expr] | None
 2424        if kind == "COLUMN":
 2425            tables = self._parse_column()
 2426        elif kind in ("TABLE", "VIEW"):
 2427            tables = self._parse_csv(lambda: self._parse_table_parts(schema=True))
 2428        else:
 2429            tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA")
 2430
 2431        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 2432
 2433        if self._match(TokenType.L_PAREN, advance=False):
 2434            expressions = self._parse_wrapped_csv(self._parse_types)
 2435        else:
 2436            expressions = None
 2437
 2438        cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper()
 2439
 2440        return self.expression(
 2441            exp.Drop(
 2442                exists=if_exists,
 2443                tables=ensure_list(tables),
 2444                expressions=expressions,
 2445                kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind,
 2446                temporary=temporary,
 2447                materialized=materialized,
 2448                cascade=cascade_or_restrict == "CASCADE",
 2449                restrict=cascade_or_restrict == "RESTRICT",
 2450                constraints=self._match_text_seq("CONSTRAINTS"),
 2451                purge=self._match_text_seq("PURGE"),
 2452                cluster=cluster,
 2453                concurrently=concurrently,
 2454                sync=self._match_text_seq("SYNC"),
 2455                iceberg=iceberg,
 2456                force=self._match_text_seq("FORCE"),
 2457            )
 2458        )
 2459
 2460    def _parse_exists(self, not_: bool = False) -> bool | None:
 2461        return (
 2462            self._match_text_seq("IF")
 2463            and (not not_ or self._match(TokenType.NOT))
 2464            and self._match(TokenType.EXISTS)
 2465        )
 2466
 2467    def _parse_create(self) -> exp.Create | exp.Command:
 2468        # Note: this can't be None because we've matched a statement parser
 2469        start = self._prev
 2470
 2471        replace = (
 2472            start.token_type == TokenType.REPLACE
 2473            or self._match_pair(TokenType.OR, TokenType.REPLACE)
 2474            or self._match_pair(TokenType.OR, TokenType.ALTER)
 2475        )
 2476        refresh = self._match_pair(TokenType.OR, TokenType.REFRESH)
 2477
 2478        unique = self._match(TokenType.UNIQUE)
 2479
 2480        if self._match_text_seq("CLUSTERED", "COLUMNSTORE"):
 2481            clustered = True
 2482        elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq(
 2483            "COLUMNSTORE"
 2484        ):
 2485            clustered = False
 2486        else:
 2487            clustered = None
 2488
 2489        if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False):
 2490            self._advance()
 2491
 2492        properties = None
 2493        create_token = self._match_set(self.CREATABLES) and self._prev
 2494
 2495        if not create_token:
 2496            # exp.Properties.Location.POST_CREATE
 2497            properties = self._parse_properties()
 2498            create_token = self._match_set(self.CREATABLES) and self._prev
 2499
 2500            if not properties or not create_token:
 2501                return self._parse_as_command(start)
 2502
 2503        create_token_type = t.cast(Token, create_token).token_type
 2504
 2505        concurrently = self._match_text_seq("CONCURRENTLY")
 2506        exists = self._parse_exists(not_=True)
 2507        this = None
 2508        expression: exp.Expr | None = None
 2509        indexes = None
 2510        no_schema_binding = None
 2511        begin = None
 2512        clone = None
 2513
 2514        def extend_props(temp_props: exp.Properties | None) -> None:
 2515            nonlocal properties
 2516            if properties and temp_props:
 2517                properties.expressions.extend(temp_props.expressions)
 2518            elif temp_props:
 2519                properties = temp_props
 2520
 2521        if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2522            this = self._parse_user_defined_function(kind=create_token_type)
 2523
 2524            # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature)
 2525            extend_props(self._parse_properties())
 2526
 2527            expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None
 2528
 2529            if (
 2530                not expression
 2531                and create_token_type == TokenType.FUNCTION
 2532                and isinstance(this, exp.UserDefinedFunction)
 2533                and this.args.get("wrapped")
 2534            ):
 2535                pre_table_index = self._index
 2536                is_table = self._match(TokenType.TABLE)
 2537
 2538                expression = self._parse_expression()
 2539                overload_mode = bool(
 2540                    expression
 2541                    and self._curr.token_type == TokenType.COMMA
 2542                    and self._next.token_type == TokenType.L_PAREN
 2543                )
 2544                if not overload_mode:
 2545                    self._retreat(pre_table_index)
 2546                    is_table = False
 2547                    expression = None
 2548            else:
 2549                is_table = False
 2550                overload_mode = False
 2551
 2552            extend_props(self._parse_function_properties())
 2553
 2554            if not expression:
 2555                if self._match(TokenType.COMMAND):
 2556                    expression = self._parse_as_command(self._prev)
 2557                else:
 2558                    begin = self._match(TokenType.BEGIN)
 2559                    return_ = self._match_text_seq("RETURN")
 2560
 2561                    if self._match(TokenType.STRING, advance=False):
 2562                        # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property
 2563                        # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement
 2564                        expression = self._parse_string()
 2565                        extend_props(self._parse_properties())
 2566                    else:
 2567                        expression = (
 2568                            self._parse_user_defined_function_expression()
 2569                            if create_token_type == TokenType.FUNCTION
 2570                            else self._parse_block()
 2571                        )
 2572
 2573                    if return_:
 2574                        expression = self.expression(exp.Return(this=expression))
 2575
 2576            if overload_mode and expression:
 2577                expression = self._parse_macro_overloads(
 2578                    t.cast(exp.UserDefinedFunction, this), expression, is_table
 2579                )
 2580        elif create_token_type == TokenType.INDEX:
 2581            # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c)
 2582            if not self._match(TokenType.ON):
 2583                index = self._parse_id_var()
 2584                anonymous = False
 2585            else:
 2586                index = None
 2587                anonymous = True
 2588
 2589            this = self._parse_index(index=index, anonymous=anonymous)
 2590        elif (
 2591            create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER)
 2592        ) or create_token_type == TokenType.TRIGGER:
 2593            if is_constraint := (create_token_type == TokenType.CONSTRAINT):
 2594                create_token = self._prev
 2595
 2596            trigger_name = self._parse_id_var()
 2597            if not trigger_name:
 2598                return self._parse_as_command(start)
 2599
 2600            timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False)
 2601            timing = timing_var.this if timing_var else None
 2602            if not timing:
 2603                return self._parse_as_command(start)
 2604
 2605            events = self._parse_trigger_events()
 2606            if not self._match(TokenType.ON):
 2607                self.raise_error("Expected ON in trigger definition")
 2608
 2609            table = self._parse_table_parts()
 2610            referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None
 2611            deferrable, initially = self._parse_trigger_deferrable()
 2612            referencing = self._parse_trigger_referencing()
 2613            for_each = self._parse_trigger_for_each()
 2614            when = self._match_text_seq("WHEN") and self._parse_wrapped(
 2615                self._parse_disjunction, optional=True
 2616            )
 2617            execute = self._parse_trigger_execute()
 2618
 2619            if execute is None:
 2620                return self._parse_as_command(start)
 2621
 2622            trigger_props = self.expression(
 2623                exp.TriggerProperties(
 2624                    table=table,
 2625                    timing=timing,
 2626                    events=events,
 2627                    execute=execute,
 2628                    constraint=is_constraint,
 2629                    referenced_table=referenced_table,
 2630                    deferrable=deferrable,
 2631                    initially=initially,
 2632                    referencing=referencing,
 2633                    for_each=for_each,
 2634                    when=when,
 2635                )
 2636            )
 2637
 2638            this = trigger_name
 2639            extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else []))
 2640        elif create_token_type == TokenType.TYPE:
 2641            this = self._parse_table_parts(schema=True)
 2642            if not this or not self._match(TokenType.ALIAS):
 2643                return self._parse_as_command(start)
 2644
 2645            if self._match(TokenType.ENUM):
 2646                expression = exp.DataType(
 2647                    this=exp.DType.ENUM,
 2648                    expressions=self._parse_wrapped_csv(self._parse_string),
 2649                )
 2650            elif self._match(TokenType.L_PAREN, advance=False):
 2651                expression = self._parse_schema()
 2652            else:
 2653                return self._parse_as_command(start)
 2654        elif create_token_type in self.DB_CREATABLES:
 2655            table_parts = self._parse_table_parts(
 2656                schema=True, is_db_reference=create_token_type == TokenType.SCHEMA
 2657            )
 2658
 2659            # exp.Properties.Location.POST_NAME
 2660            self._match(TokenType.COMMA)
 2661            extend_props(self._parse_properties(before=True))
 2662
 2663            this = self._parse_schema(this=table_parts)
 2664
 2665            # exp.Properties.Location.POST_SCHEMA and POST_WITH
 2666            extend_props(self._parse_properties())
 2667
 2668            has_alias = self._match(TokenType.ALIAS)
 2669            if not self._match_set(self.DDL_SELECT_TOKENS, advance=False):
 2670                # exp.Properties.Location.POST_ALIAS
 2671                extend_props(self._parse_properties())
 2672
 2673            if create_token_type == TokenType.SEQUENCE:
 2674                expression = self._parse_types()
 2675                props = self._parse_properties()
 2676                if props:
 2677                    sequence_props = exp.SequenceProperties()
 2678                    options = []
 2679                    for prop in props:
 2680                        if isinstance(prop, exp.SequenceProperties):
 2681                            for arg, value in prop.args.items():
 2682                                if arg == "options":
 2683                                    options.extend(value)
 2684                                else:
 2685                                    sequence_props.set(arg, value)
 2686                            prop.pop()
 2687
 2688                    if options:
 2689                        sequence_props.set("options", options)
 2690
 2691                    props.append("expressions", sequence_props)
 2692                    extend_props(props)
 2693            else:
 2694                expression = self._parse_ddl_select()
 2695
 2696                # Some dialects also support using a table as an alias instead of a SELECT.
 2697                # Here we fallback to this as an alternative.
 2698                if not expression and has_alias:
 2699                    expression = self._try_parse(self._parse_table_parts)
 2700
 2701            if create_token_type == TokenType.TABLE:
 2702                # exp.Properties.Location.POST_EXPRESSION
 2703                extend_props(self._parse_properties())
 2704
 2705                indexes = []
 2706                while True:
 2707                    index = self._parse_index()
 2708
 2709                    # exp.Properties.Location.POST_INDEX
 2710                    extend_props(self._parse_properties())
 2711                    if not index:
 2712                        break
 2713                    else:
 2714                        self._match(TokenType.COMMA)
 2715                        indexes.append(index)
 2716            elif create_token_type == TokenType.VIEW:
 2717                if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"):
 2718                    no_schema_binding = True
 2719            elif create_token_type in (TokenType.SINK, TokenType.SOURCE):
 2720                extend_props(self._parse_properties())
 2721
 2722            shallow = self._match_text_seq("SHALLOW")
 2723
 2724            if self._match_texts(self.CLONE_KEYWORDS):
 2725                copy = self._prev.text.lower() == "copy"
 2726                clone = self.expression(
 2727                    exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy)
 2728                )
 2729
 2730        if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False):
 2731            return self._parse_as_command(start)
 2732
 2733        create_kind_text = create_token.text.upper()
 2734        return self.expression(
 2735            exp.Create(
 2736                this=this,
 2737                kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text,
 2738                replace=replace,
 2739                refresh=refresh,
 2740                unique=unique,
 2741                expression=expression,
 2742                exists=exists,
 2743                properties=properties,
 2744                indexes=indexes,
 2745                no_schema_binding=no_schema_binding,
 2746                begin=begin,
 2747                clone=clone,
 2748                concurrently=concurrently,
 2749                clustered=clustered,
 2750            )
 2751        )
 2752
 2753    def _parse_sequence_properties(self) -> exp.SequenceProperties | None:
 2754        seq = exp.SequenceProperties()
 2755
 2756        options = []
 2757        index = self._index
 2758
 2759        while self._curr:
 2760            self._match(TokenType.COMMA)
 2761            if self._match_text_seq("INCREMENT"):
 2762                self._match_text_seq("BY")
 2763                self._match_text_seq("=")
 2764                seq.set("increment", self._parse_term())
 2765            elif self._match_text_seq("MINVALUE"):
 2766                seq.set("minvalue", self._parse_term())
 2767            elif self._match_text_seq("MAXVALUE"):
 2768                seq.set("maxvalue", self._parse_term())
 2769            elif self._match_text_seq("START"):
 2770                self._match_text_seq("WITH")
 2771                self._match_text_seq("=")
 2772                seq.set("start", self._parse_term())
 2773            elif self._match_text_seq("CACHE"):
 2774                # T-SQL allows empty CACHE which is initialized dynamically
 2775                seq.set("cache", self._parse_number() or True)
 2776            elif self._match_text_seq("OWNED", "BY"):
 2777                # "OWNED BY NONE" is the default
 2778                seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column())
 2779            else:
 2780                opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False)
 2781                if opt:
 2782                    options.append(opt)
 2783                else:
 2784                    break
 2785
 2786        seq.set("options", options if options else None)
 2787        return None if self._index == index else seq
 2788
 2789    def _parse_trigger_events(self) -> list[exp.TriggerEvent]:
 2790        events = []
 2791
 2792        while True:
 2793            event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper()
 2794
 2795            if not event_type:
 2796                self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)")
 2797
 2798            columns = (
 2799                self._parse_csv(self._parse_column)
 2800                if event_type == "UPDATE" and self._match_text_seq("OF")
 2801                else None
 2802            )
 2803
 2804            events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns)))
 2805
 2806            if not self._match(TokenType.OR):
 2807                break
 2808
 2809        return events
 2810
 2811    def _parse_trigger_deferrable(
 2812        self,
 2813    ) -> tuple[str | None, str | None]:
 2814        deferrable_var = self._parse_var_from_options(
 2815            self.TRIGGER_DEFERRABLE, raise_unmatched=False
 2816        )
 2817        deferrable = deferrable_var.this if deferrable_var else None
 2818
 2819        initially = None
 2820        if deferrable and self._match_text_seq("INITIALLY"):
 2821            initially = (
 2822                self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None
 2823            )
 2824
 2825        return deferrable, initially
 2826
 2827    def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None:
 2828        if not self._match_text_seq(keyword):
 2829            return None
 2830        if not self._match_text_seq("TABLE"):
 2831            self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause")
 2832        self._match_text_seq("AS")
 2833        return self._parse_id_var()
 2834
 2835    def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None:
 2836        if not self._match_text_seq("REFERENCING"):
 2837            return None
 2838
 2839        old_alias = None
 2840        new_alias = None
 2841
 2842        while True:
 2843            if alias := self._parse_trigger_referencing_clause("OLD"):
 2844                if old_alias is not None:
 2845                    self.raise_error("Duplicate OLD clause in REFERENCING")
 2846                old_alias = alias
 2847            elif alias := self._parse_trigger_referencing_clause("NEW"):
 2848                if new_alias is not None:
 2849                    self.raise_error("Duplicate NEW clause in REFERENCING")
 2850                new_alias = alias
 2851            else:
 2852                break
 2853
 2854        if old_alias is None and new_alias is None:
 2855            self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE")
 2856
 2857        return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias))
 2858
 2859    def _parse_trigger_for_each(self) -> str | None:
 2860        if not self._match_text_seq("FOR", "EACH"):
 2861            return None
 2862
 2863        return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None
 2864
 2865    def _parse_trigger_execute(self) -> exp.TriggerExecute | None:
 2866        if not self._match(TokenType.EXECUTE):
 2867            return None
 2868
 2869        if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)):
 2870            self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE")
 2871
 2872        func_call = self._parse_column()
 2873        return self.expression(exp.TriggerExecute(this=func_call))
 2874
 2875    def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None:
 2876        # only used for teradata currently
 2877        self._match(TokenType.COMMA)
 2878
 2879        kwargs = {
 2880            "no": self._match_text_seq("NO"),
 2881            "dual": self._match_text_seq("DUAL"),
 2882            "before": self._match_text_seq("BEFORE"),
 2883            "default": self._match_text_seq("DEFAULT"),
 2884            "local": (self._match_text_seq("LOCAL") and "LOCAL")
 2885            or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"),
 2886            "after": self._match_text_seq("AFTER"),
 2887            "minimum": self._match_texts(("MIN", "MINIMUM")),
 2888            "maximum": self._match_texts(("MAX", "MAXIMUM")),
 2889        }
 2890
 2891        if self._match_texts(self.PROPERTY_PARSERS):
 2892            parser = self.PROPERTY_PARSERS[self._prev.text.upper()]
 2893            try:
 2894                return parser(self, **{k: v for k, v in kwargs.items() if v})
 2895            except TypeError:
 2896                self.raise_error(f"Cannot parse property '{self._prev.text}'")
 2897
 2898        if self._match_text_seq("CHARACTER", "SET"):
 2899            return self._parse_character_set(default=bool(kwargs["default"]))
 2900
 2901        return None
 2902
 2903    def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]:
 2904        return self._parse_wrapped_csv(self._parse_property)
 2905
 2906    def _parse_property(self) -> exp.Expr | list[exp.Expr] | None:
 2907        if self._match_texts(self.PROPERTY_PARSERS):
 2908            return self.PROPERTY_PARSERS[self._prev.text.upper()](self)
 2909
 2910        if self._match_text_seq("CHARACTER", "SET"):
 2911            return self._parse_character_set()
 2912
 2913        if self._match(TokenType.DEFAULT):
 2914            if self._match_texts(self.PROPERTY_PARSERS):
 2915                return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True)
 2916
 2917            if self._match_text_seq("CHARACTER", "SET"):
 2918                return self._parse_character_set(default=True)
 2919
 2920        if self._match_text_seq("COMPOUND", "SORTKEY"):
 2921            return self._parse_sortkey(compound=True)
 2922
 2923        if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"):
 2924            return self.expression(exp.ParameterStyleProperty(this="PANDAS"))
 2925
 2926        if self._match_text_seq("NOT", "DETERMINISTIC"):
 2927            return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 2928
 2929        index = self._index
 2930
 2931        seq_props = self._parse_sequence_properties()
 2932        if seq_props:
 2933            return seq_props
 2934
 2935        self._retreat(index)
 2936        return self._parse_key_value_property()
 2937
 2938    def _parse_key_value_property(
 2939        self, parse_value: t.Callable[[], exp.Expr | None] | None = None
 2940    ) -> exp.Property | None:
 2941        index = self._index
 2942        key = self._parse_column()
 2943
 2944        if not self._match(TokenType.EQ):
 2945            self._retreat(index)
 2946            return None
 2947
 2948        # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise
 2949        if isinstance(key, exp.Column):
 2950            key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name)
 2951
 2952        value = (
 2953            parse_value()
 2954            if parse_value
 2955            else self._parse_bitwise() or self._parse_var(any_token=True)
 2956        )
 2957
 2958        # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier())
 2959        if isinstance(value, exp.Column):
 2960            value = exp.var(value.name)
 2961
 2962        return self.expression(exp.Property(this=key, value=value))
 2963
 2964    def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty:
 2965        if self._match_text_seq("BY"):
 2966            return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string()))
 2967
 2968        self._match(TokenType.ALIAS)
 2969        input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None
 2970        output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None
 2971
 2972        return self.expression(
 2973            exp.FileFormatProperty(
 2974                this=(
 2975                    self.expression(
 2976                        exp.InputOutputFormat(
 2977                            input_format=input_format, output_format=output_format
 2978                        )
 2979                    )
 2980                    if input_format or output_format
 2981                    else self._parse_var_or_string() or self._parse_number() or self._parse_id_var()
 2982                ),
 2983                hive_format=True,
 2984            )
 2985        )
 2986
 2987    def _parse_unquoted_field(self) -> exp.Expr | None:
 2988        field = self._parse_field()
 2989        if isinstance(field, exp.Identifier) and not field.quoted:
 2990            field = exp.var(field)
 2991
 2992        return field
 2993
 2994    def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E:
 2995        self._match(TokenType.EQ)
 2996        self._match(TokenType.ALIAS)
 2997
 2998        return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs))
 2999
 3000    def _parse_properties(self, before: bool | None = None) -> exp.Properties | None:
 3001        properties = []
 3002        while True:
 3003            if before:
 3004                prop = self._parse_property_before()
 3005            else:
 3006                prop = self._parse_property()
 3007            if not prop:
 3008                break
 3009            for p in ensure_list(prop):
 3010                properties.append(p)
 3011
 3012        if properties:
 3013            return self.expression(exp.Properties(expressions=properties))
 3014
 3015        return None
 3016
 3017    def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty:
 3018        return self.expression(
 3019            exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION"))
 3020        )
 3021
 3022    def _parse_sql_security(self) -> exp.SqlSecurityProperty:
 3023        return self.expression(
 3024            exp.SqlSecurityProperty(
 3025                this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper()
 3026            )
 3027        )
 3028
 3029    def _parse_settings_property(self) -> exp.SettingsProperty:
 3030        return self.expression(
 3031            exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment))
 3032        )
 3033
 3034    def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None:
 3035        if not self._match_text_seq("ON", "NULL", "INPUT"):
 3036            self._retreat(self._index - 1)
 3037            return None
 3038
 3039        return self.expression(exp.CalledOnNullInputProperty())
 3040
 3041    def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty:
 3042        if self._index >= 2:
 3043            pre_volatile_token = self._tokens[self._index - 2]
 3044        else:
 3045            pre_volatile_token = None
 3046
 3047        if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS:
 3048            return exp.VolatileProperty()
 3049
 3050        return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 3051
 3052    def _parse_retention_period(self) -> exp.Var:
 3053        # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...}
 3054        number = self._parse_number()
 3055        number_str = f"{number} " if number else ""
 3056        unit = self._parse_var(any_token=True)
 3057        return exp.var(f"{number_str}{unit}")
 3058
 3059    def _parse_system_versioning_property(
 3060        self, with_: bool = False
 3061    ) -> exp.WithSystemVersioningProperty:
 3062        self._match(TokenType.EQ)
 3063        prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_))
 3064
 3065        if self._match_text_seq("OFF"):
 3066            prop.set("on", False)
 3067            return prop
 3068
 3069        self._match(TokenType.ON)
 3070        if self._match(TokenType.L_PAREN):
 3071            while self._curr and not self._match(TokenType.R_PAREN):
 3072                if self._match_text_seq("HISTORY_TABLE", "="):
 3073                    prop.set("this", self._parse_table_parts())
 3074                elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="):
 3075                    prop.set("data_consistency", self._advance_any() and self._prev.text.upper())
 3076                elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="):
 3077                    prop.set("retention_period", self._parse_retention_period())
 3078
 3079                self._match(TokenType.COMMA)
 3080
 3081        return prop
 3082
 3083    def _parse_data_deletion_property(self) -> exp.DataDeletionProperty:
 3084        self._match(TokenType.EQ)
 3085        on = self._match_text_seq("ON") or not self._match_text_seq("OFF")
 3086        prop = self.expression(exp.DataDeletionProperty(on=on))
 3087
 3088        if self._match(TokenType.L_PAREN):
 3089            while self._curr and not self._match(TokenType.R_PAREN):
 3090                if self._match_text_seq("FILTER_COLUMN", "="):
 3091                    prop.set("filter_column", self._parse_column())
 3092                elif self._match_text_seq("RETENTION_PERIOD", "="):
 3093                    prop.set("retention_period", self._parse_retention_period())
 3094
 3095                self._match(TokenType.COMMA)
 3096
 3097        return prop
 3098
 3099    def _parse_distributed_property(self) -> exp.DistributedByProperty:
 3100        kind = "HASH"
 3101        expressions: list[exp.Expr] | None = None
 3102        if self._match_text_seq("BY", "HASH"):
 3103            expressions = self._parse_wrapped_csv(self._parse_id_var)
 3104        elif self._match_text_seq("BY", "RANDOM"):
 3105            kind = "RANDOM"
 3106
 3107        # If the BUCKETS keyword is not present, the number of buckets is AUTO
 3108        buckets: exp.Expr | None = None
 3109        if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"):
 3110            buckets = self._parse_number()
 3111
 3112        return self.expression(
 3113            exp.DistributedByProperty(
 3114                expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order()
 3115            )
 3116        )
 3117
 3118    def _parse_composite_key_property(self, expr_type: type[E]) -> E:
 3119        self._match_text_seq("KEY")
 3120        expressions = self._parse_wrapped_id_vars()
 3121        return self.expression(expr_type(expressions=expressions))
 3122
 3123    def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]:
 3124        if self._match_text_seq("(", "SYSTEM_VERSIONING"):
 3125            prop = self._parse_system_versioning_property(with_=True)
 3126            self._match_r_paren()
 3127            return prop
 3128
 3129        if self._match(TokenType.L_PAREN, advance=False):
 3130            result: list[exp.Expr] = []
 3131            for i in self._parse_wrapped_properties():
 3132                result.extend(i) if isinstance(i, list) else result.append(i)
 3133            return result
 3134
 3135        if self._match_text_seq("JOURNAL"):
 3136            return self._parse_withjournaltable()
 3137
 3138        if self._match_texts(self.VIEW_ATTRIBUTES):
 3139            return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper()))
 3140
 3141        if self._match_text_seq("DATA"):
 3142            return self._parse_withdata(no=False)
 3143        elif self._match_text_seq("NO", "DATA"):
 3144            return self._parse_withdata(no=True)
 3145
 3146        if self._match(TokenType.SERDE_PROPERTIES, advance=False):
 3147            return self._parse_serde_properties(with_=True)
 3148
 3149        if self._match(TokenType.SCHEMA):
 3150            return self.expression(
 3151                exp.WithSchemaBindingProperty(
 3152                    this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS)
 3153                )
 3154            )
 3155
 3156        if self._match_texts(self.PROCEDURE_OPTIONS, advance=False):
 3157            return self.expression(
 3158                exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option))
 3159            )
 3160
 3161        if not self._next:
 3162            return None
 3163
 3164        return self._parse_withisolatedloading()
 3165
 3166    def _parse_procedure_option(self) -> exp.Expr | None:
 3167        if self._match_text_seq("EXECUTE", "AS"):
 3168            return self.expression(
 3169                exp.ExecuteAsProperty(
 3170                    this=self._parse_var_from_options(
 3171                        self.EXECUTE_AS_OPTIONS, raise_unmatched=False
 3172                    )
 3173                    or self._parse_string()
 3174                )
 3175            )
 3176
 3177        return self._parse_var_from_options(self.PROCEDURE_OPTIONS)
 3178
 3179    # https://dev.mysql.com/doc/refman/8.0/en/create-view.html
 3180    def _parse_definer(self) -> exp.DefinerProperty | None:
 3181        self._match(TokenType.EQ)
 3182
 3183        user = self._parse_id_var()
 3184        self._match(TokenType.PARAMETER)
 3185        host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text)
 3186
 3187        if not user or not host:
 3188            return None
 3189
 3190        return exp.DefinerProperty(this=f"{user}@{host}")
 3191
 3192    def _parse_withjournaltable(self) -> exp.WithJournalTableProperty:
 3193        self._match(TokenType.TABLE)
 3194        self._match(TokenType.EQ)
 3195        return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts()))
 3196
 3197    def _parse_log(self, no: bool = False) -> exp.LogProperty:
 3198        return self.expression(exp.LogProperty(no=no))
 3199
 3200    def _parse_journal(self, **kwargs) -> exp.JournalProperty:
 3201        return self.expression(exp.JournalProperty(**kwargs))
 3202
 3203    def _parse_checksum(self) -> exp.ChecksumProperty:
 3204        self._match(TokenType.EQ)
 3205
 3206        on = None
 3207        if self._match(TokenType.ON):
 3208            on = True
 3209        elif self._match_text_seq("OFF"):
 3210            on = False
 3211
 3212        return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT)))
 3213
 3214    def _parse_cluster(self) -> exp.Cluster:
 3215        self._match(TokenType.CLUSTER_BY)
 3216        return self.expression(
 3217            exp.Cluster(
 3218                expressions=self._parse_csv(self._parse_column),
 3219            )
 3220        )
 3221
 3222    def _parse_cluster_property(self) -> exp.ClusterProperty:
 3223        return self.expression(
 3224            exp.ClusterProperty(
 3225                expressions=self._parse_wrapped_csv(self._parse_column),
 3226            )
 3227        )
 3228
 3229    def _parse_clustered_by(self) -> exp.ClusteredByProperty:
 3230        self._match_text_seq("BY")
 3231
 3232        self._match_l_paren()
 3233        expressions = self._parse_csv(self._parse_column)
 3234        self._match_r_paren()
 3235
 3236        if self._match_text_seq("SORTED", "BY"):
 3237            self._match_l_paren()
 3238            sorted_by = self._parse_csv(self._parse_ordered)
 3239            self._match_r_paren()
 3240        else:
 3241            sorted_by = None
 3242
 3243        self._match(TokenType.INTO)
 3244        buckets = self._parse_number()
 3245        self._match_text_seq("BUCKETS")
 3246
 3247        return self.expression(
 3248            exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets)
 3249        )
 3250
 3251    def _parse_copy_property(self) -> exp.CopyGrantsProperty | None:
 3252        if not self._match_text_seq("GRANTS"):
 3253            self._retreat(self._index - 1)
 3254            return None
 3255
 3256        return self.expression(exp.CopyGrantsProperty())
 3257
 3258    def _parse_freespace(self) -> exp.FreespaceProperty:
 3259        self._match(TokenType.EQ)
 3260        return self.expression(
 3261            exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT))
 3262        )
 3263
 3264    def _parse_mergeblockratio(
 3265        self, no: bool = False, default: bool = False
 3266    ) -> exp.MergeBlockRatioProperty:
 3267        if self._match(TokenType.EQ):
 3268            return self.expression(
 3269                exp.MergeBlockRatioProperty(
 3270                    this=self._parse_number(), percent=self._match(TokenType.PERCENT)
 3271                )
 3272            )
 3273
 3274        return self.expression(exp.MergeBlockRatioProperty(no=no, default=default))
 3275
 3276    def _parse_datablocksize(
 3277        self,
 3278        default: bool | None = None,
 3279        minimum: bool | None = None,
 3280        maximum: bool | None = None,
 3281    ) -> exp.DataBlocksizeProperty:
 3282        self._match(TokenType.EQ)
 3283        size = self._parse_number()
 3284
 3285        units = None
 3286        if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")):
 3287            units = self._prev.text
 3288
 3289        return self.expression(
 3290            exp.DataBlocksizeProperty(
 3291                size=size, units=units, default=default, minimum=minimum, maximum=maximum
 3292            )
 3293        )
 3294
 3295    def _parse_blockcompression(self) -> exp.BlockCompressionProperty:
 3296        self._match(TokenType.EQ)
 3297        always = self._match_text_seq("ALWAYS")
 3298        manual = self._match_text_seq("MANUAL")
 3299        never = self._match_text_seq("NEVER")
 3300        default = self._match_text_seq("DEFAULT")
 3301
 3302        autotemp = None
 3303        if self._match_text_seq("AUTOTEMP"):
 3304            autotemp = self._parse_schema()
 3305
 3306        return self.expression(
 3307            exp.BlockCompressionProperty(
 3308                always=always, manual=manual, never=never, default=default, autotemp=autotemp
 3309            )
 3310        )
 3311
 3312    def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None:
 3313        index = self._index
 3314        no = self._match_text_seq("NO")
 3315        concurrent = self._match_text_seq("CONCURRENT")
 3316
 3317        if not self._match_text_seq("ISOLATED", "LOADING"):
 3318            self._retreat(index)
 3319            return None
 3320
 3321        target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False)
 3322        return self.expression(
 3323            exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target)
 3324        )
 3325
 3326    def _parse_locking(self) -> exp.LockingProperty:
 3327        if self._match(TokenType.TABLE):
 3328            kind = "TABLE"
 3329        elif self._match(TokenType.VIEW):
 3330            kind = "VIEW"
 3331        elif self._match(TokenType.ROW):
 3332            kind = "ROW"
 3333        elif self._match_text_seq("DATABASE"):
 3334            kind = "DATABASE"
 3335        else:
 3336            kind = None
 3337
 3338        if kind in ("DATABASE", "TABLE", "VIEW"):
 3339            this = self._parse_table_parts()
 3340        else:
 3341            this = None
 3342
 3343        if self._match(TokenType.FOR):
 3344            for_or_in = "FOR"
 3345        elif self._match(TokenType.IN):
 3346            for_or_in = "IN"
 3347        else:
 3348            for_or_in = None
 3349
 3350        if self._match_text_seq("ACCESS"):
 3351            lock_type = "ACCESS"
 3352        elif self._match_texts(("EXCL", "EXCLUSIVE")):
 3353            lock_type = "EXCLUSIVE"
 3354        elif self._match_text_seq("SHARE"):
 3355            lock_type = "SHARE"
 3356        elif self._match_text_seq("READ"):
 3357            lock_type = "READ"
 3358        elif self._match_text_seq("WRITE"):
 3359            lock_type = "WRITE"
 3360        elif self._match_text_seq("CHECKSUM"):
 3361            lock_type = "CHECKSUM"
 3362        else:
 3363            lock_type = None
 3364
 3365        override = self._match_text_seq("OVERRIDE")
 3366
 3367        return self.expression(
 3368            exp.LockingProperty(
 3369                this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override
 3370            )
 3371        )
 3372
 3373    def _parse_partition_by(self) -> list[exp.Expr]:
 3374        if self._match(TokenType.PARTITION_BY):
 3375            return self._parse_csv(self._parse_disjunction)
 3376        return []
 3377
 3378    def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec:
 3379        def _parse_partition_bound_expr() -> exp.Expr | None:
 3380            if self._match_text_seq("MINVALUE"):
 3381                return exp.var("MINVALUE")
 3382            if self._match_text_seq("MAXVALUE"):
 3383                return exp.var("MAXVALUE")
 3384            return self._parse_bitwise()
 3385
 3386        this: exp.Expr | list[exp.Expr] | None = None
 3387        expression = None
 3388        from_expressions = None
 3389        to_expressions = None
 3390
 3391        if self._match(TokenType.IN):
 3392            this = self._parse_wrapped_csv(self._parse_bitwise)
 3393        elif self._match(TokenType.FROM):
 3394            from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3395            self._match_text_seq("TO")
 3396            to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3397        elif self._match_text_seq("WITH", "(", "MODULUS"):
 3398            this = self._parse_number()
 3399            self._match_text_seq(",", "REMAINDER")
 3400            expression = self._parse_number()
 3401            self._match_r_paren()
 3402        else:
 3403            self.raise_error("Failed to parse partition bound spec.")
 3404
 3405        return self.expression(
 3406            exp.PartitionBoundSpec(
 3407                this=this,
 3408                expression=expression,
 3409                from_expressions=from_expressions,
 3410                to_expressions=to_expressions,
 3411            )
 3412        )
 3413
 3414    # https://www.postgresql.org/docs/current/sql-createtable.html
 3415    def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None:
 3416        if not self._match_text_seq("OF"):
 3417            self._retreat(self._index - 1)
 3418            return None
 3419
 3420        this = self._parse_table(schema=True)
 3421
 3422        if self._match(TokenType.DEFAULT):
 3423            expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT")
 3424        elif self._match_text_seq("FOR", "VALUES"):
 3425            expression = self._parse_partition_bound_spec()
 3426        else:
 3427            self.raise_error("Expecting either DEFAULT or FOR VALUES clause.")
 3428
 3429        return self.expression(exp.PartitionedOfProperty(this=this, expression=expression))
 3430
 3431    def _parse_partitioned_by(self) -> exp.PartitionedByProperty:
 3432        self._match(TokenType.EQ)
 3433        return self.expression(
 3434            exp.PartitionedByProperty(
 3435                this=self._parse_schema() or self._parse_bracket(self._parse_field())
 3436            )
 3437        )
 3438
 3439    def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty:
 3440        if self._match_text_seq("AND", "STATISTICS"):
 3441            statistics = True
 3442        elif self._match_text_seq("AND", "NO", "STATISTICS"):
 3443            statistics = False
 3444        else:
 3445            statistics = None
 3446
 3447        return self.expression(exp.WithDataProperty(no=no, statistics=statistics))
 3448
 3449    def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None:
 3450        if self._match_text_seq("SQL"):
 3451            return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL"))
 3452        return None
 3453
 3454    def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None:
 3455        if self._match_text_seq("SQL", "DATA"):
 3456            return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA"))
 3457        return None
 3458
 3459    def _parse_no_property(self) -> exp.Expr | None:
 3460        if self._match_text_seq("PRIMARY", "INDEX"):
 3461            return exp.NoPrimaryIndexProperty()
 3462        if self._match_text_seq("SQL"):
 3463            return self.expression(exp.SqlReadWriteProperty(this="NO SQL"))
 3464        return None
 3465
 3466    def _parse_on_property(self) -> exp.Expr | None:
 3467        if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"):
 3468            return exp.OnCommitProperty()
 3469        if self._match_text_seq("COMMIT", "DELETE", "ROWS"):
 3470            return exp.OnCommitProperty(delete=True)
 3471        return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var())))
 3472
 3473    def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None:
 3474        if self._match_text_seq("SQL", "DATA"):
 3475            return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA"))
 3476        return None
 3477
 3478    def _parse_distkey(self) -> exp.DistKeyProperty:
 3479        return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var)))
 3480
 3481    def _parse_create_like(self) -> exp.LikeProperty | None:
 3482        table = self._parse_table(schema=True)
 3483
 3484        options = []
 3485        while self._match_texts(("INCLUDING", "EXCLUDING")):
 3486            this = self._prev.text.upper()
 3487
 3488            id_var = self._parse_id_var()
 3489            if not id_var:
 3490                return None
 3491
 3492            options.append(
 3493                self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper())))
 3494            )
 3495
 3496        return self.expression(exp.LikeProperty(this=table, expressions=options))
 3497
 3498    def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty:
 3499        return self.expression(
 3500            exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound)
 3501        )
 3502
 3503    def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty:
 3504        self._match(TokenType.EQ)
 3505        return self.expression(
 3506            exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default)
 3507        )
 3508
 3509    def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty:
 3510        self._match_text_seq("WITH", "CONNECTION")
 3511        return self.expression(
 3512            exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts())
 3513        )
 3514
 3515    def _parse_returns(self) -> exp.ReturnsProperty:
 3516        value: exp.Expr | None
 3517        null = None
 3518        is_table = self._match(TokenType.TABLE)
 3519
 3520        if is_table:
 3521            if self._match(TokenType.LT):
 3522                value = self.expression(
 3523                    exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types))
 3524                )
 3525                if not self._match(TokenType.GT):
 3526                    self.raise_error("Expecting >")
 3527            else:
 3528                value = self._parse_schema(exp.var("TABLE"))
 3529        elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"):
 3530            null = True
 3531            value = None
 3532        else:
 3533            value = self._parse_types()
 3534
 3535        return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null))
 3536
 3537    def _parse_describe(self) -> exp.Describe:
 3538        kind = self._prev.text if self._match_set(self.CREATABLES) else None
 3539        style: str | None = (
 3540            self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None
 3541        )
 3542        if self._match(TokenType.DOT):
 3543            style = None
 3544            self._retreat(self._index - 2)
 3545
 3546        format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None
 3547
 3548        if self._match_set(self.STATEMENT_PARSERS, advance=False):
 3549            this = self._parse_statement()
 3550        else:
 3551            this = self._parse_table(schema=True)
 3552
 3553        properties = self._parse_properties()
 3554        expressions = properties.expressions if properties else None
 3555        partition = self._parse_partition()
 3556        return self.expression(
 3557            exp.Describe(
 3558                this=this,
 3559                style=style,
 3560                kind=kind,
 3561                expressions=expressions,
 3562                partition=partition,
 3563                format=format,
 3564                as_json=self._match_text_seq("AS", "JSON"),
 3565            )
 3566        )
 3567
 3568    def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts:
 3569        kind = self._prev.text.upper()
 3570        expressions = []
 3571
 3572        def parse_conditional_insert() -> exp.ConditionalInsert | None:
 3573            if self._match(TokenType.WHEN):
 3574                expression = self._parse_disjunction()
 3575                self._match(TokenType.THEN)
 3576            else:
 3577                expression = None
 3578
 3579            else_ = self._match(TokenType.ELSE)
 3580
 3581            if not self._match(TokenType.INTO):
 3582                return None
 3583
 3584            return self.expression(
 3585                exp.ConditionalInsert(
 3586                    this=self.expression(
 3587                        exp.Insert(
 3588                            this=self._parse_table(schema=True),
 3589                            expression=self._parse_derived_table_values(),
 3590                        )
 3591                    ),
 3592                    expression=expression,
 3593                    else_=else_,
 3594                )
 3595            )
 3596
 3597        expression = parse_conditional_insert()
 3598        while expression is not None:
 3599            expressions.append(expression)
 3600            expression = parse_conditional_insert()
 3601
 3602        return self.expression(
 3603            exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()),
 3604            comments=comments,
 3605        )
 3606
 3607    def _parse_insert(self) -> exp.Insert | exp.MultitableInserts:
 3608        comments: list[str] = []
 3609        hint = self._parse_hint()
 3610        overwrite = self._match(TokenType.OVERWRITE)
 3611        ignore = self._match(TokenType.IGNORE)
 3612        local = self._match_text_seq("LOCAL")
 3613        alternative = None
 3614        is_function = None
 3615
 3616        if self._match_text_seq("DIRECTORY"):
 3617            this: exp.Expr | None = self.expression(
 3618                exp.Directory(
 3619                    this=self._parse_var_or_string(),
 3620                    local=local,
 3621                    row_format=self._parse_row_format(match_row=True),
 3622                )
 3623            )
 3624        else:
 3625            if self._match_set((TokenType.FIRST, TokenType.ALL)):
 3626                comments += ensure_list(self._prev_comments)
 3627                return self._parse_multitable_inserts(comments)
 3628
 3629            if self._match(TokenType.OR):
 3630                alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text
 3631
 3632            self._match(TokenType.INTO)
 3633            comments += ensure_list(self._prev_comments)
 3634            self._match(TokenType.TABLE)
 3635            is_function = self._match(TokenType.FUNCTION)
 3636
 3637            this = self._parse_function() if is_function else self._parse_insert_table()
 3638
 3639        # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant
 3640        set_values = None
 3641        if self._match(TokenType.SET):
 3642            columns = []
 3643            values = []
 3644
 3645            def _parse_set_assignment() -> exp.Expr | None:
 3646                target = self._parse_column()
 3647                if isinstance(target, exp.Column) and self._match(TokenType.EQ):
 3648                    if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3649                        value: exp.Expr | None = exp.var(self._prev.text.upper())
 3650                    else:
 3651                        value = self._parse_disjunction()
 3652
 3653                    if value:
 3654                        columns.append(target.this)
 3655                        values.append(value)
 3656                        return value
 3657
 3658                self.raise_error("Expected column assignment in INSERT ... SET")
 3659                return None
 3660
 3661            self._parse_csv(_parse_set_assignment)
 3662
 3663            this = self.expression(exp.Schema(this=this, expressions=columns))
 3664            set_values = self.expression(
 3665                exp.Values(
 3666                    expressions=[exp.Tuple(expressions=values)],
 3667                    alias=self._parse_table_alias(),
 3668                )
 3669            )
 3670
 3671        returning = self._parse_returning()  # TSQL allows RETURNING before source
 3672
 3673        stored = self._match_text_seq("STORED") and self._parse_stored()
 3674        by_name = self._match_text_seq("BY", "NAME")
 3675        exists = self._parse_exists()
 3676        replace_where = None
 3677        replace_using = None
 3678
 3679        if self._match(TokenType.REPLACE):
 3680            if self._match(TokenType.WHERE):
 3681                replace_where = self._parse_disjunction()
 3682            elif self._match(TokenType.USING):
 3683                replace_using = self._parse_using_identifiers()
 3684
 3685        return self.expression(
 3686            exp.Insert(
 3687                hint=hint,
 3688                is_function=is_function,
 3689                this=this,
 3690                stored=stored,
 3691                by_name=by_name,
 3692                exists=exists,
 3693                where=replace_where,
 3694                using=replace_using,
 3695                partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(),
 3696                settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(),
 3697                default=self._match_text_seq("DEFAULT", "VALUES"),
 3698                expression=set_values
 3699                or self._parse_derived_table_values(allow_value_synonym=True)
 3700                or self._parse_ddl_select(),
 3701                conflict=self._parse_on_conflict(),
 3702                returning=returning or self._parse_returning(),
 3703                overwrite=overwrite,
 3704                alternative=alternative,
 3705                ignore=ignore,
 3706                source=self._match(TokenType.TABLE) and self._parse_table(),
 3707            ),
 3708            comments=comments,
 3709        )
 3710
 3711    def _parse_insert_table(self) -> exp.Expr | None:
 3712        this = self._parse_table(schema=True, parse_partition=True)
 3713        if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False):
 3714            this.set("alias", self._parse_table_alias())
 3715        return this
 3716
 3717    def _parse_kill(self) -> exp.Kill:
 3718        kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None
 3719
 3720        return self.expression(exp.Kill(this=self._parse_primary(), kind=kind))
 3721
 3722    def _parse_on_conflict(self) -> exp.OnConflict | None:
 3723        conflict = self._match_text_seq("ON", "CONFLICT")
 3724        duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY")
 3725
 3726        if not conflict and not duplicate:
 3727            return None
 3728
 3729        conflict_keys = None
 3730        constraint = None
 3731
 3732        if conflict:
 3733            if self._match_text_seq("ON", "CONSTRAINT"):
 3734                constraint = self._parse_id_var()
 3735            elif self._match(TokenType.L_PAREN):
 3736                conflict_keys = self._parse_csv(self._parse_indexed_column)
 3737                self._match_r_paren()
 3738
 3739        index_predicate = self._parse_where()
 3740
 3741        action = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 3742        if self._prev.token_type == TokenType.UPDATE:
 3743            self._match(TokenType.SET)
 3744            expressions = self._parse_csv(self._parse_update_assignment)
 3745        else:
 3746            expressions = None
 3747
 3748        return self.expression(
 3749            exp.OnConflict(
 3750                duplicate=duplicate,
 3751                expressions=expressions,
 3752                action=action,
 3753                conflict_keys=conflict_keys,
 3754                index_predicate=index_predicate,
 3755                constraint=constraint,
 3756                where=self._parse_where(),
 3757            )
 3758        )
 3759
 3760    def _parse_returning(self) -> exp.Returning | None:
 3761        if not self._match(TokenType.RETURNING):
 3762            return None
 3763        return self.expression(
 3764            exp.Returning(
 3765                expressions=self._parse_csv(self._parse_expression),
 3766                into=self._match(TokenType.INTO) and self._parse_table_part(),
 3767            )
 3768        )
 3769
 3770    def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3771        if not self._match(TokenType.FORMAT):
 3772            return None
 3773        return self._parse_row_format()
 3774
 3775    def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None:
 3776        index = self._index
 3777        with_ = with_ or self._match_text_seq("WITH")
 3778
 3779        if not self._match(TokenType.SERDE_PROPERTIES):
 3780            self._retreat(index)
 3781            return None
 3782        return self.expression(
 3783            exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_)
 3784        )
 3785
 3786    def _parse_row_format(
 3787        self, match_row: bool = False
 3788    ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3789        if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT):
 3790            return None
 3791
 3792        if self._match_text_seq("SERDE"):
 3793            this = self._parse_string()
 3794
 3795            serde_properties = self._parse_serde_properties()
 3796
 3797            return self.expression(
 3798                exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties)
 3799            )
 3800
 3801        self._match_text_seq("DELIMITED")
 3802
 3803        kwargs = {}
 3804
 3805        if self._match_text_seq("FIELDS", "TERMINATED", "BY"):
 3806            kwargs["fields"] = self._parse_string()
 3807            if self._match_text_seq("ESCAPED", "BY"):
 3808                kwargs["escaped"] = self._parse_string()
 3809        if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"):
 3810            kwargs["collection_items"] = self._parse_string()
 3811        if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"):
 3812            kwargs["map_keys"] = self._parse_string()
 3813        if self._match_text_seq("LINES", "TERMINATED", "BY"):
 3814            kwargs["lines"] = self._parse_string()
 3815        if self._match_text_seq("NULL", "DEFINED", "AS"):
 3816            kwargs["null"] = self._parse_string()
 3817
 3818        return self.expression(exp.RowFormatDelimitedProperty(**kwargs))  # type: ignore
 3819
 3820    def _parse_load(self) -> exp.LoadData | exp.Command:
 3821        if self._match_text_seq("DATA"):
 3822            local = self._match_text_seq("LOCAL")
 3823            self._match_text_seq("INPATH")
 3824            inpath = self._parse_string()
 3825            overwrite = self._match(TokenType.OVERWRITE)
 3826            temp: bool | None = None
 3827            if self._match(TokenType.INTO):
 3828                temp = self._match(TokenType.TEMPORARY)
 3829                self._match(TokenType.TABLE)
 3830
 3831            return self.expression(
 3832                exp.LoadData(
 3833                    this=self._parse_table(schema=True),
 3834                    local=local,
 3835                    overwrite=overwrite,
 3836                    temp=temp,
 3837                    inpath=inpath,
 3838                    files=self._match_text_seq("FROM", "FILES")
 3839                    and exp.Properties(expressions=self._parse_wrapped_properties()),
 3840                    partition=self._parse_partition(),
 3841                    input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(),
 3842                    serde=self._match_text_seq("SERDE") and self._parse_string(),
 3843                )
 3844            )
 3845        return self._parse_as_command(self._prev)
 3846
 3847    def _parse_delete(self) -> exp.Delete:
 3848        hint = self._parse_hint()
 3849
 3850        # This handles MySQL's "Multiple-Table Syntax"
 3851        # https://dev.mysql.com/doc/refman/8.0/en/delete.html
 3852        tables = None
 3853        if not self._match(TokenType.FROM, advance=False):
 3854            tables = self._parse_csv(self._parse_table) or None
 3855
 3856        returning = self._parse_returning()
 3857
 3858        return self.expression(
 3859            exp.Delete(
 3860                hint=hint,
 3861                tables=tables,
 3862                this=self._match(TokenType.FROM) and self._parse_table(joins=True),
 3863                using=self._match(TokenType.USING)
 3864                and self._parse_csv(lambda: self._parse_table(joins=True)),
 3865                cluster=self._match(TokenType.ON) and self._parse_on_property(),
 3866                where=self._parse_where(),
 3867                returning=returning or self._parse_returning(),
 3868                order=self._parse_order(),
 3869                limit=self._parse_limit(),
 3870            )
 3871        )
 3872
 3873    def _parse_update_assignment(self) -> exp.Expr | None:
 3874        this = self._parse_comparison()
 3875        if self._match(TokenType.EQ):
 3876            comments = self._prev_comments
 3877            this = self.expression(
 3878                exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments
 3879            )
 3880        return this
 3881
 3882    def _parse_update(self) -> exp.Update:
 3883        hint = self._parse_hint()
 3884        kwargs: dict[str, object] = {
 3885            "hint": hint,
 3886            "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS),
 3887        }
 3888        while self._curr:
 3889            if self._match(TokenType.SET):
 3890                kwargs["expressions"] = self._parse_csv(self._parse_update_assignment)
 3891            elif self._match(TokenType.RETURNING, advance=False):
 3892                kwargs["returning"] = self._parse_returning()
 3893            elif self._match(TokenType.FROM, advance=False):
 3894                from_ = self._parse_from(joins=True)
 3895                table = from_.this if from_ else None
 3896                if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False):
 3897                    table.set("joins", list(self._parse_joins()) or None)
 3898
 3899                kwargs["from_"] = from_
 3900            elif self._match(TokenType.WHERE, advance=False):
 3901                kwargs["where"] = self._parse_where()
 3902            elif self._match(TokenType.ORDER_BY, advance=False):
 3903                kwargs["order"] = self._parse_order()
 3904            elif self._match(TokenType.LIMIT, advance=False):
 3905                kwargs["limit"] = self._parse_limit()
 3906            else:
 3907                break
 3908
 3909        return self.expression(exp.Update(**kwargs))
 3910
 3911    def _parse_use(self) -> exp.Use:
 3912        return self.expression(
 3913            exp.Use(
 3914                kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False),
 3915                this=self._parse_table(schema=False),
 3916            )
 3917        )
 3918
 3919    def _parse_uncache(self) -> exp.Uncache:
 3920        if not self._match(TokenType.TABLE):
 3921            self.raise_error("Expecting TABLE after UNCACHE")
 3922
 3923        return self.expression(
 3924            exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True))
 3925        )
 3926
 3927    def _parse_cache(self) -> exp.Cache:
 3928        lazy = self._match_text_seq("LAZY")
 3929        self._match(TokenType.TABLE)
 3930        table = self._parse_table(schema=True)
 3931
 3932        options = []
 3933        if self._match_text_seq("OPTIONS"):
 3934            self._match_l_paren()
 3935            k = self._parse_string()
 3936            self._match(TokenType.EQ)
 3937            v = self._parse_string()
 3938            options = [k, v]
 3939            self._match_r_paren()
 3940
 3941        self._match(TokenType.ALIAS)
 3942        return self.expression(
 3943            exp.Cache(
 3944                this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True)
 3945            )
 3946        )
 3947
 3948    def _parse_partition(self) -> exp.Partition | None:
 3949        if not self._match_texts(self.PARTITION_KEYWORDS):
 3950            return None
 3951
 3952        return self.expression(
 3953            exp.Partition(
 3954                subpartition=self._prev.text.upper() == "SUBPARTITION",
 3955                expressions=self._parse_wrapped_csv(self._parse_disjunction),
 3956            )
 3957        )
 3958
 3959    def _parse_value(self, values: bool = True) -> exp.Tuple | None:
 3960        def _parse_value_expression() -> exp.Expr | None:
 3961            if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3962                return exp.var(self._prev.text.upper())
 3963            return self._parse_expression()
 3964
 3965        if self._match(TokenType.L_PAREN):
 3966            expressions = self._parse_csv(_parse_value_expression)
 3967            self._match_r_paren()
 3968            return self.expression(exp.Tuple(expressions=expressions))
 3969
 3970        # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows.
 3971        expression = self._parse_expression()
 3972        if expression:
 3973            return self.expression(exp.Tuple(expressions=[expression]))
 3974        return None
 3975
 3976    def _parse_projections(
 3977        self,
 3978    ) -> tuple[list[exp.Expr], list[exp.Expr] | None]:
 3979        return self._parse_expressions(), None
 3980
 3981    def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None:
 3982        if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)):
 3983            this: exp.Expr | None = self._parse_simplified_pivot(
 3984                is_unpivot=self._prev.token_type == TokenType.UNPIVOT
 3985            )
 3986        elif self._match(TokenType.FROM):
 3987            from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True)
 3988            # Support parentheses for duckdb FROM-first syntax
 3989            select = self._parse_select(from_=from_)
 3990            if select:
 3991                if not select.args.get("from_"):
 3992                    select.set("from_", from_)
 3993                this = select
 3994            else:
 3995                this = exp.select("*").from_(t.cast(exp.From, from_))
 3996                this = self._parse_query_modifiers(self._parse_set_operations(this))
 3997        else:
 3998            this = (
 3999                self._parse_table(consume_pipe=True)
 4000                if table
 4001                else self._parse_select(nested=True, parse_set_operation=False)
 4002            )
 4003
 4004            # Transform exp.Values into a exp.Table to pass through parse_query_modifiers
 4005            # in case a modifier (e.g. join) is following
 4006            if table and isinstance(this, exp.Values) and this.alias:
 4007                alias = this.args["alias"].pop()
 4008                this = exp.Table(this=this, alias=alias)
 4009
 4010            this = self._parse_query_modifiers(self._parse_set_operations(this))
 4011
 4012        return this
 4013
 4014    def _parse_select(
 4015        self,
 4016        nested: bool = False,
 4017        table: bool = False,
 4018        parse_subquery_alias: bool = True,
 4019        parse_set_operation: bool = True,
 4020        consume_pipe: bool = True,
 4021        from_: exp.From | None = None,
 4022    ) -> exp.Expr | None:
 4023        query = self._parse_select_query(
 4024            nested=nested,
 4025            table=table,
 4026            parse_subquery_alias=parse_subquery_alias,
 4027            parse_set_operation=parse_set_operation,
 4028        )
 4029
 4030        if consume_pipe and self._match(TokenType.PIPE_GT, advance=False):
 4031            if not query and from_:
 4032                query = exp.select("*").from_(from_)
 4033            if isinstance(query, exp.Query):
 4034                query = self._parse_pipe_syntax_query(query)
 4035                query = query.subquery(copy=False) if query and table else query
 4036
 4037        return query
 4038
 4039    def _parse_select_query(
 4040        self,
 4041        nested: bool = False,
 4042        table: bool = False,
 4043        parse_subquery_alias: bool = True,
 4044        parse_set_operation: bool = True,
 4045    ) -> exp.Expr | None:
 4046        cte = self._parse_with()
 4047
 4048        if cte:
 4049            this = self._parse_statement()
 4050
 4051            if not this:
 4052                self.raise_error("Failed to parse any statement following CTE")
 4053                return cte
 4054
 4055            while isinstance(this, exp.Subquery) and this.is_wrapper:
 4056                this = this.this
 4057
 4058            assert this is not None
 4059            if "with_" in this.arg_types:
 4060                if inner_cte := this.args.get("with_"):
 4061                    cte.set("expressions", cte.expressions + inner_cte.expressions)
 4062                    if inner_cte.args.get("recursive"):
 4063                        cte.set("recursive", True)
 4064                this.set("with_", cte)
 4065            else:
 4066                self.raise_error(f"{this.key} does not support CTE")
 4067                this = cte
 4068
 4069            return this
 4070
 4071        # duckdb supports leading with FROM x
 4072        from_ = (
 4073            self._parse_from(joins=True, consume_pipe=True)
 4074            if self._match(TokenType.FROM, advance=False)
 4075            else None
 4076        )
 4077
 4078        if self._match(TokenType.SELECT):
 4079            comments = self._prev_comments
 4080
 4081            hint = self._parse_hint()
 4082
 4083            if self._next and not self._next.token_type == TokenType.DOT:
 4084                all_ = self._match(TokenType.ALL)
 4085                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4086            else:
 4087                all_, matched_distinct = None, False
 4088
 4089            kind = (
 4090                self._prev.text.upper()
 4091                if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE"))
 4092                else None
 4093            )
 4094
 4095            distinct: exp.Expr | None = (
 4096                self.expression(
 4097                    exp.Distinct(
 4098                        on=self._parse_value(values=False) if self._match(TokenType.ON) else None
 4099                    )
 4100                )
 4101                if matched_distinct
 4102                else None
 4103            )
 4104
 4105            operation_modifiers = []
 4106            while self._curr and self._match_texts(self.OPERATION_MODIFIERS):
 4107                operation_modifiers.append(exp.var(self._prev.text.upper()))
 4108
 4109            limit = self._parse_limit(top=True)
 4110
 4111            # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ...
 4112            if limit and not matched_distinct and not all_:
 4113                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4114                if matched_distinct:
 4115                    distinct = self.expression(
 4116                        exp.Distinct(
 4117                            on=self._parse_value(values=False)
 4118                            if self._match(TokenType.ON)
 4119                            else None
 4120                        )
 4121                    )
 4122                else:
 4123                    all_ = self._match(TokenType.ALL)
 4124
 4125            if all_ and distinct:
 4126                self.raise_error("Cannot specify both ALL and DISTINCT after SELECT")
 4127
 4128            projections, exclude = self._parse_projections()
 4129
 4130            this = self.expression(
 4131                exp.Select(
 4132                    kind=kind,
 4133                    hint=hint,
 4134                    distinct=distinct,
 4135                    expressions=projections,
 4136                    limit=limit,
 4137                    exclude=exclude,
 4138                    operation_modifiers=operation_modifiers or None,
 4139                )
 4140            )
 4141            this.comments = comments
 4142
 4143            into = self._parse_into()
 4144            if into:
 4145                this.set("into", into)
 4146
 4147            if not from_:
 4148                from_ = self._parse_from()
 4149
 4150            if from_:
 4151                this.set("from_", from_)
 4152
 4153            this = self._parse_query_modifiers(this)
 4154        elif (table or nested) and self._match(TokenType.L_PAREN):
 4155            comments = self._prev_comments
 4156            this = self._parse_wrapped_select(table=table)
 4157
 4158            if this:
 4159                this.add_comments(comments, prepend=True)
 4160
 4161            # We return early here so that the UNION isn't attached to the subquery by the
 4162            # following call to _parse_set_operations, but instead becomes the parent node
 4163            self._match_r_paren()
 4164            return self._parse_subquery(this, parse_alias=parse_subquery_alias)
 4165        elif self._match(TokenType.VALUES, advance=False):
 4166            this = self._parse_derived_table_values()
 4167        elif from_:
 4168            this = exp.select("*").from_(from_.this, copy=False)
 4169            this = self._parse_query_modifiers(this)
 4170        elif self._match(TokenType.SUMMARIZE):
 4171            table = self._match(TokenType.TABLE)
 4172            this = self._parse_select() or self._parse_string() or self._parse_table()
 4173            return self.expression(exp.Summarize(this=this, table=table))
 4174        elif self._match(TokenType.DESCRIBE):
 4175            this = self._parse_describe()
 4176        else:
 4177            this = None
 4178
 4179        return self._parse_set_operations(this) if parse_set_operation else this
 4180
 4181    def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None:
 4182        self._match_text_seq("SEARCH")
 4183
 4184        kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper()
 4185
 4186        if not kind:
 4187            return None
 4188
 4189        self._match_text_seq("FIRST", "BY")
 4190
 4191        return self.expression(
 4192            exp.RecursiveWithSearch(
 4193                kind=kind,
 4194                this=self._parse_id_var(),
 4195                expression=self._match_text_seq("SET") and self._parse_id_var(),
 4196                using=self._match_text_seq("USING") and self._parse_id_var(),
 4197            )
 4198        )
 4199
 4200    def _parse_with(self, skip_with_token: bool = False) -> exp.With | None:
 4201        if not skip_with_token and not self._match(TokenType.WITH):
 4202            return None
 4203
 4204        comments = self._prev_comments
 4205        recursive = self._match(TokenType.RECURSIVE)
 4206
 4207        last_comments = None
 4208        expressions = []
 4209        udfs = []
 4210        while True:
 4211            cte = self._parse_cte()
 4212            if cte:
 4213                if isinstance(cte, exp.FunctionSpecification):
 4214                    udfs.append(cte)
 4215                else:
 4216                    expressions.append(cte)
 4217
 4218                if last_comments:
 4219                    cte.add_comments(last_comments)
 4220
 4221            if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH):
 4222                break
 4223            else:
 4224                self._match(TokenType.WITH)
 4225                recursive = self._match(TokenType.RECURSIVE) or recursive
 4226
 4227            last_comments = self._prev_comments
 4228
 4229        return self.expression(
 4230            exp.With(
 4231                expressions=expressions,
 4232                recursive=recursive or None,
 4233                search=self._parse_recursive_with_search(),
 4234                udfs=udfs or None,
 4235            ),
 4236            comments=comments,
 4237        )
 4238
 4239    def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None:
 4240        index = self._index
 4241
 4242        alias = self._parse_table_alias(self.ID_VAR_TOKENS)
 4243        if not alias or not alias.this:
 4244            self.raise_error("Expected CTE to have alias")
 4245
 4246        key_expressions = (
 4247            self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None
 4248        )
 4249
 4250        if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE:
 4251            self._retreat(index)
 4252            return None
 4253
 4254        comments = self._prev_comments
 4255
 4256        if self._match_text_seq("NOT", "MATERIALIZED"):
 4257            materialized = False
 4258        elif self._match_text_seq("MATERIALIZED"):
 4259            materialized = True
 4260        else:
 4261            materialized = None
 4262
 4263        cte = self.expression(
 4264            exp.CTE(
 4265                this=self._parse_wrapped(self._parse_statement),
 4266                alias=alias,
 4267                materialized=materialized,
 4268                key_expressions=key_expressions,
 4269            ),
 4270            comments=comments,
 4271        )
 4272
 4273        values = cte.this
 4274        if isinstance(values, exp.Values):
 4275            cte.set("this", self._values_to_select(values))
 4276
 4277        return cte
 4278
 4279    def _values_to_select(self, values: exp.Values) -> exp.Select:
 4280        if values.alias:
 4281            return exp.select("*").from_(values)
 4282        return exp.select("*").from_(exp.alias_(values, "_values", table=True))
 4283
 4284    def _parse_table_alias(
 4285        self, alias_tokens: t.Collection[TokenType] | None = None
 4286    ) -> exp.TableAlias | None:
 4287        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 4288        # so this section tries to parse the clause version and if it fails, it treats the token
 4289        # as an identifier (alias)
 4290        if self._can_parse_limit_or_offset():
 4291            return None
 4292
 4293        # START is never treated as an implicit alias when followed by WITH, since that
 4294        # would swallow the beginning of a START WITH ... CONNECT BY clause
 4295        if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH":
 4296            return None
 4297
 4298        any_token = self._match(TokenType.ALIAS)
 4299        alias = (
 4300            self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 4301            or self._parse_string_as_identifier()
 4302        )
 4303
 4304        index = self._index
 4305        if self._match(TokenType.L_PAREN):
 4306            columns = self._parse_csv(self._parse_function_parameter)
 4307            self._match_r_paren() if columns else self._retreat(index)
 4308        else:
 4309            columns = None
 4310
 4311        if not alias and not columns:
 4312            return None
 4313
 4314        table_alias = self.expression(exp.TableAlias(this=alias, columns=columns))
 4315
 4316        # We bubble up comments from the Identifier to the TableAlias
 4317        if isinstance(alias, exp.Identifier):
 4318            table_alias.add_comments(alias.pop_comments())
 4319
 4320        return table_alias
 4321
 4322    def _parse_subquery(
 4323        self, this: exp.Expr | None, parse_alias: bool = True
 4324    ) -> exp.Subquery | None:
 4325        if not this:
 4326            return None
 4327
 4328        return self.expression(
 4329            exp.Subquery(
 4330                this=this,
 4331                pivots=self._parse_pivots(),
 4332                alias=self._parse_table_alias() if parse_alias else None,
 4333                sample=self._parse_table_sample(),
 4334            )
 4335        )
 4336
 4337    def _implicit_unnests_to_explicit(self, this: E) -> E:
 4338        from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm
 4339
 4340        refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name}
 4341        for i, join in enumerate(this.args.get("joins") or []):
 4342            table = join.this
 4343            normalized_table = table.copy()
 4344            normalized_table.meta["maybe_column"] = True
 4345            normalized_table = _norm(normalized_table, dialect=self.dialect)
 4346
 4347            if isinstance(table, exp.Table) and not join.args.get("on"):
 4348                if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs:
 4349                    table_as_column = table.to_column()
 4350                    unnest = exp.Unnest(expressions=[table_as_column])
 4351
 4352                    # Table.to_column creates a parent Alias node that we want to convert to
 4353                    # a TableAlias and attach to the Unnest, so it matches the parser's output
 4354                    if isinstance(table.args.get("alias"), exp.TableAlias):
 4355                        table_as_column.replace(table_as_column.this)
 4356                        exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False)
 4357
 4358                    table.replace(unnest)
 4359
 4360            refs.add(normalized_table.alias_or_name)
 4361
 4362        return this
 4363
 4364    @t.overload
 4365    def _parse_query_modifiers(self, this: E) -> E: ...
 4366
 4367    @t.overload
 4368    def _parse_query_modifiers(self, this: None) -> None: ...
 4369
 4370    def _parse_query_modifiers(self, this):
 4371        if isinstance(this, self.MODIFIABLES):
 4372            for join in self._parse_joins():
 4373                this.append("joins", join)
 4374            for lateral in iter(self._parse_lateral, None):
 4375                this.append("laterals", lateral)
 4376
 4377            while True:
 4378                if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):
 4379                    modifier_token = self._curr
 4380
 4381                    # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result
 4382                    # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union
 4383                    if (
 4384                        modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH)
 4385                        and (limit := this.args.get("limit"))
 4386                        and limit.meta.get("top")
 4387                    ):
 4388                        break
 4389
 4390                    parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type]
 4391                    key, expression = parser(self)
 4392
 4393                    if expression:
 4394                        if this.args.get(key):
 4395                            self.raise_error(
 4396                                f"Found multiple '{modifier_token.text.upper()}' clauses",
 4397                                token=modifier_token,
 4398                            )
 4399
 4400                        this.set(key, expression)
 4401                        if key == "limit":
 4402                            offset = expression.args.get("offset")
 4403                            expression.set("offset", None)
 4404
 4405                            if offset:
 4406                                if this.args.get("offset"):
 4407                                    self.raise_error(
 4408                                        "Found multiple 'OFFSET' clauses", token=modifier_token
 4409                                    )
 4410
 4411                                offset = exp.Offset(expression=offset)
 4412                                this.set("offset", offset)
 4413
 4414                                limit_by_expressions = expression.expressions
 4415                                expression.set("expressions", None)
 4416                                offset.set("expressions", limit_by_expressions)
 4417                        continue
 4418
 4419                if self._curr.text.upper() == "START":
 4420                    modifier_token = self._curr
 4421                    connect = self._parse_connect()
 4422                    if connect:
 4423                        if this.args.get("connect"):
 4424                            self.raise_error(
 4425                                "Found multiple 'START WITH' clauses", token=modifier_token
 4426                            )
 4427
 4428                        this.set("connect", connect)
 4429                        continue
 4430                break
 4431
 4432        if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"):
 4433            this = self._implicit_unnests_to_explicit(this)
 4434
 4435        return this
 4436
 4437    def _parse_hint_fallback_to_string(self) -> exp.Hint | None:
 4438        start = self._curr
 4439        while self._curr:
 4440            self._advance()
 4441
 4442        end = self._tokens[self._index - 1]
 4443        return exp.Hint(expressions=[self._find_sql(start, end)])
 4444
 4445    def _parse_hint_function_call(self) -> exp.Expr | None:
 4446        return self._parse_function_call()
 4447
 4448    def _parse_hint_body(self) -> exp.Hint | None:
 4449        start_index = self._index
 4450        should_fallback_to_string = False
 4451
 4452        hints = []
 4453        try:
 4454            for hint in iter(
 4455                lambda: self._parse_csv(
 4456                    lambda: self._parse_hint_function_call() or self._parse_var(upper=True),
 4457                ),
 4458                [],
 4459            ):
 4460                hints.extend(hint)
 4461        except ParseError:
 4462            should_fallback_to_string = True
 4463
 4464        if should_fallback_to_string or self._curr:
 4465            self._retreat(start_index)
 4466            return self._parse_hint_fallback_to_string()
 4467
 4468        return self.expression(exp.Hint(expressions=hints))
 4469
 4470    def _parse_hint(self) -> exp.Hint | None:
 4471        if self._match(TokenType.HINT) and self._prev_comments:
 4472            return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect)
 4473
 4474        return None
 4475
 4476    def _parse_into(self) -> exp.Into | None:
 4477        if not self._match(TokenType.INTO):
 4478            return None
 4479
 4480        temp = self._match(TokenType.TEMPORARY)
 4481        unlogged = self._match_text_seq("UNLOGGED")
 4482        self._match(TokenType.TABLE)
 4483
 4484        return self.expression(
 4485            exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged)
 4486        )
 4487
 4488    def _parse_from(
 4489        self,
 4490        joins: bool = False,
 4491        skip_from_token: bool = False,
 4492        consume_pipe: bool = False,
 4493    ) -> exp.From | None:
 4494        if not skip_from_token and not self._match(TokenType.FROM):
 4495            return None
 4496
 4497        comments = self._prev_comments
 4498        return self.expression(
 4499            exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)),
 4500            comments=comments,
 4501        )
 4502
 4503    def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure:
 4504        return self.expression(
 4505            exp.MatchRecognizeMeasure(
 4506                window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(),
 4507                this=self._parse_expression(),
 4508            )
 4509        )
 4510
 4511    def _parse_match_recognize(self) -> exp.MatchRecognize | None:
 4512        if not self._match(TokenType.MATCH_RECOGNIZE):
 4513            return None
 4514
 4515        self._match_l_paren()
 4516
 4517        partition = self._parse_partition_by()
 4518        order = self._parse_order()
 4519
 4520        measures = (
 4521            self._parse_csv(self._parse_match_recognize_measure)
 4522            if self._match_text_seq("MEASURES")
 4523            else None
 4524        )
 4525
 4526        if self._match_text_seq("ONE", "ROW", "PER", "MATCH"):
 4527            rows = exp.var("ONE ROW PER MATCH")
 4528        elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"):
 4529            text = "ALL ROWS PER MATCH"
 4530            if self._match_text_seq("SHOW", "EMPTY", "MATCHES"):
 4531                text += " SHOW EMPTY MATCHES"
 4532            elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"):
 4533                text += " OMIT EMPTY MATCHES"
 4534            elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"):
 4535                text += " WITH UNMATCHED ROWS"
 4536            rows = exp.var(text)
 4537        else:
 4538            rows = None
 4539
 4540        if self._match_text_seq("AFTER", "MATCH", "SKIP"):
 4541            text = "AFTER MATCH SKIP"
 4542            if self._match_text_seq("PAST", "LAST", "ROW"):
 4543                text += " PAST LAST ROW"
 4544            elif self._match_text_seq("TO", "NEXT", "ROW"):
 4545                text += " TO NEXT ROW"
 4546            elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"):
 4547                direction = self._prev.text.upper()
 4548                pattern_var = self._advance_any()
 4549                if not pattern_var:
 4550                    self.raise_error(
 4551                        f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}"
 4552                    )
 4553                text += f" TO {direction} {pattern_var.text if pattern_var else ''}"
 4554            after = exp.var(text)
 4555        else:
 4556            after = None
 4557
 4558        if self._match_text_seq("PATTERN"):
 4559            self._match_l_paren()
 4560
 4561            if not self._curr:
 4562                self.raise_error("Expecting )", self._curr)
 4563
 4564            paren = 1
 4565            start = self._curr
 4566
 4567            while self._curr and paren > 0:
 4568                if self._curr.token_type == TokenType.L_PAREN:
 4569                    paren += 1
 4570                if self._curr.token_type == TokenType.R_PAREN:
 4571                    paren -= 1
 4572
 4573                end = self._prev
 4574                self._advance()
 4575
 4576            if paren > 0:
 4577                self.raise_error("Expecting )", self._curr)
 4578
 4579            pattern = exp.var(self._find_sql(start, end))
 4580        else:
 4581            pattern = None
 4582
 4583        define = (
 4584            self._parse_csv(self._parse_name_as_expression)
 4585            if self._match_text_seq("DEFINE")
 4586            else None
 4587        )
 4588
 4589        self._match_r_paren()
 4590
 4591        return self.expression(
 4592            exp.MatchRecognize(
 4593                partition_by=partition,
 4594                order=order,
 4595                measures=measures,
 4596                rows=rows,
 4597                after=after,
 4598                pattern=pattern,
 4599                define=define,
 4600                alias=self._parse_table_alias(),
 4601            )
 4602        )
 4603
 4604    def _parse_lateral(self) -> exp.Lateral | None:
 4605        cross_apply: bool | None = None
 4606        if self._match_pair(TokenType.CROSS, TokenType.APPLY):
 4607            cross_apply = True
 4608        elif self._match_pair(TokenType.OUTER, TokenType.APPLY):
 4609            cross_apply = False
 4610
 4611        if cross_apply is not None:
 4612            this = self._parse_select(table=True)
 4613            view = None
 4614            outer = None
 4615        elif self._match(TokenType.LATERAL):
 4616            this = self._parse_select(table=True)
 4617            view = self._match(TokenType.VIEW)
 4618            outer = self._match(TokenType.OUTER)
 4619        else:
 4620            return None
 4621
 4622        if not this:
 4623            this = (
 4624                self._parse_unnest()
 4625                or self._parse_function()
 4626                or self._parse_id_var(any_token=False)
 4627            )
 4628
 4629            while self._match(TokenType.DOT):
 4630                this = exp.Dot(
 4631                    this=this,
 4632                    expression=self._parse_function() or self._parse_id_var(any_token=False),
 4633                )
 4634
 4635        ordinality: bool | None = None
 4636
 4637        if view:
 4638            table = self._parse_id_var(any_token=False)
 4639            columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else []
 4640            table_alias: exp.TableAlias | None = self.expression(
 4641                exp.TableAlias(this=table, columns=columns)
 4642            )
 4643        elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias:
 4644            # We move the alias from the lateral's child node to the lateral itself
 4645            table_alias = this.args["alias"].pop()
 4646        else:
 4647            ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 4648            table_alias = self._parse_table_alias()
 4649
 4650        return self.expression(
 4651            exp.Lateral(
 4652                this=this,
 4653                view=view,
 4654                outer=outer,
 4655                alias=table_alias,
 4656                cross_apply=cross_apply,
 4657                ordinality=ordinality,
 4658            )
 4659        )
 4660
 4661    def _parse_stream(self) -> exp.Stream | None:
 4662        index = self._index
 4663        if self._match(TokenType.STREAM):
 4664            if this := self._try_parse(self._parse_table):
 4665                return self.expression(exp.Stream(this=this))
 4666            self._retreat(index)
 4667        return None
 4668
 4669    def _parse_join_parts(
 4670        self,
 4671    ) -> tuple[Token | None, Token | None, Token | None]:
 4672        return (
 4673            self._prev if self._match_set(self.JOIN_METHODS) else None,
 4674            self._prev if self._match_set(self.JOIN_SIDES) else None,
 4675            self._prev if self._match_set(self.JOIN_KINDS) else None,
 4676        )
 4677
 4678    def _parse_using_identifiers(self) -> list[exp.Expr]:
 4679        def _parse_column_as_identifier() -> exp.Expr | None:
 4680            this = self._parse_column()
 4681            if isinstance(this, exp.Column):
 4682                return this.this
 4683            return this
 4684
 4685        return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True)
 4686
 4687    def _parse_join(
 4688        self,
 4689        skip_join_token: bool = False,
 4690        parse_bracket: bool = False,
 4691        alias_tokens: t.Collection[TokenType] | None = None,
 4692    ) -> exp.Join | None:
 4693        if self._match(TokenType.COMMA):
 4694            table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens))
 4695            cross_join = self.expression(exp.Join(this=table)) if table else None
 4696
 4697            if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE:
 4698                cross_join.set("kind", "CROSS")
 4699
 4700            return cross_join
 4701
 4702        index = self._index
 4703        method, side, kind = self._parse_join_parts()
 4704        directed = self._match_text_seq("DIRECTED")
 4705        hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None
 4706        join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN)
 4707        join_comments = self._prev_comments
 4708
 4709        if not skip_join_token and not join:
 4710            self._retreat(index)
 4711            kind = None
 4712            method = None
 4713            side = None
 4714
 4715        outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False)
 4716        cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False)
 4717
 4718        if not skip_join_token and not join and not outer_apply and not cross_apply:
 4719            return None
 4720
 4721        kwargs: dict[str, t.Any] = {
 4722            "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4723        }
 4724        if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA):
 4725            kwargs["expressions"] = self._parse_csv(
 4726                lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4727            )
 4728
 4729        if method:
 4730            kwargs["method"] = method.text.upper()
 4731        if side:
 4732            kwargs["side"] = side.text.upper()
 4733        if kind:
 4734            kwargs["kind"] = kind.text.upper()
 4735        if hint:
 4736            kwargs["hint"] = hint
 4737
 4738        if self._match(TokenType.MATCH_CONDITION):
 4739            kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison)
 4740
 4741        if self._match(TokenType.ON):
 4742            kwargs["on"] = self._parse_disjunction()
 4743        elif self._match(TokenType.USING):
 4744            kwargs["using"] = self._parse_using_identifiers()
 4745        elif (
 4746            not method
 4747            and not (outer_apply or cross_apply)
 4748            and not isinstance(kwargs["this"], exp.Unnest)
 4749            and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY))
 4750        ):
 4751            index = self._index
 4752            joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens))
 4753
 4754            if joins and self._match(TokenType.ON):
 4755                kwargs["on"] = self._parse_disjunction()
 4756            elif joins and self._match(TokenType.USING):
 4757                kwargs["using"] = self._parse_using_identifiers()
 4758            else:
 4759                joins = None
 4760                self._retreat(index)
 4761
 4762            kwargs["this"].set("joins", joins if joins else None)
 4763
 4764        kwargs["pivots"] = self._parse_pivots()
 4765
 4766        comments = [c for token in (method, side, kind) if token for c in token.comments]
 4767        comments = (join_comments or []) + comments
 4768
 4769        if (
 4770            self.ADD_JOIN_ON_TRUE
 4771            and not kwargs.get("on")
 4772            and not kwargs.get("using")
 4773            and not kwargs.get("method")
 4774            and kwargs.get("kind") in (None, "INNER", "OUTER")
 4775        ):
 4776            kwargs["on"] = exp.true()
 4777
 4778        if directed:
 4779            kwargs["directed"] = directed
 4780
 4781        return self.expression(exp.Join(**kwargs), comments=comments)
 4782
 4783    def _parse_opclass(self) -> exp.Expr | None:
 4784        this = self._parse_disjunction()
 4785
 4786        if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False):
 4787            return this
 4788
 4789        if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False):
 4790            return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts()))
 4791
 4792        return this
 4793
 4794    def _parse_index_params(self) -> exp.IndexParameters:
 4795        using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None
 4796
 4797        if self._match(TokenType.L_PAREN, advance=False):
 4798            columns = self._parse_wrapped_csv(self._parse_with_operator)
 4799        else:
 4800            columns = None
 4801
 4802        include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None
 4803        partition_by = self._parse_partition_by()
 4804        with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties()
 4805        tablespace = (
 4806            self._parse_var(any_token=True)
 4807            if self._match_text_seq("USING", "INDEX", "TABLESPACE")
 4808            else None
 4809        )
 4810        where = self._parse_where()
 4811
 4812        on = self._parse_field() if self._match(TokenType.ON) else None
 4813
 4814        return self.expression(
 4815            exp.IndexParameters(
 4816                using=using,
 4817                columns=columns,
 4818                include=include,
 4819                partition_by=partition_by,
 4820                where=where,
 4821                with_storage=with_storage,
 4822                tablespace=tablespace,
 4823                on=on,
 4824            )
 4825        )
 4826
 4827    def _parse_index(
 4828        self, index: exp.Expr | None = None, anonymous: bool = False
 4829    ) -> exp.Index | None:
 4830        if index or anonymous:
 4831            unique = None
 4832            primary = None
 4833            amp = None
 4834
 4835            self._match(TokenType.ON)
 4836            self._match(TokenType.TABLE)  # hive
 4837            table = self._parse_table_parts(schema=True)
 4838        else:
 4839            unique = self._match(TokenType.UNIQUE)
 4840            primary = self._match_text_seq("PRIMARY")
 4841            amp = self._match_text_seq("AMP")
 4842
 4843            if not self._match(TokenType.INDEX):
 4844                return None
 4845
 4846            index = self._parse_id_var()
 4847            table = None
 4848
 4849        params = self._parse_index_params()
 4850
 4851        return self.expression(
 4852            exp.Index(
 4853                this=index, table=table, unique=unique, primary=primary, amp=amp, params=params
 4854            )
 4855        )
 4856
 4857    def _parse_table_hints(self) -> list[exp.Expr] | None:
 4858        hints: list[exp.Expr] = []
 4859        if self._match_pair(TokenType.WITH, TokenType.L_PAREN):
 4860            # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16
 4861            hints.append(
 4862                self.expression(
 4863                    exp.WithTableHint(
 4864                        expressions=self._parse_csv(
 4865                            lambda: self._parse_function() or self._parse_var(any_token=True)
 4866                        )
 4867                    )
 4868                )
 4869            )
 4870            self._match_r_paren()
 4871        else:
 4872            # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
 4873            while self._match_set(self.TABLE_INDEX_HINT_TOKENS):
 4874                hint = exp.IndexTableHint(this=self._prev.text.upper())
 4875
 4876                self._match_set((TokenType.INDEX, TokenType.KEY))
 4877                if self._match(TokenType.FOR):
 4878                    hint.set("target", self._advance_any() and self._prev.text.upper())
 4879
 4880                hint.set("expressions", self._parse_wrapped_id_vars())
 4881                hints.append(hint)
 4882
 4883        return hints or None
 4884
 4885    def _parse_table_part(self, schema: bool = False) -> exp.Expr | None:
 4886        return (
 4887            (not schema and self._parse_function(optional_parens=False))
 4888            or self._parse_id_var(any_token=False)
 4889            or self._parse_string_as_identifier()
 4890            or self._parse_placeholder()
 4891        )
 4892
 4893    def _parse_table_parts_fast(self) -> exp.Table | None:
 4894        index = self._index
 4895        parts: list[exp.Identifier] | None = None
 4896        all_comments: list[str] | None = None
 4897
 4898        while self._match_set(self.IDENTIFIER_TOKENS):
 4899            token = self._prev
 4900            comments = self._prev_comments
 4901
 4902            has_dot = self._match(TokenType.DOT)
 4903            curr_tt = self._curr.token_type
 4904
 4905            if not has_dot:
 4906                if curr_tt in self.TABLE_POSTFIX_TOKENS:
 4907                    self._retreat(index)
 4908                    return None
 4909            elif curr_tt not in self.IDENTIFIER_TOKENS:
 4910                self._retreat(index)
 4911                return None
 4912
 4913            if parts is None:
 4914                parts = []
 4915
 4916            if comments:
 4917                if all_comments is None:
 4918                    all_comments = []
 4919                all_comments.extend(comments)
 4920                self._prev_comments = []
 4921
 4922            parts.append(
 4923                self.expression(
 4924                    exp.Identifier(
 4925                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 4926                    ),
 4927                    token,
 4928                )
 4929            )
 4930
 4931            if not has_dot:
 4932                break
 4933
 4934        if parts is None:
 4935            return None
 4936
 4937        n = len(parts)
 4938
 4939        if n == 1:
 4940            table: exp.Table = exp.Table(this=parts[0])
 4941        elif n == 2:
 4942            table = exp.Table(this=parts[1], db=parts[0])
 4943        elif n >= 3:
 4944            this: exp.Identifier | exp.Dot = parts[2]
 4945            for i in range(3, n):
 4946                this = exp.Dot(this=this, expression=parts[i])
 4947
 4948            table = exp.Table(this=this, db=parts[1], catalog=parts[0])
 4949
 4950        if table is None:
 4951            self._retreat(index)
 4952        elif all_comments:
 4953            table.add_comments(all_comments)
 4954        return table
 4955
 4956    def _parse_table_parts(
 4957        self,
 4958        schema: bool = False,
 4959        is_db_reference: bool = False,
 4960        wildcard: bool = False,
 4961        fast: bool = False,
 4962    ) -> exp.Table | exp.Dot | None:
 4963        if fast:
 4964            return self._parse_table_parts_fast()
 4965
 4966        catalog: exp.Expr | str | None = None
 4967        db: exp.Expr | str | None = None
 4968        table: exp.Expr | str | None = self._parse_table_part(schema=schema)
 4969
 4970        while self._match(TokenType.DOT):
 4971            if catalog:
 4972                # This allows nesting the table in arbitrarily many dot expressions if needed
 4973                table = self.expression(
 4974                    exp.Dot(this=table, expression=self._parse_table_part(schema=schema))
 4975                )
 4976            else:
 4977                catalog = db
 4978                db = table
 4979                # "" used for tsql FROM a..b case
 4980                table = self._parse_table_part(schema=schema) or ""
 4981
 4982        if (
 4983            wildcard
 4984            and self._is_connected()
 4985            and (isinstance(table, exp.Identifier) or not table)
 4986            and self._match(TokenType.STAR)
 4987        ):
 4988            if isinstance(table, exp.Identifier):
 4989                table.args["this"] += "*"
 4990            else:
 4991                table = exp.Identifier(this="*")
 4992
 4993        if is_db_reference:
 4994            catalog = db
 4995            db = table
 4996            table = None
 4997
 4998        if not table and not is_db_reference:
 4999            self.raise_error(f"Expected table name but got {self._curr}")
 5000        if not db and is_db_reference:
 5001            self.raise_error(f"Expected database name but got {self._curr}")
 5002
 5003        table = self.expression(exp.Table(this=table, db=db, catalog=catalog))
 5004
 5005        # Bubble up comments from identifier parts to the Table
 5006        comments = []
 5007        for part in table.parts:
 5008            if part_comments := part.pop_comments():
 5009                comments.extend(part_comments)
 5010        if comments:
 5011            table.add_comments(comments)
 5012
 5013        changes = self._parse_changes()
 5014        if changes:
 5015            table.set("changes", changes)
 5016
 5017        at_before = self._parse_historical_data()
 5018        if at_before:
 5019            table.set("when", at_before)
 5020
 5021        pivots = self._parse_pivots()
 5022        if pivots:
 5023            table.set("pivots", pivots)
 5024
 5025        return table
 5026
 5027    def _parse_table(
 5028        self,
 5029        schema: bool = False,
 5030        joins: bool = False,
 5031        alias_tokens: t.Collection[TokenType] | None = None,
 5032        parse_bracket: bool = False,
 5033        is_db_reference: bool = False,
 5034        parse_partition: bool = False,
 5035        consume_pipe: bool = False,
 5036    ) -> exp.Expr | None:
 5037        if not schema and not is_db_reference and not consume_pipe and not joins:
 5038            index = self._index
 5039            table = self._parse_table_parts(fast=True)
 5040
 5041            if table is not None:
 5042                curr_tt = self._curr.token_type
 5043                next_tt = self._next.token_type
 5044
 5045                fast_terminators = self.TABLE_TERMINATORS
 5046
 5047                # only return the table if we're sure there are no other operators
 5048                # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT
 5049                if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION:
 5050                    return table
 5051
 5052                postfix_tokens = self.TABLE_POSTFIX_TOKENS
 5053
 5054                if curr_tt not in postfix_tokens and next_tt not in postfix_tokens:
 5055                    if alias := self._parse_table_alias(
 5056                        alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS
 5057                    ):
 5058                        table.set("alias", alias)
 5059
 5060                    if self._curr.token_type in fast_terminators:
 5061                        return table
 5062
 5063                self._retreat(index)
 5064
 5065        if stream := self._parse_stream():
 5066            return stream
 5067
 5068        if lateral := self._parse_lateral():
 5069            return lateral
 5070
 5071        if unnest := self._parse_unnest():
 5072            return unnest
 5073
 5074        if values := self._parse_derived_table_values():
 5075            return values
 5076
 5077        if subquery := self._parse_select(table=True, consume_pipe=consume_pipe):
 5078            if not subquery.args.get("pivots"):
 5079                subquery.set("pivots", self._parse_pivots())
 5080            if joins:
 5081                for join in self._parse_joins():
 5082                    subquery.append("joins", join)
 5083            return subquery
 5084
 5085        bracket = parse_bracket and self._parse_bracket(None)
 5086        bracket = self.expression(exp.Table(this=bracket)) if bracket else None
 5087
 5088        rows_from_tables = (
 5089            self._parse_wrapped_csv(self._parse_table)
 5090            if self._match_text_seq("ROWS", "FROM")
 5091            else None
 5092        )
 5093        rows_from = (
 5094            self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None
 5095        )
 5096
 5097        only = self._match(TokenType.ONLY)
 5098
 5099        this = t.cast(
 5100            exp.Expr,
 5101            bracket
 5102            or rows_from
 5103            or self._parse_bracket(
 5104                self._parse_table_parts(schema=schema, is_db_reference=is_db_reference)
 5105            ),
 5106        )
 5107
 5108        if only:
 5109            this.set("only", only)
 5110
 5111        # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context
 5112        self._match(TokenType.STAR)
 5113
 5114        parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION
 5115        if parse_partition and self._match(TokenType.PARTITION, advance=False):
 5116            this.set("partition", self._parse_partition())
 5117
 5118        if schema:
 5119            return self._parse_schema(this=this)
 5120
 5121        if self.dialect.ALIAS_POST_VERSION:
 5122            this.set("version", self._parse_version())
 5123
 5124        if self.dialect.ALIAS_POST_TABLESAMPLE:
 5125            this.set("sample", self._parse_table_sample())
 5126
 5127        alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 5128        if alias:
 5129            this.set("alias", alias)
 5130
 5131            # DuckDB requires the time-travel clause to come after the alias, e.g.
 5132            # SELECT * FROM t AS a AT (VERSION => 1)
 5133            if isinstance(this, exp.Table) and not this.args.get("when"):
 5134                this.set("when", self._parse_historical_data())
 5135
 5136        if self._match(TokenType.INDEXED_BY):
 5137            this.set("indexed", self._parse_table_parts())
 5138        elif self._match_text_seq("NOT", "INDEXED"):
 5139            this.set("indexed", False)
 5140
 5141        if isinstance(this, exp.Table) and self._match_text_seq("AT"):
 5142            return self.expression(
 5143                exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var())
 5144            )
 5145
 5146        this.set("hints", self._parse_table_hints())
 5147
 5148        if not this.args.get("pivots"):
 5149            this.set("pivots", self._parse_pivots())
 5150
 5151        if not self.dialect.ALIAS_POST_TABLESAMPLE:
 5152            this.set("sample", self._parse_table_sample())
 5153
 5154        if not self.dialect.ALIAS_POST_VERSION:
 5155            this.set("version", self._parse_version())
 5156
 5157        if joins:
 5158            for join in self._parse_joins(alias_tokens=alias_tokens):
 5159                this.append("joins", join)
 5160
 5161        if self._match_pair(TokenType.WITH, TokenType.ORDINALITY):
 5162            this.set("ordinality", True)
 5163            this.set("alias", self._parse_table_alias())
 5164
 5165        # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we
 5166        # hoist the table args onto the latter and return it instead
 5167        if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows):
 5168            table_from_rows = this.this
 5169            for arg in exp.TableFromRows.arg_types:
 5170                if arg != "this":
 5171                    table_from_rows.set(arg, this.args.get(arg))
 5172
 5173            this = table_from_rows
 5174
 5175        return this
 5176
 5177    def _parse_version(self) -> exp.Version | None:
 5178        for phrase, this in self.VERSION_PHRASES.items():
 5179            if self._match_text_seq(*phrase):
 5180                break
 5181        else:
 5182            return None
 5183
 5184        if self._match_set((TokenType.FROM, TokenType.BETWEEN)):
 5185            kind = self._prev.text.upper()
 5186            start = self._parse_bitwise()
 5187            self._match_texts(("TO", "AND"))
 5188            end = self._parse_bitwise()
 5189            expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end]))
 5190        elif self._match_text_seq("CONTAINED", "IN"):
 5191            kind = "CONTAINED IN"
 5192            expression = self.expression(
 5193                exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise))
 5194            )
 5195        elif self._match(TokenType.ALL):
 5196            kind = "ALL"
 5197            expression = None
 5198        else:
 5199            self._match_text_seq("AS", "OF")
 5200            kind = "AS OF"
 5201            expression = self._parse_type()
 5202
 5203        return self.expression(exp.Version(this=this, expression=expression, kind=kind))
 5204
 5205    def _parse_historical_data(self) -> exp.HistoricalData | None:
 5206        # https://docs.snowflake.com/en/sql-reference/constructs/at-before
 5207        index = self._index
 5208        historical_data = None
 5209        if self._match_texts(self.HISTORICAL_DATA_PREFIX):
 5210            this = self._prev.text.upper()
 5211            kind = (
 5212                self._match(TokenType.L_PAREN)
 5213                and self._match_texts(self.HISTORICAL_DATA_KIND)
 5214                and self._prev.text.upper()
 5215            )
 5216            expression = self._match(TokenType.FARROW) and self._parse_bitwise()
 5217
 5218            if expression:
 5219                self._match_r_paren()
 5220                historical_data = self.expression(
 5221                    exp.HistoricalData(this=this, kind=kind, expression=expression)
 5222                )
 5223            else:
 5224                self._retreat(index)
 5225
 5226        return historical_data
 5227
 5228    def _parse_changes(self) -> exp.Changes | None:
 5229        if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"):
 5230            return None
 5231
 5232        information = self._parse_var(any_token=True)
 5233        self._match_r_paren()
 5234
 5235        return self.expression(
 5236            exp.Changes(
 5237                information=information,
 5238                at_before=self._parse_historical_data(),
 5239                end=self._parse_historical_data(),
 5240            )
 5241        )
 5242
 5243    def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None:
 5244        if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False):
 5245            return None
 5246
 5247        self._advance()
 5248
 5249        expressions = self._parse_wrapped_csv(self._parse_equality)
 5250        offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 5251
 5252        alias = self._parse_table_alias() if with_alias else None
 5253
 5254        if alias:
 5255            if self.dialect.UNNEST_COLUMN_ONLY:
 5256                if alias.args.get("columns"):
 5257                    self.raise_error("Unexpected extra column alias in unnest.")
 5258
 5259                alias.set("columns", [alias.this])
 5260                alias.set("this", None)
 5261
 5262            columns = alias.args.get("columns") or []
 5263            if offset and len(expressions) < len(columns):
 5264                offset = columns.pop()
 5265
 5266        if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET):
 5267            self._match(TokenType.ALIAS)
 5268            offset = self._parse_id_var(
 5269                any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS
 5270            ) or exp.to_identifier("offset")
 5271
 5272        return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset))
 5273
 5274    def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None:
 5275        is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES)
 5276        if not is_derived and not (
 5277            # ClickHouse's `FORMAT Values` is equivalent to `VALUES`
 5278            self._match_text_seq("VALUES")
 5279            or self._match_text_seq("FORMAT", "VALUES")
 5280            # MySQL accepts VALUE as a synonym for VALUES in INSERT statements.
 5281            or (allow_value_synonym and self._match_text_seq("VALUE"))
 5282        ):
 5283            return None
 5284
 5285        expressions = self._parse_csv(self._parse_value)
 5286        alias = self._parse_table_alias()
 5287
 5288        if is_derived:
 5289            self._match_r_paren()
 5290
 5291        return self.expression(
 5292            exp.Values(expressions=expressions, alias=alias or self._parse_table_alias())
 5293        )
 5294
 5295    def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None:
 5296        if not self._match(TokenType.TABLE_SAMPLE) and not (
 5297            as_modifier and self._match_text_seq("USING", "SAMPLE")
 5298        ):
 5299            return None
 5300
 5301        bucket_numerator = None
 5302        bucket_denominator = None
 5303        bucket_field = None
 5304        percent = None
 5305        size = None
 5306        seed = None
 5307
 5308        method = self._parse_var(tokens=(TokenType.ROW,), upper=True)
 5309        matched_l_paren = self._match(TokenType.L_PAREN)
 5310
 5311        if self.TABLESAMPLE_CSV:
 5312            num = None
 5313            expressions = self._parse_csv(self._parse_primary)
 5314        else:
 5315            expressions = None
 5316            num = (
 5317                self._parse_factor(parse_mod=False)
 5318                if self._match(TokenType.NUMBER, advance=False)
 5319                else self._parse_primary() or self._parse_placeholder()
 5320            )
 5321
 5322        if self._match_text_seq("BUCKET"):
 5323            bucket_numerator = self._parse_number()
 5324            self._match_text_seq("OUT", "OF")
 5325            bucket_denominator = bucket_denominator = self._parse_number()
 5326            self._match(TokenType.ON)
 5327            bucket_field = self._parse_field()
 5328        elif self._match_set((TokenType.PERCENT, TokenType.MOD)):
 5329            percent = num
 5330        elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
 5331            size = num
 5332        else:
 5333            percent = num
 5334
 5335        if matched_l_paren:
 5336            self._match_r_paren()
 5337
 5338        if self._match(TokenType.L_PAREN):
 5339            method = self._parse_var(upper=True)
 5340            seed = self._match(TokenType.COMMA) and self._parse_number()
 5341            self._match_r_paren()
 5342        elif self._match_texts(("SEED", "REPEATABLE")):
 5343            seed = self._parse_wrapped(self._parse_number)
 5344
 5345        if not method and self.DEFAULT_SAMPLING_METHOD:
 5346            method = exp.var(self.DEFAULT_SAMPLING_METHOD)
 5347
 5348        return self.expression(
 5349            exp.TableSample(
 5350                expressions=expressions,
 5351                method=method,
 5352                bucket_numerator=bucket_numerator,
 5353                bucket_denominator=bucket_denominator,
 5354                bucket_field=bucket_field,
 5355                percent=percent,
 5356                size=size,
 5357                seed=seed,
 5358            )
 5359        )
 5360
 5361    def _parse_pivots(self) -> list[exp.Pivot] | None:
 5362        if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT):
 5363            return None
 5364        return list(iter(self._parse_pivot, None)) or None
 5365
 5366    def _parse_joins(
 5367        self, alias_tokens: t.Collection[TokenType] | None = None
 5368    ) -> t.Iterator[exp.Join]:
 5369        return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None)
 5370
 5371    def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None:
 5372        if not self._match(TokenType.INTO):
 5373            return None
 5374
 5375        return self.expression(
 5376            exp.UnpivotColumns(
 5377                this=self._match_text_seq("NAME") and self._parse_column(),
 5378                expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column),
 5379            )
 5380        )
 5381
 5382    # https://duckdb.org/docs/sql/statements/pivot
 5383    def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot:
 5384        def _parse_on() -> exp.Expr | None:
 5385            this = self._parse_bitwise()
 5386
 5387            if self._match(TokenType.IN):
 5388                # PIVOT ... ON col IN (row_val1, row_val2)
 5389                return self._parse_in(this)
 5390            if self._match(TokenType.ALIAS, advance=False):
 5391                # UNPIVOT ... ON (col1, col2, col3) AS row_val
 5392                return self._parse_alias(this)
 5393
 5394            return this
 5395
 5396        this = self._parse_table()
 5397        expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on)
 5398        into = self._parse_unpivot_columns()
 5399        using = self._match(TokenType.USING) and self._parse_csv(
 5400            lambda: self._parse_alias(self._parse_column())
 5401        )
 5402        group = self._parse_group()
 5403
 5404        return self.expression(
 5405            exp.Pivot(
 5406                this=this,
 5407                expressions=expressions,
 5408                using=using,
 5409                group=group,
 5410                unpivot=is_unpivot,
 5411                into=into,
 5412            )
 5413        )
 5414
 5415    def _parse_pivot_in(self) -> exp.In:
 5416        def _parse_aliased_expression() -> exp.Expr | None:
 5417            this = self._parse_select_or_expression()
 5418
 5419            self._match(TokenType.ALIAS)
 5420            alias = self._parse_bitwise()
 5421            if alias:
 5422                if isinstance(alias, exp.Column) and not alias.db:
 5423                    alias = alias.this
 5424                return self.expression(exp.PivotAlias(this=this, alias=alias))
 5425
 5426            return this
 5427
 5428        value = self._parse_column()
 5429
 5430        if not self._match(TokenType.IN):
 5431            self.raise_error("Expecting IN")
 5432
 5433        if self._match(TokenType.L_PAREN):
 5434            if self._match(TokenType.ANY):
 5435                exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order()))
 5436            else:
 5437                exprs = self._parse_csv(_parse_aliased_expression)
 5438            self._match_r_paren()
 5439            return self.expression(exp.In(this=value, expressions=exprs))
 5440
 5441        return self.expression(exp.In(this=value, field=self._parse_id_var()))
 5442
 5443    def _parse_pivot_aggregation(self) -> exp.Expr | None:
 5444        func = self._parse_function()
 5445        if not func:
 5446            if self._prev.token_type == TokenType.COMMA:
 5447                return None
 5448            self.raise_error("Expecting an aggregation function in PIVOT")
 5449
 5450        return self._parse_alias(func)
 5451
 5452    def _parse_pivot(self) -> exp.Pivot | None:
 5453        index = self._index
 5454        include_nulls = None
 5455
 5456        if self._match(TokenType.PIVOT):
 5457            unpivot = False
 5458        elif self._match(TokenType.UNPIVOT):
 5459            unpivot = True
 5460
 5461            # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax
 5462            if self._match_text_seq("INCLUDE", "NULLS"):
 5463                include_nulls = True
 5464            elif self._match_text_seq("EXCLUDE", "NULLS"):
 5465                include_nulls = False
 5466        else:
 5467            return None
 5468
 5469        expressions = []
 5470
 5471        if not self._match(TokenType.L_PAREN):
 5472            self._retreat(index)
 5473            return None
 5474
 5475        if unpivot:
 5476            expressions = self._parse_csv(self._parse_column)
 5477        else:
 5478            expressions = self._parse_csv(self._parse_pivot_aggregation)
 5479
 5480        if not expressions:
 5481            self.raise_error("Failed to parse PIVOT's aggregation list")
 5482
 5483        if not self._match(TokenType.FOR):
 5484            self.raise_error("Expecting FOR")
 5485
 5486        fields = []
 5487        while True:
 5488            field = self._try_parse(self._parse_pivot_in)
 5489            if not field:
 5490                break
 5491            fields.append(field)
 5492
 5493        default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped(
 5494            self._parse_bitwise
 5495        )
 5496
 5497        group = self._parse_group()
 5498
 5499        self._match_r_paren()
 5500
 5501        pivot = self.expression(
 5502            exp.Pivot(
 5503                expressions=expressions,
 5504                fields=fields,
 5505                unpivot=unpivot,
 5506                include_nulls=include_nulls,
 5507                default_on_null=default_on_null,
 5508                group=group,
 5509            )
 5510        )
 5511
 5512        if unpivot:
 5513            pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions])
 5514            for pivot_field in pivot.fields:
 5515                if isinstance(pivot_field, exp.In):
 5516                    pivot_field.set("this", _unpivot_target(pivot_field.this))
 5517
 5518            pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST)
 5519
 5520        if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False):
 5521            pivot.set("alias", self._parse_table_alias())
 5522
 5523        if not unpivot:
 5524            names = self._pivot_column_names(t.cast(list[exp.Expr], expressions))
 5525
 5526            columns: list[exp.Expr] = []
 5527            all_fields = []
 5528            for pivot_field in pivot.fields:
 5529                pivot_field_expressions = pivot_field.expressions
 5530
 5531                # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case.
 5532                if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny):
 5533                    continue
 5534
 5535                all_fields.append(
 5536                    [
 5537                        # An explicit `<field> AS <alias>` names the output column directly,
 5538                        # so it wins over the dialect's string-identifying convention
 5539                        fld.sql()
 5540                        if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias)
 5541                        else fld.alias_or_name
 5542                        for fld in pivot_field_expressions
 5543                    ]
 5544                )
 5545
 5546            if all_fields:
 5547                if names:
 5548                    all_fields.append(names)
 5549
 5550                # Generate all possible combinations of the pivot columns
 5551                # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US'))
 5552                # generates the product between [[2000, 2010], ['NL', 'US'], ['total']]
 5553                for fld_parts_tuple in itertools.product(*all_fields):
 5554                    fld_parts = list(fld_parts_tuple)
 5555
 5556                    if names and self.PREFIXED_PIVOT_COLUMNS:
 5557                        # Move the "name" to the front of the list
 5558                        fld_parts.insert(0, fld_parts.pop(-1))
 5559
 5560                    columns.append(exp.to_identifier("_".join(fld_parts)))
 5561
 5562            pivot.set("columns", columns)
 5563            pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS)
 5564            pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS)
 5565            pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING)
 5566
 5567        return pivot
 5568
 5569    def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]:
 5570        return [agg.alias for agg in aggregations if agg.alias]
 5571
 5572    def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None:
 5573        if not skip_where_token and not self._match(TokenType.PREWHERE):
 5574            return None
 5575
 5576        comments = self._prev_comments
 5577        return self.expression(
 5578            exp.PreWhere(this=self._parse_disjunction()),
 5579            comments=comments,
 5580        )
 5581
 5582    def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None:
 5583        if not skip_where_token and not self._match(TokenType.WHERE):
 5584            return None
 5585
 5586        comments = self._prev_comments
 5587        return self.expression(
 5588            exp.Where(this=self._parse_disjunction()),
 5589            comments=comments,
 5590        )
 5591
 5592    def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None:
 5593        if not skip_group_by_token and not self._match(TokenType.GROUP_BY):
 5594            return None
 5595        comments = self._prev_comments
 5596
 5597        elements: dict[str, t.Any] = defaultdict(list)
 5598
 5599        if self._match(TokenType.ALL):
 5600            elements["all"] = True
 5601        elif self._match(TokenType.DISTINCT):
 5602            elements["all"] = False
 5603
 5604        while True:
 5605            # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW,
 5606            # which are also valid identifiers
 5607            if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False):
 5608                break
 5609
 5610            elements["expressions"].extend(
 5611                self._parse_csv(
 5612                    lambda: (
 5613                        self._parse_grouping_sets()
 5614                        or self._parse_cube_or_rollup()
 5615                        or self._parse_disjunction()
 5616                    )
 5617                )
 5618            )
 5619
 5620            before_with_index = self._index
 5621
 5622            if self._match(TokenType.WITH) and (
 5623                cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True)
 5624            ):
 5625                key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube"
 5626                elements[key].append(cube_or_rollup)
 5627            elif grouping_sets := self._parse_grouping_sets():
 5628                # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...)
 5629                elements["grouping_sets"].append(grouping_sets)
 5630                break
 5631            elif self._match_text_seq("TOTALS"):
 5632                elements["totals"] = True  # type: ignore
 5633
 5634            if before_with_index <= self._index <= before_with_index + 1:
 5635                self._retreat(before_with_index)
 5636                break
 5637
 5638        return self.expression(exp.Group(**elements), comments=comments)  # type: ignore
 5639
 5640    def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None:
 5641        if self._match(TokenType.CUBE):
 5642            kind: type[exp.Cube | exp.Rollup] = exp.Cube
 5643        elif self._match(TokenType.ROLLUP):
 5644            kind = exp.Rollup
 5645        else:
 5646            return None
 5647
 5648        return self.expression(
 5649            kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise))
 5650        )
 5651
 5652    def _parse_grouping_sets(self) -> exp.GroupingSets | None:
 5653        if self._match(TokenType.GROUPING_SETS):
 5654            return self.expression(
 5655                exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set))
 5656            )
 5657        return None
 5658
 5659    def _parse_grouping_set(self) -> exp.Expr | None:
 5660        return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise()
 5661
 5662    def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None:
 5663        if not skip_having_token and not self._match(TokenType.HAVING):
 5664            return None
 5665        comments = self._prev_comments
 5666        return self.expression(
 5667            exp.Having(this=self._parse_disjunction()),
 5668            comments=comments,
 5669        )
 5670
 5671    def _parse_qualify(self) -> exp.Qualify | None:
 5672        if not self._match(TokenType.QUALIFY):
 5673            return None
 5674        return self.expression(exp.Qualify(this=self._parse_disjunction()))
 5675
 5676    def _parse_connect_with_prior(self) -> exp.Expr | None:
 5677        self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression(
 5678            exp.Prior(this=self._parse_bitwise())
 5679        )
 5680        connect = self._parse_disjunction()
 5681        self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR")
 5682        return connect
 5683
 5684    def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None:
 5685        if skip_start_token:
 5686            start = None
 5687        elif self._match_text_seq("START", "WITH"):
 5688            start = self._parse_disjunction()
 5689        else:
 5690            return None
 5691
 5692        self._match(TokenType.CONNECT_BY)
 5693        nocycle = self._match_text_seq("NOCYCLE")
 5694        connect = self._parse_connect_with_prior()
 5695
 5696        if not start and self._match_text_seq("START", "WITH"):
 5697            start = self._parse_disjunction()
 5698
 5699        return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle))
 5700
 5701    def _parse_name_as_expression(self) -> exp.Expr | None:
 5702        this = self._parse_id_var(any_token=True)
 5703        if self._match(TokenType.ALIAS):
 5704            this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction()))
 5705        return this
 5706
 5707    def _parse_interpolate(self) -> list[exp.Expr] | None:
 5708        if self._match_text_seq("INTERPOLATE"):
 5709            return self._parse_wrapped_csv(self._parse_name_as_expression)
 5710        return None
 5711
 5712    def _parse_order(
 5713        self, this: exp.Expr | None = None, skip_order_token: bool = False
 5714    ) -> exp.Expr | None:
 5715        siblings = None
 5716        if not skip_order_token and not self._match(TokenType.ORDER_BY):
 5717            if not self._match(TokenType.ORDER_SIBLINGS_BY):
 5718                return this
 5719
 5720            siblings = True
 5721
 5722        comments = self._prev_comments
 5723        return self.expression(
 5724            exp.Order(
 5725                this=this,
 5726                expressions=self._parse_csv(self._parse_ordered),
 5727                siblings=siblings,
 5728            ),
 5729            comments=comments,
 5730        )
 5731
 5732    def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None:
 5733        if not self._match(token):
 5734            return None
 5735        return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered)))
 5736
 5737    def _parse_ordered(
 5738        self, parse_method: t.Callable[[], exp.Expr | None] | None = None
 5739    ) -> exp.Ordered | None:
 5740        this = parse_method() if parse_method else self._parse_disjunction()
 5741        if not this:
 5742            return None
 5743
 5744        if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL:
 5745            this = exp.var("ALL")
 5746
 5747        asc = self._match(TokenType.ASC)
 5748        desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None)
 5749
 5750        is_nulls_first = self._match_text_seq("NULLS", "FIRST")
 5751        is_nulls_last = self._match_text_seq("NULLS", "LAST")
 5752
 5753        nulls_first = is_nulls_first or False
 5754        explicitly_null_ordered = is_nulls_first or is_nulls_last
 5755
 5756        if (
 5757            not explicitly_null_ordered
 5758            and (
 5759                (not desc and self.dialect.NULL_ORDERING == "nulls_are_small")
 5760                or (desc and self.dialect.NULL_ORDERING != "nulls_are_small")
 5761            )
 5762            and self.dialect.NULL_ORDERING != "nulls_are_last"
 5763        ):
 5764            nulls_first = True
 5765
 5766        if self._match_text_seq("WITH", "FILL"):
 5767            with_fill = self.expression(
 5768                exp.WithFill(
 5769                    from_=self._match(TokenType.FROM) and self._parse_bitwise(),
 5770                    to=self._match_text_seq("TO") and self._parse_bitwise(),
 5771                    step=self._match_text_seq("STEP") and self._parse_bitwise(),
 5772                    interpolate=self._parse_interpolate(),
 5773                )
 5774            )
 5775        else:
 5776            with_fill = None
 5777
 5778        return self.expression(
 5779            exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill)
 5780        )
 5781
 5782    def _parse_limit_options(self) -> exp.LimitOptions | None:
 5783        percent = self._match_set((TokenType.PERCENT, TokenType.MOD))
 5784        rows = self._match_texts(("ROW", "ROWS"))
 5785        self._match_text_seq("ONLY")
 5786        with_ties = self._match_text_seq("WITH", "TIES")
 5787
 5788        if not (percent or rows or with_ties):
 5789            return None
 5790
 5791        return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties))
 5792
 5793    def _parse_limit(
 5794        self,
 5795        this: exp.Expr | None = None,
 5796        top: bool = False,
 5797        skip_limit_token: bool = False,
 5798    ) -> exp.Expr | None:
 5799        if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT):
 5800            comments = self._prev_comments
 5801            if top:
 5802                limit_paren = self._match(TokenType.L_PAREN)
 5803                expression = (
 5804                    self._parse_term() or self._parse_select()
 5805                    if limit_paren
 5806                    else self._parse_number()
 5807                )
 5808
 5809                if limit_paren:
 5810                    self._match_r_paren()
 5811
 5812            else:
 5813                if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL):
 5814                    return this
 5815
 5816                expression = self._parse_term(parse_mod=False)
 5817            limit_options = self._parse_limit_options()
 5818
 5819            if self._match(TokenType.COMMA):
 5820                offset = expression
 5821                expression = self._parse_term()
 5822            else:
 5823                offset = None
 5824
 5825            limit_exp = self.expression(
 5826                exp.Limit(
 5827                    this=this,
 5828                    expression=expression,
 5829                    offset=offset,
 5830                    limit_options=limit_options,
 5831                    expressions=self._parse_limit_by(),
 5832                ),
 5833                comments=comments,
 5834            )
 5835
 5836            if top:
 5837                limit_exp.meta["top"] = True
 5838
 5839            return limit_exp
 5840
 5841        if self._match(TokenType.FETCH):
 5842            direction = (
 5843                self._prev.text.upper()
 5844                if self._match_set((TokenType.FIRST, TokenType.NEXT))
 5845                else "FIRST"
 5846            )
 5847
 5848            count = (
 5849                None
 5850                if self._match_texts(("ROW", "ROWS"), advance=False)
 5851                else self._parse_field(tokens=self.FETCH_TOKENS)
 5852            )
 5853
 5854            return self.expression(
 5855                exp.Fetch(
 5856                    direction=direction, count=count, limit_options=self._parse_limit_options()
 5857                )
 5858            )
 5859
 5860        return this
 5861
 5862    def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None:
 5863        if not self._match(TokenType.OFFSET):
 5864            return this
 5865
 5866        count = self._parse_term()
 5867        self._match_texts(("ROW", "ROWS"))
 5868
 5869        return self.expression(
 5870            exp.Offset(this=this, expression=count, expressions=self._parse_limit_by())
 5871        )
 5872
 5873    def _can_parse_limit_or_offset(self) -> bool:
 5874        if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False):
 5875            return False
 5876
 5877        index = self._index
 5878        result = bool(
 5879            self._try_parse(self._parse_limit, retreat=True)
 5880            or self._try_parse(self._parse_offset, retreat=True)
 5881        )
 5882        self._retreat(index)
 5883
 5884        # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset
 5885        if self._next.token_type == TokenType.MATCH_CONDITION:
 5886            result = False
 5887
 5888        return result
 5889
 5890    def _can_parse_named_window(self) -> bool:
 5891        # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias.
 5892        # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`.
 5893        if not self._match(TokenType.WINDOW, advance=False):
 5894            return False
 5895
 5896        name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None
 5897        if name is None or name.token_type not in self.ID_VAR_TOKENS:
 5898            return False
 5899
 5900        alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None
 5901        if alias_tok is None or alias_tok.token_type != TokenType.ALIAS:
 5902            return False
 5903
 5904        body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None
 5905        return body is not None and body.token_type == TokenType.L_PAREN
 5906
 5907    def _parse_limit_by(self) -> list[exp.Expr] | None:
 5908        return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None
 5909
 5910    def _parse_locks(self) -> list[exp.Lock]:
 5911        locks = []
 5912        while True:
 5913            update, key = None, None
 5914            if self._match_text_seq("FOR", "UPDATE"):
 5915                update = True
 5916            elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq(
 5917                "LOCK", "IN", "SHARE", "MODE"
 5918            ):
 5919                update = False
 5920            elif self._match_text_seq("FOR", "KEY", "SHARE"):
 5921                update, key = False, True
 5922            elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"):
 5923                update, key = True, True
 5924            else:
 5925                break
 5926
 5927            expressions = None
 5928            if self._match_text_seq("OF"):
 5929                expressions = self._parse_csv(lambda: self._parse_table(schema=True))
 5930
 5931            wait: bool | exp.Expr | None = None
 5932            if self._match_text_seq("NOWAIT"):
 5933                wait = True
 5934            elif self._match_text_seq("WAIT"):
 5935                wait = self._parse_primary()
 5936            elif self._match_text_seq("SKIP", "LOCKED"):
 5937                wait = False
 5938
 5939            locks.append(
 5940                self.expression(
 5941                    exp.Lock(update=update, expressions=expressions, wait=wait, key=key)
 5942                )
 5943            )
 5944
 5945        return locks
 5946
 5947    def parse_set_operation(
 5948        self, this: exp.Expr | None, consume_pipe: bool = False
 5949    ) -> exp.Expr | None:
 5950        start = self._index
 5951        _, side_token, kind_token = self._parse_join_parts()
 5952
 5953        side = side_token.text if side_token else None
 5954        kind = kind_token.text if kind_token else None
 5955
 5956        if not self._match_set(self.SET_OPERATIONS):
 5957            self._retreat(start)
 5958            return None
 5959
 5960        token_type = self._prev.token_type
 5961
 5962        if token_type == TokenType.UNION:
 5963            operation: type[exp.SetOperation] = exp.Union
 5964        elif token_type == TokenType.EXCEPT:
 5965            operation = exp.Except
 5966        else:
 5967            operation = exp.Intersect
 5968
 5969        comments = self._prev.comments
 5970
 5971        if self._match(TokenType.DISTINCT):
 5972            distinct: bool | None = True
 5973        elif self._match(TokenType.ALL):
 5974            distinct = False
 5975        else:
 5976            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
 5977            if distinct is None:
 5978                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
 5979
 5980        by_name = (
 5981            self._match_text_seq("BY", "NAME")
 5982            or self._match_text_seq("STRICT", "CORRESPONDING")
 5983            or None
 5984        )
 5985        if self._match_text_seq("CORRESPONDING"):
 5986            by_name = True
 5987            if not side and not kind:
 5988                kind = "INNER"
 5989
 5990        on_column_list = None
 5991        if by_name and self._match_texts(("ON", "BY")):
 5992            on_column_list = self._parse_wrapped_csv(self._parse_column)
 5993
 5994        expression = self._parse_select(
 5995            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
 5996        )
 5997
 5998        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
 5999        # in _parse_cte and so that alias pushdown can reach into set operation branches
 6000        if isinstance(this, exp.Values):
 6001            this = self._values_to_select(this)
 6002        if isinstance(expression, exp.Values):
 6003            expression = self._values_to_select(expression)
 6004
 6005        if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery):
 6006            subquery = this.this
 6007            subquery.set("alias", exp.TableAlias(this=this.args["alias"]))
 6008            subquery.add_comments(this.pop_comments())
 6009            this = subquery
 6010
 6011        return self.expression(
 6012            operation(
 6013                this=this,
 6014                distinct=distinct,
 6015                by_name=by_name,
 6016                expression=expression,
 6017                side=side,
 6018                kind=kind,
 6019                on=on_column_list,
 6020            ),
 6021            comments=comments,
 6022        )
 6023
 6024    def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None:
 6025        while this:
 6026            setop = self.parse_set_operation(this)
 6027            if not setop:
 6028                break
 6029            this = setop
 6030
 6031        if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP:
 6032            expression = this.expression
 6033
 6034            if expression:
 6035                for arg in self.SET_OP_MODIFIERS:
 6036                    expr = expression.args.get(arg)
 6037                    if expr and not (arg == "limit" and expr.meta.get("top")):
 6038                        expression.set(arg, None)
 6039                        this.set(arg, expr)
 6040
 6041            # A trailing LIMIT/FETCH can coexist with TOP on the final operand.
 6042            if self._curr.token_type in (TokenType.LIMIT, TokenType.FETCH):
 6043                this = self._parse_query_modifiers(this)
 6044
 6045        return this
 6046
 6047    def _parse_expression(self) -> exp.Expr | None:
 6048        return self._parse_alias(self._parse_assignment())
 6049
 6050    def _parse_assignment(self) -> exp.Expr | None:
 6051        this = self._parse_disjunction()
 6052        if not this and self._next.token_type in self.ASSIGNMENT:
 6053            # This allows us to parse <non-identifier token> := <expr>
 6054            this = exp.column(
 6055                t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text)
 6056            )
 6057
 6058        while self._match_set(self.ASSIGNMENT):
 6059            if isinstance(this, exp.Column) and len(this.parts) == 1:
 6060                this = this.this
 6061
 6062            comments = self._prev_comments
 6063            this = self.expression(
 6064                self.ASSIGNMENT[self._prev.token_type](
 6065                    this=this, expression=self._parse_assignment()
 6066                ),
 6067                comments=comments,
 6068            )
 6069
 6070        return this
 6071
 6072    def _parse_disjunction(self) -> exp.Expr | None:
 6073        this = self._parse_conjunction()
 6074        while self._match_set(self.DISJUNCTION):
 6075            comments = self._prev_comments
 6076            this = self.expression(
 6077                self.DISJUNCTION[self._prev.token_type](
 6078                    this=this, expression=self._parse_conjunction()
 6079                ),
 6080                comments=comments,
 6081            )
 6082        return this
 6083
 6084    def _parse_conjunction(self) -> exp.Expr | None:
 6085        this = self._parse_equality()
 6086        while self._match_set(self.CONJUNCTION):
 6087            comments = self._prev_comments
 6088            this = self.expression(
 6089                self.CONJUNCTION[self._prev.token_type](
 6090                    this=this, expression=self._parse_equality()
 6091                ),
 6092                comments=comments,
 6093            )
 6094        return this
 6095
 6096    def _parse_equality(self) -> exp.Expr | None:
 6097        this = self._parse_comparison()
 6098        while self._match_set(self.EQUALITY):
 6099            comments = self._prev_comments
 6100            this = self.expression(
 6101                self.EQUALITY[self._prev.token_type](
 6102                    this=this, expression=self._parse_comparison()
 6103                ),
 6104                comments=comments,
 6105            )
 6106        return this
 6107
 6108    def _parse_comparison(self) -> exp.Expr | None:
 6109        this = self._parse_range()
 6110        while self._match_set(self.COMPARISON):
 6111            comments = self._prev_comments
 6112            this = self.expression(
 6113                self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()),
 6114                comments=comments,
 6115            )
 6116        return this
 6117
 6118    def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6119        this = this or self._parse_bitwise()
 6120
 6121        while True:
 6122            negate = self._match(TokenType.NOT)
 6123            if self._match_set(self.RANGE_PARSERS):
 6124                expression = self.RANGE_PARSERS[self._prev.token_type](self, this)
 6125                if not expression:
 6126                    return this
 6127
 6128                this = expression
 6129            elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)):
 6130                this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6131            elif self._match(TokenType.NOTNULL):
 6132                # Postgres supports ISNULL and NOTNULL for conditions.
 6133                # https://blog.andreiavram.ro/postgresql-null-composite-type/
 6134                if self.dialect.NORMALIZE_NOT_NULL:
 6135                    this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6136                    this = self.expression(exp.Not(this=this))
 6137                else:
 6138                    this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True))
 6139            else:
 6140                if negate:
 6141                    self._retreat(self._index - 1)
 6142                break
 6143
 6144            if negate:
 6145                this = self._negate_range(this)
 6146                if self._curr and (
 6147                    self._curr.token_type == TokenType.NOT
 6148                    or self._curr.token_type in self.RANGE_PARSERS
 6149                ):
 6150                    this = self.expression(exp.Paren(this=this))
 6151
 6152        return this
 6153
 6154    def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6155        if not this:
 6156            return this
 6157
 6158        expression = this.this if isinstance(this, exp.Escape) else this
 6159        if isinstance(expression, (exp.Like, exp.ILike)):
 6160            expression.set("negate", True)
 6161            return this
 6162
 6163        return self.expression(exp.Not(this=this))
 6164
 6165    def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None:
 6166        index = self._index - 1
 6167        negate = self._match(TokenType.NOT)
 6168
 6169        if self._match_text_seq("DISTINCT", "FROM"):
 6170            klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ
 6171            return self.expression(klass(this=this, expression=self._parse_bitwise()))
 6172
 6173        if self._match(TokenType.JSON):
 6174            kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper()
 6175
 6176            if self._match_text_seq("WITH"):
 6177                _with = True
 6178            elif self._match_text_seq("WITHOUT"):
 6179                _with = False
 6180            else:
 6181                _with = None
 6182
 6183            unique = self._match(TokenType.UNIQUE)
 6184            self._match_text_seq("KEYS")
 6185            expression: exp.Expr | None = self.expression(
 6186                exp.JSON(this=kind, with_=_with, unique=unique)
 6187            )
 6188        else:
 6189            expression = self._parse_null() or self._parse_bitwise()
 6190            if not expression:
 6191                self._retreat(index)
 6192                return None
 6193
 6194        if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL:
 6195            this = self.expression(exp.Is(this=this, expression=expression, negate=True))
 6196        else:
 6197            this = self.expression(exp.Is(this=this, expression=expression))
 6198            this = self.expression(exp.Not(this=this)) if negate else this
 6199
 6200        return self._parse_column_ops(this)
 6201
 6202    def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In:
 6203        unnest = self._parse_unnest(with_alias=False)
 6204        if unnest:
 6205            this = self.expression(exp.In(this=this, unnest=unnest))
 6206        elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)):
 6207            matched_l_paren = self._prev.token_type == TokenType.L_PAREN
 6208            expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias))
 6209
 6210            if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query):
 6211                this = self.expression(
 6212                    exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False))
 6213                )
 6214            else:
 6215                this = self.expression(exp.In(this=this, expressions=expressions))
 6216
 6217            if matched_l_paren:
 6218                self._match_r_paren(this)
 6219            elif not self._match(TokenType.R_BRACKET, expression=this):
 6220                self.raise_error("Expecting ]")
 6221        else:
 6222            this = self.expression(exp.In(this=this, field=self._parse_column()))
 6223
 6224        return this
 6225
 6226    def _parse_between(self, this: exp.Expr | None) -> exp.Between:
 6227        symmetric = None
 6228        if self._match_text_seq("SYMMETRIC"):
 6229            symmetric = True
 6230        elif self._match_text_seq("ASYMMETRIC"):
 6231            symmetric = False
 6232
 6233        low = self._parse_bitwise()
 6234        self._match(TokenType.AND)
 6235        high = self._parse_bitwise()
 6236
 6237        return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric))
 6238
 6239    def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None:
 6240        if not self._match(TokenType.ESCAPE):
 6241            return this
 6242        return self.expression(
 6243            exp.Escape(this=this, expression=self._parse_string() or self._parse_null())
 6244        )
 6245
 6246    def _parse_interval_span(
 6247        self, this: exp.Expr, parse_function_unit: bool = True
 6248    ) -> exp.Interval:
 6249        # handle day-time format interval span with omitted units:
 6250        #   INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`>
 6251        interval_span_units_omitted = None
 6252        if (
 6253            this
 6254            and this.is_string
 6255            and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
 6256            and exp.INTERVAL_DAY_TIME_RE.match(this.name)
 6257        ):
 6258            index = self._index
 6259
 6260            # Var "TO" Var
 6261            first_unit = self._parse_var(any_token=True, upper=True)
 6262            second_unit = None
 6263            if first_unit and self._match_text_seq("TO"):
 6264                second_unit = self._parse_var(any_token=True, upper=True)
 6265
 6266            interval_span_units_omitted = not (first_unit and second_unit)
 6267
 6268            self._retreat(index)
 6269
 6270        unit_index = self._index
 6271        if interval_span_units_omitted:
 6272            unit = None
 6273        else:
 6274            # Only attempt to parse a unit if the current token can actually be one, so that a
 6275            # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x)
 6276            is_unit = self._curr is not None and (
 6277                self._curr.token_type == TokenType.VAR
 6278                or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS
 6279            )
 6280            unit = self._parse_function() if parse_function_unit and is_unit else None
 6281            if not unit and is_unit:
 6282                unit = self._parse_var(any_token=True, upper=True)
 6283
 6284        # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse
 6285        # each INTERVAL expression into this canonical form so it's easy to transpile
 6286        if this and this.is_number:
 6287            try:
 6288                this = exp.Literal.string(this.to_py())
 6289            except ValueError:
 6290                self.raise_error(f"Invalid numeric interval literal: {this.name!r}")
 6291        elif this and this.is_string:
 6292            parts = exp.INTERVAL_STRING_RE.findall(this.name)
 6293            if parts and unit:
 6294                # Unconsume the eagerly-parsed unit, since the real unit was part of the string
 6295                unit = None
 6296                self._retreat(unit_index)
 6297
 6298            if len(parts) == 1:
 6299                this = exp.Literal.string(parts[0][0])
 6300                unit = self.expression(exp.Var(this=parts[0][1].upper()))
 6301
 6302        if self.INTERVAL_SPANS and self._match_text_seq("TO"):
 6303            unit = self.expression(
 6304                exp.IntervalSpan(
 6305                    this=unit,
 6306                    expression=self._parse_function()
 6307                    or self._parse_var(any_token=True, upper=True),
 6308                )
 6309            )
 6310
 6311        return self.expression(exp.Interval(this=this, unit=unit))
 6312
 6313    def _parse_interval(
 6314        self, require_interval: bool = True, parse_function_unit: bool = True
 6315    ) -> exp.Add | exp.Interval | None:
 6316        index = self._index
 6317
 6318        if not self._match(TokenType.INTERVAL) and require_interval:
 6319            return None
 6320
 6321        if self._match(TokenType.STRING, advance=False):
 6322            this = self._parse_primary()
 6323        else:
 6324            this = self._parse_term()
 6325
 6326        if not this or (
 6327            isinstance(this, exp.Column)
 6328            and not this.table
 6329            and not this.this.quoted
 6330            and self._curr
 6331            and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS
 6332        ):
 6333            self._retreat(index)
 6334            return None
 6335
 6336        interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit)
 6337
 6338        index = self._index
 6339        self._match(TokenType.PLUS)
 6340
 6341        # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals
 6342        if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False):
 6343            return self.expression(
 6344                exp.Add(
 6345                    this=interval,
 6346                    expression=self._parse_interval(False, parse_function_unit=parse_function_unit),
 6347                )
 6348            )
 6349
 6350        self._retreat(index)
 6351        return interval
 6352
 6353    def _parse_bitwise(self) -> exp.Expr | None:
 6354        this = self._parse_term()
 6355
 6356        while True:
 6357            if self._match_set(self.BITWISE):
 6358                this = self.expression(
 6359                    self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term())
 6360                )
 6361            elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE):
 6362                this = self.expression(
 6363                    exp.DPipe(
 6364                        this=this,
 6365                        expression=self._parse_term(),
 6366                        safe=not self.dialect.STRICT_STRING_CONCAT,
 6367                    )
 6368                )
 6369            elif self._match(TokenType.DQMARK):
 6370                this = self.expression(
 6371                    exp.Coalesce(this=this, expressions=ensure_list(self._parse_term()))
 6372                )
 6373            elif self._match_pair(TokenType.LT, TokenType.LT):
 6374                this = self.expression(
 6375                    exp.BitwiseLeftShift(this=this, expression=self._parse_term())
 6376                )
 6377            elif self._match_pair(TokenType.GT, TokenType.GT):
 6378                this = self.expression(
 6379                    exp.BitwiseRightShift(this=this, expression=self._parse_term())
 6380                )
 6381            elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS):
 6382                this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term())
 6383            else:
 6384                break
 6385
 6386        return this
 6387
 6388    def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None:
 6389        this = self._parse_factor(parse_mod=parse_mod)
 6390
 6391        while self._match_set(self.TERM):
 6392            klass = self.TERM[self._prev.token_type]
 6393            comments = self._prev_comments
 6394            expression = self._parse_factor(parse_mod=parse_mod)
 6395
 6396            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6397
 6398            if isinstance(this, exp.Collate):
 6399                self._normalize_collate(this)
 6400
 6401        return this
 6402
 6403    def _normalize_collate(self, collate: exp.Collate) -> None:
 6404        expr = collate.expression
 6405
 6406        # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise
 6407        # fallback to Identifier / Var
 6408        if isinstance(expr, exp.Column) and len(expr.parts) == 1:
 6409            ident = expr.this
 6410            if isinstance(ident, exp.Identifier):
 6411                collate.set("expression", ident if ident.quoted else exp.var(ident.name))
 6412
 6413    def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None:
 6414        parse_method = self._parse_factor_operand
 6415        this = self._parse_at_time_zone(parse_method())
 6416
 6417        while self._match_set(self.FACTOR, advance=False):
 6418            if not parse_mod and self._curr.token_type == TokenType.MOD:
 6419                break
 6420
 6421            self._advance()
 6422            klass = self.FACTOR[self._prev.token_type]
 6423            comments = self._prev_comments
 6424            expression = parse_method()
 6425
 6426            if not expression and klass is exp.IntDiv and self._prev.text.isalpha():
 6427                self._retreat(self._index - 1)
 6428                return this
 6429
 6430            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6431
 6432            if isinstance(this, exp.Div):
 6433                this.set("typed", self.dialect.TYPED_DIVISION)
 6434                this.set("safe", self.dialect.SAFE_DIVISION)
 6435
 6436        return this
 6437
 6438    def _parse_factor_operand(self) -> exp.Expr | None:
 6439        return self._parse_exponent() if self.EXPONENT else self._parse_unary()
 6440
 6441    def _parse_exponent(self) -> exp.Expr | None:
 6442        this = self._parse_unary()
 6443        while self._match_set(self.EXPONENT):
 6444            comments = self._prev_comments
 6445            this = self.expression(
 6446                self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()),
 6447                comments=comments,
 6448            )
 6449        return this
 6450
 6451    def _parse_unary(self) -> exp.Expr | None:
 6452        if self._match_set(self.UNARY_PARSERS):
 6453            return self.UNARY_PARSERS[self._prev.token_type](self)
 6454        return self._parse_type()
 6455
 6456    def _parse_type(
 6457        self, parse_interval: bool = True, fallback_to_identifier: bool = False
 6458    ) -> exp.Expr | None:
 6459        if not fallback_to_identifier and (atom := self._parse_atom()) is not None:
 6460            return atom
 6461
 6462        if interval := parse_interval and self._parse_interval():
 6463            return self._parse_column_ops(interval)
 6464
 6465        index = self._index
 6466        data_type = self._parse_types(check_func=True, allow_identifiers=False)
 6467
 6468        # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g.
 6469        # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>)
 6470        if isinstance(data_type, exp.Cast):
 6471            # This constructor can contain ops directly after it, for instance struct unnesting:
 6472            # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).*
 6473            return self._parse_column_ops(data_type)
 6474
 6475        if data_type:
 6476            index2 = self._index
 6477            this = self._parse_primary()
 6478
 6479            if isinstance(this, exp.Literal):
 6480                literal = this.name
 6481                this = self._parse_column_ops(this)
 6482
 6483                parser = self.TYPE_LITERAL_PARSERS.get(data_type.this)
 6484                if parser:
 6485                    return parser(self, this, data_type)
 6486
 6487                if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal):
 6488                    if data_type.is_type(exp.DType.TIMESTAMP):
 6489                        data_type = exp.DType.TIMESTAMPTZ.into_expr()
 6490                    elif data_type.is_type(exp.DType.TIME):
 6491                        data_type = exp.DType.TIMETZ.into_expr()
 6492
 6493                return self.expression(exp.Cast(this=this, to=data_type))
 6494
 6495            # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0)
 6496            # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 )
 6497            #
 6498            # If the index difference here is greater than 1, that means the parser itself must have
 6499            # consumed additional tokens such as the DECIMAL scale and precision in the above example.
 6500            #
 6501            # If it's not greater than 1, then it must be 1, because we've consumed at least the type
 6502            # keyword, meaning that the expressions arg of the DataType must have gotten set by a
 6503            # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to
 6504            # DECIMAL(38, 0)) in order to facilitate the data type's transpilation.
 6505            #
 6506            # In these cases, we don't really want to return the converted type, but instead retreat
 6507            # and try to parse a Column or Identifier in the section below.
 6508            if data_type.expressions and index2 - index > 1:
 6509                self._retreat(index2)
 6510                return self._parse_column_ops(data_type)
 6511
 6512            self._retreat(index)
 6513
 6514        if fallback_to_identifier:
 6515            return self._parse_id_var()
 6516
 6517        return self._parse_column()
 6518
 6519    def _parse_type_size(self) -> exp.DataTypeParam | None:
 6520        this = self._parse_type()
 6521        if not this:
 6522            return None
 6523
 6524        if isinstance(this, exp.Column) and not this.table:
 6525            this = exp.var(this.name.upper())
 6526
 6527        return self.expression(
 6528            exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True))
 6529        )
 6530
 6531    def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None:
 6532        type_name = identifier.name
 6533
 6534        while self._match(TokenType.DOT):
 6535            type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
 6536
 6537        return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True)
 6538
 6539    def _parse_types(
 6540        self,
 6541        check_func: bool = False,
 6542        schema: bool = False,
 6543        allow_identifiers: bool = True,
 6544        with_collation: bool = False,
 6545    ) -> exp.Expr | None:
 6546        index = self._index
 6547        this: exp.Expr | None = None
 6548
 6549        if self._match_set(self.TYPE_TOKENS):
 6550            type_token = self._prev.token_type
 6551        else:
 6552            type_token = None
 6553            identifier = allow_identifiers and self._parse_id_var(
 6554                any_token=False, tokens=(TokenType.VAR,)
 6555            )
 6556            if isinstance(identifier, exp.Identifier):
 6557                if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE:
 6558                    this = exp.DataType.build(identifier, udt=True)
 6559                else:
 6560                    try:
 6561                        tokens = self.dialect.tokenize(identifier.name)
 6562                    except TokenError:
 6563                        tokens = None
 6564
 6565                    if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS:
 6566                        if len(tokens) > 1:
 6567                            return exp.DataType.from_str(identifier.name, dialect=self.dialect)
 6568                    elif self.dialect.SUPPORTS_USER_DEFINED_TYPES:
 6569                        this = self._parse_user_defined_type(identifier)
 6570                    else:
 6571                        self._retreat(self._index - 1)
 6572                        return None
 6573            else:
 6574                return None
 6575
 6576        if type_token == TokenType.PSEUDO_TYPE:
 6577            return self.expression(exp.PseudoType(this=self._prev.text.upper()))
 6578
 6579        if type_token == TokenType.OBJECT_IDENTIFIER:
 6580            return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper()))
 6581
 6582        # https://materialize.com/docs/sql/types/map/
 6583        if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET):
 6584            key_type = self._parse_types(
 6585                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6586            )
 6587            if not self._match(TokenType.FARROW):
 6588                self._retreat(index)
 6589                return None
 6590
 6591            value_type = self._parse_types(
 6592                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6593            )
 6594            if not self._match(TokenType.R_BRACKET):
 6595                self._retreat(index)
 6596                return None
 6597
 6598            return exp.DataType(
 6599                this=exp.DType.MAP,
 6600                expressions=[key_type, value_type],
 6601                nested=True,
 6602            )
 6603
 6604        nested = type_token in self.NESTED_TYPE_TOKENS
 6605        is_struct = type_token in self.STRUCT_TYPE_TOKENS
 6606        is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS
 6607        expressions = None
 6608        maybe_func = False
 6609
 6610        if self._match(TokenType.L_PAREN):
 6611            if is_struct:
 6612                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6613            elif nested:
 6614                expressions = self._parse_csv(
 6615                    lambda: self._parse_types(
 6616                        check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6617                    )
 6618                )
 6619                if type_token == TokenType.NULLABLE and len(expressions) == 1:
 6620                    this = expressions[0]
 6621                    this.set("nullable", True)
 6622                    self._match_r_paren()
 6623                    return this
 6624            elif type_token in self.ENUM_TYPE_TOKENS:
 6625                expressions = self._parse_csv(self._parse_equality)
 6626            elif type_token == TokenType.JSON:
 6627                # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value)
 6628                # https://clickhouse.com/docs/sql-reference/data-types/newjson
 6629                expressions = self._parse_csv(self._parse_json_type_arg)
 6630            elif is_aggregate:
 6631                func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var(
 6632                    any_token=False, tokens=(TokenType.VAR, TokenType.ANY)
 6633                )
 6634                if not func_or_ident:
 6635                    return None
 6636                expressions = [func_or_ident]
 6637                if self._match(TokenType.COMMA):
 6638                    expressions.extend(
 6639                        self._parse_csv(
 6640                            lambda: self._parse_types(
 6641                                check_func=check_func,
 6642                                schema=schema,
 6643                                allow_identifiers=allow_identifiers,
 6644                            )
 6645                        )
 6646                    )
 6647            else:
 6648                expressions = self._parse_csv(self._parse_type_size)
 6649
 6650                # https://docs.snowflake.com/en/sql-reference/data-types-vector
 6651                if type_token == TokenType.VECTOR and len(expressions) == 2:
 6652                    expressions = self._parse_vector_expressions(expressions)
 6653
 6654            if not self._match(TokenType.R_PAREN):
 6655                self._retreat(index)
 6656                return None
 6657
 6658            maybe_func = True
 6659
 6660        values: list[exp.Expr] | None = None
 6661
 6662        if nested and self._match(TokenType.LT):
 6663            if is_struct:
 6664                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6665            else:
 6666                expressions = self._parse_csv(
 6667                    lambda: self._parse_types(
 6668                        check_func=check_func,
 6669                        schema=schema,
 6670                        allow_identifiers=allow_identifiers,
 6671                        with_collation=True,
 6672                    )
 6673                )
 6674
 6675            if not self._match(TokenType.GT):
 6676                self.raise_error("Expecting >")
 6677
 6678            if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)):
 6679                values = self._parse_csv(self._parse_disjunction)
 6680                if not values and is_struct:
 6681                    values = None
 6682                    self._retreat(self._index - 1)
 6683                else:
 6684                    self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN))
 6685
 6686        if type_token in self.TIMESTAMPS:
 6687            if self._match_text_seq("WITH", "TIME", "ZONE"):
 6688                maybe_func = False
 6689                tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ
 6690                this = exp.DataType(this=tz_type, expressions=expressions)
 6691            elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"):
 6692                maybe_func = False
 6693                this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions)
 6694            elif self._match_text_seq("WITHOUT", "TIME", "ZONE"):
 6695                maybe_func = False
 6696        elif type_token == TokenType.INTERVAL:
 6697            if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS:
 6698                unit = self._parse_var(upper=True)
 6699                if self._match_text_seq("TO"):
 6700                    unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True))
 6701
 6702                this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit))))
 6703            else:
 6704                this = self.expression(exp.DataType(this=exp.DType.INTERVAL))
 6705        elif type_token == TokenType.VOID:
 6706            this = exp.DataType(this=exp.DType.NULL)
 6707
 6708        if maybe_func and check_func:
 6709            index2 = self._index
 6710            peek = self._parse_string()
 6711
 6712            if not peek:
 6713                self._retreat(index)
 6714                return None
 6715
 6716            self._retreat(index2)
 6717
 6718        if not this:
 6719            assert type_token is not None
 6720            if self._match_text_seq("UNSIGNED"):
 6721                unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token)
 6722                if not unsigned_type_token:
 6723                    self.raise_error(f"Cannot convert {type_token.name} to unsigned.")
 6724
 6725                type_token = unsigned_type_token or type_token
 6726
 6727            # NULLABLE without parentheses can be a column (Presto/Trino)
 6728            if type_token == TokenType.NULLABLE and not expressions:
 6729                self._retreat(index)
 6730                return None
 6731
 6732            this = exp.DataType(
 6733                this=exp.DType[type_token.name],
 6734                expressions=expressions,
 6735                nested=nested,
 6736            )
 6737
 6738            # Empty arrays/structs are allowed
 6739            if values is not None:
 6740                cls = exp.Struct if is_struct else exp.Array
 6741                this = exp.cast(cls(expressions=values), this, copy=False)
 6742
 6743        elif expressions:
 6744            this.set("expressions", expressions)
 6745
 6746        # https://materialize.com/docs/sql/types/list/#type-name
 6747        while self._match(TokenType.LIST):
 6748            this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True)
 6749
 6750        index = self._index
 6751
 6752        # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3]
 6753        matched_array = self._match(TokenType.ARRAY)
 6754
 6755        while self._curr:
 6756            datatype_token = self._prev.token_type
 6757            matched_l_bracket = self._match(TokenType.L_BRACKET)
 6758
 6759            if (not matched_l_bracket and not matched_array) or (
 6760                datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET)
 6761            ):
 6762                # Postgres allows casting empty arrays such as ARRAY[]::INT[],
 6763                # not to be confused with the fixed size array parsing
 6764                break
 6765
 6766            matched_array = False
 6767            values = self._parse_csv(self._parse_disjunction) or None
 6768            if (
 6769                values
 6770                and not schema
 6771                and (
 6772                    not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS
 6773                    or datatype_token == TokenType.ARRAY
 6774                    or not self._match(TokenType.R_BRACKET, advance=False)
 6775                )
 6776            ):
 6777                # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB
 6778                # ARRAY[1] should retreat and instead be parsed into exp.Array in contrast to INT[x][y] which denotes a fixed-size array data type
 6779                self._retreat(index)
 6780                break
 6781
 6782            this = exp.DataType(
 6783                this=exp.DType.ARRAY, expressions=[this], values=values, nested=True
 6784            )
 6785            self._match(TokenType.R_BRACKET)
 6786
 6787        if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType):
 6788            converter = self.TYPE_CONVERTERS.get(this.this)
 6789            if converter:
 6790                this = converter(t.cast(exp.DataType, this))
 6791
 6792        if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE):
 6793            this.set("collate", self._parse_identifier() or self._parse_column())
 6794
 6795        return this
 6796
 6797    def _parse_json_type_arg(self) -> exp.Expr | None:
 6798        """Parse a single argument to ClickHouse's JSON type."""
 6799
 6800        # SKIP col or SKIP REGEXP 'pattern'
 6801        if self._match_text_seq("SKIP"):
 6802            regexp = self._match(TokenType.RLIKE)
 6803            arg = self._parse_column()
 6804            if isinstance(arg, exp.Column):
 6805                arg = arg.to_dot()
 6806            return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg))
 6807
 6808        param_or_col = self._parse_column()
 6809        if not isinstance(param_or_col, exp.Column):
 6810            return None
 6811
 6812        # Parameter: name=value (e.g., max_dynamic_paths=2)
 6813        if len(param_or_col.parts) == 1 and self._match(TokenType.EQ):
 6814            param = param_or_col.name
 6815            value = self._parse_primary()
 6816            return self.expression(exp.EQ(this=exp.var(param), expression=value))
 6817
 6818        # Column type hint: col_name Type
 6819        col = param_or_col.to_dot()
 6820        kind = self._parse_types(check_func=False, allow_identifiers=False)
 6821        return self.expression(exp.ColumnDef(this=col, kind=kind))
 6822
 6823    def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]:
 6824        return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]]
 6825
 6826    def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None:
 6827        index = self._index
 6828
 6829        if (
 6830            self._curr
 6831            and self._next
 6832            and self._curr.token_type in self.TYPE_TOKENS
 6833            and self._next.token_type in self.TYPE_TOKENS
 6834        ):
 6835            # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a
 6836            # type token. Without this, the list will be parsed as a type and we'll eventually crash
 6837            this = self._parse_id_var()
 6838        else:
 6839            this = (
 6840                self._parse_type(parse_interval=False, fallback_to_identifier=True)
 6841                or self._parse_id_var()
 6842            )
 6843
 6844        self._match(TokenType.COLON)
 6845
 6846        if (
 6847            type_required
 6848            and not isinstance(this, exp.DataType)
 6849            and not self._match_set(self.TYPE_TOKENS, advance=False)
 6850        ):
 6851            self._retreat(index)
 6852            return self._parse_types()
 6853
 6854        return self._parse_column_def(this)
 6855
 6856    def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None:
 6857        if not self._match_text_seq("AT", "TIME", "ZONE"):
 6858            return this
 6859        return self._parse_at_time_zone(
 6860            self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary()))
 6861        )
 6862
 6863    def _parse_atom(self) -> exp.Expr | None:
 6864        if (
 6865            self._curr.token_type in self.IDENTIFIER_TOKENS
 6866            and (column := self._parse_column()) is not None
 6867        ):
 6868            return column
 6869
 6870        token = self._curr
 6871        token_type = token.token_type
 6872
 6873        if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)):
 6874            return None
 6875
 6876        next_type = self._next.token_type
 6877
 6878        if (
 6879            next_type in self.COLUMN_OPERATORS
 6880            or next_type in self.COLUMN_POSTFIX_TOKENS
 6881            or (token_type == TokenType.STRING and next_type == TokenType.STRING)
 6882        ):
 6883            return None
 6884
 6885        self._advance()
 6886        return primary_parser(self, token)
 6887
 6888    def _parse_column(self) -> exp.Expr | None:
 6889        column: exp.Expr | None = self._parse_column_parts_fast()
 6890        if column is None:
 6891            this = self._parse_column_reference()
 6892            if not this:
 6893                this = self._parse_bracket(this)
 6894            column = self._parse_column_ops(this) if this else this
 6895
 6896        if column:
 6897            if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 6898                column.set("join_mark", self._match(TokenType.JOIN_MARKER))
 6899            if self.COLON_IS_VARIANT_EXTRACT:
 6900                column = self._parse_colon_as_variant_extract(column)
 6901
 6902        return column
 6903
 6904    def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None:
 6905        """Fast path for simple column and dot references (a, a.b, ...).
 6906
 6907        Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks
 6908        that nothing complex follows. If it does, retreats and returns None so
 6909        the slow path can handle it. For >4 parts, wraps in exp.Dot nodes.
 6910        """
 6911        index = self._index
 6912        parts: list[exp.Identifier] | None = None
 6913        all_comments: list[str] | None = None
 6914
 6915        while self._match_set(self.IDENTIFIER_TOKENS):
 6916            token = self._prev
 6917            comments = self._prev_comments
 6918
 6919            if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS:
 6920                self._retreat(index)
 6921                return None
 6922
 6923            has_dot = self._match(TokenType.DOT)
 6924            curr_tt = self._curr.token_type
 6925
 6926            if not has_dot:
 6927                if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS:
 6928                    self._retreat(index)
 6929                    return None
 6930            elif curr_tt not in self.IDENTIFIER_TOKENS:
 6931                self._retreat(index)
 6932                return None
 6933
 6934            if parts is None:
 6935                parts = []
 6936
 6937            if comments:
 6938                if all_comments is None:
 6939                    all_comments = []
 6940                all_comments.extend(comments)
 6941                self._prev_comments = []
 6942
 6943            parts.append(
 6944                self.expression(
 6945                    exp.Identifier(
 6946                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 6947                    ),
 6948                    token,
 6949                )
 6950            )
 6951
 6952            if not has_dot:
 6953                break
 6954
 6955        if parts is None:
 6956            return None
 6957
 6958        n = len(parts)
 6959
 6960        if n == 1:
 6961            column: exp.Column | exp.Dot = exp.Column(this=parts[0])
 6962        elif n == 2:
 6963            column = exp.Column(this=parts[1], table=parts[0])
 6964        elif n == 3:
 6965            column = exp.Column(this=parts[2], table=parts[1], db=parts[0])
 6966        else:
 6967            column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0])
 6968
 6969            for i in range(4, n):
 6970                column = exp.Dot(this=column, expression=parts[i])
 6971
 6972        if all_comments:
 6973            column.add_comments(all_comments)
 6974
 6975        return column
 6976
 6977    def _parse_column_reference(self) -> exp.Expr | None:
 6978        this = self._parse_field()
 6979        if (
 6980            not this
 6981            and self._match(TokenType.VALUES, advance=False)
 6982            and self.VALUES_FOLLOWED_BY_PAREN
 6983            and (not self._next or self._next.token_type != TokenType.L_PAREN)
 6984        ):
 6985            this = self._parse_id_var()
 6986
 6987        if isinstance(this, exp.Identifier):
 6988            # We bubble up comments from the Identifier to the Column
 6989            this = self.expression(exp.Column(this=this), comments=this.pop_comments())
 6990
 6991        return this
 6992
 6993    def _build_json_extract(
 6994        self,
 6995        this: exp.Expr | None,
 6996        path_parts: list[exp.JSONPathPart],
 6997    ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]:
 6998        if len(path_parts) > 1:
 6999            this = self.expression(
 7000                exp.JSONExtract(
 7001                    this=this,
 7002                    expression=exp.JSONPath(expressions=path_parts),
 7003                    variant_extract=True,
 7004                    requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION,
 7005                )
 7006            )
 7007            path_parts = [exp.JSONPathRoot()]
 7008
 7009        return this, path_parts
 7010
 7011    def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None:
 7012        path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()]
 7013
 7014        while self._match(TokenType.COLON):
 7015            if not self.COLON_CHAIN_IS_SINGLE_EXTRACT:
 7016                this, path_parts = self._build_json_extract(this, path_parts)
 7017
 7018            key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 7019
 7020            if key:
 7021                quoted = isinstance(key, exp.Identifier) and key.quoted
 7022                path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted))
 7023
 7024            while True:
 7025                if self._match(TokenType.DOT):
 7026                    next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 7027
 7028                    if next_key:
 7029                        quoted = isinstance(next_key, exp.Identifier) and next_key.quoted
 7030                        path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted))
 7031                elif self._match(TokenType.L_BRACKET):
 7032                    bracket_expr = self._parse_bracket_key_value()
 7033
 7034                    if not self._match(TokenType.R_BRACKET):
 7035                        self.raise_error("Expected ]")
 7036
 7037                    if bracket_expr:
 7038                        if bracket_expr.is_string:
 7039                            path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True))
 7040                        elif bracket_expr.is_star:
 7041                            path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard()))
 7042                        elif bracket_expr.is_number:
 7043                            path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py()))
 7044                        else:
 7045                            this, path_parts = self._build_json_extract(this, path_parts)
 7046
 7047                            this = self.expression(
 7048                                exp.Bracket(
 7049                                    this=this, expressions=[bracket_expr], json_access=True
 7050                                ),
 7051                            )
 7052
 7053                elif self._match(TokenType.DCOLON):
 7054                    this, path_parts = self._build_json_extract(this, path_parts)
 7055
 7056                    cast_type = self._parse_types()
 7057                    if cast_type:
 7058                        this = self.expression(exp.Cast(this=this, to=cast_type))
 7059                    else:
 7060                        self.raise_error("Expected type after '::'")
 7061                else:
 7062                    break
 7063
 7064        this, _ = self._build_json_extract(this, path_parts)
 7065
 7066        return this
 7067
 7068    def _parse_dcolon(self) -> exp.Expr | None:
 7069        return self._parse_types()
 7070
 7071    def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None:
 7072        while self._curr.token_type in self.BRACKETS:
 7073            this = self._parse_bracket(this)
 7074
 7075        column_operators = self.COLUMN_OPERATORS
 7076        cast_column_operators = self.CAST_COLUMN_OPERATORS
 7077        while self._curr:
 7078            op_token = self._curr.token_type
 7079
 7080            if op_token not in column_operators:
 7081                break
 7082            op = column_operators[op_token]
 7083            self._advance()
 7084
 7085            if op_token in cast_column_operators:
 7086                field = self._parse_dcolon()
 7087                if not field:
 7088                    self.raise_error("Expected type")
 7089            elif op and self._curr:
 7090                field = self._parse_column_reference() or self._parse_bitwise()
 7091                if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False):
 7092                    field = self._parse_column_ops(field)
 7093            else:
 7094                dot = self._is_connected() and self._prev.token_type == TokenType.DOT
 7095                field = self._parse_field(any_token=True, anonymous_func=True)
 7096
 7097                # In t.true, t.null we should produce an Identifier node
 7098                if dot and isinstance(field, (exp.Null, exp.Boolean)):
 7099                    field = self.expression(
 7100                        exp.Identifier(this=self._prev.text),
 7101                        comments=field.comments,
 7102                    )
 7103
 7104            # Function calls can be qualified, e.g., x.y.FOO()
 7105            # This converts the final AST to a series of Dots leading to the function call
 7106            # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules
 7107            if isinstance(field, (exp.Func, exp.Window)) and this:
 7108                this = this.transform(
 7109                    lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n
 7110                )
 7111
 7112            if op:
 7113                this = op(self, this, field)
 7114            elif isinstance(this, exp.Column) and not this.args.get("catalog"):
 7115                this = self.expression(
 7116                    exp.Column(
 7117                        this=field,
 7118                        table=this.this,
 7119                        db=this.args.get("table"),
 7120                        catalog=this.args.get("db"),
 7121                    ),
 7122                    comments=this.comments,
 7123                )
 7124            elif isinstance(field, exp.Window):
 7125                # Move the exp.Dot's to the window's function
 7126                window_func = self.expression(exp.Dot(this=this, expression=field.this))
 7127                field.set("this", window_func)
 7128                this = field
 7129            else:
 7130                this = self.expression(exp.Dot(this=this, expression=field))
 7131
 7132            if field and field.comments:
 7133                t.cast(exp.Expr, this).add_comments(field.pop_comments())
 7134
 7135            this = self._parse_bracket(this)
 7136
 7137        return this
 7138
 7139    def _parse_paren(self) -> exp.Expr | None:
 7140        if not self._match(TokenType.L_PAREN):
 7141            return None
 7142
 7143        comments = self._prev_comments
 7144        query = self._parse_select()
 7145
 7146        if query:
 7147            expressions = [query]
 7148        else:
 7149            expressions = self._parse_expressions()
 7150
 7151        this = seq_get(expressions, 0)
 7152
 7153        if not this and self._match(TokenType.R_PAREN, advance=False):
 7154            this = self.expression(exp.Tuple())
 7155        elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA:
 7156            this = self.expression(exp.Tuple(expressions=expressions))
 7157        elif isinstance(this, exp.UNWRAPPED_QUERIES):
 7158            this = self._parse_subquery(this=this, parse_alias=False)
 7159        elif isinstance(this, (exp.Subquery, exp.Values)):
 7160            this = self._parse_subquery(
 7161                this=self._parse_query_modifiers(self._parse_set_operations(this)),
 7162                parse_alias=False,
 7163            )
 7164        else:
 7165            this = self.expression(exp.Paren(this=this))
 7166
 7167        if this:
 7168            this.add_comments(comments)
 7169
 7170        self._match_r_paren(expression=this)
 7171
 7172        if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc):
 7173            return self._parse_window(this)
 7174
 7175        return this
 7176
 7177    def _parse_primary(self) -> exp.Expr | None:
 7178        if self._match_set(self.PRIMARY_PARSERS):
 7179            token_type = self._prev.token_type
 7180            primary = self.PRIMARY_PARSERS[token_type](self, self._prev)
 7181
 7182            if token_type == TokenType.STRING:
 7183                expressions = [primary]
 7184                while self._match(TokenType.STRING, advance=False):
 7185                    if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED:
 7186                        self.raise_error(
 7187                            "Adjacent string literals need to be separated by whitespace or comments"
 7188                        )
 7189
 7190                    self._advance()
 7191                    expressions.append(exp.Literal.string(self._prev.text))
 7192
 7193                if len(expressions) > 1:
 7194                    return self.expression(
 7195                        exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE)
 7196                    )
 7197
 7198            return primary
 7199
 7200        if self._match_pair(TokenType.DOT, TokenType.NUMBER):
 7201            return exp.Literal.number(f"0.{self._prev.text}")
 7202
 7203        return self._parse_paren()
 7204
 7205    def _parse_field(
 7206        self,
 7207        any_token: bool = False,
 7208        tokens: t.Collection[TokenType] | None = None,
 7209        anonymous_func: bool = False,
 7210    ) -> exp.Expr | None:
 7211        after_dot = (
 7212            self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT
 7213        )
 7214
 7215        if anonymous_func:
 7216            field = (
 7217                self._parse_function(anonymous=anonymous_func, any_token=any_token)
 7218                or self._parse_primary()
 7219            )
 7220        else:
 7221            field = self._parse_primary() or self._parse_function(
 7222                anonymous=anonymous_func, any_token=any_token
 7223            )
 7224
 7225        field = field or self._parse_id_var(any_token=any_token, tokens=tokens)
 7226
 7227        if after_dot and isinstance(field, exp.Literal) and field.is_number:
 7228            name = field.name
 7229            if self._is_connected() and self._parse_var(any_token=True):
 7230                name += self._prev.text
 7231
 7232            field = exp.Identifier(this=name, quoted=True).update_positions(field)
 7233
 7234        return field
 7235
 7236    def _parse_function(
 7237        self,
 7238        functions: dict[str, t.Callable] | None = None,
 7239        anonymous: bool = False,
 7240        optional_parens: bool = True,
 7241        any_token: bool = False,
 7242    ) -> exp.Expr | None:
 7243        # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this)
 7244        # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences
 7245        fn_syntax = False
 7246        if (
 7247            self._match(TokenType.L_BRACE, advance=False)
 7248            and self._next
 7249            and self._next.text.upper() == "FN"
 7250        ):
 7251            self._advance(2)
 7252            fn_syntax = True
 7253
 7254        func = self._parse_function_call(
 7255            functions=functions,
 7256            anonymous=anonymous,
 7257            optional_parens=optional_parens,
 7258            any_token=any_token,
 7259        )
 7260
 7261        if fn_syntax:
 7262            self._match(TokenType.R_BRACE)
 7263
 7264        return func
 7265
 7266    def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]:
 7267        return self._parse_csv(lambda: self._parse_lambda(alias=alias))
 7268
 7269    def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren:
 7270        args = self._parse_function_args(alias=False)
 7271        if not args:
 7272            self.raise_error("Expected at least one argument")
 7273
 7274        # Wrapped so the connector keeps its precedence in the parent context
 7275        return exp.Paren(this=connector(*args, copy=False))
 7276
 7277    def _parse_function_call(
 7278        self,
 7279        functions: dict[str, t.Callable] | None = None,
 7280        anonymous: bool = False,
 7281        optional_parens: bool = True,
 7282        any_token: bool = False,
 7283    ) -> exp.Expr | None:
 7284        if not self._curr:
 7285            return None
 7286
 7287        comments = self._curr.comments
 7288        prev = self._prev
 7289        token = self._curr
 7290        token_type = self._curr.token_type
 7291        this: str | exp.Expr = self._curr.text
 7292        upper = self._curr.text.upper()
 7293
 7294        after_dot = prev.token_type == TokenType.DOT
 7295        parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper)
 7296        if (
 7297            optional_parens
 7298            and parser
 7299            and token_type not in self.INVALID_FUNC_NAME_TOKENS
 7300            and not after_dot
 7301        ):
 7302            self._advance()
 7303            return self._parse_window(parser(self))
 7304
 7305        if self._next.token_type != TokenType.L_PAREN:
 7306            if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot:
 7307                self._advance()
 7308                return self.expression(self.NO_PAREN_FUNCTIONS[token_type]())
 7309
 7310            return None
 7311
 7312        if any_token:
 7313            if token_type in self.RESERVED_TOKENS:
 7314                return None
 7315        elif token_type not in self.FUNC_TOKENS:
 7316            return None
 7317
 7318        self._advance(2)
 7319
 7320        parser = self.FUNCTION_PARSERS.get(upper)
 7321        if parser and not anonymous:
 7322            result = parser(self)
 7323        else:
 7324            subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type)
 7325
 7326            if subquery_predicate:
 7327                expr = None
 7328                if self._curr.token_type in self.SUBQUERY_TOKENS:
 7329                    expr = self._parse_select()
 7330                    self._match_r_paren()
 7331                elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE):
 7332                    # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like
 7333                    # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren
 7334                    self._advance(-1)
 7335                    expr = self._parse_bitwise()
 7336
 7337                if expr:
 7338                    return self.expression(subquery_predicate(this=expr), comments=comments)
 7339
 7340            if functions is None:
 7341                functions = self.FUNCTIONS
 7342
 7343            function = functions.get(upper)
 7344            known_function = function and not anonymous
 7345
 7346            alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS
 7347            args = self._parse_function_args(alias)
 7348
 7349            post_func_comments = self._curr.comments if self._curr else None
 7350            if known_function and post_func_comments:
 7351                # If the user-inputted comment "/* sqlglot.anonymous */" is following the function
 7352                # call we'll construct it as exp.Anonymous, even if it's "known"
 7353                if any(
 7354                    comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS)
 7355                    for comment in post_func_comments
 7356                ):
 7357                    known_function = False
 7358
 7359            if alias and known_function:
 7360                args = self._kv_to_prop_eq(args)
 7361
 7362            if known_function:
 7363                func_builder = t.cast(t.Callable, function)
 7364
 7365                # mypyc compiled functions don't have __code__, so we use
 7366                # try/except to check if func_builder accepts 'dialect'.
 7367                try:
 7368                    func = func_builder(args)
 7369                except TypeError:
 7370                    func = func_builder(args, dialect=self.dialect)
 7371
 7372                func = self.validate_expression(func, args)
 7373                if self.dialect.PRESERVE_ORIGINAL_NAMES:
 7374                    func.meta["name"] = this
 7375
 7376                result = func
 7377            else:
 7378                if token_type == TokenType.IDENTIFIER:
 7379                    this = exp.Identifier(this=this, quoted=True).update_positions(token)
 7380
 7381                result = self.expression(exp.Anonymous(this=this, expressions=args))
 7382
 7383            result = result.update_positions(token)
 7384
 7385        if isinstance(result, exp.Expr):
 7386            result.add_comments(comments)
 7387
 7388        if parser:
 7389            self._match(TokenType.R_PAREN, expression=result)
 7390        else:
 7391            self._match_r_paren(result)
 7392        return self._parse_window(result)
 7393
 7394    def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr:
 7395        return expression
 7396
 7397    def _kv_to_prop_eq(
 7398        self, expressions: list[exp.Expr], parse_map: bool = False
 7399    ) -> list[exp.Expr]:
 7400        transformed = []
 7401
 7402        for index, e in enumerate(expressions):
 7403            if isinstance(e, self.KEY_VALUE_DEFINITIONS):
 7404                if isinstance(e, exp.Alias):
 7405                    e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this))
 7406
 7407                if not isinstance(e, exp.PropertyEQ):
 7408                    e = self.expression(
 7409                        exp.PropertyEQ(
 7410                            this=e.this if parse_map else exp.to_identifier(e.this.name),
 7411                            expression=e.expression,
 7412                        )
 7413                    )
 7414
 7415                if isinstance(e.this, exp.Column):
 7416                    e.this.replace(e.this.this)
 7417            else:
 7418                e = self._to_prop_eq(e, index)
 7419
 7420            transformed.append(e)
 7421
 7422        return transformed
 7423
 7424    def _parse_function_properties(self) -> exp.Properties | None:
 7425        # Skip the generic `key = value` fallback in _parse_property since this
 7426        # runs post-AS where a function body like `name = expr` can be misread
 7427        # as a property.
 7428        properties = []
 7429        while True:
 7430            if self._match_texts(self.PROPERTY_PARSERS):
 7431                keyword = self._prev.text.upper()
 7432                prop = self.PROPERTY_PARSERS[keyword](self)
 7433            elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS):
 7434                keyword = self._prev.text.upper()
 7435                prop = self.PROPERTY_PARSERS[keyword](self, default=True)
 7436            else:
 7437                break
 7438            if not prop:
 7439                self.raise_error(f"Failed to parse property '{keyword}'")
 7440                break
 7441            for p in ensure_list(prop):
 7442                properties.append(p)
 7443
 7444        return self.expression(exp.Properties(expressions=properties)) if properties else None
 7445
 7446    def _parse_user_defined_function_expression(self) -> exp.Expr | None:
 7447        return self._parse_statement()
 7448
 7449    def _parse_function_parameter(self) -> exp.Expr | None:
 7450        return self._parse_column_def(this=self._parse_id_var(), computed_column=False)
 7451
 7452    def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None:
 7453        this = self._parse_table_parts(schema=True)
 7454
 7455        if not self._match(TokenType.L_PAREN):
 7456            return this
 7457
 7458        expressions = self._parse_csv(self._parse_function_parameter)
 7459        self._match_r_paren()
 7460        return self.expression(
 7461            exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True)
 7462        )
 7463
 7464    def _parse_macro_overloads(
 7465        self,
 7466        this: exp.UserDefinedFunction,
 7467        first_body: exp.Expr,
 7468        first_is_table: bool = False,
 7469    ) -> exp.MacroOverloads:
 7470        overloads = [
 7471            self.expression(
 7472                exp.MacroOverload(
 7473                    this=first_body,
 7474                    expressions=this.expressions or None,
 7475                    is_table=first_is_table,
 7476                )
 7477            )
 7478        ]
 7479        this.set("expressions", None)
 7480        this.set("wrapped", False)
 7481
 7482        while self._match(TokenType.COMMA):
 7483            if not self._match(TokenType.L_PAREN):
 7484                break
 7485
 7486            params = self._parse_csv(self._parse_function_parameter)
 7487            self._match_r_paren()
 7488
 7489            if not self._match(TokenType.ALIAS):
 7490                break
 7491
 7492            is_table = self._match(TokenType.TABLE)
 7493            body = self._parse_expression()
 7494            macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table)
 7495            overloads.append(self.expression(macro))
 7496
 7497        return self.expression(exp.MacroOverloads(expressions=overloads))
 7498
 7499    def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier:
 7500        literal = self._parse_primary()
 7501        if literal:
 7502            return self.expression(exp.Introducer(this=token.text, expression=literal), token)
 7503
 7504        return self._identifier_expression(token)
 7505
 7506    def _parse_session_parameter(self) -> exp.SessionParameter:
 7507        kind = None
 7508        this = self._parse_id_var() or self._parse_primary()
 7509
 7510        if this and self._match(TokenType.DOT):
 7511            kind = this.name
 7512            this = self._parse_var() or self._parse_primary()
 7513
 7514        return self.expression(exp.SessionParameter(this=this, kind=kind))
 7515
 7516    def _parse_lambda_arg(self) -> exp.Expr | None:
 7517        return self._parse_id_var()
 7518
 7519    def _parse_lambda(self, alias: bool = False) -> exp.Expr | None:
 7520        next_token_type = self._next.token_type
 7521
 7522        # Fast path: simple atom (column, literal, null, bool) followed by , or )
 7523        if (
 7524            next_token_type in self.LAMBDA_ARG_TERMINATORS
 7525            and (atom := self._parse_atom()) is not None
 7526        ):
 7527            return atom
 7528
 7529        index = self._index
 7530
 7531        if self._match(TokenType.L_PAREN):
 7532            expressions = t.cast(
 7533                list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg)
 7534            )
 7535
 7536            if not self._match(TokenType.R_PAREN):
 7537                self._retreat(index)
 7538            elif self._match_set(self.LAMBDAS):
 7539                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7540            else:
 7541                self._retreat(index)
 7542        elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS:
 7543            expressions = [self._parse_lambda_arg()]
 7544
 7545            if self._match_set(self.LAMBDAS):
 7546                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7547
 7548            self._retreat(index)
 7549
 7550        this: exp.Expr | None
 7551
 7552        if self._match(TokenType.DISTINCT):
 7553            this = self.expression(
 7554                exp.Distinct(expressions=self._parse_csv(self._parse_disjunction))
 7555            )
 7556        else:
 7557            self._match(TokenType.ALL)  # ALL is the default/no-op aggregate modifier (SQL-92)
 7558            this = self._parse_select_or_expression(alias=alias)
 7559
 7560        return self._parse_limit(
 7561            self._parse_respect_or_ignore_nulls(
 7562                self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this)))
 7563            )
 7564        )
 7565
 7566    def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7567        index = self._index
 7568        if not self._match(TokenType.L_PAREN):
 7569            return this
 7570
 7571        # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>),
 7572        # expr can be of both types
 7573        if self._match_set(self.SELECT_START_TOKENS):
 7574            self._retreat(index)
 7575            return this
 7576        args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def())
 7577        self._match_r_paren()
 7578        return self.expression(exp.Schema(this=this, expressions=args))
 7579
 7580    def _parse_field_def(self) -> exp.Expr | None:
 7581        return self._parse_column_def(self._parse_field(any_token=True))
 7582
 7583    def _parse_column_def(
 7584        self, this: exp.Expr | None, computed_column: bool = True
 7585    ) -> exp.Expr | None:
 7586        # column defs are not really columns, they're identifiers
 7587        if isinstance(this, exp.Column):
 7588            this = this.this
 7589
 7590        if not computed_column:
 7591            self._match(TokenType.ALIAS)
 7592
 7593        kind = self._parse_types(schema=True)
 7594
 7595        if self._match_text_seq("FOR", "ORDINALITY"):
 7596            return self.expression(exp.ColumnDef(this=this, ordinality=True))
 7597
 7598        constraints: list[exp.Expr] = []
 7599
 7600        if (not kind and self._match(TokenType.ALIAS)) or self._match_texts(
 7601            ("ALIAS", "MATERIALIZED")
 7602        ):
 7603            # Match storage before _parse_types so STORED is not treated as a data type
 7604            # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`).
 7605            persisted = self._prev.text.upper() == "MATERIALIZED"
 7606            expression = self._parse_disjunction()
 7607            if not persisted:
 7608                if self._match_text_seq("PERSISTED"):
 7609                    persisted = True
 7610                elif self._match_texts(("STORED", "VIRTUAL")):
 7611                    persisted = self._prev.text.upper() == "STORED"
 7612            constraint_kind = exp.ComputedColumnConstraint(
 7613                this=expression,
 7614                persisted=persisted,
 7615                data_type=exp.Var(this="AUTO")
 7616                if self._match_text_seq("AUTO")
 7617                else self._parse_types(),
 7618                not_null=self._match_pair(TokenType.NOT, TokenType.NULL),
 7619            )
 7620            constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind)))
 7621        elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False):
 7622            in_out_constraint = self.expression(
 7623                exp.InOutColumnConstraint(
 7624                    input_=self._match(TokenType.IN), output=self._match(TokenType.OUT)
 7625                )
 7626            )
 7627            constraints.append(in_out_constraint)
 7628            kind = self._parse_types()
 7629        elif (
 7630            kind
 7631            and self._match(TokenType.ALIAS, advance=False)
 7632            and (
 7633                not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
 7634                or self._next.token_type == TokenType.L_PAREN
 7635            )
 7636        ):
 7637            self._advance()
 7638            constraints.append(
 7639                self.expression(
 7640                    exp.ColumnConstraint(
 7641                        kind=exp.ComputedColumnConstraint(
 7642                            this=self._parse_disjunction(),
 7643                            persisted=self._match_texts(("STORED", "VIRTUAL"))
 7644                            and self._prev.text.upper() == "STORED",
 7645                        )
 7646                    )
 7647                )
 7648            )
 7649
 7650        while True:
 7651            constraint = self._parse_column_constraint()
 7652            if not constraint:
 7653                break
 7654            constraints.append(constraint)
 7655
 7656        if not kind and not constraints:
 7657            return this
 7658
 7659        position = None
 7660        if self._match_texts(("FIRST", "AFTER")):
 7661            pos = self._prev.text
 7662            position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos))
 7663
 7664        return self.expression(
 7665            exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position)
 7666        )
 7667
 7668    def _parse_auto_increment(
 7669        self,
 7670    ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint:
 7671        start = None
 7672        increment = None
 7673        order = None
 7674
 7675        if self._match(TokenType.L_PAREN, advance=False):
 7676            args = self._parse_wrapped_csv(self._parse_bitwise)
 7677            start = seq_get(args, 0)
 7678            increment = seq_get(args, 1)
 7679
 7680        # The remaining parts form an unordered bag and any of them can be omitted, in which
 7681        # case the engine falls back to its own default, so they're parsed independently.
 7682        while True:
 7683            if self._match_text_seq("START"):
 7684                start = self._parse_bitwise()
 7685            elif self._match_text_seq("INCREMENT"):
 7686                increment = self._parse_bitwise()
 7687            elif self._match_text_seq("ORDER"):
 7688                order = True
 7689            elif self._match_text_seq("NOORDER"):
 7690                order = False
 7691            else:
 7692                break
 7693
 7694        if start or increment or order is not None:
 7695            return exp.GeneratedAsIdentityColumnConstraint(
 7696                start=start, increment=increment, this=False, order=order
 7697            )
 7698
 7699        return exp.AutoIncrementColumnConstraint()
 7700
 7701    def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None:
 7702        if not self._match(TokenType.L_PAREN, advance=False):
 7703            return None
 7704
 7705        return self.expression(
 7706            exp.CheckColumnConstraint(
 7707                this=self._parse_wrapped(self._parse_assignment),
 7708                enforced=self._match_text_seq("ENFORCED"),
 7709            )
 7710        )
 7711
 7712    def _parse_auto_property(self) -> exp.AutoRefreshProperty | None:
 7713        if not self._match_text_seq("REFRESH"):
 7714            self._retreat(self._index - 1)
 7715            return None
 7716        return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True)))
 7717
 7718    def _parse_compress(self) -> exp.CompressColumnConstraint:
 7719        if self._match(TokenType.L_PAREN, advance=False):
 7720            return self.expression(
 7721                exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise))
 7722            )
 7723
 7724        return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise()))
 7725
 7726    def _parse_generated_as_identity(
 7727        self,
 7728    ) -> (
 7729        exp.GeneratedAsIdentityColumnConstraint
 7730        | exp.ComputedColumnConstraint
 7731        | exp.GeneratedAsRowColumnConstraint
 7732    ):
 7733        if self._match_text_seq("BY", "DEFAULT"):
 7734            on_null = self._match_pair(TokenType.ON, TokenType.NULL)
 7735            this = self.expression(
 7736                exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null)
 7737            )
 7738        else:
 7739            self._match_text_seq("ALWAYS")
 7740            this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True))
 7741
 7742        self._match(TokenType.ALIAS)
 7743
 7744        if self._match_text_seq("ROW"):
 7745            start = self._match_text_seq("START")
 7746            if not start:
 7747                self._match(TokenType.END)
 7748            hidden = self._match_text_seq("HIDDEN")
 7749            return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden))
 7750
 7751        identity = self._match_text_seq("IDENTITY")
 7752
 7753        if self._match(TokenType.L_PAREN):
 7754            if self._match_text_seq("START", "WITH"):
 7755                this.set("start", self._parse_bitwise())
 7756            if self._match_text_seq("INCREMENT", "BY"):
 7757                this.set("increment", self._parse_bitwise())
 7758            if self._match_text_seq("MINVALUE"):
 7759                this.set("minvalue", self._parse_bitwise())
 7760            if self._match_text_seq("MAXVALUE"):
 7761                this.set("maxvalue", self._parse_bitwise())
 7762
 7763            if self._match_text_seq("CYCLE"):
 7764                this.set("cycle", True)
 7765            elif self._match_text_seq("NO", "CYCLE"):
 7766                this.set("cycle", False)
 7767
 7768            if not identity:
 7769                this.set("expression", self._parse_range())
 7770            elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False):
 7771                args = self._parse_csv(self._parse_bitwise)
 7772                this.set("start", seq_get(args, 0))
 7773                this.set("increment", seq_get(args, 1))
 7774
 7775            self._match_r_paren()
 7776
 7777        return this
 7778
 7779    def _parse_inline(self) -> exp.InlineLengthColumnConstraint:
 7780        self._match_text_seq("LENGTH")
 7781        return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise()))
 7782
 7783    def _parse_not_constraint(self) -> exp.Expr | None:
 7784        if self._match_text_seq("NULL"):
 7785            return self.expression(exp.NotNullColumnConstraint())
 7786        if self._match_text_seq("CASESPECIFIC"):
 7787            return self.expression(exp.CaseSpecificColumnConstraint(not_=True))
 7788        if self._match_text_seq("FOR", "REPLICATION"):
 7789            return self.expression(exp.NotForReplicationColumnConstraint())
 7790
 7791        # Unconsume the `NOT` token
 7792        self._retreat(self._index - 1)
 7793        return None
 7794
 7795    def _parse_column_constraint(self) -> exp.Expr | None:
 7796        this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None
 7797
 7798        procedure_option_follows = (
 7799            self._match(TokenType.WITH, advance=False)
 7800            and self._next
 7801            and self._next.text.upper() in self.PROCEDURE_OPTIONS
 7802        )
 7803
 7804        index = self._index
 7805        if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS):
 7806            constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self)
 7807            if not constraint:
 7808                self._retreat(index)
 7809                return None
 7810
 7811            return self.expression(exp.ColumnConstraint(this=this, kind=constraint))
 7812
 7813        if self._match_text_seq("CHARACTER", "SET"):
 7814            return self.expression(
 7815                exp.ColumnConstraint(
 7816                    this=this,
 7817                    kind=self.expression(
 7818                        exp.CharacterSetColumnConstraint(this=self._parse_var_or_string())
 7819                    ),
 7820                )
 7821            )
 7822
 7823        return this
 7824
 7825    def _parse_constraint(self) -> exp.Expr | None:
 7826        if not self._match(TokenType.CONSTRAINT):
 7827            return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS)
 7828
 7829        return self.expression(
 7830            exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints())
 7831        )
 7832
 7833    def _parse_unnamed_constraints(self) -> list[exp.Expr]:
 7834        constraints = []
 7835        while True:
 7836            constraint = self._parse_unnamed_constraint() or self._parse_function()
 7837            if not constraint:
 7838                break
 7839            constraints.append(constraint)
 7840
 7841        return constraints
 7842
 7843    def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None:
 7844        index = self._index
 7845
 7846        if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts(
 7847            constraints or self.CONSTRAINT_PARSERS
 7848        ):
 7849            return None
 7850
 7851        constraint_key = self._prev.text.upper()
 7852        if constraint_key not in self.CONSTRAINT_PARSERS:
 7853            self.raise_error(f"No parser found for schema constraint {constraint_key}.")
 7854
 7855        result = self.CONSTRAINT_PARSERS[constraint_key](self)
 7856        if not result:
 7857            self._retreat(index)
 7858
 7859        return result
 7860
 7861    def _parse_unique_key(self) -> exp.Expr | None:
 7862        if (
 7863            self._curr
 7864            and self._curr.token_type != TokenType.IDENTIFIER
 7865            and self._curr.text.upper() in self.CONSTRAINT_PARSERS
 7866        ):
 7867            return None
 7868        return self._parse_id_var(any_token=False)
 7869
 7870    def _parse_unique(self) -> exp.UniqueColumnConstraint:
 7871        self._match_texts(("KEY", "INDEX"))
 7872        return self.expression(
 7873            exp.UniqueColumnConstraint(
 7874                nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"),
 7875                this=self._parse_schema(self._parse_unique_key()),
 7876                index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text,
 7877                on_conflict=self._parse_on_conflict(),
 7878                options=self._parse_key_constraint_options(),
 7879            )
 7880        )
 7881
 7882    def _parse_key_constraint_options(self) -> list[str]:
 7883        options = []
 7884        while True:
 7885            if not self._curr:
 7886                break
 7887
 7888            if self._match(TokenType.ON):
 7889                action = None
 7890                on = self._advance_any() and self._prev.text
 7891
 7892                if self._match_text_seq("NO", "ACTION"):
 7893                    action = "NO ACTION"
 7894                elif self._match_text_seq("CASCADE"):
 7895                    action = "CASCADE"
 7896                elif self._match_text_seq("RESTRICT"):
 7897                    action = "RESTRICT"
 7898                elif self._match_pair(TokenType.SET, TokenType.NULL):
 7899                    action = "SET NULL"
 7900                elif self._match_pair(TokenType.SET, TokenType.DEFAULT):
 7901                    action = "SET DEFAULT"
 7902                else:
 7903                    self.raise_error("Invalid key constraint")
 7904
 7905                options.append(f"ON {on} {action}")
 7906            else:
 7907                var = self._parse_var_from_options(
 7908                    self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False
 7909                )
 7910                if not var:
 7911                    break
 7912                options.append(var.name)
 7913
 7914        return options
 7915
 7916    def _parse_references(self, match: bool = True) -> exp.Reference | None:
 7917        if match and not self._match(TokenType.REFERENCES):
 7918            return None
 7919
 7920        expressions: list | None = None
 7921        this = self._parse_table(schema=True)
 7922        options = self._parse_key_constraint_options()
 7923        return self.expression(exp.Reference(this=this, expressions=expressions, options=options))
 7924
 7925    def _parse_foreign_key(self) -> exp.ForeignKey:
 7926        expressions = (
 7927            self._parse_wrapped_id_vars()
 7928            if not self._match(TokenType.REFERENCES, advance=False)
 7929            else None
 7930        )
 7931        reference = self._parse_references()
 7932        on_options = {}
 7933
 7934        while self._match(TokenType.ON):
 7935            if not self._match_set((TokenType.DELETE, TokenType.UPDATE)):
 7936                self.raise_error("Expected DELETE or UPDATE")
 7937
 7938            kind = self._prev.text.lower()
 7939
 7940            if self._match_text_seq("NO", "ACTION"):
 7941                action = "NO ACTION"
 7942            elif self._match(TokenType.SET):
 7943                self._match_set((TokenType.NULL, TokenType.DEFAULT))
 7944                action = "SET " + self._prev.text.upper()
 7945            else:
 7946                self._advance()
 7947                action = self._prev.text.upper()
 7948
 7949            on_options[kind] = action
 7950
 7951        return self.expression(
 7952            exp.ForeignKey(
 7953                expressions=expressions,
 7954                reference=reference,
 7955                options=self._parse_key_constraint_options(),
 7956                **on_options,
 7957            )
 7958        )
 7959
 7960    def _parse_primary_key_part(self) -> exp.Expr | None:
 7961        return self._parse_field()
 7962
 7963    def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None:
 7964        if not self._match_text_seq("FOR", "SYSTEM_TIME"):
 7965            self._retreat(self._index - 1)
 7966            return None
 7967
 7968        id_vars = self._parse_wrapped_id_vars()
 7969        return self.expression(
 7970            exp.PeriodForSystemTimeConstraint(
 7971                this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1)
 7972            )
 7973        )
 7974
 7975    def _parse_primary_key(
 7976        self,
 7977        wrapped_optional: bool = False,
 7978        in_props: bool = False,
 7979        named_primary_key: bool = False,
 7980    ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:
 7981        desc = (
 7982            self._prev.token_type == TokenType.DESC
 7983            if self._match_set((TokenType.ASC, TokenType.DESC))
 7984            else None
 7985        )
 7986
 7987        this = None
 7988        if (
 7989            named_primary_key
 7990            and self._curr.text.upper() not in self.CONSTRAINT_PARSERS
 7991            and self._next
 7992            and self._next.token_type == TokenType.L_PAREN
 7993        ):
 7994            this = self._parse_id_var()
 7995
 7996        if not in_props and not self._match(TokenType.L_PAREN, advance=False):
 7997            return self.expression(
 7998                exp.PrimaryKeyColumnConstraint(
 7999                    desc=desc, options=self._parse_key_constraint_options()
 8000                )
 8001            )
 8002
 8003        expressions = self._parse_wrapped_csv(
 8004            self._parse_primary_key_part, optional=wrapped_optional
 8005        )
 8006
 8007        return self.expression(
 8008            exp.PrimaryKey(
 8009                this=this,
 8010                expressions=expressions,
 8011                include=self._parse_index_params(),
 8012                options=self._parse_key_constraint_options(),
 8013            )
 8014        )
 8015
 8016    def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None:
 8017        return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True))
 8018
 8019    def _parse_odbc_datetime_literal(self) -> exp.Expr:
 8020        """
 8021        Parses a datetime column in ODBC format. We parse the column into the corresponding
 8022        types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the
 8023        same as we did for `DATE('yyyy-mm-dd')`.
 8024
 8025        Reference:
 8026        https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals
 8027        """
 8028        self._match(TokenType.VAR)
 8029        exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()]
 8030        expression = self.expression(exp_class(this=self._parse_string()))
 8031        if not self._match(TokenType.R_BRACE):
 8032            self.raise_error("Expected }")
 8033        return expression
 8034
 8035    def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None:
 8036        if not self._match_set(self.BRACKETS):
 8037            return this
 8038
 8039        if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS:
 8040            map_token = seq_get(self._tokens, self._index - 2)
 8041            parse_map = map_token is not None and map_token.text.upper() == "MAP"
 8042        else:
 8043            parse_map = False
 8044
 8045        bracket_kind = self._prev.token_type
 8046        if (
 8047            bracket_kind == TokenType.L_BRACE
 8048            and self._curr
 8049            and self._curr.token_type == TokenType.VAR
 8050            and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS
 8051        ):
 8052            return self._parse_odbc_datetime_literal()
 8053
 8054        expressions = self._parse_csv(
 8055            lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE)
 8056        )
 8057
 8058        if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET):
 8059            self.raise_error("Expected ]")
 8060        elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE):
 8061            self.raise_error("Expected }")
 8062
 8063        # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs
 8064        if bracket_kind == TokenType.L_BRACE:
 8065            this = self.expression(
 8066                exp.Struct(
 8067                    expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map)
 8068                )
 8069            )
 8070        elif not this:
 8071            this = build_array_constructor(
 8072                exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect
 8073            )
 8074        else:
 8075            constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper())
 8076            if constructor_type:
 8077                return build_array_constructor(
 8078                    constructor_type,
 8079                    args=expressions,
 8080                    bracket_kind=bracket_kind,
 8081                    dialect=self.dialect,
 8082                )
 8083
 8084            expressions = apply_index_offset(
 8085                this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect
 8086            )
 8087            this = self.expression(
 8088                exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments()
 8089            )
 8090
 8091        self._add_comments(this)
 8092        return self._parse_bracket(this)
 8093
 8094    def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None:
 8095        if not self._match(TokenType.COLON):
 8096            return this
 8097
 8098        if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False):
 8099            self._advance()
 8100            end: exp.Expr | None = -exp.Literal.number("1")
 8101        else:
 8102            end = self._parse_assignment()
 8103        step = self._parse_unary() if self._match(TokenType.COLON) else None
 8104        return self.expression(exp.Slice(this=this, expression=end, step=step))
 8105
 8106    def _parse_case(self) -> exp.Expr | None:
 8107        if self._match(TokenType.DOT, advance=False):
 8108            # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake
 8109            self._retreat(self._index - 1)
 8110            return None
 8111
 8112        ifs = []
 8113        default = None
 8114
 8115        comments = self._prev_comments
 8116        expression = self._parse_disjunction()
 8117
 8118        while self._match(TokenType.WHEN):
 8119            this = self._parse_disjunction()
 8120            self._match(TokenType.THEN)
 8121            then = self._parse_disjunction()
 8122            ifs.append(self.expression(exp.If(this=this, true=then)))
 8123
 8124        if self._match(TokenType.ELSE):
 8125            default = self._parse_disjunction()
 8126
 8127        if not self._match(TokenType.END):
 8128            if isinstance(default, exp.Interval) and default.this.sql().upper() == "END":
 8129                default = exp.column("interval")
 8130            else:
 8131                self.raise_error("Expected END after CASE", self._prev)
 8132
 8133        return self.expression(
 8134            exp.Case(this=expression, ifs=ifs, default=default), comments=comments
 8135        )
 8136
 8137    def _parse_if(self) -> exp.Expr | None:
 8138        if self._match(TokenType.L_PAREN):
 8139            args = self._parse_csv(
 8140                lambda: self._parse_alias(self._parse_assignment(), explicit=True)
 8141            )
 8142            this = self.validate_expression(exp.If.from_arg_list(args), args)
 8143            self._match_r_paren()
 8144        else:
 8145            index = self._index - 1
 8146
 8147            if self.NO_PAREN_IF_COMMANDS and index == 0:
 8148                return self._parse_as_command(self._prev)
 8149
 8150            condition = self._parse_disjunction()
 8151
 8152            if not condition:
 8153                self._retreat(index)
 8154                return None
 8155
 8156            self._match(TokenType.THEN)
 8157            true = self._parse_disjunction()
 8158            false = self._parse_disjunction() if self._match(TokenType.ELSE) else None
 8159            self._match(TokenType.END)
 8160            this = self.expression(exp.If(this=condition, true=true, false=false))
 8161
 8162        return this
 8163
 8164    def _parse_next_value_for(self) -> exp.Expr | None:
 8165        if not self._match_text_seq("VALUE", "FOR"):
 8166            self._retreat(self._index - 1)
 8167            return None
 8168
 8169        return self.expression(
 8170            exp.NextValueFor(
 8171                this=self._parse_column(),
 8172                order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order),
 8173            )
 8174        )
 8175
 8176    def _parse_extract(self) -> exp.Extract | exp.Anonymous:
 8177        this = self._parse_function() or self._parse_var_or_string(upper=True)
 8178
 8179        if self._match(TokenType.FROM):
 8180            return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8181
 8182        if not self._match(TokenType.COMMA):
 8183            self.raise_error("Expected FROM or comma after EXTRACT", self._prev)
 8184
 8185        return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8186
 8187    def _parse_gap_fill(self) -> exp.GapFill:
 8188        self._match(TokenType.TABLE)
 8189        this = self._parse_table()
 8190
 8191        self._match(TokenType.COMMA)
 8192        args = [this, *self._parse_csv(self._parse_lambda)]
 8193
 8194        gap_fill = exp.GapFill.from_arg_list(args)
 8195        return self.validate_expression(gap_fill, args)
 8196
 8197    def _parse_char(self) -> exp.Chr:
 8198        return self.expression(
 8199            exp.Chr(
 8200                expressions=self._parse_csv(self._parse_assignment),
 8201                charset=self._match(TokenType.USING) and self._parse_charset_name(),
 8202            )
 8203        )
 8204
 8205    def _parse_charset_name(self) -> exp.Expr | None:
 8206        """
 8207        Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting
 8208        for specific name shapes override this.
 8209        """
 8210        return self._parse_var(
 8211            tokens={TokenType.BINARY, TokenType.IDENTIFIER},
 8212        )
 8213
 8214    def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr:
 8215        this = self._parse_assignment()
 8216
 8217        if not self._match(TokenType.ALIAS):
 8218            if self._match(TokenType.COMMA):
 8219                return self.expression(exp.CastToStrType(this=this, to=self._parse_string()))
 8220
 8221            self.raise_error("Expected AS after CAST")
 8222
 8223        fmt = None
 8224        to = self._parse_types(with_collation=True)
 8225
 8226        default = None
 8227        if self._match(TokenType.DEFAULT):
 8228            default = self._parse_bitwise()
 8229            self._match_text_seq("ON", "CONVERSION", "ERROR")
 8230
 8231        if self._match_set((TokenType.FORMAT, TokenType.COMMA)):
 8232            fmt_string = self._parse_wrapped(self._parse_string, optional=True)
 8233            fmt = self._parse_at_time_zone(fmt_string)
 8234
 8235            if not to:
 8236                to = exp.DType.UNKNOWN.into_expr()
 8237            if to.this in exp.DataType.TEMPORAL_TYPES:
 8238                this = self.expression(
 8239                    (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)(
 8240                        this=this,
 8241                        format=exp.Literal.string(
 8242                            format_time(
 8243                                fmt_string.this if fmt_string else "",
 8244                                self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING,
 8245                                self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE,
 8246                            )
 8247                        ),
 8248                        safe=safe,
 8249                    )
 8250                )
 8251
 8252                if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime):
 8253                    this.set("zone", fmt.args["zone"])
 8254                return this
 8255        elif not to:
 8256            self.raise_error("Expected TYPE after CAST")
 8257        elif isinstance(to, exp.Identifier):
 8258            to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True)
 8259        elif to.this == exp.DType.CHAR and (
 8260            self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")
 8261        ):
 8262            to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string())
 8263
 8264        return self.build_cast(
 8265            strict=strict,
 8266            this=this,
 8267            to=to,
 8268            format=fmt,
 8269            safe=safe,
 8270            action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False),
 8271            default=default,
 8272        )
 8273
 8274    def _parse_string_agg(self) -> exp.GroupConcat:
 8275        if self._match(TokenType.DISTINCT):
 8276            args: list[exp.Expr | None] = [
 8277                self.expression(exp.Distinct(expressions=[self._parse_disjunction()]))
 8278            ]
 8279            if self._match(TokenType.COMMA):
 8280                args.extend(self._parse_csv(self._parse_disjunction))
 8281        else:
 8282            args = self._parse_csv(self._parse_disjunction)  # type: ignore
 8283
 8284        if self._match_text_seq("ON", "OVERFLOW"):
 8285            # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior])
 8286            if self._match_text_seq("ERROR"):
 8287                on_overflow: exp.Expr | None = exp.var("ERROR")
 8288            else:
 8289                self._match_text_seq("TRUNCATE")
 8290                on_overflow = self.expression(
 8291                    exp.OverflowTruncateBehavior(
 8292                        this=self._parse_string(),
 8293                        with_count=(
 8294                            self._match_text_seq("WITH", "COUNT")
 8295                            or not self._match_text_seq("WITHOUT", "COUNT")
 8296                        ),
 8297                    )
 8298                )
 8299        else:
 8300            on_overflow = None
 8301
 8302        index = self._index
 8303        if not self._match(TokenType.R_PAREN) and args:
 8304            # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]])
 8305            # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n])
 8306            # The order is parsed through `this` as a canonicalization for WITHIN GROUPs
 8307            args[0] = self._parse_limit(this=self._parse_order(this=args[0]))
 8308            return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1)))
 8309
 8310        # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]).
 8311        # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that
 8312        # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them.
 8313        if not self._match_text_seq("WITHIN", "GROUP"):
 8314            self._retreat(index)
 8315            return self.validate_expression(exp.GroupConcat.from_arg_list(args), args)
 8316
 8317        # The corresponding match_r_paren will be called in parse_function (caller)
 8318        self._match_l_paren()
 8319
 8320        return self.expression(
 8321            exp.GroupConcat(
 8322                this=self._parse_order(this=seq_get(args, 0)),
 8323                separator=seq_get(args, 1),
 8324                on_overflow=on_overflow,
 8325            )
 8326        )
 8327
 8328    def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None:
 8329        this = self._parse_bitwise()
 8330
 8331        if self._match(TokenType.USING):
 8332            to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name())
 8333        elif self._match(TokenType.COMMA):
 8334            to = self._parse_types()
 8335        else:
 8336            to = None
 8337
 8338        return self.build_cast(strict=strict, this=this, to=to, safe=safe)
 8339
 8340    def _parse_xml_element(self) -> exp.XMLElement:
 8341        if self._match_text_seq("EVALNAME"):
 8342            evalname = True
 8343            this = self._parse_bitwise()
 8344        else:
 8345            evalname = None
 8346            self._match_text_seq("NAME")
 8347            this = self._parse_id_var()
 8348
 8349        return self.expression(
 8350            exp.XMLElement(
 8351                this=this,
 8352                expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise),
 8353                evalname=evalname,
 8354            )
 8355        )
 8356
 8357    def _parse_xml_table(self) -> exp.XMLTable:
 8358        namespaces = None
 8359        passing = None
 8360        columns = None
 8361
 8362        if self._match_text_seq("XMLNAMESPACES", "("):
 8363            namespaces = self._parse_xml_namespace()
 8364            self._match_text_seq(")", ",")
 8365
 8366        this = self._parse_string()
 8367
 8368        if self._match_text_seq("PASSING"):
 8369            # The BY VALUE keywords are optional and are provided for semantic clarity
 8370            self._match_text_seq("BY", "VALUE")
 8371            passing = self._parse_csv(self._parse_column)
 8372
 8373        by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF")
 8374
 8375        if self._match_text_seq("COLUMNS"):
 8376            columns = self._parse_csv(self._parse_field_def)
 8377
 8378        return self.expression(
 8379            exp.XMLTable(
 8380                this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref
 8381            )
 8382        )
 8383
 8384    def _parse_xml_namespace(self) -> list[exp.XMLNamespace]:
 8385        namespaces = []
 8386
 8387        while True:
 8388            if self._match(TokenType.DEFAULT):
 8389                uri = self._parse_string()
 8390            else:
 8391                uri = self._parse_alias(self._parse_string())
 8392            namespaces.append(self.expression(exp.XMLNamespace(this=uri)))
 8393            if not self._match(TokenType.COMMA):
 8394                break
 8395
 8396        return namespaces
 8397
 8398    def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None:
 8399        args = self._parse_csv(self._parse_disjunction)
 8400
 8401        if len(args) < 3:
 8402            return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1)))
 8403
 8404        return self.expression(exp.DecodeCase(expressions=args))
 8405
 8406    def _parse_json_key_value(self) -> exp.JSONKeyValue | None:
 8407        self._match_text_seq("KEY")
 8408        key = self._parse_column()
 8409        self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS)
 8410        self._match_text_seq("VALUE")
 8411        value = self._parse_bitwise()
 8412
 8413        if not key and not value:
 8414            return None
 8415        return self.expression(exp.JSONKeyValue(this=key, expression=value))
 8416
 8417    def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None:
 8418        if not this or not self._match_text_seq("FORMAT", "JSON"):
 8419            return this
 8420
 8421        return self.expression(exp.FormatJson(this=this))
 8422
 8423    def _parse_on_condition(self) -> exp.OnCondition | None:
 8424        # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS)
 8425        if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR:
 8426            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8427            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8428        else:
 8429            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8430            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8431
 8432        null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS)
 8433
 8434        if not empty and not error and not null:
 8435            return None
 8436
 8437        return self.expression(exp.OnCondition(empty=empty, error=error, null=null))
 8438
 8439    def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None:
 8440        # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL)
 8441        for value in values:
 8442            if self._match_text_seq(value, "ON", on):
 8443                return f"{value} ON {on}"
 8444
 8445        index = self._index
 8446        if self._match(TokenType.DEFAULT):
 8447            default_value = self._parse_bitwise()
 8448            if self._match_text_seq("ON", on):
 8449                return default_value
 8450
 8451            self._retreat(index)
 8452
 8453        return None
 8454
 8455    @t.overload
 8456    def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ...
 8457
 8458    @t.overload
 8459    def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ...
 8460
 8461    def _parse_json_object(self, agg=False):
 8462        star = self._parse_star()
 8463        expressions = (
 8464            [star]
 8465            if star
 8466            else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value()))
 8467        )
 8468        null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT")
 8469
 8470        unique_keys = None
 8471        if self._match_text_seq("WITH", "UNIQUE"):
 8472            unique_keys = True
 8473        elif self._match_text_seq("WITHOUT", "UNIQUE"):
 8474            unique_keys = False
 8475
 8476        self._match_text_seq("KEYS")
 8477
 8478        return_type = self._match_text_seq("RETURNING") and self._parse_format_json(
 8479            self._parse_type()
 8480        )
 8481        encoding = self._match_text_seq("ENCODING") and self._parse_var()
 8482
 8483        return self.expression(
 8484            (exp.JSONObjectAgg if agg else exp.JSONObject)(
 8485                expressions=expressions,
 8486                null_handling=null_handling,
 8487                unique_keys=unique_keys,
 8488                return_type=return_type,
 8489                encoding=encoding,
 8490            )
 8491        )
 8492
 8493    # Note: this is currently incomplete; it only implements the "JSON_value_column" part
 8494    def _parse_json_column_def(self) -> exp.JSONColumnDef:
 8495        if not self._match_text_seq("NESTED"):
 8496            this = self._parse_id_var()
 8497            ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY)
 8498            kind = self._parse_types(allow_identifiers=False)
 8499            nested = None
 8500        else:
 8501            this = None
 8502            ordinality = None
 8503            kind = None
 8504            nested = True
 8505
 8506        format_json = self._match_text_seq("FORMAT", "JSON")
 8507        path = self._match_text_seq("PATH") and self._parse_string()
 8508        nested_schema = nested and self._parse_json_schema()
 8509
 8510        return self.expression(
 8511            exp.JSONColumnDef(
 8512                this=this,
 8513                kind=kind,
 8514                path=path,
 8515                nested_schema=nested_schema,
 8516                ordinality=ordinality,
 8517                format_json=format_json,
 8518            )
 8519        )
 8520
 8521    def _parse_json_schema(self) -> exp.JSONSchema:
 8522        self._match_text_seq("COLUMNS")
 8523        return self.expression(
 8524            exp.JSONSchema(
 8525                expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True)
 8526            )
 8527        )
 8528
 8529    def _parse_json_table(self) -> exp.JSONTable:
 8530        this = self._parse_format_json(self._parse_bitwise())
 8531        path = self._match(TokenType.COMMA) and self._parse_string()
 8532        error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL")
 8533        empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL")
 8534        schema = self._parse_json_schema()
 8535
 8536        return exp.JSONTable(
 8537            this=this,
 8538            schema=schema,
 8539            path=path,
 8540            error_handling=error_handling,
 8541            empty_handling=empty_handling,
 8542        )
 8543
 8544    def _parse_match_against(self) -> exp.MatchAgainst:
 8545        if self._match_text_seq("TABLE"):
 8546            # parse SingleStore MATCH(TABLE ...) syntax
 8547            # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/
 8548            expressions = []
 8549            table = self._parse_table()
 8550            if table:
 8551                expressions = [table]
 8552        else:
 8553            expressions = self._parse_csv(self._parse_column)
 8554
 8555        self._match_text_seq(")", "AGAINST", "(")
 8556
 8557        this = self._parse_string()
 8558
 8559        if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"):
 8560            modifier = "IN NATURAL LANGUAGE MODE"
 8561            if self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8562                modifier = f"{modifier} WITH QUERY EXPANSION"
 8563        elif self._match_text_seq("IN", "BOOLEAN", "MODE"):
 8564            modifier = "IN BOOLEAN MODE"
 8565        elif self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8566            modifier = "WITH QUERY EXPANSION"
 8567        else:
 8568            modifier = None
 8569
 8570        return self.expression(
 8571            exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier)
 8572        )
 8573
 8574    # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16
 8575    def _parse_open_json(self) -> exp.OpenJSON:
 8576        this = self._parse_bitwise()
 8577        path = self._match(TokenType.COMMA) and self._parse_string()
 8578
 8579        def _parse_open_json_column_def() -> exp.OpenJSONColumnDef:
 8580            this = self._parse_field(any_token=True)
 8581            kind = self._parse_types()
 8582            path = self._parse_string()
 8583            as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON)
 8584
 8585            return self.expression(
 8586                exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json)
 8587            )
 8588
 8589        expressions = None
 8590        if self._match_pair(TokenType.R_PAREN, TokenType.WITH):
 8591            self._match_l_paren()
 8592            expressions = self._parse_csv(_parse_open_json_column_def)
 8593
 8594        return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions))
 8595
 8596    def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition:
 8597        args = self._parse_csv(self._parse_bitwise)
 8598
 8599        if self._match(TokenType.IN):
 8600            return self.expression(
 8601                exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0))
 8602            )
 8603
 8604        if haystack_first:
 8605            haystack = seq_get(args, 0)
 8606            needle = seq_get(args, 1)
 8607        else:
 8608            haystack = seq_get(args, 1)
 8609            needle = seq_get(args, 0)
 8610
 8611        return self.expression(
 8612            exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2))
 8613        )
 8614
 8615    def _parse_join_hint(self, func_name: str) -> exp.JoinHint:
 8616        args = self._parse_csv(self._parse_table)
 8617        return exp.JoinHint(this=func_name.upper(), expressions=args)
 8618
 8619    def _parse_substring(self) -> exp.Substring:
 8620        # Postgres supports the form: substring(string [from int] [for int])
 8621        # (despite being undocumented, the reverse order also works)
 8622        # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6
 8623
 8624        args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise))
 8625
 8626        start, length = None, None
 8627
 8628        while self._curr:
 8629            if self._match(TokenType.FROM):
 8630                start = self._parse_bitwise()
 8631            elif self._match(TokenType.FOR):
 8632                if not start:
 8633                    start = exp.Literal.number(1)
 8634                length = self._parse_bitwise()
 8635            else:
 8636                break
 8637
 8638        if start:
 8639            args.append(start)
 8640        if length:
 8641            args.append(length)
 8642
 8643        return self.validate_expression(exp.Substring.from_arg_list(args), args)
 8644
 8645    def _parse_trim(self) -> exp.Trim:
 8646        # https://www.w3resource.com/sql/character-functions/trim.php
 8647        # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html
 8648
 8649        position = None
 8650        collation = None
 8651        expression = None
 8652
 8653        if self._match_texts(self.TRIM_TYPES):
 8654            position = self._prev.text.upper()
 8655
 8656        this = self._parse_bitwise()
 8657        if self._match_set((TokenType.FROM, TokenType.COMMA)):
 8658            invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST
 8659            expression = self._parse_bitwise()
 8660
 8661            if invert_order:
 8662                this, expression = expression, this
 8663
 8664        if self._match(TokenType.COLLATE):
 8665            collation = self._parse_bitwise()
 8666
 8667        return self.expression(
 8668            exp.Trim(this=this, position=position, expression=expression, collation=collation)
 8669        )
 8670
 8671    def _parse_window_clause(self) -> list[exp.Expr] | None:
 8672        return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None
 8673
 8674    def _parse_named_window(self) -> exp.Expr | None:
 8675        return self._parse_window(self._parse_id_var(), alias=True)
 8676
 8677    def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None:
 8678        if self._curr.token_type == TokenType.VAR:
 8679            if self._match_text_seq("IGNORE", "NULLS"):
 8680                return self.expression(exp.IgnoreNulls(this=this))
 8681            if self._match_text_seq("RESPECT", "NULLS"):
 8682                return self.expression(exp.RespectNulls(this=this))
 8683        return this
 8684
 8685    def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None:
 8686        if self._match(TokenType.HAVING):
 8687            self._match_texts(("MAX", "MIN"))
 8688            max = self._prev.text.upper() != "MIN"
 8689            return self.expression(
 8690                exp.HavingMax(this=this, expression=self._parse_column(), max=max)
 8691            )
 8692
 8693        return this
 8694
 8695    def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None:
 8696        func = this
 8697        comments = func.comments if isinstance(func, exp.Expr) else None
 8698
 8699        # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html
 8700        if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue):
 8701            if self._match_text_seq("FROM", "FIRST"):
 8702                this.set("from_first", True)
 8703            elif self._match_text_seq("FROM", "LAST"):
 8704                this.set("from_first", False)
 8705
 8706        # T-SQL allows the OVER (...) syntax after WITHIN GROUP.
 8707        # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16
 8708        if self._match_text_seq("WITHIN", "GROUP"):
 8709            order = self._parse_wrapped(self._parse_order)
 8710            this = self.expression(exp.WithinGroup(this=this, expression=order))
 8711
 8712        if self._match_pair(TokenType.FILTER, TokenType.L_PAREN):
 8713            self._match(TokenType.WHERE)
 8714            this = self.expression(
 8715                exp.Filter(this=this, expression=self._parse_where(skip_where_token=True))
 8716            )
 8717            self._match_r_paren()
 8718
 8719        # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER
 8720        # Some dialects choose to implement and some do not.
 8721        # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html
 8722
 8723        # There is some code above in _parse_lambda that handles
 8724        #   SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ...
 8725
 8726        # The below changes handle
 8727        #   SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ...
 8728
 8729        # Oracle allows both formats
 8730        #   (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html)
 8731        #   and Snowflake chose to do the same for familiarity
 8732        #   https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes
 8733        if isinstance(this, exp.AggFunc):
 8734            ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls)
 8735
 8736            if ignore_respect and ignore_respect is not this:
 8737                ignore_respect.replace(ignore_respect.this)
 8738                this = self.expression(ignore_respect.__class__(this=this))
 8739
 8740        this = self._parse_respect_or_ignore_nulls(this)
 8741
 8742        # bigquery select from window x AS (partition by ...)
 8743        if alias:
 8744            over = None
 8745            self._match(TokenType.ALIAS)
 8746        elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS):
 8747            return this
 8748        else:
 8749            over = self._prev.text.upper()
 8750
 8751        if comments and isinstance(func, exp.Expr):
 8752            func.pop_comments()
 8753
 8754        if not self._match(TokenType.L_PAREN):
 8755            return self.expression(
 8756                exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments
 8757            )
 8758
 8759        window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS)
 8760
 8761        first: bool | None = True if self._match(TokenType.FIRST) else None
 8762        if self._match_text_seq("LAST"):
 8763            first = False
 8764
 8765        partition, order = self._parse_partition_and_order()
 8766        kind = (
 8767            self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS")
 8768        ) and self._prev.text
 8769
 8770        if kind:
 8771            self._match(TokenType.BETWEEN)
 8772            start = self._parse_window_spec()
 8773
 8774            end = self._parse_window_spec() if self._match(TokenType.AND) else {}
 8775            exclude = (
 8776                self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS)
 8777                if self._match_text_seq("EXCLUDE")
 8778                else None
 8779            )
 8780
 8781            spec = self.expression(
 8782                exp.WindowSpec(
 8783                    kind=kind,
 8784                    start=start["value"],
 8785                    start_side=start["side"],
 8786                    end=end.get("value"),
 8787                    end_side=end.get("side"),
 8788                    exclude=exclude,
 8789                )
 8790            )
 8791        else:
 8792            spec = None
 8793
 8794        self._match_r_paren()
 8795
 8796        window = self.expression(
 8797            exp.Window(
 8798                this=this,
 8799                partition_by=partition,
 8800                order=order,
 8801                spec=spec,
 8802                alias=window_alias,
 8803                over=over,
 8804                first=first,
 8805            ),
 8806            comments=comments,
 8807        )
 8808
 8809        # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...)
 8810        if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False):
 8811            return self._parse_window(window, alias=alias)
 8812
 8813        return window
 8814
 8815    def _parse_partition_and_order(
 8816        self,
 8817    ) -> tuple[list[exp.Expr], exp.Expr | None]:
 8818        return self._parse_partition_by(), self._parse_order()
 8819
 8820    def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]:
 8821        self._match(TokenType.BETWEEN)
 8822
 8823        return {
 8824            "value": (
 8825                (self._match_text_seq("UNBOUNDED") and "UNBOUNDED")
 8826                or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW")
 8827                or self._parse_bitwise()
 8828            ),
 8829            "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None,
 8830        }
 8831
 8832    def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None:
 8833        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 8834        # so this section tries to parse the clause version and if it fails, it treats the token
 8835        # as an identifier (alias)
 8836        if self._can_parse_limit_or_offset():
 8837            return this
 8838
 8839        # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the
 8840        # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it.
 8841        if self._can_parse_named_window():
 8842            return this
 8843
 8844        any_token = self._match(TokenType.ALIAS)
 8845        comments = self._prev_comments
 8846
 8847        if explicit and not any_token:
 8848            return this
 8849
 8850        if self._match(TokenType.L_PAREN):
 8851            aliases = self.expression(
 8852                exp.Aliases(
 8853                    this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token))
 8854                ),
 8855                comments=comments,
 8856            )
 8857            self._match_r_paren(aliases)
 8858            return aliases
 8859
 8860        alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or (
 8861            self.STRING_ALIASES and self._parse_string_as_identifier()
 8862        )
 8863
 8864        if alias:
 8865            comments.extend(alias.pop_comments())
 8866            this = self.expression(exp.Alias(this=this, alias=alias), comments=comments)
 8867            column = this.this
 8868
 8869            # Moves the comment next to the alias in `expr /* comment */ AS alias`
 8870            if not this.comments and column and column.comments:
 8871                this.comments = column.pop_comments()
 8872
 8873        return this
 8874
 8875    def _parse_id_var(
 8876        self,
 8877        any_token: bool = True,
 8878        tokens: t.Collection[TokenType] | None = None,
 8879    ) -> exp.Expr | None:
 8880        expression = self._parse_identifier()
 8881        if not expression and (
 8882            (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS)
 8883        ):
 8884            quoted = self._prev.token_type == TokenType.STRING
 8885            expression = self._identifier_expression(quoted=quoted)
 8886
 8887        return expression
 8888
 8889    def _parse_string(self) -> exp.Expr | None:
 8890        if self._match_set(self.STRING_PARSERS):
 8891            return self.STRING_PARSERS[self._prev.token_type](self, self._prev)
 8892        return self._parse_placeholder()
 8893
 8894    def _parse_string_as_identifier(self) -> exp.Identifier | None:
 8895        if not self._match(TokenType.STRING):
 8896            return None
 8897        output = exp.to_identifier(self._prev.text, quoted=True)
 8898        output.update_positions(self._prev)
 8899        return output
 8900
 8901    def _parse_number(self) -> exp.Expr | None:
 8902        if self._match_set(self.NUMERIC_PARSERS):
 8903            return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev)
 8904        return self._parse_placeholder()
 8905
 8906    def _parse_identifier(self) -> exp.Expr | None:
 8907        if self._match(TokenType.IDENTIFIER):
 8908            return self._identifier_expression(quoted=True)
 8909        return self._parse_placeholder()
 8910
 8911    def _parse_var(
 8912        self,
 8913        any_token: bool = False,
 8914        tokens: t.Collection[TokenType] | None = None,
 8915        upper: bool = False,
 8916    ) -> exp.Expr | None:
 8917        if (
 8918            (any_token and self._advance_any())
 8919            or self._match(TokenType.VAR)
 8920            or (self._match_set(tokens) if tokens else False)
 8921        ):
 8922            return self.expression(
 8923                exp.Var(this=self._prev.text.upper() if upper else self._prev.text)
 8924            )
 8925        return self._parse_placeholder()
 8926
 8927    def _advance_any(self, ignore_reserved: bool = False) -> Token | None:
 8928        if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS):
 8929            self._advance()
 8930            return self._prev
 8931        return None
 8932
 8933    def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None:
 8934        return self._parse_string() or self._parse_var(any_token=True, upper=upper)
 8935
 8936    def _parse_primary_or_var(self) -> exp.Expr | None:
 8937        return self._parse_primary() or self._parse_var(any_token=True)
 8938
 8939    def _parse_null(self) -> exp.Expr | None:
 8940        if self._match_set((TokenType.NULL, TokenType.UNKNOWN)):
 8941            return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev)
 8942        return self._parse_placeholder()
 8943
 8944    def _parse_boolean(self) -> exp.Expr | None:
 8945        if self._match(TokenType.TRUE):
 8946            return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev)
 8947        if self._match(TokenType.FALSE):
 8948            return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev)
 8949        return self._parse_placeholder()
 8950
 8951    def _parse_star(self) -> exp.Expr | None:
 8952        if self._match(TokenType.STAR):
 8953            return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev)
 8954        return self._parse_placeholder()
 8955
 8956    def _parse_parameter(self) -> exp.Parameter:
 8957        this = self._parse_identifier() or self._parse_primary_or_var()
 8958        return self.expression(exp.Parameter(this=this))
 8959
 8960    def _parse_placeholder(self) -> exp.Expr | None:
 8961        if self._match_set(self.PLACEHOLDER_PARSERS):
 8962            placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self)
 8963            if placeholder:
 8964                return placeholder
 8965            self._advance(-1)
 8966        return None
 8967
 8968    def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None:
 8969        if not self._match_texts(keywords):
 8970            return None
 8971        if self._match(TokenType.L_PAREN, advance=False):
 8972            return self._parse_wrapped_csv(self._parse_expression)
 8973
 8974        expression = self._parse_alias(self._parse_disjunction(), explicit=True)
 8975        return [expression] if expression else None
 8976
 8977    def _parse_csv(
 8978        self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA
 8979    ) -> list[T]:
 8980        parse_result = parse_method()
 8981        items = [parse_result] if parse_result is not None else []
 8982
 8983        while self._match(sep):
 8984            if isinstance(parse_result, exp.Expr):
 8985                self._add_comments(parse_result)
 8986            parse_result = parse_method()
 8987            if parse_result is not None:
 8988                items.append(parse_result)
 8989
 8990        return items
 8991
 8992    def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]:
 8993        return self._parse_wrapped_csv(self._parse_id_var, optional=optional)
 8994
 8995    def _parse_wrapped_csv(
 8996        self,
 8997        parse_method: t.Callable[[], T | None],
 8998        sep: TokenType = TokenType.COMMA,
 8999        optional: bool = False,
 9000    ) -> list[T]:
 9001        return self._parse_wrapped(
 9002            lambda: self._parse_csv(parse_method, sep=sep), optional=optional
 9003        )
 9004
 9005    def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T:
 9006        wrapped = self._match(TokenType.L_PAREN)
 9007        if not wrapped and not optional:
 9008            self.raise_error("Expecting (")
 9009        parse_result = parse_method()
 9010        if wrapped:
 9011            self._match_r_paren()
 9012        return parse_result
 9013
 9014    def _parse_expressions(self) -> list[exp.Expr]:
 9015        return self._parse_csv(self._parse_expression)
 9016
 9017    def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None:
 9018        return (
 9019            self._parse_set_operations(
 9020                self._parse_alias(self._parse_assignment(), explicit=True)
 9021                if alias
 9022                else self._parse_assignment()
 9023            )
 9024            or self._parse_select()
 9025        )
 9026
 9027    def _parse_ddl_select(self) -> exp.Expr | None:
 9028        return self._parse_query_modifiers(
 9029            self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False))
 9030        )
 9031
 9032    def _parse_transaction(self) -> exp.Transaction | exp.Command:
 9033        this = None
 9034        if self._match_texts(self.TRANSACTION_KIND):
 9035            this = self._prev.text
 9036
 9037        self._match_texts(("TRANSACTION", "WORK"))
 9038
 9039        modes = []
 9040        while True:
 9041            mode = []
 9042            while self._match(TokenType.VAR) or self._match(TokenType.NOT):
 9043                mode.append(self._prev.text)
 9044
 9045            if mode:
 9046                modes.append(" ".join(mode))
 9047            if not self._match(TokenType.COMMA):
 9048                break
 9049
 9050        return self.expression(exp.Transaction(this=this, modes=modes))
 9051
 9052    def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback:
 9053        chain = None
 9054        savepoint = None
 9055        is_rollback = self._prev.token_type == TokenType.ROLLBACK
 9056
 9057        self._match_texts(("TRANSACTION", "WORK"))
 9058
 9059        if self._match_text_seq("TO"):
 9060            self._match_text_seq("SAVEPOINT")
 9061            savepoint = self._parse_id_var()
 9062
 9063        if self._match(TokenType.AND):
 9064            chain = not self._match_text_seq("NO")
 9065            self._match_text_seq("CHAIN")
 9066
 9067        if is_rollback:
 9068            return self.expression(exp.Rollback(savepoint=savepoint))
 9069
 9070        return self.expression(exp.Commit(chain=chain))
 9071
 9072    def _parse_refresh(self) -> exp.Refresh | exp.Command:
 9073        if self._match_text_seq("EXTERNAL", "TABLE"):
 9074            kind = "EXTERNAL TABLE"
 9075        elif self._match(TokenType.TABLE):
 9076            kind = "TABLE"
 9077        elif self._match_text_seq("MATERIALIZED", "VIEW"):
 9078            kind = "MATERIALIZED VIEW"
 9079        else:
 9080            kind = ""
 9081
 9082        this = self._parse_string() or self._parse_table()
 9083        if not kind and not isinstance(this, exp.Literal):
 9084            return self._parse_as_command(self._prev)
 9085
 9086        return self.expression(exp.Refresh(this=this, kind=kind))
 9087
 9088    def _parse_column_def_with_exists(self):
 9089        start = self._index
 9090        self._match(TokenType.COLUMN)
 9091
 9092        exists_column = self._parse_exists(not_=True)
 9093        expression = self._parse_field_def()
 9094
 9095        if not isinstance(expression, exp.ColumnDef):
 9096            self._retreat(start)
 9097            return None
 9098
 9099        expression.set("exists", exists_column)
 9100
 9101        return expression
 9102
 9103    def _parse_add_column(self) -> exp.ColumnDef | None:
 9104        if not self._prev.text.upper() == "ADD":
 9105            return None
 9106
 9107        return self._parse_column_def_with_exists()
 9108
 9109    def _parse_drop_column(self) -> exp.Drop | exp.Command | None:
 9110        drop = self._parse_drop() if self._match(TokenType.DROP) else None
 9111        if drop and not isinstance(drop, exp.Command):
 9112            drop.set("kind", drop.args.get("kind", "COLUMN"))
 9113        return drop
 9114
 9115    def _parse_alter_drop_action(self) -> exp.Expr | None:
 9116        return self._parse_drop_column()
 9117
 9118    # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html
 9119    def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition:
 9120        return self.expression(
 9121            exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists)
 9122        )
 9123
 9124    def _parse_alter_table_add(self) -> list[exp.Expr]:
 9125        def _parse_add_alteration() -> exp.Expr | None:
 9126            self._match_text_seq("ADD")
 9127            if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False):
 9128                return self.expression(
 9129                    exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint))
 9130                )
 9131
 9132            column_def = self._parse_add_column()
 9133            if isinstance(column_def, exp.ColumnDef):
 9134                return column_def
 9135
 9136            exists = self._parse_exists(not_=True)
 9137            if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False):
 9138                return self.expression(
 9139                    exp.AddPartition(
 9140                        exists=exists,
 9141                        this=self._parse_field(any_token=True),
 9142                        location=self._match_text_seq("LOCATION", advance=False)
 9143                        and self._parse_property(),
 9144                    )
 9145                )
 9146
 9147            return None
 9148
 9149        if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and (
 9150            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
 9151            or self._match_text_seq("COLUMNS")
 9152        ):
 9153            schema = self._parse_schema()
 9154
 9155            return (
 9156                ensure_list(schema)
 9157                if schema
 9158                else self._parse_csv(self._parse_column_def_with_exists)
 9159            )
 9160
 9161        return self._parse_csv(_parse_add_alteration)
 9162
 9163    def _parse_alter_table_alter(self) -> exp.Expr | None:
 9164        if self._match_texts(self.ALTER_ALTER_PARSERS):
 9165            return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self)
 9166
 9167        # Many dialects support the ALTER [COLUMN] syntax, so if there is no
 9168        # keyword after ALTER we default to parsing this statement
 9169        self._match(TokenType.COLUMN)
 9170        exists = self._parse_exists()
 9171        column = self._parse_field(any_token=True)
 9172
 9173        if self._match_pair(TokenType.DROP, TokenType.DEFAULT):
 9174            return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None))
 9175        if self._match_pair(TokenType.SET, TokenType.DEFAULT):
 9176            return self.expression(
 9177                exp.AlterColumn(
 9178                    this=column, default=self._parse_disjunction(), exists=exists or None
 9179                )
 9180            )
 9181        if self._match(TokenType.COMMENT):
 9182            return self.expression(
 9183                exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None)
 9184            )
 9185        if self._match_text_seq("DROP", "NOT", "NULL"):
 9186            return self.expression(
 9187                exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None)
 9188            )
 9189        if self._match_text_seq("SET", "NOT", "NULL"):
 9190            return self.expression(
 9191                exp.AlterColumn(this=column, allow_null=False, exists=exists or None)
 9192            )
 9193
 9194        if self._match_text_seq("SET", "VISIBLE"):
 9195            return self.expression(
 9196                exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None)
 9197            )
 9198        if self._match_text_seq("SET", "INVISIBLE"):
 9199            return self.expression(
 9200                exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None)
 9201            )
 9202
 9203        self._match_text_seq("SET", "DATA")
 9204        self._match_text_seq("TYPE")
 9205        return self.expression(
 9206            exp.AlterColumn(
 9207                this=column,
 9208                dtype=self._parse_types(),
 9209                collate=self._match(TokenType.COLLATE) and self._parse_term(),
 9210                using=self._match(TokenType.USING) and self._parse_disjunction(),
 9211                exists=exists or None,
 9212            )
 9213        )
 9214
 9215    def _parse_alter_diststyle(self) -> exp.AlterDistStyle:
 9216        if self._match_texts(("ALL", "EVEN", "AUTO")):
 9217            return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper())))
 9218
 9219        self._match_text_seq("KEY", "DISTKEY")
 9220        return self.expression(exp.AlterDistStyle(this=self._parse_column()))
 9221
 9222    def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey:
 9223        if compound:
 9224            self._match_text_seq("SORTKEY")
 9225
 9226        if self._match(TokenType.L_PAREN, advance=False):
 9227            return self.expression(
 9228                exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound)
 9229            )
 9230
 9231        self._match_texts(("AUTO", "NONE"))
 9232        return self.expression(
 9233            exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound)
 9234        )
 9235
 9236    def _parse_alter_table_drop(self) -> list[exp.Expr]:
 9237        index = self._index - 1
 9238
 9239        partition_exists = self._parse_exists()
 9240        if self._match(TokenType.PARTITION, advance=False):
 9241            return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists))
 9242
 9243        self._retreat(index)
 9244        return self._parse_csv(self._parse_alter_drop_action)
 9245
 9246    def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None:
 9247        if self._match(TokenType.COLUMN) or (
 9248            not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False)
 9249        ):
 9250            exists = self._parse_exists()
 9251            old_column = self._parse_column()
 9252            to = self._match_text_seq("TO")
 9253            new_column = self._parse_column()
 9254
 9255            if old_column is None or not to or new_column is None:
 9256                return None
 9257
 9258            return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists))
 9259
 9260        self._match_text_seq("TO")
 9261        return self.expression(exp.AlterRename(this=self._parse_table(schema=True)))
 9262
 9263    def _parse_alter_table_set(self) -> exp.AlterSet:
 9264        alter_set = self.expression(exp.AlterSet())
 9265
 9266        if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq(
 9267            "TABLE", "PROPERTIES"
 9268        ):
 9269            alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment))
 9270        elif self._match_text_seq("FILESTREAM_ON", advance=False):
 9271            alter_set.set("expressions", [self._parse_assignment()])
 9272        elif self._match_texts(("LOGGED", "UNLOGGED")):
 9273            alter_set.set("option", exp.var(self._prev.text.upper()))
 9274        elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")):
 9275            alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}"))
 9276        elif self._match_text_seq("LOCATION"):
 9277            alter_set.set("location", self._parse_field())
 9278        elif self._match_text_seq("ACCESS", "METHOD"):
 9279            alter_set.set("access_method", self._parse_field())
 9280        elif self._match_text_seq("TABLESPACE"):
 9281            alter_set.set("tablespace", self._parse_field())
 9282        elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"):
 9283            alter_set.set("file_format", [self._parse_field()])
 9284        elif self._match_text_seq("STAGE_FILE_FORMAT"):
 9285            alter_set.set("file_format", self._parse_wrapped_options())
 9286        elif self._match_text_seq("STAGE_COPY_OPTIONS"):
 9287            alter_set.set("copy_options", self._parse_wrapped_options())
 9288        elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"):
 9289            alter_set.set("tag", self._parse_csv(self._parse_assignment))
 9290        else:
 9291            if self._match_text_seq("SERDE"):
 9292                alter_set.set("serde", self._parse_field())
 9293
 9294            properties = self._parse_wrapped(self._parse_properties, optional=True)
 9295            alter_set.set("expressions", [properties])
 9296
 9297        return alter_set
 9298
 9299    def _parse_alter_session(self) -> exp.AlterSession:
 9300        """Parse ALTER SESSION SET/UNSET statements."""
 9301        if self._match(TokenType.SET):
 9302            expressions = self._parse_csv(lambda: self._parse_set_item_assignment())
 9303            return self.expression(exp.AlterSession(expressions=expressions, unset=False))
 9304
 9305        self._match_text_seq("UNSET")
 9306        expressions = self._parse_csv(
 9307            lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True)))
 9308        )
 9309        return self.expression(exp.AlterSession(expressions=expressions, unset=True))
 9310
 9311    def _parse_alter(self) -> exp.Alter | exp.Command:
 9312        start = self._prev
 9313
 9314        iceberg = self._match_text_seq("ICEBERG")
 9315
 9316        alter_token = self._match_set(self.ALTERABLES) and self._prev
 9317        if not alter_token:
 9318            return self._parse_as_command(start)
 9319        if iceberg and alter_token.token_type != TokenType.TABLE:
 9320            return self._parse_as_command(start)
 9321
 9322        exists = self._parse_exists()
 9323        only = self._match_text_seq("ONLY")
 9324
 9325        if alter_token.token_type == TokenType.SESSION:
 9326            this = None
 9327            check = None
 9328            cluster = None
 9329        else:
 9330            this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS)
 9331            check = self._match_text_seq("WITH", "CHECK")
 9332            cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9333
 9334            if self._next:
 9335                self._advance()
 9336
 9337        parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None
 9338        if parser:
 9339            actions = ensure_list(parser(self))
 9340            not_valid = self._match_text_seq("NOT", "VALID")
 9341            options = self._parse_csv(self._parse_property)
 9342            cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE")
 9343
 9344            if not self._curr and actions:
 9345                return self.expression(
 9346                    exp.Alter(
 9347                        this=this,
 9348                        kind=alter_token.text.upper(),
 9349                        exists=exists,
 9350                        actions=actions,
 9351                        only=only,
 9352                        options=options,
 9353                        cluster=cluster,
 9354                        not_valid=not_valid,
 9355                        check=check,
 9356                        cascade=cascade,
 9357                        iceberg=iceberg,
 9358                    )
 9359                )
 9360
 9361        return self._parse_as_command(start)
 9362
 9363    def _parse_analyze(self) -> exp.Analyze | exp.Command:
 9364        start = self._prev
 9365        # https://duckdb.org/docs/sql/statements/analyze
 9366        if not self._curr:
 9367            return self.expression(exp.Analyze())
 9368
 9369        options = []
 9370        while self._match_texts(self.ANALYZE_STYLES):
 9371            if self._prev.text.upper() == "BUFFER_USAGE_LIMIT":
 9372                options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}")
 9373            else:
 9374                options.append(self._prev.text.upper())
 9375
 9376        tables: exp.Expr | list[exp.Expr] | None = None
 9377        inner_expression: exp.Expr | None = None
 9378
 9379        kind = self._curr.text.upper() if self._curr else None
 9380
 9381        if self._match(TokenType.TABLE):
 9382            tables = self._parse_csv(self._parse_table_parts)
 9383        elif self._match(TokenType.INDEX):
 9384            tables = self._parse_table_parts()
 9385        elif self._match_text_seq("TABLES"):
 9386            if self._match_set((TokenType.FROM, TokenType.IN)):
 9387                kind = f"{kind} {self._prev.text.upper()}"
 9388                tables = self._parse_table(schema=True, is_db_reference=True)
 9389        elif self._match_text_seq("DATABASE"):
 9390            tables = self._parse_table(schema=True, is_db_reference=True)
 9391        elif self._match_text_seq("CLUSTER"):
 9392            tables = self._parse_table()
 9393        # Try matching inner expr keywords before fallback to parse table.
 9394        elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9395            kind = None
 9396            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9397        else:
 9398            # Empty kind  https://prestodb.io/docs/current/sql/analyze.html
 9399            kind = None
 9400            tables = self._parse_csv(self._parse_table_parts)
 9401
 9402        partition = self._try_parse(self._parse_partition)
 9403        if not partition and self._match_texts(self.PARTITION_KEYWORDS):
 9404            return self._parse_as_command(start)
 9405
 9406        # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9407        if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq(
 9408            "WITH", "ASYNC", "MODE"
 9409        ):
 9410            mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE"
 9411        else:
 9412            mode = None
 9413
 9414        if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9415            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9416
 9417        properties = self._parse_properties()
 9418        return self.expression(
 9419            exp.Analyze(
 9420                kind=kind,
 9421                tables=ensure_list(tables),
 9422                mode=mode,
 9423                partition=partition,
 9424                properties=properties,
 9425                expression=inner_expression,
 9426                options=options,
 9427            )
 9428        )
 9429
 9430    # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html
 9431    def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics:
 9432        this = None
 9433        kind = self._prev.text.upper()
 9434        option = self._prev.text.upper() if self._match_text_seq("DELTA") else None
 9435        expressions = []
 9436
 9437        if not self._match_text_seq("STATISTICS"):
 9438            self.raise_error("Expecting token STATISTICS")
 9439
 9440        if self._match_text_seq("NOSCAN"):
 9441            this = "NOSCAN"
 9442        elif self._match(TokenType.FOR):
 9443            if self._match_text_seq("ALL", "COLUMNS"):
 9444                this = "FOR ALL COLUMNS"
 9445            if self._match_text_seq("COLUMNS"):
 9446                this = "FOR COLUMNS"
 9447                expressions = self._parse_csv(self._parse_column_reference)
 9448        elif self._match_text_seq("SAMPLE"):
 9449            sample = self._parse_number()
 9450            expressions = [
 9451                self.expression(
 9452                    exp.AnalyzeSample(
 9453                        sample=sample,
 9454                        kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None,
 9455                    )
 9456                )
 9457            ]
 9458
 9459        return self.expression(
 9460            exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions)
 9461        )
 9462
 9463    # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html
 9464    def _parse_analyze_validate(self) -> exp.AnalyzeValidate:
 9465        kind = None
 9466        this = None
 9467        expression: exp.Expr | None = None
 9468        if self._match_text_seq("REF", "UPDATE"):
 9469            kind = "REF"
 9470            this = "UPDATE"
 9471            if self._match_text_seq("SET", "DANGLING", "TO", "NULL"):
 9472                this = "UPDATE SET DANGLING TO NULL"
 9473        elif self._match_text_seq("STRUCTURE"):
 9474            kind = "STRUCTURE"
 9475            if self._match_text_seq("CASCADE", "FAST"):
 9476                this = "CASCADE FAST"
 9477            elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts(
 9478                ("ONLINE", "OFFLINE")
 9479            ):
 9480                this = f"CASCADE COMPLETE {self._prev.text.upper()}"
 9481                expression = self._parse_into()
 9482
 9483        return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression))
 9484
 9485    def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None:
 9486        this = self._prev.text.upper()
 9487        if self._match_text_seq("COLUMNS"):
 9488            return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}"))
 9489        return None
 9490
 9491    def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None:
 9492        kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None
 9493        if self._match_text_seq("STATISTICS"):
 9494            return self.expression(exp.AnalyzeDelete(kind=kind))
 9495        return None
 9496
 9497    def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None:
 9498        if self._match_text_seq("CHAINED", "ROWS"):
 9499            return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into()))
 9500        return None
 9501
 9502    # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html
 9503    def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram:
 9504        this = self._prev.text.upper()
 9505        expression: exp.Expr | None = None
 9506        expressions = []
 9507        update_options = None
 9508
 9509        if self._match_text_seq("HISTOGRAM", "ON"):
 9510            expressions = self._parse_csv(self._parse_column_reference)
 9511            with_expressions = []
 9512            while self._match(TokenType.WITH):
 9513                # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9514                if self._match_texts(("SYNC", "ASYNC")):
 9515                    if self._match_text_seq("MODE", advance=False):
 9516                        with_expressions.append(f"{self._prev.text.upper()} MODE")
 9517                        self._advance()
 9518                else:
 9519                    buckets = self._parse_number()
 9520                    if self._match_text_seq("BUCKETS"):
 9521                        with_expressions.append(f"{buckets} BUCKETS")
 9522            if with_expressions:
 9523                expression = self.expression(exp.AnalyzeWith(expressions=with_expressions))
 9524
 9525            if self._match_texts(("MANUAL", "AUTO")) and self._match(
 9526                TokenType.UPDATE, advance=False
 9527            ):
 9528                update_options = self._prev.text.upper()
 9529                self._advance()
 9530            elif self._match_text_seq("USING", "DATA"):
 9531                expression = self.expression(exp.UsingData(this=self._parse_string()))
 9532
 9533        return self.expression(
 9534            exp.AnalyzeHistogram(
 9535                this=this,
 9536                expressions=expressions,
 9537                expression=expression,
 9538                update_options=update_options,
 9539            )
 9540        )
 9541
 9542    def _parse_merge(self) -> exp.Merge:
 9543        self._match(TokenType.INTO)
 9544        target = self._parse_table()
 9545
 9546        if target and self._match(TokenType.ALIAS, advance=False):
 9547            target.set("alias", self._parse_table_alias())
 9548
 9549        self._match(TokenType.USING)
 9550        using = self._parse_table()
 9551
 9552        return self.expression(
 9553            exp.Merge(
 9554                this=target,
 9555                using=using,
 9556                on=self._match(TokenType.ON) and self._parse_disjunction(),
 9557                using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(),
 9558                whens=self._parse_when_matched(),
 9559                returning=self._parse_returning(),
 9560            )
 9561        )
 9562
 9563    def _parse_when_matched(self) -> exp.Whens:
 9564        whens = []
 9565
 9566        while self._match(TokenType.WHEN):
 9567            matched = not self._match(TokenType.NOT)
 9568            self._match_text_seq("MATCHED")
 9569            source = (
 9570                False
 9571                if self._match_text_seq("BY", "TARGET")
 9572                else self._match_text_seq("BY", "SOURCE")
 9573            )
 9574            condition = self._parse_disjunction() if self._match(TokenType.AND) else None
 9575
 9576            self._match(TokenType.THEN)
 9577
 9578            if self._match(TokenType.INSERT):
 9579                this = self._parse_star()
 9580                if this:
 9581                    then: exp.Expr | None = self.expression(exp.Insert(this=this))
 9582                else:
 9583                    then = self.expression(
 9584                        exp.Insert(
 9585                            this=exp.var("ROW")
 9586                            if self._match_text_seq("ROW")
 9587                            else self._parse_value(values=False),
 9588                            expression=self._match_text_seq("VALUES") and self._parse_value(),
 9589                            where=self._parse_where(),
 9590                        )
 9591                    )
 9592            elif self._match(TokenType.UPDATE):
 9593                expressions = self._parse_star()
 9594                if expressions:
 9595                    then = self.expression(exp.Update(expressions=expressions))
 9596                else:
 9597                    then = self.expression(
 9598                        exp.Update(
 9599                            expressions=self._match(TokenType.SET)
 9600                            and self._parse_csv(self._parse_update_assignment),
 9601                            where=self._parse_where(),
 9602                        )
 9603                    )
 9604            elif self._match(TokenType.DELETE):
 9605                then = self.expression(exp.Var(this=self._prev.text))
 9606            else:
 9607                then = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 9608
 9609            whens.append(
 9610                self.expression(
 9611                    exp.When(matched=matched, source=source, condition=condition, then=then)
 9612                )
 9613            )
 9614        return self.expression(exp.Whens(expressions=whens))
 9615
 9616    def _parse_show(self) -> exp.Expr | None:
 9617        parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE)
 9618        if parser:
 9619            return parser(self)
 9620        return self._parse_as_command(self._prev)
 9621
 9622    def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None:
 9623        index = self._index
 9624
 9625        if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"):
 9626            return self._parse_set_transaction(global_=kind == "GLOBAL")
 9627
 9628        left = self._parse_primary() or self._parse_column()
 9629        assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS)
 9630
 9631        if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter):
 9632            self._retreat(index)
 9633            return None
 9634
 9635        right = self._parse_statement() or self._parse_id_var()
 9636        if isinstance(right, (exp.Column, exp.Identifier)):
 9637            right = exp.var(right.name)
 9638
 9639        this = self.expression(exp.EQ(this=left, expression=right))
 9640        return self.expression(exp.SetItem(this=this, kind=kind))
 9641
 9642    def _parse_set_transaction(self, global_: bool = False) -> exp.Expr:
 9643        self._match_text_seq("TRANSACTION")
 9644        characteristics = self._parse_csv(
 9645            lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS)
 9646        )
 9647        return self.expression(
 9648            exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_)
 9649        )
 9650
 9651    def _parse_set_item(self) -> exp.Expr | None:
 9652        parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE)
 9653        return parser(self) if parser else self._parse_set_item_assignment(kind=None)
 9654
 9655    def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command:
 9656        index = self._index
 9657        set_ = self.expression(
 9658            exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag)
 9659        )
 9660
 9661        if self._curr:
 9662            self._retreat(index)
 9663            return self._parse_as_command(self._prev)
 9664
 9665        return set_
 9666
 9667    def _parse_var_from_options(
 9668        self, options: OPTIONS_TYPE, raise_unmatched: bool = True
 9669    ) -> exp.Var | None:
 9670        start = self._curr
 9671        if not start:
 9672            return None
 9673
 9674        option = start.text.upper()
 9675        continuations = (
 9676            None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option)
 9677        )
 9678
 9679        index = self._index
 9680        self._advance()
 9681        for keywords in continuations or []:
 9682            if isinstance(keywords, str):
 9683                keywords = (keywords,)
 9684
 9685            if self._match_text_seq(*keywords):
 9686                option = f"{option} {' '.join(keywords)}"
 9687                break
 9688        else:
 9689            if continuations or continuations is None:
 9690                if raise_unmatched:
 9691                    self.raise_error(f"Unknown option {option}")
 9692
 9693                self._retreat(index)
 9694                return None
 9695
 9696        return exp.var(option)
 9697
 9698    def _parse_as_command(self, start: Token) -> exp.Command:
 9699        while self._curr:
 9700            self._advance()
 9701        text = self._find_sql(start, self._prev)
 9702        size = len(start.text)
 9703        self._warn_unsupported()
 9704        return exp.Command(this=text[:size], expression=text[size:])
 9705
 9706    def _parse_dict_property(self, this: str) -> exp.DictProperty:
 9707        settings = []
 9708
 9709        self._match_l_paren()
 9710        kind = self._parse_id_var()
 9711
 9712        if self._match(TokenType.L_PAREN):
 9713            while True:
 9714                key = self._parse_id_var()
 9715                value = self._parse_function() or self._parse_primary_or_var()
 9716                if not key and value is None:
 9717                    break
 9718                settings.append(self.expression(exp.DictSubProperty(this=key, value=value)))
 9719            self._match(TokenType.R_PAREN)
 9720
 9721        self._match_r_paren()
 9722
 9723        return self.expression(
 9724            exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings)
 9725        )
 9726
 9727    def _parse_dict_range(self, this: str) -> exp.DictRange:
 9728        self._match_l_paren()
 9729        has_min = self._match_text_seq("MIN")
 9730        if has_min:
 9731            min = self._parse_var() or self._parse_primary()
 9732            self._match_text_seq("MAX")
 9733            max = self._parse_var() or self._parse_primary()
 9734        else:
 9735            max = self._parse_var() or self._parse_primary()
 9736            min = exp.Literal.number(0)
 9737        self._match_r_paren()
 9738        return self.expression(exp.DictRange(this=this, min=min, max=max))
 9739
 9740    def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None:
 9741        index = self._index
 9742        expression = self._parse_column()
 9743        position = self._match(TokenType.COMMA) and self._parse_column()
 9744
 9745        if not self._match(TokenType.IN):
 9746            self._retreat(index - 1)
 9747            return None
 9748        iterator = self._parse_column()
 9749        condition = self._parse_disjunction() if self._match_text_seq("IF") else None
 9750        return self.expression(
 9751            exp.Comprehension(
 9752                this=this,
 9753                expression=expression,
 9754                position=position,
 9755                iterator=iterator,
 9756                condition=condition,
 9757            )
 9758        )
 9759
 9760    def _parse_heredoc(self) -> exp.Heredoc | None:
 9761        if self._match(TokenType.HEREDOC_STRING):
 9762            return self.expression(exp.Heredoc(this=self._prev.text))
 9763
 9764        if not self._match_text_seq("$"):
 9765            return None
 9766
 9767        tags = ["$"]
 9768        tag_text = None
 9769
 9770        if self._is_connected():
 9771            self._advance()
 9772            tags.append(self._prev.text.upper())
 9773        else:
 9774            self.raise_error("No closing $ found")
 9775
 9776        if tags[-1] != "$":
 9777            if self._is_connected() and self._match_text_seq("$"):
 9778                tag_text = tags[-1]
 9779                tags.append("$")
 9780            else:
 9781                self.raise_error("No closing $ found")
 9782
 9783        heredoc_start = self._curr
 9784
 9785        while self._curr:
 9786            if self._match_text_seq(*tags, advance=False):
 9787                this = self._find_sql(heredoc_start, self._prev)
 9788                self._advance(len(tags))
 9789                return self.expression(exp.Heredoc(this=this, tag=tag_text))
 9790
 9791            self._advance()
 9792
 9793        self.raise_error(f"No closing {''.join(tags)} found")
 9794        return None
 9795
 9796    def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None:
 9797        if not self._curr:
 9798            return None
 9799
 9800        index = self._index
 9801        this = []
 9802        while True:
 9803            # The current token might be multiple words
 9804            curr = self._curr.text.upper()
 9805            key = curr.split(" ")
 9806            this.append(curr)
 9807
 9808            self._advance()
 9809            result, trie = in_trie(trie, key)
 9810            if result == TrieResult.FAILED:
 9811                break
 9812
 9813            if result == TrieResult.EXISTS:
 9814                subparser = parsers[" ".join(this)]
 9815                return subparser
 9816
 9817        self._retreat(index)
 9818        return None
 9819
 9820    def _match_l_paren(self, expression: exp.Expr | None = None) -> None:
 9821        if not self._match(TokenType.L_PAREN, expression=expression):
 9822            self.raise_error("Expecting (")
 9823
 9824    def _match_r_paren(self, expression: exp.Expr | None = None) -> None:
 9825        if not self._match(TokenType.R_PAREN, expression=expression):
 9826            self.raise_error("Expecting )")
 9827
 9828    def _replace_lambda(
 9829        self, node: exp.Expr | None, expressions: list[exp.Expr]
 9830    ) -> exp.Expr | None:
 9831        if not node:
 9832            return node
 9833
 9834        lambda_types = {e.name: e.args.get("to") or False for e in expressions}
 9835
 9836        for column in node.find_all(exp.Column):
 9837            typ = lambda_types.get(column.parts[0].name)
 9838            if typ is not None:
 9839                dot_or_id = column.to_dot() if column.table else column.this
 9840
 9841                if typ:
 9842                    dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ))
 9843
 9844                parent = column.parent
 9845
 9846                while isinstance(parent, exp.Dot):
 9847                    if not isinstance(parent.parent, exp.Dot):
 9848                        parent.replace(dot_or_id)
 9849                        break
 9850                    parent = parent.parent
 9851                else:
 9852                    if column is node:
 9853                        node = dot_or_id
 9854                    else:
 9855                        column.replace(dot_or_id)
 9856        return node
 9857
 9858    def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr:
 9859        start = self._prev
 9860
 9861        # Not to be confused with TRUNCATE(number, decimals) function call
 9862        if self._match(TokenType.L_PAREN):
 9863            self._retreat(self._index - 2)
 9864            return self._parse_function()
 9865
 9866        # Clickhouse supports TRUNCATE DATABASE as well
 9867        is_database = self._match(TokenType.DATABASE)
 9868
 9869        self._match(TokenType.TABLE)
 9870
 9871        exists = self._parse_exists(not_=False)
 9872
 9873        expressions = self._parse_csv(
 9874            lambda: self._parse_table(schema=True, is_db_reference=is_database)
 9875        )
 9876
 9877        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9878
 9879        if self._match_text_seq("RESTART", "IDENTITY"):
 9880            identity = "RESTART"
 9881        elif self._match_text_seq("CONTINUE", "IDENTITY"):
 9882            identity = "CONTINUE"
 9883        else:
 9884            identity = None
 9885
 9886        if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"):
 9887            option = self._prev.text
 9888        else:
 9889            option = None
 9890
 9891        partition = self._parse_partition()
 9892
 9893        # Fallback case
 9894        if self._curr:
 9895            return self._parse_as_command(start)
 9896
 9897        return self.expression(
 9898            exp.TruncateTable(
 9899                expressions=expressions,
 9900                is_database=is_database,
 9901                exists=exists,
 9902                cluster=cluster,
 9903                identity=identity,
 9904                option=option,
 9905                partition=partition,
 9906            )
 9907        )
 9908
 9909    def _parse_indexed_column(self) -> exp.Expr | None:
 9910        return self._parse_ordered(self._parse_opclass)
 9911
 9912    def _parse_with_operator(self) -> exp.Expr | None:
 9913        this = self._parse_indexed_column()
 9914
 9915        if not self._match(TokenType.WITH):
 9916            return this
 9917
 9918        op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS)
 9919
 9920        return self.expression(exp.WithOperator(this=this, op=op))
 9921
 9922    def _parse_wrapped_options(self) -> list[exp.Expr]:
 9923        self._match(TokenType.EQ)
 9924        self._match(TokenType.L_PAREN)
 9925
 9926        opts: list[exp.Expr] = []
 9927        option: exp.Expr | list[exp.Expr] | None
 9928        while self._curr and not self._match(TokenType.R_PAREN):
 9929            if self._match_text_seq("FORMAT_NAME", "="):
 9930                # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL
 9931                option = self._parse_format_name()
 9932            else:
 9933                option = self._parse_property()
 9934
 9935            if option is None:
 9936                self.raise_error("Unable to parse option")
 9937                break
 9938
 9939            opts.extend(ensure_list(option))
 9940
 9941        return opts
 9942
 9943    def _parse_copy_parameters(self) -> list[exp.CopyParameter]:
 9944        sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None
 9945
 9946        options = []
 9947        while self._curr and not self._match(TokenType.R_PAREN, advance=False):
 9948            option = self._parse_var(any_token=True)
 9949            prev = self._prev.text.upper()
 9950
 9951            # Different dialects might separate options and values by white space, "=" and "AS"
 9952            self._match(TokenType.EQ)
 9953            self._match(TokenType.ALIAS)
 9954
 9955            param = self.expression(exp.CopyParameter(this=option))
 9956
 9957            if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match(
 9958                TokenType.L_PAREN, advance=False
 9959            ):
 9960                # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options
 9961                param.set("expressions", self._parse_wrapped_options())
 9962            elif prev == "FILE_FORMAT":
 9963                # T-SQL's external file format case
 9964                param.set("expression", self._parse_field())
 9965            elif (
 9966                prev == "FORMAT"
 9967                and self._prev.token_type == TokenType.ALIAS
 9968                and self._match_texts(("AVRO", "JSON"))
 9969            ):
 9970                param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}"))
 9971                param.set("expression", self._parse_field())
 9972            else:
 9973                param.set("expression", self._parse_unquoted_field() or self._parse_bracket())
 9974
 9975            options.append(param)
 9976
 9977            if sep:
 9978                self._match(sep)
 9979
 9980        return options
 9981
 9982    def _parse_credentials(self) -> exp.Credentials | None:
 9983        expr = self.expression(exp.Credentials())
 9984
 9985        if self._match_text_seq("STORAGE_INTEGRATION", "="):
 9986            expr.set("storage", self._parse_field())
 9987        if self._match_text_seq("CREDENTIALS"):
 9988            # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string>
 9989            creds = (
 9990                self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field()
 9991            )
 9992            expr.set("credentials", creds)
 9993        if self._match_text_seq("ENCRYPTION"):
 9994            expr.set("encryption", self._parse_wrapped_options())
 9995        if self._match_text_seq("IAM_ROLE"):
 9996            expr.set(
 9997                "iam_role",
 9998                exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(),
 9999            )
10000        if self._match_text_seq("REGION"):
10001            expr.set("region", self._parse_field())
10002
10003        return expr
10004
10005    def _parse_file_location(self) -> exp.Expr | None:
10006        return self._parse_field()
10007
10008    def _parse_copy(self) -> exp.Copy | exp.Command:
10009        start = self._prev
10010
10011        self._match(TokenType.INTO)
10012
10013        this = (
10014            self._parse_select(nested=True, parse_subquery_alias=False)
10015            if self._match(TokenType.L_PAREN, advance=False)
10016            else self._parse_table(schema=True)
10017        )
10018
10019        kind = self._match(TokenType.FROM) or not self._match_text_seq("TO")
10020
10021        files = self._parse_csv(self._parse_file_location)
10022        if self._match(TokenType.EQ, advance=False):
10023            # Backtrack one token since we've consumed the lhs of a parameter assignment here.
10024            # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter
10025            # list via `_parse_wrapped(..)` below.
10026            self._advance(-1)
10027            files = []
10028
10029        credentials = self._parse_credentials()
10030
10031        self._match_text_seq("WITH")
10032
10033        params = self._parse_wrapped(self._parse_copy_parameters, optional=True)
10034
10035        # Fallback case
10036        if self._curr:
10037            return self._parse_as_command(start)
10038
10039        return self.expression(
10040            exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params)
10041        )
10042
10043    def _parse_normalize(self) -> exp.Normalize:
10044        return self.expression(
10045            exp.Normalize(
10046                this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var()
10047            )
10048        )
10049
10050    def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor:
10051        args = self._parse_csv(lambda: self._parse_lambda())
10052
10053        this = seq_get(args, 0)
10054        decimals = seq_get(args, 1)
10055
10056        return expr_type(
10057            this=this,
10058            decimals=decimals,
10059            to=self._parse_var() if self._match_text_seq("TO") else None,
10060        )
10061
10062    def _parse_star_ops(self) -> exp.Expr | None:
10063        star_token = self._prev
10064
10065        if self._match_text_seq("COLUMNS", "(", advance=False):
10066            this = self._parse_function()
10067            if isinstance(this, exp.Columns):
10068                this.set("unpack", True)
10069            return this
10070
10071        index = self._index
10072        ilike = self._parse_string() if self._match(TokenType.ILIKE) else None
10073        if not ilike:
10074            # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)`
10075            self._retreat(index)
10076
10077        return self.expression(
10078            exp.Star(
10079                ilike=ilike,
10080                except_=self._parse_star_op("EXCEPT", "EXCLUDE"),
10081                replace=self._parse_star_op("REPLACE"),
10082                rename=self._parse_star_op("RENAME"),
10083            )
10084        ).update_positions(star_token)
10085
10086    def _parse_grant_privilege(self) -> exp.GrantPrivilege | None:
10087        privilege_parts = []
10088
10089        # Keep consuming consecutive keywords until comma (end of this privilege) or ON
10090        # (end of privilege list) or L_PAREN (start of column list) are met
10091        while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False):
10092            privilege_parts.append(self._curr.text.upper())
10093            self._advance()
10094
10095        if not privilege_parts:
10096            self.raise_error("Expected privilege")
10097            return None
10098
10099        this = exp.var(" ".join(privilege_parts))
10100        expressions = (
10101            self._parse_wrapped_csv(self._parse_column)
10102            if self._match(TokenType.L_PAREN, advance=False)
10103            else None
10104        )
10105
10106        return self.expression(exp.GrantPrivilege(this=this, expressions=expressions))
10107
10108    def _parse_grant_principal(self) -> exp.GrantPrincipal | None:
10109        kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper()
10110        principal = self._parse_id_var()
10111
10112        if not principal:
10113            return None
10114
10115        return self.expression(exp.GrantPrincipal(this=principal, kind=kind))
10116
10117    def _parse_grant_revoke_common(
10118        self,
10119    ) -> tuple[list | None, str | None, exp.Expr | None]:
10120        privileges = self._parse_csv(self._parse_grant_privilege)
10121
10122        self._match(TokenType.ON)
10123        kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None
10124
10125        # Attempt to parse the securable e.g. MySQL allows names
10126        # such as "foo.*", "*.*" which are not easily parseable yet
10127        securable = self._try_parse(self._parse_table_parts)
10128
10129        return privileges, kind, securable
10130
10131    def _parse_grant(self) -> exp.Grant | exp.Command:
10132        start = self._prev
10133
10134        privileges, kind, securable = self._parse_grant_revoke_common()
10135
10136        if not securable or not self._match_text_seq("TO"):
10137            return self._parse_as_command(start)
10138
10139        principals = self._parse_csv(self._parse_grant_principal)
10140
10141        grant_option = self._match_text_seq("WITH", "GRANT", "OPTION")
10142
10143        if self._curr:
10144            return self._parse_as_command(start)
10145
10146        return self.expression(
10147            exp.Grant(
10148                privileges=privileges,
10149                kind=kind,
10150                securable=securable,
10151                principals=principals,
10152                grant_option=grant_option,
10153            )
10154        )
10155
10156    def _parse_revoke(self) -> exp.Revoke | exp.Command:
10157        start = self._prev
10158
10159        grant_option = self._match_text_seq("GRANT", "OPTION", "FOR")
10160
10161        privileges, kind, securable = self._parse_grant_revoke_common()
10162
10163        if not securable or not self._match_text_seq("FROM"):
10164            return self._parse_as_command(start)
10165
10166        principals = self._parse_csv(self._parse_grant_principal)
10167
10168        cascade = None
10169        if self._match_texts(("CASCADE", "RESTRICT")):
10170            cascade = self._prev.text.upper()
10171
10172        if self._curr:
10173            return self._parse_as_command(start)
10174
10175        return self.expression(
10176            exp.Revoke(
10177                privileges=privileges,
10178                kind=kind,
10179                securable=securable,
10180                principals=principals,
10181                grant_option=grant_option,
10182                cascade=cascade,
10183            )
10184        )
10185
10186    def _parse_overlay(self) -> exp.Overlay:
10187        def _parse_overlay_arg(text: str) -> exp.Expr | None:
10188            return (
10189                self._parse_bitwise()
10190                if self._match(TokenType.COMMA) or self._match_text_seq(text)
10191                else None
10192            )
10193
10194        return self.expression(
10195            exp.Overlay(
10196                this=self._parse_bitwise(),
10197                expression=_parse_overlay_arg("PLACING"),
10198                from_=_parse_overlay_arg("FROM"),
10199                for_=_parse_overlay_arg("FOR"),
10200            )
10201        )
10202
10203    def _parse_format_name(self) -> exp.Property:
10204        # Note: Although not specified in the docs, Snowflake does accept a string/identifier
10205        # for FILE_FORMAT = <format_name>
10206        return self.expression(
10207            exp.Property(
10208                this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts()
10209            )
10210        )
10211
10212    def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F:
10213        is_distinct = self._match(TokenType.DISTINCT)
10214        if not is_distinct:
10215            self._match(TokenType.ALL)
10216
10217        args = [self._parse_lambda()]
10218        if self._match(TokenType.COMMA):
10219            args.extend(self._parse_function_args())
10220
10221        target = seq_get(args, distinct_index)
10222        if is_distinct and target:
10223            args[distinct_index] = self.expression(exp.Distinct(expressions=[target]))
10224
10225        return func.from_arg_list(args)
10226
10227    def _identifier_expression(
10228        self, token: Token | None = None, quoted: bool | None = None
10229    ) -> exp.Identifier:
10230        token = token or self._prev
10231        return self.expression(exp.Identifier(this=token.text, quoted=quoted), token)
10232
10233    def _build_pipe_cte(
10234        self,
10235        query: exp.Query,
10236        expressions: list[exp.Expr],
10237        alias_cte: exp.TableAlias | None = None,
10238    ) -> exp.Select:
10239        new_cte: str | exp.TableAlias | None
10240        if alias_cte:
10241            new_cte = alias_cte
10242        else:
10243            self._pipe_cte_counter += 1
10244            new_cte = f"__tmp{self._pipe_cte_counter}"
10245
10246        with_ = query.args.get("with_")
10247        ctes = with_.pop() if with_ else None
10248
10249        new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False)
10250        if ctes:
10251            new_select.set("with_", ctes)
10252
10253        return new_select.with_(new_cte, as_=query, copy=False)
10254
10255    def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select:
10256        select = self._parse_select(consume_pipe=False)
10257        if not select:
10258            return query
10259
10260        return self._build_pipe_cte(
10261            query=query.select(*select.expressions, append=False), expressions=[exp.Star()]
10262        )
10263
10264    def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select:
10265        limit = self._parse_limit()
10266        offset = self._parse_offset()
10267        if limit:
10268            curr_limit = query.args.get("limit", limit)
10269            if curr_limit.expression.to_py() >= limit.expression.to_py():
10270                query.limit(limit, copy=False)
10271        if offset:
10272            curr_offset = query.args.get("offset")
10273            curr_offset = curr_offset.expression.to_py() if curr_offset else 0
10274            query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False)
10275
10276        return query
10277
10278    def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None:
10279        this = self._parse_disjunction()
10280        if self._match_text_seq("GROUP", "AND", advance=False):
10281            return this
10282
10283        this = self._parse_alias(this)
10284
10285        if self._match_set((TokenType.ASC, TokenType.DESC), advance=False):
10286            return self._parse_ordered(lambda: this)
10287
10288        return this
10289
10290    def _parse_pipe_syntax_aggregate_group_order_by(
10291        self, query: exp.Select, group_by_exists: bool = True
10292    ) -> exp.Select:
10293        expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields)
10294        aggregates_or_groups, orders = [], []
10295        for element in expr:
10296            if isinstance(element, exp.Ordered):
10297                this = element.this
10298                if isinstance(this, exp.Alias):
10299                    element.set("this", this.args["alias"])
10300                orders.append(element)
10301            else:
10302                this = element
10303            aggregates_or_groups.append(this)
10304
10305        if group_by_exists:
10306            query.select(
10307                *aggregates_or_groups, *query.expressions, append=False, copy=False
10308            ).group_by(
10309                *[projection.args.get("alias", projection) for projection in aggregates_or_groups],
10310                copy=False,
10311            )
10312        else:
10313            query.select(*aggregates_or_groups, append=False, copy=False)
10314
10315        if orders:
10316            return query.order_by(*orders, append=False, copy=False)
10317
10318        return query
10319
10320    def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select:
10321        self._match_text_seq("AGGREGATE")
10322        query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False)
10323
10324        if self._match(TokenType.GROUP_BY) or (
10325            self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY)
10326        ):
10327            query = self._parse_pipe_syntax_aggregate_group_order_by(query)
10328
10329        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10330
10331    def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None:
10332        first_setop = self.parse_set_operation(this=query)
10333        if not first_setop:
10334            return None
10335
10336        def _parse_and_unwrap_query() -> exp.Expr | None:
10337            expr = self._parse_paren()
10338            return expr.assert_is(exp.Subquery).unnest() if expr else None
10339
10340        first_setop.this.pop()
10341
10342        setops = [
10343            first_setop.expression.pop().assert_is(exp.Subquery).unnest(),
10344            *self._parse_csv(_parse_and_unwrap_query),
10345        ]
10346
10347        query = self._build_pipe_cte(query=query, expressions=[exp.Star()])
10348        with_ = query.args.get("with_")
10349        ctes = with_.pop() if with_ else None
10350
10351        if isinstance(first_setop, exp.Union):
10352            query = query.union(*setops, copy=False, **first_setop.args)
10353        elif isinstance(first_setop, exp.Except):
10354            query = query.except_(*setops, copy=False, **first_setop.args)
10355        else:
10356            query = query.intersect(*setops, copy=False, **first_setop.args)
10357
10358        query.set("with_", ctes)
10359
10360        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10361
10362    def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None:
10363        join = self._parse_join()
10364        if not join:
10365            return None
10366
10367        if isinstance(query, exp.Select):
10368            return query.join(join, copy=False)
10369
10370        return query
10371
10372    def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select:
10373        pivots = self._parse_pivots()
10374        if not pivots:
10375            return query
10376
10377        from_ = query.args.get("from_")
10378        if from_:
10379            from_.this.set("pivots", pivots)
10380
10381        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10382
10383    def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select:
10384        self._match_text_seq("EXTEND")
10385        query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False)
10386        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10387
10388    def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select:
10389        sample = self._parse_table_sample()
10390
10391        with_ = query.args.get("with_")
10392        if with_:
10393            with_.expressions[-1].this.set("sample", sample)
10394        else:
10395            query.set("sample", sample)
10396
10397        return query
10398
10399    def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None:
10400        if isinstance(query, exp.Subquery):
10401            query = exp.select("*").from_(query, copy=False)
10402
10403        if not query.args.get("from_"):
10404            query = exp.select("*").from_(query.subquery(copy=False), copy=False)
10405
10406        while self._match(TokenType.PIPE_GT):
10407            start_index = self._index
10408            start_text = self._curr.text.upper()
10409            parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text)
10410            if not parser:
10411                # The set operators (UNION, etc) and the JOIN operator have a few common starting
10412                # keywords, making it tricky to disambiguate them without lookahead. The approach
10413                # here is to try and parse a set operation and if that fails, then try to parse a
10414                # join operator. If that fails as well, then the operator is not supported.
10415                parsed_query = self._parse_pipe_syntax_set_operator(query)
10416                parsed_query = parsed_query or self._parse_pipe_syntax_join(query)
10417                if not parsed_query:
10418                    self._retreat(start_index)
10419                    self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.")
10420                    break
10421                query = parsed_query
10422            else:
10423                query = parser(self, query)
10424
10425        return query
10426
10427    def _parse_declareitem(self) -> exp.DeclareItem | None:
10428        self._match_texts(("VAR", "VARIABLE"))
10429
10430        vars = self._parse_csv(self._parse_id_var)
10431        if not vars:
10432            return None
10433
10434        self._match(TokenType.ALIAS)
10435        kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types()
10436        default = (
10437            self._match(TokenType.DEFAULT) or self._match(TokenType.EQ)
10438        ) and self._parse_bitwise()
10439
10440        return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default))
10441
10442    def _parse_declare(self) -> exp.Declare | exp.Command:
10443        start = self._prev
10444        replace = self._match_text_seq("OR", "REPLACE")
10445        expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem))
10446
10447        if not expressions or self._curr:
10448            return self._parse_as_command(start)
10449
10450        return self.expression(exp.Declare(expressions=expressions, replace=replace))
10451
10452    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10453        exp_class = exp.Cast if strict else exp.TryCast
10454
10455        if exp_class == exp.TryCast:
10456            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10457
10458        return self.expression(exp_class(**kwargs))
10459
10460    def _parse_json_value(self) -> exp.JSONValue:
10461        this = self._parse_bitwise()
10462        self._match(TokenType.COMMA)
10463        path = self._parse_bitwise()
10464
10465        returning = self._match(TokenType.RETURNING) and self._parse_type()
10466
10467        return self.expression(
10468            exp.JSONValue(
10469                this=this,
10470                path=self.dialect.to_json_path(path),
10471                returning=returning,
10472                on_condition=self._parse_on_condition(),
10473            )
10474        )
10475
10476    def _parse_group_concat(self) -> exp.Expr | None:
10477        def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr:
10478            if isinstance(node, exp.Distinct) and len(node.expressions) > 1:
10479                concat_exprs = [
10480                    self.expression(
10481                        exp.Concat(
10482                            expressions=node.expressions,
10483                            safe=True,
10484                            coalesce=self.dialect.CONCAT_COALESCE,
10485                        )
10486                    )
10487                ]
10488                node.set("expressions", concat_exprs)
10489                return node
10490            if len(exprs) == 1:
10491                return exprs[0]
10492            return self.expression(
10493                exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE)
10494            )
10495
10496        args = self._parse_csv(self._parse_lambda)
10497
10498        if args:
10499            order = args[-1] if isinstance(args[-1], exp.Order) else None
10500
10501            if order:
10502                # Order By is the last (or only) expression in the list and has consumed the 'expr' before it,
10503                # remove 'expr' from exp.Order and add it back to args
10504                args[-1] = order.this
10505                order.set("this", concat_exprs(order.this, args))
10506
10507            this = order or concat_exprs(args[0], args)
10508        else:
10509            this = None
10510
10511        separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None
10512
10513        return self.expression(exp.GroupConcat(this=this, separator=separator))
10514
10515    def _parse_initcap(self) -> exp.Initcap:
10516        expr = exp.Initcap.from_arg_list(self._parse_function_args())
10517
10518        # attach dialect's default delimiters
10519        if expr.args.get("expression") is None:
10520            expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS))
10521
10522        return expr
10523
10524    def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None:
10525        if not self._match(TokenType.L_PAREN):
10526            self._retreat(self._index - 1)
10527            return None
10528
10529        op = ""
10530        while self._curr and not self._match(TokenType.R_PAREN):
10531            op += self._curr.text
10532            self._advance()
10533
10534        comments = self._prev_comments
10535        return self.expression(
10536            exp.Operator(this=this, operator=op, expression=self._parse_bitwise()),
10537            comments=comments,
10538        )

Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.

Arguments:
  • error_level: The desired error level. Default: ErrorLevel.IMMEDIATE
  • error_message_context: The amount of context to capture from a query string when displaying the error message (in number of characters). Default: 100
  • max_errors: Maximum number of error messages to include in a raised ParseError. This is only relevant if error_level is ErrorLevel.RAISE. Default: 3
  • max_nodes: Maximum number of AST nodes to prevent memory exhaustion. Set to -1 (default) to disable the check.
Parser( error_level: sqlglot.errors.ErrorLevel | None = None, error_message_context: int = 100, max_errors: int = 3, max_nodes: int = -1, dialect: Union[str, sqlglot.dialects.Dialect, type[sqlglot.dialects.Dialect], NoneType] = None)
1957    def __init__(
1958        self,
1959        error_level: ErrorLevel | None = None,
1960        error_message_context: int = 100,
1961        max_errors: int = 3,
1962        max_nodes: int = -1,
1963        dialect: DialectType = None,
1964    ):
1965        self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE
1966        self.error_message_context: int = error_message_context
1967        self.max_errors: int = max_errors
1968        self.max_nodes: int = max_nodes
1969        self.dialect: t.Any = _resolve_dialect(dialect)
1970        self.sql: str = ""
1971        self.errors: list[ParseError] = []
1972        self._tokens: list[Token] = []
1973        self._tokens_size: i64 = 0
1974        self._index: i64 = 0
1975        self._curr: Token = SENTINEL_NONE
1976        self._next: Token = SENTINEL_NONE
1977        self._prev: Token = SENTINEL_NONE
1978        self._prev_comments: list[str] = []
1979        self._pipe_cte_counter: int = 0
1980        self._chunks: list[list[Token]] = []
1981        self._chunk_index: i64 = 0
1982        self._node_count: int = 0
FUNCTIONS: ClassVar[dict[str, Callable]] = {'AI_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.AIAgg'>>, 'AI_CLASSIFY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.AIClassify'>>, 'AI_EMBED': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.AIEmbed'>>, 'A_I_FORECAST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.AIForecast'>>, 'AI_GENERATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.AIGenerate'>>, 'AI_SIMILARITY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.AISimilarity'>>, 'AI_SUMMARIZE_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.AISummarizeAgg'>>, 'ABS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Abs'>>, 'ACOS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Acos'>>, 'ACOSH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Acosh'>>, 'ADD_MONTHS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.AddMonths'>>, 'AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Agg'>>, 'AND': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.And'>>, 'ANONYMOUS_AGG_FUNC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.AnonymousAggFunc'>>, 'ANY_VALUE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.AnyValue'>>, 'APPLY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Apply'>>, 'APPROX_DISTINCT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.ApproxDistinct'>>, 'APPROX_COUNT_DISTINCT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.ApproxDistinct'>>, 'APPROX_PERCENTILE_ACCUMULATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproxPercentileAccumulate'>>, 'APPROX_PERCENTILE_COMBINE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproxPercentileCombine'>>, 'APPROX_PERCENTILE_ESTIMATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproxPercentileEstimate'>>, 'APPROX_QUANTILE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproxQuantile'>>, 'APPROX_QUANTILES': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproxQuantiles'>>, 'APPROX_TOP_K': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproxTopK'>>, 'APPROX_TOP_K_ACCUMULATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproxTopKAccumulate'>>, 'APPROX_TOP_K_COMBINE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproxTopKCombine'>>, 'APPROX_TOP_K_ESTIMATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproxTopKEstimate'>>, 'APPROX_TOP_SUM': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproxTopSum'>>, 'APPROXIMATE_SIMILARITY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproximateSimilarity'>>, 'APPROXIMATE_JACCARD_INDEX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ApproximateSimilarity'>>, 'ARG_MAX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ArgMax'>>, 'ARGMAX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ArgMax'>>, 'MAX_BY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ArgMax'>>, 'ARG_MIN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ArgMin'>>, 'ARGMIN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ArgMin'>>, 'MIN_BY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ArgMin'>>, 'ARRAY': <function Parser.<lambda>>, 'ARRAY_AGG': <function Parser.<lambda>>, 'ARRAY_ALL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayAll'>>, 'ARRAY_ANY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayAny'>>, 'ARRAY_APPEND': <function build_array_append>, 'ARRAY_COMPACT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayCompact'>>, 'ARRAY_CONCAT': <function build_array_concat>, 'ARRAY_CAT': <function build_array_concat>, 'ARRAY_CONCAT_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ArrayConcatAgg'>>, 'ARRAY_CONSTRUCT_COMPACT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayConstructCompact'>>, 'ARRAY_CONTAINED_BY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayContainedBy'>>, 'ARRAY_CONTAINS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayContains'>>, 'ARRAY_HAS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayContains'>>, 'ARRAY_CONTAINS_ALL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayContainsAll'>>, 'ARRAY_HAS_ALL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayContainsAll'>>, 'ARRAY_DISTINCT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayDistinct'>>, 'ARRAY_EXCEPT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayExcept'>>, 'FILTER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayFilter'>>, 'ARRAY_FILTER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayFilter'>>, 'ARRAY_FIRST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayFirst'>>, 'ARRAY_INSERT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayInsert'>>, 'ARRAY_INTERSECT': <function Parser.<lambda>>, 'ARRAY_INTERSECTION': <function Parser.<lambda>>, 'ARRAY_LAST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayLast'>>, 'ARRAY_MAX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayMax'>>, 'ARRAY_MIN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayMin'>>, 'ARRAY_OVERLAPS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayOverlaps'>>, 'ARRAY_POSITION': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayPosition'>>, 'ARRAY_PREPEND': <function build_array_prepend>, 'ARRAY_REMOVE': <function build_array_remove>, 'ARRAY_REMOVE_AT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayRemoveAt'>>, 'ARRAY_REVERSE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayReverse'>>, 'ARRAY_SIZE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArraySize'>>, 'ARRAY_LENGTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArraySize'>>, 'ARRAY_SLICE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArraySlice'>>, 'ARRAY_SORT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArraySort'>>, 'ARRAY_SUM': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArraySum'>>, 'ARRAY_TO_STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayToString'>>, 'ARRAY_JOIN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArrayToString'>>, 'ARRAY_UNION_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ArrayUnionAgg'>>, 'ARRAY_UNIQUE_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ArrayUniqueAgg'>>, 'ARRAYS_ZIP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ArraysZip'>>, 'ASCII': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Ascii'>>, 'ASIN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Asin'>>, 'ASINH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Asinh'>>, 'ATAN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Atan'>>, 'ATAN2': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Atan2'>>, 'ATANH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Atanh'>>, 'AVG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Avg'>>, 'BASE64_DECODE_BINARY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Base64DecodeBinary'>>, 'BASE64_DECODE_STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Base64DecodeString'>>, 'BASE64_ENCODE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Base64Encode'>>, 'BIT_LENGTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.BitLength'>>, 'BITMAP_BIT_POSITION': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.BitmapBitPosition'>>, 'BITMAP_BUCKET_NUMBER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.BitmapBucketNumber'>>, 'BITMAP_CONSTRUCT_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.BitmapConstructAgg'>>, 'BITMAP_COUNT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.BitmapCount'>>, 'BITMAP_OR_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.BitmapOrAgg'>>, 'BITWISE_AND_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.BitwiseAndAgg'>>, 'BITWISE_COUNT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.BitwiseCount'>>, 'BITWISE_OR_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.BitwiseOrAgg'>>, 'BITWISE_XOR_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.BitwiseXorAgg'>>, 'BOOLAND': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Booland'>>, 'BOOLNOT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Boolnot'>>, 'BOOLOR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Boolor'>>, 'BOOLXOR_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.BoolxorAgg'>>, 'BYTE_LENGTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ByteLength'>>, 'CASE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Case'>>, 'CAST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Cast'>>, 'CAST_TO_STR_TYPE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CastToStrType'>>, 'CBRT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Cbrt'>>, 'CEIL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Ceil'>>, 'CEILING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Ceil'>>, 'CHECK_JSON': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.CheckJson'>>, 'CHECK_XML': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CheckXml'>>, 'CHR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Chr'>>, 'CHAR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Chr'>>, 'CITY_HASH64': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.CityHash64'>>, 'COALESCE': <function build_coalesce>, 'IFNULL': <function build_coalesce>, 'NVL': <function build_coalesce>, 'CODE_POINTS_TO_BYTES': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.CodePointsToBytes'>>, 'CODE_POINTS_TO_STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.CodePointsToString'>>, 'COLLATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Collate'>>, 'COLLATION': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Collation'>>, 'COLUMNS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Columns'>>, 'COMBINED_AGG_FUNC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.CombinedAggFunc'>>, 'COMBINED_PARAMETERIZED_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.CombinedParameterizedAgg'>>, 'COMPRESS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Compress'>>, 'CONCAT': <function Parser.<lambda>>, 'CONCAT_WS': <function Parser.<lambda>>, 'CONNECT_BY_ROOT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.ConnectByRoot'>>, 'CONTAINS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Contains'>>, 'CONVERT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Convert'>>, 'CONVERT_TIMEZONE': <function build_convert_timezone>, 'CONVERT_TO_CHARSET': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ConvertToCharset'>>, 'CORR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Corr'>>, 'COS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Cos'>>, 'COSH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Cosh'>>, 'COSINE_DISTANCE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.CosineDistance'>>, 'COT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Cot'>>, 'COTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Coth'>>, 'COUNT': <function Parser.<lambda>>, 'COUNT_IF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.CountIf'>>, 'COUNTIF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.CountIf'>>, 'COVAR_POP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.CovarPop'>>, 'COVAR_SAMP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.CovarSamp'>>, 'CSC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Csc'>>, 'CSCH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Csch'>>, 'CUME_DIST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.CumeDist'>>, 'CURRENT_ACCOUNT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentAccount'>>, 'CURRENT_ACCOUNT_NAME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentAccountName'>>, 'CURRENT_AVAILABLE_ROLES': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentAvailableRoles'>>, 'CURRENT_CATALOG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentCatalog'>>, 'CURRENT_CLIENT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentClient'>>, 'CURRENT_DATABASE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentDatabase'>>, 'CURRENT_DATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.CurrentDate'>>, 'CURRENT_DATETIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.CurrentDatetime'>>, 'CURRENT_IP_ADDRESS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentIpAddress'>>, 'CURRENT_ORGANIZATION_NAME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentOrganizationName'>>, 'CURRENT_ORGANIZATION_USER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentOrganizationUser'>>, 'CURRENT_REGION': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentRegion'>>, 'CURRENT_ROLE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentRole'>>, 'CURRENT_ROLE_TYPE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentRoleType'>>, 'CURRENT_SCHEMA': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentSchema'>>, 'CURRENT_SCHEMAS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentSchemas'>>, 'CURRENT_SECONDARY_ROLES': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentSecondaryRoles'>>, 'CURRENT_SESSION': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentSession'>>, 'CURRENT_STATEMENT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentStatement'>>, 'CURRENT_TIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.CurrentTime'>>, 'CURRENT_TIMESTAMP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.CurrentTimestamp'>>, 'CURRENT_TIMESTAMP_L_T_Z': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.CurrentTimestampLTZ'>>, 'CURRENT_TIMEZONE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.CurrentTimezone'>>, 'CURRENT_TRANSACTION': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentTransaction'>>, 'CURRENT_USER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentUser'>>, 'CURRENT_USER_ID': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentUserId'>>, 'CURRENT_VERSION': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentVersion'>>, 'CURRENT_WAREHOUSE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.CurrentWarehouse'>>, 'DATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Date'>>, 'DATE_ADD': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DateAdd'>>, 'DATE_BIN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DateBin'>>, 'DATEDIFF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DateDiff'>>, 'DATE_DIFF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DateDiff'>>, 'DATE_FROM_PARTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DateFromParts'>>, 'DATEFROMPARTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DateFromParts'>>, 'DATE_FROM_UNIX_DATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DateFromUnixDate'>>, 'DATE_STR_TO_DATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DateStrToDate'>>, 'DATE_SUB': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DateSub'>>, 'DATE_TO_DATE_STR': <function Parser.<lambda>>, 'DATE_TO_DI': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DateToDi'>>, 'DATE_TRUNC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DateTrunc'>>, 'DATETIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Datetime'>>, 'DATETIME_ADD': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DatetimeAdd'>>, 'DATETIME_DIFF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DatetimeDiff'>>, 'DATETIME_SUB': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DatetimeSub'>>, 'DATETIME_TRUNC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DatetimeTrunc'>>, 'DAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Day'>>, 'DAY_OF_MONTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DayOfMonth'>>, 'DAYOFMONTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DayOfMonth'>>, 'DAY_OF_WEEK': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DayOfWeek'>>, 'DAYOFWEEK': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DayOfWeek'>>, 'DAYOFWEEK_ISO': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DayOfWeekIso'>>, 'ISODOW': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DayOfWeekIso'>>, 'DAY_OF_YEAR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DayOfYear'>>, 'DAYOFYEAR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DayOfYear'>>, 'DAYNAME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Dayname'>>, 'DECODE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Decode'>>, 'DECODE_CASE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.DecodeCase'>>, 'DECOMPRESS_BINARY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.DecompressBinary'>>, 'DECOMPRESS_STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.DecompressString'>>, 'DECRYPT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Decrypt'>>, 'DECRYPT_RAW': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.DecryptRaw'>>, 'DEGREES': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Degrees'>>, 'DENSE_RANK': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.DenseRank'>>, 'DI_TO_DATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.DiToDate'>>, 'DOT_PRODUCT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.DotProduct'>>, 'DYNAMIC_IDENTIFIER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.DynamicIdentifier'>>, 'ELT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Elt'>>, 'ENCODE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Encode'>>, 'ENCRYPT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Encrypt'>>, 'ENCRYPT_RAW': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.EncryptRaw'>>, 'ENDS_WITH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.EndsWith'>>, 'ENDSWITH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.EndsWith'>>, 'EQUAL_NULL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.EqualNull'>>, 'EUCLIDEAN_DISTANCE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.EuclideanDistance'>>, 'EXISTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Exists'>>, 'EXP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Exp'>>, 'EXPLODE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Explode'>>, 'EXPLODING_GENERATE_SERIES': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ExplodingGenerateSeries'>>, 'EXTRACT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Extract'>>, 'FACTORIAL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Factorial'>>, 'FARM_FINGERPRINT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.FarmFingerprint'>>, 'FARMFINGERPRINT64': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.FarmFingerprint'>>, 'FEATURES_AT_TIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.FeaturesAtTime'>>, 'FIRST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.First'>>, 'FIRST_VALUE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.FirstValue'>>, 'FLATTEN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Flatten'>>, 'FLOAT64': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Float64'>>, 'FLOOR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Floor'>>, 'FORMAT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Format'>>, 'FROM_BASE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.FromBase'>>, 'FROM_BASE32': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.FromBase32'>>, 'FROM_BASE64': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.FromBase64'>>, 'FROM_ISO8601_DATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.FromISO8601Date'>>, 'FROM_ISO8601_TIMESTAMP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.FromISO8601Timestamp'>>, 'FROM_ISO8601_TIMESTAMP_NANOS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.FromISO8601TimestampNanos'>>, 'FROM_JSON': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.FromJson'>>, 'GAP_FILL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.GapFill'>>, 'GENERATE_BOOL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.GenerateBool'>>, 'GENERATE_DATE_ARRAY': <function Parser.<lambda>>, 'GENERATE_DOUBLE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.GenerateDouble'>>, 'GENERATE_EMBEDDING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.GenerateEmbedding'>>, 'GENERATE_INT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.GenerateInt'>>, 'GENERATE_SERIES': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.GenerateSeries'>>, 'GENERATE_TABLE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.GenerateTable'>>, 'GENERATE_TEXT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.GenerateText'>>, 'GENERATE_TIMESTAMP_ARRAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.GenerateTimestampArray'>>, 'GENERATOR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Generator'>>, 'GET_EXTRACT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.GetExtract'>>, 'GET_IGNORE_CASE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.GetIgnoreCase'>>, 'GETBIT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Getbit'>>, 'GET_BIT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Getbit'>>, 'GREATEST': <function Parser.<lambda>>, 'GROUP_CONCAT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.GroupConcat'>>, 'GROUPING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Grouping'>>, 'GROUPING_ID': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.GroupingId'>>, 'HASH_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.HashAgg'>>, 'HEX': <function build_hex>, 'HEX_DECODE_STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.HexDecodeString'>>, 'HLL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.Hll'>>, 'HOST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Host'>>, 'HOUR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Hour'>>, 'IF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.If'>>, 'IIF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.If'>>, 'INITCAP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Initcap'>>, 'INLINE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Inline'>>, 'INT64': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Int64'>>, 'IS_ARRAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.IsArray'>>, 'IS_ASCII': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.IsAscii'>>, 'IS_INF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.IsInf'>>, 'ISINF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.IsInf'>>, 'IS_NAN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.IsNan'>>, 'ISNAN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.IsNan'>>, 'IS_NULL_VALUE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.IsNullValue'>>, 'J_S_O_N_ARRAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONArray'>>, 'J_S_O_N_ARRAY_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONArrayAgg'>>, 'JSON_ARRAY_APPEND': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONArrayAppend'>>, 'JSON_ARRAY_CONTAINS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONArrayContains'>>, 'JSON_ARRAY_INSERT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONArrayInsert'>>, 'JSONB_CONTAINS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONBContains'>>, 'J_S_O_N_B_CONTAINS_ALL_TOP_KEYS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONBContainsAllTopKeys'>>, 'J_S_O_N_B_CONTAINS_ANY_TOP_KEYS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONBContainsAnyTopKeys'>>, 'J_S_O_N_B_CONTAINS_TOP_KEY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONBContainsTopKey'>>, 'J_S_O_N_B_DELETE_AT_PATH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONBDeleteAtPath'>>, 'JSONB_EXISTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONBExists'>>, 'JSONB_EXTRACT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONBExtract'>>, 'JSONB_EXTRACT_SCALAR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONBExtractScalar'>>, 'J_S_O_N_B_OBJECT_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONBObjectAgg'>>, 'J_S_O_N_B_PATH_EXISTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONBPathExists'>>, 'J_S_O_N_BOOL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONBool'>>, 'J_S_O_N_CAST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.JSONCast'>>, 'J_S_O_N_EXISTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONExists'>>, 'JSON_EXTRACT': <function build_extract_json_with_path.<locals>._builder>, 'JSON_EXTRACT_ARRAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONExtractArray'>>, 'JSON_EXTRACT_SCALAR': <function build_extract_json_with_path.<locals>._builder>, 'JSON_FORMAT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONFormat'>>, 'JSON_KEYS': <function Parser.<lambda>>, 'J_S_O_N_KEYS_AT_DEPTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONKeysAtDepth'>>, 'J_S_O_N_OBJECT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONObject'>>, 'J_S_O_N_OBJECT_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONObjectAgg'>>, 'JSON_REMOVE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONRemove'>>, 'JSON_SET': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONSet'>>, 'JSON_STRIP_NULLS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONStripNulls'>>, 'J_S_O_N_TABLE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONTable'>>, 'JSON_TYPE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.JSONType'>>, 'J_S_O_N_VALUE_ARRAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.query.JSONValueArray'>>, 'JAROWINKLER_SIMILARITY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.JarowinklerSimilarity'>>, 'JUSTIFY_DAYS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.JustifyDays'>>, 'JUSTIFY_HOURS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.JustifyHours'>>, 'JUSTIFY_INTERVAL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.JustifyInterval'>>, 'KURTOSIS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Kurtosis'>>, 'LAG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Lag'>>, 'LAST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Last'>>, 'LAST_DAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.LastDay'>>, 'LAST_DAY_OF_MONTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.LastDay'>>, 'LAST_VALUE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.LastValue'>>, 'LAX_BOOL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.LaxBool'>>, 'LAX_FLOAT64': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.LaxFloat64'>>, 'LAX_INT64': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.LaxInt64'>>, 'LAX_STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.LaxString'>>, 'LEAD': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Lead'>>, 'LEAST': <function Parser.<lambda>>, 'LEFT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Left'>>, 'LENGTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Length'>>, 'LEN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Length'>>, 'CHAR_LENGTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Length'>>, 'CHARACTER_LENGTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Length'>>, 'LEVENSHTEIN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Levenshtein'>>, 'LIST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.List'>>, 'LN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Ln'>>, 'LOCALTIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Localtime'>>, 'LOCALTIMESTAMP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Localtimestamp'>>, 'LOG': <function build_logarithm>, 'LOGICAL_AND': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.LogicalAnd'>>, 'BOOL_AND': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.LogicalAnd'>>, 'BOOLAND_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.LogicalAnd'>>, 'LOGICAL_OR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.LogicalOr'>>, 'BOOL_OR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.LogicalOr'>>, 'BOOLOR_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.LogicalOr'>>, 'LOWER': <function build_lower>, 'LCASE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Lower'>>, 'LOWER_HEX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.LowerHex'>>, 'MD5': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.MD5'>>, 'MD5_DIGEST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.MD5Digest'>>, 'M_D5_NUMBER_LOWER64': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.MD5NumberLower64'>>, 'M_D5_NUMBER_UPPER64': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.MD5NumberUpper64'>>, 'M_L_FORECAST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.MLForecast'>>, 'M_L_TRANSLATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.MLTranslate'>>, 'MAKE_INTERVAL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.MakeInterval'>>, 'MANHATTAN_DISTANCE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.ManhattanDistance'>>, 'MAP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Map'>>, 'MAP_CAT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.MapCat'>>, 'MAP_CONTAINS_KEY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.MapContainsKey'>>, 'MAP_DELETE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.MapDelete'>>, 'MAP_FROM_ENTRIES': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.MapFromEntries'>>, 'MAP_INSERT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.MapInsert'>>, 'MAP_KEYS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.MapKeys'>>, 'MAP_PICK': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.MapPick'>>, 'MAP_SIZE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.MapSize'>>, 'MATCH_AGAINST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.MatchAgainst'>>, 'MAX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Max'>>, 'MEDIAN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Median'>>, 'MIN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Min'>>, 'MINHASH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Minhash'>>, 'MINHASH_COMBINE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.MinhashCombine'>>, 'MINUTE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Minute'>>, 'MODE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Mode'>>, 'MONTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Month'>>, 'MONTHNAME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Monthname'>>, 'MONTHS_BETWEEN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.MonthsBetween'>>, 'NANVL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Nanvl'>>, 'NEGATIVE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Negative'>>, 'NET_FUNC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.NetFunc'>>, 'NEXT_DAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.NextDay'>>, 'NEXT_VALUE_FOR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.ddl.NextValueFor'>>, 'NORMAL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Normal'>>, 'NORMALIZE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Normalize'>>, 'NTH_VALUE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.NthValue'>>, 'NTILE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Ntile'>>, 'NULLIF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Nullif'>>, 'NUMBER_TO_STR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.NumberToStr'>>, 'NVL2': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Nvl2'>>, 'OBJECT_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.ObjectAgg'>>, 'OBJECT_ID': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.ObjectId'>>, 'OBJECT_INSERT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.ObjectInsert'>>, 'OBJECT_TRANSFORM': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.ObjectTransform'>>, 'OPEN_J_S_O_N': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.OpenJSON'>>, 'OR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.Or'>>, 'OVERLAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Overlay'>>, 'PAD': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Pad'>>, 'PARAMETERIZED_AGG': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.ParameterizedAgg'>>, 'PARSE_BIGNUMERIC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ParseBignumeric'>>, 'PARSE_DATETIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.ParseDatetime'>>, 'PARSE_IP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.ParseIp'>>, 'PARSE_JSON': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.ParseJSON'>>, 'JSON_PARSE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.ParseJSON'>>, 'PARSE_NUMERIC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ParseNumeric'>>, 'PARSE_TIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.ParseTime'>>, 'PARSE_URL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ParseUrl'>>, 'PERCENT_RANK': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.PercentRank'>>, 'PERCENTILE_CONT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.PercentileCont'>>, 'PERCENTILE_DISC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.PercentileDisc'>>, 'PI': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Pi'>>, 'POSEXPLODE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Posexplode'>>, 'POSEXPLODE_OUTER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.PosexplodeOuter'>>, 'POWER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.Pow'>>, 'POW': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.Pow'>>, 'PREDICT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Predict'>>, 'PREVIOUS_DAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.PreviousDay'>>, 'QUANTILE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Quantile'>>, 'QUARTER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Quarter'>>, 'RADIANS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Radians'>>, 'RAND': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Rand'>>, 'RANDOM': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Rand'>>, 'RANDN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Randn'>>, 'RANDSTR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Randstr'>>, 'RANGE_BUCKET': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.RangeBucket'>>, 'RANGE_N': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.RangeN'>>, 'RANK': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Rank'>>, 'READ_CSV': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.ReadCSV'>>, 'READ_PARQUET': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.ReadParquet'>>, 'REDUCE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Reduce'>>, 'REG_DOMAIN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.RegDomain'>>, 'REGEXP_COUNT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.RegexpCount'>>, 'REGEXP_EXTRACT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.RegexpExtract'>>, 'REGEXP_EXTRACT_ALL': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.RegexpExtractAll'>>, 'REGEXP_FULL_MATCH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.RegexpFullMatch'>>, 'REGEXP_I_LIKE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.RegexpILike'>>, 'REGEXP_INSTR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.RegexpInstr'>>, 'REGEXP_LIKE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.RegexpLike'>>, 'REGEXP_REPLACE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.RegexpReplace'>>, 'REGEXP_SPLIT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.RegexpSplit'>>, 'REGEXP_SUBSTR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.RegexpSubstr'>>, 'REGR_AVGX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RegrAvgx'>>, 'REGR_AVGY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RegrAvgy'>>, 'REGR_COUNT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RegrCount'>>, 'REGR_INTERCEPT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RegrIntercept'>>, 'REGR_R2': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RegrR2'>>, 'REGR_SLOPE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RegrSlope'>>, 'REGR_SXX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RegrSxx'>>, 'REGR_SXY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RegrSxy'>>, 'REGR_SYY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RegrSyy'>>, 'REGR_VALX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RegrValx'>>, 'REGR_VALY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RegrValy'>>, 'REPEAT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Repeat'>>, 'REPLACE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Replace'>>, 'REVERSE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Reverse'>>, 'RIGHT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Right'>>, 'RINT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Rint'>>, 'ROUND': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Round'>>, 'ROW_NUMBER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.RowNumber'>>, 'RTRIMMED_LENGTH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.RtrimmedLength'>>, 'SHA': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.SHA'>>, 'SHA1': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.SHA'>>, 'S_H_A1_DIGEST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.SHA1Digest'>>, 'SHA2': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.SHA2'>>, 'S_H_A2_DIGEST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.SHA2Digest'>>, 'SAFE_ADD': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.SafeAdd'>>, 'SAFE_CONVERT_BYTES_TO_STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.SafeConvertBytesToString'>>, 'SAFE_DIVIDE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.SafeDivide'>>, 'SAFE_FUNC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.SafeFunc'>>, 'SAFE_MULTIPLY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.SafeMultiply'>>, 'SAFE_NEGATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.SafeNegate'>>, 'SAFE_SUBTRACT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.SafeSubtract'>>, 'SEARCH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Search'>>, 'SEARCH_IP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.SearchIp'>>, 'SEC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Sec'>>, 'SECH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Sech'>>, 'SECOND': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Second'>>, 'SECRET': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Secret'>>, 'SEQ1': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Seq1'>>, 'SEQ2': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Seq2'>>, 'SEQ4': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Seq4'>>, 'SEQ8': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Seq8'>>, 'SESSION_USER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.SessionUser'>>, 'SHUFFLE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Shuffle'>>, 'SIGN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Sign'>>, 'SIGNUM': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Sign'>>, 'SIN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Sin'>>, 'SINH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Sinh'>>, 'SKEWNESS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Skewness'>>, 'SORT_ARRAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.SortArray'>>, 'SOUNDEX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Soundex'>>, 'SOUNDEX_P123': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.SoundexP123'>>, 'SPACE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Space'>>, 'SPLIT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Split'>>, 'SPLIT_PART': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.SplitPart'>>, 'SQRT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Sqrt'>>, 'ST_DISTANCE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.StDistance'>>, 'ST_POINT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.StPoint'>>, 'ST_MAKEPOINT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.StPoint'>>, 'STACK': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Stack'>>, 'STANDARD_HASH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.StandardHash'>>, 'STAR_MAP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.StarMap'>>, 'STARTS_WITH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.StartsWith'>>, 'STARTSWITH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.StartsWith'>>, 'STDDEV': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Stddev'>>, 'STDEV': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Stddev'>>, 'STDDEV_POP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.StddevPop'>>, 'STDDEV_SAMP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.StddevSamp'>>, 'STR_POSITION': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.StrPosition'>>, 'STR_TO_DATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.StrToDate'>>, 'STR_TO_MAP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.StrToMap'>>, 'STR_TO_TIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.StrToTime'>>, 'STR_TO_UNIX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.StrToUnix'>>, 'STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.String'>>, 'STRING_TO_ARRAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.StringToArray'>>, 'SPLIT_BY_STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.StringToArray'>>, 'STRIP_NULL_VALUE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.json.StripNullValue'>>, 'STRTOK': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Strtok'>>, 'STRTOK_TO_ARRAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.StrtokToArray'>>, 'STRUCT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Struct'>>, 'STRUCT_EXTRACT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.StructExtract'>>, 'STUFF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Stuff'>>, 'INSERT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Stuff'>>, 'SUBSTRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Substring'>>, 'SUBSTR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Substring'>>, 'SUBSTRING_INDEX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.SubstringIndex'>>, 'SUM': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Sum'>>, 'SYSTIMESTAMP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Systimestamp'>>, 'TAN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Tan'>>, 'TANH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Tanh'>>, 'TIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Time'>>, 'TIME_ADD': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeAdd'>>, 'TIME_DIFF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeDiff'>>, 'TIME_FROM_PARTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeFromParts'>>, 'TIMEFROMPARTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeFromParts'>>, 'TIME_SLICE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeSlice'>>, 'TIME_STR_TO_DATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeStrToDate'>>, 'TIME_STR_TO_TIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeStrToTime'>>, 'TIME_STR_TO_UNIX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeStrToUnix'>>, 'TIME_SUB': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeSub'>>, 'TIME_TO_STR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeToStr'>>, 'TIME_TO_TIME_STR': <function Parser.<lambda>>, 'TIME_TO_UNIX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeToUnix'>>, 'TIME_TRUNC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimeTrunc'>>, 'TIMESTAMP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Timestamp'>>, 'TIMESTAMP_ADD': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimestampAdd'>>, 'TIMESTAMPDIFF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimestampDiff'>>, 'TIMESTAMP_DIFF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimestampDiff'>>, 'TIMESTAMP_FROM_PARTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimestampFromParts'>>, 'TIMESTAMPFROMPARTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimestampFromParts'>>, 'TIMESTAMP_LTZ_FROM_PARTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimestampLtzFromParts'>>, 'TIMESTAMPLTZFROMPARTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimestampLtzFromParts'>>, 'TIMESTAMP_SUB': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimestampSub'>>, 'TIMESTAMP_TRUNC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimestampTrunc'>>, 'TIMESTAMP_TZ_FROM_PARTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimestampTzFromParts'>>, 'TIMESTAMPTZFROMPARTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TimestampTzFromParts'>>, 'TO_ARRAY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ToArray'>>, 'TO_BASE32': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ToBase32'>>, 'TO_BASE64': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ToBase64'>>, 'TO_BINARY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ToBinary'>>, 'TO_BOOLEAN': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.ToBoolean'>>, 'TO_CHAR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ToChar'>>, 'TO_CODE_POINTS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ToCodePoints'>>, 'TO_DAYS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.ToDays'>>, 'TO_DECFLOAT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ToDecfloat'>>, 'TO_DOUBLE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ToDouble'>>, 'TO_FILE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ToFile'>>, 'TO_MAP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.ToMap'>>, 'TO_NUMBER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.ToNumber'>>, 'TO_VARIANT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.ToVariant'>>, 'TRANSFORM': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.Transform'>>, 'TRANSLATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Translate'>>, 'TRIM': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Trim'>>, 'TRUNC': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Trunc'>>, 'TRUNCATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.math.Trunc'>>, 'TRY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Try'>>, 'TRY_BASE64_DECODE_BINARY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.TryBase64DecodeBinary'>>, 'TRY_BASE64_DECODE_STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.TryBase64DecodeString'>>, 'TRY_CAST': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.TryCast'>>, 'TRY_HEX_DECODE_BINARY': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.TryHexDecodeBinary'>>, 'TRY_HEX_DECODE_STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.TryHexDecodeString'>>, 'TRY_TO_DECFLOAT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.TryToDecfloat'>>, 'TS_OR_DI_TO_DI': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TsOrDiToDi'>>, 'TS_OR_DS_ADD': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TsOrDsAdd'>>, 'TS_OR_DS_DIFF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TsOrDsDiff'>>, 'TS_OR_DS_TO_DATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TsOrDsToDate'>>, 'TS_OR_DS_TO_DATE_STR': <function Parser.<lambda>>, 'TS_OR_DS_TO_DATETIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TsOrDsToDatetime'>>, 'TS_OR_DS_TO_TIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TsOrDsToTime'>>, 'TS_OR_DS_TO_TIMESTAMP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.TsOrDsToTimestamp'>>, 'TYPEOF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.Typeof'>>, 'UNHEX': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Unhex'>>, 'UNICODE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Unicode'>>, 'UNIFORM': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Uniform'>>, 'UNIX_DATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.UnixDate'>>, 'UNIX_MICROS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.UnixMicros'>>, 'UNIX_MILLIS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.UnixMillis'>>, 'UNIX_SECONDS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.UnixSeconds'>>, 'UNIX_TO_STR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.UnixToStr'>>, 'UNIX_TO_TIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.UnixToTime'>>, 'UNIX_TO_TIME_STR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.UnixToTimeStr'>>, 'UNNEST': <function Parser.<lambda>>, 'UPPER': <function build_upper>, 'UCASE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.Upper'>>, 'UTC_DATE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.UtcDate'>>, 'UTC_TIME': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.UtcTime'>>, 'UTC_TIMESTAMP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.UtcTimestamp'>>, 'UUID': <function Parser.<lambda>>, 'GEN_RANDOM_UUID': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Uuid'>>, 'GENERATE_UUID': <function Parser.<lambda>>, 'UUID_STRING': <function Parser.<lambda>>, 'VAR_MAP': <function build_var_map>, 'VARIANCE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Variance'>>, 'VARIANCE_SAMP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Variance'>>, 'VAR_SAMP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.Variance'>>, 'VARIANCE_POP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.VariancePop'>>, 'VAR_POP': <bound method Func.from_arg_list of <class 'sqlglot.expressions.aggregate.VariancePop'>>, 'VECTOR_SEARCH': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.VectorSearch'>>, 'WEEK': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Week'>>, 'WEEK_OF_YEAR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.WeekOfYear'>>, 'WEEKOFYEAR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.WeekOfYear'>>, 'WEEK_START': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.WeekStart'>>, 'WIDTH_BUCKET': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.WidthBucket'>>, 'XMLELEMENT': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.XMLElement'>>, 'XMLGET': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.XMLGet'>>, 'X_M_L_TABLE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.XMLTable'>>, 'XOR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.core.Xor'>>, 'YEAR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.Year'>>, 'YEAR_OF_WEEK': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.YearOfWeek'>>, 'YEAROFWEEK': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.YearOfWeek'>>, 'YEAR_OF_WEEK_ISO': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.YearOfWeekIso'>>, 'YEAROFWEEKISO': <bound method Func.from_arg_list of <class 'sqlglot.expressions.temporal.YearOfWeekIso'>>, 'ZIPF': <bound method Func.from_arg_list of <class 'sqlglot.expressions.functions.Zipf'>>, 'EXPLODE_OUTER': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array._ExplodeOuter'>>, 'ARRAYAGG': <function Parser.<lambda>>, 'GLOB': <function Parser.<lambda>>, 'JSON_EXTRACT_PATH_TEXT': <function build_extract_json_with_path.<locals>._builder>, 'LIKE': <function build_like>, 'LOG2': <function Parser.<lambda>>, 'LOG10': <function Parser.<lambda>>, 'LPAD': <function Parser.<lambda>>, 'LEFTPAD': <function Parser.<lambda>>, 'LTRIM': <function Parser.<lambda>>, 'MOD': <function build_mod>, 'RIGHTPAD': <function Parser.<lambda>>, 'RPAD': <function Parser.<lambda>>, 'RTRIM': <function Parser.<lambda>>, 'SCOPE_RESOLUTION': <function Parser.<lambda>>, 'STRPOS': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.StrPosition'>>, 'CHARINDEX': <function Parser.<lambda>>, 'INSTR': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.StrPosition'>>, 'LOCATE': <function Parser.<lambda>>, 'TO_HEX': <function build_hex>}
NO_PAREN_FUNCTIONS: ClassVar[dict] = {<TokenType.CURRENT_DATE: 246>: <class 'sqlglot.expressions.temporal.CurrentDate'>, <TokenType.CURRENT_DATETIME: 247>: <class 'sqlglot.expressions.temporal.CurrentDate'>, <TokenType.CURRENT_TIME: 249>: <class 'sqlglot.expressions.temporal.CurrentTime'>, <TokenType.CURRENT_TIMESTAMP: 250>: <class 'sqlglot.expressions.temporal.CurrentTimestamp'>, <TokenType.CURRENT_USER: 251>: <class 'sqlglot.expressions.functions.CurrentUser'>, <TokenType.CURRENT_ROLE: 253>: <class 'sqlglot.expressions.functions.CurrentRole'>}
STRUCT_TYPE_TOKENS: ClassVar = {<TokenType.UNION: 416>, <TokenType.NESTED: 210>, <TokenType.STRUCT: 403>, <TokenType.OBJECT: 199>}
NESTED_TYPE_TOKENS: ClassVar = {<TokenType.ARRAY: 224>, <TokenType.LIST: 320>, <TokenType.UNION: 416>, <TokenType.MAP: 323>, <TokenType.OBJECT: 199>, <TokenType.NULLABLE: 174>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.RANGE: 371>, <TokenType.STRUCT: 403>}
ENUM_TYPE_TOKENS: ClassVar = {<TokenType.DYNAMIC: 216>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>}
AGGREGATE_TYPE_TOKENS: ClassVar = {<TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>}
TYPE_TOKENS: ClassVar = {<TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ARRAY: 224>, <TokenType.INTERVAL: 304>, <TokenType.LIST: 320>, <TokenType.MAP: 323>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.RANGE: 371>, <TokenType.STRUCT: 403>, <TokenType.UNION: 416>}
SIGNED_TO_UNSIGNED_TYPE_TOKEN: ClassVar = {<TokenType.BIGINT: 107>: <TokenType.UBIGINT: 108>, <TokenType.INT: 105>: <TokenType.UINT: 106>, <TokenType.MEDIUMINT: 103>: <TokenType.UMEDIUMINT: 104>, <TokenType.SMALLINT: 101>: <TokenType.USMALLINT: 102>, <TokenType.TINYINT: 99>: <TokenType.UTINYINT: 100>, <TokenType.DECIMAL: 117>: <TokenType.UDECIMAL: 123>, <TokenType.DOUBLE: 115>: <TokenType.UDOUBLE: 116>}
SUBQUERY_PREDICATES: ClassVar = {<TokenType.ANY: 222>: <class 'sqlglot.expressions.core.Any'>, <TokenType.ALL: 220>: <class 'sqlglot.expressions.core.All'>, <TokenType.EXISTS: 271>: <class 'sqlglot.expressions.functions.Exists'>, <TokenType.SOME: 396>: <class 'sqlglot.expressions.core.Any'>}
SUBQUERY_TOKENS: ClassVar = {<TokenType.FROM: 283>, <TokenType.SELECT: 387>, <TokenType.WITH: 431>}
RESERVED_TOKENS: ClassVar = {<TokenType.L_PAREN: 1>, <TokenType.R_PAREN: 2>, <TokenType.L_BRACKET: 3>, <TokenType.R_BRACKET: 4>, <TokenType.L_BRACE: 5>, <TokenType.R_BRACE: 6>, <TokenType.COMMA: 7>, <TokenType.DOT: 8>, <TokenType.DASH: 9>, <TokenType.PLUS: 10>, <TokenType.COLON: 11>, <TokenType.SELECT: 387>, <TokenType.MOD: 329>, <TokenType.SEMICOLON: 19>, <TokenType.STAR: 20>, <TokenType.BACKSLASH: 21>, <TokenType.SLASH: 22>, <TokenType.LT: 23>, <TokenType.UNKNOWN: 214>, <TokenType.GT: 25>, <TokenType.NOT: 27>, <TokenType.EQ: 28>, <TokenType.AMP: 36>, <TokenType.PLACEHOLDER: 356>, <TokenType.PIPE: 39>, <TokenType.CARET: 42>, <TokenType.TILDE: 44>, <TokenType.HASH: 48>, <TokenType.PARAMETER: 58>}
TEXT_MATCH_EXCLUDED_TOKENS: ClassVar[frozenset] = frozenset({<TokenType.UNICODE_STRING: 96>, <TokenType.STRING: 77>, <TokenType.IDENTIFIER: 79>, <TokenType.BIT_STRING: 90>, <TokenType.HEX_STRING: 91>, <TokenType.BYTE_STRING: 92>, <TokenType.NATIONAL_STRING: 93>, <TokenType.RAW_STRING: 94>, <TokenType.HEREDOC_STRING: 95>})
DB_CREATABLES: ClassVar = {<TokenType.DICTIONARY: 261>, <TokenType.SEQUENCE: 390>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.MODEL: 330>, <TokenType.DATABASE: 80>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.FILE_FORMAT: 275>, <TokenType.SCHEMA: 83>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.STAGE: 86>, <TokenType.NAMESPACE: 440>, <TokenType.TABLE: 84>, <TokenType.TAG: 406>, <TokenType.STREAMLIT: 88>, <TokenType.WAREHOUSE: 85>}
CREATABLES: ClassVar = {<TokenType.DICTIONARY: 261>, <TokenType.SEQUENCE: 390>, <TokenType.MODEL: 330>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.FILE_FORMAT: 275>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.STAGE: 86>, <TokenType.TAG: 406>, <TokenType.STREAMLIT: 88>, <TokenType.FOREIGN_KEY: 281>, <TokenType.WAREHOUSE: 85>, <TokenType.TRIGGER: 412>, <TokenType.FUNCTION: 285>, <TokenType.TYPE: 413>, <TokenType.VIEW: 424>, <TokenType.INDEX: 297>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.PROCEDURE: 363>, <TokenType.CONSTRAINT: 241>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.NAMESPACE: 440>}
TRIGGER_EVENTS: ClassVar = {<TokenType.DELETE: 257>, <TokenType.UPDATE: 419>, <TokenType.INSERT: 300>, <TokenType.TRUNCATE: 411>}
ALTERABLES: ClassVar = {<TokenType.VIEW: 424>, <TokenType.INDEX: 297>, <TokenType.SESSION: 59>, <TokenType.TABLE: 84>}
ID_VAR_TOKENS: ClassVar[set] = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANTI: 221>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ASOF: 226>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FULL: 284>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.IS: 308>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LEFT: 317>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.LOCK: 322>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NATURAL: 331>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PERCENT: 354>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RANGE: 371>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.RIGHT: 379>, <TokenType.ROLLUP: 383>, <TokenType.ROW: 384>, <TokenType.ROWS: 385>, <TokenType.SEMI: 388>, <TokenType.SEQUENCE: 390>, <TokenType.SET: 392>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.WINDOW: 430>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
TABLE_ALIAS_TOKENS: ClassVar[set] = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.IS: 308>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PERCENT: 354>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RANGE: 371>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.ROLLUP: 383>, <TokenType.ROW: 384>, <TokenType.ROWS: 385>, <TokenType.SEQUENCE: 390>, <TokenType.SET: 392>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
ALIAS_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANTI: 221>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ASOF: 226>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FULL: 284>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.IS: 308>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LEFT: 317>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.LOCK: 322>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NATURAL: 331>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PERCENT: 354>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RANGE: 371>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.RIGHT: 379>, <TokenType.ROLLUP: 383>, <TokenType.ROW: 384>, <TokenType.ROWS: 385>, <TokenType.SEMI: 388>, <TokenType.SEQUENCE: 390>, <TokenType.SET: 392>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.WINDOW: 430>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
COLON_PLACEHOLDER_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANTI: 221>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ASOF: 226>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FULL: 284>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.IS: 308>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LEFT: 317>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.LOCK: 322>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NATURAL: 331>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PERCENT: 354>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RANGE: 371>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.RIGHT: 379>, <TokenType.ROLLUP: 383>, <TokenType.ROW: 384>, <TokenType.ROWS: 385>, <TokenType.SEMI: 388>, <TokenType.SEQUENCE: 390>, <TokenType.SET: 392>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.WINDOW: 430>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
ARRAY_CONSTRUCTORS: ClassVar = {'ARRAY': <class 'sqlglot.expressions.array.Array'>, 'LIST': <class 'sqlglot.expressions.array.List'>}
COMMENT_TABLE_ALIAS_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PERCENT: 354>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RANGE: 371>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.ROLLUP: 383>, <TokenType.ROW: 384>, <TokenType.ROWS: 385>, <TokenType.SEQUENCE: 390>, <TokenType.SET: 392>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
UPDATE_ALIAS_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.IS: 308>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PERCENT: 354>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RANGE: 371>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.ROLLUP: 383>, <TokenType.ROW: 384>, <TokenType.ROWS: 385>, <TokenType.SEQUENCE: 390>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
TRIM_TYPES: ClassVar = {'BOTH', 'LEADING', 'TRAILING'}
IDENTIFIER_TOKENS: ClassVar[frozenset] = frozenset({<TokenType.VAR: 89>, <TokenType.IDENTIFIER: 79>})
BRACKETS: ClassVar[frozenset] = frozenset({<TokenType.L_BRACKET: 3>, <TokenType.L_BRACE: 5>})
COLUMN_POSTFIX_TOKENS: ClassVar[frozenset] = frozenset({<TokenType.L_PAREN: 1>, <TokenType.L_BRACKET: 3>, <TokenType.L_BRACE: 5>, <TokenType.JOIN_MARKER: 311>, <TokenType.COLON: 11>})
TABLE_POSTFIX_TOKENS: ClassVar[frozenset] = frozenset({<TokenType.L_PAREN: 1>, <TokenType.UNPIVOT: 418>, <TokenType.PIVOT: 355>, <TokenType.L_BRACKET: 3>, <TokenType.L_BRACE: 5>, <TokenType.TABLE_SAMPLE: 405>})
FUNC_TOKENS: ClassVar = {<TokenType.SESSION_USER: 61>, <TokenType.XOR: 66>, <TokenType.IDENTIFIER: 79>, <TokenType.TABLE: 84>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANY: 222>, <TokenType.ARRAY: 224>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.EXISTS: 271>, <TokenType.FILTER: 276>, <TokenType.FIRST: 278>, <TokenType.FORMAT: 282>, <TokenType.GET: 286>, <TokenType.GLOB: 287>, <TokenType.ILIKE: 295>, <TokenType.INDEX: 297>, <TokenType.INSERT: 300>, <TokenType.INTERVAL: 304>, <TokenType.ISNULL: 309>, <TokenType.LEFT: 317>, <TokenType.LIKE: 318>, <TokenType.LIST: 320>, <TokenType.MAP: 323>, <TokenType.MERGE: 328>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.PRIMARY_KEY: 362>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.RANGE: 371>, <TokenType.REPLACE: 375>, <TokenType.RIGHT: 379>, <TokenType.RLIKE: 380>, <TokenType.ROW: 384>, <TokenType.SEQUENCE: 390>, <TokenType.SOME: 396>, <TokenType.STRUCT: 403>, <TokenType.TRUNCATE: 411>, <TokenType.UNION: 416>, <TokenType.UNNEST: 417>, <TokenType.WINDOW: 430>, <TokenType.UTC_DATE: 433>, <TokenType.UTC_TIME: 434>, <TokenType.UTC_TIMESTAMP: 435>}
CONJUNCTION: ClassVar[dict[sqlglot.tokenizer_core.TokenType, type[sqlglot.expressions.core.Expr]]] = {<TokenType.AND: 34>: <class 'sqlglot.expressions.core.And'>}
ASSIGNMENT: ClassVar[dict[sqlglot.tokenizer_core.TokenType, type[sqlglot.expressions.core.Expr]]] = {<TokenType.COLON_EQ: 31>: <class 'sqlglot.expressions.core.PropertyEQ'>}
DISJUNCTION: ClassVar[dict[sqlglot.tokenizer_core.TokenType, type[sqlglot.expressions.core.Expr]]] = {<TokenType.OR: 35>: <class 'sqlglot.expressions.core.Or'>}
EQUALITY: ClassVar = {<TokenType.EQ: 28>: <class 'sqlglot.expressions.core.EQ'>, <TokenType.NEQ: 29>: <class 'sqlglot.expressions.core.NEQ'>, <TokenType.NULLSAFE_EQ: 30>: <class 'sqlglot.expressions.core.NullSafeEQ'>}
COMPARISON: ClassVar = {<TokenType.GT: 25>: <class 'sqlglot.expressions.core.GT'>, <TokenType.GTE: 26>: <class 'sqlglot.expressions.core.GTE'>, <TokenType.LT: 23>: <class 'sqlglot.expressions.core.LT'>, <TokenType.LTE: 24>: <class 'sqlglot.expressions.core.LTE'>}
BITWISE: ClassVar = {<TokenType.AMP: 36>: <class 'sqlglot.expressions.core.BitwiseAnd'>, <TokenType.CARET: 42>: <class 'sqlglot.expressions.core.BitwiseXor'>, <TokenType.PIPE: 39>: <class 'sqlglot.expressions.core.BitwiseOr'>}
TERM: ClassVar = {<TokenType.DASH: 9>: <class 'sqlglot.expressions.core.Sub'>, <TokenType.PLUS: 10>: <class 'sqlglot.expressions.core.Add'>, <TokenType.COLLATE: 236>: <class 'sqlglot.expressions.functions.Collate'>}
FACTOR: ClassVar = {<TokenType.DIV: 264>: <class 'sqlglot.expressions.core.IntDiv'>, <TokenType.LR_ARROW: 51>: <class 'sqlglot.expressions.core.Distance'>, <TokenType.LLRR_ARROW: 52>: <class 'sqlglot.expressions.core.DistanceNd'>, <TokenType.MOD: 329>: <class 'sqlglot.expressions.core.Mod'>, <TokenType.SLASH: 22>: <class 'sqlglot.expressions.core.Div'>, <TokenType.STAR: 20>: <class 'sqlglot.expressions.core.Mul'>}
EXPONENT: ClassVar[dict[sqlglot.tokenizer_core.TokenType, type[sqlglot.expressions.core.Expr]]] = {}
TIMES: ClassVar = {<TokenType.TIMETZ: 144>, <TokenType.TIME: 143>}
TIMESTAMPS: ClassVar = {<TokenType.TIMETZ: 144>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIME: 143>}
SET_OPERATIONS: ClassVar = {<TokenType.UNION: 416>, <TokenType.EXCEPT: 269>, <TokenType.INTERSECT: 303>}
JOIN_METHODS: ClassVar = {<TokenType.ASOF: 226>, <TokenType.NATURAL: 331>, <TokenType.POSITIONAL: 359>}
JOIN_SIDES: ClassVar = {<TokenType.RIGHT: 379>, <TokenType.FULL: 284>, <TokenType.LEFT: 317>}
JOIN_KINDS: ClassVar = {<TokenType.SEMI: 388>, <TokenType.INNER: 299>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.CROSS: 244>, <TokenType.OUTER: 347>, <TokenType.ANTI: 221>}
JOIN_HINTS: ClassVar[set[str]] = set()
TABLE_TERMINATORS: ClassVar[frozenset] = frozenset({<TokenType.R_PAREN: 2>, <TokenType.SEMI: 388>, <TokenType.COMMA: 7>, <TokenType.NATURAL: 331>, <TokenType.EXCEPT: 269>, <TokenType.ON: 338>, <TokenType.SEMICOLON: 19>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.ORDER_BY: 341>, <TokenType.OUTER: 347>, <TokenType.FULL: 284>, <TokenType.ANTI: 221>, <TokenType.UNION: 416>, <TokenType.GROUP_BY: 290>, <TokenType.ASOF: 226>, <TokenType.HAVING: 292>, <TokenType.POSITIONAL: 359>, <TokenType.INNER: 299>, <TokenType.WHERE: 429>, <TokenType.INTERSECT: 303>, <TokenType.RIGHT: 379>, <TokenType.CROSS: 244>, <TokenType.JOIN: 310>, <TokenType.SENTINEL: 443>, <TokenType.LEFT: 317>, <TokenType.LIMIT: 319>})
LAMBDAS: ClassVar = {<TokenType.ARROW: 45>: <function Parser.<lambda>>, <TokenType.FARROW: 47>: <function Parser.<lambda>>}
TYPED_LAMBDA_ARGS: ClassVar[bool] = False
LAMBDA_ARG_TERMINATORS: ClassVar[frozenset] = frozenset({<TokenType.R_PAREN: 2>, <TokenType.COMMA: 7>})
COLUMN_OPERATORS: ClassVar = {<TokenType.DOT: 8>: None, <TokenType.DOTCOLON: 12>: <function Parser.<lambda>>, <TokenType.DCOLON: 14>: <function Parser.<lambda>>, <TokenType.ARROW: 45>: <function Parser.<lambda>>, <TokenType.DARROW: 46>: <function Parser.<lambda>>, <TokenType.HASH_ARROW: 49>: <function Parser.<lambda>>, <TokenType.DHASH_ARROW: 50>: <function Parser.<lambda>>, <TokenType.PLACEHOLDER: 356>: <function Parser.<lambda>>}
JSON_OPERATORS: ClassVar[dict[sqlglot.tokenizer_core.TokenType, Callable]] = {}
CAST_COLUMN_OPERATORS: ClassVar = {<TokenType.DOTCOLON: 12>, <TokenType.DCOLON: 14>}
EXPRESSION_PARSERS: ClassVar = {<class 'sqlglot.expressions.query.Cluster'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.core.Column'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.ColumnDef'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.core.Condition'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.datatypes.DataType'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.core.Expr'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.From'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.properties.GrantPrincipal'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.properties.GrantPrivilege'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Group'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Having'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.core.Hint'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.core.Identifier'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Join'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Lambda'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Lateral'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Limit'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Offset'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Order'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.core.Ordered'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.properties.Properties'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Qualify'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.dml.Returning'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Select'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Sort'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Table'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.TableAlias'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Tuple'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.dml.Whens'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Where'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.Window'>: <function Parser.<lambda>>, <class 'sqlglot.expressions.query.With'>: <function Parser.<lambda>>}
STATEMENT_PARSERS: ClassVar = {<TokenType.ALTER: 219>: <function Parser.<lambda>>, <TokenType.ANALYZE: 439>: <function Parser.<lambda>>, <TokenType.BEGIN: 229>: <function Parser.<lambda>>, <TokenType.CACHE: 232>: <function Parser.<lambda>>, <TokenType.COMMENT: 238>: <function Parser.<lambda>>, <TokenType.COMMIT: 239>: <function Parser.<lambda>>, <TokenType.COPY: 242>: <function Parser.<lambda>>, <TokenType.CREATE: 243>: <function Parser.<lambda>>, <TokenType.DECLARE: 255>: <function Parser.<lambda>>, <TokenType.DELETE: 257>: <function Parser.<lambda>>, <TokenType.DESC: 258>: <function Parser.<lambda>>, <TokenType.DESCRIBE: 259>: <function Parser.<lambda>>, <TokenType.DROP: 265>: <function Parser.<lambda>>, <TokenType.GRANT: 289>: <function Parser.<lambda>>, <TokenType.REVOKE: 377>: <function Parser.<lambda>>, <TokenType.INSERT: 300>: <function Parser.<lambda>>, <TokenType.KILL: 314>: <function Parser.<lambda>>, <TokenType.LOAD: 321>: <function Parser.<lambda>>, <TokenType.MERGE: 328>: <function Parser.<lambda>>, <TokenType.PIVOT: 355>: <function Parser.<lambda>>, <TokenType.PRAGMA: 360>: <function Parser.<lambda>>, <TokenType.REFRESH: 373>: <function Parser.<lambda>>, <TokenType.ROLLBACK: 382>: <function Parser.<lambda>>, <TokenType.SET: 392>: <function Parser.<lambda>>, <TokenType.TRUNCATE: 411>: <function Parser.<lambda>>, <TokenType.UNCACHE: 414>: <function Parser.<lambda>>, <TokenType.UNPIVOT: 418>: <function Parser.<lambda>>, <TokenType.UPDATE: 419>: <function Parser.<lambda>>, <TokenType.USE: 420>: <function Parser.<lambda>>, <TokenType.SEMICOLON: 19>: <function Parser.<lambda>>}
UNARY_PARSERS: ClassVar = {<TokenType.PLUS: 10>: <function Parser.<lambda>>, <TokenType.NOT: 27>: <function Parser.<lambda>>, <TokenType.TILDE: 44>: <function Parser.<lambda>>, <TokenType.DASH: 9>: <function Parser.<lambda>>, <TokenType.PIPE_SLASH: 40>: <function Parser.<lambda>>, <TokenType.DPIPE_SLASH: 41>: <function Parser.<lambda>>}
STRING_PARSERS: ClassVar = {<TokenType.HEREDOC_STRING: 95>: <function Parser.<lambda>>, <TokenType.NATIONAL_STRING: 93>: <function Parser.<lambda>>, <TokenType.RAW_STRING: 94>: <function Parser.<lambda>>, <TokenType.STRING: 77>: <function Parser.<lambda>>, <TokenType.UNICODE_STRING: 96>: <function Parser.<lambda>>}
NUMERIC_PARSERS: ClassVar = {<TokenType.BIT_STRING: 90>: <function Parser.<lambda>>, <TokenType.BYTE_STRING: 92>: <function Parser.<lambda>>, <TokenType.HEX_STRING: 91>: <function Parser.<lambda>>, <TokenType.NUMBER: 78>: <function Parser.<lambda>>}
PRIMARY_PARSERS: ClassVar = {<TokenType.HEREDOC_STRING: 95>: <function Parser.<lambda>>, <TokenType.NATIONAL_STRING: 93>: <function Parser.<lambda>>, <TokenType.RAW_STRING: 94>: <function Parser.<lambda>>, <TokenType.STRING: 77>: <function Parser.<lambda>>, <TokenType.UNICODE_STRING: 96>: <function Parser.<lambda>>, <TokenType.BIT_STRING: 90>: <function Parser.<lambda>>, <TokenType.BYTE_STRING: 92>: <function Parser.<lambda>>, <TokenType.HEX_STRING: 91>: <function Parser.<lambda>>, <TokenType.NUMBER: 78>: <function Parser.<lambda>>, <TokenType.INTRODUCER: 306>: <function Parser.<lambda>>, <TokenType.NULL: 335>: <function Parser.<lambda>>, <TokenType.TRUE: 410>: <function Parser.<lambda>>, <TokenType.FALSE: 272>: <function Parser.<lambda>>, <TokenType.SESSION_PARAMETER: 60>: <function Parser.<lambda>>, <TokenType.STAR: 20>: <function Parser.<lambda>>}
PLACEHOLDER_PARSERS: ClassVar = {<TokenType.PLACEHOLDER: 356>: <function Parser.<lambda>>, <TokenType.PARAMETER: 58>: <function Parser.<lambda>>, <TokenType.COLON: 11>: <function Parser.<lambda>>}
RANGE_PARSERS: ClassVar = {<TokenType.AT_GT: 56>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.BETWEEN: 230>: <function Parser.<lambda>>, <TokenType.GLOB: 287>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.ILIKE: 295>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.IN: 296>: <function Parser.<lambda>>, <TokenType.IRLIKE: 307>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.IS: 308>: <function Parser.<lambda>>, <TokenType.LIKE: 318>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.LT_AT: 55>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.OVERLAPS: 349>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.RLIKE: 380>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.SIMILAR_TO: 395>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.FOR: 279>: <function Parser.<lambda>>, <TokenType.QMARK_AMP: 68>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.QMARK_PIPE: 69>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.HASH_DASH: 70>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.AT_QMARK: 54>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.ADJACENT: 65>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.OPERATOR: 340>: <function Parser.<lambda>>, <TokenType.AMP_LT: 63>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.AMP_GT: 64>: <function binary_range_parser.<locals>._parse_binary_range>}
PIPE_SYNTAX_TRANSFORM_PARSERS: ClassVar = {'AGGREGATE': <function Parser.<lambda>>, 'AS': <function Parser.<lambda>>, 'DISTINCT': <function Parser.<lambda>>, 'EXTEND': <function Parser.<lambda>>, 'LIMIT': <function Parser.<lambda>>, 'ORDER BY': <function Parser.<lambda>>, 'PIVOT': <function Parser.<lambda>>, 'SELECT': <function Parser.<lambda>>, 'TABLESAMPLE': <function Parser.<lambda>>, 'UNPIVOT': <function Parser.<lambda>>, 'WHERE': <function Parser.<lambda>>}
PROPERTY_PARSERS: ClassVar[dict[str, Callable]] = {'ALLOWED_VALUES': <function Parser.<lambda>>, 'ALGORITHM': <function Parser.<lambda>>, 'AUTO': <function Parser.<lambda>>, 'AUTO_INCREMENT': <function Parser.<lambda>>, 'BACKUP': <function Parser.<lambda>>, 'BLOCKCOMPRESSION': <function Parser.<lambda>>, 'CALLED': <function Parser.<lambda>>, 'CHARSET': <function Parser.<lambda>>, 'CHECKSUM': <function Parser.<lambda>>, 'CLUSTER BY': <function Parser.<lambda>>, 'CLUSTERED': <function Parser.<lambda>>, 'COLLATE': <function Parser.<lambda>>, 'COMMENT': <function Parser.<lambda>>, 'CONTAINS': <function Parser.<lambda>>, 'COPY': <function Parser.<lambda>>, 'DATABLOCKSIZE': <function Parser.<lambda>>, 'DATA_DELETION': <function Parser.<lambda>>, 'DEFINER': <function Parser.<lambda>>, 'DETERMINISTIC': <function Parser.<lambda>>, 'DISTRIBUTED': <function Parser.<lambda>>, 'DUPLICATE': <function Parser.<lambda>>, 'DYNAMIC': <function Parser.<lambda>>, 'DISTKEY': <function Parser.<lambda>>, 'DISTSTYLE': <function Parser.<lambda>>, 'EMPTY': <function Parser.<lambda>>, 'ENGINE': <function Parser.<lambda>>, 'ENVIRONMENT': <function Parser.<lambda>>, 'HANDLER': <function Parser.<lambda>>, 'EXECUTE': <function Parser.<lambda>>, 'EXTERNAL': <function Parser.<lambda>>, 'FALLBACK': <function Parser.<lambda>>, 'FORMAT': <function Parser.<lambda>>, 'FREESPACE': <function Parser.<lambda>>, 'GLOBAL': <function Parser.<lambda>>, 'HEAP': <function Parser.<lambda>>, 'ICEBERG': <function Parser.<lambda>>, 'IMMUTABLE': <function Parser.<lambda>>, 'INHERITS': <function Parser.<lambda>>, 'INPUT': <function Parser.<lambda>>, 'JOURNAL': <function Parser.<lambda>>, 'LANGUAGE': <function Parser.<lambda>>, 'LAYOUT': <function Parser.<lambda>>, 'LIFETIME': <function Parser.<lambda>>, 'LIKE': <function Parser.<lambda>>, 'LOCATION': <function Parser.<lambda>>, 'LOCK': <function Parser.<lambda>>, 'LOCKING': <function Parser.<lambda>>, 'LOG': <function Parser.<lambda>>, 'MATERIALIZED': <function Parser.<lambda>>, 'MERGEBLOCKRATIO': <function Parser.<lambda>>, 'MODIFIES': <function Parser.<lambda>>, 'MULTISET': <function Parser.<lambda>>, 'NO': <function Parser.<lambda>>, 'ON': <function Parser.<lambda>>, 'ORDER BY': <function Parser.<lambda>>, 'OUTPUT': <function Parser.<lambda>>, 'PARTITION': <function Parser.<lambda>>, 'PARTITION BY': <function Parser.<lambda>>, 'PARTITIONED BY': <function Parser.<lambda>>, 'PARTITIONED_BY': <function Parser.<lambda>>, 'PRIMARY KEY': <function Parser.<lambda>>, 'RANGE': <function Parser.<lambda>>, 'READS': <function Parser.<lambda>>, 'REMOTE': <function Parser.<lambda>>, 'RETURNS': <function Parser.<lambda>>, 'STRICT': <function Parser.<lambda>>, 'STREAMING': <function Parser.<lambda>>, 'ROW': <function Parser.<lambda>>, 'ROW_FORMAT': <function Parser.<lambda>>, 'SAMPLE': <function Parser.<lambda>>, 'SECURE': <function Parser.<lambda>>, 'SECURITY': <function Parser.<lambda>>, 'SQL SECURITY': <function Parser.<lambda>>, 'SET': <function Parser.<lambda>>, 'SETTINGS': <function Parser.<lambda>>, 'SHARING': <function Parser.<lambda>>, 'SORTKEY': <function Parser.<lambda>>, 'SOURCE': <function Parser.<lambda>>, 'STABLE': <function Parser.<lambda>>, 'STORED': <function Parser.<lambda>>, 'SYSTEM_VERSIONING': <function Parser.<lambda>>, 'TBLPROPERTIES': <function Parser.<lambda>>, 'TEMP': <function Parser.<lambda>>, 'TEMPORARY': <function Parser.<lambda>>, 'TO': <function Parser.<lambda>>, 'TRANSIENT': <function Parser.<lambda>>, 'TRANSFORM': <function Parser.<lambda>>, 'TTL': <function Parser.<lambda>>, 'USING': <function Parser.<lambda>>, 'UNLOGGED': <function Parser.<lambda>>, 'VOLATILE': <function Parser.<lambda>>, 'WITH': <function Parser.<lambda>>}
CONSTRAINT_PARSERS: ClassVar = {'AUTOINCREMENT': <function Parser.<lambda>>, 'AUTO_INCREMENT': <function Parser.<lambda>>, 'CASESPECIFIC': <function Parser.<lambda>>, 'CHECK': <function Parser.<lambda>>, 'COLLATE': <function Parser.<lambda>>, 'COMMENT': <function Parser.<lambda>>, 'COMPRESS': <function Parser.<lambda>>, 'CLUSTERED': <function Parser.<lambda>>, 'NONCLUSTERED': <function Parser.<lambda>>, 'DEFAULT': <function Parser.<lambda>>, 'ENCODE': <function Parser.<lambda>>, 'EPHEMERAL': <function Parser.<lambda>>, 'EXCLUDE': <function Parser.<lambda>>, 'FOREIGN KEY': <function Parser.<lambda>>, 'FORMAT': <function Parser.<lambda>>, 'GENERATED': <function Parser.<lambda>>, 'IDENTITY': <function Parser.<lambda>>, 'INLINE': <function Parser.<lambda>>, 'LIKE': <function Parser.<lambda>>, 'NOT': <function Parser.<lambda>>, 'NULL': <function Parser.<lambda>>, 'ON': <function Parser.<lambda>>, 'PATH': <function Parser.<lambda>>, 'PERIOD': <function Parser.<lambda>>, 'PRIMARY KEY': <function Parser.<lambda>>, 'REFERENCES': <function Parser.<lambda>>, 'TITLE': <function Parser.<lambda>>, 'TTL': <function Parser.<lambda>>, 'UNIQUE': <function Parser.<lambda>>, 'UPPERCASE': <function Parser.<lambda>>, 'WITH': <function Parser.<lambda>>, 'BUCKET': <function Parser.<lambda>>, 'TRUNCATE': <function Parser.<lambda>>}
ALTER_PARSERS: ClassVar = {'ADD': <function Parser.<lambda>>, 'AS': <function Parser.<lambda>>, 'ALTER': <function Parser.<lambda>>, 'CLUSTER BY': <function Parser.<lambda>>, 'DELETE': <function Parser.<lambda>>, 'DROP': <function Parser.<lambda>>, 'RENAME': <function Parser.<lambda>>, 'SET': <function Parser.<lambda>>, 'SWAP': <function Parser.<lambda>>}
ALTER_ALTER_PARSERS: ClassVar = {'DISTKEY': <function Parser.<lambda>>, 'DISTSTYLE': <function Parser.<lambda>>, 'SORTKEY': <function Parser.<lambda>>, 'COMPOUND': <function Parser.<lambda>>}
SCHEMA_UNNAMED_CONSTRAINTS: ClassVar = {'UNIQUE', 'FOREIGN KEY', 'PRIMARY KEY', 'TRUNCATE', 'EXCLUDE', 'LIKE', 'PERIOD', 'CHECK', 'BUCKET'}
NO_PAREN_FUNCTION_PARSERS: ClassVar = {'ANY': <function Parser.<lambda>>, 'CASE': <function Parser.<lambda>>, 'CONNECT_BY_ROOT': <function Parser.<lambda>>, 'IF': <function Parser.<lambda>>}
INVALID_FUNC_NAME_TOKENS: ClassVar = {<TokenType.STRING: 77>, <TokenType.IDENTIFIER: 79>}
FUNCTIONS_WITH_ALIASED_ARGS: ClassVar = {'STRUCT'}
KEY_VALUE_DEFINITIONS: ClassVar = (<class 'sqlglot.expressions.core.Alias'>, <class 'sqlglot.expressions.core.EQ'>, <class 'sqlglot.expressions.core.PropertyEQ'>, <class 'sqlglot.expressions.core.Slice'>)
FUNCTION_PARSERS: ClassVar[dict[str, Callable]] = {'ARG_MAX': <function Parser.<dictcomp>.<lambda>>, 'ARGMAX': <function Parser.<dictcomp>.<lambda>>, 'MAX_BY': <function Parser.<dictcomp>.<lambda>>, 'ARG_MIN': <function Parser.<dictcomp>.<lambda>>, 'ARGMIN': <function Parser.<dictcomp>.<lambda>>, 'MIN_BY': <function Parser.<dictcomp>.<lambda>>, 'CAST': <function Parser.<lambda>>, 'CEIL': <function Parser.<lambda>>, 'CONVERT': <function Parser.<lambda>>, 'CHAR': <function Parser.<lambda>>, 'CHR': <function Parser.<lambda>>, 'DECODE': <function Parser.<lambda>>, 'EXTRACT': <function Parser.<lambda>>, 'FLOOR': <function Parser.<lambda>>, 'GAP_FILL': <function Parser.<lambda>>, 'INITCAP': <function Parser.<lambda>>, 'JSON_OBJECT': <function Parser.<lambda>>, 'JSON_OBJECTAGG': <function Parser.<lambda>>, 'JSON_TABLE': <function Parser.<lambda>>, 'MATCH': <function Parser.<lambda>>, 'NORMALIZE': <function Parser.<lambda>>, 'OPENJSON': <function Parser.<lambda>>, 'OVERLAY': <function Parser.<lambda>>, 'POSITION': <function Parser.<lambda>>, 'SAFE_CAST': <function Parser.<lambda>>, 'STRING_AGG': <function Parser.<lambda>>, 'SUBSTRING': <function Parser.<lambda>>, 'TRIM': <function Parser.<lambda>>, 'TRY_CAST': <function Parser.<lambda>>, 'TRY_CONVERT': <function Parser.<lambda>>, 'XMLELEMENT': <function Parser.<lambda>>, 'XMLTABLE': <function Parser.<lambda>>}
QUERY_MODIFIER_PARSERS: ClassVar = {<TokenType.MATCH_RECOGNIZE: 326>: <function Parser.<lambda>>, <TokenType.PREWHERE: 361>: <function Parser.<lambda>>, <TokenType.WHERE: 429>: <function Parser.<lambda>>, <TokenType.GROUP_BY: 290>: <function Parser.<lambda>>, <TokenType.HAVING: 292>: <function Parser.<lambda>>, <TokenType.QUALIFY: 368>: <function Parser.<lambda>>, <TokenType.WINDOW: 430>: <function Parser.<lambda>>, <TokenType.ORDER_BY: 341>: <function Parser.<lambda>>, <TokenType.LIMIT: 319>: <function Parser.<lambda>>, <TokenType.FETCH: 273>: <function Parser.<lambda>>, <TokenType.OFFSET: 337>: <function Parser.<lambda>>, <TokenType.FOR: 279>: <function Parser.<lambda>>, <TokenType.LOCK: 322>: <function Parser.<lambda>>, <TokenType.TABLE_SAMPLE: 405>: <function Parser.<lambda>>, <TokenType.USING: 421>: <function Parser.<lambda>>, <TokenType.CLUSTER_BY: 235>: <function Parser.<lambda>>, <TokenType.DISTRIBUTE_BY: 263>: <function Parser.<lambda>>, <TokenType.SORT_BY: 397>: <function Parser.<lambda>>, <TokenType.CONNECT_BY: 240>: <function Parser.<lambda>>}
QUERY_MODIFIER_TOKENS: ClassVar = {<TokenType.LOCK: 322>, <TokenType.MATCH_RECOGNIZE: 326>, <TokenType.DISTRIBUTE_BY: 263>, <TokenType.SORT_BY: 397>, <TokenType.FETCH: 273>, <TokenType.OFFSET: 337>, <TokenType.ORDER_BY: 341>, <TokenType.TABLE_SAMPLE: 405>, <TokenType.FOR: 279>, <TokenType.GROUP_BY: 290>, <TokenType.HAVING: 292>, <TokenType.USING: 421>, <TokenType.PREWHERE: 361>, <TokenType.CLUSTER_BY: 235>, <TokenType.WHERE: 429>, <TokenType.WINDOW: 430>, <TokenType.QUALIFY: 368>, <TokenType.CONNECT_BY: 240>, <TokenType.LIMIT: 319>}
SET_PARSERS: ClassVar = {'GLOBAL': <function Parser.<lambda>>, 'LOCAL': <function Parser.<lambda>>, 'SESSION': <function Parser.<lambda>>, 'TRANSACTION': <function Parser.<lambda>>}
SHOW_PARSERS: ClassVar[dict[str, Callable]] = {}
TYPE_LITERAL_PARSERS: ClassVar = {<DType.JSON: 'JSON'>: <function Parser.<lambda>>}
DDL_SELECT_TOKENS: ClassVar = {<TokenType.L_PAREN: 1>, <TokenType.SELECT: 387>, <TokenType.WITH: 431>}
PRE_VOLATILE_TOKENS: ClassVar = {<TokenType.UNIQUE: 432>, <TokenType.CREATE: 243>, <TokenType.REPLACE: 375>}
TRANSACTION_KIND: ClassVar = {'EXCLUSIVE', 'IMMEDIATE', 'DEFERRED'}
TRANSACTION_CHARACTERISTICS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'ISOLATION': (('LEVEL', 'REPEATABLE', 'READ'), ('LEVEL', 'READ', 'COMMITTED'), ('LEVEL', 'READ', 'UNCOMITTED'), ('LEVEL', 'SERIALIZABLE')), 'READ': ('WRITE', 'ONLY')}
CONFLICT_ACTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'ABORT': (), 'FAIL': (), 'IGNORE': (), 'REPLACE': (), 'ROLLBACK': (), 'UPDATE': (), 'DO': ('NOTHING', 'UPDATE')}
TRIGGER_TIMING: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'INSTEAD': (('OF',),), 'BEFORE': (), 'AFTER': ()}
TRIGGER_DEFERRABLE: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'NOT': (('DEFERRABLE',),), 'DEFERRABLE': ()}
CREATE_SEQUENCE: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'SCALE': ('EXTEND', 'NOEXTEND'), 'SHARD': ('EXTEND', 'NOEXTEND'), 'NO': ('CYCLE', 'CACHE', 'MAXVALUE', 'MINVALUE'), 'SESSION': (), 'GLOBAL': (), 'KEEP': (), 'NOKEEP': (), 'ORDER': (), 'NOORDER': (), 'NOCACHE': (), 'CYCLE': (), 'NOCYCLE': (), 'NOMINVALUE': (), 'NOMAXVALUE': (), 'NOSCALE': (), 'NOSHARD': ()}
ISOLATED_LOADING_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'FOR': ('ALL', 'INSERT', 'NONE')}
USABLES: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'ROLE': (), 'WAREHOUSE': (), 'DATABASE': (), 'SCHEMA': (), 'CATALOG': ()}
CAST_ACTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'RENAME': ('FIELDS',), 'ADD': ('FIELDS',)}
SCHEMA_BINDING_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'TYPE': ('EVOLUTION',), 'BINDING': (), 'COMPENSATION': (), 'EVOLUTION': ()}
PROCEDURE_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {}
EXECUTE_AS_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'CALLER': (), 'SELF': (), 'OWNER': ()}
KEY_CONSTRAINT_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'NOT': ('ENFORCED',), 'MATCH': ('FULL', 'PARTIAL', 'SIMPLE'), 'INITIALLY': ('DEFERRED', 'IMMEDIATE'), 'USING': ('BTREE', 'HASH'), 'DEFERRABLE': (), 'NORELY': (), 'RELY': ()}
WINDOW_EXCLUDE_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'NO': ('OTHERS',), 'CURRENT': ('ROW',), 'GROUP': (), 'TIES': ()}
INSERT_ALTERNATIVES: ClassVar = {'REPLACE', 'ROLLBACK', 'FAIL', 'IGNORE', 'ABORT'}
CLONE_KEYWORDS: ClassVar = {'COPY', 'CLONE'}
VERSION_PHRASES: ClassVar[dict[tuple[str, ...], str]] = {('FOR', 'SYSTEM_TIME'): 'TIMESTAMP', ('FOR', 'SYSTEM', 'TIME'): 'TIMESTAMP', ('FOR', 'TIMESTAMP'): 'TIMESTAMP', ('FOR', 'VERSION'): 'VERSION', ('TIMESTAMP', 'AS', 'OF'): 'TIMESTAMP', ('VERSION', 'AS', 'OF'): 'VERSION'}
HISTORICAL_DATA_PREFIX: ClassVar = {'AT', 'BEFORE', 'END'}
HISTORICAL_DATA_KIND: ClassVar = {'STATEMENT', 'VERSION', 'OFFSET', 'TIMESTAMP', 'STREAM'}
OPCLASS_FOLLOW_KEYWORDS: ClassVar = {'DESC', 'WITH', 'ASC', 'NULLS'}
OPTYPE_FOLLOW_TOKENS: ClassVar = {<TokenType.R_PAREN: 2>, <TokenType.COMMA: 7>}
TABLE_INDEX_HINT_TOKENS: ClassVar = {<TokenType.FORCE: 280>, <TokenType.USE: 420>, <TokenType.IGNORE: 294>}
VIEW_ATTRIBUTES: ClassVar = {'ENCRYPTION', 'VIEW_METADATA', 'SCHEMABINDING'}
WINDOW_ALIAS_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANTI: 221>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ASOF: 226>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FULL: 284>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.IS: 308>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LEFT: 317>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.LOCK: 322>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NATURAL: 331>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PERCENT: 354>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.RIGHT: 379>, <TokenType.ROLLUP: 383>, <TokenType.ROW: 384>, <TokenType.SEMI: 388>, <TokenType.SEQUENCE: 390>, <TokenType.SET: 392>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.WINDOW: 430>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
WINDOW_BEFORE_PAREN_TOKENS: ClassVar = {<TokenType.OVER: 348>}
WINDOW_SIDES: ClassVar = {'FOLLOWING', 'PRECEDING'}
JSON_KEY_VALUE_SEPARATOR_TOKENS: ClassVar = {<TokenType.COLON: 11>, <TokenType.IS: 308>, <TokenType.COMMA: 7>}
FETCH_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANTI: 221>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ASOF: 226>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FULL: 284>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.IS: 308>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LEFT: 317>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.LOCK: 322>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NATURAL: 331>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RANGE: 371>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.RIGHT: 379>, <TokenType.ROLLUP: 383>, <TokenType.SEMI: 388>, <TokenType.SEQUENCE: 390>, <TokenType.SET: 392>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.WINDOW: 430>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
ADD_CONSTRAINT_TOKENS: ClassVar = {<TokenType.FOREIGN_KEY: 281>, <TokenType.INDEX: 297>, <TokenType.PRIMARY_KEY: 362>, <TokenType.UNIQUE: 432>, <TokenType.CONSTRAINT: 241>, <TokenType.KEY: 313>}
DISTINCT_TOKENS: ClassVar = {<TokenType.DISTINCT: 262>}
UNNEST_OFFSET_ALIAS_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.IS: 308>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PERCENT: 354>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RANGE: 371>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.ROLLUP: 383>, <TokenType.ROW: 384>, <TokenType.ROWS: 385>, <TokenType.SEQUENCE: 390>, <TokenType.SET: 392>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
SELECT_START_TOKENS: ClassVar = {<TokenType.L_PAREN: 1>, <TokenType.SELECT: 387>, <TokenType.WITH: 431>}
COPY_INTO_VARLEN_OPTIONS: ClassVar = {'COPY_OPTIONS', 'FORMAT_OPTIONS', 'FILE_FORMAT', 'CREDENTIAL'}
IS_JSON_PREDICATE_KIND: ClassVar = {'SCALAR', 'OBJECT', 'VALUE', 'ARRAY'}
ODBC_DATETIME_LITERALS: ClassVar[dict[str, type[sqlglot.expressions.core.Expr]]] = {}
ON_CONDITION_TOKENS: ClassVar = {'FALSE', 'ERROR', 'NULL', 'TRUE', 'EMPTY'}
PRIVILEGE_FOLLOW_TOKENS: ClassVar = {<TokenType.L_PAREN: 1>, <TokenType.ON: 338>, <TokenType.COMMA: 7>}
DESCRIBE_STYLES: ClassVar = {'EXTENDED', 'HISTORY', 'FORMATTED', 'ANALYZE'}
SET_ASSIGNMENT_DELIMITERS: ClassVar = {'=', ':=', 'TO'}
ANALYZE_STYLES: ClassVar = {'BUFFER_USAGE_LIMIT', 'LOCAL', 'SAMPLE', 'VERBOSE', 'FULL', 'SKIP_LOCKED', 'NO_WRITE_TO_BINLOG'}
ANALYZE_EXPRESSION_PARSERS: ClassVar = {'ALL': <function Parser.<lambda>>, 'COMPUTE': <function Parser.<lambda>>, 'DELETE': <function Parser.<lambda>>, 'DROP': <function Parser.<lambda>>, 'ESTIMATE': <function Parser.<lambda>>, 'LIST': <function Parser.<lambda>>, 'PREDICATE': <function Parser.<lambda>>, 'UPDATE': <function Parser.<lambda>>, 'VALIDATE': <function Parser.<lambda>>}
PARTITION_KEYWORDS: ClassVar = {'PARTITION', 'SUBPARTITION'}
AMBIGUOUS_ALIAS_TOKENS: ClassVar = (<TokenType.LIMIT: 319>, <TokenType.OFFSET: 337>)
OPERATION_MODIFIERS: ClassVar[set[str]] = set()
RECURSIVE_CTE_SEARCH_KIND: ClassVar = {'CYCLE', 'BREADTH', 'DEPTH'}
SECURITY_PROPERTY_KEYWORDS: ClassVar = {'NONE', 'DEFINER', 'INVOKER'}
STRICT_CAST: ClassVar = True
PREFIXED_PIVOT_COLUMNS: ClassVar = False
IDENTIFY_PIVOT_STRINGS: ClassVar = False
UNPIVOT_VALUE_COLUMNS_FIRST: ClassVar = False
PIVOT_COLUMN_NAMING: ClassVar[str] = 'agg_name_if_aliased'
LOG_DEFAULTS_TO_LN: ClassVar = False
TABLESAMPLE_CSV: ClassVar = False
DEFAULT_SAMPLING_METHOD: ClassVar[str | None] = None
SET_REQUIRES_ASSIGNMENT_DELIMITER: ClassVar = True
TRIM_PATTERN_FIRST: ClassVar = False
STRING_ALIASES: ClassVar = False
MODIFIERS_ATTACHED_TO_SET_OP: ClassVar = True
SET_OP_MODIFIERS: ClassVar = {'sort', 'offset', 'order', 'limit', 'cluster', 'distribute'}
NO_PAREN_IF_COMMANDS: ClassVar = True
JSON_ARROWS_REQUIRE_JSON_TYPE: ClassVar = False
COLON_IS_VARIANT_EXTRACT: ClassVar = False
COLON_CHAIN_IS_SINGLE_EXTRACT: ClassVar = True
VALUES_FOLLOWED_BY_PAREN: ClassVar = True
SUPPORTS_IMPLICIT_UNNEST: ClassVar = False
SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: ClassVar = False
INTERVAL_SPANS: ClassVar = True
SUPPORTS_PARTITION_SELECTION: ClassVar = False
WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: ClassVar = True
OPTIONAL_ALIAS_TOKEN_CTE: ClassVar = True
ALTER_RENAME_REQUIRES_COLUMN: ClassVar = True
ALTER_TABLE_PARTITIONS: ClassVar = False
JOINS_HAVE_EQUAL_PRECEDENCE: ClassVar = False
ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: ClassVar = False
MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: ClassVar = False
JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: ClassVar = False
ADD_JOIN_ON_TRUE: ClassVar = False
SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: ClassVar = False
ADJACENT_STRINGS_CANNOT_BE_CONNECTED: ClassVar = False
SUPPORTS_NTH_VALUE_FROM_MODIFIER: ClassVar = False
QUOTED_TYPES_TO_PRESERVE: ClassVar[set[str]] = set()
SHOW_TRIE: ClassVar[dict] = {}
SET_TRIE: ClassVar[dict] = {'GLOBAL': {0: True}, 'LOCAL': {0: True}, 'SESSION': {0: True}, 'TRANSACTION': {0: True}}
error_message_context: int
max_errors: int
max_nodes: int
dialect: Any
sql: str
def reset(self) -> None:
1984    def reset(self) -> None:
1985        self.sql = ""
1986        self.errors = []
1987        self._tokens = []
1988        self._tokens_size = 0
1989        self._index = 0
1990        self._curr = SENTINEL_NONE
1991        self._next = SENTINEL_NONE
1992        self._prev = SENTINEL_NONE
1993        self._prev_comments = []
1994        self._pipe_cte_counter = 0
1995        self._chunks = []
1996        self._chunk_index = 0
1997        self._node_count = 0
def raise_error( self, message: str, token: sqlglot.tokenizer_core.Token = <Token token_type: TokenType.SENTINEL, text: SENTINEL, line: 1, col: 1, start: 0, end: 0, comments: []>) -> None:
2090    def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None:
2091        token = token or self._curr or self._prev or Token.string("")
2092        formatted_sql, start_context, highlight, end_context = highlight_sql(
2093            sql=self.sql,
2094            positions=[(token.start, token.end)],
2095            context_length=self.error_message_context,
2096        )
2097        formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n  {formatted_sql}"
2098
2099        error = ParseError.new(
2100            formatted_message,
2101            description=message,
2102            line=token.line,
2103            col=token.col,
2104            start_context=start_context,
2105            highlight=highlight,
2106            end_context=end_context,
2107        )
2108
2109        if self.error_level == ErrorLevel.IMMEDIATE:
2110            raise error
2111
2112        self.errors.append(error)
def validate_expression(self, expression: ~E, args: list | None = None) -> ~E:
2114    def validate_expression(self, expression: E, args: list | None = None) -> E:
2115        if self.max_nodes > -1:
2116            self._node_count += 1
2117            if self._node_count > self.max_nodes:
2118                self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded")
2119        if self.error_level != ErrorLevel.IGNORE:
2120            for error_message in expression.error_messages(args):
2121                self.raise_error(error_message)
2122        return expression
def parse( self, raw_tokens: list[sqlglot.tokenizer_core.Token], sql: str) -> list[sqlglot.expressions.core.Expr | None]:
2141    def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]:
2142        """
2143        Parses a list of tokens and returns a list of syntax trees, one tree
2144        per parsed SQL statement.
2145
2146        Args:
2147            raw_tokens: The list of tokens.
2148            sql: The original SQL string.
2149
2150        Returns:
2151            The list of the produced syntax trees.
2152        """
2153        return self._parse(
2154            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
2155        )

Parses a list of tokens and returns a list of syntax trees, one tree per parsed SQL statement.

Arguments:
  • raw_tokens: The list of tokens.
  • sql: The original SQL string.
Returns:

The list of the produced syntax trees.

def parse_into( self, expression_types: Union[type[sqlglot.expressions.core.Expr], Collection[type[sqlglot.expressions.core.Expr]]], raw_tokens: list[sqlglot.tokenizer_core.Token], sql: str | None = None) -> list[sqlglot.expressions.core.Expr | None]:
2157    def parse_into(
2158        self,
2159        expression_types: exp.IntoType,
2160        raw_tokens: list[Token],
2161        sql: str | None = None,
2162    ) -> list[exp.Expr | None]:
2163        """
2164        Parses a list of tokens into a given Expr type. If a collection of Expr
2165        types is given instead, this method will try to parse the token list into each one
2166        of them, stopping at the first for which the parsing succeeds.
2167
2168        Args:
2169            expression_types: The expression type(s) to try and parse the token list into.
2170            raw_tokens: The list of tokens.
2171            sql: The original SQL string, used to produce helpful debug messages.
2172
2173        Returns:
2174            The target Expr.
2175        """
2176        errors = []
2177        for expression_type in ensure_list(expression_types):
2178            parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type))
2179            if not parser:
2180                raise TypeError(f"No parser registered for {expression_type}")
2181
2182            try:
2183                return self._parse(parser, raw_tokens, sql)
2184            except ParseError as e:
2185                e.errors[0]["into_expression"] = expression_type
2186                errors.append(e)
2187
2188        raise ParseError(
2189            f"Failed to parse '{sql or raw_tokens}' into {expression_types}",
2190            errors=merge_errors(errors),
2191        ) from errors[-1]

Parses a list of tokens into a given Expr type. If a collection of Expr types is given instead, this method will try to parse the token list into each one of them, stopping at the first for which the parsing succeeds.

Arguments:
  • expression_types: The expression type(s) to try and parse the token list into.
  • raw_tokens: The list of tokens.
  • sql: The original SQL string, used to produce helpful debug messages.
Returns:

The target Expr.

def check_errors(self) -> None:
2193    def check_errors(self) -> None:
2194        """Logs or raises any found errors, depending on the chosen error level setting."""
2195        if self.error_level == ErrorLevel.WARN:
2196            for error in self.errors:
2197                logger.error(str(error))
2198        elif self.error_level == ErrorLevel.RAISE and self.errors:
2199            raise ParseError(
2200                concat_messages(self.errors, self.max_errors),
2201                errors=merge_errors(self.errors),
2202            )

Logs or raises any found errors, depending on the chosen error level setting.

def expression( self, instance: ~E, token: sqlglot.tokenizer_core.Token | None = None, comments: list[str] | None = None) -> ~E:
2204    def expression(
2205        self,
2206        instance: E,
2207        token: Token | None = None,
2208        comments: list[str] | None = None,
2209    ) -> E:
2210        if token:
2211            instance.update_positions(token)
2212        instance.add_comments(comments) if comments else self._add_comments(instance)
2213        if not instance.is_primitive:
2214            instance = self.validate_expression(instance)
2215        return instance
def parse_set_operation( self, this: sqlglot.expressions.core.Expr | None, consume_pipe: bool = False) -> sqlglot.expressions.core.Expr | None:
5947    def parse_set_operation(
5948        self, this: exp.Expr | None, consume_pipe: bool = False
5949    ) -> exp.Expr | None:
5950        start = self._index
5951        _, side_token, kind_token = self._parse_join_parts()
5952
5953        side = side_token.text if side_token else None
5954        kind = kind_token.text if kind_token else None
5955
5956        if not self._match_set(self.SET_OPERATIONS):
5957            self._retreat(start)
5958            return None
5959
5960        token_type = self._prev.token_type
5961
5962        if token_type == TokenType.UNION:
5963            operation: type[exp.SetOperation] = exp.Union
5964        elif token_type == TokenType.EXCEPT:
5965            operation = exp.Except
5966        else:
5967            operation = exp.Intersect
5968
5969        comments = self._prev.comments
5970
5971        if self._match(TokenType.DISTINCT):
5972            distinct: bool | None = True
5973        elif self._match(TokenType.ALL):
5974            distinct = False
5975        else:
5976            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
5977            if distinct is None:
5978                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
5979
5980        by_name = (
5981            self._match_text_seq("BY", "NAME")
5982            or self._match_text_seq("STRICT", "CORRESPONDING")
5983            or None
5984        )
5985        if self._match_text_seq("CORRESPONDING"):
5986            by_name = True
5987            if not side and not kind:
5988                kind = "INNER"
5989
5990        on_column_list = None
5991        if by_name and self._match_texts(("ON", "BY")):
5992            on_column_list = self._parse_wrapped_csv(self._parse_column)
5993
5994        expression = self._parse_select(
5995            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
5996        )
5997
5998        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
5999        # in _parse_cte and so that alias pushdown can reach into set operation branches
6000        if isinstance(this, exp.Values):
6001            this = self._values_to_select(this)
6002        if isinstance(expression, exp.Values):
6003            expression = self._values_to_select(expression)
6004
6005        if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery):
6006            subquery = this.this
6007            subquery.set("alias", exp.TableAlias(this=this.args["alias"]))
6008            subquery.add_comments(this.pop_comments())
6009            this = subquery
6010
6011        return self.expression(
6012            operation(
6013                this=this,
6014                distinct=distinct,
6015                by_name=by_name,
6016                expression=expression,
6017                side=side,
6018                kind=kind,
6019                on=on_column_list,
6020            ),
6021            comments=comments,
6022        )
def build_cast(self, strict: bool, **kwargs) -> sqlglot.expressions.core.Expr:
10452    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10453        exp_class = exp.Cast if strict else exp.TryCast
10454
10455        if exp_class == exp.TryCast:
10456            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10457
10458        return self.expression(exp_class(**kwargs))