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

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:
234def build_array_prepend(args: BuilderArgs, dialect: Dialect) -> exp.ArrayPrepend:
235    """
236    Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration.
237
238    Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL.
239    Others (DuckDB, PostgreSQL) create a new single-element array instead.
240
241    Args:
242        args: Function arguments [array, element]
243        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
244
245    Returns:
246        ArrayPrepend expression with appropriate null_propagation flag
247    """
248    return exp.ArrayPrepend(
249        this=seq_get(args, 0),
250        expression=seq_get(args, 1),
251        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,
252    )

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:
255def build_array_concat(args: BuilderArgs, dialect: Dialect) -> exp.ArrayConcat:
256    """
257    Builds ArrayConcat with NULL propagation semantics based on the dialect configuration.
258
259    Some dialects (Redshift, Snowflake) return NULL when any input array is NULL.
260    Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation.
261
262    Args:
263        args: Function arguments [array1, array2, ...] (variadic)
264        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
265
266    Returns:
267        ArrayConcat expression with appropriate null_propagation flag
268    """
269    return exp.ArrayConcat(
270        this=seq_get(args, 0),
271        expressions=args[1:],
272        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,
273    )

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:
276def build_array_remove(args: BuilderArgs, dialect: Dialect) -> exp.ArrayRemove:
277    """
278    Builds ArrayRemove with NULL propagation semantics based on the dialect configuration.
279
280    Some dialects (Snowflake) return NULL when the removal value is NULL.
281    Others (DuckDB) may return empty array due to NULL comparison semantics.
282
283    Args:
284        args: Function arguments [array, value_to_remove]
285        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from
286
287    Returns:
288        ArrayRemove expression with appropriate null_propagation flag
289    """
290    return exp.ArrayRemove(
291        this=seq_get(args, 0),
292        expression=seq_get(args, 1),
293        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,
294    )

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

315def build_json_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONExtract:
316    return self.expression(
317        exp.JSONExtract(
318            this=this,
319            expression=self.dialect.to_json_path(path),
320            only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
321        )
322    )
def build_json_extract_scalar( self: Parser, this: sqlglot.expressions.core.Expr, path: sqlglot.expressions.core.Expr) -> sqlglot.expressions.json.JSONExtractScalar:
325def build_json_extract_scalar(
326    self: Parser, this: exp.Expr, path: exp.Expr
327) -> exp.JSONExtractScalar:
328    return self.expression(
329        exp.JSONExtractScalar(
330            this=this,
331            expression=self.dialect.to_json_path(path),
332            only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
333            scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY,
334        )
335    )
338def build_jsonb_extract(self: Parser, this: exp.Expr, path: exp.Expr) -> exp.JSONBExtract:
339    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:
342def build_jsonb_extract_scalar(
343    self: Parser, this: exp.Expr, path: exp.Expr
344) -> exp.JSONBExtractScalar:
345    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:
348def build_jsonb_contains_top_key(
349    self: Parser, this: exp.Expr, key: exp.Expr
350) -> exp.JSONBContainsTopKey:
351    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 IsPredicatePrecedence(enum.Enum):
357class IsPredicatePrecedence(Enum):
358    HIGH = auto()
359    COMPARISON = auto()
360    EQUALITY = auto()
361    LOW = auto()

An enumeration.

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

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)
2018    def __init__(
2019        self,
2020        error_level: ErrorLevel | None = None,
2021        error_message_context: int = 100,
2022        max_errors: int = 3,
2023        max_nodes: int = -1,
2024        dialect: DialectType = None,
2025    ):
2026        self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE
2027        self.error_message_context: int = error_message_context
2028        self.max_errors: int = max_errors
2029        self.max_nodes: int = max_nodes
2030        self.dialect: t.Any = _resolve_dialect(dialect)
2031        self.sql: str = ""
2032        self.errors: list[ParseError] = []
2033        self._tokens: list[Token] = []
2034        self._tokens_size: i64 = 0
2035        self._index: i64 = 0
2036        self._curr: Token = SENTINEL_NONE
2037        self._next: Token = SENTINEL_NONE
2038        self._prev: Token = SENTINEL_NONE
2039        self._prev_comments: list[str] = []
2040        self._pipe_cte_counter: int = 0
2041        self._chunks: list[list[Token]] = []
2042        self._chunk_index: i64 = 0
2043        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'>>, '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'>>, 'SPLIT_TO_TABLE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.SplitToTable'>>, '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'>>, 'WEIGHT_STRING': <bound method Func.from_arg_list of <class 'sqlglot.expressions.string.WeightString'>>, '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'>>, 'SORT_ARRAY': <function build_sort_array>, '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: 247>: <class 'sqlglot.expressions.temporal.CurrentDate'>, <TokenType.CURRENT_DATETIME: 248>: <class 'sqlglot.expressions.temporal.CurrentDate'>, <TokenType.CURRENT_TIME: 250>: <class 'sqlglot.expressions.temporal.CurrentTime'>, <TokenType.CURRENT_TIMESTAMP: 251>: <class 'sqlglot.expressions.temporal.CurrentTimestamp'>, <TokenType.CURRENT_USER: 252>: <class 'sqlglot.expressions.functions.CurrentUser'>, <TokenType.CURRENT_ROLE: 254>: <class 'sqlglot.expressions.functions.CurrentRole'>}
STRUCT_TYPE_TOKENS: ClassVar = {<TokenType.OBJECT: 200>, <TokenType.UNION: 418>, <TokenType.NESTED: 211>, <TokenType.STRUCT: 405>}
NESTED_TYPE_TOKENS: ClassVar = {<TokenType.LIST: 321>, <TokenType.ARRAY: 225>, <TokenType.UNION: 418>, <TokenType.MAP: 324>, <TokenType.OBJECT: 200>, <TokenType.NULLABLE: 175>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.RANGE: 373>, <TokenType.STRUCT: 405>}
ENUM_TYPE_TOKENS: ClassVar = {<TokenType.ENUM16: 208>, <TokenType.DYNAMIC: 217>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>}
AGGREGATE_TYPE_TOKENS: ClassVar = {<TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>}
TYPE_TOKENS: ClassVar = {<TokenType.BIT: 98>, <TokenType.BOOLEAN: 99>, <TokenType.TINYINT: 100>, <TokenType.UTINYINT: 101>, <TokenType.SMALLINT: 102>, <TokenType.USMALLINT: 103>, <TokenType.MEDIUMINT: 104>, <TokenType.UMEDIUMINT: 105>, <TokenType.INT: 106>, <TokenType.UINT: 107>, <TokenType.BIGINT: 108>, <TokenType.UBIGINT: 109>, <TokenType.BIGNUM: 110>, <TokenType.INT128: 111>, <TokenType.UINT128: 112>, <TokenType.INT256: 113>, <TokenType.UINT256: 114>, <TokenType.FLOAT: 115>, <TokenType.DOUBLE: 116>, <TokenType.UDOUBLE: 117>, <TokenType.DECIMAL: 118>, <TokenType.DECIMAL32: 119>, <TokenType.DECIMAL64: 120>, <TokenType.DECIMAL128: 121>, <TokenType.DECIMAL256: 122>, <TokenType.DECFLOAT: 123>, <TokenType.UDECIMAL: 124>, <TokenType.BIGDECIMAL: 125>, <TokenType.CHAR: 126>, <TokenType.NCHAR: 127>, <TokenType.VARCHAR: 128>, <TokenType.NVARCHAR: 129>, <TokenType.BPCHAR: 130>, <TokenType.TEXT: 131>, <TokenType.MEDIUMTEXT: 132>, <TokenType.LONGTEXT: 133>, <TokenType.BLOB: 134>, <TokenType.MEDIUMBLOB: 135>, <TokenType.LONGBLOB: 136>, <TokenType.TINYBLOB: 137>, <TokenType.TINYTEXT: 138>, <TokenType.NAME: 139>, <TokenType.BINARY: 140>, <TokenType.VARBINARY: 141>, <TokenType.JSON: 142>, <TokenType.JSONB: 143>, <TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIME_NS: 146>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>, <TokenType.TIMESTAMP_S: 151>, <TokenType.TIMESTAMP_MS: 152>, <TokenType.TIMESTAMP_NS: 153>, <TokenType.DATETIME: 154>, <TokenType.DATETIME2: 155>, <TokenType.DATETIME64: 156>, <TokenType.SMALLDATETIME: 157>, <TokenType.DATE: 158>, <TokenType.DATE32: 159>, <TokenType.INT4RANGE: 160>, <TokenType.INT4MULTIRANGE: 161>, <TokenType.INT8RANGE: 162>, <TokenType.INT8MULTIRANGE: 163>, <TokenType.NUMRANGE: 164>, <TokenType.NUMMULTIRANGE: 165>, <TokenType.TSRANGE: 166>, <TokenType.TSMULTIRANGE: 167>, <TokenType.TSTZRANGE: 168>, <TokenType.TSTZMULTIRANGE: 169>, <TokenType.DATERANGE: 170>, <TokenType.DATEMULTIRANGE: 171>, <TokenType.UUID: 172>, <TokenType.GEOGRAPHY: 173>, <TokenType.GEOGRAPHYPOINT: 174>, <TokenType.NULLABLE: 175>, <TokenType.GEOMETRY: 176>, <TokenType.POINT: 177>, <TokenType.RING: 178>, <TokenType.LINESTRING: 179>, <TokenType.MULTILINESTRING: 183>, <TokenType.POLYGON: 184>, <TokenType.MULTIPOLYGON: 185>, <TokenType.HLLSKETCH: 186>, <TokenType.HSTORE: 187>, <TokenType.SUPER: 188>, <TokenType.SERIAL: 189>, <TokenType.SMALLSERIAL: 190>, <TokenType.BIGSERIAL: 191>, <TokenType.XML: 192>, <TokenType.YEAR: 193>, <TokenType.USERDEFINED: 194>, <TokenType.MONEY: 195>, <TokenType.SMALLMONEY: 196>, <TokenType.ROWVERSION: 197>, <TokenType.IMAGE: 198>, <TokenType.VARIANT: 199>, <TokenType.OBJECT: 200>, <TokenType.INET: 201>, <TokenType.IPADDRESS: 202>, <TokenType.IPPREFIX: 203>, <TokenType.IPV4: 204>, <TokenType.IPV6: 205>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>, <TokenType.ENUM16: 208>, <TokenType.FIXEDSTRING: 209>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>, <TokenType.TDIGEST: 214>, <TokenType.UNKNOWN: 215>, <TokenType.VECTOR: 216>, <TokenType.DYNAMIC: 217>, <TokenType.VOID: 218>, <TokenType.ARRAY: 225>, <TokenType.INTERVAL: 305>, <TokenType.LIST: 321>, <TokenType.MAP: 324>, <TokenType.NOTHING: 334>, <TokenType.NULL: 336>, <TokenType.OBJECT_IDENTIFIER: 337>, <TokenType.PSEUDO_TYPE: 368>, <TokenType.RANGE: 373>, <TokenType.STRUCT: 405>, <TokenType.UNION: 418>}
SIGNED_TO_UNSIGNED_TYPE_TOKEN: ClassVar = {<TokenType.BIGINT: 108>: <TokenType.UBIGINT: 109>, <TokenType.INT: 106>: <TokenType.UINT: 107>, <TokenType.MEDIUMINT: 104>: <TokenType.UMEDIUMINT: 105>, <TokenType.SMALLINT: 102>: <TokenType.USMALLINT: 103>, <TokenType.TINYINT: 100>: <TokenType.UTINYINT: 101>, <TokenType.DECIMAL: 118>: <TokenType.UDECIMAL: 124>, <TokenType.DOUBLE: 116>: <TokenType.UDOUBLE: 117>}
SUBQUERY_PREDICATES: ClassVar = {<TokenType.ANY: 223>: <class 'sqlglot.expressions.core.Any'>, <TokenType.ALL: 221>: <class 'sqlglot.expressions.core.All'>, <TokenType.EXISTS: 272>: <class 'sqlglot.expressions.functions.Exists'>, <TokenType.SOME: 398>: <class 'sqlglot.expressions.core.Any'>}
SUBQUERY_TOKENS: ClassVar = {<TokenType.WITH: 433>, <TokenType.FROM: 284>, <TokenType.SELECT: 389>}
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: 389>, <TokenType.MOD: 330>, <TokenType.SEMICOLON: 19>, <TokenType.STAR: 20>, <TokenType.BACKSLASH: 21>, <TokenType.SLASH: 22>, <TokenType.LT: 23>, <TokenType.UNKNOWN: 215>, <TokenType.GT: 25>, <TokenType.NOT: 27>, <TokenType.EQ: 28>, <TokenType.AMP: 36>, <TokenType.PLACEHOLDER: 358>, <TokenType.PIPE: 39>, <TokenType.CARET: 42>, <TokenType.TILDE: 44>, <TokenType.HASH: 48>, <TokenType.PARAMETER: 58>}
TEXT_MATCH_EXCLUDED_TOKENS: ClassVar[frozenset] = frozenset({<TokenType.HEREDOC_STRING: 96>, <TokenType.UNICODE_STRING: 97>, <TokenType.STRING: 77>, <TokenType.IDENTIFIER: 79>, <TokenType.BIT_STRING: 90>, <TokenType.HEX_STRING: 91>, <TokenType.BYTE_STRING: 92>, <TokenType.NATIONAL_RAW_STRING: 93>, <TokenType.NATIONAL_STRING: 94>, <TokenType.RAW_STRING: 95>})
DB_CREATABLES: ClassVar = {<TokenType.TAG: 408>, <TokenType.DICTIONARY: 262>, <TokenType.SEQUENCE: 392>, <TokenType.VIEW: 426>, <TokenType.MODEL: 331>, <TokenType.SEMANTIC_VIEW: 427>, <TokenType.DATABASE: 80>, <TokenType.SCHEMA: 83>, <TokenType.FILE_FORMAT: 276>, <TokenType.STORAGE_INTEGRATION: 403>, <TokenType.STAGE: 86>, <TokenType.SINK: 439>, <TokenType.SOURCE: 440>, <TokenType.TABLE: 84>, <TokenType.NAMESPACE: 442>, <TokenType.STREAMLIT: 88>, <TokenType.WAREHOUSE: 85>}
CREATABLES: ClassVar = {<TokenType.DICTIONARY: 262>, <TokenType.SEQUENCE: 392>, <TokenType.MODEL: 331>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.FILE_FORMAT: 276>, <TokenType.STORAGE_INTEGRATION: 403>, <TokenType.STAGE: 86>, <TokenType.TABLE: 84>, <TokenType.TAG: 408>, <TokenType.STREAMLIT: 88>, <TokenType.FOREIGN_KEY: 282>, <TokenType.WAREHOUSE: 85>, <TokenType.TRIGGER: 414>, <TokenType.FUNCTION: 286>, <TokenType.TYPE: 415>, <TokenType.INDEX: 298>, <TokenType.VIEW: 426>, <TokenType.SEMANTIC_VIEW: 427>, <TokenType.PROCEDURE: 365>, <TokenType.CONSTRAINT: 242>, <TokenType.SINK: 439>, <TokenType.SOURCE: 440>, <TokenType.NAMESPACE: 442>}
TRIGGER_EVENTS: ClassVar = {<TokenType.DELETE: 258>, <TokenType.INSERT: 301>, <TokenType.TRUNCATE: 413>, <TokenType.UPDATE: 421>}
ALTERABLES: ClassVar = {<TokenType.VIEW: 426>, <TokenType.INDEX: 298>, <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: 98>, <TokenType.BOOLEAN: 99>, <TokenType.TINYINT: 100>, <TokenType.UTINYINT: 101>, <TokenType.SMALLINT: 102>, <TokenType.USMALLINT: 103>, <TokenType.MEDIUMINT: 104>, <TokenType.UMEDIUMINT: 105>, <TokenType.INT: 106>, <TokenType.UINT: 107>, <TokenType.BIGINT: 108>, <TokenType.UBIGINT: 109>, <TokenType.BIGNUM: 110>, <TokenType.INT128: 111>, <TokenType.UINT128: 112>, <TokenType.INT256: 113>, <TokenType.UINT256: 114>, <TokenType.FLOAT: 115>, <TokenType.DOUBLE: 116>, <TokenType.UDOUBLE: 117>, <TokenType.DECIMAL: 118>, <TokenType.DECIMAL32: 119>, <TokenType.DECIMAL64: 120>, <TokenType.DECIMAL128: 121>, <TokenType.DECIMAL256: 122>, <TokenType.DECFLOAT: 123>, <TokenType.UDECIMAL: 124>, <TokenType.BIGDECIMAL: 125>, <TokenType.CHAR: 126>, <TokenType.NCHAR: 127>, <TokenType.VARCHAR: 128>, <TokenType.NVARCHAR: 129>, <TokenType.BPCHAR: 130>, <TokenType.TEXT: 131>, <TokenType.MEDIUMTEXT: 132>, <TokenType.LONGTEXT: 133>, <TokenType.BLOB: 134>, <TokenType.MEDIUMBLOB: 135>, <TokenType.LONGBLOB: 136>, <TokenType.TINYBLOB: 137>, <TokenType.TINYTEXT: 138>, <TokenType.NAME: 139>, <TokenType.BINARY: 140>, <TokenType.VARBINARY: 141>, <TokenType.JSON: 142>, <TokenType.JSONB: 143>, <TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIME_NS: 146>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>, <TokenType.TIMESTAMP_S: 151>, <TokenType.TIMESTAMP_MS: 152>, <TokenType.TIMESTAMP_NS: 153>, <TokenType.DATETIME: 154>, <TokenType.DATETIME2: 155>, <TokenType.DATETIME64: 156>, <TokenType.SMALLDATETIME: 157>, <TokenType.DATE: 158>, <TokenType.DATE32: 159>, <TokenType.INT4RANGE: 160>, <TokenType.INT4MULTIRANGE: 161>, <TokenType.INT8RANGE: 162>, <TokenType.INT8MULTIRANGE: 163>, <TokenType.NUMRANGE: 164>, <TokenType.NUMMULTIRANGE: 165>, <TokenType.TSRANGE: 166>, <TokenType.TSMULTIRANGE: 167>, <TokenType.TSTZRANGE: 168>, <TokenType.TSTZMULTIRANGE: 169>, <TokenType.DATERANGE: 170>, <TokenType.DATEMULTIRANGE: 171>, <TokenType.UUID: 172>, <TokenType.GEOGRAPHY: 173>, <TokenType.GEOGRAPHYPOINT: 174>, <TokenType.NULLABLE: 175>, <TokenType.GEOMETRY: 176>, <TokenType.POINT: 177>, <TokenType.RING: 178>, <TokenType.LINESTRING: 179>, <TokenType.LOCALTIME: 180>, <TokenType.LOCALTIMESTAMP: 181>, <TokenType.MULTILINESTRING: 183>, <TokenType.POLYGON: 184>, <TokenType.MULTIPOLYGON: 185>, <TokenType.HLLSKETCH: 186>, <TokenType.HSTORE: 187>, <TokenType.SUPER: 188>, <TokenType.SERIAL: 189>, <TokenType.SMALLSERIAL: 190>, <TokenType.BIGSERIAL: 191>, <TokenType.XML: 192>, <TokenType.YEAR: 193>, <TokenType.USERDEFINED: 194>, <TokenType.MONEY: 195>, <TokenType.SMALLMONEY: 196>, <TokenType.ROWVERSION: 197>, <TokenType.IMAGE: 198>, <TokenType.VARIANT: 199>, <TokenType.OBJECT: 200>, <TokenType.INET: 201>, <TokenType.IPADDRESS: 202>, <TokenType.IPPREFIX: 203>, <TokenType.IPV4: 204>, <TokenType.IPV6: 205>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>, <TokenType.ENUM16: 208>, <TokenType.FIXEDSTRING: 209>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>, <TokenType.TDIGEST: 214>, <TokenType.UNKNOWN: 215>, <TokenType.VECTOR: 216>, <TokenType.DYNAMIC: 217>, <TokenType.VOID: 218>, <TokenType.ALL: 221>, <TokenType.ANTI: 222>, <TokenType.ANY: 223>, <TokenType.APPLY: 224>, <TokenType.ARRAY: 225>, <TokenType.ASC: 226>, <TokenType.ASOF: 227>, <TokenType.ATTACH: 228>, <TokenType.AUTO_INCREMENT: 229>, <TokenType.BEGIN: 230>, <TokenType.CACHE: 233>, <TokenType.CASE: 234>, <TokenType.COLLATE: 237>, <TokenType.COMMAND: 238>, <TokenType.COMMENT: 239>, <TokenType.COMMIT: 240>, <TokenType.CONSTRAINT: 242>, <TokenType.COPY: 243>, <TokenType.CUBE: 246>, <TokenType.CURRENT_DATE: 247>, <TokenType.CURRENT_DATETIME: 248>, <TokenType.CURRENT_SCHEMA: 249>, <TokenType.CURRENT_TIME: 250>, <TokenType.CURRENT_TIMESTAMP: 251>, <TokenType.CURRENT_USER: 252>, <TokenType.CURRENT_ROLE: 254>, <TokenType.CURRENT_CATALOG: 255>, <TokenType.DECLARE: 256>, <TokenType.DEFAULT: 257>, <TokenType.DELETE: 258>, <TokenType.DESC: 259>, <TokenType.DESCRIBE: 260>, <TokenType.DETACH: 261>, <TokenType.DICTIONARY: 262>, <TokenType.DIV: 265>, <TokenType.END: 268>, <TokenType.ESCAPE: 269>, <TokenType.EXECUTE: 271>, <TokenType.EXISTS: 272>, <TokenType.FALSE: 273>, <TokenType.FILE: 275>, <TokenType.FILE_FORMAT: 276>, <TokenType.FILTER: 277>, <TokenType.FINAL: 278>, <TokenType.FIRST: 279>, <TokenType.FOREIGN_KEY: 282>, <TokenType.FORMAT: 283>, <TokenType.FULL: 285>, <TokenType.FUNCTION: 286>, <TokenType.GET: 287>, <TokenType.INDEX: 298>, <TokenType.INTERVAL: 305>, <TokenType.IS: 309>, <TokenType.ISNULL: 310>, <TokenType.KEEP: 313>, <TokenType.KILL: 315>, <TokenType.LEFT: 318>, <TokenType.LIMIT: 320>, <TokenType.LIST: 321>, <TokenType.LOAD: 322>, <TokenType.LOCK: 323>, <TokenType.MAP: 324>, <TokenType.MATCH: 325>, <TokenType.MERGE: 329>, <TokenType.MODEL: 331>, <TokenType.NATURAL: 332>, <TokenType.NEXT: 333>, <TokenType.NOTHING: 334>, <TokenType.NULL: 336>, <TokenType.OBJECT_IDENTIFIER: 337>, <TokenType.OFFSET: 338>, <TokenType.OPERATOR: 341>, <TokenType.ORDINALITY: 345>, <TokenType.OUT: 346>, <TokenType.INOUT: 347>, <TokenType.OVER: 349>, <TokenType.OVERLAPS: 350>, <TokenType.OVERWRITE: 351>, <TokenType.PARTITION: 353>, <TokenType.PASTE: 355>, <TokenType.PERCENT: 356>, <TokenType.PIVOT: 357>, <TokenType.PRAGMA: 362>, <TokenType.PROCEDURE: 365>, <TokenType.PROJECTION: 367>, <TokenType.PSEUDO_TYPE: 368>, <TokenType.PUT: 369>, <TokenType.RANGE: 373>, <TokenType.RECURSIVE: 374>, <TokenType.REFRESH: 375>, <TokenType.RENAME: 376>, <TokenType.REPLACE: 377>, <TokenType.REFERENCES: 380>, <TokenType.RIGHT: 381>, <TokenType.ROLLUP: 385>, <TokenType.ROW: 386>, <TokenType.ROWS: 387>, <TokenType.SEMI: 390>, <TokenType.SEQUENCE: 392>, <TokenType.SET: 394>, <TokenType.SETTINGS: 395>, <TokenType.SHOW: 396>, <TokenType.SOME: 398>, <TokenType.STORAGE_INTEGRATION: 403>, <TokenType.STRAIGHT_JOIN: 404>, <TokenType.STRUCT: 405>, <TokenType.TAG: 408>, <TokenType.TEMPORARY: 409>, <TokenType.TOP: 410>, <TokenType.TRUE: 412>, <TokenType.TRUNCATE: 413>, <TokenType.TRIGGER: 414>, <TokenType.TYPE: 415>, <TokenType.UNNEST: 419>, <TokenType.UNPIVOT: 420>, <TokenType.UPDATE: 421>, <TokenType.USE: 422>, <TokenType.VIEW: 426>, <TokenType.SEMANTIC_VIEW: 427>, <TokenType.VOLATILE: 428>, <TokenType.WINDOW: 432>, <TokenType.UNIQUE: 434>, <TokenType.SINK: 439>, <TokenType.SOURCE: 440>, <TokenType.ANALYZE: 441>, <TokenType.NAMESPACE: 442>, <TokenType.EXPORT: 443>}
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: 98>, <TokenType.BOOLEAN: 99>, <TokenType.TINYINT: 100>, <TokenType.UTINYINT: 101>, <TokenType.SMALLINT: 102>, <TokenType.USMALLINT: 103>, <TokenType.MEDIUMINT: 104>, <TokenType.UMEDIUMINT: 105>, <TokenType.INT: 106>, <TokenType.UINT: 107>, <TokenType.BIGINT: 108>, <TokenType.UBIGINT: 109>, <TokenType.BIGNUM: 110>, <TokenType.INT128: 111>, <TokenType.UINT128: 112>, <TokenType.INT256: 113>, <TokenType.UINT256: 114>, <TokenType.FLOAT: 115>, <TokenType.DOUBLE: 116>, <TokenType.UDOUBLE: 117>, <TokenType.DECIMAL: 118>, <TokenType.DECIMAL32: 119>, <TokenType.DECIMAL64: 120>, <TokenType.DECIMAL128: 121>, <TokenType.DECIMAL256: 122>, <TokenType.DECFLOAT: 123>, <TokenType.UDECIMAL: 124>, <TokenType.BIGDECIMAL: 125>, <TokenType.CHAR: 126>, <TokenType.NCHAR: 127>, <TokenType.VARCHAR: 128>, <TokenType.NVARCHAR: 129>, <TokenType.BPCHAR: 130>, <TokenType.TEXT: 131>, <TokenType.MEDIUMTEXT: 132>, <TokenType.LONGTEXT: 133>, <TokenType.BLOB: 134>, <TokenType.MEDIUMBLOB: 135>, <TokenType.LONGBLOB: 136>, <TokenType.TINYBLOB: 137>, <TokenType.TINYTEXT: 138>, <TokenType.NAME: 139>, <TokenType.BINARY: 140>, <TokenType.VARBINARY: 141>, <TokenType.JSON: 142>, <TokenType.JSONB: 143>, <TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIME_NS: 146>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>, <TokenType.TIMESTAMP_S: 151>, <TokenType.TIMESTAMP_MS: 152>, <TokenType.TIMESTAMP_NS: 153>, <TokenType.DATETIME: 154>, <TokenType.DATETIME2: 155>, <TokenType.DATETIME64: 156>, <TokenType.SMALLDATETIME: 157>, <TokenType.DATE: 158>, <TokenType.DATE32: 159>, <TokenType.INT4RANGE: 160>, <TokenType.INT4MULTIRANGE: 161>, <TokenType.INT8RANGE: 162>, <TokenType.INT8MULTIRANGE: 163>, <TokenType.NUMRANGE: 164>, <TokenType.NUMMULTIRANGE: 165>, <TokenType.TSRANGE: 166>, <TokenType.TSMULTIRANGE: 167>, <TokenType.TSTZRANGE: 168>, <TokenType.TSTZMULTIRANGE: 169>, <TokenType.DATERANGE: 170>, <TokenType.DATEMULTIRANGE: 171>, <TokenType.UUID: 172>, <TokenType.GEOGRAPHY: 173>, <TokenType.GEOGRAPHYPOINT: 174>, <TokenType.NULLABLE: 175>, <TokenType.GEOMETRY: 176>, <TokenType.POINT: 177>, <TokenType.RING: 178>, <TokenType.LINESTRING: 179>, <TokenType.LOCALTIME: 180>, <TokenType.LOCALTIMESTAMP: 181>, <TokenType.MULTILINESTRING: 183>, <TokenType.POLYGON: 184>, <TokenType.MULTIPOLYGON: 185>, <TokenType.HLLSKETCH: 186>, <TokenType.HSTORE: 187>, <TokenType.SUPER: 188>, <TokenType.SERIAL: 189>, <TokenType.SMALLSERIAL: 190>, <TokenType.BIGSERIAL: 191>, <TokenType.XML: 192>, <TokenType.YEAR: 193>, <TokenType.USERDEFINED: 194>, <TokenType.MONEY: 195>, <TokenType.SMALLMONEY: 196>, <TokenType.ROWVERSION: 197>, <TokenType.IMAGE: 198>, <TokenType.VARIANT: 199>, <TokenType.OBJECT: 200>, <TokenType.INET: 201>, <TokenType.IPADDRESS: 202>, <TokenType.IPPREFIX: 203>, <TokenType.IPV4: 204>, <TokenType.IPV6: 205>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>, <TokenType.ENUM16: 208>, <TokenType.FIXEDSTRING: 209>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>, <TokenType.TDIGEST: 214>, <TokenType.UNKNOWN: 215>, <TokenType.VECTOR: 216>, <TokenType.DYNAMIC: 217>, <TokenType.VOID: 218>, <TokenType.ALL: 221>, <TokenType.ANY: 223>, <TokenType.APPLY: 224>, <TokenType.ARRAY: 225>, <TokenType.ASC: 226>, <TokenType.ATTACH: 228>, <TokenType.AUTO_INCREMENT: 229>, <TokenType.BEGIN: 230>, <TokenType.CACHE: 233>, <TokenType.CASE: 234>, <TokenType.COLLATE: 237>, <TokenType.COMMAND: 238>, <TokenType.COMMENT: 239>, <TokenType.COMMIT: 240>, <TokenType.CONSTRAINT: 242>, <TokenType.COPY: 243>, <TokenType.CUBE: 246>, <TokenType.CURRENT_DATE: 247>, <TokenType.CURRENT_DATETIME: 248>, <TokenType.CURRENT_SCHEMA: 249>, <TokenType.CURRENT_TIME: 250>, <TokenType.CURRENT_TIMESTAMP: 251>, <TokenType.CURRENT_USER: 252>, <TokenType.CURRENT_ROLE: 254>, <TokenType.CURRENT_CATALOG: 255>, <TokenType.DECLARE: 256>, <TokenType.DEFAULT: 257>, <TokenType.DELETE: 258>, <TokenType.DESC: 259>, <TokenType.DESCRIBE: 260>, <TokenType.DETACH: 261>, <TokenType.DICTIONARY: 262>, <TokenType.DIV: 265>, <TokenType.END: 268>, <TokenType.ESCAPE: 269>, <TokenType.EXECUTE: 271>, <TokenType.EXISTS: 272>, <TokenType.FALSE: 273>, <TokenType.FILE: 275>, <TokenType.FILE_FORMAT: 276>, <TokenType.FILTER: 277>, <TokenType.FINAL: 278>, <TokenType.FIRST: 279>, <TokenType.FOREIGN_KEY: 282>, <TokenType.FORMAT: 283>, <TokenType.FUNCTION: 286>, <TokenType.GET: 287>, <TokenType.INDEX: 298>, <TokenType.INTERVAL: 305>, <TokenType.IS: 309>, <TokenType.ISNULL: 310>, <TokenType.KEEP: 313>, <TokenType.KILL: 315>, <TokenType.LIMIT: 320>, <TokenType.LIST: 321>, <TokenType.LOAD: 322>, <TokenType.MAP: 324>, <TokenType.MATCH: 325>, <TokenType.MERGE: 329>, <TokenType.MODEL: 331>, <TokenType.NEXT: 333>, <TokenType.NOTHING: 334>, <TokenType.NULL: 336>, <TokenType.OBJECT_IDENTIFIER: 337>, <TokenType.OFFSET: 338>, <TokenType.OPERATOR: 341>, <TokenType.ORDINALITY: 345>, <TokenType.OUT: 346>, <TokenType.INOUT: 347>, <TokenType.OVER: 349>, <TokenType.OVERLAPS: 350>, <TokenType.OVERWRITE: 351>, <TokenType.PARTITION: 353>, <TokenType.PASTE: 355>, <TokenType.PERCENT: 356>, <TokenType.PIVOT: 357>, <TokenType.PRAGMA: 362>, <TokenType.PROCEDURE: 365>, <TokenType.PROJECTION: 367>, <TokenType.PSEUDO_TYPE: 368>, <TokenType.PUT: 369>, <TokenType.RANGE: 373>, <TokenType.RECURSIVE: 374>, <TokenType.REFRESH: 375>, <TokenType.RENAME: 376>, <TokenType.REPLACE: 377>, <TokenType.REFERENCES: 380>, <TokenType.ROLLUP: 385>, <TokenType.ROW: 386>, <TokenType.ROWS: 387>, <TokenType.SEQUENCE: 392>, <TokenType.SET: 394>, <TokenType.SETTINGS: 395>, <TokenType.SHOW: 396>, <TokenType.SOME: 398>, <TokenType.STORAGE_INTEGRATION: 403>, <TokenType.STRAIGHT_JOIN: 404>, <TokenType.STRUCT: 405>, <TokenType.TAG: 408>, <TokenType.TEMPORARY: 409>, <TokenType.TOP: 410>, <TokenType.TRUE: 412>, <TokenType.TRUNCATE: 413>, <TokenType.TRIGGER: 414>, <TokenType.TYPE: 415>, <TokenType.UNNEST: 419>, <TokenType.UNPIVOT: 420>, <TokenType.UPDATE: 421>, <TokenType.USE: 422>, <TokenType.VIEW: 426>, <TokenType.SEMANTIC_VIEW: 427>, <TokenType.VOLATILE: 428>, <TokenType.UNIQUE: 434>, <TokenType.SINK: 439>, <TokenType.SOURCE: 440>, <TokenType.ANALYZE: 441>, <TokenType.NAMESPACE: 442>, <TokenType.EXPORT: 443>}
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: 98>, <TokenType.BOOLEAN: 99>, <TokenType.TINYINT: 100>, <TokenType.UTINYINT: 101>, <TokenType.SMALLINT: 102>, <TokenType.USMALLINT: 103>, <TokenType.MEDIUMINT: 104>, <TokenType.UMEDIUMINT: 105>, <TokenType.INT: 106>, <TokenType.UINT: 107>, <TokenType.BIGINT: 108>, <TokenType.UBIGINT: 109>, <TokenType.BIGNUM: 110>, <TokenType.INT128: 111>, <TokenType.UINT128: 112>, <TokenType.INT256: 113>, <TokenType.UINT256: 114>, <TokenType.FLOAT: 115>, <TokenType.DOUBLE: 116>, <TokenType.UDOUBLE: 117>, <TokenType.DECIMAL: 118>, <TokenType.DECIMAL32: 119>, <TokenType.DECIMAL64: 120>, <TokenType.DECIMAL128: 121>, <TokenType.DECIMAL256: 122>, <TokenType.DECFLOAT: 123>, <TokenType.UDECIMAL: 124>, <TokenType.BIGDECIMAL: 125>, <TokenType.CHAR: 126>, <TokenType.NCHAR: 127>, <TokenType.VARCHAR: 128>, <TokenType.NVARCHAR: 129>, <TokenType.BPCHAR: 130>, <TokenType.TEXT: 131>, <TokenType.MEDIUMTEXT: 132>, <TokenType.LONGTEXT: 133>, <TokenType.BLOB: 134>, <TokenType.MEDIUMBLOB: 135>, <TokenType.LONGBLOB: 136>, <TokenType.TINYBLOB: 137>, <TokenType.TINYTEXT: 138>, <TokenType.NAME: 139>, <TokenType.BINARY: 140>, <TokenType.VARBINARY: 141>, <TokenType.JSON: 142>, <TokenType.JSONB: 143>, <TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIME_NS: 146>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>, <TokenType.TIMESTAMP_S: 151>, <TokenType.TIMESTAMP_MS: 152>, <TokenType.TIMESTAMP_NS: 153>, <TokenType.DATETIME: 154>, <TokenType.DATETIME2: 155>, <TokenType.DATETIME64: 156>, <TokenType.SMALLDATETIME: 157>, <TokenType.DATE: 158>, <TokenType.DATE32: 159>, <TokenType.INT4RANGE: 160>, <TokenType.INT4MULTIRANGE: 161>, <TokenType.INT8RANGE: 162>, <TokenType.INT8MULTIRANGE: 163>, <TokenType.NUMRANGE: 164>, <TokenType.NUMMULTIRANGE: 165>, <TokenType.TSRANGE: 166>, <TokenType.TSMULTIRANGE: 167>, <TokenType.TSTZRANGE: 168>, <TokenType.TSTZMULTIRANGE: 169>, <TokenType.DATERANGE: 170>, <TokenType.DATEMULTIRANGE: 171>, <TokenType.UUID: 172>, <TokenType.GEOGRAPHY: 173>, <TokenType.GEOGRAPHYPOINT: 174>, <TokenType.NULLABLE: 175>, <TokenType.GEOMETRY: 176>, <TokenType.POINT: 177>, <TokenType.RING: 178>, <TokenType.LINESTRING: 179>, <TokenType.LOCALTIME: 180>, <TokenType.LOCALTIMESTAMP: 181>, <TokenType.MULTILINESTRING: 183>, <TokenType.POLYGON: 184>, <TokenType.MULTIPOLYGON: 185>, <TokenType.HLLSKETCH: 186>, <TokenType.HSTORE: 187>, <TokenType.SUPER: 188>, <TokenType.SERIAL: 189>, <TokenType.SMALLSERIAL: 190>, <TokenType.BIGSERIAL: 191>, <TokenType.XML: 192>, <TokenType.YEAR: 193>, <TokenType.USERDEFINED: 194>, <TokenType.MONEY: 195>, <TokenType.SMALLMONEY: 196>, <TokenType.ROWVERSION: 197>, <TokenType.IMAGE: 198>, <TokenType.VARIANT: 199>, <TokenType.OBJECT: 200>, <TokenType.INET: 201>, <TokenType.IPADDRESS: 202>, <TokenType.IPPREFIX: 203>, <TokenType.IPV4: 204>, <TokenType.IPV6: 205>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>, <TokenType.ENUM16: 208>, <TokenType.FIXEDSTRING: 209>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>, <TokenType.TDIGEST: 214>, <TokenType.UNKNOWN: 215>, <TokenType.VECTOR: 216>, <TokenType.DYNAMIC: 217>, <TokenType.VOID: 218>, <TokenType.ALL: 221>, <TokenType.ANTI: 222>, <TokenType.ANY: 223>, <TokenType.APPLY: 224>, <TokenType.ARRAY: 225>, <TokenType.ASC: 226>, <TokenType.ASOF: 227>, <TokenType.ATTACH: 228>, <TokenType.AUTO_INCREMENT: 229>, <TokenType.BEGIN: 230>, <TokenType.CACHE: 233>, <TokenType.CASE: 234>, <TokenType.COLLATE: 237>, <TokenType.COMMAND: 238>, <TokenType.COMMENT: 239>, <TokenType.COMMIT: 240>, <TokenType.CONSTRAINT: 242>, <TokenType.COPY: 243>, <TokenType.CUBE: 246>, <TokenType.CURRENT_DATE: 247>, <TokenType.CURRENT_DATETIME: 248>, <TokenType.CURRENT_SCHEMA: 249>, <TokenType.CURRENT_TIME: 250>, <TokenType.CURRENT_TIMESTAMP: 251>, <TokenType.CURRENT_USER: 252>, <TokenType.CURRENT_ROLE: 254>, <TokenType.CURRENT_CATALOG: 255>, <TokenType.DECLARE: 256>, <TokenType.DEFAULT: 257>, <TokenType.DELETE: 258>, <TokenType.DESC: 259>, <TokenType.DESCRIBE: 260>, <TokenType.DETACH: 261>, <TokenType.DICTIONARY: 262>, <TokenType.DIV: 265>, <TokenType.END: 268>, <TokenType.ESCAPE: 269>, <TokenType.EXECUTE: 271>, <TokenType.EXISTS: 272>, <TokenType.FALSE: 273>, <TokenType.FILE: 275>, <TokenType.FILE_FORMAT: 276>, <TokenType.FILTER: 277>, <TokenType.FINAL: 278>, <TokenType.FIRST: 279>, <TokenType.FOREIGN_KEY: 282>, <TokenType.FORMAT: 283>, <TokenType.FULL: 285>, <TokenType.FUNCTION: 286>, <TokenType.GET: 287>, <TokenType.INDEX: 298>, <TokenType.INTERVAL: 305>, <TokenType.IS: 309>, <TokenType.ISNULL: 310>, <TokenType.KEEP: 313>, <TokenType.KILL: 315>, <TokenType.LEFT: 318>, <TokenType.LIMIT: 320>, <TokenType.LIST: 321>, <TokenType.LOAD: 322>, <TokenType.LOCK: 323>, <TokenType.MAP: 324>, <TokenType.MATCH: 325>, <TokenType.MERGE: 329>, <TokenType.MODEL: 331>, <TokenType.NATURAL: 332>, <TokenType.NEXT: 333>, <TokenType.NOTHING: 334>, <TokenType.NULL: 336>, <TokenType.OBJECT_IDENTIFIER: 337>, <TokenType.OFFSET: 338>, <TokenType.OPERATOR: 341>, <TokenType.ORDINALITY: 345>, <TokenType.OUT: 346>, <TokenType.INOUT: 347>, <TokenType.OVER: 349>, <TokenType.OVERLAPS: 350>, <TokenType.OVERWRITE: 351>, <TokenType.PARTITION: 353>, <TokenType.PASTE: 355>, <TokenType.PERCENT: 356>, <TokenType.PIVOT: 357>, <TokenType.PRAGMA: 362>, <TokenType.PROCEDURE: 365>, <TokenType.PROJECTION: 367>, <TokenType.PSEUDO_TYPE: 368>, <TokenType.PUT: 369>, <TokenType.RANGE: 373>, <TokenType.RECURSIVE: 374>, <TokenType.REFRESH: 375>, <TokenType.RENAME: 376>, <TokenType.REPLACE: 377>, <TokenType.REFERENCES: 380>, <TokenType.RIGHT: 381>, <TokenType.ROLLUP: 385>, <TokenType.ROW: 386>, <TokenType.ROWS: 387>, <TokenType.SEMI: 390>, <TokenType.SEQUENCE: 392>, <TokenType.SET: 394>, <TokenType.SETTINGS: 395>, <TokenType.SHOW: 396>, <TokenType.SOME: 398>, <TokenType.STORAGE_INTEGRATION: 403>, <TokenType.STRAIGHT_JOIN: 404>, <TokenType.STRUCT: 405>, <TokenType.TAG: 408>, <TokenType.TEMPORARY: 409>, <TokenType.TOP: 410>, <TokenType.TRUE: 412>, <TokenType.TRUNCATE: 413>, <TokenType.TRIGGER: 414>, <TokenType.TYPE: 415>, <TokenType.UNNEST: 419>, <TokenType.UNPIVOT: 420>, <TokenType.UPDATE: 421>, <TokenType.USE: 422>, <TokenType.VIEW: 426>, <TokenType.SEMANTIC_VIEW: 427>, <TokenType.VOLATILE: 428>, <TokenType.WINDOW: 432>, <TokenType.UNIQUE: 434>, <TokenType.SINK: 439>, <TokenType.SOURCE: 440>, <TokenType.ANALYZE: 441>, <TokenType.NAMESPACE: 442>, <TokenType.EXPORT: 443>}
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: 98>, <TokenType.BOOLEAN: 99>, <TokenType.TINYINT: 100>, <TokenType.UTINYINT: 101>, <TokenType.SMALLINT: 102>, <TokenType.USMALLINT: 103>, <TokenType.MEDIUMINT: 104>, <TokenType.UMEDIUMINT: 105>, <TokenType.INT: 106>, <TokenType.UINT: 107>, <TokenType.BIGINT: 108>, <TokenType.UBIGINT: 109>, <TokenType.BIGNUM: 110>, <TokenType.INT128: 111>, <TokenType.UINT128: 112>, <TokenType.INT256: 113>, <TokenType.UINT256: 114>, <TokenType.FLOAT: 115>, <TokenType.DOUBLE: 116>, <TokenType.UDOUBLE: 117>, <TokenType.DECIMAL: 118>, <TokenType.DECIMAL32: 119>, <TokenType.DECIMAL64: 120>, <TokenType.DECIMAL128: 121>, <TokenType.DECIMAL256: 122>, <TokenType.DECFLOAT: 123>, <TokenType.UDECIMAL: 124>, <TokenType.BIGDECIMAL: 125>, <TokenType.CHAR: 126>, <TokenType.NCHAR: 127>, <TokenType.VARCHAR: 128>, <TokenType.NVARCHAR: 129>, <TokenType.BPCHAR: 130>, <TokenType.TEXT: 131>, <TokenType.MEDIUMTEXT: 132>, <TokenType.LONGTEXT: 133>, <TokenType.BLOB: 134>, <TokenType.MEDIUMBLOB: 135>, <TokenType.LONGBLOB: 136>, <TokenType.TINYBLOB: 137>, <TokenType.TINYTEXT: 138>, <TokenType.NAME: 139>, <TokenType.BINARY: 140>, <TokenType.VARBINARY: 141>, <TokenType.JSON: 142>, <TokenType.JSONB: 143>, <TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIME_NS: 146>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>, <TokenType.TIMESTAMP_S: 151>, <TokenType.TIMESTAMP_MS: 152>, <TokenType.TIMESTAMP_NS: 153>, <TokenType.DATETIME: 154>, <TokenType.DATETIME2: 155>, <TokenType.DATETIME64: 156>, <TokenType.SMALLDATETIME: 157>, <TokenType.DATE: 158>, <TokenType.DATE32: 159>, <TokenType.INT4RANGE: 160>, <TokenType.INT4MULTIRANGE: 161>, <TokenType.INT8RANGE: 162>, <TokenType.INT8MULTIRANGE: 163>, <TokenType.NUMRANGE: 164>, <TokenType.NUMMULTIRANGE: 165>, <TokenType.TSRANGE: 166>, <TokenType.TSMULTIRANGE: 167>, <TokenType.TSTZRANGE: 168>, <TokenType.TSTZMULTIRANGE: 169>, <TokenType.DATERANGE: 170>, <TokenType.DATEMULTIRANGE: 171>, <TokenType.UUID: 172>, <TokenType.GEOGRAPHY: 173>, <TokenType.GEOGRAPHYPOINT: 174>, <TokenType.NULLABLE: 175>, <TokenType.GEOMETRY: 176>, <TokenType.POINT: 177>, <TokenType.RING: 178>, <TokenType.LINESTRING: 179>, <TokenType.LOCALTIME: 180>, <TokenType.LOCALTIMESTAMP: 181>, <TokenType.MULTILINESTRING: 183>, <TokenType.POLYGON: 184>, <TokenType.MULTIPOLYGON: 185>, <TokenType.HLLSKETCH: 186>, <TokenType.HSTORE: 187>, <TokenType.SUPER: 188>, <TokenType.SERIAL: 189>, <TokenType.SMALLSERIAL: 190>, <TokenType.BIGSERIAL: 191>, <TokenType.XML: 192>, <TokenType.YEAR: 193>, <TokenType.USERDEFINED: 194>, <TokenType.MONEY: 195>, <TokenType.SMALLMONEY: 196>, <TokenType.ROWVERSION: 197>, <TokenType.IMAGE: 198>, <TokenType.VARIANT: 199>, <TokenType.OBJECT: 200>, <TokenType.INET: 201>, <TokenType.IPADDRESS: 202>, <TokenType.IPPREFIX: 203>, <TokenType.IPV4: 204>, <TokenType.IPV6: 205>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>, <TokenType.ENUM16: 208>, <TokenType.FIXEDSTRING: 209>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>, <TokenType.TDIGEST: 214>, <TokenType.UNKNOWN: 215>, <TokenType.VECTOR: 216>, <TokenType.DYNAMIC: 217>, <TokenType.VOID: 218>, <TokenType.ALL: 221>, <TokenType.ANTI: 222>, <TokenType.ANY: 223>, <TokenType.APPLY: 224>, <TokenType.ARRAY: 225>, <TokenType.ASC: 226>, <TokenType.ASOF: 227>, <TokenType.ATTACH: 228>, <TokenType.AUTO_INCREMENT: 229>, <TokenType.BEGIN: 230>, <TokenType.CACHE: 233>, <TokenType.CASE: 234>, <TokenType.COLLATE: 237>, <TokenType.COMMAND: 238>, <TokenType.COMMENT: 239>, <TokenType.COMMIT: 240>, <TokenType.CONSTRAINT: 242>, <TokenType.COPY: 243>, <TokenType.CUBE: 246>, <TokenType.CURRENT_DATE: 247>, <TokenType.CURRENT_DATETIME: 248>, <TokenType.CURRENT_SCHEMA: 249>, <TokenType.CURRENT_TIME: 250>, <TokenType.CURRENT_TIMESTAMP: 251>, <TokenType.CURRENT_USER: 252>, <TokenType.CURRENT_ROLE: 254>, <TokenType.CURRENT_CATALOG: 255>, <TokenType.DECLARE: 256>, <TokenType.DEFAULT: 257>, <TokenType.DELETE: 258>, <TokenType.DESC: 259>, <TokenType.DESCRIBE: 260>, <TokenType.DETACH: 261>, <TokenType.DICTIONARY: 262>, <TokenType.DIV: 265>, <TokenType.END: 268>, <TokenType.ESCAPE: 269>, <TokenType.EXECUTE: 271>, <TokenType.EXISTS: 272>, <TokenType.FALSE: 273>, <TokenType.FILE: 275>, <TokenType.FILE_FORMAT: 276>, <TokenType.FILTER: 277>, <TokenType.FINAL: 278>, <TokenType.FIRST: 279>, <TokenType.FOREIGN_KEY: 282>, <TokenType.FORMAT: 283>, <TokenType.FULL: 285>, <TokenType.FUNCTION: 286>, <TokenType.GET: 287>, <TokenType.INDEX: 298>, <TokenType.INTERVAL: 305>, <TokenType.IS: 309>, <TokenType.ISNULL: 310>, <TokenType.KEEP: 313>, <TokenType.KILL: 315>, <TokenType.LEFT: 318>, <TokenType.LIMIT: 320>, <TokenType.LIST: 321>, <TokenType.LOAD: 322>, <TokenType.LOCK: 323>, <TokenType.MAP: 324>, <TokenType.MATCH: 325>, <TokenType.MERGE: 329>, <TokenType.MODEL: 331>, <TokenType.NATURAL: 332>, <TokenType.NEXT: 333>, <TokenType.NOTHING: 334>, <TokenType.NULL: 336>, <TokenType.OBJECT_IDENTIFIER: 337>, <TokenType.OFFSET: 338>, <TokenType.OPERATOR: 341>, <TokenType.ORDINALITY: 345>, <TokenType.OUT: 346>, <TokenType.INOUT: 347>, <TokenType.OVER: 349>, <TokenType.OVERLAPS: 350>, <TokenType.OVERWRITE: 351>, <TokenType.PARTITION: 353>, <TokenType.PASTE: 355>, <TokenType.PERCENT: 356>, <TokenType.PIVOT: 357>, <TokenType.PRAGMA: 362>, <TokenType.PROCEDURE: 365>, <TokenType.PROJECTION: 367>, <TokenType.PSEUDO_TYPE: 368>, <TokenType.PUT: 369>, <TokenType.RANGE: 373>, <TokenType.RECURSIVE: 374>, <TokenType.REFRESH: 375>, <TokenType.RENAME: 376>, <TokenType.REPLACE: 377>, <TokenType.REFERENCES: 380>, <TokenType.RIGHT: 381>, <TokenType.ROLLUP: 385>, <TokenType.ROW: 386>, <TokenType.ROWS: 387>, <TokenType.SEMI: 390>, <TokenType.SEQUENCE: 392>, <TokenType.SET: 394>, <TokenType.SETTINGS: 395>, <TokenType.SHOW: 396>, <TokenType.SOME: 398>, <TokenType.STORAGE_INTEGRATION: 403>, <TokenType.STRAIGHT_JOIN: 404>, <TokenType.STRUCT: 405>, <TokenType.TAG: 408>, <TokenType.TEMPORARY: 409>, <TokenType.TOP: 410>, <TokenType.TRUE: 412>, <TokenType.TRUNCATE: 413>, <TokenType.TRIGGER: 414>, <TokenType.TYPE: 415>, <TokenType.UNNEST: 419>, <TokenType.UNPIVOT: 420>, <TokenType.UPDATE: 421>, <TokenType.USE: 422>, <TokenType.VIEW: 426>, <TokenType.SEMANTIC_VIEW: 427>, <TokenType.VOLATILE: 428>, <TokenType.WINDOW: 432>, <TokenType.UNIQUE: 434>, <TokenType.SINK: 439>, <TokenType.SOURCE: 440>, <TokenType.ANALYZE: 441>, <TokenType.NAMESPACE: 442>, <TokenType.EXPORT: 443>}
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: 98>, <TokenType.BOOLEAN: 99>, <TokenType.TINYINT: 100>, <TokenType.UTINYINT: 101>, <TokenType.SMALLINT: 102>, <TokenType.USMALLINT: 103>, <TokenType.MEDIUMINT: 104>, <TokenType.UMEDIUMINT: 105>, <TokenType.INT: 106>, <TokenType.UINT: 107>, <TokenType.BIGINT: 108>, <TokenType.UBIGINT: 109>, <TokenType.BIGNUM: 110>, <TokenType.INT128: 111>, <TokenType.UINT128: 112>, <TokenType.INT256: 113>, <TokenType.UINT256: 114>, <TokenType.FLOAT: 115>, <TokenType.DOUBLE: 116>, <TokenType.UDOUBLE: 117>, <TokenType.DECIMAL: 118>, <TokenType.DECIMAL32: 119>, <TokenType.DECIMAL64: 120>, <TokenType.DECIMAL128: 121>, <TokenType.DECIMAL256: 122>, <TokenType.DECFLOAT: 123>, <TokenType.UDECIMAL: 124>, <TokenType.BIGDECIMAL: 125>, <TokenType.CHAR: 126>, <TokenType.NCHAR: 127>, <TokenType.VARCHAR: 128>, <TokenType.NVARCHAR: 129>, <TokenType.BPCHAR: 130>, <TokenType.TEXT: 131>, <TokenType.MEDIUMTEXT: 132>, <TokenType.LONGTEXT: 133>, <TokenType.BLOB: 134>, <TokenType.MEDIUMBLOB: 135>, <TokenType.LONGBLOB: 136>, <TokenType.TINYBLOB: 137>, <TokenType.TINYTEXT: 138>, <TokenType.NAME: 139>, <TokenType.BINARY: 140>, <TokenType.VARBINARY: 141>, <TokenType.JSON: 142>, <TokenType.JSONB: 143>, <TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIME_NS: 146>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>, <TokenType.TIMESTAMP_S: 151>, <TokenType.TIMESTAMP_MS: 152>, <TokenType.TIMESTAMP_NS: 153>, <TokenType.DATETIME: 154>, <TokenType.DATETIME2: 155>, <TokenType.DATETIME64: 156>, <TokenType.SMALLDATETIME: 157>, <TokenType.DATE: 158>, <TokenType.DATE32: 159>, <TokenType.INT4RANGE: 160>, <TokenType.INT4MULTIRANGE: 161>, <TokenType.INT8RANGE: 162>, <TokenType.INT8MULTIRANGE: 163>, <TokenType.NUMRANGE: 164>, <TokenType.NUMMULTIRANGE: 165>, <TokenType.TSRANGE: 166>, <TokenType.TSMULTIRANGE: 167>, <TokenType.TSTZRANGE: 168>, <TokenType.TSTZMULTIRANGE: 169>, <TokenType.DATERANGE: 170>, <TokenType.DATEMULTIRANGE: 171>, <TokenType.UUID: 172>, <TokenType.GEOGRAPHY: 173>, <TokenType.GEOGRAPHYPOINT: 174>, <TokenType.NULLABLE: 175>, <TokenType.GEOMETRY: 176>, <TokenType.POINT: 177>, <TokenType.RING: 178>, <TokenType.LINESTRING: 179>, <TokenType.LOCALTIME: 180>, <TokenType.LOCALTIMESTAMP: 181>, <TokenType.MULTILINESTRING: 183>, <TokenType.POLYGON: 184>, <TokenType.MULTIPOLYGON: 185>, <TokenType.HLLSKETCH: 186>, <TokenType.HSTORE: 187>, <TokenType.SUPER: 188>, <TokenType.SERIAL: 189>, <TokenType.SMALLSERIAL: 190>, <TokenType.BIGSERIAL: 191>, <TokenType.XML: 192>, <TokenType.YEAR: 193>, <TokenType.USERDEFINED: 194>, <TokenType.MONEY: 195>, <TokenType.SMALLMONEY: 196>, <TokenType.ROWVERSION: 197>, <TokenType.IMAGE: 198>, <TokenType.VARIANT: 199>, <TokenType.OBJECT: 200>, <TokenType.INET: 201>, <TokenType.IPADDRESS: 202>, <TokenType.IPPREFIX: 203>, <TokenType.IPV4: 204>, <TokenType.IPV6: 205>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>, <TokenType.ENUM16: 208>, <TokenType.FIXEDSTRING: 209>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>, <TokenType.TDIGEST: 214>, <TokenType.UNKNOWN: 215>, <TokenType.VECTOR: 216>, <TokenType.DYNAMIC: 217>, <TokenType.VOID: 218>, <TokenType.ALL: 221>, <TokenType.ANY: 223>, <TokenType.APPLY: 224>, <TokenType.ARRAY: 225>, <TokenType.ASC: 226>, <TokenType.ATTACH: 228>, <TokenType.AUTO_INCREMENT: 229>, <TokenType.BEGIN: 230>, <TokenType.CACHE: 233>, <TokenType.CASE: 234>, <TokenType.COLLATE: 237>, <TokenType.COMMAND: 238>, <TokenType.COMMENT: 239>, <TokenType.COMMIT: 240>, <TokenType.CONSTRAINT: 242>, <TokenType.COPY: 243>, <TokenType.CUBE: 246>, <TokenType.CURRENT_DATE: 247>, <TokenType.CURRENT_DATETIME: 248>, <TokenType.CURRENT_SCHEMA: 249>, <TokenType.CURRENT_TIME: 250>, <TokenType.CURRENT_TIMESTAMP: 251>, <TokenType.CURRENT_USER: 252>, <TokenType.CURRENT_ROLE: 254>, <TokenType.CURRENT_CATALOG: 255>, <TokenType.DECLARE: 256>, <TokenType.DEFAULT: 257>, <TokenType.DELETE: 258>, <TokenType.DESC: 259>, <TokenType.DESCRIBE: 260>, <TokenType.DETACH: 261>, <TokenType.DICTIONARY: 262>, <TokenType.DIV: 265>, <TokenType.END: 268>, <TokenType.ESCAPE: 269>, <TokenType.EXECUTE: 271>, <TokenType.EXISTS: 272>, <TokenType.FALSE: 273>, <TokenType.FILE: 275>, <TokenType.FILE_FORMAT: 276>, <TokenType.FILTER: 277>, <TokenType.FINAL: 278>, <TokenType.FIRST: 279>, <TokenType.FOREIGN_KEY: 282>, <TokenType.FORMAT: 283>, <TokenType.FUNCTION: 286>, <TokenType.GET: 287>, <TokenType.INDEX: 298>, <TokenType.INTERVAL: 305>, <TokenType.ISNULL: 310>, <TokenType.KEEP: 313>, <TokenType.KILL: 315>, <TokenType.LIMIT: 320>, <TokenType.LIST: 321>, <TokenType.LOAD: 322>, <TokenType.MAP: 324>, <TokenType.MATCH: 325>, <TokenType.MERGE: 329>, <TokenType.MODEL: 331>, <TokenType.NEXT: 333>, <TokenType.NOTHING: 334>, <TokenType.NULL: 336>, <TokenType.OBJECT_IDENTIFIER: 337>, <TokenType.OFFSET: 338>, <TokenType.OPERATOR: 341>, <TokenType.ORDINALITY: 345>, <TokenType.OUT: 346>, <TokenType.INOUT: 347>, <TokenType.OVER: 349>, <TokenType.OVERLAPS: 350>, <TokenType.OVERWRITE: 351>, <TokenType.PARTITION: 353>, <TokenType.PASTE: 355>, <TokenType.PERCENT: 356>, <TokenType.PIVOT: 357>, <TokenType.PRAGMA: 362>, <TokenType.PROCEDURE: 365>, <TokenType.PROJECTION: 367>, <TokenType.PSEUDO_TYPE: 368>, <TokenType.PUT: 369>, <TokenType.RANGE: 373>, <TokenType.RECURSIVE: 374>, <TokenType.REFRESH: 375>, <TokenType.RENAME: 376>, <TokenType.REPLACE: 377>, <TokenType.REFERENCES: 380>, <TokenType.ROLLUP: 385>, <TokenType.ROW: 386>, <TokenType.ROWS: 387>, <TokenType.SEQUENCE: 392>, <TokenType.SET: 394>, <TokenType.SETTINGS: 395>, <TokenType.SHOW: 396>, <TokenType.SOME: 398>, <TokenType.STORAGE_INTEGRATION: 403>, <TokenType.STRAIGHT_JOIN: 404>, <TokenType.STRUCT: 405>, <TokenType.TAG: 408>, <TokenType.TEMPORARY: 409>, <TokenType.TOP: 410>, <TokenType.TRUE: 412>, <TokenType.TRUNCATE: 413>, <TokenType.TRIGGER: 414>, <TokenType.TYPE: 415>, <TokenType.UNNEST: 419>, <TokenType.UNPIVOT: 420>, <TokenType.UPDATE: 421>, <TokenType.USE: 422>, <TokenType.VIEW: 426>, <TokenType.SEMANTIC_VIEW: 427>, <TokenType.VOLATILE: 428>, <TokenType.UNIQUE: 434>, <TokenType.SINK: 439>, <TokenType.SOURCE: 440>, <TokenType.ANALYZE: 441>, <TokenType.NAMESPACE: 442>, <TokenType.EXPORT: 443>}
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: 98>, <TokenType.BOOLEAN: 99>, <TokenType.TINYINT: 100>, <TokenType.UTINYINT: 101>, <TokenType.SMALLINT: 102>, <TokenType.USMALLINT: 103>, <TokenType.MEDIUMINT: 104>, <TokenType.UMEDIUMINT: 105>, <TokenType.INT: 106>, <TokenType.UINT: 107>, <TokenType.BIGINT: 108>, <TokenType.UBIGINT: 109>, <TokenType.BIGNUM: 110>, <TokenType.INT128: 111>, <TokenType.UINT128: 112>, <TokenType.INT256: 113>, <TokenType.UINT256: 114>, <TokenType.FLOAT: 115>, <TokenType.DOUBLE: 116>, <TokenType.UDOUBLE: 117>, <TokenType.DECIMAL: 118>, <TokenType.DECIMAL32: 119>, <TokenType.DECIMAL64: 120>, <TokenType.DECIMAL128: 121>, <TokenType.DECIMAL256: 122>, <TokenType.DECFLOAT: 123>, <TokenType.UDECIMAL: 124>, <TokenType.BIGDECIMAL: 125>, <TokenType.CHAR: 126>, <TokenType.NCHAR: 127>, <TokenType.VARCHAR: 128>, <TokenType.NVARCHAR: 129>, <TokenType.BPCHAR: 130>, <TokenType.TEXT: 131>, <TokenType.MEDIUMTEXT: 132>, <TokenType.LONGTEXT: 133>, <TokenType.BLOB: 134>, <TokenType.MEDIUMBLOB: 135>, <TokenType.LONGBLOB: 136>, <TokenType.TINYBLOB: 137>, <TokenType.TINYTEXT: 138>, <TokenType.NAME: 139>, <TokenType.BINARY: 140>, <TokenType.VARBINARY: 141>, <TokenType.JSON: 142>, <TokenType.JSONB: 143>, <TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIME_NS: 146>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>, <TokenType.TIMESTAMP_S: 151>, <TokenType.TIMESTAMP_MS: 152>, <TokenType.TIMESTAMP_NS: 153>, <TokenType.DATETIME: 154>, <TokenType.DATETIME2: 155>, <TokenType.DATETIME64: 156>, <TokenType.SMALLDATETIME: 157>, <TokenType.DATE: 158>, <TokenType.DATE32: 159>, <TokenType.INT4RANGE: 160>, <TokenType.INT4MULTIRANGE: 161>, <TokenType.INT8RANGE: 162>, <TokenType.INT8MULTIRANGE: 163>, <TokenType.NUMRANGE: 164>, <TokenType.NUMMULTIRANGE: 165>, <TokenType.TSRANGE: 166>, <TokenType.TSMULTIRANGE: 167>, <TokenType.TSTZRANGE: 168>, <TokenType.TSTZMULTIRANGE: 169>, <TokenType.DATERANGE: 170>, <TokenType.DATEMULTIRANGE: 171>, <TokenType.UUID: 172>, <TokenType.GEOGRAPHY: 173>, <TokenType.GEOGRAPHYPOINT: 174>, <TokenType.NULLABLE: 175>, <TokenType.GEOMETRY: 176>, <TokenType.POINT: 177>, <TokenType.RING: 178>, <TokenType.LINESTRING: 179>, <TokenType.LOCALTIME: 180>, <TokenType.LOCALTIMESTAMP: 181>, <TokenType.MULTILINESTRING: 183>, <TokenType.POLYGON: 184>, <TokenType.MULTIPOLYGON: 185>, <TokenType.HLLSKETCH: 186>, <TokenType.HSTORE: 187>, <TokenType.SUPER: 188>, <TokenType.SERIAL: 189>, <TokenType.SMALLSERIAL: 190>, <TokenType.BIGSERIAL: 191>, <TokenType.XML: 192>, <TokenType.YEAR: 193>, <TokenType.USERDEFINED: 194>, <TokenType.MONEY: 195>, <TokenType.SMALLMONEY: 196>, <TokenType.ROWVERSION: 197>, <TokenType.IMAGE: 198>, <TokenType.VARIANT: 199>, <TokenType.OBJECT: 200>, <TokenType.INET: 201>, <TokenType.IPADDRESS: 202>, <TokenType.IPPREFIX: 203>, <TokenType.IPV4: 204>, <TokenType.IPV6: 205>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>, <TokenType.ENUM16: 208>, <TokenType.FIXEDSTRING: 209>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>, <TokenType.TDIGEST: 214>, <TokenType.UNKNOWN: 215>, <TokenType.VECTOR: 216>, <TokenType.DYNAMIC: 217>, <TokenType.VOID: 218>, <TokenType.ALL: 221>, <TokenType.ANY: 223>, <TokenType.APPLY: 224>, <TokenType.ARRAY: 225>, <TokenType.ASC: 226>, <TokenType.ATTACH: 228>, <TokenType.AUTO_INCREMENT: 229>, <TokenType.BEGIN: 230>, <TokenType.CACHE: 233>, <TokenType.CASE: 234>, <TokenType.COLLATE: 237>, <TokenType.COMMAND: 238>, <TokenType.COMMENT: 239>, <TokenType.COMMIT: 240>, <TokenType.CONSTRAINT: 242>, <TokenType.COPY: 243>, <TokenType.CUBE: 246>, <TokenType.CURRENT_DATE: 247>, <TokenType.CURRENT_DATETIME: 248>, <TokenType.CURRENT_SCHEMA: 249>, <TokenType.CURRENT_TIME: 250>, <TokenType.CURRENT_TIMESTAMP: 251>, <TokenType.CURRENT_USER: 252>, <TokenType.CURRENT_ROLE: 254>, <TokenType.CURRENT_CATALOG: 255>, <TokenType.DECLARE: 256>, <TokenType.DEFAULT: 257>, <TokenType.DELETE: 258>, <TokenType.DESC: 259>, <TokenType.DESCRIBE: 260>, <TokenType.DETACH: 261>, <TokenType.DICTIONARY: 262>, <TokenType.DIV: 265>, <TokenType.END: 268>, <TokenType.ESCAPE: 269>, <TokenType.EXECUTE: 271>, <TokenType.EXISTS: 272>, <TokenType.FALSE: 273>, <TokenType.FILE: 275>, <TokenType.FILE_FORMAT: 276>, <TokenType.FILTER: 277>, <TokenType.FINAL: 278>, <TokenType.FIRST: 279>, <TokenType.FOREIGN_KEY: 282>, <TokenType.FORMAT: 283>, <TokenType.FUNCTION: 286>, <TokenType.GET: 287>, <TokenType.INDEX: 298>, <TokenType.INTERVAL: 305>, <TokenType.IS: 309>, <TokenType.ISNULL: 310>, <TokenType.KEEP: 313>, <TokenType.KILL: 315>, <TokenType.LIMIT: 320>, <TokenType.LIST: 321>, <TokenType.LOAD: 322>, <TokenType.MAP: 324>, <TokenType.MATCH: 325>, <TokenType.MERGE: 329>, <TokenType.MODEL: 331>, <TokenType.NEXT: 333>, <TokenType.NOTHING: 334>, <TokenType.NULL: 336>, <TokenType.OBJECT_IDENTIFIER: 337>, <TokenType.OFFSET: 338>, <TokenType.OPERATOR: 341>, <TokenType.ORDINALITY: 345>, <TokenType.OUT: 346>, <TokenType.INOUT: 347>, <TokenType.OVER: 349>, <TokenType.OVERLAPS: 350>, <TokenType.OVERWRITE: 351>, <TokenType.PARTITION: 353>, <TokenType.PASTE: 355>, <TokenType.PERCENT: 356>, <TokenType.PIVOT: 357>, <TokenType.PRAGMA: 362>, <TokenType.PROCEDURE: 365>, <TokenType.PROJECTION: 367>, <TokenType.PSEUDO_TYPE: 368>, <TokenType.PUT: 369>, <TokenType.RANGE: 373>, <TokenType.RECURSIVE: 374>, <TokenType.REFRESH: 375>, <TokenType.RENAME: 376>, <TokenType.REPLACE: 377>, <TokenType.REFERENCES: 380>, <TokenType.ROLLUP: 385>, <TokenType.ROW: 386>, <TokenType.ROWS: 387>, <TokenType.SEQUENCE: 392>, <TokenType.SETTINGS: 395>, <TokenType.SHOW: 396>, <TokenType.SOME: 398>, <TokenType.STORAGE_INTEGRATION: 403>, <TokenType.STRAIGHT_JOIN: 404>, <TokenType.STRUCT: 405>, <TokenType.TAG: 408>, <TokenType.TEMPORARY: 409>, <TokenType.TOP: 410>, <TokenType.TRUE: 412>, <TokenType.TRUNCATE: 413>, <TokenType.TRIGGER: 414>, <TokenType.TYPE: 415>, <TokenType.UNNEST: 419>, <TokenType.UNPIVOT: 420>, <TokenType.UPDATE: 421>, <TokenType.USE: 422>, <TokenType.VIEW: 426>, <TokenType.SEMANTIC_VIEW: 427>, <TokenType.VOLATILE: 428>, <TokenType.UNIQUE: 434>, <TokenType.SINK: 439>, <TokenType.SOURCE: 440>, <TokenType.ANALYZE: 441>, <TokenType.NAMESPACE: 442>, <TokenType.EXPORT: 443>}
TRIM_TYPES: ClassVar = {'BOTH', 'TRAILING', 'LEADING'}
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: 312>, <TokenType.COLON: 11>})
TABLE_POSTFIX_TOKENS: ClassVar[frozenset] = frozenset({<TokenType.L_PAREN: 1>, <TokenType.L_BRACKET: 3>, <TokenType.UNPIVOT: 420>, <TokenType.PIVOT: 357>, <TokenType.L_BRACE: 5>, <TokenType.TABLE_SAMPLE: 407>})
FUNC_TOKENS: ClassVar = {<TokenType.SESSION_USER: 61>, <TokenType.XOR: 66>, <TokenType.IDENTIFIER: 79>, <TokenType.TABLE: 84>, <TokenType.VAR: 89>, <TokenType.BIT: 98>, <TokenType.BOOLEAN: 99>, <TokenType.TINYINT: 100>, <TokenType.UTINYINT: 101>, <TokenType.SMALLINT: 102>, <TokenType.USMALLINT: 103>, <TokenType.MEDIUMINT: 104>, <TokenType.UMEDIUMINT: 105>, <TokenType.INT: 106>, <TokenType.UINT: 107>, <TokenType.BIGINT: 108>, <TokenType.UBIGINT: 109>, <TokenType.BIGNUM: 110>, <TokenType.INT128: 111>, <TokenType.UINT128: 112>, <TokenType.INT256: 113>, <TokenType.UINT256: 114>, <TokenType.FLOAT: 115>, <TokenType.DOUBLE: 116>, <TokenType.UDOUBLE: 117>, <TokenType.DECIMAL: 118>, <TokenType.DECIMAL32: 119>, <TokenType.DECIMAL64: 120>, <TokenType.DECIMAL128: 121>, <TokenType.DECIMAL256: 122>, <TokenType.DECFLOAT: 123>, <TokenType.UDECIMAL: 124>, <TokenType.BIGDECIMAL: 125>, <TokenType.CHAR: 126>, <TokenType.NCHAR: 127>, <TokenType.VARCHAR: 128>, <TokenType.NVARCHAR: 129>, <TokenType.BPCHAR: 130>, <TokenType.TEXT: 131>, <TokenType.MEDIUMTEXT: 132>, <TokenType.LONGTEXT: 133>, <TokenType.BLOB: 134>, <TokenType.MEDIUMBLOB: 135>, <TokenType.LONGBLOB: 136>, <TokenType.TINYBLOB: 137>, <TokenType.TINYTEXT: 138>, <TokenType.NAME: 139>, <TokenType.BINARY: 140>, <TokenType.VARBINARY: 141>, <TokenType.JSON: 142>, <TokenType.JSONB: 143>, <TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIME_NS: 146>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>, <TokenType.TIMESTAMP_S: 151>, <TokenType.TIMESTAMP_MS: 152>, <TokenType.TIMESTAMP_NS: 153>, <TokenType.DATETIME: 154>, <TokenType.DATETIME2: 155>, <TokenType.DATETIME64: 156>, <TokenType.SMALLDATETIME: 157>, <TokenType.DATE: 158>, <TokenType.DATE32: 159>, <TokenType.INT4RANGE: 160>, <TokenType.INT4MULTIRANGE: 161>, <TokenType.INT8RANGE: 162>, <TokenType.INT8MULTIRANGE: 163>, <TokenType.NUMRANGE: 164>, <TokenType.NUMMULTIRANGE: 165>, <TokenType.TSRANGE: 166>, <TokenType.TSMULTIRANGE: 167>, <TokenType.TSTZRANGE: 168>, <TokenType.TSTZMULTIRANGE: 169>, <TokenType.DATERANGE: 170>, <TokenType.DATEMULTIRANGE: 171>, <TokenType.UUID: 172>, <TokenType.GEOGRAPHY: 173>, <TokenType.GEOGRAPHYPOINT: 174>, <TokenType.NULLABLE: 175>, <TokenType.GEOMETRY: 176>, <TokenType.POINT: 177>, <TokenType.RING: 178>, <TokenType.LINESTRING: 179>, <TokenType.LOCALTIME: 180>, <TokenType.LOCALTIMESTAMP: 181>, <TokenType.MULTILINESTRING: 183>, <TokenType.POLYGON: 184>, <TokenType.MULTIPOLYGON: 185>, <TokenType.HLLSKETCH: 186>, <TokenType.HSTORE: 187>, <TokenType.SUPER: 188>, <TokenType.SERIAL: 189>, <TokenType.SMALLSERIAL: 190>, <TokenType.BIGSERIAL: 191>, <TokenType.XML: 192>, <TokenType.YEAR: 193>, <TokenType.USERDEFINED: 194>, <TokenType.MONEY: 195>, <TokenType.SMALLMONEY: 196>, <TokenType.ROWVERSION: 197>, <TokenType.IMAGE: 198>, <TokenType.VARIANT: 199>, <TokenType.OBJECT: 200>, <TokenType.INET: 201>, <TokenType.IPADDRESS: 202>, <TokenType.IPPREFIX: 203>, <TokenType.IPV4: 204>, <TokenType.IPV6: 205>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>, <TokenType.ENUM16: 208>, <TokenType.FIXEDSTRING: 209>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>, <TokenType.TDIGEST: 214>, <TokenType.UNKNOWN: 215>, <TokenType.VECTOR: 216>, <TokenType.DYNAMIC: 217>, <TokenType.VOID: 218>, <TokenType.ALL: 221>, <TokenType.ANY: 223>, <TokenType.ARRAY: 225>, <TokenType.COLLATE: 237>, <TokenType.COMMAND: 238>, <TokenType.CURRENT_DATE: 247>, <TokenType.CURRENT_DATETIME: 248>, <TokenType.CURRENT_SCHEMA: 249>, <TokenType.CURRENT_TIME: 250>, <TokenType.CURRENT_TIMESTAMP: 251>, <TokenType.CURRENT_USER: 252>, <TokenType.CURRENT_CATALOG: 255>, <TokenType.DECLARE: 256>, <TokenType.EXISTS: 272>, <TokenType.FILTER: 277>, <TokenType.FIRST: 279>, <TokenType.FORMAT: 283>, <TokenType.GET: 287>, <TokenType.GLOB: 288>, <TokenType.ILIKE: 296>, <TokenType.INDEX: 298>, <TokenType.INSERT: 301>, <TokenType.INTERVAL: 305>, <TokenType.ISNULL: 310>, <TokenType.LEFT: 318>, <TokenType.LIKE: 319>, <TokenType.LIST: 321>, <TokenType.MAP: 324>, <TokenType.MERGE: 329>, <TokenType.NEXT: 333>, <TokenType.NOTHING: 334>, <TokenType.NULL: 336>, <TokenType.OBJECT_IDENTIFIER: 337>, <TokenType.OFFSET: 338>, <TokenType.PRIMARY_KEY: 364>, <TokenType.PSEUDO_TYPE: 368>, <TokenType.RANGE: 373>, <TokenType.REPLACE: 377>, <TokenType.RIGHT: 381>, <TokenType.RLIKE: 382>, <TokenType.ROW: 386>, <TokenType.SEQUENCE: 392>, <TokenType.SOME: 398>, <TokenType.STRUCT: 405>, <TokenType.TRUNCATE: 413>, <TokenType.UNION: 418>, <TokenType.UNNEST: 419>, <TokenType.WINDOW: 432>, <TokenType.UTC_DATE: 435>, <TokenType.UTC_TIME: 436>, <TokenType.UTC_TIMESTAMP: 437>}
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: 237>: <class 'sqlglot.expressions.functions.Collate'>}
FACTOR: ClassVar = {<TokenType.DIV: 265>: <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: 330>: <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.TIME: 144>, <TokenType.TIMETZ: 145>}
TIMESTAMPS: ClassVar = {<TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>}
SET_OPERATIONS: ClassVar = {<TokenType.INTERSECT: 304>, <TokenType.UNION: 418>, <TokenType.EXCEPT: 270>}
JOIN_METHODS: ClassVar = {<TokenType.POSITIONAL: 361>, <TokenType.ASOF: 227>, <TokenType.NATURAL: 332>}
JOIN_SIDES: ClassVar = {<TokenType.FULL: 285>, <TokenType.RIGHT: 381>, <TokenType.LEFT: 318>}
JOIN_KINDS: ClassVar = {<TokenType.SEMI: 390>, <TokenType.INNER: 300>, <TokenType.STRAIGHT_JOIN: 404>, <TokenType.CROSS: 245>, <TokenType.OUTER: 348>, <TokenType.ANTI: 222>}
JOIN_HINTS: ClassVar[set[str]] = set()
TABLE_TERMINATORS: ClassVar[frozenset] = frozenset({<TokenType.LIMIT: 320>, <TokenType.R_PAREN: 2>, <TokenType.SEMI: 390>, <TokenType.COMMA: 7>, <TokenType.NATURAL: 332>, <TokenType.EXCEPT: 270>, <TokenType.ON: 339>, <TokenType.SEMICOLON: 19>, <TokenType.STRAIGHT_JOIN: 404>, <TokenType.ORDER_BY: 342>, <TokenType.OUTER: 348>, <TokenType.FULL: 285>, <TokenType.ANTI: 222>, <TokenType.UNION: 418>, <TokenType.GROUP_BY: 291>, <TokenType.ASOF: 227>, <TokenType.HAVING: 293>, <TokenType.POSITIONAL: 361>, <TokenType.INNER: 300>, <TokenType.WHERE: 431>, <TokenType.INTERSECT: 304>, <TokenType.RIGHT: 381>, <TokenType.CROSS: 245>, <TokenType.JOIN: 311>, <TokenType.SENTINEL: 445>, <TokenType.LEFT: 318>})
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: 358>: <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: 220>: <function Parser.<lambda>>, <TokenType.ANALYZE: 441>: <function Parser.<lambda>>, <TokenType.BEGIN: 230>: <function Parser.<lambda>>, <TokenType.CACHE: 233>: <function Parser.<lambda>>, <TokenType.COMMENT: 239>: <function Parser.<lambda>>, <TokenType.COMMIT: 240>: <function Parser.<lambda>>, <TokenType.COPY: 243>: <function Parser.<lambda>>, <TokenType.CREATE: 244>: <function Parser.<lambda>>, <TokenType.DECLARE: 256>: <function Parser.<lambda>>, <TokenType.DELETE: 258>: <function Parser.<lambda>>, <TokenType.DESC: 259>: <function Parser.<lambda>>, <TokenType.DESCRIBE: 260>: <function Parser.<lambda>>, <TokenType.DROP: 266>: <function Parser.<lambda>>, <TokenType.GRANT: 290>: <function Parser.<lambda>>, <TokenType.REVOKE: 379>: <function Parser.<lambda>>, <TokenType.INSERT: 301>: <function Parser.<lambda>>, <TokenType.KILL: 315>: <function Parser.<lambda>>, <TokenType.LOAD: 322>: <function Parser.<lambda>>, <TokenType.MERGE: 329>: <function Parser.<lambda>>, <TokenType.PIVOT: 357>: <function Parser.<lambda>>, <TokenType.PRAGMA: 362>: <function Parser.<lambda>>, <TokenType.REFRESH: 375>: <function Parser.<lambda>>, <TokenType.ROLLBACK: 384>: <function Parser.<lambda>>, <TokenType.SET: 394>: <function Parser.<lambda>>, <TokenType.TRUNCATE: 413>: <function Parser.<lambda>>, <TokenType.UNCACHE: 416>: <function Parser.<lambda>>, <TokenType.UNPIVOT: 420>: <function Parser.<lambda>>, <TokenType.UPDATE: 421>: <function Parser.<lambda>>, <TokenType.USE: 422>: <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: 96>: <function Parser.<lambda>>, <TokenType.NATIONAL_STRING: 94>: <function Parser.<dictcomp>.<lambda>>, <TokenType.NATIONAL_RAW_STRING: 93>: <function Parser.<dictcomp>.<lambda>>, <TokenType.RAW_STRING: 95>: <function Parser.<lambda>>, <TokenType.STRING: 77>: <function Parser.<lambda>>, <TokenType.UNICODE_STRING: 97>: <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: 96>: <function Parser.<lambda>>, <TokenType.NATIONAL_STRING: 94>: <function Parser.<dictcomp>.<lambda>>, <TokenType.NATIONAL_RAW_STRING: 93>: <function Parser.<dictcomp>.<lambda>>, <TokenType.RAW_STRING: 95>: <function Parser.<lambda>>, <TokenType.STRING: 77>: <function Parser.<lambda>>, <TokenType.UNICODE_STRING: 97>: <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: 307>: <function Parser.<lambda>>, <TokenType.NULL: 336>: <function Parser.<lambda>>, <TokenType.TRUE: 412>: <function Parser.<lambda>>, <TokenType.FALSE: 273>: <function Parser.<lambda>>, <TokenType.SESSION_PARAMETER: 60>: <function Parser.<lambda>>, <TokenType.STAR: 20>: <function Parser.<lambda>>}
PLACEHOLDER_PARSERS: ClassVar = {<TokenType.PLACEHOLDER: 358>: <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: 231>: <function Parser.<lambda>>, <TokenType.GLOB: 288>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.ILIKE: 296>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.IN: 297>: <function Parser.<lambda>>, <TokenType.IRLIKE: 308>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.IS: 309>: <function Parser.<lambda>>, <TokenType.LIKE: 319>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.LT_AT: 55>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.OVERLAPS: 350>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.RLIKE: 382>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.SIMILAR_TO: 397>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.FOR: 280>: <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: 341>: <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>, <TokenType.CARET_AT: 43>: <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', 'PRIMARY KEY', 'FOREIGN KEY', 'BUCKET', 'PERIOD', 'LIKE', 'CHECK', 'EXCLUDE', 'TRUNCATE'}
RESERVED_CONSTRAINT_KINDS: ClassVar[set[str]] = set()
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>>, 'WEIGHT_STRING': <function Parser.<lambda>>, 'XMLELEMENT': <function Parser.<lambda>>, 'XMLTABLE': <function Parser.<lambda>>}
QUERY_MODIFIER_PARSERS: ClassVar = {<TokenType.MATCH_RECOGNIZE: 327>: <function Parser.<lambda>>, <TokenType.PREWHERE: 363>: <function Parser.<lambda>>, <TokenType.WHERE: 431>: <function Parser.<lambda>>, <TokenType.GROUP_BY: 291>: <function Parser.<lambda>>, <TokenType.HAVING: 293>: <function Parser.<lambda>>, <TokenType.QUALIFY: 370>: <function Parser.<lambda>>, <TokenType.WINDOW: 432>: <function Parser.<lambda>>, <TokenType.ORDER_BY: 342>: <function Parser.<lambda>>, <TokenType.LIMIT: 320>: <function Parser.<lambda>>, <TokenType.FETCH: 274>: <function Parser.<lambda>>, <TokenType.OFFSET: 338>: <function Parser.<lambda>>, <TokenType.FOR: 280>: <function Parser.<lambda>>, <TokenType.LOCK: 323>: <function Parser.<lambda>>, <TokenType.TABLE_SAMPLE: 407>: <function Parser.<lambda>>, <TokenType.USING: 423>: <function Parser.<lambda>>, <TokenType.CLUSTER_BY: 236>: <function Parser.<lambda>>, <TokenType.DISTRIBUTE_BY: 264>: <function Parser.<lambda>>, <TokenType.SORT_BY: 399>: <function Parser.<lambda>>, <TokenType.CONNECT_BY: 241>: <function Parser.<lambda>>}
QUERY_MODIFIER_TOKENS: ClassVar = {<TokenType.LIMIT: 320>, <TokenType.LOCK: 323>, <TokenType.MATCH_RECOGNIZE: 327>, <TokenType.DISTRIBUTE_BY: 264>, <TokenType.SORT_BY: 399>, <TokenType.FETCH: 274>, <TokenType.OFFSET: 338>, <TokenType.ORDER_BY: 342>, <TokenType.TABLE_SAMPLE: 407>, <TokenType.FOR: 280>, <TokenType.GROUP_BY: 291>, <TokenType.HAVING: 293>, <TokenType.USING: 423>, <TokenType.PREWHERE: 363>, <TokenType.CLUSTER_BY: 236>, <TokenType.WHERE: 431>, <TokenType.WINDOW: 432>, <TokenType.CONNECT_BY: 241>, <TokenType.QUALIFY: 370>}
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.WITH: 433>, <TokenType.SELECT: 389>, <TokenType.L_PAREN: 1>}
PRE_VOLATILE_TOKENS: ClassVar = {<TokenType.REPLACE: 377>, <TokenType.UNIQUE: 434>, <TokenType.CREATE: 244>}
TRANSACTION_KIND: ClassVar = {'IMMEDIATE', 'DEFERRED', 'EXCLUSIVE'}
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 = {'ROLLBACK', 'FAIL', 'REPLACE', 'ABORT', 'IGNORE'}
CLONE_KEYWORDS: ClassVar = {'CLONE', 'COPY'}
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', 'STREAM', 'TIMESTAMP', 'VERSION', 'OFFSET'}
OPCLASS_FOLLOW_KEYWORDS: ClassVar = {'NULLS', 'DESC', 'WITH', 'ASC'}
OPTYPE_FOLLOW_TOKENS: ClassVar = {<TokenType.R_PAREN: 2>, <TokenType.COMMA: 7>}
TABLE_INDEX_HINT_TOKENS: ClassVar = {<TokenType.FORCE: 281>, <TokenType.USE: 422>, <TokenType.IGNORE: 295>}
VIEW_ATTRIBUTES: ClassVar = {'VIEW_METADATA', 'SCHEMABINDING', 'ENCRYPTION'}
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: 98>, <TokenType.BOOLEAN: 99>, <TokenType.TINYINT: 100>, <TokenType.UTINYINT: 101>, <TokenType.SMALLINT: 102>, <TokenType.USMALLINT: 103>, <TokenType.MEDIUMINT: 104>, <TokenType.UMEDIUMINT: 105>, <TokenType.INT: 106>, <TokenType.UINT: 107>, <TokenType.BIGINT: 108>, <TokenType.UBIGINT: 109>, <TokenType.BIGNUM: 110>, <TokenType.INT128: 111>, <TokenType.UINT128: 112>, <TokenType.INT256: 113>, <TokenType.UINT256: 114>, <TokenType.FLOAT: 115>, <TokenType.DOUBLE: 116>, <TokenType.UDOUBLE: 117>, <TokenType.DECIMAL: 118>, <TokenType.DECIMAL32: 119>, <TokenType.DECIMAL64: 120>, <TokenType.DECIMAL128: 121>, <TokenType.DECIMAL256: 122>, <TokenType.DECFLOAT: 123>, <TokenType.UDECIMAL: 124>, <TokenType.BIGDECIMAL: 125>, <TokenType.CHAR: 126>, <TokenType.NCHAR: 127>, <TokenType.VARCHAR: 128>, <TokenType.NVARCHAR: 129>, <TokenType.BPCHAR: 130>, <TokenType.TEXT: 131>, <TokenType.MEDIUMTEXT: 132>, <TokenType.LONGTEXT: 133>, <TokenType.BLOB: 134>, <TokenType.MEDIUMBLOB: 135>, <TokenType.LONGBLOB: 136>, <TokenType.TINYBLOB: 137>, <TokenType.TINYTEXT: 138>, <TokenType.NAME: 139>, <TokenType.BINARY: 140>, <TokenType.VARBINARY: 141>, <TokenType.JSON: 142>, <TokenType.JSONB: 143>, <TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIME_NS: 146>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>, <TokenType.TIMESTAMP_S: 151>, <TokenType.TIMESTAMP_MS: 152>, <TokenType.TIMESTAMP_NS: 153>, <TokenType.DATETIME: 154>, <TokenType.DATETIME2: 155>, <TokenType.DATETIME64: 156>, <TokenType.SMALLDATETIME: 157>, <TokenType.DATE: 158>, <TokenType.DATE32: 159>, <TokenType.INT4RANGE: 160>, <TokenType.INT4MULTIRANGE: 161>, <TokenType.INT8RANGE: 162>, <TokenType.INT8MULTIRANGE: 163>, <TokenType.NUMRANGE: 164>, <TokenType.NUMMULTIRANGE: 165>, <TokenType.TSRANGE: 166>, <TokenType.TSMULTIRANGE: 167>, <TokenType.TSTZRANGE: 168>, <TokenType.TSTZMULTIRANGE: 169>, <TokenType.DATERANGE: 170>, <TokenType.DATEMULTIRANGE: 171>, <TokenType.UUID: 172>, <TokenType.GEOGRAPHY: 173>, <TokenType.GEOGRAPHYPOINT: 174>, <TokenType.NULLABLE: 175>, <TokenType.GEOMETRY: 176>, <TokenType.POINT: 177>, <TokenType.RING: 178>, <TokenType.LINESTRING: 179>, <TokenType.LOCALTIME: 180>, <TokenType.LOCALTIMESTAMP: 181>, <TokenType.MULTILINESTRING: 183>, <TokenType.POLYGON: 184>, <TokenType.MULTIPOLYGON: 185>, <TokenType.HLLSKETCH: 186>, <TokenType.HSTORE: 187>, <TokenType.SUPER: 188>, <TokenType.SERIAL: 189>, <TokenType.SMALLSERIAL: 190>, <TokenType.BIGSERIAL: 191>, <TokenType.XML: 192>, <TokenType.YEAR: 193>, <TokenType.USERDEFINED: 194>, <TokenType.MONEY: 195>, <TokenType.SMALLMONEY: 196>, <TokenType.ROWVERSION: 197>, <TokenType.IMAGE: 198>, <TokenType.VARIANT: 199>, <TokenType.OBJECT: 200>, <TokenType.INET: 201>, <TokenType.IPADDRESS: 202>, <TokenType.IPPREFIX: 203>, <TokenType.IPV4: 204>, <TokenType.IPV6: 205>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>, <TokenType.ENUM16: 208>, <TokenType.FIXEDSTRING: 209>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>, <TokenType.TDIGEST: 214>, <TokenType.UNKNOWN: 215>, <TokenType.VECTOR: 216>, <TokenType.DYNAMIC: 217>, <TokenType.VOID: 218>, <TokenType.ALL: 221>, <TokenType.ANTI: 222>, <TokenType.ANY: 223>, <TokenType.APPLY: 224>, <TokenType.ARRAY: 225>, <TokenType.ASC: 226>, <TokenType.ASOF: 227>, <TokenType.ATTACH: 228>, <TokenType.AUTO_INCREMENT: 229>, <TokenType.BEGIN: 230>, <TokenType.CACHE: 233>, <TokenType.CASE: 234>, <TokenType.COLLATE: 237>, <TokenType.COMMAND: 238>, <TokenType.COMMENT: 239>, <TokenType.COMMIT: 240>, <TokenType.CONSTRAINT: 242>, <TokenType.COPY: 243>, <TokenType.CUBE: 246>, <TokenType.CURRENT_DATE: 247>, <TokenType.CURRENT_DATETIME: 248>, <TokenType.CURRENT_SCHEMA: 249>, <TokenType.CURRENT_TIME: 250>, <TokenType.CURRENT_TIMESTAMP: 251>, <TokenType.CURRENT_USER: 252>, <TokenType.CURRENT_ROLE: 254>, <TokenType.CURRENT_CATALOG: 255>, <TokenType.DECLARE: 256>, <TokenType.DEFAULT: 257>, <TokenType.DELETE: 258>, <TokenType.DESC: 259>, <TokenType.DESCRIBE: 260>, <TokenType.DETACH: 261>, <TokenType.DICTIONARY: 262>, <TokenType.DIV: 265>, <TokenType.END: 268>, <TokenType.ESCAPE: 269>, <TokenType.EXECUTE: 271>, <TokenType.EXISTS: 272>, <TokenType.FALSE: 273>, <TokenType.FILE: 275>, <TokenType.FILE_FORMAT: 276>, <TokenType.FILTER: 277>, <TokenType.FINAL: 278>, <TokenType.FIRST: 279>, <TokenType.FOREIGN_KEY: 282>, <TokenType.FORMAT: 283>, <TokenType.FULL: 285>, <TokenType.FUNCTION: 286>, <TokenType.GET: 287>, <TokenType.INDEX: 298>, <TokenType.INTERVAL: 305>, <TokenType.IS: 309>, <TokenType.ISNULL: 310>, <TokenType.KEEP: 313>, <TokenType.KILL: 315>, <TokenType.LEFT: 318>, <TokenType.LIMIT: 320>, <TokenType.LIST: 321>, <TokenType.LOAD: 322>, <TokenType.LOCK: 323>, <TokenType.MAP: 324>, <TokenType.MATCH: 325>, <TokenType.MERGE: 329>, <TokenType.MODEL: 331>, <TokenType.NATURAL: 332>, <TokenType.NEXT: 333>, <TokenType.NOTHING: 334>, <TokenType.NULL: 336>, <TokenType.OBJECT_IDENTIFIER: 337>, <TokenType.OFFSET: 338>, <TokenType.OPERATOR: 341>, <TokenType.ORDINALITY: 345>, <TokenType.OUT: 346>, <TokenType.INOUT: 347>, <TokenType.OVER: 349>, <TokenType.OVERLAPS: 350>, <TokenType.OVERWRITE: 351>, <TokenType.PARTITION: 353>, <TokenType.PASTE: 355>, <TokenType.PERCENT: 356>, <TokenType.PIVOT: 357>, <TokenType.PRAGMA: 362>, <TokenType.PROCEDURE: 365>, <TokenType.PROJECTION: 367>, <TokenType.PSEUDO_TYPE: 368>, <TokenType.PUT: 369>, <TokenType.RECURSIVE: 374>, <TokenType.REFRESH: 375>, <TokenType.RENAME: 376>, <TokenType.REPLACE: 377>, <TokenType.REFERENCES: 380>, <TokenType.RIGHT: 381>, <TokenType.ROLLUP: 385>, <TokenType.ROW: 386>, <TokenType.SEMI: 390>, <TokenType.SEQUENCE: 392>, <TokenType.SET: 394>, <TokenType.SETTINGS: 395>, <TokenType.SHOW: 396>, <TokenType.SOME: 398>, <TokenType.STORAGE_INTEGRATION: 403>, <TokenType.STRAIGHT_JOIN: 404>, <TokenType.STRUCT: 405>, <TokenType.TAG: 408>, <TokenType.TEMPORARY: 409>, <TokenType.TOP: 410>, <TokenType.TRUE: 412>, <TokenType.TRUNCATE: 413>, <TokenType.TRIGGER: 414>, <TokenType.TYPE: 415>, <TokenType.UNNEST: 419>, <TokenType.UNPIVOT: 420>, <TokenType.UPDATE: 421>, <TokenType.USE: 422>, <TokenType.VIEW: 426>, <TokenType.SEMANTIC_VIEW: 427>, <TokenType.VOLATILE: 428>, <TokenType.WINDOW: 432>, <TokenType.UNIQUE: 434>, <TokenType.SINK: 439>, <TokenType.SOURCE: 440>, <TokenType.ANALYZE: 441>, <TokenType.NAMESPACE: 442>, <TokenType.EXPORT: 443>}
WINDOW_BEFORE_PAREN_TOKENS: ClassVar = {<TokenType.OVER: 349>}
WINDOW_SIDES: ClassVar = {'FOLLOWING', 'PRECEDING'}
JSON_KEY_VALUE_SEPARATOR_TOKENS: ClassVar = {<TokenType.COLON: 11>, <TokenType.IS: 309>, <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: 98>, <TokenType.BOOLEAN: 99>, <TokenType.TINYINT: 100>, <TokenType.UTINYINT: 101>, <TokenType.SMALLINT: 102>, <TokenType.USMALLINT: 103>, <TokenType.MEDIUMINT: 104>, <TokenType.UMEDIUMINT: 105>, <TokenType.INT: 106>, <TokenType.UINT: 107>, <TokenType.BIGINT: 108>, <TokenType.UBIGINT: 109>, <TokenType.BIGNUM: 110>, <TokenType.INT128: 111>, <TokenType.UINT128: 112>, <TokenType.INT256: 113>, <TokenType.UINT256: 114>, <TokenType.FLOAT: 115>, <TokenType.DOUBLE: 116>, <TokenType.UDOUBLE: 117>, <TokenType.DECIMAL: 118>, <TokenType.DECIMAL32: 119>, <TokenType.DECIMAL64: 120>, <TokenType.DECIMAL128: 121>, <TokenType.DECIMAL256: 122>, <TokenType.DECFLOAT: 123>, <TokenType.UDECIMAL: 124>, <TokenType.BIGDECIMAL: 125>, <TokenType.CHAR: 126>, <TokenType.NCHAR: 127>, <TokenType.VARCHAR: 128>, <TokenType.NVARCHAR: 129>, <TokenType.BPCHAR: 130>, <TokenType.TEXT: 131>, <TokenType.MEDIUMTEXT: 132>, <TokenType.LONGTEXT: 133>, <TokenType.BLOB: 134>, <TokenType.MEDIUMBLOB: 135>, <TokenType.LONGBLOB: 136>, <TokenType.TINYBLOB: 137>, <TokenType.TINYTEXT: 138>, <TokenType.NAME: 139>, <TokenType.BINARY: 140>, <TokenType.VARBINARY: 141>, <TokenType.JSON: 142>, <TokenType.JSONB: 143>, <TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIME_NS: 146>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>, <TokenType.TIMESTAMP_S: 151>, <TokenType.TIMESTAMP_MS: 152>, <TokenType.TIMESTAMP_NS: 153>, <TokenType.DATETIME: 154>, <TokenType.DATETIME2: 155>, <TokenType.DATETIME64: 156>, <TokenType.SMALLDATETIME: 157>, <TokenType.DATE: 158>, <TokenType.DATE32: 159>, <TokenType.INT4RANGE: 160>, <TokenType.INT4MULTIRANGE: 161>, <TokenType.INT8RANGE: 162>, <TokenType.INT8MULTIRANGE: 163>, <TokenType.NUMRANGE: 164>, <TokenType.NUMMULTIRANGE: 165>, <TokenType.TSRANGE: 166>, <TokenType.TSMULTIRANGE: 167>, <TokenType.TSTZRANGE: 168>, <TokenType.TSTZMULTIRANGE: 169>, <TokenType.DATERANGE: 170>, <TokenType.DATEMULTIRANGE: 171>, <TokenType.UUID: 172>, <TokenType.GEOGRAPHY: 173>, <TokenType.GEOGRAPHYPOINT: 174>, <TokenType.NULLABLE: 175>, <TokenType.GEOMETRY: 176>, <TokenType.POINT: 177>, <TokenType.RING: 178>, <TokenType.LINESTRING: 179>, <TokenType.LOCALTIME: 180>, <TokenType.LOCALTIMESTAMP: 181>, <TokenType.MULTILINESTRING: 183>, <TokenType.POLYGON: 184>, <TokenType.MULTIPOLYGON: 185>, <TokenType.HLLSKETCH: 186>, <TokenType.HSTORE: 187>, <TokenType.SUPER: 188>, <TokenType.SERIAL: 189>, <TokenType.SMALLSERIAL: 190>, <TokenType.BIGSERIAL: 191>, <TokenType.XML: 192>, <TokenType.YEAR: 193>, <TokenType.USERDEFINED: 194>, <TokenType.MONEY: 195>, <TokenType.SMALLMONEY: 196>, <TokenType.ROWVERSION: 197>, <TokenType.IMAGE: 198>, <TokenType.VARIANT: 199>, <TokenType.OBJECT: 200>, <TokenType.INET: 201>, <TokenType.IPADDRESS: 202>, <TokenType.IPPREFIX: 203>, <TokenType.IPV4: 204>, <TokenType.IPV6: 205>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>, <TokenType.ENUM16: 208>, <TokenType.FIXEDSTRING: 209>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>, <TokenType.TDIGEST: 214>, <TokenType.UNKNOWN: 215>, <TokenType.VECTOR: 216>, <TokenType.DYNAMIC: 217>, <TokenType.VOID: 218>, <TokenType.ALL: 221>, <TokenType.ANTI: 222>, <TokenType.ANY: 223>, <TokenType.APPLY: 224>, <TokenType.ARRAY: 225>, <TokenType.ASC: 226>, <TokenType.ASOF: 227>, <TokenType.ATTACH: 228>, <TokenType.AUTO_INCREMENT: 229>, <TokenType.BEGIN: 230>, <TokenType.CACHE: 233>, <TokenType.CASE: 234>, <TokenType.COLLATE: 237>, <TokenType.COMMAND: 238>, <TokenType.COMMENT: 239>, <TokenType.COMMIT: 240>, <TokenType.CONSTRAINT: 242>, <TokenType.COPY: 243>, <TokenType.CUBE: 246>, <TokenType.CURRENT_DATE: 247>, <TokenType.CURRENT_DATETIME: 248>, <TokenType.CURRENT_SCHEMA: 249>, <TokenType.CURRENT_TIME: 250>, <TokenType.CURRENT_TIMESTAMP: 251>, <TokenType.CURRENT_USER: 252>, <TokenType.CURRENT_ROLE: 254>, <TokenType.CURRENT_CATALOG: 255>, <TokenType.DECLARE: 256>, <TokenType.DEFAULT: 257>, <TokenType.DELETE: 258>, <TokenType.DESC: 259>, <TokenType.DESCRIBE: 260>, <TokenType.DETACH: 261>, <TokenType.DICTIONARY: 262>, <TokenType.DIV: 265>, <TokenType.END: 268>, <TokenType.ESCAPE: 269>, <TokenType.EXECUTE: 271>, <TokenType.EXISTS: 272>, <TokenType.FALSE: 273>, <TokenType.FILE: 275>, <TokenType.FILE_FORMAT: 276>, <TokenType.FILTER: 277>, <TokenType.FINAL: 278>, <TokenType.FIRST: 279>, <TokenType.FOREIGN_KEY: 282>, <TokenType.FORMAT: 283>, <TokenType.FULL: 285>, <TokenType.FUNCTION: 286>, <TokenType.GET: 287>, <TokenType.INDEX: 298>, <TokenType.INTERVAL: 305>, <TokenType.IS: 309>, <TokenType.ISNULL: 310>, <TokenType.KEEP: 313>, <TokenType.KILL: 315>, <TokenType.LEFT: 318>, <TokenType.LIMIT: 320>, <TokenType.LIST: 321>, <TokenType.LOAD: 322>, <TokenType.LOCK: 323>, <TokenType.MAP: 324>, <TokenType.MATCH: 325>, <TokenType.MERGE: 329>, <TokenType.MODEL: 331>, <TokenType.NATURAL: 332>, <TokenType.NEXT: 333>, <TokenType.NOTHING: 334>, <TokenType.NULL: 336>, <TokenType.OBJECT_IDENTIFIER: 337>, <TokenType.OFFSET: 338>, <TokenType.OPERATOR: 341>, <TokenType.ORDINALITY: 345>, <TokenType.OUT: 346>, <TokenType.INOUT: 347>, <TokenType.OVER: 349>, <TokenType.OVERLAPS: 350>, <TokenType.OVERWRITE: 351>, <TokenType.PARTITION: 353>, <TokenType.PASTE: 355>, <TokenType.PIVOT: 357>, <TokenType.PRAGMA: 362>, <TokenType.PROCEDURE: 365>, <TokenType.PROJECTION: 367>, <TokenType.PSEUDO_TYPE: 368>, <TokenType.PUT: 369>, <TokenType.RANGE: 373>, <TokenType.RECURSIVE: 374>, <TokenType.REFRESH: 375>, <TokenType.RENAME: 376>, <TokenType.REPLACE: 377>, <TokenType.REFERENCES: 380>, <TokenType.RIGHT: 381>, <TokenType.ROLLUP: 385>, <TokenType.SEMI: 390>, <TokenType.SEQUENCE: 392>, <TokenType.SET: 394>, <TokenType.SETTINGS: 395>, <TokenType.SHOW: 396>, <TokenType.SOME: 398>, <TokenType.STORAGE_INTEGRATION: 403>, <TokenType.STRAIGHT_JOIN: 404>, <TokenType.STRUCT: 405>, <TokenType.TAG: 408>, <TokenType.TEMPORARY: 409>, <TokenType.TOP: 410>, <TokenType.TRUE: 412>, <TokenType.TRUNCATE: 413>, <TokenType.TRIGGER: 414>, <TokenType.TYPE: 415>, <TokenType.UNNEST: 419>, <TokenType.UNPIVOT: 420>, <TokenType.UPDATE: 421>, <TokenType.USE: 422>, <TokenType.VIEW: 426>, <TokenType.SEMANTIC_VIEW: 427>, <TokenType.VOLATILE: 428>, <TokenType.WINDOW: 432>, <TokenType.UNIQUE: 434>, <TokenType.SINK: 439>, <TokenType.SOURCE: 440>, <TokenType.ANALYZE: 441>, <TokenType.NAMESPACE: 442>, <TokenType.EXPORT: 443>}
ADD_CONSTRAINT_TOKENS: ClassVar = {<TokenType.INDEX: 298>, <TokenType.PRIMARY_KEY: 364>, <TokenType.KEY: 314>, <TokenType.CONSTRAINT: 242>, <TokenType.UNIQUE: 434>, <TokenType.FOREIGN_KEY: 282>}
ADD_CONSTRAINT_KEYWORDS: ClassVar[set[str]] = set()
DISTINCT_TOKENS: ClassVar = {<TokenType.DISTINCT: 263>}
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: 98>, <TokenType.BOOLEAN: 99>, <TokenType.TINYINT: 100>, <TokenType.UTINYINT: 101>, <TokenType.SMALLINT: 102>, <TokenType.USMALLINT: 103>, <TokenType.MEDIUMINT: 104>, <TokenType.UMEDIUMINT: 105>, <TokenType.INT: 106>, <TokenType.UINT: 107>, <TokenType.BIGINT: 108>, <TokenType.UBIGINT: 109>, <TokenType.BIGNUM: 110>, <TokenType.INT128: 111>, <TokenType.UINT128: 112>, <TokenType.INT256: 113>, <TokenType.UINT256: 114>, <TokenType.FLOAT: 115>, <TokenType.DOUBLE: 116>, <TokenType.UDOUBLE: 117>, <TokenType.DECIMAL: 118>, <TokenType.DECIMAL32: 119>, <TokenType.DECIMAL64: 120>, <TokenType.DECIMAL128: 121>, <TokenType.DECIMAL256: 122>, <TokenType.DECFLOAT: 123>, <TokenType.UDECIMAL: 124>, <TokenType.BIGDECIMAL: 125>, <TokenType.CHAR: 126>, <TokenType.NCHAR: 127>, <TokenType.VARCHAR: 128>, <TokenType.NVARCHAR: 129>, <TokenType.BPCHAR: 130>, <TokenType.TEXT: 131>, <TokenType.MEDIUMTEXT: 132>, <TokenType.LONGTEXT: 133>, <TokenType.BLOB: 134>, <TokenType.MEDIUMBLOB: 135>, <TokenType.LONGBLOB: 136>, <TokenType.TINYBLOB: 137>, <TokenType.TINYTEXT: 138>, <TokenType.NAME: 139>, <TokenType.BINARY: 140>, <TokenType.VARBINARY: 141>, <TokenType.JSON: 142>, <TokenType.JSONB: 143>, <TokenType.TIME: 144>, <TokenType.TIMETZ: 145>, <TokenType.TIME_NS: 146>, <TokenType.TIMESTAMP: 147>, <TokenType.TIMESTAMPTZ: 148>, <TokenType.TIMESTAMPLTZ: 149>, <TokenType.TIMESTAMPNTZ: 150>, <TokenType.TIMESTAMP_S: 151>, <TokenType.TIMESTAMP_MS: 152>, <TokenType.TIMESTAMP_NS: 153>, <TokenType.DATETIME: 154>, <TokenType.DATETIME2: 155>, <TokenType.DATETIME64: 156>, <TokenType.SMALLDATETIME: 157>, <TokenType.DATE: 158>, <TokenType.DATE32: 159>, <TokenType.INT4RANGE: 160>, <TokenType.INT4MULTIRANGE: 161>, <TokenType.INT8RANGE: 162>, <TokenType.INT8MULTIRANGE: 163>, <TokenType.NUMRANGE: 164>, <TokenType.NUMMULTIRANGE: 165>, <TokenType.TSRANGE: 166>, <TokenType.TSMULTIRANGE: 167>, <TokenType.TSTZRANGE: 168>, <TokenType.TSTZMULTIRANGE: 169>, <TokenType.DATERANGE: 170>, <TokenType.DATEMULTIRANGE: 171>, <TokenType.UUID: 172>, <TokenType.GEOGRAPHY: 173>, <TokenType.GEOGRAPHYPOINT: 174>, <TokenType.NULLABLE: 175>, <TokenType.GEOMETRY: 176>, <TokenType.POINT: 177>, <TokenType.RING: 178>, <TokenType.LINESTRING: 179>, <TokenType.LOCALTIME: 180>, <TokenType.LOCALTIMESTAMP: 181>, <TokenType.MULTILINESTRING: 183>, <TokenType.POLYGON: 184>, <TokenType.MULTIPOLYGON: 185>, <TokenType.HLLSKETCH: 186>, <TokenType.HSTORE: 187>, <TokenType.SUPER: 188>, <TokenType.SERIAL: 189>, <TokenType.SMALLSERIAL: 190>, <TokenType.BIGSERIAL: 191>, <TokenType.XML: 192>, <TokenType.YEAR: 193>, <TokenType.USERDEFINED: 194>, <TokenType.MONEY: 195>, <TokenType.SMALLMONEY: 196>, <TokenType.ROWVERSION: 197>, <TokenType.IMAGE: 198>, <TokenType.VARIANT: 199>, <TokenType.OBJECT: 200>, <TokenType.INET: 201>, <TokenType.IPADDRESS: 202>, <TokenType.IPPREFIX: 203>, <TokenType.IPV4: 204>, <TokenType.IPV6: 205>, <TokenType.ENUM: 206>, <TokenType.ENUM8: 207>, <TokenType.ENUM16: 208>, <TokenType.FIXEDSTRING: 209>, <TokenType.LOWCARDINALITY: 210>, <TokenType.NESTED: 211>, <TokenType.AGGREGATEFUNCTION: 212>, <TokenType.SIMPLEAGGREGATEFUNCTION: 213>, <TokenType.TDIGEST: 214>, <TokenType.UNKNOWN: 215>, <TokenType.VECTOR: 216>, <TokenType.DYNAMIC: 217>, <TokenType.VOID: 218>, <TokenType.ALL: 221>, <TokenType.ANY: 223>, <TokenType.APPLY: 224>, <TokenType.ARRAY: 225>, <TokenType.ASC: 226>, <TokenType.ATTACH: 228>, <TokenType.AUTO_INCREMENT: 229>, <TokenType.BEGIN: 230>, <TokenType.CACHE: 233>, <TokenType.CASE: 234>, <TokenType.COLLATE: 237>, <TokenType.COMMAND: 238>, <TokenType.COMMENT: 239>, <TokenType.COMMIT: 240>, <TokenType.CONSTRAINT: 242>, <TokenType.COPY: 243>, <TokenType.CUBE: 246>, <TokenType.CURRENT_DATE: 247>, <TokenType.CURRENT_DATETIME: 248>, <TokenType.CURRENT_SCHEMA: 249>, <TokenType.CURRENT_TIME: 250>, <TokenType.CURRENT_TIMESTAMP: 251>, <TokenType.CURRENT_USER: 252>, <TokenType.CURRENT_ROLE: 254>, <TokenType.CURRENT_CATALOG: 255>, <TokenType.DECLARE: 256>, <TokenType.DEFAULT: 257>, <TokenType.DELETE: 258>, <TokenType.DESC: 259>, <TokenType.DESCRIBE: 260>, <TokenType.DETACH: 261>, <TokenType.DICTIONARY: 262>, <TokenType.DIV: 265>, <TokenType.END: 268>, <TokenType.ESCAPE: 269>, <TokenType.EXECUTE: 271>, <TokenType.EXISTS: 272>, <TokenType.FALSE: 273>, <TokenType.FILE: 275>, <TokenType.FILE_FORMAT: 276>, <TokenType.FILTER: 277>, <TokenType.FINAL: 278>, <TokenType.FIRST: 279>, <TokenType.FOREIGN_KEY: 282>, <TokenType.FORMAT: 283>, <TokenType.FUNCTION: 286>, <TokenType.GET: 287>, <TokenType.INDEX: 298>, <TokenType.INTERVAL: 305>, <TokenType.IS: 309>, <TokenType.ISNULL: 310>, <TokenType.KEEP: 313>, <TokenType.KILL: 315>, <TokenType.LIMIT: 320>, <TokenType.LIST: 321>, <TokenType.LOAD: 322>, <TokenType.MAP: 324>, <TokenType.MATCH: 325>, <TokenType.MERGE: 329>, <TokenType.MODEL: 331>, <TokenType.NEXT: 333>, <TokenType.NOTHING: 334>, <TokenType.NULL: 336>, <TokenType.OBJECT_IDENTIFIER: 337>, <TokenType.OFFSET: 338>, <TokenType.OPERATOR: 341>, <TokenType.ORDINALITY: 345>, <TokenType.OUT: 346>, <TokenType.INOUT: 347>, <TokenType.OVER: 349>, <TokenType.OVERLAPS: 350>, <TokenType.OVERWRITE: 351>, <TokenType.PARTITION: 353>, <TokenType.PASTE: 355>, <TokenType.PERCENT: 356>, <TokenType.PIVOT: 357>, <TokenType.PRAGMA: 362>, <TokenType.PROCEDURE: 365>, <TokenType.PROJECTION: 367>, <TokenType.PSEUDO_TYPE: 368>, <TokenType.PUT: 369>, <TokenType.RANGE: 373>, <TokenType.RECURSIVE: 374>, <TokenType.REFRESH: 375>, <TokenType.RENAME: 376>, <TokenType.REPLACE: 377>, <TokenType.REFERENCES: 380>, <TokenType.ROLLUP: 385>, <TokenType.ROW: 386>, <TokenType.ROWS: 387>, <TokenType.SEQUENCE: 392>, <TokenType.SET: 394>, <TokenType.SETTINGS: 395>, <TokenType.SHOW: 396>, <TokenType.SOME: 398>, <TokenType.STORAGE_INTEGRATION: 403>, <TokenType.STRAIGHT_JOIN: 404>, <TokenType.STRUCT: 405>, <TokenType.TAG: 408>, <TokenType.TEMPORARY: 409>, <TokenType.TOP: 410>, <TokenType.TRUE: 412>, <TokenType.TRUNCATE: 413>, <TokenType.TRIGGER: 414>, <TokenType.TYPE: 415>, <TokenType.UNNEST: 419>, <TokenType.UNPIVOT: 420>, <TokenType.UPDATE: 421>, <TokenType.USE: 422>, <TokenType.VIEW: 426>, <TokenType.SEMANTIC_VIEW: 427>, <TokenType.VOLATILE: 428>, <TokenType.UNIQUE: 434>, <TokenType.SINK: 439>, <TokenType.SOURCE: 440>, <TokenType.ANALYZE: 441>, <TokenType.NAMESPACE: 442>, <TokenType.EXPORT: 443>}
SELECT_START_TOKENS: ClassVar = {<TokenType.L_PAREN: 1>, <TokenType.WITH: 433>, <TokenType.SELECT: 389>}
COPY_INTO_VARLEN_OPTIONS: ClassVar = {'FORMAT_OPTIONS', 'FILE_FORMAT', 'COPY_OPTIONS', 'CREDENTIAL'}
IS_PREDICATE_PRECEDENCE: ClassVar[IsPredicatePrecedence] = <IsPredicatePrecedence.HIGH: 1>
IS_PREDICATE_TOKENS: ClassVar = {<TokenType.NOT: 27>, <TokenType.IS: 309>, <TokenType.ISNULL: 310>, <TokenType.NOTNULL: 335>}
IS_JSON_PREDICATE_KIND: ClassVar = {'OBJECT', 'ARRAY', 'VALUE', 'SCALAR'}
ODBC_DATETIME_LITERALS: ClassVar[dict[str, type[sqlglot.expressions.core.Expr]]] = {}
ON_CONDITION_TOKENS: ClassVar = {'NULL', 'TRUE', 'FALSE', 'EMPTY', 'ERROR'}
PRIVILEGE_FOLLOW_TOKENS: ClassVar = {<TokenType.L_PAREN: 1>, <TokenType.ON: 339>, <TokenType.COMMA: 7>}
DESCRIBE_STYLES: ClassVar = {'ANALYZE', 'FORMATTED', 'HISTORY', 'EXTENDED'}
SET_ASSIGNMENT_DELIMITERS: ClassVar = {'=', 'TO', ':='}
ANALYZE_STYLES: ClassVar = {'SAMPLE', 'FULL', 'SKIP_LOCKED', 'BUFFER_USAGE_LIMIT', 'NO_WRITE_TO_BINLOG', 'LOCAL', 'VERBOSE'}
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 = {'SUBPARTITION', 'PARTITION'}
AMBIGUOUS_ALIAS_TOKENS: ClassVar = (<TokenType.LIMIT: 320>, <TokenType.OFFSET: 338>)
OPERATION_MODIFIERS: ClassVar[set[str]] = set()
RECURSIVE_CTE_SEARCH_KIND: ClassVar = {'CYCLE', 'DEPTH', 'BREADTH'}
SECURITY_PROPERTY_KEYWORDS: ClassVar = {'INVOKER', 'DEFINER', 'NONE'}
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 = {'cluster', 'order', 'distribute', 'limit', 'offset', 'sort'}
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_DROP_REQUIRES_COLUMN: ClassVar = True
ALTER_TABLE_PARTITIONS: ClassVar = False
ALTER_TABLE_MIXED_ACTIONS: ClassVar = False
ALTER_TABLE_REQUIRES_ACTION: ClassVar = True
ALTER_COLUMN_TYPE_REQUIRES_KEYWORD: 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:
2045    def reset(self) -> None:
2046        self.sql = ""
2047        self.errors = []
2048        self._tokens = []
2049        self._tokens_size = 0
2050        self._index = 0
2051        self._curr = SENTINEL_NONE
2052        self._next = SENTINEL_NONE
2053        self._prev = SENTINEL_NONE
2054        self._prev_comments = []
2055        self._pipe_cte_counter = 0
2056        self._chunks = []
2057        self._chunk_index = 0
2058        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:
2151    def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None:
2152        token = token or self._curr or self._prev or Token.string("")
2153        formatted_sql, start_context, highlight, end_context = highlight_sql(
2154            sql=self.sql,
2155            positions=[(token.start, token.end)],
2156            context_length=self.error_message_context,
2157        )
2158        formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n  {formatted_sql}"
2159
2160        error = ParseError.new(
2161            formatted_message,
2162            description=message,
2163            line=token.line,
2164            col=token.col,
2165            start_context=start_context,
2166            highlight=highlight,
2167            end_context=end_context,
2168        )
2169
2170        if self.error_level == ErrorLevel.IMMEDIATE:
2171            raise error
2172
2173        self.errors.append(error)
def validate_expression(self, expression: ~E, args: list | None = None) -> ~E:
2175    def validate_expression(self, expression: E, args: list | None = None) -> E:
2176        if self.max_nodes > -1:
2177            self._node_count += 1
2178            if self._node_count > self.max_nodes:
2179                self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded")
2180        if self.error_level != ErrorLevel.IGNORE:
2181            for error_message in expression.error_messages(args):
2182                self.raise_error(error_message)
2183        return expression
def parse( self, raw_tokens: list[sqlglot.tokenizer_core.Token], sql: str) -> list[sqlglot.expressions.core.Expr | None]:
2202    def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]:
2203        """
2204        Parses a list of tokens and returns a list of syntax trees, one tree
2205        per parsed SQL statement.
2206
2207        Args:
2208            raw_tokens: The list of tokens.
2209            sql: The original SQL string.
2210
2211        Returns:
2212            The list of the produced syntax trees.
2213        """
2214        return self._parse(
2215            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
2216        )

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]:
2218    def parse_into(
2219        self,
2220        expression_types: exp.IntoType,
2221        raw_tokens: list[Token],
2222        sql: str | None = None,
2223    ) -> list[exp.Expr | None]:
2224        """
2225        Parses a list of tokens into a given Expr type. If a collection of Expr
2226        types is given instead, this method will try to parse the token list into each one
2227        of them, stopping at the first for which the parsing succeeds.
2228
2229        Args:
2230            expression_types: The expression type(s) to try and parse the token list into.
2231            raw_tokens: The list of tokens.
2232            sql: The original SQL string, used to produce helpful debug messages.
2233
2234        Returns:
2235            The target Expr.
2236        """
2237        errors = []
2238        for expression_type in ensure_list(expression_types):
2239            parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type))
2240            if not parser:
2241                raise TypeError(f"No parser registered for {expression_type}")
2242
2243            try:
2244                return self._parse(parser, raw_tokens, sql)
2245            except ParseError as e:
2246                e.errors[0]["into_expression"] = expression_type
2247                errors.append(e)
2248
2249        raise ParseError(
2250            f"Failed to parse '{sql or raw_tokens}' into {expression_types}",
2251            errors=merge_errors(errors),
2252        ) 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:
2254    def check_errors(self) -> None:
2255        """Logs or raises any found errors, depending on the chosen error level setting."""
2256        if self.error_level == ErrorLevel.WARN:
2257            for error in self.errors:
2258                logger.error(str(error))
2259        elif self.error_level == ErrorLevel.RAISE and self.errors:
2260            raise ParseError(
2261                concat_messages(self.errors, self.max_errors),
2262                errors=merge_errors(self.errors),
2263            )

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:
2265    def expression(
2266        self,
2267        instance: E,
2268        token: Token | None = None,
2269        comments: list[str] | None = None,
2270    ) -> E:
2271        if token:
2272            instance.update_positions(token)
2273        instance.add_comments(comments) if comments else self._add_comments(instance)
2274        if not instance.is_primitive:
2275            instance = self.validate_expression(instance)
2276        return instance
def parse_set_operation( self, this: sqlglot.expressions.core.Expr | None, consume_pipe: bool = False) -> sqlglot.expressions.core.Expr | None:
6071    def parse_set_operation(
6072        self, this: exp.Expr | None, consume_pipe: bool = False
6073    ) -> exp.Expr | None:
6074        start = self._index
6075        _, side_token, kind_token = self._parse_join_parts()
6076
6077        side = side_token.text if side_token else None
6078        kind = kind_token.text if kind_token else None
6079
6080        if not self._match_set(self.SET_OPERATIONS):
6081            self._retreat(start)
6082            return None
6083
6084        token_type = self._prev.token_type
6085
6086        if token_type == TokenType.UNION:
6087            operation: type[exp.SetOperation] = exp.Union
6088        elif token_type == TokenType.EXCEPT:
6089            operation = exp.Except
6090        else:
6091            operation = exp.Intersect
6092
6093        comments = self._prev.comments
6094
6095        if self._match(TokenType.DISTINCT):
6096            distinct: bool | None = True
6097        elif self._match(TokenType.ALL):
6098            distinct = False
6099        else:
6100            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
6101            if distinct is None:
6102                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
6103
6104        by_name = (
6105            self._match_text_seq("BY", "NAME")
6106            or self._match_text_seq("STRICT", "CORRESPONDING")
6107            or None
6108        )
6109        if self._match_text_seq("CORRESPONDING"):
6110            by_name = True
6111            if not side and not kind:
6112                kind = "INNER"
6113
6114        on_column_list = None
6115        if by_name and self._match_texts(("ON", "BY")):
6116            on_column_list = self._parse_wrapped_csv(self._parse_column)
6117
6118        expression = self._parse_select(
6119            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
6120        )
6121
6122        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
6123        # in _parse_cte and so that alias pushdown can reach into set operation branches
6124        if isinstance(this, exp.Values):
6125            this = self._values_to_select(this)
6126        if isinstance(expression, exp.Values):
6127            expression = self._values_to_select(expression)
6128
6129        if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery):
6130            subquery = this.this
6131            subquery.set("alias", exp.TableAlias(this=this.args["alias"]))
6132            subquery.add_comments(this.pop_comments())
6133            this = subquery
6134
6135        return self.expression(
6136            operation(
6137                this=this,
6138                distinct=distinct,
6139                by_name=by_name,
6140                expression=expression,
6141                side=side,
6142                kind=kind,
6143                on=on_column_list,
6144            ),
6145            comments=comments,
6146        )
def build_cast(self, strict: bool, **kwargs) -> sqlglot.expressions.core.Expr:
10753    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10754        exp_class = exp.Cast if strict else exp.TryCast
10755
10756        if exp_class == exp.TryCast:
10757            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10758
10759        return self.expression(exp_class(**kwargs))