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.REF,
  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.CONNECT,
  777        TokenType.CONSTRAINT,
  778        TokenType.COPY,
  779        TokenType.CUBE,
  780        TokenType.CURRENT_SCHEMA,
  781        TokenType.DECLARE,
  782        TokenType.DEFAULT,
  783        TokenType.DELETE,
  784        TokenType.DESC,
  785        TokenType.DESCRIBE,
  786        TokenType.DETACH,
  787        TokenType.DICTIONARY,
  788        TokenType.DISCONNECT,
  789        TokenType.DIV,
  790        TokenType.END,
  791        TokenType.EXECUTE,
  792        TokenType.EXPORT,
  793        TokenType.ESCAPE,
  794        TokenType.FALSE,
  795        TokenType.FIRST,
  796        TokenType.FILE,
  797        TokenType.FILTER,
  798        TokenType.FINAL,
  799        TokenType.FORMAT,
  800        TokenType.FULL,
  801        TokenType.GET,
  802        TokenType.IDENTIFIER,
  803        TokenType.IN,
  804        TokenType.INOUT,
  805        TokenType.IS,
  806        TokenType.ISNULL,
  807        TokenType.INTERVAL,
  808        TokenType.KEEP,
  809        TokenType.KILL,
  810        TokenType.LEFT,
  811        TokenType.LIMIT,
  812        TokenType.LOAD,
  813        TokenType.LOCK,
  814        TokenType.MATCH,
  815        TokenType.MERGE,
  816        TokenType.NATURAL,
  817        TokenType.NEXT,
  818        TokenType.OFFSET,
  819        TokenType.OPERATOR,
  820        TokenType.ORDINALITY,
  821        TokenType.OUT,
  822        TokenType.OVER,
  823        TokenType.OVERLAPS,
  824        TokenType.OVERWRITE,
  825        TokenType.PARTITION,
  826        TokenType.PASTE,
  827        TokenType.PERCENT,
  828        TokenType.PIVOT,
  829        TokenType.PROJECTION,
  830        TokenType.PRAGMA,
  831        TokenType.PUT,
  832        TokenType.RANGE,
  833        TokenType.RECURSIVE,
  834        TokenType.REFERENCES,
  835        TokenType.REFRESH,
  836        TokenType.RENAME,
  837        TokenType.REPLACE,
  838        TokenType.RIGHT,
  839        TokenType.ROLLUP,
  840        TokenType.ROW,
  841        TokenType.ROWS,
  842        TokenType.SEMI,
  843        TokenType.SET,
  844        TokenType.SETTINGS,
  845        TokenType.SHOW,
  846        TokenType.STREAM,
  847        TokenType.STREAMLIT,
  848        TokenType.TEMPORARY,
  849        TokenType.TOP,
  850        TokenType.TRUE,
  851        TokenType.TRUNCATE,
  852        TokenType.UNIQUE,
  853        TokenType.UNNEST,
  854        TokenType.UNPIVOT,
  855        TokenType.UPDATE,
  856        TokenType.USE,
  857        TokenType.VOLATILE,
  858        TokenType.WINDOW,
  859        TokenType.CURRENT_CATALOG,
  860        TokenType.LOCALTIME,
  861        TokenType.LOCALTIMESTAMP,
  862        TokenType.SESSION_USER,
  863        TokenType.STRAIGHT_JOIN,
  864        *ALTERABLES,
  865        *CREATABLES,
  866        *SUBQUERY_PREDICATES,
  867        *TYPE_TOKENS,
  868        *NO_PAREN_FUNCTIONS,
  869    } - {TokenType.UNION}
  870
  871    TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - {
  872        TokenType.ANTI,
  873        TokenType.ASOF,
  874        TokenType.FULL,
  875        TokenType.LEFT,
  876        TokenType.LOCK,
  877        TokenType.NATURAL,
  878        TokenType.RIGHT,
  879        TokenType.SEMI,
  880        TokenType.WINDOW,
  881    }
  882
  883    ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS
  884
  885    COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS
  886
  887    ARRAY_CONSTRUCTORS: t.ClassVar = {
  888        "ARRAY": exp.Array,
  889        "LIST": exp.List,
  890    }
  891
  892    COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS}
  893
  894    UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET}
  895
  896    TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"}
  897
  898    # Tokens that indicate a simple column reference
  899    IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER})
  900
  901    BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE})
  902
  903    # Postfix tokens that prevent the bare column fast path
  904    COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  905        {
  906            TokenType.L_PAREN,
  907            TokenType.L_BRACKET,
  908            TokenType.L_BRACE,
  909            TokenType.COLON,
  910            TokenType.JOIN_MARKER,
  911        }
  912    )
  913
  914    TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  915        {
  916            TokenType.L_PAREN,
  917            TokenType.L_BRACKET,
  918            TokenType.L_BRACE,
  919            TokenType.PIVOT,
  920            TokenType.UNPIVOT,
  921            TokenType.TABLE_SAMPLE,
  922        }
  923    )
  924
  925    FUNC_TOKENS: t.ClassVar = {
  926        TokenType.COLLATE,
  927        TokenType.COMMAND,
  928        TokenType.CURRENT_DATE,
  929        TokenType.CURRENT_DATETIME,
  930        TokenType.CURRENT_SCHEMA,
  931        TokenType.CURRENT_TIMESTAMP,
  932        TokenType.CURRENT_TIME,
  933        TokenType.CURRENT_USER,
  934        TokenType.CURRENT_CATALOG,
  935        TokenType.DECLARE,
  936        TokenType.FILTER,
  937        TokenType.FIRST,
  938        TokenType.FORMAT,
  939        TokenType.GET,
  940        TokenType.GLOB,
  941        TokenType.IDENTIFIER,
  942        TokenType.INDEX,
  943        TokenType.ISNULL,
  944        TokenType.ILIKE,
  945        TokenType.INSERT,
  946        TokenType.LIKE,
  947        TokenType.LOCALTIME,
  948        TokenType.LOCALTIMESTAMP,
  949        TokenType.MERGE,
  950        TokenType.NEXT,
  951        TokenType.OFFSET,
  952        TokenType.PRIMARY_KEY,
  953        TokenType.RANGE,
  954        TokenType.REPLACE,
  955        TokenType.RLIKE,
  956        TokenType.ROW,
  957        TokenType.SESSION_USER,
  958        TokenType.UNNEST,
  959        TokenType.VAR,
  960        TokenType.LEFT,
  961        TokenType.RIGHT,
  962        TokenType.SEQUENCE,
  963        TokenType.DATE,
  964        TokenType.DATETIME,
  965        TokenType.TABLE,
  966        TokenType.TIMESTAMP,
  967        TokenType.TIMESTAMPTZ,
  968        TokenType.TRUNCATE,
  969        TokenType.UTC_DATE,
  970        TokenType.UTC_TIME,
  971        TokenType.UTC_TIMESTAMP,
  972        TokenType.WINDOW,
  973        TokenType.XOR,
  974        *TYPE_TOKENS,
  975        *SUBQUERY_PREDICATES,
  976    }
  977
  978    CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  979        TokenType.AND: exp.And,
  980    }
  981
  982    ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  983        TokenType.COLON_EQ: exp.PropertyEQ,
  984    }
  985
  986    DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  987        TokenType.OR: exp.Or,
  988    }
  989
  990    EQUALITY: t.ClassVar = {
  991        TokenType.EQ: exp.EQ,
  992        TokenType.NEQ: exp.NEQ,
  993        TokenType.NULLSAFE_EQ: exp.NullSafeEQ,
  994    }
  995
  996    COMPARISON: t.ClassVar = {
  997        TokenType.GT: exp.GT,
  998        TokenType.GTE: exp.GTE,
  999        TokenType.LT: exp.LT,
 1000        TokenType.LTE: exp.LTE,
 1001    }
 1002
 1003    BITWISE: t.ClassVar = {
 1004        TokenType.AMP: exp.BitwiseAnd,
 1005        TokenType.CARET: exp.BitwiseXor,
 1006        TokenType.PIPE: exp.BitwiseOr,
 1007    }
 1008
 1009    TERM: t.ClassVar = {
 1010        TokenType.DASH: exp.Sub,
 1011        TokenType.PLUS: exp.Add,
 1012        TokenType.COLLATE: exp.Collate,
 1013    }
 1014
 1015    FACTOR: t.ClassVar = {
 1016        TokenType.DIV: exp.IntDiv,
 1017        TokenType.LR_ARROW: exp.Distance,
 1018        TokenType.LLRR_ARROW: exp.DistanceNd,
 1019        TokenType.MOD: exp.Mod,
 1020        TokenType.SLASH: exp.Div,
 1021        TokenType.STAR: exp.Mul,
 1022    }
 1023
 1024    EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {}
 1025
 1026    TIMES: t.ClassVar = {
 1027        TokenType.TIME,
 1028        TokenType.TIMETZ,
 1029    }
 1030
 1031    TIMESTAMPS: t.ClassVar = {
 1032        TokenType.TIMESTAMP,
 1033        TokenType.TIMESTAMPNTZ,
 1034        TokenType.TIMESTAMPTZ,
 1035        TokenType.TIMESTAMPLTZ,
 1036        *TIMES,
 1037    }
 1038
 1039    SET_OPERATIONS: t.ClassVar = {
 1040        TokenType.UNION,
 1041        TokenType.INTERSECT,
 1042        TokenType.EXCEPT,
 1043    }
 1044
 1045    JOIN_METHODS: t.ClassVar = {
 1046        TokenType.ASOF,
 1047        TokenType.NATURAL,
 1048        TokenType.POSITIONAL,
 1049    }
 1050
 1051    JOIN_SIDES: t.ClassVar = {
 1052        TokenType.LEFT,
 1053        TokenType.RIGHT,
 1054        TokenType.FULL,
 1055    }
 1056
 1057    JOIN_KINDS: t.ClassVar = {
 1058        TokenType.ANTI,
 1059        TokenType.CROSS,
 1060        TokenType.INNER,
 1061        TokenType.OUTER,
 1062        TokenType.SEMI,
 1063        TokenType.STRAIGHT_JOIN,
 1064    }
 1065
 1066    JOIN_HINTS: t.ClassVar[set[str]] = set()
 1067
 1068    SUPPORTS_DIRECTED_JOIN = True
 1069
 1070    # Tokens that unambiguously end a table reference on the fast path
 1071    TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset(
 1072        {
 1073            TokenType.COMMA,
 1074            TokenType.GROUP_BY,
 1075            TokenType.HAVING,
 1076            TokenType.JOIN,
 1077            TokenType.LIMIT,
 1078            TokenType.ON,
 1079            TokenType.ORDER_BY,
 1080            TokenType.R_PAREN,
 1081            TokenType.SEMICOLON,
 1082            TokenType.SENTINEL,
 1083            TokenType.WHERE,
 1084            *SET_OPERATIONS,
 1085            *JOIN_KINDS,
 1086            *JOIN_METHODS,
 1087            *JOIN_SIDES,
 1088        }
 1089    )
 1090
 1091    LAMBDAS: t.ClassVar = {
 1092        TokenType.ARROW: lambda self, expressions: self.expression(
 1093            exp.Lambda(
 1094                this=self._replace_lambda(
 1095                    self._parse_disjunction(),
 1096                    expressions,
 1097                ),
 1098                expressions=expressions,
 1099            )
 1100        ),
 1101        TokenType.FARROW: lambda self, expressions: self.expression(
 1102            exp.Kwarg(
 1103                this=exp.var(expressions[0].name),
 1104                expression=self._parse_disjunction() or self._parse_select(),
 1105            )
 1106        ),
 1107    }
 1108
 1109    # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake
 1110    TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False
 1111
 1112    LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN})
 1113
 1114    COLUMN_OPERATORS: t.ClassVar = {
 1115        TokenType.DOT: None,
 1116        TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)),
 1117        TokenType.DCOLON: lambda self, this, to: self.build_cast(
 1118            strict=self.STRICT_CAST, this=this, to=to
 1119        ),
 1120        TokenType.ARROW: lambda self, this, path: self.expression(
 1121            exp.JSONExtract(
 1122                this=this,
 1123                expression=self.dialect.to_json_path(path),
 1124                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1125            )
 1126        ),
 1127        TokenType.DARROW: lambda self, this, path: self.expression(
 1128            exp.JSONExtractScalar(
 1129                this=this,
 1130                expression=self.dialect.to_json_path(path),
 1131                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1132                scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY,
 1133            )
 1134        ),
 1135        TokenType.HASH_ARROW: lambda self, this, path: self.expression(
 1136            exp.JSONBExtract(this=this, expression=path)
 1137        ),
 1138        TokenType.DHASH_ARROW: lambda self, this, path: self.expression(
 1139            exp.JSONBExtractScalar(this=this, expression=path)
 1140        ),
 1141        TokenType.PLACEHOLDER: lambda self, this, key: self.expression(
 1142            exp.JSONBContainsTopKey(this=this, expression=key)
 1143        ),
 1144    }
 1145
 1146    # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator"
 1147    # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs).
 1148    JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {}
 1149
 1150    CAST_COLUMN_OPERATORS: t.ClassVar = {
 1151        TokenType.DOTCOLON,
 1152        TokenType.DCOLON,
 1153    }
 1154
 1155    EXPRESSION_PARSERS: t.ClassVar = {
 1156        exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY),
 1157        exp.Column: lambda self: self._parse_column(),
 1158        exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()),
 1159        exp.Condition: lambda self: self._parse_disjunction(),
 1160        exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True),
 1161        exp.Expr: lambda self: self._parse_expression(),
 1162        exp.From: lambda self: self._parse_from(joins=True),
 1163        exp.GrantPrincipal: lambda self: self._parse_grant_principal(),
 1164        exp.GrantPrivilege: lambda self: self._parse_grant_privilege(),
 1165        exp.Group: lambda self: self._parse_group(),
 1166        exp.Having: lambda self: self._parse_having(),
 1167        exp.Hint: lambda self: self._parse_hint_body(),
 1168        exp.Identifier: lambda self: self._parse_id_var(),
 1169        exp.Join: lambda self: self._parse_join(),
 1170        exp.Lambda: lambda self: self._parse_lambda(),
 1171        exp.Lateral: lambda self: self._parse_lateral(),
 1172        exp.Limit: lambda self: self._parse_limit(),
 1173        exp.Offset: lambda self: self._parse_offset(),
 1174        exp.Order: lambda self: self._parse_order(),
 1175        exp.Ordered: lambda self: self._parse_ordered(),
 1176        exp.Properties: lambda self: self._parse_properties(),
 1177        exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(),
 1178        exp.Qualify: lambda self: self._parse_qualify(),
 1179        exp.Returning: lambda self: self._parse_returning(),
 1180        exp.Select: lambda self: self._parse_select(),
 1181        exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY),
 1182        exp.Table: lambda self: self._parse_table_parts(),
 1183        exp.TableAlias: lambda self: self._parse_table_alias(),
 1184        exp.Tuple: lambda self: self._parse_value(values=False),
 1185        exp.Whens: lambda self: self._parse_when_matched(),
 1186        exp.Where: lambda self: self._parse_where(),
 1187        exp.Window: lambda self: self._parse_named_window(),
 1188        exp.With: lambda self: self._parse_with(),
 1189    }
 1190
 1191    STATEMENT_PARSERS: t.ClassVar = {
 1192        TokenType.ALTER: lambda self: self._parse_alter(),
 1193        TokenType.ANALYZE: lambda self: self._parse_analyze(),
 1194        TokenType.BEGIN: lambda self: self._parse_transaction(),
 1195        TokenType.CACHE: lambda self: self._parse_cache(),
 1196        TokenType.COMMENT: lambda self: self._parse_comment(),
 1197        TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(),
 1198        TokenType.COPY: lambda self: self._parse_copy(),
 1199        TokenType.CREATE: lambda self: self._parse_create(),
 1200        TokenType.DECLARE: lambda self: self._parse_declare(),
 1201        TokenType.DELETE: lambda self: self._parse_delete(),
 1202        TokenType.DESC: lambda self: self._parse_describe(),
 1203        TokenType.DESCRIBE: lambda self: self._parse_describe(),
 1204        TokenType.DROP: lambda self: self._parse_drop(),
 1205        TokenType.GRANT: lambda self: self._parse_grant(),
 1206        TokenType.REVOKE: lambda self: self._parse_revoke(),
 1207        TokenType.INSERT: lambda self: self._parse_insert(),
 1208        TokenType.KILL: lambda self: self._parse_kill(),
 1209        TokenType.LOAD: lambda self: self._parse_load(),
 1210        TokenType.MERGE: lambda self: self._parse_merge(),
 1211        TokenType.PIVOT: lambda self: self._parse_simplified_pivot(),
 1212        TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())),
 1213        TokenType.REFRESH: lambda self: self._parse_refresh(),
 1214        TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(),
 1215        TokenType.SET: lambda self: self._parse_set(),
 1216        TokenType.TRUNCATE: lambda self: self._parse_truncate_table(),
 1217        TokenType.UNCACHE: lambda self: self._parse_uncache(),
 1218        TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True),
 1219        TokenType.UPDATE: lambda self: self._parse_update(),
 1220        TokenType.USE: lambda self: self._parse_use(),
 1221        TokenType.SEMICOLON: lambda self: exp.Semicolon(),
 1222    }
 1223
 1224    UNARY_PARSERS: t.ClassVar = {
 1225        TokenType.PLUS: lambda self: self._parse_unary(),  # Unary + is handled as a no-op
 1226        TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())),
 1227        TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())),
 1228        TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())),
 1229        TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())),
 1230        TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())),
 1231    }
 1232
 1233    STRING_PARSERS: t.ClassVar = {
 1234        TokenType.HEREDOC_STRING: lambda self, token: self.expression(
 1235            exp.RawString(this=token.text), token
 1236        ),
 1237        **{
 1238            token_type: lambda self, token: self.expression(exp.National(this=token.text), token)
 1239            for token_type in (TokenType.NATIONAL_STRING, TokenType.NATIONAL_RAW_STRING)
 1240        },
 1241        TokenType.RAW_STRING: lambda self, token: self.expression(
 1242            exp.RawString(this=token.text), token
 1243        ),
 1244        TokenType.STRING: lambda self, token: self.expression(
 1245            exp.Literal(this=token.text, is_string=True), token
 1246        ),
 1247        TokenType.UNICODE_STRING: lambda self, token: self.expression(
 1248            exp.UnicodeString(
 1249                this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string()
 1250            ),
 1251            token,
 1252        ),
 1253    }
 1254
 1255    NUMERIC_PARSERS: t.ClassVar = {
 1256        TokenType.BIT_STRING: lambda self, token: self.expression(
 1257            exp.BitString(this=token.text), token
 1258        ),
 1259        TokenType.BYTE_STRING: lambda self, token: self.expression(
 1260            exp.ByteString(
 1261                this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None
 1262            ),
 1263            token,
 1264        ),
 1265        TokenType.HEX_STRING: lambda self, token: self.expression(
 1266            exp.HexString(
 1267                this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None
 1268            ),
 1269            token,
 1270        ),
 1271        TokenType.NUMBER: lambda self, token: self.expression(
 1272            exp.Literal(this=token.text, is_string=False), token
 1273        ),
 1274    }
 1275
 1276    PRIMARY_PARSERS: t.ClassVar = {
 1277        **STRING_PARSERS,
 1278        **NUMERIC_PARSERS,
 1279        TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token),
 1280        TokenType.NULL: lambda self, _: self.expression(exp.Null()),
 1281        TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)),
 1282        TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)),
 1283        TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(),
 1284        TokenType.STAR: lambda self, _: self._parse_star_ops(),
 1285    }
 1286
 1287    PLACEHOLDER_PARSERS: t.ClassVar = {
 1288        TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()),
 1289        TokenType.PARAMETER: lambda self: self._parse_parameter(),
 1290        TokenType.COLON: lambda self: (
 1291            self.expression(exp.Placeholder(this=self._prev.text))
 1292            if self._match_set(self.COLON_PLACEHOLDER_TOKENS)
 1293            else None
 1294        ),
 1295    }
 1296
 1297    RANGE_PARSERS: t.ClassVar = {
 1298        TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll),
 1299        TokenType.BETWEEN: lambda self, this: self._parse_between(this),
 1300        TokenType.GLOB: binary_range_parser(exp.Glob),
 1301        TokenType.ILIKE: binary_range_parser(exp.ILike),
 1302        TokenType.IN: lambda self, this: self._parse_in(this),
 1303        TokenType.IRLIKE: binary_range_parser(exp.RegexpILike),
 1304        TokenType.IS: lambda self, this: self._parse_is(this),
 1305        TokenType.LIKE: binary_range_parser(exp.Like),
 1306        TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy),
 1307        TokenType.OVERLAPS: binary_range_parser(exp.Overlaps),
 1308        TokenType.RLIKE: binary_range_parser(exp.RegexpLike),
 1309        TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo),
 1310        TokenType.FOR: lambda self, this: self._parse_comprehension(this),
 1311        TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys),
 1312        TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys),
 1313        TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath),
 1314        TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists),
 1315        TokenType.ADJACENT: binary_range_parser(exp.Adjacent),
 1316        TokenType.OPERATOR: lambda self, this: self._parse_operator(this),
 1317        TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft),
 1318        TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight),
 1319        TokenType.CARET_AT: binary_range_parser(exp.StartsWith),
 1320    }
 1321
 1322    PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = {
 1323        "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query),
 1324        "AS": lambda self, query: self._build_pipe_cte(
 1325            query, [exp.Star()], self._parse_table_alias()
 1326        ),
 1327        "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False),
 1328        "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query),
 1329        "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query),
 1330        "ORDER BY": lambda self, query: query.order_by(
 1331            self._parse_order(), append=False, copy=False
 1332        ),
 1333        "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1334        "SELECT": lambda self, query: self._parse_pipe_syntax_select(query),
 1335        "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query),
 1336        "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1337        "WHERE": lambda self, query: query.where(self._parse_where(), copy=False),
 1338    }
 1339
 1340    PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1341        "ALLOWED_VALUES": lambda self: self.expression(
 1342            exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary))
 1343        ),
 1344        "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty),
 1345        "AUTO": lambda self: self._parse_auto_property(),
 1346        "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty),
 1347        "BACKUP": lambda self: self.expression(
 1348            exp.BackupProperty(this=self._parse_var(any_token=True))
 1349        ),
 1350        "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(),
 1351        "CALLED": lambda self: self._parse_called_on_null_input_property(),
 1352        "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs),
 1353        "CHECKSUM": lambda self: self._parse_checksum(),
 1354        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1355        "CLUSTERED": lambda self: self._parse_clustered_by(),
 1356        "COLLATE": lambda self, **kwargs: self._parse_property_assignment(
 1357            exp.CollateProperty, **kwargs
 1358        ),
 1359        "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty),
 1360        "CONTAINS": lambda self: self._parse_contains_property(),
 1361        "COPY": lambda self: self._parse_copy_property(),
 1362        "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs),
 1363        "DATA_DELETION": lambda self: self._parse_data_deletion_property(),
 1364        "DEFINER": lambda self: self._parse_definer(),
 1365        "DETERMINISTIC": lambda self: self.expression(
 1366            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1367        ),
 1368        "DISTRIBUTED": lambda self: self._parse_distributed_property(),
 1369        "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty),
 1370        "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()),
 1371        "DISTKEY": lambda self: self._parse_distkey(),
 1372        "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty),
 1373        "EMPTY": lambda self: self.expression(exp.EmptyProperty()),
 1374        "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty),
 1375        "ENVIRONMENT": lambda self: self.expression(
 1376            exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment))
 1377        ),
 1378        "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty),
 1379        "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty),
 1380        "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()),
 1381        "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs),
 1382        "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1383        "FREESPACE": lambda self: self._parse_freespace(),
 1384        "GLOBAL": lambda self: self.expression(exp.GlobalProperty()),
 1385        "HEAP": lambda self: self.expression(exp.HeapProperty()),
 1386        "ICEBERG": lambda self: self.expression(exp.IcebergProperty()),
 1387        "IMMUTABLE": lambda self: self.expression(
 1388            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1389        ),
 1390        "INHERITS": lambda self: self.expression(
 1391            exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table))
 1392        ),
 1393        "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())),
 1394        "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs),
 1395        "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty),
 1396        "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"),
 1397        "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"),
 1398        "LIKE": lambda self: self._parse_create_like(),
 1399        "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty),
 1400        "LOCK": lambda self: self._parse_locking(),
 1401        "LOCKING": lambda self: self._parse_locking(),
 1402        "LOG": lambda self, **kwargs: self._parse_log(**kwargs),
 1403        "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()),
 1404        "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs),
 1405        "MODIFIES": lambda self: self._parse_modifies_property(),
 1406        "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)),
 1407        "NO": lambda self: self._parse_no_property(),
 1408        "ON": lambda self: self._parse_on_property(),
 1409        "ORDER BY": lambda self: self._parse_order(skip_order_token=True),
 1410        "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())),
 1411        "PARTITION": lambda self: self._parse_partitioned_of(),
 1412        "PARTITION BY": lambda self: self._parse_partitioned_by(),
 1413        "PARTITIONED BY": lambda self: self._parse_partitioned_by(),
 1414        "PARTITIONED_BY": lambda self: self._parse_partitioned_by(),
 1415        "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True),
 1416        "RANGE": lambda self: self._parse_dict_range(this="RANGE"),
 1417        "READS": lambda self: self._parse_reads_property(),
 1418        "REMOTE": lambda self: self._parse_remote_with_connection(),
 1419        "RETURNS": lambda self: self._parse_returns(),
 1420        "STRICT": lambda self: self.expression(exp.StrictProperty()),
 1421        "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()),
 1422        "ROW": lambda self: self._parse_row(),
 1423        "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty),
 1424        "SAMPLE": lambda self: self.expression(
 1425            exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise())
 1426        ),
 1427        "SECURE": lambda self: self.expression(exp.SecureProperty()),
 1428        "SECURITY": lambda self: self._parse_sql_security(),
 1429        "SQL SECURITY": lambda self: self._parse_sql_security(),
 1430        "SET": lambda self: self.expression(exp.SetProperty(multi=False)),
 1431        "SETTINGS": lambda self: self._parse_settings_property(),
 1432        "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty),
 1433        "SORTKEY": lambda self: self._parse_sortkey(),
 1434        "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"),
 1435        "STABLE": lambda self: self.expression(
 1436            exp.StabilityProperty(this=exp.Literal.string("STABLE"))
 1437        ),
 1438        "STORED": lambda self: self._parse_stored(),
 1439        "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(),
 1440        "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(),
 1441        "TEMP": lambda self: self.expression(exp.TemporaryProperty()),
 1442        "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()),
 1443        "TO": lambda self: self._parse_to_table(),
 1444        "TRANSIENT": lambda self: self.expression(exp.TransientProperty()),
 1445        "TRANSFORM": lambda self: self.expression(
 1446            exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression))
 1447        ),
 1448        "TTL": lambda self: self._parse_ttl(),
 1449        "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1450        "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()),
 1451        "VOLATILE": lambda self: self._parse_volatile_property(),
 1452        "WITH": lambda self: self._parse_with_property(),
 1453    }
 1454
 1455    CONSTRAINT_PARSERS: t.ClassVar = {
 1456        "AUTOINCREMENT": lambda self: self._parse_auto_increment(),
 1457        "AUTO_INCREMENT": lambda self: self._parse_auto_increment(),
 1458        "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)),
 1459        "CHECK": lambda self: self._parse_check_constraint(),
 1460        "COLLATE": lambda self: self.expression(
 1461            exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column())
 1462        ),
 1463        "COMMENT": lambda self: self.expression(
 1464            exp.CommentColumnConstraint(this=self._parse_string())
 1465        ),
 1466        "COMPRESS": lambda self: self._parse_compress(),
 1467        "CLUSTERED": lambda self: self.expression(
 1468            exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1469        ),
 1470        "NONCLUSTERED": lambda self: self.expression(
 1471            exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1472        ),
 1473        "DEFAULT": lambda self: self.expression(
 1474            exp.DefaultColumnConstraint(this=self._parse_bitwise())
 1475        ),
 1476        "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())),
 1477        "EPHEMERAL": lambda self: self.expression(
 1478            exp.EphemeralColumnConstraint(this=self._parse_bitwise())
 1479        ),
 1480        "EXCLUDE": lambda self: self.expression(
 1481            exp.ExcludeColumnConstraint(this=self._parse_index_params())
 1482        ),
 1483        "FOREIGN KEY": lambda self: self._parse_foreign_key(),
 1484        "FORMAT": lambda self: self.expression(
 1485            exp.DateFormatColumnConstraint(this=self._parse_var_or_string())
 1486        ),
 1487        "GENERATED": lambda self: self._parse_generated_as_identity(),
 1488        "IDENTITY": lambda self: self._parse_auto_increment(),
 1489        "INLINE": lambda self: self._parse_inline(),
 1490        "LIKE": lambda self: self._parse_create_like(),
 1491        "NOT": lambda self: self._parse_not_constraint(),
 1492        "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)),
 1493        "ON": lambda self: (
 1494            (
 1495                self._match(TokenType.UPDATE)
 1496                and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function()))
 1497            )
 1498            or self.expression(exp.OnProperty(this=self._parse_id_var()))
 1499        ),
 1500        "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())),
 1501        "PERIOD": lambda self: self._parse_period_for_system_time(),
 1502        "PRIMARY KEY": lambda self: self._parse_primary_key(),
 1503        "REFERENCES": lambda self: self._parse_references(match=False),
 1504        "TITLE": lambda self: self.expression(
 1505            exp.TitleColumnConstraint(this=self._parse_var_or_string())
 1506        ),
 1507        "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])),
 1508        "UNIQUE": lambda self: self._parse_unique(),
 1509        "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()),
 1510        "WITH": lambda self: self.expression(
 1511            exp.Properties(expressions=self._parse_wrapped_properties())
 1512        ),
 1513        "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1514        "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1515    }
 1516
 1517    def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None:
 1518        if not self._match(TokenType.L_PAREN, advance=False):
 1519            # Partitioning by bucket or truncate follows the syntax:
 1520            # PARTITION BY (BUCKET(..) | TRUNCATE(..))
 1521            # If we don't have parenthesis after each keyword, we should instead parse this as an identifier
 1522            self._retreat(self._index - 1)
 1523            return None
 1524
 1525        klass = (
 1526            exp.PartitionedByBucket
 1527            if self._prev.text.upper() == "BUCKET"
 1528            else exp.PartitionByTruncate
 1529        )
 1530
 1531        args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column())
 1532        this, expression = seq_get(args, 0), seq_get(args, 1)
 1533
 1534        if isinstance(this, exp.Literal):
 1535            # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order
 1536            #  - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)`
 1537            #  - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)`
 1538            # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)`
 1539            #
 1540            # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning
 1541            # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties
 1542            this, expression = expression, this
 1543
 1544        return self.expression(klass(this=this, expression=expression))
 1545
 1546    ALTER_PARSERS: t.ClassVar = {
 1547        "ADD": lambda self: self._parse_alter_table_add(),
 1548        "AS": lambda self: self._parse_select(),
 1549        "ALTER": lambda self: self._parse_alter_table_alter(),
 1550        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1551        "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())),
 1552        "DROP": lambda self: self._parse_alter_table_drop(),
 1553        "RENAME": lambda self: self._parse_alter_table_rename(),
 1554        "SET": lambda self: self._parse_alter_table_set(),
 1555        "SWAP": lambda self: self.expression(
 1556            exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True))
 1557        ),
 1558    }
 1559
 1560    ALTER_ALTER_PARSERS: t.ClassVar = {
 1561        "DISTKEY": lambda self: self._parse_alter_diststyle(),
 1562        "DISTSTYLE": lambda self: self._parse_alter_diststyle(),
 1563        "SORTKEY": lambda self: self._parse_alter_sortkey(),
 1564        "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True),
 1565    }
 1566
 1567    SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = {
 1568        "CHECK",
 1569        "EXCLUDE",
 1570        "FOREIGN KEY",
 1571        "LIKE",
 1572        "PERIOD",
 1573        "PRIMARY KEY",
 1574        "UNIQUE",
 1575        "BUCKET",
 1576        "TRUNCATE",
 1577    }
 1578
 1579    # Kinds that can't be a constraint name, so `CONSTRAINT` directly followed by one is unnamed
 1580    RESERVED_CONSTRAINT_KINDS: t.ClassVar[set[str]] = set()
 1581
 1582    NO_PAREN_FUNCTION_PARSERS: t.ClassVar = {
 1583        "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())),
 1584        "CASE": lambda self: self._parse_case(),
 1585        "CONNECT_BY_ROOT": lambda self: self.expression(
 1586            exp.ConnectByRoot(this=self._parse_column())
 1587        ),
 1588        "IF": lambda self: self._parse_if(),
 1589    }
 1590
 1591    INVALID_FUNC_NAME_TOKENS: t.ClassVar = {
 1592        TokenType.IDENTIFIER,
 1593        TokenType.STRING,
 1594    }
 1595
 1596    FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"}
 1597
 1598    KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice)
 1599
 1600    FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1601        **{
 1602            name: lambda self: self._parse_distinct_arg_function(exp.ArgMax)
 1603            for name in exp.ArgMax.sql_names()
 1604        },
 1605        **{
 1606            name: lambda self: self._parse_distinct_arg_function(exp.ArgMin)
 1607            for name in exp.ArgMin.sql_names()
 1608        },
 1609        "CAST": lambda self: self._parse_cast(self.STRICT_CAST),
 1610        "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil),
 1611        "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST),
 1612        "CHAR": lambda self: self._parse_char(),
 1613        "CHR": lambda self: self._parse_char(),
 1614        "DECODE": lambda self: self._parse_decode(),
 1615        "EXTRACT": lambda self: self._parse_extract(),
 1616        "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor),
 1617        "GAP_FILL": lambda self: self._parse_gap_fill(),
 1618        "INITCAP": lambda self: self._parse_initcap(),
 1619        "JSON_OBJECT": lambda self: self._parse_json_object(),
 1620        "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True),
 1621        "JSON_TABLE": lambda self: self._parse_json_table(),
 1622        "MATCH": lambda self: self._parse_match_against(),
 1623        "NORMALIZE": lambda self: self._parse_normalize(),
 1624        "OPENJSON": lambda self: self._parse_open_json(),
 1625        "OVERLAY": lambda self: self._parse_overlay(),
 1626        "POSITION": lambda self: self._parse_position(),
 1627        "SAFE_CAST": lambda self: self._parse_cast(False, safe=True),
 1628        "STRING_AGG": lambda self: self._parse_string_agg(),
 1629        "SUBSTRING": lambda self: self._parse_substring(),
 1630        "TRIM": lambda self: self._parse_trim(),
 1631        "TRY_CAST": lambda self: self._parse_cast(False, safe=True),
 1632        "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True),
 1633        "WEIGHT_STRING": lambda self: self._parse_weight_string(),
 1634        "XMLELEMENT": lambda self: self._parse_xml_element(),
 1635        "XMLTABLE": lambda self: self._parse_xml_table(),
 1636    }
 1637
 1638    QUERY_MODIFIER_PARSERS: t.ClassVar = {
 1639        TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()),
 1640        TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()),
 1641        TokenType.WHERE: lambda self: ("where", self._parse_where()),
 1642        TokenType.GROUP_BY: lambda self: ("group", self._parse_group()),
 1643        TokenType.HAVING: lambda self: ("having", self._parse_having()),
 1644        TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()),
 1645        TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()),
 1646        TokenType.ORDER_BY: lambda self: ("order", self._parse_order()),
 1647        TokenType.LIMIT: lambda self: ("limit", self._parse_limit()),
 1648        TokenType.FETCH: lambda self: ("limit", self._parse_limit()),
 1649        TokenType.OFFSET: lambda self: ("offset", self._parse_offset()),
 1650        TokenType.FOR: lambda self: ("locks", self._parse_locks()),
 1651        TokenType.LOCK: lambda self: ("locks", self._parse_locks()),
 1652        TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1653        TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1654        TokenType.CLUSTER_BY: lambda self: (
 1655            "cluster",
 1656            self._parse_cluster(),
 1657        ),
 1658        TokenType.DISTRIBUTE_BY: lambda self: (
 1659            "distribute",
 1660            self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY),
 1661        ),
 1662        TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)),
 1663        TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)),
 1664    }
 1665    QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS)
 1666
 1667    SET_PARSERS: t.ClassVar = {
 1668        "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"),
 1669        "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"),
 1670        "SESSION": lambda self: self._parse_set_item_assignment("SESSION"),
 1671        "TRANSACTION": lambda self: self._parse_set_transaction(),
 1672    }
 1673
 1674    SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {}
 1675
 1676    TYPE_LITERAL_PARSERS: t.ClassVar = {
 1677        exp.DType.JSON: lambda self, this, _: self.expression(
 1678            exp.ParseJSON(this=this, is_literal=True)
 1679        ),
 1680    }
 1681
 1682    TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {}
 1683
 1684    DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN}
 1685
 1686    PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE}
 1687
 1688    TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"}
 1689    TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = {
 1690        "ISOLATION": (
 1691            ("LEVEL", "REPEATABLE", "READ"),
 1692            ("LEVEL", "READ", "COMMITTED"),
 1693            ("LEVEL", "READ", "UNCOMITTED"),
 1694            ("LEVEL", "SERIALIZABLE"),
 1695        ),
 1696        "READ": ("WRITE", "ONLY"),
 1697    }
 1698
 1699    CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1700        **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()),
 1701        "DO": ("NOTHING", "UPDATE"),
 1702    }
 1703
 1704    TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = {
 1705        "INSTEAD": (("OF",),),
 1706        "BEFORE": tuple(),
 1707        "AFTER": tuple(),
 1708    }
 1709
 1710    TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = {
 1711        "NOT": (("DEFERRABLE",),),
 1712        "DEFERRABLE": tuple(),
 1713    }
 1714
 1715    CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = {
 1716        "SCALE": ("EXTEND", "NOEXTEND"),
 1717        "SHARD": ("EXTEND", "NOEXTEND"),
 1718        "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"),
 1719        **dict.fromkeys(
 1720            (
 1721                "SESSION",
 1722                "GLOBAL",
 1723                "KEEP",
 1724                "NOKEEP",
 1725                "ORDER",
 1726                "NOORDER",
 1727                "NOCACHE",
 1728                "CYCLE",
 1729                "NOCYCLE",
 1730                "NOMINVALUE",
 1731                "NOMAXVALUE",
 1732                "NOSCALE",
 1733                "NOSHARD",
 1734            ),
 1735            tuple(),
 1736        ),
 1737    }
 1738
 1739    ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")}
 1740
 1741    USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1742        ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple()
 1743    )
 1744
 1745    CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",))
 1746
 1747    SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1748        "TYPE": ("EVOLUTION",),
 1749        **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()),
 1750    }
 1751
 1752    PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {}
 1753
 1754    EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1755        ("CALLER", "SELF", "OWNER"), tuple()
 1756    )
 1757
 1758    KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1759        "NOT": ("ENFORCED",),
 1760        "MATCH": (
 1761            "FULL",
 1762            "PARTIAL",
 1763            "SIMPLE",
 1764        ),
 1765        "INITIALLY": ("DEFERRED", "IMMEDIATE"),
 1766        "USING": (
 1767            "BTREE",
 1768            "HASH",
 1769        ),
 1770        **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()),
 1771    }
 1772
 1773    WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1774        "NO": ("OTHERS",),
 1775        "CURRENT": ("ROW",),
 1776        **dict.fromkeys(("GROUP", "TIES"), tuple()),
 1777    }
 1778
 1779    INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"}
 1780
 1781    CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"}
 1782    # Time travel clause prefixes, mapped to whether they pin a timestamp or a version
 1783    VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = {
 1784        ("FOR", "SYSTEM_TIME"): "TIMESTAMP",
 1785        ("FOR", "SYSTEM", "TIME"): "TIMESTAMP",
 1786        ("FOR", "TIMESTAMP"): "TIMESTAMP",
 1787        ("FOR", "VERSION"): "VERSION",
 1788        ("TIMESTAMP", "AS", "OF"): "TIMESTAMP",
 1789        ("VERSION", "AS", "OF"): "VERSION",
 1790    }
 1791
 1792    HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"}
 1793    HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"}
 1794
 1795    OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"}
 1796
 1797    OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN}
 1798
 1799    TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE}
 1800
 1801    VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"}
 1802
 1803    WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS}
 1804    WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER}
 1805    WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"}
 1806
 1807    JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS}
 1808
 1809    FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT}
 1810
 1811    ADD_CONSTRAINT_TOKENS: t.ClassVar = {
 1812        TokenType.CONSTRAINT,
 1813        TokenType.FOREIGN_KEY,
 1814        TokenType.INDEX,
 1815        TokenType.KEY,
 1816        TokenType.PRIMARY_KEY,
 1817        TokenType.UNIQUE,
 1818    }
 1819
 1820    # Constraint kinds that start an ALTER ... ADD action but aren't tokens of their own
 1821    ADD_CONSTRAINT_KEYWORDS: t.ClassVar[set[str]] = set()
 1822
 1823    DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT}
 1824
 1825    UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS
 1826
 1827    SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT}
 1828
 1829    COPY_INTO_VARLEN_OPTIONS: t.ClassVar = {
 1830        "FILE_FORMAT",
 1831        "COPY_OPTIONS",
 1832        "FORMAT_OPTIONS",
 1833        "CREDENTIAL",
 1834    }
 1835
 1836    # IS binds above comparisons (high), alongside ordering comparisons (comparison),
 1837    # alongside equality (equality), or below both (low). Preserve the historical
 1838    # high precedence unless the dialect specifies otherwise.
 1839    IS_PREDICATE_PRECEDENCE: t.ClassVar[IsPredicatePrecedence] = IsPredicatePrecedence.HIGH
 1840    IS_PREDICATE_TOKENS: t.ClassVar = {
 1841        TokenType.IS,
 1842        TokenType.ISNULL,
 1843        TokenType.NOTNULL,
 1844        TokenType.NOT,
 1845    }
 1846
 1847    IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"}
 1848
 1849    ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {}
 1850
 1851    ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"}
 1852
 1853    PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN}
 1854
 1855    # The style options for the DESCRIBE statement
 1856    DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"}
 1857
 1858    SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"}
 1859
 1860    # The style options for the ANALYZE statement
 1861    ANALYZE_STYLES: t.ClassVar = {
 1862        "BUFFER_USAGE_LIMIT",
 1863        "FULL",
 1864        "LOCAL",
 1865        "NO_WRITE_TO_BINLOG",
 1866        "SAMPLE",
 1867        "SKIP_LOCKED",
 1868        "VERBOSE",
 1869    }
 1870
 1871    ANALYZE_EXPRESSION_PARSERS: t.ClassVar = {
 1872        "ALL": lambda self: self._parse_analyze_columns(),
 1873        "COMPUTE": lambda self: self._parse_analyze_statistics(),
 1874        "DELETE": lambda self: self._parse_analyze_delete(),
 1875        "DROP": lambda self: self._parse_analyze_histogram(),
 1876        "ESTIMATE": lambda self: self._parse_analyze_statistics(),
 1877        "LIST": lambda self: self._parse_analyze_list(),
 1878        "PREDICATE": lambda self: self._parse_analyze_columns(),
 1879        "UPDATE": lambda self: self._parse_analyze_histogram(),
 1880        "VALIDATE": lambda self: self._parse_analyze_validate(),
 1881    }
 1882
 1883    PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"}
 1884
 1885    AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET)
 1886
 1887    OPERATION_MODIFIERS: t.ClassVar[set[str]] = set()
 1888
 1889    RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"}
 1890
 1891    SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"}
 1892
 1893    MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values)
 1894
 1895    STRICT_CAST: t.ClassVar = True
 1896
 1897    PREFIXED_PIVOT_COLUMNS: t.ClassVar = False
 1898    IDENTIFY_PIVOT_STRINGS: t.ClassVar = False
 1899    # Whether an UNPIVOT outputs its value column(s) before the name column
 1900    UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False
 1901    # Controls when an aggregation's name is included in a pivoted column's name:
 1902    # "agg_name_if_aliased" - only for aggregations that carry an explicit alias
 1903    # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations
 1904    # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only)
 1905    PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased"
 1906
 1907    LOG_DEFAULTS_TO_LN: t.ClassVar = False
 1908
 1909    # Whether the table sample clause expects CSV syntax
 1910    TABLESAMPLE_CSV: t.ClassVar = False
 1911
 1912    # The default method used for table sampling
 1913    DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None
 1914
 1915    # Whether the SET command needs a delimiter (e.g. "=") for assignments
 1916    SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True
 1917
 1918    # Whether the TRIM function expects the characters to trim as its first argument
 1919    TRIM_PATTERN_FIRST: t.ClassVar = False
 1920
 1921    # Whether string aliases are supported `SELECT COUNT(*) 'count'`
 1922    STRING_ALIASES: t.ClassVar = False
 1923
 1924    # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand)
 1925    MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True
 1926    SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"}
 1927
 1928    # Whether to parse IF statements that aren't followed by a left parenthesis as commands
 1929    NO_PAREN_IF_COMMANDS: t.ClassVar = True
 1930
 1931    # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres)
 1932    JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False
 1933
 1934    # Whether the `:` operator is used to extract a value from a VARIANT column
 1935    COLON_IS_VARIANT_EXTRACT: t.ClassVar = False
 1936
 1937    # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged
 1938    # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks)
 1939    COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True
 1940
 1941    # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause.
 1942    # If this is True and '(' is not found, the keyword will be treated as an identifier
 1943    VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True
 1944
 1945    # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift)
 1946    SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False
 1947
 1948    # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery)
 1949    SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False
 1950
 1951    # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS
 1952    INTERVAL_SPANS: t.ClassVar = True
 1953
 1954    # Whether a PARTITION clause can follow a table reference
 1955    SUPPORTS_PARTITION_SELECTION: t.ClassVar = False
 1956
 1957    # Whether the `name AS expr` schema/column constraint requires parentheses around `expr`
 1958    WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True
 1959
 1960    # Whether the 'AS' keyword is optional in the CTE definition syntax
 1961    OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True
 1962
 1963    # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword
 1964    ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True
 1965
 1966    # Whether dropping a column with an ALTER statement requires the presence of the COLUMN keyword
 1967    ALTER_DROP_REQUIRES_COLUMN: t.ClassVar = True
 1968
 1969    # Whether Alter statements are allowed to contain Partition specifications
 1970    ALTER_TABLE_PARTITIONS: t.ClassVar = False
 1971
 1972    # Whether an ALTER can combine different actions, e.g. ADD COLUMN a INT, DROP COLUMN b
 1973    ALTER_TABLE_MIXED_ACTIONS: t.ClassVar = False
 1974
 1975    # Whether an ALTER TABLE needs an action, e.g. MySQL's ALTER TABLE t ENGINE=InnoDB has none
 1976    ALTER_TABLE_REQUIRES_ACTION: t.ClassVar = True
 1977
 1978    # Whether changing a column's type with ALTER COLUMN requires the TYPE keyword
 1979    ALTER_COLUMN_TYPE_REQUIRES_KEYWORD: t.ClassVar = False
 1980
 1981    # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree.
 1982    # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is
 1983    # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such
 1984    # as BigQuery, where all joins have the same precedence.
 1985    JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False
 1986
 1987    # Whether TIMESTAMP <literal> can produce a zone-aware timestamp
 1988    ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False
 1989
 1990    # Whether map literals support arbitrary expressions as keys.
 1991    # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB).
 1992    # When False, keys are typically restricted to identifiers.
 1993    MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False
 1994
 1995    # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this
 1996    # is true for Snowflake but not for BigQuery which can also process strings
 1997    JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False
 1998
 1999    # Dialects like Databricks support JOINS without join criteria
 2000    # Adding an ON TRUE, makes transpilation semantically correct for other dialects
 2001    ADD_JOIN_ON_TRUE: t.ClassVar = False
 2002
 2003    # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]'
 2004    # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND`
 2005    SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False
 2006
 2007    # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them
 2008    # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation.
 2009    ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False
 2010
 2011    # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause,
 2012    # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake)
 2013    SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False
 2014
 2015    # Type names that denote a different type when they're quoted, so quoting has to be
 2016    # preserved instead of resolving them into the built-in type of the same name. These
 2017    # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR
 2018    QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set()
 2019
 2020    SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS)
 2021    SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS)
 2022
 2023    def __init__(
 2024        self,
 2025        error_level: ErrorLevel | None = None,
 2026        error_message_context: int = 100,
 2027        max_errors: int = 3,
 2028        max_nodes: int = -1,
 2029        dialect: DialectType = None,
 2030    ):
 2031        self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE
 2032        self.error_message_context: int = error_message_context
 2033        self.max_errors: int = max_errors
 2034        self.max_nodes: int = max_nodes
 2035        self.dialect: t.Any = _resolve_dialect(dialect)
 2036        self.sql: str = ""
 2037        self.errors: list[ParseError] = []
 2038        self._tokens: list[Token] = []
 2039        self._tokens_size: i64 = 0
 2040        self._index: i64 = 0
 2041        self._curr: Token = SENTINEL_NONE
 2042        self._next: Token = SENTINEL_NONE
 2043        self._prev: Token = SENTINEL_NONE
 2044        self._prev_comments: list[str] = []
 2045        self._pipe_cte_counter: int = 0
 2046        self._chunks: list[list[Token]] = []
 2047        self._chunk_index: i64 = 0
 2048        self._node_count: int = 0
 2049
 2050    def reset(self) -> None:
 2051        self.sql = ""
 2052        self.errors = []
 2053        self._tokens = []
 2054        self._tokens_size = 0
 2055        self._index = 0
 2056        self._curr = SENTINEL_NONE
 2057        self._next = SENTINEL_NONE
 2058        self._prev = SENTINEL_NONE
 2059        self._prev_comments = []
 2060        self._pipe_cte_counter = 0
 2061        self._chunks = []
 2062        self._chunk_index = 0
 2063        self._node_count = 0
 2064
 2065    def _advance(self, times: i64 = 1) -> None:
 2066        index = self._index + times
 2067        self._index = index
 2068        tokens = self._tokens
 2069        size = self._tokens_size
 2070        self._curr = tokens[index] if index < size else SENTINEL_NONE
 2071        self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE
 2072
 2073        if index > 0:
 2074            prev = tokens[index - 1]
 2075            self._prev = prev
 2076            self._prev_comments = prev.comments
 2077        else:
 2078            self._prev = SENTINEL_NONE
 2079            self._prev_comments = []
 2080
 2081    def _advance_chunk(self) -> None:
 2082        self._index = -1
 2083        self._tokens = self._chunks[self._chunk_index]
 2084        self._tokens_size = i64(len(self._tokens))
 2085        self._chunk_index += 1
 2086        self._advance()
 2087
 2088    def _retreat(self, index: i64) -> None:
 2089        if index != self._index:
 2090            self._advance(index - self._index)
 2091
 2092    def _add_comments(self, expression: exp.Expr | None) -> None:
 2093        if expression and self._prev_comments:
 2094            expression.add_comments(self._prev_comments)
 2095            self._prev_comments = []
 2096
 2097    def _match(
 2098        self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None
 2099    ) -> bool:
 2100        if self._curr.token_type == token_type:
 2101            if advance:
 2102                self._advance()
 2103            self._add_comments(expression)
 2104            return True
 2105        return False
 2106
 2107    def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool:
 2108        if self._curr.token_type in types:
 2109            if advance:
 2110                self._advance()
 2111            return True
 2112        return False
 2113
 2114    def _match_pair(
 2115        self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True
 2116    ) -> bool:
 2117        if self._curr.token_type == token_type_a and self._next.token_type == token_type_b:
 2118            if advance:
 2119                self._advance(2)
 2120            return True
 2121        return False
 2122
 2123    def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool:
 2124        if (
 2125            self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS
 2126            and self._curr.text.upper() in texts
 2127        ):
 2128            if advance:
 2129                self._advance()
 2130            return True
 2131        return False
 2132
 2133    def _match_text_seq(self, *texts: str, advance: bool = True) -> bool:
 2134        index = self._index
 2135        excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS
 2136        for text in texts:
 2137            if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text:
 2138                self._advance()
 2139            else:
 2140                self._retreat(index)
 2141                return False
 2142
 2143        if not advance:
 2144            self._retreat(index)
 2145
 2146        return True
 2147
 2148    def _is_connected(self) -> bool:
 2149        prev = self._prev
 2150        curr = self._curr
 2151        return bool(prev and curr and prev.end + 1 == curr.start)
 2152
 2153    def _find_sql(self, start: Token, end: Token) -> str:
 2154        return self.sql[start.start : end.end + 1]
 2155
 2156    def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None:
 2157        token = token or self._curr or self._prev or Token.string("")
 2158        formatted_sql, start_context, highlight, end_context = highlight_sql(
 2159            sql=self.sql,
 2160            positions=[(token.start, token.end)],
 2161            context_length=self.error_message_context,
 2162        )
 2163        formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n  {formatted_sql}"
 2164
 2165        error = ParseError.new(
 2166            formatted_message,
 2167            description=message,
 2168            line=token.line,
 2169            col=token.col,
 2170            start_context=start_context,
 2171            highlight=highlight,
 2172            end_context=end_context,
 2173        )
 2174
 2175        if self.error_level == ErrorLevel.IMMEDIATE:
 2176            raise error
 2177
 2178        self.errors.append(error)
 2179
 2180    def validate_expression(self, expression: E, args: list | None = None) -> E:
 2181        if self.max_nodes > -1:
 2182            self._node_count += 1
 2183            if self._node_count > self.max_nodes:
 2184                self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded")
 2185        if self.error_level != ErrorLevel.IGNORE:
 2186            for error_message in expression.error_messages(args):
 2187                self.raise_error(error_message)
 2188        return expression
 2189
 2190    def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None:
 2191        index = self._index
 2192        error_level = self.error_level
 2193        this: T | None = None
 2194
 2195        self.error_level = ErrorLevel.IMMEDIATE
 2196        try:
 2197            this = parse_method()
 2198        except ParseError:
 2199            this = None
 2200        finally:
 2201            if not this or retreat:
 2202                self._retreat(index)
 2203            self.error_level = error_level
 2204
 2205        return this
 2206
 2207    def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]:
 2208        """
 2209        Parses a list of tokens and returns a list of syntax trees, one tree
 2210        per parsed SQL statement.
 2211
 2212        Args:
 2213            raw_tokens: The list of tokens.
 2214            sql: The original SQL string.
 2215
 2216        Returns:
 2217            The list of the produced syntax trees.
 2218        """
 2219        return self._parse(
 2220            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
 2221        )
 2222
 2223    def parse_into(
 2224        self,
 2225        expression_types: exp.IntoType,
 2226        raw_tokens: list[Token],
 2227        sql: str | None = None,
 2228    ) -> list[exp.Expr | None]:
 2229        """
 2230        Parses a list of tokens into a given Expr type. If a collection of Expr
 2231        types is given instead, this method will try to parse the token list into each one
 2232        of them, stopping at the first for which the parsing succeeds.
 2233
 2234        Args:
 2235            expression_types: The expression type(s) to try and parse the token list into.
 2236            raw_tokens: The list of tokens.
 2237            sql: The original SQL string, used to produce helpful debug messages.
 2238
 2239        Returns:
 2240            The target Expr.
 2241        """
 2242        errors = []
 2243        for expression_type in ensure_list(expression_types):
 2244            parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type))
 2245            if not parser:
 2246                raise TypeError(f"No parser registered for {expression_type}")
 2247
 2248            try:
 2249                return self._parse(parser, raw_tokens, sql)
 2250            except ParseError as e:
 2251                e.errors[0]["into_expression"] = expression_type
 2252                errors.append(e)
 2253
 2254        raise ParseError(
 2255            f"Failed to parse '{sql or raw_tokens}' into {expression_types}",
 2256            errors=merge_errors(errors),
 2257        ) from errors[-1]
 2258
 2259    def check_errors(self) -> None:
 2260        """Logs or raises any found errors, depending on the chosen error level setting."""
 2261        if self.error_level == ErrorLevel.WARN:
 2262            for error in self.errors:
 2263                logger.error(str(error))
 2264        elif self.error_level == ErrorLevel.RAISE and self.errors:
 2265            raise ParseError(
 2266                concat_messages(self.errors, self.max_errors),
 2267                errors=merge_errors(self.errors),
 2268            )
 2269
 2270    def expression(
 2271        self,
 2272        instance: E,
 2273        token: Token | None = None,
 2274        comments: list[str] | None = None,
 2275    ) -> E:
 2276        if token:
 2277            instance.update_positions(token)
 2278        instance.add_comments(comments) if comments else self._add_comments(instance)
 2279        if not instance.is_primitive:
 2280            instance = self.validate_expression(instance)
 2281        return instance
 2282
 2283    def _parse_batch_statements(
 2284        self,
 2285        parse_method: t.Callable[[Parser], exp.Expr | None],
 2286        sep_first_statement: bool = True,
 2287    ) -> list[exp.Expr | None]:
 2288        expressions = []
 2289
 2290        # Chunkification binds if/while statements with the first statement of the body
 2291        if sep_first_statement:
 2292            self._match(TokenType.BEGIN)
 2293            expressions.append(parse_method(self))
 2294
 2295        chunks_length = len(self._chunks)
 2296        while self._chunk_index < chunks_length:
 2297            self._advance_chunk()
 2298
 2299            if self._match(TokenType.ELSE, advance=False):
 2300                return expressions
 2301
 2302            if expressions and not self._next and self._match(TokenType.END):
 2303                expressions.append(exp.EndStatement())
 2304                continue
 2305
 2306            expressions.append(parse_method(self))
 2307
 2308            if self._index < self._tokens_size:
 2309                self.raise_error("Invalid expression / Unexpected token")
 2310
 2311            self.check_errors()
 2312
 2313        return expressions
 2314
 2315    def _parse(
 2316        self,
 2317        parse_method: t.Callable[[Parser], exp.Expr | None],
 2318        raw_tokens: list[Token],
 2319        sql: str | None = None,
 2320    ) -> list[exp.Expr | None]:
 2321        self.reset()
 2322        self.sql = sql or ""
 2323
 2324        total = len(raw_tokens)
 2325        chunks: list[list[Token]] = [[]]
 2326
 2327        for i, token in enumerate(raw_tokens):
 2328            if token.token_type == TokenType.SEMICOLON:
 2329                if token.comments:
 2330                    chunks.append([token])
 2331
 2332                if i < total - 1:
 2333                    chunks.append([])
 2334            else:
 2335                chunks[-1].append(token)
 2336
 2337        self._chunks = chunks
 2338
 2339        return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False)
 2340
 2341    def _warn_unsupported(self) -> None:
 2342        if self._tokens_size <= 1:
 2343            return
 2344
 2345        # We use _find_sql because self.sql may comprise multiple chunks, and we're only
 2346        # interested in emitting a warning for the one being currently processed.
 2347        sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context]
 2348
 2349        logger.warning(
 2350            f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'."
 2351        )
 2352
 2353    def _parse_command(self) -> exp.Command:
 2354        self._warn_unsupported()
 2355        comments = self._prev_comments
 2356        return self.expression(
 2357            exp.Command(this=self._prev.text.upper(), expression=self._parse_string()),
 2358            comments=comments,
 2359        )
 2360
 2361    def _parse_comment(self, allow_exists: bool = True) -> exp.Expr:
 2362        start = self._prev
 2363        exists = self._parse_exists() if allow_exists else None
 2364
 2365        self._match(TokenType.ON)
 2366
 2367        materialized = self._match_text_seq("MATERIALIZED")
 2368        kind = self._match_set(self.CREATABLES) and self._prev
 2369        if not kind:
 2370            return self._parse_as_command(start)
 2371
 2372        if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2373            this = self._parse_user_defined_function(kind=kind.token_type)
 2374        elif kind.token_type == TokenType.TABLE:
 2375            this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS)
 2376        elif kind.token_type == TokenType.COLUMN:
 2377            this = self._parse_column()
 2378        else:
 2379            this = self._parse_table_parts(schema=True)
 2380
 2381        self._match(TokenType.IS)
 2382
 2383        return self.expression(
 2384            exp.Comment(
 2385                this=this,
 2386                kind=kind.text,
 2387                expression=self._parse_string(),
 2388                exists=exists,
 2389                materialized=materialized,
 2390            )
 2391        )
 2392
 2393    def _parse_to_table(
 2394        self,
 2395    ) -> exp.ToTableProperty:
 2396        table = self._parse_table_parts(schema=True)
 2397        return self.expression(exp.ToTableProperty(this=table))
 2398
 2399    # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl
 2400    def _parse_ttl(self) -> exp.Expr:
 2401        def _parse_ttl_action() -> exp.Expr | None:
 2402            this = self._parse_bitwise()
 2403
 2404            if self._match_text_seq("DELETE"):
 2405                return self.expression(exp.MergeTreeTTLAction(this=this, delete=True))
 2406            if self._match_text_seq("RECOMPRESS"):
 2407                return self.expression(
 2408                    exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise())
 2409                )
 2410            if self._match_text_seq("TO", "DISK"):
 2411                return self.expression(
 2412                    exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string())
 2413                )
 2414            if self._match_text_seq("TO", "VOLUME"):
 2415                return self.expression(
 2416                    exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string())
 2417                )
 2418
 2419            return this
 2420
 2421        expressions = self._parse_csv(_parse_ttl_action)
 2422        where = self._parse_where()
 2423        group = self._parse_group()
 2424
 2425        aggregates = None
 2426        if group and self._match(TokenType.SET):
 2427            aggregates = self._parse_csv(self._parse_set_item)
 2428
 2429        return self.expression(
 2430            exp.MergeTreeTTL(
 2431                expressions=expressions, where=where, group=group, aggregates=aggregates
 2432            )
 2433        )
 2434
 2435    def _parse_condition(self) -> exp.Expr | None:
 2436        return self._parse_wrapped(parse_method=self._parse_expression, optional=True)
 2437
 2438    def _parse_block(self) -> exp.Block:
 2439        return self.expression(
 2440            exp.Block(
 2441                expressions=self._parse_batch_statements(
 2442                    parse_method=lambda self: self._parse_statement()
 2443                )
 2444            )
 2445        )
 2446
 2447    def _parse_whileblock(self) -> exp.WhileBlock:
 2448        return self.expression(
 2449            exp.WhileBlock(this=self._parse_condition(), body=self._parse_block())
 2450        )
 2451
 2452    def _parse_statement(self) -> exp.Expr | None:
 2453        if not self._curr:
 2454            return None
 2455
 2456        if self._match_set(self.STATEMENT_PARSERS):
 2457            comments = self._prev_comments
 2458            stmt = self.STATEMENT_PARSERS[self._prev.token_type](self)
 2459            stmt.add_comments(comments, prepend=True)
 2460            return stmt
 2461
 2462        if self._match_set(self.dialect.tokenizer_class.COMMANDS):
 2463            return self._parse_command()
 2464
 2465        if self._match_text_seq("WHILE"):
 2466            return self._parse_whileblock()
 2467
 2468        expression = self._parse_expression()
 2469        expression = self._parse_set_operations(expression) if expression else self._parse_select()
 2470
 2471        if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False):
 2472            expression = self._parse_pipe_syntax_query(expression)
 2473
 2474        return self._parse_query_modifiers(expression)
 2475
 2476    def _parse_drop(self, exists: bool = False, kind: str | None = None) -> exp.Drop | exp.Command:
 2477        start = self._prev
 2478
 2479        if not kind:
 2480            temporary = self._match(TokenType.TEMPORARY)
 2481            materialized = self._match_text_seq("MATERIALIZED")
 2482            iceberg = self._match_text_seq("ICEBERG")
 2483            kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None
 2484        else:
 2485            temporary = False
 2486            materialized = False
 2487            iceberg = False
 2488
 2489        if not kind or (iceberg and kind != "TABLE"):
 2490            return self._parse_as_command(start)
 2491
 2492        concurrently = (
 2493            kind == "INDEX"
 2494            and self._next.token_type in self.ID_VAR_TOKENS
 2495            and self._match_text_seq("CONCURRENTLY")
 2496        )
 2497
 2498        if_exists = exists or self._parse_exists()
 2499
 2500        tables: exp.Expr | list[exp.Expr] | None
 2501        if kind == "COLUMN":
 2502            # A dropped column named after a keyword, like `any` or `charset`, is only a name
 2503            name = (
 2504                None
 2505                if self._curr.text.upper() in self.NO_PAREN_FUNCTION_PARSERS
 2506                else self._parse_column()
 2507            ) or self._parse_id_var()
 2508            tables = (
 2509                self.expression(exp.Column(this=name)) if isinstance(name, exp.Identifier) else name
 2510            )
 2511        elif kind in ("TABLE", "VIEW"):
 2512            tables = self._parse_csv(lambda: self._parse_table_parts(schema=True))
 2513        else:
 2514            tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA")
 2515
 2516        cluster: exp.Expr | None = None
 2517        if self._match(TokenType.ON):
 2518            if kind in ("INDEX", "TRIGGER"):
 2519                cluster = self.expression(exp.OnProperty(this=self._parse_table_parts()))
 2520            else:
 2521                cluster = self._parse_on_property()
 2522
 2523        if self._match(TokenType.L_PAREN, advance=False):
 2524            expressions = self._parse_wrapped_csv(self._parse_types)
 2525        else:
 2526            expressions = None
 2527
 2528        cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper()
 2529
 2530        return self.expression(
 2531            exp.Drop(
 2532                exists=if_exists,
 2533                tables=ensure_list(tables),
 2534                expressions=expressions,
 2535                kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind,
 2536                temporary=temporary,
 2537                materialized=materialized,
 2538                cascade=cascade_or_restrict == "CASCADE",
 2539                restrict=cascade_or_restrict == "RESTRICT",
 2540                constraints=self._match_text_seq("CONSTRAINTS"),
 2541                purge=self._match_text_seq("PURGE"),
 2542                cluster=cluster,
 2543                concurrently=concurrently,
 2544                sync=self._match_text_seq("SYNC"),
 2545                iceberg=iceberg,
 2546                force=self._match_text_seq("FORCE"),
 2547            )
 2548        )
 2549
 2550    def _parse_exists(self, not_: bool = False) -> bool | None:
 2551        if not_:
 2552            return self._match_text_seq("IF", "NOT", "EXISTS")
 2553        return self._match_text_seq("IF", "EXISTS")
 2554
 2555    def _parse_create(self) -> exp.Create | exp.Command:
 2556        # Note: this can't be None because we've matched a statement parser
 2557        start = self._prev
 2558
 2559        replace = (
 2560            start.token_type == TokenType.REPLACE
 2561            or self._match_pair(TokenType.OR, TokenType.REPLACE)
 2562            or self._match_pair(TokenType.OR, TokenType.ALTER)
 2563        )
 2564        refresh = self._match_pair(TokenType.OR, TokenType.REFRESH)
 2565
 2566        unique = self._match(TokenType.UNIQUE)
 2567
 2568        if self._match_text_seq("CLUSTERED", "COLUMNSTORE"):
 2569            clustered = True
 2570        elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq(
 2571            "COLUMNSTORE"
 2572        ):
 2573            clustered = False
 2574        else:
 2575            clustered = None
 2576
 2577        if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False):
 2578            self._advance()
 2579
 2580        properties = None
 2581        create_token = self._match_set(self.CREATABLES) and self._prev
 2582
 2583        if not create_token:
 2584            # exp.Properties.Location.POST_CREATE
 2585            properties = self._parse_properties()
 2586            create_token = self._match_set(self.CREATABLES) and self._prev
 2587
 2588            if not properties or not create_token:
 2589                return self._parse_as_command(start)
 2590
 2591        create_token_type = t.cast(Token, create_token).token_type
 2592
 2593        concurrently = create_token_type == TokenType.INDEX and self._match_text_seq("CONCURRENTLY")
 2594        exists = self._parse_exists(not_=True)
 2595        this = None
 2596        expression: exp.Expr | None = None
 2597        indexes = None
 2598        no_schema_binding = None
 2599        begin = None
 2600        clone = None
 2601
 2602        def extend_props(temp_props: exp.Properties | None) -> None:
 2603            nonlocal properties
 2604            if properties and temp_props:
 2605                properties.expressions.extend(temp_props.expressions)
 2606            elif temp_props:
 2607                properties = temp_props
 2608
 2609        if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2610            this = self._parse_user_defined_function(kind=create_token_type)
 2611
 2612            # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature)
 2613            extend_props(self._parse_properties())
 2614
 2615            expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None
 2616
 2617            if (
 2618                not expression
 2619                and create_token_type == TokenType.FUNCTION
 2620                and isinstance(this, exp.UserDefinedFunction)
 2621                and this.args.get("wrapped")
 2622            ):
 2623                pre_table_index = self._index
 2624                is_table = self._match(TokenType.TABLE)
 2625
 2626                expression = self._parse_expression()
 2627                overload_mode = bool(
 2628                    expression
 2629                    and self._curr.token_type == TokenType.COMMA
 2630                    and self._next.token_type == TokenType.L_PAREN
 2631                )
 2632                if not overload_mode:
 2633                    self._retreat(pre_table_index)
 2634                    is_table = False
 2635                    expression = None
 2636            else:
 2637                is_table = False
 2638                overload_mode = False
 2639
 2640            extend_props(self._parse_function_properties())
 2641
 2642            if not expression:
 2643                if self._match(TokenType.COMMAND):
 2644                    expression = self._parse_as_command(self._prev)
 2645                else:
 2646                    begin = self._match(TokenType.BEGIN)
 2647                    return_ = self._match_text_seq("RETURN")
 2648
 2649                    if self._match(TokenType.STRING, advance=False):
 2650                        # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property
 2651                        # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement
 2652                        expression = self._parse_string()
 2653                        extend_props(self._parse_properties())
 2654                    else:
 2655                        expression = (
 2656                            self._parse_user_defined_function_expression()
 2657                            if create_token_type == TokenType.FUNCTION
 2658                            else self._parse_block()
 2659                        )
 2660
 2661                    if return_:
 2662                        expression = self.expression(exp.Return(this=expression))
 2663
 2664            if overload_mode and expression:
 2665                expression = self._parse_macro_overloads(
 2666                    t.cast(exp.UserDefinedFunction, this), expression, is_table
 2667                )
 2668        elif create_token_type == TokenType.INDEX:
 2669            # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c)
 2670            if not self._match(TokenType.ON):
 2671                index = self._parse_id_var()
 2672                anonymous = False
 2673            else:
 2674                index = None
 2675                anonymous = True
 2676
 2677            this = self._parse_index(index=index, anonymous=anonymous)
 2678        elif (
 2679            create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER)
 2680        ) or create_token_type == TokenType.TRIGGER:
 2681            if is_constraint := (create_token_type == TokenType.CONSTRAINT):
 2682                create_token = self._prev
 2683
 2684            trigger_name = self._parse_id_var()
 2685            if not trigger_name:
 2686                return self._parse_as_command(start)
 2687
 2688            timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False)
 2689            timing = timing_var.this if timing_var else None
 2690            if not timing:
 2691                return self._parse_as_command(start)
 2692
 2693            events = self._parse_trigger_events()
 2694            if not self._match(TokenType.ON):
 2695                self.raise_error("Expected ON in trigger definition")
 2696
 2697            table = self._parse_table_parts()
 2698            referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None
 2699            deferrable, initially = self._parse_trigger_deferrable()
 2700            referencing = self._parse_trigger_referencing()
 2701            for_each = self._parse_trigger_for_each()
 2702            when = self._match_text_seq("WHEN") and self._parse_wrapped(
 2703                self._parse_disjunction, optional=True
 2704            )
 2705            execute = self._parse_trigger_execute()
 2706
 2707            if execute is None:
 2708                return self._parse_as_command(start)
 2709
 2710            trigger_props = self.expression(
 2711                exp.TriggerProperties(
 2712                    table=table,
 2713                    timing=timing,
 2714                    events=events,
 2715                    execute=execute,
 2716                    constraint=is_constraint,
 2717                    referenced_table=referenced_table,
 2718                    deferrable=deferrable,
 2719                    initially=initially,
 2720                    referencing=referencing,
 2721                    for_each=for_each,
 2722                    when=when,
 2723                )
 2724            )
 2725
 2726            this = trigger_name
 2727            extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else []))
 2728        elif create_token_type == TokenType.TYPE:
 2729            this = self._parse_table_parts(schema=True)
 2730            if not this or not self._match(TokenType.ALIAS):
 2731                return self._parse_as_command(start)
 2732
 2733            if self._match(TokenType.ENUM):
 2734                expression = exp.DataType(
 2735                    this=exp.DType.ENUM,
 2736                    expressions=self._parse_wrapped_csv(self._parse_string),
 2737                )
 2738            elif self._match(TokenType.L_PAREN, advance=False):
 2739                expression = self._parse_schema()
 2740            else:
 2741                return self._parse_as_command(start)
 2742        elif create_token_type in self.DB_CREATABLES:
 2743            table_parts = self._parse_table_parts(
 2744                schema=True, is_db_reference=create_token_type == TokenType.SCHEMA
 2745            )
 2746
 2747            # exp.Properties.Location.POST_NAME
 2748            self._match(TokenType.COMMA)
 2749            extend_props(self._parse_properties(before=True))
 2750
 2751            this = self._parse_schema(this=table_parts)
 2752
 2753            # exp.Properties.Location.POST_SCHEMA and POST_WITH
 2754            extend_props(self._parse_properties())
 2755
 2756            has_alias = self._match(TokenType.ALIAS)
 2757            if not self._match_set(self.DDL_SELECT_TOKENS, advance=False):
 2758                # exp.Properties.Location.POST_ALIAS
 2759                extend_props(self._parse_properties())
 2760
 2761            if create_token_type == TokenType.SEQUENCE:
 2762                expression = self._parse_types()
 2763                props = self._parse_properties()
 2764                if props:
 2765                    sequence_props = exp.SequenceProperties()
 2766                    options = []
 2767                    for prop in props:
 2768                        if isinstance(prop, exp.SequenceProperties):
 2769                            for arg, value in prop.args.items():
 2770                                if arg == "options":
 2771                                    options.extend(value)
 2772                                else:
 2773                                    sequence_props.set(arg, value)
 2774                            prop.pop()
 2775
 2776                    if options:
 2777                        sequence_props.set("options", options)
 2778
 2779                    props.append("expressions", sequence_props)
 2780                    extend_props(props)
 2781            else:
 2782                expression = self._parse_ddl_select()
 2783
 2784                # Some dialects also support using a table as an alias instead of a SELECT.
 2785                # Here we fallback to this as an alternative.
 2786                if not expression and has_alias:
 2787                    expression = self._try_parse(self._parse_table_parts)
 2788
 2789            if create_token_type == TokenType.TABLE:
 2790                # exp.Properties.Location.POST_EXPRESSION
 2791                extend_props(self._parse_properties())
 2792
 2793                indexes = []
 2794                while True:
 2795                    index = self._parse_index()
 2796
 2797                    # exp.Properties.Location.POST_INDEX
 2798                    extend_props(self._parse_properties())
 2799                    if not index:
 2800                        break
 2801                    else:
 2802                        self._match(TokenType.COMMA)
 2803                        indexes.append(index)
 2804            elif create_token_type == TokenType.VIEW:
 2805                if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"):
 2806                    no_schema_binding = True
 2807            elif create_token_type in (TokenType.SINK, TokenType.SOURCE):
 2808                extend_props(self._parse_properties())
 2809
 2810            shallow = self._match_text_seq("SHALLOW")
 2811
 2812            if self._match_texts(self.CLONE_KEYWORDS):
 2813                copy = self._prev.text.lower() == "copy"
 2814                clone = self.expression(
 2815                    exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy)
 2816                )
 2817
 2818        if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False):
 2819            return self._parse_as_command(start)
 2820
 2821        create_kind_text = create_token.text.upper()
 2822        return self.expression(
 2823            exp.Create(
 2824                this=this,
 2825                kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text,
 2826                replace=replace,
 2827                refresh=refresh,
 2828                unique=unique,
 2829                expression=expression,
 2830                exists=exists,
 2831                properties=properties,
 2832                indexes=indexes,
 2833                no_schema_binding=no_schema_binding,
 2834                begin=begin,
 2835                clone=clone,
 2836                concurrently=concurrently,
 2837                clustered=clustered,
 2838            )
 2839        )
 2840
 2841    def _parse_sequence_properties(self) -> exp.SequenceProperties | None:
 2842        seq = exp.SequenceProperties()
 2843
 2844        options = []
 2845        index = self._index
 2846
 2847        while self._curr:
 2848            self._match(TokenType.COMMA)
 2849            if self._match_text_seq("INCREMENT"):
 2850                self._match_text_seq("BY")
 2851                self._match_text_seq("=")
 2852                seq.set("increment", self._parse_term())
 2853            elif self._match_text_seq("MINVALUE"):
 2854                seq.set("minvalue", self._parse_term())
 2855            elif self._match_text_seq("MAXVALUE"):
 2856                seq.set("maxvalue", self._parse_term())
 2857            elif self._match_text_seq("START"):
 2858                self._match_text_seq("WITH")
 2859                self._match_text_seq("=")
 2860                seq.set("start", self._parse_term())
 2861            elif self._match_text_seq("CACHE"):
 2862                # T-SQL allows empty CACHE which is initialized dynamically
 2863                seq.set("cache", self._parse_number() or True)
 2864            elif self._match_text_seq("OWNED", "BY"):
 2865                # "OWNED BY NONE" is the default
 2866                seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column())
 2867            else:
 2868                opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False)
 2869                if opt:
 2870                    options.append(opt)
 2871                else:
 2872                    break
 2873
 2874        seq.set("options", options if options else None)
 2875        return None if self._index == index else seq
 2876
 2877    def _parse_trigger_events(self) -> list[exp.TriggerEvent]:
 2878        events = []
 2879
 2880        while True:
 2881            event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper()
 2882
 2883            if not event_type:
 2884                self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)")
 2885
 2886            columns = (
 2887                self._parse_csv(self._parse_column)
 2888                if event_type == "UPDATE" and self._match_text_seq("OF")
 2889                else None
 2890            )
 2891
 2892            events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns)))
 2893
 2894            if not self._match(TokenType.OR):
 2895                break
 2896
 2897        return events
 2898
 2899    def _parse_trigger_deferrable(
 2900        self,
 2901    ) -> tuple[str | None, str | None]:
 2902        deferrable_var = self._parse_var_from_options(
 2903            self.TRIGGER_DEFERRABLE, raise_unmatched=False
 2904        )
 2905        deferrable = deferrable_var.this if deferrable_var else None
 2906
 2907        initially = None
 2908        if deferrable and self._match_text_seq("INITIALLY"):
 2909            initially = (
 2910                self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None
 2911            )
 2912
 2913        return deferrable, initially
 2914
 2915    def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None:
 2916        if not self._match_text_seq(keyword):
 2917            return None
 2918        if not self._match_text_seq("TABLE"):
 2919            self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause")
 2920        self._match_text_seq("AS")
 2921        return self._parse_id_var()
 2922
 2923    def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None:
 2924        if not self._match_text_seq("REFERENCING"):
 2925            return None
 2926
 2927        old_alias = None
 2928        new_alias = None
 2929
 2930        while True:
 2931            if alias := self._parse_trigger_referencing_clause("OLD"):
 2932                if old_alias is not None:
 2933                    self.raise_error("Duplicate OLD clause in REFERENCING")
 2934                old_alias = alias
 2935            elif alias := self._parse_trigger_referencing_clause("NEW"):
 2936                if new_alias is not None:
 2937                    self.raise_error("Duplicate NEW clause in REFERENCING")
 2938                new_alias = alias
 2939            else:
 2940                break
 2941
 2942        if old_alias is None and new_alias is None:
 2943            self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE")
 2944
 2945        return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias))
 2946
 2947    def _parse_trigger_for_each(self) -> str | None:
 2948        if not self._match_text_seq("FOR", "EACH"):
 2949            return None
 2950
 2951        return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None
 2952
 2953    def _parse_trigger_execute(self) -> exp.Expr | None:
 2954        if not self._match(TokenType.EXECUTE):
 2955            return None
 2956
 2957        if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)):
 2958            self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE")
 2959
 2960        func_call = self._parse_column()
 2961        return self.expression(exp.TriggerExecute(this=func_call))
 2962
 2963    def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None:
 2964        # only used for teradata currently
 2965        self._match(TokenType.COMMA)
 2966
 2967        kwargs = {
 2968            "no": self._match_text_seq("NO"),
 2969            "dual": self._match_text_seq("DUAL"),
 2970            "before": self._match_text_seq("BEFORE"),
 2971            "default": self._match_text_seq("DEFAULT"),
 2972            "local": (self._match_text_seq("LOCAL") and "LOCAL")
 2973            or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"),
 2974            "after": self._match_text_seq("AFTER"),
 2975            "minimum": self._match_texts(("MIN", "MINIMUM")),
 2976            "maximum": self._match_texts(("MAX", "MAXIMUM")),
 2977        }
 2978
 2979        if self._match_texts(self.PROPERTY_PARSERS):
 2980            parser = self.PROPERTY_PARSERS[self._prev.text.upper()]
 2981            try:
 2982                return parser(self, **{k: v for k, v in kwargs.items() if v})
 2983            except TypeError:
 2984                self.raise_error(f"Cannot parse property '{self._prev.text}'")
 2985
 2986        if self._match_pair(TokenType.CHAR, TokenType.SET):
 2987            return self._parse_character_set(default=bool(kwargs["default"]))
 2988
 2989        return None
 2990
 2991    def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]:
 2992        return self._parse_wrapped_csv(self._parse_property)
 2993
 2994    def _parse_property(self) -> exp.Expr | list[exp.Expr] | None:
 2995        if self._match_texts(self.PROPERTY_PARSERS):
 2996            return self.PROPERTY_PARSERS[self._prev.text.upper()](self)
 2997
 2998        if self._match_pair(TokenType.CHAR, TokenType.SET):
 2999            return self._parse_character_set()
 3000
 3001        if self._match(TokenType.DEFAULT):
 3002            if self._match_texts(self.PROPERTY_PARSERS):
 3003                return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True)
 3004
 3005            if self._match_pair(TokenType.CHAR, TokenType.SET):
 3006                return self._parse_character_set(default=True)
 3007
 3008        if self._match_text_seq("COMPOUND", "SORTKEY"):
 3009            return self._parse_sortkey(compound=True)
 3010
 3011        if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"):
 3012            return self.expression(exp.ParameterStyleProperty(this="PANDAS"))
 3013
 3014        if self._match_text_seq("NOT", "DETERMINISTIC"):
 3015            return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 3016
 3017        index = self._index
 3018
 3019        seq_props = self._parse_sequence_properties()
 3020        if seq_props:
 3021            return seq_props
 3022
 3023        self._retreat(index)
 3024        return self._parse_key_value_property()
 3025
 3026    def _parse_key_value_property(
 3027        self, parse_value: t.Callable[[], exp.Expr | None] | None = None
 3028    ) -> exp.Property | None:
 3029        index = self._index
 3030        key = self._parse_column()
 3031
 3032        if not self._match(TokenType.EQ):
 3033            self._retreat(index)
 3034            return None
 3035
 3036        # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise
 3037        if isinstance(key, exp.Column):
 3038            key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name)
 3039
 3040        value = (
 3041            parse_value()
 3042            if parse_value
 3043            else self._parse_bitwise() or self._parse_var(any_token=True)
 3044        )
 3045
 3046        # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier())
 3047        if isinstance(value, exp.Column):
 3048            value = exp.var(value.name)
 3049
 3050        return self.expression(exp.Property(this=key, value=value))
 3051
 3052    def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty:
 3053        if self._match_text_seq("BY"):
 3054            return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string()))
 3055
 3056        self._match(TokenType.ALIAS)
 3057        input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None
 3058        output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None
 3059
 3060        return self.expression(
 3061            exp.FileFormatProperty(
 3062                this=(
 3063                    self.expression(
 3064                        exp.InputOutputFormat(
 3065                            input_format=input_format, output_format=output_format
 3066                        )
 3067                    )
 3068                    if input_format or output_format
 3069                    else self._parse_var_or_string() or self._parse_number() or self._parse_id_var()
 3070                ),
 3071                hive_format=True,
 3072            )
 3073        )
 3074
 3075    def _parse_unquoted_field(self) -> exp.Expr | None:
 3076        field = self._parse_field()
 3077        if isinstance(field, exp.Identifier) and not field.quoted:
 3078            field = exp.var(field)
 3079
 3080        return field
 3081
 3082    def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E:
 3083        self._match(TokenType.EQ)
 3084        self._match(TokenType.ALIAS)
 3085
 3086        return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs))
 3087
 3088    def _parse_properties(self, before: bool | None = None) -> exp.Properties | None:
 3089        properties = []
 3090        while True:
 3091            if before:
 3092                prop = self._parse_property_before()
 3093            else:
 3094                prop = self._parse_property()
 3095            if not prop:
 3096                break
 3097            for p in ensure_list(prop):
 3098                properties.append(p)
 3099
 3100            self._match(TokenType.COMMA, expression=properties[-1])
 3101
 3102        if properties:
 3103            return self.expression(exp.Properties(expressions=properties))
 3104
 3105        return None
 3106
 3107    def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty:
 3108        return self.expression(
 3109            exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION"))
 3110        )
 3111
 3112    def _parse_sql_security(self) -> exp.SqlSecurityProperty:
 3113        return self.expression(
 3114            exp.SqlSecurityProperty(
 3115                this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper()
 3116            )
 3117        )
 3118
 3119    def _parse_settings_property(self) -> exp.SettingsProperty:
 3120        return self.expression(
 3121            exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment))
 3122        )
 3123
 3124    def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None:
 3125        if not self._match_text_seq("ON", "NULL", "INPUT"):
 3126            self._retreat(self._index - 1)
 3127            return None
 3128
 3129        return self.expression(exp.CalledOnNullInputProperty())
 3130
 3131    def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty:
 3132        if self._index >= 2:
 3133            pre_volatile_token = self._tokens[self._index - 2]
 3134        else:
 3135            pre_volatile_token = None
 3136
 3137        if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS:
 3138            return exp.VolatileProperty()
 3139
 3140        return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 3141
 3142    def _parse_retention_period(self) -> exp.Var:
 3143        # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...}
 3144        number = self._parse_number()
 3145        number_str = f"{number} " if number else ""
 3146        unit = self._parse_var(any_token=True)
 3147        return exp.var(f"{number_str}{unit}")
 3148
 3149    def _parse_system_versioning_property(
 3150        self, with_: bool = False
 3151    ) -> exp.WithSystemVersioningProperty:
 3152        self._match(TokenType.EQ)
 3153        prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_))
 3154
 3155        if self._match_text_seq("OFF"):
 3156            prop.set("on", False)
 3157            return prop
 3158
 3159        self._match(TokenType.ON)
 3160        if self._match(TokenType.L_PAREN):
 3161            while self._curr and not self._match(TokenType.R_PAREN):
 3162                if self._match_text_seq("HISTORY_TABLE", "="):
 3163                    prop.set("this", self._parse_table_parts())
 3164                elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="):
 3165                    prop.set("data_consistency", self._advance_any() and self._prev.text.upper())
 3166                elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="):
 3167                    prop.set("retention_period", self._parse_retention_period())
 3168
 3169                self._match(TokenType.COMMA)
 3170
 3171        return prop
 3172
 3173    def _parse_data_deletion_property(self) -> exp.DataDeletionProperty:
 3174        self._match(TokenType.EQ)
 3175        on = self._match_text_seq("ON") or not self._match_text_seq("OFF")
 3176        prop = self.expression(exp.DataDeletionProperty(on=on))
 3177
 3178        if self._match(TokenType.L_PAREN):
 3179            while self._curr and not self._match(TokenType.R_PAREN):
 3180                if self._match_text_seq("FILTER_COLUMN", "="):
 3181                    prop.set("filter_column", self._parse_column())
 3182                elif self._match_text_seq("RETENTION_PERIOD", "="):
 3183                    prop.set("retention_period", self._parse_retention_period())
 3184
 3185                self._match(TokenType.COMMA)
 3186
 3187        return prop
 3188
 3189    def _parse_distributed_property(self) -> exp.DistributedByProperty:
 3190        kind = "HASH"
 3191        expressions: list[exp.Expr] | None = None
 3192        if self._match_text_seq("BY", "HASH"):
 3193            expressions = self._parse_wrapped_csv(self._parse_id_var)
 3194        elif self._match_text_seq("BY", "RANDOM"):
 3195            kind = "RANDOM"
 3196
 3197        # If the BUCKETS keyword is not present, the number of buckets is AUTO
 3198        buckets: exp.Expr | None = None
 3199        if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"):
 3200            buckets = self._parse_number()
 3201
 3202        return self.expression(
 3203            exp.DistributedByProperty(
 3204                expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order()
 3205            )
 3206        )
 3207
 3208    def _parse_composite_key_property(self, expr_type: type[E]) -> E:
 3209        self._match_text_seq("KEY")
 3210        expressions = self._parse_wrapped_id_vars()
 3211        return self.expression(expr_type(expressions=expressions))
 3212
 3213    def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]:
 3214        if self._match_text_seq("(", "SYSTEM_VERSIONING"):
 3215            prop = self._parse_system_versioning_property(with_=True)
 3216            self._match_r_paren()
 3217            return prop
 3218
 3219        if self._match(TokenType.L_PAREN, advance=False):
 3220            result: list[exp.Expr] = []
 3221            for i in self._parse_wrapped_properties():
 3222                result.extend(i) if isinstance(i, list) else result.append(i)
 3223            return result
 3224
 3225        if self._match_text_seq("JOURNAL"):
 3226            return self._parse_withjournaltable()
 3227
 3228        if self._match_texts(self.VIEW_ATTRIBUTES):
 3229            return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper()))
 3230
 3231        if self._match_text_seq("DATA"):
 3232            return self._parse_withdata(no=False)
 3233        elif self._match_text_seq("NO", "DATA"):
 3234            return self._parse_withdata(no=True)
 3235
 3236        if self._match(TokenType.SERDE_PROPERTIES, advance=False):
 3237            return self._parse_serde_properties(with_=True)
 3238
 3239        if self._match(TokenType.SCHEMA):
 3240            return self.expression(
 3241                exp.WithSchemaBindingProperty(
 3242                    this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS)
 3243                )
 3244            )
 3245
 3246        if self._match_texts(self.PROCEDURE_OPTIONS, advance=False):
 3247            return self.expression(
 3248                exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option))
 3249            )
 3250
 3251        if not self._next:
 3252            return None
 3253
 3254        return self._parse_withisolatedloading()
 3255
 3256    def _parse_procedure_option(self) -> exp.Expr | None:
 3257        if self._match_text_seq("EXECUTE", "AS"):
 3258            return self.expression(
 3259                exp.ExecuteAsProperty(
 3260                    this=self._parse_var_from_options(
 3261                        self.EXECUTE_AS_OPTIONS, raise_unmatched=False
 3262                    )
 3263                    or self._parse_string()
 3264                )
 3265            )
 3266
 3267        return self._parse_var_from_options(self.PROCEDURE_OPTIONS)
 3268
 3269    # https://dev.mysql.com/doc/refman/8.0/en/create-view.html
 3270    def _parse_definer(self) -> exp.DefinerProperty | None:
 3271        self._match(TokenType.EQ)
 3272
 3273        user = self._parse_id_var()
 3274        self._match(TokenType.PARAMETER)
 3275        host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text)
 3276
 3277        if not user or not host:
 3278            return None
 3279
 3280        return exp.DefinerProperty(this=f"{user}@{host}")
 3281
 3282    def _parse_withjournaltable(self) -> exp.WithJournalTableProperty:
 3283        self._match(TokenType.TABLE)
 3284        self._match(TokenType.EQ)
 3285        return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts()))
 3286
 3287    def _parse_log(self, no: bool = False) -> exp.LogProperty:
 3288        return self.expression(exp.LogProperty(no=no))
 3289
 3290    def _parse_journal(self, **kwargs) -> exp.JournalProperty:
 3291        return self.expression(exp.JournalProperty(**kwargs))
 3292
 3293    def _parse_checksum(self) -> exp.ChecksumProperty:
 3294        self._match(TokenType.EQ)
 3295
 3296        on = None
 3297        if self._match(TokenType.ON):
 3298            on = True
 3299        elif self._match_text_seq("OFF"):
 3300            on = False
 3301
 3302        return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT)))
 3303
 3304    def _parse_cluster(self) -> exp.Cluster:
 3305        self._match(TokenType.CLUSTER_BY)
 3306        return self.expression(
 3307            exp.Cluster(
 3308                expressions=self._parse_csv(self._parse_column),
 3309            )
 3310        )
 3311
 3312    def _parse_cluster_property(self) -> exp.ClusterProperty:
 3313        return self.expression(
 3314            exp.ClusterProperty(
 3315                expressions=self._parse_wrapped_csv(self._parse_column),
 3316            )
 3317        )
 3318
 3319    def _parse_clustered_by(self) -> exp.ClusteredByProperty:
 3320        self._match_text_seq("BY")
 3321
 3322        self._match_l_paren()
 3323        expressions = self._parse_csv(self._parse_column)
 3324        self._match_r_paren()
 3325
 3326        if self._match_text_seq("SORTED", "BY"):
 3327            self._match_l_paren()
 3328            sorted_by = self._parse_csv(self._parse_ordered)
 3329            self._match_r_paren()
 3330        else:
 3331            sorted_by = None
 3332
 3333        self._match(TokenType.INTO)
 3334        buckets = self._parse_number()
 3335        self._match_text_seq("BUCKETS")
 3336
 3337        return self.expression(
 3338            exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets)
 3339        )
 3340
 3341    def _parse_copy_property(self) -> exp.Property | None:
 3342        if not self._match_text_seq("GRANTS"):
 3343            self._retreat(self._index - 1)
 3344            return None
 3345
 3346        return self.expression(exp.CopyGrantsProperty())
 3347
 3348    def _parse_freespace(self) -> exp.FreespaceProperty:
 3349        self._match(TokenType.EQ)
 3350        return self.expression(
 3351            exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT))
 3352        )
 3353
 3354    def _parse_mergeblockratio(
 3355        self, no: bool = False, default: bool = False
 3356    ) -> exp.MergeBlockRatioProperty:
 3357        if self._match(TokenType.EQ):
 3358            return self.expression(
 3359                exp.MergeBlockRatioProperty(
 3360                    this=self._parse_number(), percent=self._match(TokenType.PERCENT)
 3361                )
 3362            )
 3363
 3364        return self.expression(exp.MergeBlockRatioProperty(no=no, default=default))
 3365
 3366    def _parse_datablocksize(
 3367        self,
 3368        default: bool | None = None,
 3369        minimum: bool | None = None,
 3370        maximum: bool | None = None,
 3371    ) -> exp.DataBlocksizeProperty:
 3372        self._match(TokenType.EQ)
 3373        size = self._parse_number()
 3374
 3375        units = None
 3376        if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")):
 3377            units = self._prev.text
 3378
 3379        return self.expression(
 3380            exp.DataBlocksizeProperty(
 3381                size=size, units=units, default=default, minimum=minimum, maximum=maximum
 3382            )
 3383        )
 3384
 3385    def _parse_blockcompression(self) -> exp.BlockCompressionProperty:
 3386        self._match(TokenType.EQ)
 3387        always = self._match_text_seq("ALWAYS")
 3388        manual = self._match_text_seq("MANUAL")
 3389        never = self._match_text_seq("NEVER")
 3390        default = self._match_text_seq("DEFAULT")
 3391
 3392        autotemp = None
 3393        if self._match_text_seq("AUTOTEMP"):
 3394            autotemp = self._parse_schema()
 3395
 3396        return self.expression(
 3397            exp.BlockCompressionProperty(
 3398                always=always, manual=manual, never=never, default=default, autotemp=autotemp
 3399            )
 3400        )
 3401
 3402    def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None:
 3403        index = self._index
 3404        no = self._match_text_seq("NO")
 3405        concurrent = self._match_text_seq("CONCURRENT")
 3406
 3407        if not self._match_text_seq("ISOLATED", "LOADING"):
 3408            self._retreat(index)
 3409            return None
 3410
 3411        target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False)
 3412        return self.expression(
 3413            exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target)
 3414        )
 3415
 3416    def _parse_locking(self) -> exp.LockingProperty:
 3417        if self._match(TokenType.TABLE):
 3418            kind = "TABLE"
 3419        elif self._match(TokenType.VIEW):
 3420            kind = "VIEW"
 3421        elif self._match(TokenType.ROW):
 3422            kind = "ROW"
 3423        elif self._match_text_seq("DATABASE"):
 3424            kind = "DATABASE"
 3425        else:
 3426            kind = None
 3427
 3428        if kind in ("DATABASE", "TABLE", "VIEW"):
 3429            this = self._parse_table_parts()
 3430        else:
 3431            this = None
 3432
 3433        if self._match(TokenType.FOR):
 3434            for_or_in = "FOR"
 3435        elif self._match(TokenType.IN):
 3436            for_or_in = "IN"
 3437        else:
 3438            for_or_in = None
 3439
 3440        if self._match_text_seq("ACCESS"):
 3441            lock_type = "ACCESS"
 3442        elif self._match_texts(("EXCL", "EXCLUSIVE")):
 3443            lock_type = "EXCLUSIVE"
 3444        elif self._match_text_seq("SHARE"):
 3445            lock_type = "SHARE"
 3446        elif self._match_text_seq("READ"):
 3447            lock_type = "READ"
 3448        elif self._match_text_seq("WRITE"):
 3449            lock_type = "WRITE"
 3450        elif self._match_text_seq("CHECKSUM"):
 3451            lock_type = "CHECKSUM"
 3452        else:
 3453            lock_type = None
 3454
 3455        override = self._match_text_seq("OVERRIDE")
 3456
 3457        return self.expression(
 3458            exp.LockingProperty(
 3459                this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override
 3460            )
 3461        )
 3462
 3463    def _parse_partition_by(self) -> list[exp.Expr]:
 3464        if not self._match(TokenType.PARTITION_BY):
 3465            return []
 3466
 3467        if self._next.token_type in self.ID_VAR_TOKENS and self._match_text_seq("EXCLUDING"):
 3468            return [
 3469                self.expression(
 3470                    exp.PartitionByExcluding(expressions=self._parse_csv(self._parse_disjunction))
 3471                )
 3472            ]
 3473
 3474        return self._parse_csv(self._parse_disjunction)
 3475
 3476    def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec:
 3477        def _parse_partition_bound_expr() -> exp.Expr | None:
 3478            if self._match_text_seq("MINVALUE"):
 3479                return exp.var("MINVALUE")
 3480            if self._match_text_seq("MAXVALUE"):
 3481                return exp.var("MAXVALUE")
 3482            return self._parse_bitwise()
 3483
 3484        this: exp.Expr | list[exp.Expr] | None = None
 3485        expression = None
 3486        from_expressions = None
 3487        to_expressions = None
 3488
 3489        if self._match(TokenType.IN):
 3490            this = self._parse_wrapped_csv(self._parse_bitwise)
 3491        elif self._match(TokenType.FROM):
 3492            from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3493            self._match_text_seq("TO")
 3494            to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3495        elif self._match_text_seq("WITH", "(", "MODULUS"):
 3496            this = self._parse_number()
 3497            self._match_text_seq(",", "REMAINDER")
 3498            expression = self._parse_number()
 3499            self._match_r_paren()
 3500        else:
 3501            self.raise_error("Failed to parse partition bound spec.")
 3502
 3503        return self.expression(
 3504            exp.PartitionBoundSpec(
 3505                this=this,
 3506                expression=expression,
 3507                from_expressions=from_expressions,
 3508                to_expressions=to_expressions,
 3509            )
 3510        )
 3511
 3512    # https://www.postgresql.org/docs/current/sql-createtable.html
 3513    def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None:
 3514        if not self._match_text_seq("OF"):
 3515            self._retreat(self._index - 1)
 3516            return None
 3517
 3518        this = self._parse_table(schema=True)
 3519
 3520        if self._match(TokenType.DEFAULT):
 3521            expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT")
 3522        elif self._match_text_seq("FOR", "VALUES"):
 3523            expression = self._parse_partition_bound_spec()
 3524        else:
 3525            self.raise_error("Expecting either DEFAULT or FOR VALUES clause.")
 3526
 3527        return self.expression(exp.PartitionedOfProperty(this=this, expression=expression))
 3528
 3529    def _parse_partitioned_by(self) -> exp.PartitionedByProperty:
 3530        self._match(TokenType.EQ)
 3531        return self.expression(
 3532            exp.PartitionedByProperty(
 3533                this=self._parse_schema() or self._parse_bracket(self._parse_field())
 3534            )
 3535        )
 3536
 3537    def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty:
 3538        if self._match_text_seq("AND", "STATISTICS"):
 3539            statistics = True
 3540        elif self._match_text_seq("AND", "NO", "STATISTICS"):
 3541            statistics = False
 3542        else:
 3543            statistics = None
 3544
 3545        return self.expression(exp.WithDataProperty(no=no, statistics=statistics))
 3546
 3547    def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None:
 3548        if self._match_text_seq("SQL"):
 3549            return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL"))
 3550        return None
 3551
 3552    def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None:
 3553        if self._match_text_seq("SQL", "DATA"):
 3554            return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA"))
 3555        return None
 3556
 3557    def _parse_no_property(self) -> exp.Expr | None:
 3558        if self._match_text_seq("PRIMARY", "INDEX"):
 3559            return exp.NoPrimaryIndexProperty()
 3560        if self._match_text_seq("SQL"):
 3561            return self.expression(exp.SqlReadWriteProperty(this="NO SQL"))
 3562        return None
 3563
 3564    def _parse_on_property(self) -> exp.Expr | None:
 3565        if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"):
 3566            return exp.OnCommitProperty()
 3567        if self._match_text_seq("COMMIT", "DELETE", "ROWS"):
 3568            return exp.OnCommitProperty(delete=True)
 3569        return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var())))
 3570
 3571    def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None:
 3572        if self._match_text_seq("SQL", "DATA"):
 3573            return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA"))
 3574        return None
 3575
 3576    def _parse_distkey(self) -> exp.DistKeyProperty:
 3577        return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var)))
 3578
 3579    def _parse_create_like(self) -> exp.LikeProperty | None:
 3580        table = self._parse_table(schema=True)
 3581
 3582        options = []
 3583        while self._match_texts(("INCLUDING", "EXCLUDING")):
 3584            this = self._prev.text.upper()
 3585
 3586            id_var = self._parse_id_var()
 3587            if not id_var:
 3588                return None
 3589
 3590            options.append(
 3591                self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper())))
 3592            )
 3593
 3594        return self.expression(exp.LikeProperty(this=table, expressions=options))
 3595
 3596    def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty:
 3597        return self.expression(
 3598            exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound)
 3599        )
 3600
 3601    def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty:
 3602        self._match(TokenType.EQ)
 3603        return self.expression(
 3604            exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default)
 3605        )
 3606
 3607    def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty:
 3608        self._match_text_seq("WITH", "CONNECTION")
 3609        return self.expression(
 3610            exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts())
 3611        )
 3612
 3613    def _parse_returns(self) -> exp.ReturnsProperty:
 3614        value: exp.Expr | None
 3615        null = None
 3616        is_table = self._match(TokenType.TABLE)
 3617
 3618        if is_table:
 3619            if self._match(TokenType.LT):
 3620                value = self.expression(
 3621                    exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types))
 3622                )
 3623                if not self._match(TokenType.GT):
 3624                    self.raise_error("Expecting >")
 3625            else:
 3626                value = self._parse_schema(exp.var("TABLE"))
 3627        elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"):
 3628            null = True
 3629            value = None
 3630        else:
 3631            value = self._parse_types()
 3632
 3633        return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null))
 3634
 3635    def _parse_describe(self) -> exp.Describe:
 3636        kind = self._prev.text if self._match_set(self.CREATABLES) else None
 3637        style: str | None = (
 3638            self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None
 3639        )
 3640        if self._match(TokenType.DOT):
 3641            style = None
 3642            self._retreat(self._index - 2)
 3643
 3644        format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None
 3645
 3646        if self._match_set(self.STATEMENT_PARSERS, advance=False):
 3647            this = self._parse_statement()
 3648        else:
 3649            this = self._parse_table(schema=True)
 3650
 3651        properties = self._parse_properties()
 3652        expressions = properties.expressions if properties else None
 3653        partition = self._parse_partition()
 3654        return self.expression(
 3655            exp.Describe(
 3656                this=this,
 3657                style=style,
 3658                kind=kind,
 3659                expressions=expressions,
 3660                partition=partition,
 3661                format=format,
 3662                as_json=self._match_text_seq("AS", "JSON"),
 3663            )
 3664        )
 3665
 3666    def _parse_multitable_inserts(
 3667        self, comments: list[str] | None, kind: str | None = None
 3668    ) -> exp.MultitableInserts:
 3669        kind = kind or self._prev.text.upper()
 3670        expressions = []
 3671
 3672        def parse_conditional_insert() -> exp.ConditionalInsert | None:
 3673            if self._match(TokenType.WHEN):
 3674                expression = self._parse_disjunction()
 3675                self._match(TokenType.THEN)
 3676            else:
 3677                expression = None
 3678
 3679            else_ = self._match(TokenType.ELSE)
 3680
 3681            if not self._match(TokenType.INTO):
 3682                return None
 3683
 3684            return self.expression(
 3685                exp.ConditionalInsert(
 3686                    this=self.expression(
 3687                        exp.Insert(
 3688                            this=self._parse_table(schema=True),
 3689                            expression=self._parse_derived_table_values(),
 3690                        )
 3691                    ),
 3692                    expression=expression,
 3693                    else_=else_,
 3694                )
 3695            )
 3696
 3697        expression = parse_conditional_insert()
 3698        while expression is not None:
 3699            expressions.append(expression)
 3700            expression = parse_conditional_insert()
 3701
 3702        return self.expression(
 3703            exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()),
 3704            comments=comments,
 3705        )
 3706
 3707    def _parse_insert(self) -> exp.Insert | exp.MultitableInserts:
 3708        comments: list[str] = []
 3709        hint = self._parse_hint()
 3710        overwrite = self._match(TokenType.OVERWRITE)
 3711        ignore = self._match(TokenType.IGNORE)
 3712        local = self._match_text_seq("LOCAL")
 3713        alternative = None
 3714        is_function = None
 3715
 3716        if self._match_text_seq("DIRECTORY"):
 3717            this: exp.Expr | None = self.expression(
 3718                exp.Directory(
 3719                    this=self._parse_var_or_string(),
 3720                    local=local,
 3721                    row_format=self._parse_row_format(match_row=True),
 3722                )
 3723            )
 3724        else:
 3725            if self._match_set((TokenType.FIRST, TokenType.ALL)):
 3726                comments += ensure_list(self._prev_comments)
 3727                return self._parse_multitable_inserts(comments)
 3728
 3729            # Oracle's conditional form defaults to ALL when the keyword is omitted
 3730            if self._match(TokenType.WHEN, advance=False):
 3731                return self._parse_multitable_inserts(comments, kind="ALL")
 3732
 3733            if self._match(TokenType.OR):
 3734                alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text
 3735
 3736            self._match(TokenType.INTO)
 3737            comments += ensure_list(self._prev_comments)
 3738            self._match(TokenType.TABLE)
 3739            is_function = self._match(TokenType.FUNCTION)
 3740
 3741            this = self._parse_function() if is_function else self._parse_insert_table()
 3742
 3743        # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant
 3744        set_values = None
 3745        if self._match(TokenType.SET):
 3746            columns = []
 3747            values = []
 3748
 3749            def _parse_set_assignment() -> exp.Expr | None:
 3750                target = self._parse_column()
 3751                if isinstance(target, exp.Column) and self._match(TokenType.EQ):
 3752                    if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3753                        value: exp.Expr | None = exp.var(self._prev.text.upper())
 3754                    else:
 3755                        value = self._parse_disjunction()
 3756
 3757                    if value:
 3758                        columns.append(target.this)
 3759                        values.append(value)
 3760                        return value
 3761
 3762                self.raise_error("Expected column assignment in INSERT ... SET")
 3763                return None
 3764
 3765            self._parse_csv(_parse_set_assignment)
 3766
 3767            this = self.expression(exp.Schema(this=this, expressions=columns))
 3768            set_values = self.expression(
 3769                exp.Values(
 3770                    expressions=[exp.Tuple(expressions=values)],
 3771                    alias=self._parse_table_alias(),
 3772                )
 3773            )
 3774
 3775        returning = self._parse_returning()  # TSQL allows RETURNING before source
 3776
 3777        stored = self._match_text_seq("STORED") and self._parse_stored()
 3778        by_name = self._match_text_seq("BY", "NAME")
 3779        exists = self._parse_exists()
 3780        replace_where = None
 3781        replace_using = None
 3782
 3783        if self._match(TokenType.REPLACE):
 3784            if self._match(TokenType.WHERE):
 3785                replace_where = self._parse_disjunction()
 3786            elif self._match(TokenType.USING):
 3787                replace_using = self._parse_using_identifiers()
 3788
 3789        return self.expression(
 3790            exp.Insert(
 3791                hint=hint,
 3792                is_function=is_function,
 3793                this=this,
 3794                stored=stored,
 3795                by_name=by_name,
 3796                exists=exists,
 3797                where=replace_where,
 3798                using=replace_using,
 3799                partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(),
 3800                settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(),
 3801                default=self._match_text_seq("DEFAULT", "VALUES"),
 3802                expression=set_values
 3803                or self._parse_derived_table_values(allow_value_synonym=True)
 3804                or self._parse_ddl_select(),
 3805                conflict=self._parse_on_conflict(),
 3806                returning=returning or self._parse_returning(),
 3807                overwrite=overwrite,
 3808                alternative=alternative,
 3809                ignore=ignore,
 3810                source=self._match(TokenType.TABLE) and self._parse_table(),
 3811            ),
 3812            comments=comments,
 3813        )
 3814
 3815    def _parse_insert_table(self) -> exp.Expr | None:
 3816        this = self._parse_table(schema=True, parse_partition=True)
 3817        if isinstance(this, exp.Table) and self._match(TokenType.ALIAS):
 3818            # The column list after the alias belongs to the INSERT: INSERT INTO t AS s (a, b)
 3819            alias = self._parse_id_var()
 3820            if alias:
 3821                this.set(
 3822                    "alias",
 3823                    self.expression(exp.TableAlias(this=alias), comments=alias.pop_comments()),
 3824                )
 3825            return self._parse_schema(this=this)
 3826        return this
 3827
 3828    def _parse_kill(self) -> exp.Kill:
 3829        kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None
 3830
 3831        return self.expression(exp.Kill(this=self._parse_primary(), kind=kind))
 3832
 3833    def _parse_on_conflict(self) -> exp.OnConflict | None:
 3834        conflict = self._match_text_seq("ON", "CONFLICT")
 3835        duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY")
 3836
 3837        if not conflict and not duplicate:
 3838            return None
 3839
 3840        conflict_keys = None
 3841        constraint = None
 3842
 3843        if conflict:
 3844            if self._match_text_seq("ON", "CONSTRAINT"):
 3845                constraint = self._parse_id_var()
 3846            elif self._match(TokenType.L_PAREN):
 3847                conflict_keys = self._parse_csv(self._parse_indexed_column)
 3848                self._match_r_paren()
 3849
 3850        index_predicate = self._parse_where()
 3851
 3852        action = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 3853        if self._prev.token_type == TokenType.UPDATE:
 3854            self._match(TokenType.SET)
 3855            expressions = self._parse_csv(self._parse_update_assignment)
 3856        else:
 3857            expressions = None
 3858
 3859        return self.expression(
 3860            exp.OnConflict(
 3861                duplicate=duplicate,
 3862                expressions=expressions,
 3863                action=action,
 3864                conflict_keys=conflict_keys,
 3865                index_predicate=index_predicate,
 3866                constraint=constraint,
 3867                where=self._parse_where(),
 3868            )
 3869        )
 3870
 3871    def _parse_returning(self) -> exp.Returning | None:
 3872        if not self._match(TokenType.RETURNING):
 3873            return None
 3874        return self.expression(
 3875            exp.Returning(
 3876                expressions=self._parse_csv(self._parse_expression),
 3877                into=self._match(TokenType.INTO) and self._parse_table_part(),
 3878            )
 3879        )
 3880
 3881    def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3882        if not self._match(TokenType.FORMAT):
 3883            return None
 3884        return self._parse_row_format()
 3885
 3886    def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None:
 3887        index = self._index
 3888        with_ = with_ or self._match_text_seq("WITH")
 3889
 3890        if not self._match(TokenType.SERDE_PROPERTIES):
 3891            self._retreat(index)
 3892            return None
 3893        return self.expression(
 3894            exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_)
 3895        )
 3896
 3897    def _parse_row_format(
 3898        self, match_row: bool = False
 3899    ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3900        if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT):
 3901            return None
 3902
 3903        if self._match_text_seq("SERDE"):
 3904            this = self._parse_string()
 3905
 3906            serde_properties = self._parse_serde_properties()
 3907
 3908            return self.expression(
 3909                exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties)
 3910            )
 3911
 3912        self._match_text_seq("DELIMITED")
 3913
 3914        kwargs = {}
 3915
 3916        if self._match_text_seq("FIELDS", "TERMINATED", "BY"):
 3917            kwargs["fields"] = self._parse_string()
 3918            if self._match_text_seq("ESCAPED", "BY"):
 3919                kwargs["escaped"] = self._parse_string()
 3920        if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"):
 3921            kwargs["collection_items"] = self._parse_string()
 3922        if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"):
 3923            kwargs["map_keys"] = self._parse_string()
 3924        if self._match_text_seq("LINES", "TERMINATED", "BY"):
 3925            kwargs["lines"] = self._parse_string()
 3926        if self._match_text_seq("NULL", "DEFINED", "AS"):
 3927            kwargs["null"] = self._parse_string()
 3928
 3929        return self.expression(exp.RowFormatDelimitedProperty(**kwargs))  # type: ignore
 3930
 3931    def _parse_load(self) -> exp.LoadData | exp.Command:
 3932        if self._match_text_seq("DATA"):
 3933            local = self._match_text_seq("LOCAL")
 3934            self._match_text_seq("INPATH")
 3935            inpath = self._parse_string()
 3936            overwrite = self._match(TokenType.OVERWRITE)
 3937            temp: bool | None = None
 3938            if self._match(TokenType.INTO):
 3939                temp = self._match(TokenType.TEMPORARY)
 3940                self._match(TokenType.TABLE)
 3941
 3942            return self.expression(
 3943                exp.LoadData(
 3944                    this=self._parse_table(schema=True),
 3945                    local=local,
 3946                    overwrite=overwrite,
 3947                    temp=temp,
 3948                    inpath=inpath,
 3949                    files=self._match_text_seq("FROM", "FILES")
 3950                    and exp.Properties(expressions=self._parse_wrapped_properties()),
 3951                    partition=self._parse_partition(),
 3952                    input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(),
 3953                    serde=self._match_text_seq("SERDE") and self._parse_string(),
 3954                )
 3955            )
 3956        return self._parse_as_command(self._prev)
 3957
 3958    def _parse_delete(self) -> exp.Delete:
 3959        hint = self._parse_hint()
 3960
 3961        # This handles MySQL's "Multiple-Table Syntax"
 3962        # https://dev.mysql.com/doc/refman/8.0/en/delete.html
 3963        tables = None
 3964        if not self._match(TokenType.FROM, advance=False):
 3965            tables = self._parse_csv(self._parse_table) or None
 3966
 3967        returning = self._parse_returning()
 3968
 3969        return self.expression(
 3970            exp.Delete(
 3971                hint=hint,
 3972                tables=tables,
 3973                this=self._match(TokenType.FROM) and self._parse_table(joins=True),
 3974                using=self._match(TokenType.USING)
 3975                and self._parse_csv(lambda: self._parse_table(joins=True)),
 3976                cluster=self._match(TokenType.ON) and self._parse_on_property(),
 3977                where=self._parse_where(),
 3978                returning=returning or self._parse_returning(),
 3979                order=self._parse_order(),
 3980                limit=self._parse_limit(),
 3981            )
 3982        )
 3983
 3984    def _parse_update_assignment(self) -> exp.Expr | None:
 3985        this = self._parse_comparison()
 3986        if self._match(TokenType.EQ):
 3987            comments = self._prev_comments
 3988            this = self.expression(
 3989                exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments
 3990            )
 3991        return this
 3992
 3993    def _parse_update(self) -> exp.Update:
 3994        hint = self._parse_hint()
 3995        kwargs: dict[str, object] = {
 3996            "hint": hint,
 3997            "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS),
 3998        }
 3999        while self._curr:
 4000            if self._match(TokenType.SET):
 4001                kwargs["expressions"] = self._parse_csv(self._parse_update_assignment)
 4002            elif self._match(TokenType.RETURNING, advance=False):
 4003                kwargs["returning"] = self._parse_returning()
 4004            elif self._match(TokenType.FROM, advance=False):
 4005                from_ = self._parse_from(joins=True)
 4006                table = from_.this if from_ else None
 4007                if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False):
 4008                    table.set("joins", list(self._parse_joins()) or None)
 4009
 4010                kwargs["from_"] = from_
 4011            elif self._match(TokenType.WHERE, advance=False):
 4012                kwargs["where"] = self._parse_where()
 4013            elif self._match(TokenType.ORDER_BY, advance=False):
 4014                kwargs["order"] = self._parse_order()
 4015            elif self._match(TokenType.LIMIT, advance=False):
 4016                kwargs["limit"] = self._parse_limit()
 4017            else:
 4018                break
 4019
 4020        return self.expression(exp.Update(**kwargs))
 4021
 4022    def _parse_use(self) -> exp.Use:
 4023        return self.expression(
 4024            exp.Use(
 4025                kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False),
 4026                this=self._parse_table(schema=False),
 4027            )
 4028        )
 4029
 4030    def _parse_uncache(self) -> exp.Uncache:
 4031        if not self._match(TokenType.TABLE):
 4032            self.raise_error("Expecting TABLE after UNCACHE")
 4033
 4034        return self.expression(
 4035            exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True))
 4036        )
 4037
 4038    def _parse_cache(self) -> exp.Cache:
 4039        lazy = self._match_text_seq("LAZY")
 4040        self._match(TokenType.TABLE)
 4041        table = self._parse_table(schema=True)
 4042
 4043        options = []
 4044        if self._match_text_seq("OPTIONS"):
 4045            self._match_l_paren()
 4046            k = self._parse_string()
 4047            self._match(TokenType.EQ)
 4048            v = self._parse_string()
 4049            options = [k, v]
 4050            self._match_r_paren()
 4051
 4052        self._match(TokenType.ALIAS)
 4053        return self.expression(
 4054            exp.Cache(
 4055                this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True)
 4056            )
 4057        )
 4058
 4059    def _parse_partition(self) -> exp.Partition | None:
 4060        if not self._match_texts(self.PARTITION_KEYWORDS):
 4061            return None
 4062
 4063        return self.expression(
 4064            exp.Partition(
 4065                subpartition=self._prev.text.upper() == "SUBPARTITION",
 4066                expressions=self._parse_wrapped_csv(self._parse_disjunction),
 4067            )
 4068        )
 4069
 4070    def _parse_value(self, values: bool = True) -> exp.Tuple | None:
 4071        def _parse_value_expression() -> exp.Expr | None:
 4072            if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 4073                return exp.var(self._prev.text.upper())
 4074            return self._parse_expression()
 4075
 4076        if self._match(TokenType.L_PAREN):
 4077            expressions = self._parse_csv(_parse_value_expression)
 4078            self._match_r_paren()
 4079            return self.expression(exp.Tuple(expressions=expressions))
 4080
 4081        # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows.
 4082        expression = self._parse_expression()
 4083        if expression:
 4084            return self.expression(exp.Tuple(expressions=[expression]))
 4085        return None
 4086
 4087    def _parse_projections(
 4088        self,
 4089    ) -> tuple[list[exp.Expr], list[exp.Expr] | None]:
 4090        return self._parse_expressions(), None
 4091
 4092    def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None:
 4093        if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)):
 4094            this: exp.Expr | None = self._parse_simplified_pivot(
 4095                is_unpivot=self._prev.token_type == TokenType.UNPIVOT
 4096            )
 4097        else:
 4098            # Support parentheses for duckdb FROM-first syntax
 4099            this = (
 4100                self._parse_table(consume_pipe=True)
 4101                if table and not self._match(TokenType.FROM, advance=False)
 4102                else self._parse_select(nested=True, parse_set_operation=False)
 4103            )
 4104
 4105            # Transform exp.Values into a exp.Table to pass through parse_query_modifiers
 4106            # in case a modifier (e.g. join) is following
 4107            if table and isinstance(this, exp.Values) and this.alias:
 4108                alias = this.args["alias"].pop()
 4109                this = exp.Table(this=this, alias=alias)
 4110
 4111            this = self._parse_query_modifiers(self._parse_set_operations(this))
 4112
 4113        return this
 4114
 4115    def _parse_select(
 4116        self,
 4117        nested: bool = False,
 4118        table: bool = False,
 4119        parse_subquery_alias: bool = True,
 4120        parse_set_operation: bool = True,
 4121        consume_pipe: bool = True,
 4122    ) -> exp.Expr | None:
 4123        query = self._parse_select_query(
 4124            nested=nested,
 4125            table=table,
 4126            parse_subquery_alias=parse_subquery_alias,
 4127            parse_set_operation=parse_set_operation,
 4128        )
 4129
 4130        if consume_pipe and self._match(TokenType.PIPE_GT, advance=False):
 4131            if isinstance(query, exp.Query):
 4132                query = self._parse_pipe_syntax_query(query)
 4133                query = query.subquery(copy=False) if query and table else query
 4134
 4135        return query
 4136
 4137    def _parse_select_query(
 4138        self,
 4139        nested: bool = False,
 4140        table: bool = False,
 4141        parse_subquery_alias: bool = True,
 4142        parse_set_operation: bool = True,
 4143    ) -> exp.Expr | None:
 4144        cte = self._parse_with()
 4145
 4146        if cte:
 4147            this = self._parse_statement()
 4148
 4149            if not this:
 4150                self.raise_error("Failed to parse any statement following CTE")
 4151                return cte
 4152
 4153            while isinstance(this, exp.Subquery) and this.is_wrapper:
 4154                this = this.this
 4155
 4156            assert this is not None
 4157            if "with_" in this.arg_types:
 4158                if inner_cte := this.args.get("with_"):
 4159                    cte.set("expressions", cte.expressions + inner_cte.expressions)
 4160                    if inner_cte.args.get("recursive"):
 4161                        cte.set("recursive", True)
 4162                this.set("with_", cte)
 4163            else:
 4164                self.raise_error(f"{this.key} does not support CTE")
 4165                this = cte
 4166
 4167            return this
 4168
 4169        # duckdb supports leading with FROM x
 4170        from_ = (
 4171            self._parse_from(joins=True, consume_pipe=True)
 4172            if self._match(TokenType.FROM, advance=False)
 4173            else None
 4174        )
 4175        laterals = list(iter(self._parse_lateral, None)) if from_ else None
 4176
 4177        # Hive supports multi-table inserts: FROM x INSERT ... SELECT ... [INSERT ... SELECT ...]
 4178        if from_ and self._match(TokenType.INSERT, advance=False):
 4179            source = from_.this
 4180            source.set("laterals", laterals or None)
 4181
 4182            inserts = []
 4183            while self._match(TokenType.INSERT, advance=False):
 4184                inserts.append(self._parse_statement())
 4185
 4186            return self.expression(
 4187                exp.MultitableInserts(expressions=inserts, source=source),
 4188                comments=from_.comments,
 4189            )
 4190
 4191        if self._match(TokenType.SELECT):
 4192            comments = self._prev_comments
 4193
 4194            hint = self._parse_hint()
 4195
 4196            if self._next and not self._next.token_type == TokenType.DOT:
 4197                all_ = self._match(TokenType.ALL)
 4198                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4199            else:
 4200                all_, matched_distinct = None, False
 4201
 4202            kind = (
 4203                self._prev.text.upper()
 4204                if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE"))
 4205                else None
 4206            )
 4207
 4208            distinct: exp.Expr | None = (
 4209                self.expression(
 4210                    exp.Distinct(
 4211                        on=self._parse_value(values=False) if self._match(TokenType.ON) else None
 4212                    )
 4213                )
 4214                if matched_distinct
 4215                else None
 4216            )
 4217
 4218            operation_modifiers = []
 4219            while self._curr and self._match_texts(self.OPERATION_MODIFIERS):
 4220                operation_modifiers.append(exp.var(self._prev.text.upper()))
 4221
 4222            limit = self._parse_limit(top=True)
 4223
 4224            # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ...
 4225            if limit and not matched_distinct and not all_:
 4226                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4227                if matched_distinct:
 4228                    distinct = self.expression(
 4229                        exp.Distinct(
 4230                            on=self._parse_value(values=False)
 4231                            if self._match(TokenType.ON)
 4232                            else None
 4233                        )
 4234                    )
 4235                else:
 4236                    all_ = self._match(TokenType.ALL)
 4237
 4238            if all_ and distinct:
 4239                self.raise_error("Cannot specify both ALL and DISTINCT after SELECT")
 4240
 4241            projections, exclude = self._parse_projections()
 4242
 4243            this = self.expression(
 4244                exp.Select(
 4245                    kind=kind,
 4246                    hint=hint,
 4247                    distinct=distinct,
 4248                    expressions=projections,
 4249                    limit=limit,
 4250                    exclude=exclude,
 4251                    operation_modifiers=operation_modifiers or None,
 4252                )
 4253            )
 4254            this.comments = comments
 4255
 4256            into = self._parse_into()
 4257            if into:
 4258                this.set("into", into)
 4259
 4260            if not from_:
 4261                from_ = self._parse_from()
 4262
 4263            if from_:
 4264                this.set("from_", from_)
 4265
 4266            this = self._parse_query_modifiers(this)
 4267        elif (table or nested) and self._match(TokenType.L_PAREN):
 4268            comments = self._prev_comments
 4269            this = self._parse_wrapped_select(table=table)
 4270
 4271            if this:
 4272                this.add_comments(comments, prepend=True)
 4273
 4274            # We return early here so that the UNION isn't attached to the subquery by the
 4275            # following call to _parse_set_operations, but instead becomes the parent node
 4276            self._match_r_paren()
 4277            return self._parse_subquery(this, parse_alias=parse_subquery_alias)
 4278        elif self._match(TokenType.VALUES, advance=False):
 4279            this = self._parse_derived_table_values()
 4280        elif from_:
 4281            this = exp.select("*").from_(from_.this, copy=False)
 4282            this = self._parse_query_modifiers(this)
 4283        elif self._match(TokenType.SUMMARIZE):
 4284            table = self._match(TokenType.TABLE)
 4285            this = self._parse_select() or self._parse_string() or self._parse_table()
 4286            return self.expression(exp.Summarize(this=this, table=table))
 4287        elif self._match(TokenType.DESCRIBE):
 4288            this = self._parse_describe()
 4289        else:
 4290            this = None
 4291
 4292        if laterals and isinstance(this, exp.Select):
 4293            this.set("laterals", [*laterals, *(this.args.get("laterals") or [])])
 4294
 4295        return self._parse_set_operations(this) if parse_set_operation else this
 4296
 4297    def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None:
 4298        self._match_text_seq("SEARCH")
 4299
 4300        kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper()
 4301
 4302        if not kind:
 4303            return None
 4304
 4305        self._match_text_seq("FIRST", "BY")
 4306
 4307        return self.expression(
 4308            exp.RecursiveWithSearch(
 4309                kind=kind,
 4310                this=self._parse_id_var(),
 4311                expression=self._match_text_seq("SET") and self._parse_id_var(),
 4312                to=self._match_text_seq("TO") and self._parse_bitwise(),
 4313                default=self._match(TokenType.DEFAULT) and self._parse_bitwise(),
 4314                using=self._match_text_seq("USING") and self._parse_id_var(),
 4315            )
 4316        )
 4317
 4318    def _parse_with(self, skip_with_token: bool = False) -> exp.With | None:
 4319        if not skip_with_token and not self._match(TokenType.WITH):
 4320            return None
 4321
 4322        comments = self._prev_comments
 4323        recursive = self._match(TokenType.RECURSIVE)
 4324
 4325        last_comments = None
 4326        expressions = []
 4327        udfs = []
 4328        while True:
 4329            cte = self._parse_cte()
 4330            if cte:
 4331                if isinstance(cte, exp.FunctionSpecification):
 4332                    udfs.append(cte)
 4333                else:
 4334                    expressions.append(cte)
 4335
 4336                if last_comments:
 4337                    cte.add_comments(last_comments)
 4338
 4339            if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH):
 4340                break
 4341            else:
 4342                self._match(TokenType.WITH)
 4343                recursive = self._match(TokenType.RECURSIVE) or recursive
 4344
 4345            last_comments = self._prev_comments
 4346
 4347        search = self._parse_recursive_with_search()
 4348
 4349        # A SEARCH clause can be followed by a CYCLE clause
 4350        cycle = (
 4351            self._parse_recursive_with_search()
 4352            if search
 4353            and search.args.get("kind") != "CYCLE"
 4354            and self._match_text_seq("CYCLE", advance=False)
 4355            else None
 4356        )
 4357
 4358        return self.expression(
 4359            exp.With(
 4360                expressions=expressions,
 4361                recursive=recursive or None,
 4362                search=search,
 4363                cycle=cycle,
 4364                udfs=udfs or None,
 4365            ),
 4366            comments=comments,
 4367        )
 4368
 4369    def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None:
 4370        index = self._index
 4371
 4372        alias = self._parse_table_alias(self.ID_VAR_TOKENS)
 4373        if not alias or not alias.this:
 4374            self.raise_error("Expected CTE to have alias")
 4375
 4376        key_expressions = (
 4377            self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None
 4378        )
 4379
 4380        if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE:
 4381            self._retreat(index)
 4382            return None
 4383
 4384        comments = self._prev_comments
 4385
 4386        if self._match_text_seq("NOT", "MATERIALIZED"):
 4387            materialized = False
 4388        elif self._match_text_seq("MATERIALIZED"):
 4389            materialized = True
 4390        else:
 4391            materialized = None
 4392
 4393        cte = self.expression(
 4394            exp.CTE(
 4395                this=self._parse_wrapped(self._parse_statement),
 4396                alias=alias,
 4397                materialized=materialized,
 4398                key_expressions=key_expressions,
 4399            ),
 4400            comments=comments,
 4401        )
 4402
 4403        values = cte.this
 4404        if isinstance(values, exp.Values):
 4405            cte.set("this", self._values_to_select(values))
 4406
 4407        return cte
 4408
 4409    def _values_to_select(self, values: exp.Values) -> exp.Select:
 4410        select = exp.select("*")
 4411        for key in exp.QUERY_MODIFIERS:
 4412            if modifier := values.args.get(key):
 4413                values.set(key, None)
 4414                select.set(key, modifier)
 4415        return select.from_(values if values.alias else exp.alias_(values, "_values", table=True))
 4416
 4417    def _parse_table_alias(
 4418        self, alias_tokens: t.Collection[TokenType] | None = None
 4419    ) -> exp.TableAlias | None:
 4420        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 4421        # so this section tries to parse the clause version and if it fails, it treats the token
 4422        # as an identifier (alias)
 4423        if self._can_parse_limit_or_offset():
 4424            return None
 4425
 4426        # START is never treated as an implicit alias when followed by WITH, since that
 4427        # would swallow the beginning of a START WITH ... CONNECT BY clause
 4428        if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH":
 4429            return None
 4430
 4431        any_token = self._match(TokenType.ALIAS)
 4432        alias = (
 4433            self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 4434            or self._parse_string_as_identifier()
 4435        )
 4436
 4437        index = self._index
 4438        if self._match(TokenType.L_PAREN):
 4439            columns = self._parse_csv(self._parse_function_parameter)
 4440            self._match_r_paren() if columns else self._retreat(index)
 4441        else:
 4442            columns = None
 4443
 4444        if not alias and not columns:
 4445            return None
 4446
 4447        table_alias = self.expression(exp.TableAlias(this=alias, columns=columns))
 4448
 4449        # We bubble up comments from the Identifier to the TableAlias
 4450        if isinstance(alias, exp.Identifier):
 4451            table_alias.add_comments(alias.pop_comments())
 4452
 4453        return table_alias
 4454
 4455    def _parse_subquery(
 4456        self, this: exp.Expr | None, parse_alias: bool = True
 4457    ) -> exp.Subquery | None:
 4458        if not this:
 4459            return None
 4460
 4461        return self.expression(
 4462            exp.Subquery(
 4463                this=this,
 4464                pivots=self._parse_pivots(),
 4465                alias=self._parse_table_alias() if parse_alias else None,
 4466                sample=self._parse_table_sample(),
 4467            )
 4468        )
 4469
 4470    def _implicit_unnests_to_explicit(self, this: E) -> E:
 4471        from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm
 4472
 4473        refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name}
 4474        for i, join in enumerate(this.args.get("joins") or []):
 4475            table = join.this
 4476            normalized_table = table.copy()
 4477            normalized_table.meta["maybe_column"] = True
 4478            normalized_table = _norm(normalized_table, dialect=self.dialect)
 4479
 4480            if isinstance(table, exp.Table) and not join.args.get("on"):
 4481                if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs:
 4482                    table_as_column = table.to_column()
 4483                    unnest = exp.Unnest(expressions=[table_as_column])
 4484
 4485                    # Table.to_column creates a parent Alias node that we want to convert to
 4486                    # a TableAlias and attach to the Unnest, so it matches the parser's output
 4487                    if isinstance(table.args.get("alias"), exp.TableAlias):
 4488                        table_as_column.replace(table_as_column.this)
 4489                        exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False)
 4490
 4491                    table.replace(unnest)
 4492
 4493            refs.add(normalized_table.alias_or_name)
 4494
 4495        return this
 4496
 4497    @t.overload
 4498    def _parse_query_modifiers(self, this: E) -> E: ...
 4499
 4500    @t.overload
 4501    def _parse_query_modifiers(self, this: None) -> None: ...
 4502
 4503    def _parse_query_modifiers(self, this):
 4504        if isinstance(this, self.MODIFIABLES):
 4505            for join in self._parse_joins():
 4506                this.append("joins", join)
 4507            for lateral in iter(self._parse_lateral, None):
 4508                this.append("laterals", lateral)
 4509
 4510            while True:
 4511                if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):
 4512                    modifier_token = self._curr
 4513
 4514                    # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result
 4515                    # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union
 4516                    if (
 4517                        modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH)
 4518                        and (limit := this.args.get("limit"))
 4519                        and limit.meta.get("top")
 4520                    ):
 4521                        break
 4522
 4523                    parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type]
 4524                    key, expression = parser(self)
 4525
 4526                    if expression:
 4527                        if this.args.get(key):
 4528                            self.raise_error(
 4529                                f"Found multiple '{modifier_token.text.upper()}' clauses",
 4530                                token=modifier_token,
 4531                            )
 4532
 4533                        this.set(key, expression)
 4534                        if key == "limit":
 4535                            offset = expression.args.get("offset")
 4536                            expression.set("offset", None)
 4537
 4538                            if offset:
 4539                                if this.args.get("offset"):
 4540                                    self.raise_error(
 4541                                        "Found multiple 'OFFSET' clauses", token=modifier_token
 4542                                    )
 4543
 4544                                offset = exp.Offset(expression=offset)
 4545                                this.set("offset", offset)
 4546
 4547                                limit_by_expressions = expression.expressions
 4548                                expression.set("expressions", None)
 4549                                offset.set("expressions", limit_by_expressions)
 4550                        continue
 4551
 4552                if self._curr.text.upper() == "START":
 4553                    modifier_token = self._curr
 4554                    connect = self._parse_connect()
 4555                    if connect:
 4556                        if this.args.get("connect"):
 4557                            self.raise_error(
 4558                                "Found multiple 'START WITH' clauses", token=modifier_token
 4559                            )
 4560
 4561                        this.set("connect", connect)
 4562                        continue
 4563                break
 4564
 4565        if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"):
 4566            this = self._implicit_unnests_to_explicit(this)
 4567
 4568        return this
 4569
 4570    def _parse_hint_fallback_to_string(self) -> exp.Hint | None:
 4571        start = self._curr
 4572        while self._curr:
 4573            self._advance()
 4574
 4575        end = self._tokens[self._index - 1]
 4576        return exp.Hint(expressions=[self._find_sql(start, end)])
 4577
 4578    def _parse_hint_function_call(self) -> exp.Expr | None:
 4579        return self._parse_function_call()
 4580
 4581    def _parse_hint_body(self) -> exp.Hint | None:
 4582        start_index = self._index
 4583        should_fallback_to_string = False
 4584
 4585        hints = []
 4586        try:
 4587            for hint in iter(
 4588                lambda: self._parse_csv(
 4589                    lambda: self._parse_hint_function_call() or self._parse_var(upper=True),
 4590                ),
 4591                [],
 4592            ):
 4593                hints.extend(hint)
 4594        except ParseError:
 4595            should_fallback_to_string = True
 4596
 4597        if should_fallback_to_string or self._curr:
 4598            self._retreat(start_index)
 4599            return self._parse_hint_fallback_to_string()
 4600
 4601        return self.expression(exp.Hint(expressions=hints))
 4602
 4603    def _parse_hint(self) -> exp.Hint | None:
 4604        if self._match(TokenType.HINT) and self._prev_comments:
 4605            return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect)
 4606
 4607        return None
 4608
 4609    def _parse_into(self) -> exp.Into | None:
 4610        if not self._match(TokenType.INTO):
 4611            return None
 4612
 4613        temp = self._match(TokenType.TEMPORARY)
 4614        unlogged = self._match_text_seq("UNLOGGED")
 4615        self._match(TokenType.TABLE)
 4616
 4617        return self.expression(
 4618            exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged)
 4619        )
 4620
 4621    def _parse_from(
 4622        self,
 4623        joins: bool = False,
 4624        skip_from_token: bool = False,
 4625        consume_pipe: bool = False,
 4626    ) -> exp.From | None:
 4627        if not skip_from_token and not self._match(TokenType.FROM):
 4628            return None
 4629
 4630        comments = self._prev_comments
 4631        return self.expression(
 4632            exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)),
 4633            comments=comments,
 4634        )
 4635
 4636    def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure:
 4637        return self.expression(
 4638            exp.MatchRecognizeMeasure(
 4639                window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(),
 4640                this=self._parse_expression(),
 4641            )
 4642        )
 4643
 4644    def _parse_match_recognize(self) -> exp.MatchRecognize | None:
 4645        if not self._match(TokenType.MATCH_RECOGNIZE):
 4646            return None
 4647
 4648        self._match_l_paren()
 4649
 4650        partition = self._parse_partition_by()
 4651        order = self._parse_order()
 4652
 4653        measures = (
 4654            self._parse_csv(self._parse_match_recognize_measure)
 4655            if self._match_text_seq("MEASURES")
 4656            else None
 4657        )
 4658
 4659        if self._match_text_seq("ONE", "ROW", "PER", "MATCH"):
 4660            rows = exp.var("ONE ROW PER MATCH")
 4661        elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"):
 4662            text = "ALL ROWS PER MATCH"
 4663            if self._match_text_seq("SHOW", "EMPTY", "MATCHES"):
 4664                text += " SHOW EMPTY MATCHES"
 4665            elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"):
 4666                text += " OMIT EMPTY MATCHES"
 4667            elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"):
 4668                text += " WITH UNMATCHED ROWS"
 4669            rows = exp.var(text)
 4670        else:
 4671            rows = None
 4672
 4673        if self._match_text_seq("AFTER", "MATCH", "SKIP"):
 4674            text = "AFTER MATCH SKIP"
 4675            if self._match_text_seq("PAST", "LAST", "ROW"):
 4676                text += " PAST LAST ROW"
 4677            elif self._match_text_seq("TO", "NEXT", "ROW"):
 4678                text += " TO NEXT ROW"
 4679            elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"):
 4680                direction = self._prev.text.upper()
 4681                pattern_var = self._advance_any()
 4682                if not pattern_var:
 4683                    self.raise_error(
 4684                        f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}"
 4685                    )
 4686                text += f" TO {direction} {pattern_var.text if pattern_var else ''}"
 4687            after = exp.var(text)
 4688        else:
 4689            after = None
 4690
 4691        if self._match_text_seq("PATTERN"):
 4692            self._match_l_paren()
 4693
 4694            if not self._curr:
 4695                self.raise_error("Expecting )", self._curr)
 4696
 4697            paren = 1
 4698            start = self._curr
 4699
 4700            while self._curr and paren > 0:
 4701                if self._curr.token_type == TokenType.L_PAREN:
 4702                    paren += 1
 4703                if self._curr.token_type == TokenType.R_PAREN:
 4704                    paren -= 1
 4705
 4706                end = self._prev
 4707                self._advance()
 4708
 4709            if paren > 0:
 4710                self.raise_error("Expecting )", self._curr)
 4711
 4712            pattern = exp.var(self._find_sql(start, end))
 4713        else:
 4714            pattern = None
 4715
 4716        define = (
 4717            self._parse_csv(self._parse_name_as_expression)
 4718            if self._match_text_seq("DEFINE")
 4719            else None
 4720        )
 4721
 4722        self._match_r_paren()
 4723
 4724        return self.expression(
 4725            exp.MatchRecognize(
 4726                partition_by=partition,
 4727                order=order,
 4728                measures=measures,
 4729                rows=rows,
 4730                after=after,
 4731                pattern=pattern,
 4732                define=define,
 4733                alias=self._parse_table_alias(),
 4734            )
 4735        )
 4736
 4737    def _parse_lateral(self) -> exp.Lateral | None:
 4738        cross_apply: bool | None = None
 4739        if self._match_pair(TokenType.CROSS, TokenType.APPLY):
 4740            cross_apply = True
 4741        elif self._match_pair(TokenType.OUTER, TokenType.APPLY):
 4742            cross_apply = False
 4743
 4744        if cross_apply is not None:
 4745            this = self._parse_select(table=True)
 4746            view = None
 4747            outer = None
 4748        elif self._match(TokenType.LATERAL):
 4749            this = self._parse_select(table=True)
 4750            view = self._match(TokenType.VIEW)
 4751            outer = self._match(TokenType.OUTER)
 4752        else:
 4753            return None
 4754
 4755        if not this:
 4756            this = (
 4757                self._parse_unnest()
 4758                or self._parse_function()
 4759                or self._parse_id_var(any_token=False)
 4760            )
 4761
 4762            while self._match(TokenType.DOT):
 4763                this = exp.Dot(
 4764                    this=this,
 4765                    expression=self._parse_function() or self._parse_id_var(any_token=False),
 4766                )
 4767
 4768        ordinality: bool | None = None
 4769
 4770        if view:
 4771            table = self._parse_id_var(any_token=False)
 4772            columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else []
 4773            table_alias: exp.TableAlias | None = self.expression(
 4774                exp.TableAlias(this=table, columns=columns)
 4775            )
 4776        elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias:
 4777            # We move the alias from the lateral's child node to the lateral itself
 4778            table_alias = this.args["alias"].pop()
 4779        else:
 4780            ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 4781            table_alias = self._parse_table_alias()
 4782
 4783        return self.expression(
 4784            exp.Lateral(
 4785                this=this,
 4786                view=view,
 4787                outer=outer,
 4788                alias=table_alias,
 4789                cross_apply=cross_apply,
 4790                ordinality=ordinality,
 4791            )
 4792        )
 4793
 4794    def _parse_stream(self) -> exp.Stream | None:
 4795        index = self._index
 4796        if self._match(TokenType.STREAM):
 4797            if this := self._try_parse(self._parse_table):
 4798                return self.expression(exp.Stream(this=this))
 4799            self._retreat(index)
 4800        return None
 4801
 4802    def _parse_join_parts(
 4803        self,
 4804    ) -> tuple[Token | None, Token | None, Token | None]:
 4805        return (
 4806            self._prev if self._match_set(self.JOIN_METHODS) else None,
 4807            self._prev if self._match_set(self.JOIN_SIDES) else None,
 4808            self._prev if self._match_set(self.JOIN_KINDS) else None,
 4809        )
 4810
 4811    def _parse_using_identifiers(self) -> list[exp.Expr]:
 4812        def _parse_column_as_identifier() -> exp.Expr | None:
 4813            this = self._parse_column()
 4814            if isinstance(this, exp.Column):
 4815                return this.this
 4816            return this
 4817
 4818        return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True)
 4819
 4820    def _parse_join(
 4821        self,
 4822        skip_join_token: bool = False,
 4823        parse_bracket: bool = False,
 4824        alias_tokens: t.Collection[TokenType] | None = None,
 4825    ) -> exp.Join | None:
 4826        if self._match(TokenType.COMMA):
 4827            table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens))
 4828            cross_join = self.expression(exp.Join(this=table)) if table else None
 4829
 4830            if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE:
 4831                cross_join.set("kind", "CROSS")
 4832
 4833            return cross_join
 4834
 4835        index = self._index
 4836        method, side, kind = self._parse_join_parts()
 4837        directed = self.SUPPORTS_DIRECTED_JOIN and self._match_text_seq("DIRECTED")
 4838        hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None
 4839        join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN)
 4840        join_comments = self._prev_comments
 4841
 4842        if not skip_join_token and not join:
 4843            self._retreat(index)
 4844            kind = None
 4845            method = None
 4846            side = None
 4847
 4848        outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False)
 4849        cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False)
 4850
 4851        if not skip_join_token and not join and not outer_apply and not cross_apply:
 4852            return None
 4853
 4854        kwargs: dict[str, t.Any] = {
 4855            "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4856        }
 4857        if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA):
 4858            kwargs["expressions"] = self._parse_csv(
 4859                lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4860            )
 4861
 4862        if method:
 4863            kwargs["method"] = method.text.upper()
 4864        if side:
 4865            kwargs["side"] = side.text.upper()
 4866        if kind:
 4867            kwargs["kind"] = kind.text.upper()
 4868        if hint:
 4869            kwargs["hint"] = hint
 4870
 4871        if self._match(TokenType.MATCH_CONDITION):
 4872            kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison)
 4873
 4874        if self._match(TokenType.ON):
 4875            kwargs["on"] = self._parse_disjunction()
 4876        elif self._match(TokenType.USING):
 4877            kwargs["using"] = self._parse_using_identifiers()
 4878        elif (
 4879            not method
 4880            and not (outer_apply or cross_apply)
 4881            and not isinstance(kwargs["this"], exp.Unnest)
 4882            and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY))
 4883        ):
 4884            index = self._index
 4885            joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens))
 4886
 4887            if joins and self._match(TokenType.ON):
 4888                kwargs["on"] = self._parse_disjunction()
 4889            elif joins and self._match(TokenType.USING):
 4890                kwargs["using"] = self._parse_using_identifiers()
 4891            else:
 4892                joins = None
 4893                self._retreat(index)
 4894
 4895            kwargs["this"].set("joins", joins if joins else None)
 4896
 4897        kwargs["pivots"] = self._parse_pivots()
 4898
 4899        comments = [c for token in (method, side, kind) if token for c in token.comments]
 4900        comments = (join_comments or []) + comments
 4901
 4902        if (
 4903            self.ADD_JOIN_ON_TRUE
 4904            and not kwargs.get("on")
 4905            and not kwargs.get("using")
 4906            and not kwargs.get("method")
 4907            and kwargs.get("kind") in (None, "INNER", "OUTER")
 4908        ):
 4909            kwargs["on"] = exp.true()
 4910
 4911        if directed:
 4912            kwargs["directed"] = directed
 4913
 4914        return self.expression(exp.Join(**kwargs), comments=comments)
 4915
 4916    def _parse_opclass(self) -> exp.Expr | None:
 4917        this = self._parse_disjunction()
 4918
 4919        if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False):
 4920            return this
 4921
 4922        if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False):
 4923            return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts()))
 4924
 4925        return this
 4926
 4927    def _parse_index_params(self) -> exp.IndexParameters:
 4928        using = (
 4929            self._parse_var(any_token=True)
 4930            if not self._match_text_seq("USING", "INDEX", "TABLESPACE", advance=False)
 4931            and self._match(TokenType.USING)
 4932            else None
 4933        )
 4934
 4935        if self._match(TokenType.L_PAREN, advance=False):
 4936            columns = self._parse_wrapped_csv(self._parse_with_operator)
 4937        else:
 4938            columns = None
 4939
 4940        include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None
 4941        partition_by = self._parse_partition_by()
 4942        with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties()
 4943        tablespace = (
 4944            self._parse_id_var()
 4945            if self._match_text_seq("USING", "INDEX", "TABLESPACE")
 4946            or self._match_text_seq("TABLESPACE")
 4947            else None
 4948        )
 4949        where = self._parse_where()
 4950
 4951        on = self._parse_field() if self._match(TokenType.ON) else None
 4952
 4953        return self.expression(
 4954            exp.IndexParameters(
 4955                using=using,
 4956                columns=columns,
 4957                include=include,
 4958                partition_by=partition_by,
 4959                where=where,
 4960                with_storage=with_storage,
 4961                tablespace=tablespace,
 4962                on=on,
 4963            )
 4964        )
 4965
 4966    def _parse_index(
 4967        self, index: exp.Expr | None = None, anonymous: bool = False
 4968    ) -> exp.Index | None:
 4969        if index or anonymous:
 4970            unique = None
 4971            primary = None
 4972            amp = None
 4973
 4974            self._match(TokenType.ON)
 4975            self._match(TokenType.TABLE)  # hive
 4976            table = self._parse_table_parts(schema=True)
 4977        else:
 4978            unique = self._match(TokenType.UNIQUE)
 4979            primary = self._match_text_seq("PRIMARY")
 4980            amp = self._match_text_seq("AMP")
 4981
 4982            if not self._match(TokenType.INDEX):
 4983                return None
 4984
 4985            index = self._parse_id_var()
 4986            table = None
 4987
 4988        params = self._parse_index_params()
 4989
 4990        return self.expression(
 4991            exp.Index(
 4992                this=index, table=table, unique=unique, primary=primary, amp=amp, params=params
 4993            )
 4994        )
 4995
 4996    def _parse_table_hints(self) -> list[exp.Expr] | None:
 4997        hints: list[exp.Expr] = []
 4998        if self._match_pair(TokenType.WITH, TokenType.L_PAREN):
 4999            # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16
 5000            hints.append(
 5001                self.expression(
 5002                    exp.WithTableHint(
 5003                        expressions=self._parse_csv(
 5004                            lambda: self._parse_function() or self._parse_var(any_token=True)
 5005                        )
 5006                    )
 5007                )
 5008            )
 5009            self._match_r_paren()
 5010        else:
 5011            # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
 5012            while self._match_set(self.TABLE_INDEX_HINT_TOKENS):
 5013                hint = exp.IndexTableHint(this=self._prev.text.upper())
 5014
 5015                self._match_set((TokenType.INDEX, TokenType.KEY))
 5016                if self._match(TokenType.FOR):
 5017                    hint.set("target", self._advance_any() and self._prev.text.upper())
 5018
 5019                hint.set("expressions", self._parse_wrapped_id_vars())
 5020                hints.append(hint)
 5021
 5022        return hints or None
 5023
 5024    def _parse_table_part(self, schema: bool = False) -> exp.Expr | None:
 5025        return (
 5026            (not schema and self._parse_function(optional_parens=False))
 5027            or self._parse_id_var(any_token=False)
 5028            or self._parse_string_as_identifier()
 5029            or self._parse_placeholder()
 5030        )
 5031
 5032    def _parse_table_parts_fast(self) -> exp.Table | None:
 5033        index = self._index
 5034        parts: list[exp.Identifier] | None = None
 5035        all_comments: list[str] | None = None
 5036
 5037        while self._match_set(self.IDENTIFIER_TOKENS):
 5038            token = self._prev
 5039            comments = self._prev_comments
 5040
 5041            has_dot = self._match(TokenType.DOT)
 5042            curr_tt = self._curr.token_type
 5043
 5044            if not has_dot:
 5045                if curr_tt in self.TABLE_POSTFIX_TOKENS:
 5046                    self._retreat(index)
 5047                    return None
 5048            elif curr_tt not in self.IDENTIFIER_TOKENS:
 5049                self._retreat(index)
 5050                return None
 5051
 5052            if parts is None:
 5053                parts = []
 5054
 5055            if comments:
 5056                if all_comments is None:
 5057                    all_comments = []
 5058                all_comments.extend(comments)
 5059                self._prev_comments = []
 5060
 5061            parts.append(
 5062                self.expression(
 5063                    exp.Identifier(
 5064                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 5065                    ),
 5066                    token,
 5067                )
 5068            )
 5069
 5070            if not has_dot:
 5071                break
 5072
 5073        if parts is None:
 5074            return None
 5075
 5076        n = len(parts)
 5077
 5078        if n == 1:
 5079            table: exp.Table = exp.Table(this=parts[0])
 5080        elif n == 2:
 5081            table = exp.Table(this=parts[1], db=parts[0])
 5082        elif n >= 3:
 5083            this: exp.Identifier | exp.Dot = parts[2]
 5084            for i in range(3, n):
 5085                this = exp.Dot(this=this, expression=parts[i])
 5086
 5087            table = exp.Table(this=this, db=parts[1], catalog=parts[0])
 5088
 5089        if table is None:
 5090            self._retreat(index)
 5091        elif all_comments:
 5092            table.add_comments(all_comments)
 5093        return table
 5094
 5095    def _parse_table_parts(
 5096        self,
 5097        schema: bool = False,
 5098        is_db_reference: bool = False,
 5099        wildcard: bool = False,
 5100        fast: bool = False,
 5101    ) -> exp.Table | exp.Dot | None:
 5102        if fast:
 5103            return self._parse_table_parts_fast()
 5104
 5105        catalog: exp.Expr | str | None = None
 5106        db: exp.Expr | str | None = None
 5107        table: exp.Expr | str | None = self._parse_table_part(schema=schema)
 5108
 5109        while self._match(TokenType.DOT):
 5110            if catalog:
 5111                # This allows nesting the table in arbitrarily many dot expressions if needed
 5112                table = self.expression(
 5113                    exp.Dot(this=table, expression=self._parse_table_part(schema=schema))
 5114                )
 5115            else:
 5116                catalog = db
 5117                db = table
 5118                # "" used for tsql FROM a..b case
 5119                table = self._parse_table_part(schema=schema) or ""
 5120
 5121        if (
 5122            wildcard
 5123            and self._is_connected()
 5124            and (isinstance(table, exp.Identifier) or not table)
 5125            and self._match(TokenType.STAR)
 5126        ):
 5127            if isinstance(table, exp.Identifier):
 5128                table.args["this"] += "*"
 5129            else:
 5130                table = exp.Identifier(this="*")
 5131
 5132        if is_db_reference:
 5133            catalog = db
 5134            db = table
 5135            table = None
 5136
 5137        if not table and not is_db_reference:
 5138            self.raise_error(f"Expected table name but got {self._curr}")
 5139        if not db and is_db_reference:
 5140            self.raise_error(f"Expected database name but got {self._curr}")
 5141
 5142        table = self.expression(exp.Table(this=table, db=db, catalog=catalog))
 5143
 5144        # Bubble up comments from identifier parts to the Table
 5145        comments = []
 5146        for part in table.parts:
 5147            if part_comments := part.pop_comments():
 5148                comments.extend(part_comments)
 5149        if comments:
 5150            table.add_comments(comments)
 5151
 5152        changes = self._parse_changes()
 5153        if changes:
 5154            table.set("changes", changes)
 5155
 5156        at_before = self._parse_historical_data()
 5157        if at_before:
 5158            table.set("when", at_before)
 5159
 5160        pivots = self._parse_pivots()
 5161        if pivots:
 5162            table.set("pivots", pivots)
 5163
 5164        return table
 5165
 5166    def _parse_table(
 5167        self,
 5168        schema: bool = False,
 5169        joins: bool = False,
 5170        alias_tokens: t.Collection[TokenType] | None = None,
 5171        parse_bracket: bool = False,
 5172        is_db_reference: bool = False,
 5173        parse_partition: bool = False,
 5174        consume_pipe: bool = False,
 5175    ) -> exp.Expr | None:
 5176        if not schema and not is_db_reference and not consume_pipe and not joins:
 5177            index = self._index
 5178            table = self._parse_table_parts(fast=True)
 5179
 5180            if table is not None:
 5181                curr_tt = self._curr.token_type
 5182                next_tt = self._next.token_type
 5183
 5184                fast_terminators = self.TABLE_TERMINATORS
 5185
 5186                # only return the table if we're sure there are no other operators
 5187                # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT
 5188                if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION:
 5189                    return table
 5190
 5191                postfix_tokens = self.TABLE_POSTFIX_TOKENS
 5192
 5193                if curr_tt not in postfix_tokens and next_tt not in postfix_tokens:
 5194                    if alias := self._parse_table_alias(
 5195                        alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS
 5196                    ):
 5197                        table.set("alias", alias)
 5198
 5199                    if self._curr.token_type in fast_terminators:
 5200                        return table
 5201
 5202                self._retreat(index)
 5203
 5204        if stream := self._parse_stream():
 5205            return stream
 5206
 5207        if lateral := self._parse_lateral():
 5208            return lateral
 5209
 5210        if unnest := self._parse_unnest():
 5211            return unnest
 5212
 5213        if values := self._parse_derived_table_values():
 5214            return values
 5215
 5216        if subquery := self._parse_select(table=True, consume_pipe=consume_pipe):
 5217            if not subquery.args.get("pivots"):
 5218                subquery.set("pivots", self._parse_pivots())
 5219            if joins:
 5220                for join in self._parse_joins():
 5221                    subquery.append("joins", join)
 5222            return subquery
 5223
 5224        bracket = parse_bracket and self._parse_bracket(None)
 5225        bracket = self.expression(exp.Table(this=bracket)) if bracket else None
 5226
 5227        rows_from_tables = (
 5228            self._parse_wrapped_csv(self._parse_table)
 5229            if self._match_text_seq("ROWS", "FROM")
 5230            else None
 5231        )
 5232        rows_from = (
 5233            self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None
 5234        )
 5235
 5236        only = self._match(TokenType.ONLY)
 5237
 5238        this = t.cast(
 5239            exp.Expr,
 5240            bracket
 5241            or rows_from
 5242            or self._parse_bracket(
 5243                self._parse_table_parts(schema=schema, is_db_reference=is_db_reference)
 5244            ),
 5245        )
 5246
 5247        if only:
 5248            this.set("only", only)
 5249
 5250        # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context
 5251        self._match(TokenType.STAR)
 5252
 5253        parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION
 5254        if parse_partition and self._match(TokenType.PARTITION, advance=False):
 5255            this.set("partition", self._parse_partition())
 5256
 5257        if schema:
 5258            return self._parse_schema(this=this)
 5259
 5260        if self.dialect.ALIAS_POST_VERSION:
 5261            this.set("version", self._parse_version())
 5262
 5263        if self.dialect.ALIAS_POST_TABLESAMPLE:
 5264            this.set("sample", self._parse_table_sample())
 5265            if (
 5266                this.args.get("sample")
 5267                and self.dialect.ALIAS_POST_VERSION
 5268                and not this.args.get("version")
 5269            ):
 5270                this.set("version", self._parse_version())
 5271
 5272        alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 5273        if alias:
 5274            this.set("alias", alias)
 5275
 5276            # DuckDB requires the time-travel clause to come after the alias, e.g.
 5277            # SELECT * FROM t AS a AT (VERSION => 1)
 5278            if isinstance(this, exp.Table) and not this.args.get("when"):
 5279                this.set("when", self._parse_historical_data())
 5280
 5281        if self._match(TokenType.INDEXED_BY):
 5282            this.set("indexed", self._parse_table_parts())
 5283        elif self._match_text_seq("NOT", "INDEXED"):
 5284            this.set("indexed", False)
 5285
 5286        if isinstance(this, exp.Table) and self._match_text_seq("AT"):
 5287            return self.expression(
 5288                exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var())
 5289            )
 5290
 5291        this.set("hints", self._parse_table_hints())
 5292
 5293        if not this.args.get("pivots"):
 5294            this.set("pivots", self._parse_pivots())
 5295
 5296        if not self.dialect.ALIAS_POST_TABLESAMPLE:
 5297            this.set("sample", self._parse_table_sample())
 5298
 5299        if not self.dialect.ALIAS_POST_VERSION:
 5300            this.set("version", self._parse_version())
 5301
 5302        if joins:
 5303            for join in self._parse_joins(alias_tokens=alias_tokens):
 5304                this.append("joins", join)
 5305
 5306        if self._match_pair(TokenType.WITH, TokenType.ORDINALITY):
 5307            this.set("ordinality", True)
 5308            this.set("alias", self._parse_table_alias())
 5309
 5310        # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we
 5311        # hoist the table args onto the latter and return it instead
 5312        if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows):
 5313            table_from_rows = this.this
 5314            for arg in exp.TableFromRows.arg_types:
 5315                if arg != "this":
 5316                    table_from_rows.set(arg, this.args.get(arg))
 5317
 5318            this = table_from_rows
 5319
 5320        return this
 5321
 5322    def _parse_version(self) -> exp.Version | None:
 5323        for phrase, this in self.VERSION_PHRASES.items():
 5324            if self._match_text_seq(*phrase):
 5325                break
 5326        else:
 5327            return None
 5328
 5329        if self._match_set((TokenType.FROM, TokenType.BETWEEN)):
 5330            kind = self._prev.text.upper()
 5331            start = self._parse_bitwise()
 5332            self._match_texts(("TO", "AND"))
 5333            end = self._parse_bitwise()
 5334            expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end]))
 5335        elif self._match_text_seq("CONTAINED", "IN"):
 5336            kind = "CONTAINED IN"
 5337            expression = self.expression(
 5338                exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise))
 5339            )
 5340        elif self._match(TokenType.ALL):
 5341            kind = "ALL"
 5342            expression = None
 5343        else:
 5344            self._match_text_seq("AS", "OF")
 5345            kind = "AS OF"
 5346            expression = self._parse_bitwise()
 5347
 5348        return self.expression(exp.Version(this=this, expression=expression, kind=kind))
 5349
 5350    def _parse_historical_data(self) -> exp.HistoricalData | None:
 5351        # https://docs.snowflake.com/en/sql-reference/constructs/at-before
 5352        index = self._index
 5353        historical_data = None
 5354        if self._match_texts(self.HISTORICAL_DATA_PREFIX):
 5355            this = self._prev.text.upper()
 5356            kind = (
 5357                self._match(TokenType.L_PAREN)
 5358                and self._match_texts(self.HISTORICAL_DATA_KIND)
 5359                and self._prev.text.upper()
 5360            )
 5361            expression = self._match(TokenType.FARROW) and self._parse_bitwise()
 5362
 5363            if expression:
 5364                self._match_r_paren()
 5365                historical_data = self.expression(
 5366                    exp.HistoricalData(this=this, kind=kind, expression=expression)
 5367                )
 5368            else:
 5369                self._retreat(index)
 5370
 5371        return historical_data
 5372
 5373    def _parse_changes(self) -> exp.Changes | None:
 5374        if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"):
 5375            return None
 5376
 5377        information = self._parse_var(any_token=True)
 5378        self._match_r_paren()
 5379
 5380        return self.expression(
 5381            exp.Changes(
 5382                information=information,
 5383                at_before=self._parse_historical_data(),
 5384                end=self._parse_historical_data(),
 5385            )
 5386        )
 5387
 5388    def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None:
 5389        if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False):
 5390            return None
 5391
 5392        self._advance()
 5393
 5394        expressions = self._parse_wrapped_csv(self._parse_equality)
 5395        offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 5396
 5397        alias = self._parse_table_alias() if with_alias else None
 5398
 5399        if alias:
 5400            if self.dialect.UNNEST_COLUMN_ONLY:
 5401                if alias.args.get("columns"):
 5402                    self.raise_error("Unexpected extra column alias in unnest.")
 5403
 5404                alias.set("columns", [alias.this])
 5405                alias.set("this", None)
 5406
 5407            columns = alias.args.get("columns") or []
 5408            if offset and len(expressions) < len(columns):
 5409                offset = columns.pop()
 5410
 5411        if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET):
 5412            self._match(TokenType.ALIAS)
 5413            offset = self._parse_id_var(
 5414                any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS
 5415            ) or exp.to_identifier("offset")
 5416
 5417        return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset))
 5418
 5419    def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None:
 5420        is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES)
 5421        if not is_derived and not (
 5422            # ClickHouse's `FORMAT Values` is equivalent to `VALUES`
 5423            self._match_text_seq("VALUES")
 5424            or self._match_text_seq("FORMAT", "VALUES")
 5425            # MySQL accepts VALUE as a synonym for VALUES in INSERT statements.
 5426            or (allow_value_synonym and self._match_text_seq("VALUE"))
 5427        ):
 5428            return None
 5429
 5430        expressions = self._parse_csv(self._parse_value)
 5431        alias = self._parse_table_alias()
 5432
 5433        if is_derived:
 5434            self._match_r_paren()
 5435
 5436        return self.expression(
 5437            exp.Values(expressions=expressions, alias=alias or self._parse_table_alias())
 5438        )
 5439
 5440    def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None:
 5441        if not self._match(TokenType.TABLE_SAMPLE) and not (
 5442            as_modifier and self._match_text_seq("USING", "SAMPLE")
 5443        ):
 5444            return None
 5445
 5446        bucket_numerator = None
 5447        bucket_denominator = None
 5448        bucket_field = None
 5449        percent = None
 5450        size = None
 5451        seed = None
 5452
 5453        method = self._parse_var(tokens=(TokenType.ROW,), upper=True)
 5454        matched_l_paren = self._match(TokenType.L_PAREN)
 5455
 5456        if self.TABLESAMPLE_CSV:
 5457            num = None
 5458            expressions = self._parse_csv(self._parse_primary)
 5459        else:
 5460            expressions = None
 5461            num = (
 5462                self._parse_factor(parse_mod=False)
 5463                if self._match(TokenType.NUMBER, advance=False)
 5464                else self._parse_primary() or self._parse_placeholder()
 5465            )
 5466
 5467        if self._match_text_seq("BUCKET"):
 5468            bucket_numerator = self._parse_number()
 5469            self._match_text_seq("OUT", "OF")
 5470            bucket_denominator = bucket_denominator = self._parse_number()
 5471            self._match(TokenType.ON)
 5472            bucket_field = self._parse_field()
 5473        elif self._match_set((TokenType.PERCENT, TokenType.MOD)):
 5474            percent = num
 5475        elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
 5476            size = num
 5477        else:
 5478            percent = num
 5479
 5480        if matched_l_paren:
 5481            self._match_r_paren()
 5482
 5483        if self._match(TokenType.L_PAREN):
 5484            method = self._parse_var(upper=True)
 5485            seed = self._match(TokenType.COMMA) and self._parse_number()
 5486            self._match_r_paren()
 5487        elif self._match_texts(("SEED", "REPEATABLE")):
 5488            seed = self._parse_wrapped(self._parse_number)
 5489
 5490        if not method and self.DEFAULT_SAMPLING_METHOD:
 5491            method = exp.var(self.DEFAULT_SAMPLING_METHOD)
 5492
 5493        return self.expression(
 5494            exp.TableSample(
 5495                expressions=expressions,
 5496                method=method,
 5497                bucket_numerator=bucket_numerator,
 5498                bucket_denominator=bucket_denominator,
 5499                bucket_field=bucket_field,
 5500                percent=percent,
 5501                size=size,
 5502                seed=seed,
 5503            )
 5504        )
 5505
 5506    def _parse_pivots(self) -> list[exp.Pivot] | None:
 5507        if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT):
 5508            return None
 5509        return list(iter(self._parse_pivot, None)) or None
 5510
 5511    def _parse_joins(
 5512        self, alias_tokens: t.Collection[TokenType] | None = None
 5513    ) -> t.Iterator[exp.Join]:
 5514        return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None)
 5515
 5516    def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None:
 5517        if not self._match(TokenType.INTO):
 5518            return None
 5519
 5520        return self.expression(
 5521            exp.UnpivotColumns(
 5522                this=self._match_text_seq("NAME") and self._parse_column(),
 5523                expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column),
 5524            )
 5525        )
 5526
 5527    # https://duckdb.org/docs/sql/statements/pivot
 5528    def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot:
 5529        def _parse_on() -> exp.Expr | None:
 5530            this = self._parse_bitwise()
 5531
 5532            if self._match(TokenType.IN):
 5533                # PIVOT ... ON col IN (row_val1, row_val2)
 5534                return self._parse_in(this)
 5535            if self._match(TokenType.ALIAS, advance=False):
 5536                # UNPIVOT ... ON (col1, col2, col3) AS row_val
 5537                return self._parse_alias(this)
 5538
 5539            return this
 5540
 5541        this = self._parse_table()
 5542        expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on)
 5543        into = self._parse_unpivot_columns()
 5544        using = self._match(TokenType.USING) and self._parse_csv(
 5545            lambda: self._parse_alias(self._parse_column())
 5546        )
 5547        group = self._parse_group()
 5548
 5549        return self.expression(
 5550            exp.Pivot(
 5551                this=this,
 5552                expressions=expressions,
 5553                using=using,
 5554                group=group,
 5555                unpivot=is_unpivot,
 5556                into=into,
 5557            )
 5558        )
 5559
 5560    def _parse_pivot_in(self) -> exp.In:
 5561        def _parse_aliased_expression() -> exp.Expr | None:
 5562            this = self._parse_select_or_expression()
 5563
 5564            self._match(TokenType.ALIAS)
 5565            alias = self._parse_bitwise()
 5566            if alias:
 5567                if isinstance(alias, exp.Column) and not alias.db:
 5568                    alias = alias.this
 5569                return self.expression(exp.PivotAlias(this=this, alias=alias))
 5570
 5571            return this
 5572
 5573        value = self._parse_column()
 5574
 5575        if not self._match(TokenType.IN):
 5576            self.raise_error("Expecting IN")
 5577
 5578        if self._match(TokenType.L_PAREN):
 5579            if self._match(TokenType.ANY):
 5580                exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order()))
 5581            else:
 5582                exprs = self._parse_csv(_parse_aliased_expression)
 5583            self._match_r_paren()
 5584            return self.expression(exp.In(this=value, expressions=exprs))
 5585
 5586        return self.expression(exp.In(this=value, field=self._parse_id_var()))
 5587
 5588    def _parse_pivot_aggregation(self) -> exp.Expr | None:
 5589        func = self._parse_function()
 5590        if not func:
 5591            if self._prev.token_type == TokenType.COMMA:
 5592                return None
 5593            self.raise_error("Expecting an aggregation function in PIVOT")
 5594
 5595        return self._parse_alias(func)
 5596
 5597    def _parse_pivot(self) -> exp.Pivot | None:
 5598        index = self._index
 5599        include_nulls = None
 5600
 5601        if self._match(TokenType.PIVOT):
 5602            unpivot = False
 5603        elif self._match(TokenType.UNPIVOT):
 5604            unpivot = True
 5605
 5606            # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax
 5607            if self._match_text_seq("INCLUDE", "NULLS"):
 5608                include_nulls = True
 5609            elif self._match_text_seq("EXCLUDE", "NULLS"):
 5610                include_nulls = False
 5611        else:
 5612            return None
 5613
 5614        expressions = []
 5615
 5616        if not self._match(TokenType.L_PAREN):
 5617            self._retreat(index)
 5618            return None
 5619
 5620        if unpivot:
 5621            expressions = self._parse_csv(self._parse_column)
 5622        else:
 5623            expressions = self._parse_csv(self._parse_pivot_aggregation)
 5624
 5625        if not expressions:
 5626            self.raise_error("Failed to parse PIVOT's aggregation list")
 5627
 5628        if not self._match(TokenType.FOR):
 5629            self.raise_error("Expecting FOR")
 5630
 5631        fields = []
 5632        while True:
 5633            field = self._try_parse(self._parse_pivot_in)
 5634            if not field:
 5635                break
 5636            fields.append(field)
 5637
 5638        default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped(
 5639            self._parse_bitwise
 5640        )
 5641
 5642        group = self._parse_group()
 5643
 5644        self._match_r_paren()
 5645
 5646        pivot = self.expression(
 5647            exp.Pivot(
 5648                expressions=expressions,
 5649                fields=fields,
 5650                unpivot=unpivot,
 5651                include_nulls=include_nulls,
 5652                default_on_null=default_on_null,
 5653                group=group,
 5654            )
 5655        )
 5656
 5657        if unpivot:
 5658            pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions])
 5659            for pivot_field in pivot.fields:
 5660                if isinstance(pivot_field, exp.In):
 5661                    pivot_field.set("this", _unpivot_target(pivot_field.this))
 5662
 5663            pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST)
 5664
 5665        if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False):
 5666            pivot.set("alias", self._parse_table_alias())
 5667
 5668        if not unpivot:
 5669            names = self._pivot_column_names(t.cast(list[exp.Expr], expressions))
 5670
 5671            columns: list[exp.Expr] = []
 5672            all_fields = []
 5673            for pivot_field in pivot.fields:
 5674                pivot_field_expressions = pivot_field.expressions
 5675
 5676                # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case.
 5677                if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny):
 5678                    continue
 5679
 5680                all_fields.append(
 5681                    [
 5682                        # An explicit `<field> AS <alias>` names the output column directly,
 5683                        # so it wins over the dialect's string-identifying convention
 5684                        fld.sql()
 5685                        if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias)
 5686                        else fld.alias_or_name
 5687                        for fld in pivot_field_expressions
 5688                    ]
 5689                )
 5690
 5691            if all_fields:
 5692                if names:
 5693                    all_fields.append(names)
 5694
 5695                # Generate all possible combinations of the pivot columns
 5696                # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US'))
 5697                # generates the product between [[2000, 2010], ['NL', 'US'], ['total']]
 5698                for fld_parts_tuple in itertools.product(*all_fields):
 5699                    fld_parts = list(fld_parts_tuple)
 5700
 5701                    if names and self.PREFIXED_PIVOT_COLUMNS:
 5702                        # Move the "name" to the front of the list
 5703                        fld_parts.insert(0, fld_parts.pop(-1))
 5704
 5705                    columns.append(exp.to_identifier("_".join(fld_parts)))
 5706
 5707            pivot.set("columns", columns)
 5708            pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS)
 5709            pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS)
 5710            pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING)
 5711
 5712        return pivot
 5713
 5714    def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]:
 5715        return [agg.alias for agg in aggregations if agg.alias]
 5716
 5717    def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None:
 5718        if not skip_where_token and not self._match(TokenType.PREWHERE):
 5719            return None
 5720
 5721        comments = self._prev_comments
 5722        return self.expression(
 5723            exp.PreWhere(this=self._parse_disjunction()),
 5724            comments=comments,
 5725        )
 5726
 5727    def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None:
 5728        if not skip_where_token and not self._match(TokenType.WHERE):
 5729            return None
 5730
 5731        comments = self._prev_comments
 5732        return self.expression(
 5733            exp.Where(this=self._parse_disjunction()),
 5734            comments=comments,
 5735        )
 5736
 5737    def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None:
 5738        if not skip_group_by_token and not self._match(TokenType.GROUP_BY):
 5739            return None
 5740        comments = self._prev_comments
 5741
 5742        elements: dict[str, t.Any] = defaultdict(list)
 5743
 5744        if self._match(TokenType.ALL):
 5745            elements["all"] = True
 5746        elif self._match(TokenType.DISTINCT):
 5747            elements["all"] = False
 5748
 5749        while True:
 5750            # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW,
 5751            # which are also valid identifiers
 5752            if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False):
 5753                break
 5754
 5755            elements["expressions"].extend(
 5756                self._parse_csv(
 5757                    lambda: (
 5758                        self._parse_grouping_sets()
 5759                        or self._parse_cube_or_rollup()
 5760                        or self._parse_disjunction()
 5761                    )
 5762                )
 5763            )
 5764
 5765            before_with_index = self._index
 5766
 5767            if self._match(TokenType.WITH) and (
 5768                cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True)
 5769            ):
 5770                key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube"
 5771                elements[key].append(cube_or_rollup)
 5772            elif grouping_sets := self._parse_grouping_sets():
 5773                # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...)
 5774                elements["grouping_sets"].append(grouping_sets)
 5775                break
 5776            elif self._match_text_seq("TOTALS"):
 5777                elements["totals"] = True  # type: ignore
 5778
 5779            if before_with_index <= self._index <= before_with_index + 1:
 5780                self._retreat(before_with_index)
 5781                break
 5782
 5783        return self.expression(exp.Group(**elements), comments=comments)  # type: ignore
 5784
 5785    def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None:
 5786        if self._match(TokenType.CUBE):
 5787            kind: type[exp.Cube | exp.Rollup] = exp.Cube
 5788        elif self._match(TokenType.ROLLUP):
 5789            kind = exp.Rollup
 5790        else:
 5791            return None
 5792
 5793        return self.expression(
 5794            kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise))
 5795        )
 5796
 5797    def _parse_grouping_sets(self) -> exp.GroupingSets | None:
 5798        if self._match(TokenType.GROUPING_SETS):
 5799            return self.expression(
 5800                exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set))
 5801            )
 5802        return None
 5803
 5804    def _parse_grouping_set(self) -> exp.Expr | None:
 5805        return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise()
 5806
 5807    def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None:
 5808        if not skip_having_token and not self._match(TokenType.HAVING):
 5809            return None
 5810        comments = self._prev_comments
 5811        return self.expression(
 5812            exp.Having(this=self._parse_disjunction()),
 5813            comments=comments,
 5814        )
 5815
 5816    def _parse_qualify(self) -> exp.Qualify | None:
 5817        if not self._match(TokenType.QUALIFY):
 5818            return None
 5819        return self.expression(exp.Qualify(this=self._parse_disjunction()))
 5820
 5821    def _parse_connect_with_prior(self) -> exp.Expr | None:
 5822        self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression(
 5823            exp.Prior(this=self._parse_bitwise())
 5824        )
 5825        connect = self._parse_disjunction()
 5826        self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR")
 5827        return connect
 5828
 5829    def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None:
 5830        if skip_start_token:
 5831            start = None
 5832        elif self._match_text_seq("START", "WITH"):
 5833            start = self._parse_disjunction()
 5834        else:
 5835            return None
 5836
 5837        self._match(TokenType.CONNECT_BY)
 5838        nocycle = self._match_text_seq("NOCYCLE")
 5839        connect = self._parse_connect_with_prior()
 5840
 5841        if not start and self._match_text_seq("START", "WITH"):
 5842            start = self._parse_disjunction()
 5843
 5844        return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle))
 5845
 5846    def _parse_name_as_expression(self) -> exp.Expr | None:
 5847        this = self._parse_id_var(any_token=True)
 5848        if self._match(TokenType.ALIAS):
 5849            this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction()))
 5850        return this
 5851
 5852    def _parse_interpolate(self) -> list[exp.Expr] | None:
 5853        if self._match_text_seq("INTERPOLATE"):
 5854            return self._parse_wrapped_csv(self._parse_name_as_expression)
 5855        return None
 5856
 5857    def _parse_order(
 5858        self, this: exp.Expr | None = None, skip_order_token: bool = False
 5859    ) -> exp.Expr | None:
 5860        siblings = None
 5861        if not skip_order_token and not self._match(TokenType.ORDER_BY):
 5862            if not self._match(TokenType.ORDER_SIBLINGS_BY):
 5863                return this
 5864
 5865            siblings = True
 5866
 5867        comments = self._prev_comments
 5868        return self.expression(
 5869            exp.Order(
 5870                this=this,
 5871                expressions=self._parse_csv(self._parse_ordered),
 5872                siblings=siblings,
 5873            ),
 5874            comments=comments,
 5875        )
 5876
 5877    def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None:
 5878        if not self._match(token):
 5879            return None
 5880        return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered)))
 5881
 5882    def _parse_ordered(
 5883        self, parse_method: t.Callable[[], exp.Expr | None] | None = None
 5884    ) -> exp.Ordered | None:
 5885        this = parse_method() if parse_method else self._parse_disjunction()
 5886        if not this:
 5887            return None
 5888
 5889        if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL:
 5890            this = exp.var("ALL")
 5891
 5892        asc = self._match(TokenType.ASC)
 5893        desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None)
 5894
 5895        is_nulls_first = self._match_text_seq("NULLS", "FIRST")
 5896        is_nulls_last = self._match_text_seq("NULLS", "LAST")
 5897
 5898        nulls_first = is_nulls_first or False
 5899        explicitly_null_ordered = is_nulls_first or is_nulls_last
 5900
 5901        if (
 5902            not explicitly_null_ordered
 5903            and (
 5904                (not desc and self.dialect.NULL_ORDERING == "nulls_are_small")
 5905                or (desc and self.dialect.NULL_ORDERING != "nulls_are_small")
 5906            )
 5907            and self.dialect.NULL_ORDERING != "nulls_are_last"
 5908        ):
 5909            nulls_first = True
 5910
 5911        if self._match_text_seq("WITH", "FILL"):
 5912            with_fill = self.expression(
 5913                exp.WithFill(
 5914                    from_=self._match(TokenType.FROM) and self._parse_bitwise(),
 5915                    to=self._match_text_seq("TO") and self._parse_bitwise(),
 5916                    step=self._match_text_seq("STEP") and self._parse_bitwise(),
 5917                    interpolate=self._parse_interpolate(),
 5918                )
 5919            )
 5920        else:
 5921            with_fill = None
 5922
 5923        return self.expression(
 5924            exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill)
 5925        )
 5926
 5927    def _parse_limit_options(self) -> exp.LimitOptions | None:
 5928        percent = self._match_set((TokenType.PERCENT, TokenType.MOD))
 5929        rows = self._match_texts(("ROW", "ROWS"))
 5930        self._match_text_seq("ONLY")
 5931        with_ties = self._match_text_seq("WITH", "TIES")
 5932
 5933        if not (percent or rows or with_ties):
 5934            return None
 5935
 5936        return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties))
 5937
 5938    def _parse_limit(
 5939        self,
 5940        this: exp.Expr | None = None,
 5941        top: bool = False,
 5942        skip_limit_token: bool = False,
 5943    ) -> exp.Expr | None:
 5944        if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT):
 5945            comments = self._prev_comments
 5946            if top:
 5947                limit_paren = self._match(TokenType.L_PAREN)
 5948                expression = (
 5949                    self._parse_term() or self._parse_select()
 5950                    if limit_paren
 5951                    else self._parse_number()
 5952                )
 5953
 5954                if limit_paren:
 5955                    self._match_r_paren()
 5956
 5957            else:
 5958                if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL):
 5959                    expression = exp.var("ALL")
 5960                else:
 5961                    expression = self._parse_term(parse_mod=False)
 5962            limit_options = self._parse_limit_options()
 5963
 5964            if self._match(TokenType.COMMA):
 5965                offset = expression
 5966                expression = self._parse_term()
 5967            else:
 5968                offset = None
 5969
 5970            limit_exp = self.expression(
 5971                exp.Limit(
 5972                    this=this,
 5973                    expression=expression,
 5974                    offset=offset,
 5975                    limit_options=limit_options,
 5976                    expressions=self._parse_limit_by(),
 5977                ),
 5978                comments=comments,
 5979            )
 5980
 5981            if top:
 5982                limit_exp.meta["top"] = True
 5983
 5984            return limit_exp
 5985
 5986        if self._match(TokenType.FETCH):
 5987            direction = (
 5988                self._prev.text.upper()
 5989                if self._match_set((TokenType.FIRST, TokenType.NEXT))
 5990                else "FIRST"
 5991            )
 5992
 5993            count = (
 5994                None
 5995                if self._match_texts(("ROW", "ROWS"), advance=False)
 5996                else self._parse_field(tokens=self.FETCH_TOKENS)
 5997            )
 5998
 5999            return self.expression(
 6000                exp.Fetch(
 6001                    direction=direction, count=count, limit_options=self._parse_limit_options()
 6002                )
 6003            )
 6004
 6005        return this
 6006
 6007    def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6008        if not self._match(TokenType.OFFSET):
 6009            return this
 6010
 6011        count = self._parse_term()
 6012        self._match_texts(("ROW", "ROWS"))
 6013
 6014        return self.expression(
 6015            exp.Offset(this=this, expression=count, expressions=self._parse_limit_by())
 6016        )
 6017
 6018    def _can_parse_limit_or_offset(self) -> bool:
 6019        if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False):
 6020            return False
 6021
 6022        index = self._index
 6023        result = bool(
 6024            self._try_parse(self._parse_limit, retreat=True)
 6025            or self._try_parse(self._parse_offset, retreat=True)
 6026        )
 6027        self._retreat(index)
 6028
 6029        # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset
 6030        if self._next.token_type == TokenType.MATCH_CONDITION:
 6031            result = False
 6032
 6033        return result
 6034
 6035    def _can_parse_named_window(self) -> bool:
 6036        # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias.
 6037        # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`.
 6038        if not self._match(TokenType.WINDOW, advance=False):
 6039            return False
 6040
 6041        name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None
 6042        if name is None or name.token_type not in self.ID_VAR_TOKENS:
 6043            return False
 6044
 6045        alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None
 6046        if alias_tok is None or alias_tok.token_type != TokenType.ALIAS:
 6047            return False
 6048
 6049        body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None
 6050        return body is not None and body.token_type == TokenType.L_PAREN
 6051
 6052    def _parse_limit_by(self) -> list[exp.Expr] | None:
 6053        return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None
 6054
 6055    def _parse_locks(self) -> list[exp.Lock]:
 6056        locks = []
 6057        while True:
 6058            update, key = None, None
 6059            if self._match_text_seq("FOR", "UPDATE"):
 6060                update = True
 6061            elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq(
 6062                "LOCK", "IN", "SHARE", "MODE"
 6063            ):
 6064                update = False
 6065            elif self._match_text_seq("FOR", "KEY", "SHARE"):
 6066                update, key = False, True
 6067            elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"):
 6068                update, key = True, True
 6069            else:
 6070                break
 6071
 6072            expressions = None
 6073            if self._match_text_seq("OF"):
 6074                expressions = self._parse_csv(lambda: self._parse_table(schema=True))
 6075
 6076            wait: bool | exp.Expr | None = None
 6077            if self._match_text_seq("NOWAIT"):
 6078                wait = True
 6079            elif self._match_text_seq("WAIT"):
 6080                wait = self._parse_primary()
 6081            elif self._match_text_seq("SKIP", "LOCKED"):
 6082                wait = False
 6083
 6084            locks.append(
 6085                self.expression(
 6086                    exp.Lock(update=update, expressions=expressions, wait=wait, key=key)
 6087                )
 6088            )
 6089
 6090        return locks
 6091
 6092    def parse_set_operation(
 6093        self, this: exp.Expr | None, consume_pipe: bool = False, intersect_only: bool = False
 6094    ) -> exp.SetOperation | None:
 6095        start = self._index
 6096        _, side_token, kind_token = self._parse_join_parts()
 6097
 6098        side = side_token.text if side_token else None
 6099        kind = kind_token.text if kind_token else None
 6100
 6101        if not self._match_set(self.SET_OPERATIONS) or (
 6102            intersect_only and self._prev.token_type != TokenType.INTERSECT
 6103        ):
 6104            self._retreat(start)
 6105            return None
 6106
 6107        token_type = self._prev.token_type
 6108
 6109        if token_type == TokenType.UNION:
 6110            operation: type[exp.SetOperation] = exp.Union
 6111        elif token_type == TokenType.EXCEPT:
 6112            operation = exp.Except
 6113        else:
 6114            operation = exp.Intersect
 6115
 6116        comments = self._prev.comments
 6117
 6118        if self._match(TokenType.DISTINCT):
 6119            distinct: bool | None = True
 6120        elif self._match(TokenType.ALL):
 6121            distinct = False
 6122        else:
 6123            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
 6124            if distinct is None:
 6125                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
 6126
 6127        by_name = (
 6128            self._match_text_seq("BY", "NAME")
 6129            or self._match_text_seq("STRICT", "CORRESPONDING")
 6130            or None
 6131        )
 6132        if self._match_text_seq("CORRESPONDING"):
 6133            by_name = True
 6134            if not side and not kind:
 6135                kind = "INNER"
 6136
 6137        on_column_list = None
 6138        if by_name and self._match_texts(("ON", "BY")):
 6139            on_column_list = self._parse_wrapped_csv(self._parse_column)
 6140
 6141        expression = self._parse_select(
 6142            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
 6143        )
 6144
 6145        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
 6146        # in _parse_cte and so that alias pushdown can reach into set operation branches
 6147        if isinstance(this, exp.Values):
 6148            this = self._values_to_select(this)
 6149        if isinstance(expression, exp.Values):
 6150            expression = self._values_to_select(expression)
 6151        for operand in (this, expression):
 6152            if isinstance(operand, exp.Subquery) and isinstance(
 6153                values := operand.unnest(), exp.Values
 6154            ):
 6155                wrapper = t.cast(exp.Subquery, values.parent)
 6156                wrapper.set("this", self._values_to_select(values))
 6157
 6158        if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery):
 6159            subquery = this.this
 6160            subquery.set("alias", exp.TableAlias(this=this.args["alias"]))
 6161            subquery.add_comments(this.pop_comments())
 6162            this = subquery
 6163
 6164        return self.expression(
 6165            operation(
 6166                this=this,
 6167                distinct=distinct,
 6168                by_name=by_name,
 6169                expression=expression,
 6170                side=side,
 6171                kind=kind,
 6172                on=on_column_list,
 6173            ),
 6174            comments=comments,
 6175        )
 6176
 6177    def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None:
 6178        intersect_binds_tighter = self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT
 6179        last_setop: exp.SetOperation | None = None
 6180
 6181        while this:
 6182            setop = self.parse_set_operation(this)
 6183            if not setop:
 6184                break
 6185
 6186            last_setop = setop
 6187            if (
 6188                isinstance(setop, exp.Intersect)
 6189                and isinstance(setop.this, (exp.Union, exp.Except))
 6190                and not intersect_binds_tighter
 6191            ):
 6192                setop.set("this", setop.this.subquery(copy=False))
 6193            elif not isinstance(setop, exp.Intersect) and intersect_binds_tighter:
 6194                # INTERSECT binds tighter than this UNION / EXCEPT, so any INTERSECTs that
 6195                # follow it group with its right operand first:
 6196                # a UNION b INTERSECT c INTERSECT d -> a UNION (b INTERSECT c INTERSECT d)
 6197                operand = setop.expression
 6198                while intersect := self.parse_set_operation(operand, intersect_only=True):
 6199                    last_setop = operand = intersect
 6200                if operand is not setop.expression:
 6201                    setop.set("expression", operand.subquery(copy=False))
 6202
 6203            this = setop
 6204
 6205        if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP:
 6206            expression = last_setop.expression if last_setop else None
 6207
 6208            if expression:
 6209                for arg in self.SET_OP_MODIFIERS:
 6210                    expr = expression.args.get(arg)
 6211                    if expr and not (arg == "limit" and expr.meta.get("top")):
 6212                        expression.set(arg, None)
 6213                        this.set(arg, expr)
 6214
 6215            # Consume modifiers after a parenthesized final operand (e.g., SELECT 1 UNION (SELECT 2)
 6216            # ORDER BY 1), or a LIMIT/FETCH that a final operand with TOP leaves for the whole
 6217            # result (e.g., SELECT 1 UNION SELECT TOP 2 2 LIMIT 1).
 6218            if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):
 6219                this = self._parse_query_modifiers(this)
 6220
 6221        return this
 6222
 6223    def _parse_expression(self) -> exp.Expr | None:
 6224        return self._parse_alias(self._parse_assignment())
 6225
 6226    def _parse_assignment(self) -> exp.Expr | None:
 6227        this = self._parse_disjunction()
 6228        if not this and self._next.token_type in self.ASSIGNMENT:
 6229            # This allows us to parse <non-identifier token> := <expr>
 6230            this = exp.column(
 6231                t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text)
 6232            )
 6233
 6234        while self._match_set(self.ASSIGNMENT):
 6235            if isinstance(this, exp.Column) and len(this.parts) == 1:
 6236                this = this.this
 6237
 6238            comments = self._prev_comments
 6239            this = self.expression(
 6240                self.ASSIGNMENT[self._prev.token_type](
 6241                    this=this, expression=self._parse_assignment()
 6242                ),
 6243                comments=comments,
 6244            )
 6245
 6246        return this
 6247
 6248    def _parse_disjunction(self) -> exp.Expr | None:
 6249        this = self._parse_conjunction()
 6250        while self._match_set(self.DISJUNCTION):
 6251            comments = self._prev_comments
 6252            this = self.expression(
 6253                self.DISJUNCTION[self._prev.token_type](
 6254                    this=this, expression=self._parse_conjunction()
 6255                ),
 6256                comments=comments,
 6257            )
 6258        return this
 6259
 6260    def _parse_conjunction(self) -> exp.Expr | None:
 6261        this = self._parse_equality()
 6262        while self._match_set(self.CONJUNCTION):
 6263            comments = self._prev_comments
 6264            this = self.expression(
 6265                self.CONJUNCTION[self._prev.token_type](
 6266                    this=this, expression=self._parse_equality()
 6267                ),
 6268                comments=comments,
 6269            )
 6270        return this
 6271
 6272    def _build_predicate(
 6273        self,
 6274        klass: Type[exp.Binary],
 6275        this: exp.Expr | None,
 6276        expression: exp.Expr | None,
 6277        comments: list[str] | None = None,
 6278        negate: bool = False,
 6279    ) -> exp.Expr:
 6280        is_predicates = (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ)
 6281        if klass in is_predicates:
 6282            if (
 6283                isinstance(this, (exp.Connector, exp.Not, exp.NullSafeNEQ))
 6284                or type(this) in self.EQUALITY.values()
 6285                or type(this) in self.COMPARISON.values()
 6286                or (klass is not exp.Is and isinstance(this, exp.Is))
 6287            ):
 6288                this = exp.Paren(this=this)
 6289            if isinstance(expression, (exp.Predicate, exp.Connector, exp.Not, exp.Escape)):
 6290                expression = exp.Paren(this=expression)
 6291        else:
 6292            if isinstance(this.this if isinstance(this, exp.Not) else this, is_predicates):
 6293                this = exp.Paren(this=this)
 6294            if isinstance(
 6295                expression.this if isinstance(expression, exp.Not) else expression, is_predicates
 6296            ):
 6297                expression = exp.Paren(this=expression)
 6298
 6299        predicate = klass(this=this, expression=expression)
 6300        if negate:
 6301            predicate.set("negate", True)
 6302        return self.expression(predicate, comments=comments)
 6303
 6304    def _parse_equality(self, parse_is: bool = True) -> exp.Expr | None:
 6305        this = self._parse_comparison()
 6306        while True:
 6307            if self._match_set(self.EQUALITY):
 6308                comments = self._prev_comments
 6309                this = self._build_predicate(
 6310                    self.EQUALITY[self._prev.token_type],
 6311                    this,
 6312                    self._parse_comparison(),
 6313                    comments=comments,
 6314                )
 6315            elif parse_is and self._is_predicate_ahead():
 6316                index = self._index
 6317                # A postfix predicate can itself be an operand, e.g. x IS NULL = FALSE.
 6318                this = self._parse_comparison(self._parse_range(this, parse_is=True))
 6319                if self._index == index:
 6320                    break
 6321            else:
 6322                break
 6323        return this
 6324
 6325    def _parse_comparison(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6326        this = this or self._parse_range()
 6327        while self._match_set(self.COMPARISON):
 6328            comments = self._prev_comments
 6329            this = self._build_predicate(
 6330                self.COMPARISON[self._prev.token_type],
 6331                this,
 6332                self._parse_range(),
 6333                comments=comments,
 6334            )
 6335        return this
 6336
 6337    def _is_predicate_ahead(self) -> bool:
 6338        if (
 6339            self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.HIGH
 6340            or self._curr.token_type not in self.IS_PREDICATE_TOKENS
 6341        ):
 6342            return False
 6343        return self._curr.token_type != TokenType.NOT or self._next.token_type == TokenType.NULL
 6344
 6345    def _parse_range(self, this: exp.Expr | None = None, parse_is: bool = False) -> exp.Expr | None:
 6346        this = this or self._parse_bitwise()
 6347
 6348        while True:
 6349            if not parse_is and self._is_predicate_ahead():
 6350                break
 6351            negate = self._match(TokenType.NOT)
 6352            if self._match_set(self.RANGE_PARSERS):
 6353                expression = self.RANGE_PARSERS[self._prev.token_type](self, this)
 6354                if not expression:
 6355                    return this
 6356
 6357                this = expression
 6358            elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)):
 6359                this = self._build_predicate(exp.Is, this, exp.Null())
 6360            elif self._match(TokenType.NOTNULL):
 6361                # Postgres supports ISNULL and NOTNULL for conditions.
 6362                # https://blog.andreiavram.ro/postgresql-null-composite-type/
 6363                if self.dialect.NORMALIZE_NOT_NULL:
 6364                    this = self._build_predicate(exp.Is, this, exp.Null())
 6365                    this = self.expression(exp.Not(this=this))
 6366                else:
 6367                    this = self._build_predicate(exp.Is, this, exp.Null(), negate=True)
 6368            else:
 6369                if negate:
 6370                    self._retreat(self._index - 1)
 6371                break
 6372
 6373            if negate:
 6374                this = self._negate_range(this)
 6375            if (negate or (isinstance(this, exp.Not) and isinstance(this.this, exp.Is))) and (
 6376                self._curr.token_type == TokenType.NOT
 6377                or self._curr.token_type in self.RANGE_PARSERS
 6378            ):
 6379                this = self.expression(exp.Paren(this=this))
 6380
 6381        return this
 6382
 6383    def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6384        if not this:
 6385            return this
 6386
 6387        expression = this.this if isinstance(this, exp.Escape) else this
 6388        if isinstance(expression, (exp.Like, exp.ILike)):
 6389            expression.set("negate", True)
 6390            return this
 6391
 6392        return self.expression(exp.Not(this=this))
 6393
 6394    def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None:
 6395        index = self._index - 1
 6396        negate = self._match(TokenType.NOT)
 6397
 6398        if self._match_text_seq("DISTINCT", "FROM"):
 6399            klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ
 6400            return self._build_predicate(klass, this, self._parse_is_rhs(distinct=True))
 6401
 6402        if self._match(TokenType.JSON):
 6403            kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper()
 6404
 6405            if self._match_text_seq("WITH"):
 6406                _with = True
 6407            elif self._match_text_seq("WITHOUT"):
 6408                _with = False
 6409            else:
 6410                _with = None
 6411
 6412            unique = self._match(TokenType.UNIQUE)
 6413            self._match_text_seq("KEYS")
 6414            expression: exp.Expr | None = self.expression(
 6415                exp.JSON(this=kind, with_=_with, unique=unique)
 6416            )
 6417        else:
 6418            expression = self._parse_is_rhs()
 6419            if not expression:
 6420                self._retreat(index)
 6421                return None
 6422
 6423        if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL:
 6424            this = self._build_predicate(exp.Is, this, expression, negate=True)
 6425        else:
 6426            this = self._build_predicate(exp.Is, this, expression)
 6427            this = self.expression(exp.Not(this=this)) if negate else this
 6428
 6429        return self._parse_column_ops(this)
 6430
 6431    def _parse_is_rhs(self, distinct: bool = False) -> exp.Expr | None:
 6432        if distinct:
 6433            if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.LOW:
 6434                return self._parse_equality(parse_is=False)
 6435            if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.EQUALITY:
 6436                return self._parse_comparison()
 6437            if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.COMPARISON:
 6438                return self._parse_range()
 6439            return self._parse_bitwise()
 6440        return self._parse_null() or self._parse_bitwise()
 6441
 6442    def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In:
 6443        unnest = self._parse_unnest(with_alias=False)
 6444        if unnest:
 6445            this = self.expression(exp.In(this=this, unnest=unnest))
 6446        elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)):
 6447            matched_l_paren = self._prev.token_type == TokenType.L_PAREN
 6448            expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias))
 6449
 6450            if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query):
 6451                this = self.expression(
 6452                    exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False))
 6453                )
 6454            else:
 6455                this = self.expression(exp.In(this=this, expressions=expressions))
 6456
 6457            if matched_l_paren:
 6458                self._match_r_paren(this)
 6459            elif not self._match(TokenType.R_BRACKET, expression=this):
 6460                self.raise_error("Expecting ]")
 6461        else:
 6462            this = self.expression(exp.In(this=this, field=self._parse_column()))
 6463
 6464        return this
 6465
 6466    def _parse_between(self, this: exp.Expr | None) -> exp.Between:
 6467        # Preserve grouping when BETWEEN is rewritten into comparisons.
 6468        if isinstance(this, (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ)):
 6469            this = exp.Paren(this=this)
 6470
 6471        symmetric = None
 6472        if self._match_text_seq("SYMMETRIC"):
 6473            symmetric = True
 6474        elif self._match_text_seq("ASYMMETRIC"):
 6475            symmetric = False
 6476
 6477        low = self._parse_bitwise()
 6478        self._match(TokenType.AND)
 6479        high = self._parse_bitwise()
 6480
 6481        return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric))
 6482
 6483    def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None:
 6484        if not self._match(TokenType.ESCAPE):
 6485            return this
 6486        return self.expression(
 6487            exp.Escape(this=this, expression=self._parse_string() or self._parse_null())
 6488        )
 6489
 6490    def _parse_interval_span(
 6491        self, this: exp.Expr, parse_function_unit: bool = True
 6492    ) -> exp.Interval:
 6493        # handle day-time format interval span with omitted units:
 6494        #   INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`>
 6495        interval_span_units_omitted = None
 6496        fraction_in_string = False
 6497        if (
 6498            this
 6499            and this.is_string
 6500            and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
 6501            and exp.INTERVAL_DAY_TIME_RE.match(this.name)
 6502        ):
 6503            index = self._index
 6504
 6505            # Var "TO" Var
 6506            first_unit = self._parse_var(any_token=True, upper=True)
 6507            second_unit = None
 6508            if first_unit and self._match_text_seq("TO"):
 6509                second_unit = self._parse_var(any_token=True, upper=True)
 6510
 6511            interval_span_units_omitted = not (first_unit and second_unit)
 6512
 6513            self._retreat(index)
 6514
 6515        unit_index = self._index
 6516        if interval_span_units_omitted:
 6517            unit = None
 6518        else:
 6519            # Only attempt to parse a unit if the current token can actually be one, so that a
 6520            # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x)
 6521            is_unit = self._curr is not None and (
 6522                self._curr.token_type == TokenType.VAR
 6523                or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS
 6524            )
 6525            unit = self._parse_function() if parse_function_unit and is_unit else None
 6526            if not unit and is_unit:
 6527                unit = self._parse_var(any_token=True, upper=True)
 6528
 6529        # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse
 6530        # each INTERVAL expression into this canonical form so it's easy to transpile
 6531        if this and this.is_number:
 6532            try:
 6533                this = exp.Literal.string(this.to_py())
 6534            except ValueError:
 6535                self.raise_error(f"Invalid numeric interval literal: {this.name!r}")
 6536        elif this and this.is_string:
 6537            parts = exp.INTERVAL_STRING_RE.findall(this.name)
 6538            if parts and unit:
 6539                # Unconsume the eagerly-parsed unit, since the real unit was part of the string
 6540                unit = None
 6541                self._retreat(unit_index)
 6542
 6543            if len(parts) == 1:
 6544                this = exp.Literal.string(parts[0][0])
 6545                unit = self.expression(exp.Var(this=parts[0][1].upper()))
 6546                fraction_in_string = "." in parts[0][0]
 6547
 6548        if self.INTERVAL_SPANS and self._match_text_seq("TO"):
 6549            unit = self.expression(
 6550                exp.IntervalSpan(
 6551                    this=unit,
 6552                    expression=self._parse_function()
 6553                    or self._parse_var(any_token=True, upper=True),
 6554                )
 6555            )
 6556
 6557        return self.expression(
 6558            exp.Interval(this=this, unit=unit, fraction_in_string=fraction_in_string or None)
 6559        )
 6560
 6561    def _parse_interval(
 6562        self, require_interval: bool = True, parse_function_unit: bool = True
 6563    ) -> exp.Add | exp.Interval | None:
 6564        index = self._index
 6565
 6566        if not self._match(TokenType.INTERVAL) and require_interval:
 6567            return None
 6568
 6569        if self._match(TokenType.STRING, advance=False):
 6570            this = self._parse_primary()
 6571        else:
 6572            this = self._parse_term()
 6573
 6574        if not this or (
 6575            isinstance(this, exp.Column)
 6576            and not this.table
 6577            and not this.this.quoted
 6578            and self._curr
 6579            and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS
 6580        ):
 6581            self._retreat(index)
 6582            return None
 6583
 6584        interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit)
 6585
 6586        index = self._index
 6587        self._match(TokenType.PLUS)
 6588
 6589        # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals
 6590        if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False):
 6591            return self.expression(
 6592                exp.Add(
 6593                    this=interval,
 6594                    expression=self._parse_interval(False, parse_function_unit=parse_function_unit),
 6595                )
 6596            )
 6597
 6598        self._retreat(index)
 6599        return interval
 6600
 6601    def _parse_bitwise(self) -> exp.Expr | None:
 6602        this = self._parse_term()
 6603
 6604        while True:
 6605            if self._match_set(self.BITWISE):
 6606                this = self.expression(
 6607                    self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term())
 6608                )
 6609            elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE):
 6610                this = self.expression(
 6611                    exp.DPipe(
 6612                        this=this,
 6613                        expression=self._parse_term(),
 6614                        safe=not self.dialect.STRICT_STRING_CONCAT,
 6615                    )
 6616                )
 6617            elif self._match(TokenType.DQMARK):
 6618                this = self.expression(
 6619                    exp.Coalesce(this=this, expressions=ensure_list(self._parse_term()))
 6620                )
 6621            elif self._match_pair(TokenType.LT, TokenType.LT):
 6622                this = self.expression(
 6623                    exp.BitwiseLeftShift(this=this, expression=self._parse_term())
 6624                )
 6625            elif self._match_pair(TokenType.GT, TokenType.GT):
 6626                this = self.expression(
 6627                    exp.BitwiseRightShift(this=this, expression=self._parse_term())
 6628                )
 6629            elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS):
 6630                this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term())
 6631            else:
 6632                break
 6633
 6634        return this
 6635
 6636    def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None:
 6637        this = self._parse_factor(parse_mod=parse_mod)
 6638
 6639        while self._match_set(self.TERM):
 6640            klass = self.TERM[self._prev.token_type]
 6641            comments = self._prev_comments
 6642            expression = self._parse_factor(parse_mod=parse_mod)
 6643
 6644            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6645
 6646            if isinstance(this, exp.Collate):
 6647                self._normalize_collate(this)
 6648
 6649        return this
 6650
 6651    def _normalize_collate(self, collate: exp.Collate) -> None:
 6652        expr = collate.expression
 6653
 6654        # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise
 6655        # fallback to Identifier / Var
 6656        if isinstance(expr, exp.Column) and len(expr.parts) == 1:
 6657            ident = expr.this
 6658            if isinstance(ident, exp.Identifier):
 6659                collate.set("expression", ident if ident.quoted else exp.var(ident.name))
 6660
 6661    def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None:
 6662        parse_method = self._parse_factor_operand
 6663        this = self._parse_at_time_zone(parse_method())
 6664
 6665        while self._match_set(self.FACTOR, advance=False):
 6666            if not parse_mod and self._curr.token_type == TokenType.MOD:
 6667                break
 6668
 6669            self._advance()
 6670            klass = self.FACTOR[self._prev.token_type]
 6671            comments = self._prev_comments
 6672            expression = parse_method()
 6673
 6674            if not expression and klass is exp.IntDiv and self._prev.text.isalpha():
 6675                self._retreat(self._index - 1)
 6676                return this
 6677
 6678            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6679
 6680            if isinstance(this, exp.Div):
 6681                this.set("typed", self.dialect.TYPED_DIVISION)
 6682                this.set("safe", self.dialect.SAFE_DIVISION)
 6683
 6684        return this
 6685
 6686    def _parse_factor_operand(self) -> exp.Expr | None:
 6687        return self._parse_exponent() if self.EXPONENT else self._parse_unary()
 6688
 6689    def _parse_exponent(self) -> exp.Expr | None:
 6690        this = self._parse_unary()
 6691        while self._match_set(self.EXPONENT):
 6692            comments = self._prev_comments
 6693            this = self.expression(
 6694                self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()),
 6695                comments=comments,
 6696            )
 6697        return this
 6698
 6699    def _parse_unary(self) -> exp.Expr | None:
 6700        if self._match_set(self.UNARY_PARSERS):
 6701            return self.UNARY_PARSERS[self._prev.token_type](self)
 6702        return self._parse_type()
 6703
 6704    def _parse_type(
 6705        self, parse_interval: bool = True, fallback_to_identifier: bool = False
 6706    ) -> exp.Expr | None:
 6707        if not fallback_to_identifier and (atom := self._parse_atom()) is not None:
 6708            return atom
 6709
 6710        if interval := parse_interval and self._parse_interval():
 6711            return self._parse_column_ops(interval)
 6712
 6713        index = self._index
 6714        data_type = self._parse_types(check_func=True, allow_identifiers=False)
 6715
 6716        # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g.
 6717        # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>)
 6718        if isinstance(data_type, exp.Cast):
 6719            # This constructor can contain ops directly after it, for instance struct unnesting:
 6720            # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).*
 6721            return self._parse_column_ops(data_type)
 6722
 6723        if data_type:
 6724            index2 = self._index
 6725            this = self._parse_primary()
 6726
 6727            if isinstance(this, exp.Literal):
 6728                literal = this.name
 6729                this = self._parse_column_ops(this)
 6730
 6731                if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal):
 6732                    if data_type.is_type(exp.DType.TIMESTAMP):
 6733                        data_type = exp.DType.TIMESTAMPTZ.into_expr()
 6734                    elif data_type.is_type(exp.DType.TIME):
 6735                        data_type = exp.DType.TIMETZ.into_expr()
 6736
 6737                parser = self.TYPE_LITERAL_PARSERS.get(data_type.this)
 6738                if parser:
 6739                    return parser(self, this, data_type)
 6740
 6741                return self.expression(exp.Cast(this=this, to=data_type))
 6742
 6743            # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0)
 6744            # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 )
 6745            #
 6746            # If the index difference here is greater than 1, that means the parser itself must have
 6747            # consumed additional tokens such as the DECIMAL scale and precision in the above example.
 6748            #
 6749            # If it's not greater than 1, then it must be 1, because we've consumed at least the type
 6750            # keyword, meaning that the expressions arg of the DataType must have gotten set by a
 6751            # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to
 6752            # DECIMAL(38, 0)) in order to facilitate the data type's transpilation.
 6753            #
 6754            # In these cases, we don't really want to return the converted type, but instead retreat
 6755            # and try to parse a Column or Identifier in the section below.
 6756            if data_type.expressions and index2 - index > 1:
 6757                self._retreat(index2)
 6758                return self._parse_column_ops(data_type)
 6759
 6760            self._retreat(index)
 6761
 6762        if fallback_to_identifier:
 6763            return self._parse_id_var()
 6764
 6765        return self._parse_column()
 6766
 6767    def _parse_type_size(self) -> exp.DataTypeParam | None:
 6768        this = self._parse_type()
 6769        if not this:
 6770            return None
 6771
 6772        if isinstance(this, exp.Column) and not this.table:
 6773            this = exp.var(this.name.upper())
 6774
 6775        return self.expression(
 6776            exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True))
 6777        )
 6778
 6779    def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None:
 6780        type_name = identifier.name
 6781
 6782        while self._match(TokenType.DOT):
 6783            type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
 6784
 6785        return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True)
 6786
 6787    def _parse_types(
 6788        self,
 6789        check_func: bool = False,
 6790        schema: bool = False,
 6791        allow_identifiers: bool = True,
 6792        with_collation: bool = False,
 6793    ) -> exp.Expr | None:
 6794        index = self._index
 6795        this: exp.Expr | None = None
 6796
 6797        if self._match_set(self.TYPE_TOKENS):
 6798            type_token = self._prev.token_type
 6799        else:
 6800            type_token = None
 6801            identifier = allow_identifiers and self._parse_id_var(
 6802                any_token=False, tokens=(TokenType.VAR,)
 6803            )
 6804            if isinstance(identifier, exp.Identifier):
 6805                if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE:
 6806                    this = exp.DataType.build(identifier, udt=True)
 6807                else:
 6808                    try:
 6809                        tokens = self.dialect.tokenize(identifier.name)
 6810                    except TokenError:
 6811                        tokens = None
 6812
 6813                    if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS:
 6814                        if len(tokens) > 1:
 6815                            return exp.DataType.from_str(identifier.name, dialect=self.dialect)
 6816                    elif self.dialect.SUPPORTS_USER_DEFINED_TYPES:
 6817                        this = self._parse_user_defined_type(identifier)
 6818                    else:
 6819                        self._retreat(self._index - 1)
 6820                        return None
 6821            else:
 6822                return None
 6823
 6824        if type_token == TokenType.PSEUDO_TYPE:
 6825            return self.expression(exp.PseudoType(this=self._prev.text.upper()))
 6826
 6827        if type_token == TokenType.OBJECT_IDENTIFIER:
 6828            return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper()))
 6829
 6830        # https://materialize.com/docs/sql/types/map/
 6831        if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET):
 6832            key_type = self._parse_types(
 6833                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6834            )
 6835            if not self._match(TokenType.FARROW):
 6836                self._retreat(index)
 6837                return None
 6838
 6839            value_type = self._parse_types(
 6840                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6841            )
 6842            if not self._match(TokenType.R_BRACKET):
 6843                self._retreat(index)
 6844                return None
 6845
 6846            return exp.DataType(
 6847                this=exp.DType.MAP,
 6848                expressions=[key_type, value_type],
 6849                nested=True,
 6850            )
 6851
 6852        nested = type_token in self.NESTED_TYPE_TOKENS
 6853        is_struct = type_token in self.STRUCT_TYPE_TOKENS
 6854        is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS
 6855        expressions = None
 6856        maybe_func = False
 6857
 6858        if self._match(TokenType.L_PAREN):
 6859            if is_struct:
 6860                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6861            elif nested:
 6862                expressions = self._parse_csv(
 6863                    lambda: self._parse_types(
 6864                        check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6865                    )
 6866                )
 6867                if type_token == TokenType.NULLABLE and len(expressions) == 1:
 6868                    this = expressions[0]
 6869                    this.set("nullable", True)
 6870                    self._match_r_paren()
 6871                    return this
 6872            elif type_token in self.ENUM_TYPE_TOKENS:
 6873                expressions = self._parse_csv(self._parse_equality)
 6874            elif type_token == TokenType.REF:
 6875                # REF(schema.customer) names a target; REF(1) can still be a function call.
 6876                target = self._try_parse(lambda: self._parse_table_parts(schema=True))
 6877                if target is None:
 6878                    self._retreat(index)
 6879                    return None
 6880                expressions = [target]
 6881            elif type_token == TokenType.JSON:
 6882                # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value)
 6883                # https://clickhouse.com/docs/sql-reference/data-types/newjson
 6884                expressions = self._parse_csv(self._parse_json_type_arg)
 6885            elif is_aggregate:
 6886                func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var(
 6887                    any_token=False, tokens=(TokenType.VAR, TokenType.ANY)
 6888                )
 6889                if not func_or_ident:
 6890                    return None
 6891                expressions = [func_or_ident]
 6892                if self._match(TokenType.COMMA):
 6893                    expressions.extend(
 6894                        self._parse_csv(
 6895                            lambda: self._parse_types(
 6896                                check_func=check_func,
 6897                                schema=schema,
 6898                                allow_identifiers=allow_identifiers,
 6899                            )
 6900                        )
 6901                    )
 6902            else:
 6903                expressions = self._parse_csv(self._parse_type_size)
 6904
 6905                # https://docs.snowflake.com/en/sql-reference/data-types-vector
 6906                if type_token == TokenType.VECTOR and len(expressions) == 2:
 6907                    expressions = self._parse_vector_expressions(expressions)
 6908
 6909            if not self._match(TokenType.R_PAREN):
 6910                self._retreat(index)
 6911                return None
 6912
 6913            maybe_func = True
 6914
 6915        values: list[exp.Expr] | None = None
 6916
 6917        if nested and self._match(TokenType.LT):
 6918            if is_struct:
 6919                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6920            else:
 6921                expressions = self._parse_csv(
 6922                    lambda: self._parse_types(
 6923                        check_func=check_func,
 6924                        schema=schema,
 6925                        allow_identifiers=allow_identifiers,
 6926                        with_collation=True,
 6927                    )
 6928                )
 6929
 6930            if not self._match(TokenType.GT):
 6931                self.raise_error("Expecting >")
 6932
 6933            if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)):
 6934                values = self._parse_csv(self._parse_disjunction)
 6935                if not values and is_struct:
 6936                    values = None
 6937                    self._retreat(self._index - 1)
 6938                else:
 6939                    self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN))
 6940
 6941        if type_token in self.TIMESTAMPS:
 6942            if self._match_text_seq("WITH", "TIME", "ZONE"):
 6943                maybe_func = False
 6944                tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ
 6945                this = exp.DataType(this=tz_type, expressions=expressions)
 6946            elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"):
 6947                maybe_func = False
 6948                this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions)
 6949            elif self._match_text_seq("WITHOUT", "TIME", "ZONE"):
 6950                maybe_func = False
 6951        elif type_token == TokenType.INTERVAL:
 6952            if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS:
 6953                unit = self._parse_var(upper=True)
 6954                if self._match_text_seq("TO"):
 6955                    unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True))
 6956
 6957                this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit))))
 6958            else:
 6959                this = self.expression(exp.DataType(this=exp.DType.INTERVAL))
 6960        elif type_token == TokenType.VOID:
 6961            this = exp.DataType(this=exp.DType.NULL)
 6962
 6963        if maybe_func and check_func:
 6964            index2 = self._index
 6965            peek = self._parse_string()
 6966
 6967            if not peek:
 6968                self._retreat(index)
 6969                return None
 6970
 6971            self._retreat(index2)
 6972
 6973        if not this:
 6974            assert type_token is not None
 6975            if self._match_text_seq("UNSIGNED"):
 6976                unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token)
 6977                if not unsigned_type_token:
 6978                    self.raise_error(f"Cannot convert {type_token.name} to unsigned.")
 6979
 6980                type_token = unsigned_type_token or type_token
 6981
 6982            # NULLABLE without parentheses can be a column (Presto/Trino)
 6983            if type_token == TokenType.NULLABLE and not expressions:
 6984                self._retreat(index)
 6985                return None
 6986
 6987            this = exp.DataType(
 6988                this=exp.DType[type_token.name],
 6989                expressions=expressions,
 6990                nested=nested,
 6991            )
 6992
 6993            # Empty arrays/structs are allowed
 6994            if values is not None:
 6995                cls = exp.Struct if is_struct else exp.Array
 6996                this = exp.cast(cls(expressions=values), this, copy=False)
 6997
 6998        elif expressions:
 6999            this.set("expressions", expressions)
 7000
 7001        # https://materialize.com/docs/sql/types/list/#type-name
 7002        while self._match(TokenType.LIST):
 7003            this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True)
 7004
 7005        index = self._index
 7006
 7007        # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3]
 7008        matched_array = self._match(TokenType.ARRAY)
 7009
 7010        while self._curr:
 7011            datatype_token = self._prev.token_type
 7012            matched_l_bracket = self._match(TokenType.L_BRACKET)
 7013
 7014            if (not matched_l_bracket and not matched_array) or (
 7015                datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET)
 7016            ):
 7017                # Postgres allows casting empty arrays such as ARRAY[]::INT[],
 7018                # not to be confused with the fixed size array parsing
 7019                break
 7020
 7021            matched_array = False
 7022            values = self._parse_csv(self._parse_disjunction) or None
 7023            if (
 7024                values
 7025                and not schema
 7026                and (
 7027                    not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS
 7028                    or datatype_token == TokenType.ARRAY
 7029                    or not self._match(TokenType.R_BRACKET, advance=False)
 7030                )
 7031            ):
 7032                # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB
 7033                # 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
 7034                self._retreat(index)
 7035                break
 7036
 7037            this = exp.DataType(
 7038                this=exp.DType.ARRAY, expressions=[this], values=values, nested=True
 7039            )
 7040            self._match(TokenType.R_BRACKET)
 7041
 7042        if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType):
 7043            converter = self.TYPE_CONVERTERS.get(this.this)
 7044            if converter:
 7045                this = converter(t.cast(exp.DataType, this))
 7046
 7047        if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE):
 7048            this.set("collate", self._parse_identifier() or self._parse_column())
 7049
 7050        return this
 7051
 7052    def _parse_json_type_arg(self) -> exp.Expr | None:
 7053        """Parse a single argument to ClickHouse's JSON type."""
 7054
 7055        # SKIP col or SKIP REGEXP 'pattern'
 7056        if self._match_text_seq("SKIP"):
 7057            regexp = self._match(TokenType.RLIKE)
 7058            arg = self._parse_column()
 7059            if isinstance(arg, exp.Column):
 7060                arg = arg.to_dot()
 7061            return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg))
 7062
 7063        param_or_col = self._parse_column()
 7064        if not isinstance(param_or_col, exp.Column):
 7065            return None
 7066
 7067        # Parameter: name=value (e.g., max_dynamic_paths=2)
 7068        if len(param_or_col.parts) == 1 and self._match(TokenType.EQ):
 7069            param = param_or_col.name
 7070            value = self._parse_primary()
 7071            return self.expression(exp.EQ(this=exp.var(param), expression=value))
 7072
 7073        # Column type hint: col_name Type
 7074        col = param_or_col.to_dot()
 7075        kind = self._parse_types(check_func=False, allow_identifiers=False)
 7076        return self.expression(exp.ColumnDef(this=col, kind=kind))
 7077
 7078    def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]:
 7079        return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]]
 7080
 7081    def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None:
 7082        index = self._index
 7083
 7084        if (
 7085            self._curr
 7086            and self._next
 7087            and self._curr.token_type in self.TYPE_TOKENS
 7088            and self._next.token_type in self.TYPE_TOKENS
 7089        ):
 7090            # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a
 7091            # type token. Without this, the list will be parsed as a type and we'll eventually crash
 7092            this = self._parse_id_var()
 7093        else:
 7094            this = (
 7095                self._parse_type(parse_interval=False, fallback_to_identifier=True)
 7096                or self._parse_id_var()
 7097            )
 7098
 7099        self._match(TokenType.COLON)
 7100
 7101        if (
 7102            type_required
 7103            and not isinstance(this, exp.DataType)
 7104            and not self._match_set(self.TYPE_TOKENS, advance=False)
 7105        ):
 7106            self._retreat(index)
 7107            return self._parse_types()
 7108
 7109        return self._parse_column_def(this)
 7110
 7111    def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None:
 7112        if not self._match_text_seq("AT", "TIME", "ZONE"):
 7113            return this
 7114        return self._parse_at_time_zone(
 7115            self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary()))
 7116        )
 7117
 7118    def _parse_atom(self) -> exp.Expr | None:
 7119        if (
 7120            self._curr.token_type in self.IDENTIFIER_TOKENS
 7121            and (column := self._parse_column()) is not None
 7122        ):
 7123            return column
 7124
 7125        token = self._curr
 7126        token_type = token.token_type
 7127
 7128        if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)):
 7129            return None
 7130
 7131        next_type = self._next.token_type
 7132
 7133        if (
 7134            next_type in self.COLUMN_OPERATORS
 7135            or next_type in self.COLUMN_POSTFIX_TOKENS
 7136            or (token_type == TokenType.STRING and next_type == TokenType.STRING)
 7137        ):
 7138            return None
 7139
 7140        self._advance()
 7141        return primary_parser(self, token)
 7142
 7143    def _parse_column(self) -> exp.Expr | None:
 7144        column: exp.Expr | None = self._parse_column_parts_fast()
 7145        if column is None:
 7146            this = self._parse_column_reference()
 7147            if not this:
 7148                this = self._parse_bracket(this)
 7149            column = self._parse_column_ops(this) if this else this
 7150
 7151        if column:
 7152            if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS and self._match(TokenType.JOIN_MARKER):
 7153                column.set("join_mark", True)
 7154            if self.COLON_IS_VARIANT_EXTRACT:
 7155                column = self._parse_colon_as_variant_extract(column)
 7156
 7157        return column
 7158
 7159    def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None:
 7160        """Fast path for simple column and dot references (a, a.b, ...).
 7161
 7162        Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks
 7163        that nothing complex follows. If it does, retreats and returns None so
 7164        the slow path can handle it. For >4 parts, wraps in exp.Dot nodes.
 7165        """
 7166        index = self._index
 7167        parts: list[exp.Identifier] | None = None
 7168        all_comments: list[str] | None = None
 7169
 7170        while self._match_set(self.IDENTIFIER_TOKENS):
 7171            token = self._prev
 7172            comments = self._prev_comments
 7173
 7174            if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS:
 7175                self._retreat(index)
 7176                return None
 7177
 7178            has_dot = self._match(TokenType.DOT)
 7179            curr_tt = self._curr.token_type
 7180
 7181            if not has_dot:
 7182                if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS:
 7183                    self._retreat(index)
 7184                    return None
 7185            elif curr_tt not in self.IDENTIFIER_TOKENS:
 7186                self._retreat(index)
 7187                return None
 7188
 7189            if parts is None:
 7190                parts = []
 7191
 7192            if comments:
 7193                if all_comments is None:
 7194                    all_comments = []
 7195                all_comments.extend(comments)
 7196                self._prev_comments = []
 7197
 7198            parts.append(
 7199                self.expression(
 7200                    exp.Identifier(
 7201                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 7202                    ),
 7203                    token,
 7204                )
 7205            )
 7206
 7207            if not has_dot:
 7208                break
 7209
 7210        if parts is None:
 7211            return None
 7212
 7213        n = len(parts)
 7214
 7215        if n == 1:
 7216            column: exp.Column | exp.Dot = exp.Column(this=parts[0])
 7217        elif n == 2:
 7218            column = exp.Column(this=parts[1], table=parts[0])
 7219        elif n == 3:
 7220            column = exp.Column(this=parts[2], table=parts[1], db=parts[0])
 7221        else:
 7222            column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0])
 7223
 7224            for i in range(4, n):
 7225                column = exp.Dot(this=column, expression=parts[i])
 7226
 7227        if all_comments:
 7228            column.add_comments(all_comments)
 7229
 7230        return column
 7231
 7232    def _parse_column_reference(self) -> exp.Expr | None:
 7233        this = self._parse_field()
 7234        if (
 7235            not this
 7236            and self._match(TokenType.VALUES, advance=False)
 7237            and self.VALUES_FOLLOWED_BY_PAREN
 7238            and (not self._next or self._next.token_type != TokenType.L_PAREN)
 7239        ):
 7240            this = self._parse_id_var()
 7241
 7242        if isinstance(this, exp.Identifier):
 7243            # We bubble up comments from the Identifier to the Column
 7244            this = self.expression(exp.Column(this=this), comments=this.pop_comments())
 7245
 7246        return this
 7247
 7248    def _build_json_extract(
 7249        self,
 7250        this: exp.Expr | None,
 7251        path_parts: list[exp.JSONPathPart],
 7252    ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]:
 7253        if len(path_parts) > 1:
 7254            this = self.expression(
 7255                exp.JSONExtract(
 7256                    this=this,
 7257                    expression=exp.JSONPath(expressions=path_parts),
 7258                    variant_extract=True,
 7259                    requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION,
 7260                )
 7261            )
 7262            path_parts = [exp.JSONPathRoot()]
 7263
 7264        return this, path_parts
 7265
 7266    def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None:
 7267        path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()]
 7268
 7269        while self._match(TokenType.COLON):
 7270            if not self.COLON_CHAIN_IS_SINGLE_EXTRACT:
 7271                this, path_parts = self._build_json_extract(this, path_parts)
 7272
 7273            key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 7274
 7275            if key:
 7276                quoted = isinstance(key, exp.Identifier) and key.quoted
 7277                path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted))
 7278
 7279            while True:
 7280                if self._match(TokenType.DOT):
 7281                    next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 7282
 7283                    if next_key:
 7284                        quoted = isinstance(next_key, exp.Identifier) and next_key.quoted
 7285                        path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted))
 7286                elif self._match(TokenType.L_BRACKET):
 7287                    bracket_expr = self._parse_bracket_key_value()
 7288
 7289                    if not self._match(TokenType.R_BRACKET):
 7290                        self.raise_error("Expected ]")
 7291
 7292                    if bracket_expr:
 7293                        if bracket_expr.is_string:
 7294                            path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True))
 7295                        elif bracket_expr.is_star:
 7296                            path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard()))
 7297                        elif bracket_expr.is_number:
 7298                            path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py()))
 7299                        else:
 7300                            this, path_parts = self._build_json_extract(this, path_parts)
 7301
 7302                            this = self.expression(
 7303                                exp.Bracket(
 7304                                    this=this, expressions=[bracket_expr], json_access=True
 7305                                ),
 7306                            )
 7307
 7308                elif self._match(TokenType.DCOLON):
 7309                    this, path_parts = self._build_json_extract(this, path_parts)
 7310
 7311                    cast_type = self._parse_types()
 7312                    if cast_type:
 7313                        this = self.expression(exp.Cast(this=this, to=cast_type))
 7314                    else:
 7315                        self.raise_error("Expected type after '::'")
 7316                else:
 7317                    break
 7318
 7319        this, _ = self._build_json_extract(this, path_parts)
 7320
 7321        return this
 7322
 7323    def _parse_dcolon(self) -> exp.Expr | None:
 7324        return self._parse_types()
 7325
 7326    def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None:
 7327        while self._curr.token_type in self.BRACKETS:
 7328            this = self._parse_bracket(this)
 7329
 7330        column_operators = self.COLUMN_OPERATORS
 7331        cast_column_operators = self.CAST_COLUMN_OPERATORS
 7332        while self._curr:
 7333            op_token = self._curr.token_type
 7334
 7335            if op_token not in column_operators:
 7336                break
 7337            op = column_operators[op_token]
 7338            self._advance()
 7339
 7340            if op_token in cast_column_operators:
 7341                field = self._parse_dcolon()
 7342                if not field:
 7343                    self.raise_error("Expected type")
 7344            elif op and self._curr:
 7345                field = self._parse_column_reference() or self._parse_bitwise()
 7346                if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False):
 7347                    field = self._parse_column_ops(field)
 7348            else:
 7349                dot = self._is_connected() and self._prev.token_type == TokenType.DOT
 7350                field = self._parse_field(any_token=True, anonymous_func=True)
 7351
 7352                # In t.true, t.null we should produce an Identifier node
 7353                if dot and isinstance(field, (exp.Null, exp.Boolean)):
 7354                    field = self.expression(
 7355                        exp.Identifier(this=self._prev.text),
 7356                        comments=field.comments,
 7357                    )
 7358
 7359            # Function calls can be qualified, e.g., x.y.FOO()
 7360            # This converts the final AST to a series of Dots leading to the function call
 7361            # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules
 7362            if isinstance(field, (exp.Func, exp.Window)) and this:
 7363                this = this.transform(
 7364                    lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n
 7365                )
 7366
 7367            if op:
 7368                this = op(self, this, field)
 7369            elif isinstance(this, exp.Column) and not this.args.get("catalog"):
 7370                this = self.expression(
 7371                    exp.Column(
 7372                        this=field,
 7373                        table=this.this,
 7374                        db=this.args.get("table"),
 7375                        catalog=this.args.get("db"),
 7376                    ),
 7377                    comments=this.comments,
 7378                )
 7379            elif isinstance(field, exp.Window):
 7380                # Move the exp.Dot's to the window's function
 7381                window_func = self.expression(exp.Dot(this=this, expression=field.this))
 7382                field.set("this", window_func)
 7383                this = field
 7384            else:
 7385                this = self.expression(exp.Dot(this=this, expression=field))
 7386
 7387            if field and field.comments:
 7388                t.cast(exp.Expr, this).add_comments(field.pop_comments())
 7389
 7390            this = self._parse_bracket(this)
 7391
 7392        return this
 7393
 7394    def _parse_paren(self) -> exp.Expr | None:
 7395        if not self._match(TokenType.L_PAREN):
 7396            return None
 7397
 7398        comments = self._prev_comments
 7399        query = self._parse_select()
 7400
 7401        if query:
 7402            expressions = [query]
 7403        else:
 7404            expressions = self._parse_expressions()
 7405
 7406        this = seq_get(expressions, 0)
 7407
 7408        if not this and self._match(TokenType.R_PAREN, advance=False):
 7409            this = self.expression(exp.Tuple())
 7410        elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA:
 7411            this = self.expression(exp.Tuple(expressions=expressions))
 7412        elif isinstance(this, exp.UNWRAPPED_QUERIES):
 7413            this = self._parse_subquery(this=this, parse_alias=False)
 7414        elif isinstance(this, (exp.Subquery, exp.Values)):
 7415            this = self._parse_subquery(
 7416                this=self._parse_query_modifiers(self._parse_set_operations(this)),
 7417                parse_alias=False,
 7418            )
 7419        else:
 7420            this = self.expression(exp.Paren(this=this))
 7421
 7422        if this:
 7423            this.add_comments(comments)
 7424
 7425        self._match_r_paren(expression=this)
 7426
 7427        if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc):
 7428            return self._parse_window(this)
 7429
 7430        return this
 7431
 7432    def _parse_primary(self) -> exp.Expr | None:
 7433        if self._match_set(self.PRIMARY_PARSERS):
 7434            token_type = self._prev.token_type
 7435            primary = self.PRIMARY_PARSERS[token_type](self, self._prev)
 7436
 7437            if token_type == TokenType.STRING:
 7438                expressions = [primary]
 7439                while self._match(TokenType.STRING, advance=False):
 7440                    if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED:
 7441                        self.raise_error(
 7442                            "Adjacent string literals need to be separated by whitespace or comments"
 7443                        )
 7444
 7445                    self._advance()
 7446                    expressions.append(exp.Literal.string(self._prev.text))
 7447
 7448                if len(expressions) > 1:
 7449                    return self.expression(
 7450                        exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE)
 7451                    )
 7452
 7453            return primary
 7454
 7455        if self._match_pair(TokenType.DOT, TokenType.NUMBER):
 7456            return exp.Literal.number(f"0.{self._prev.text}")
 7457
 7458        return self._parse_paren()
 7459
 7460    def _parse_field(
 7461        self,
 7462        any_token: bool = False,
 7463        tokens: t.Collection[TokenType] | None = None,
 7464        anonymous_func: bool = False,
 7465    ) -> exp.Expr | None:
 7466        after_dot = (
 7467            self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT
 7468        )
 7469
 7470        if anonymous_func:
 7471            field = (
 7472                self._parse_function(anonymous=anonymous_func, any_token=any_token)
 7473                or self._parse_primary()
 7474            )
 7475        else:
 7476            field = self._parse_primary() or self._parse_function(
 7477                anonymous=anonymous_func, any_token=any_token
 7478            )
 7479
 7480        field = field or self._parse_id_var(any_token=any_token, tokens=tokens)
 7481
 7482        if after_dot and isinstance(field, exp.Literal) and field.is_number:
 7483            name = field.name
 7484            if self._is_connected() and self._parse_var(any_token=True):
 7485                name += self._prev.text
 7486
 7487            field = exp.Identifier(this=name, quoted=True).update_positions(field)
 7488
 7489        return field
 7490
 7491    def _parse_function(
 7492        self,
 7493        functions: dict[str, t.Callable] | None = None,
 7494        anonymous: bool = False,
 7495        optional_parens: bool = True,
 7496        any_token: bool = False,
 7497    ) -> exp.Expr | None:
 7498        # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this)
 7499        # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences
 7500        fn_syntax = False
 7501        if (
 7502            self._match(TokenType.L_BRACE, advance=False)
 7503            and self._next
 7504            and self._next.text.upper() == "FN"
 7505        ):
 7506            self._advance(2)
 7507            fn_syntax = True
 7508
 7509        func = self._parse_function_call(
 7510            functions=functions,
 7511            anonymous=anonymous,
 7512            optional_parens=optional_parens,
 7513            any_token=any_token,
 7514        )
 7515
 7516        if fn_syntax:
 7517            self._match(TokenType.R_BRACE)
 7518
 7519        return func
 7520
 7521    def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]:
 7522        return self._parse_csv(lambda: self._parse_lambda(alias=alias))
 7523
 7524    def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren:
 7525        args = self._parse_function_args(alias=False)
 7526        if not args:
 7527            self.raise_error("Expected at least one argument")
 7528
 7529        # Wrapped so the connector keeps its precedence in the parent context
 7530        return exp.Paren(this=connector(*args, copy=False))
 7531
 7532    def _parse_function_call(
 7533        self,
 7534        functions: dict[str, t.Callable] | None = None,
 7535        anonymous: bool = False,
 7536        optional_parens: bool = True,
 7537        any_token: bool = False,
 7538    ) -> exp.Expr | None:
 7539        if not self._curr:
 7540            return None
 7541
 7542        comments = self._curr.comments
 7543        prev = self._prev
 7544        token = self._curr
 7545        token_type = self._curr.token_type
 7546        this: str | exp.Expr = self._curr.text
 7547        upper = self._curr.text.upper()
 7548
 7549        after_dot = prev.token_type == TokenType.DOT
 7550        parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper)
 7551        if (
 7552            optional_parens
 7553            and parser
 7554            and token_type not in self.INVALID_FUNC_NAME_TOKENS
 7555            and not after_dot
 7556        ):
 7557            self._advance()
 7558            return self._parse_window(parser(self))
 7559
 7560        if self._next.token_type != TokenType.L_PAREN:
 7561            if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot:
 7562                self._advance()
 7563                return self.expression(self.NO_PAREN_FUNCTIONS[token_type]())
 7564
 7565            return None
 7566
 7567        if any_token:
 7568            if token_type in self.RESERVED_TOKENS:
 7569                return None
 7570        elif token_type not in self.FUNC_TOKENS:
 7571            return None
 7572
 7573        self._advance(2)
 7574
 7575        parser = self.FUNCTION_PARSERS.get(upper)
 7576        if parser and not anonymous:
 7577            result = parser(self)
 7578        else:
 7579            subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type)
 7580
 7581            if subquery_predicate:
 7582                expr = None
 7583                if self._curr.token_type in self.SUBQUERY_TOKENS:
 7584                    expr = self._parse_select()
 7585                    self._match_r_paren()
 7586                elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE):
 7587                    # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like
 7588                    # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren
 7589                    self._advance(-1)
 7590                    expr = self._parse_bitwise()
 7591
 7592                if expr:
 7593                    return self.expression(subquery_predicate(this=expr), comments=comments)
 7594
 7595            if functions is None:
 7596                functions = self.FUNCTIONS
 7597
 7598            function = functions.get(upper)
 7599            known_function = function and not anonymous
 7600
 7601            alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS
 7602            args = self._parse_function_args(alias)
 7603
 7604            post_func_comments = self._curr.comments if self._curr else None
 7605            if known_function and post_func_comments:
 7606                # If the user-inputted comment "/* sqlglot.anonymous */" is following the function
 7607                # call we'll construct it as exp.Anonymous, even if it's "known"
 7608                if any(
 7609                    comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS)
 7610                    for comment in post_func_comments
 7611                ):
 7612                    known_function = False
 7613
 7614            if alias and known_function:
 7615                args = self._kv_to_prop_eq(args)
 7616
 7617            if known_function:
 7618                func_builder = t.cast(t.Callable, function)
 7619
 7620                # mypyc compiled functions don't have __code__, so we use
 7621                # try/except to check if func_builder accepts 'dialect'.
 7622                try:
 7623                    func = func_builder(args)
 7624                except TypeError:
 7625                    func = func_builder(args, dialect=self.dialect)
 7626
 7627                func = self.validate_expression(func, args)
 7628                if name_key := self.dialect.ORIGINAL_NAME_META_KEY:
 7629                    func.meta[name_key] = this
 7630
 7631                result = func
 7632            else:
 7633                if token_type == TokenType.IDENTIFIER:
 7634                    this = exp.Identifier(this=this, quoted=True).update_positions(token)
 7635
 7636                result = self.expression(exp.Anonymous(this=this, expressions=args))
 7637
 7638            result = result.update_positions(token)
 7639
 7640        if isinstance(result, exp.Expr):
 7641            result.add_comments(comments)
 7642
 7643        if parser:
 7644            self._match(TokenType.R_PAREN, expression=result)
 7645        else:
 7646            self._match_r_paren(result)
 7647        return self._parse_window(result)
 7648
 7649    def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr:
 7650        return expression
 7651
 7652    def _kv_to_prop_eq(
 7653        self, expressions: list[exp.Expr], parse_map: bool = False
 7654    ) -> list[exp.Expr]:
 7655        transformed = []
 7656
 7657        for index, e in enumerate(expressions):
 7658            if isinstance(e, self.KEY_VALUE_DEFINITIONS):
 7659                if isinstance(e, exp.Alias):
 7660                    e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this))
 7661
 7662                if not isinstance(e, exp.PropertyEQ):
 7663                    e = self.expression(
 7664                        exp.PropertyEQ(
 7665                            this=e.this if parse_map else exp.to_identifier(e.this.name),
 7666                            expression=e.expression,
 7667                        )
 7668                    )
 7669
 7670                if not parse_map and isinstance(e.this, exp.Column):
 7671                    e.this.replace(e.this.this)
 7672            else:
 7673                e = self._to_prop_eq(e, index)
 7674
 7675            transformed.append(e)
 7676
 7677        return transformed
 7678
 7679    def _parse_function_properties(self) -> exp.Properties | None:
 7680        # Skip the generic `key = value` fallback in _parse_property since this
 7681        # runs post-AS where a function body like `name = expr` can be misread
 7682        # as a property.
 7683        properties = []
 7684        while True:
 7685            if self._match_texts(self.PROPERTY_PARSERS):
 7686                keyword = self._prev.text.upper()
 7687                prop = self.PROPERTY_PARSERS[keyword](self)
 7688            elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS):
 7689                keyword = self._prev.text.upper()
 7690                prop = self.PROPERTY_PARSERS[keyword](self, default=True)
 7691            else:
 7692                break
 7693            if not prop:
 7694                self.raise_error(f"Failed to parse property '{keyword}'")
 7695                break
 7696            for p in ensure_list(prop):
 7697                properties.append(p)
 7698
 7699        return self.expression(exp.Properties(expressions=properties)) if properties else None
 7700
 7701    def _parse_user_defined_function_expression(self) -> exp.Expr | None:
 7702        return self._parse_statement()
 7703
 7704    def _parse_function_parameter(self) -> exp.Expr | None:
 7705        return self._parse_column_def(this=self._parse_id_var(), computed_column=False)
 7706
 7707    def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None:
 7708        this = self._parse_table_parts(schema=True)
 7709
 7710        if not self._match(TokenType.L_PAREN):
 7711            return this
 7712
 7713        expressions = self._parse_csv(self._parse_function_parameter)
 7714        self._match_r_paren()
 7715        return self.expression(
 7716            exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True)
 7717        )
 7718
 7719    def _parse_macro_overloads(
 7720        self,
 7721        this: exp.UserDefinedFunction,
 7722        first_body: exp.Expr,
 7723        first_is_table: bool = False,
 7724    ) -> exp.MacroOverloads:
 7725        overloads = [
 7726            self.expression(
 7727                exp.MacroOverload(
 7728                    this=first_body,
 7729                    expressions=this.expressions or None,
 7730                    is_table=first_is_table,
 7731                )
 7732            )
 7733        ]
 7734        this.set("expressions", None)
 7735        this.set("wrapped", False)
 7736
 7737        while self._match(TokenType.COMMA):
 7738            if not self._match(TokenType.L_PAREN):
 7739                break
 7740
 7741            params = self._parse_csv(self._parse_function_parameter)
 7742            self._match_r_paren()
 7743
 7744            if not self._match(TokenType.ALIAS):
 7745                break
 7746
 7747            is_table = self._match(TokenType.TABLE)
 7748            body = self._parse_expression()
 7749            macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table)
 7750            overloads.append(self.expression(macro))
 7751
 7752        return self.expression(exp.MacroOverloads(expressions=overloads))
 7753
 7754    def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier:
 7755        literal = self._parse_primary()
 7756        if literal:
 7757            return self.expression(exp.Introducer(this=token.text, expression=literal), token)
 7758
 7759        return self._identifier_expression(token)
 7760
 7761    def _parse_session_parameter(self) -> exp.SessionParameter:
 7762        kind = None
 7763        this = self._parse_id_var() or self._parse_primary()
 7764
 7765        if this and self._match(TokenType.DOT):
 7766            kind = this.name
 7767            this = self._parse_var() or self._parse_primary()
 7768
 7769        return self.expression(exp.SessionParameter(this=this, kind=kind))
 7770
 7771    def _parse_lambda_arg(self) -> exp.Expr | None:
 7772        return self._parse_id_var()
 7773
 7774    def _parse_lambda(self, alias: bool = False) -> exp.Expr | None:
 7775        next_token_type = self._next.token_type
 7776
 7777        # Fast path: simple atom (column, literal, null, bool) followed by , or )
 7778        if (
 7779            next_token_type in self.LAMBDA_ARG_TERMINATORS
 7780            and (atom := self._parse_atom()) is not None
 7781        ):
 7782            return atom
 7783
 7784        index = self._index
 7785
 7786        if self._match(TokenType.L_PAREN):
 7787            expressions = t.cast(
 7788                list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg)
 7789            )
 7790
 7791            if not self._match(TokenType.R_PAREN):
 7792                self._retreat(index)
 7793            elif self._match_set(self.LAMBDAS):
 7794                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7795            else:
 7796                self._retreat(index)
 7797        elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS:
 7798            expressions = [self._parse_lambda_arg()]
 7799
 7800            if self._match_set(self.LAMBDAS):
 7801                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7802
 7803            self._retreat(index)
 7804
 7805        this: exp.Expr | None
 7806
 7807        if self._match(TokenType.DISTINCT):
 7808            this = self.expression(
 7809                exp.Distinct(expressions=self._parse_csv(self._parse_disjunction))
 7810            )
 7811        else:
 7812            self._match(TokenType.ALL)  # ALL is the default/no-op aggregate modifier (SQL-92)
 7813            this = self._parse_select_or_expression(alias=alias)
 7814
 7815        return self._parse_limit(
 7816            self._parse_respect_or_ignore_nulls(
 7817                self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this)))
 7818            )
 7819        )
 7820
 7821    def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7822        index = self._index
 7823        if not self._match(TokenType.L_PAREN):
 7824            return this
 7825
 7826        # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>),
 7827        # expr can be of both types
 7828        if self._match_set(self.SELECT_START_TOKENS):
 7829            self._retreat(index)
 7830            return this
 7831        args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def())
 7832        self._match_r_paren()
 7833        return self.expression(exp.Schema(this=this, expressions=args))
 7834
 7835    def _parse_field_def(self) -> exp.Expr | None:
 7836        return self._parse_column_def(self._parse_field(any_token=True))
 7837
 7838    def _parse_column_def(
 7839        self, this: exp.Expr | None, computed_column: bool = True
 7840    ) -> exp.Expr | None:
 7841        # column defs are not really columns, they're identifiers
 7842        if isinstance(this, exp.Column):
 7843            this = this.this
 7844
 7845        if not computed_column:
 7846            self._match(TokenType.ALIAS)
 7847
 7848        kind = self._parse_types(schema=True)
 7849
 7850        if self._match_text_seq("FOR", "ORDINALITY"):
 7851            return self.expression(exp.ColumnDef(this=this, ordinality=True))
 7852
 7853        constraints: list[exp.Expr] = []
 7854
 7855        if (not kind and self._match(TokenType.ALIAS)) or self._match_texts(
 7856            ("ALIAS", "MATERIALIZED")
 7857        ):
 7858            # Match storage before _parse_types so STORED is not treated as a data type
 7859            # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`).
 7860            persisted = self._prev.text.upper() == "MATERIALIZED"
 7861            expression = self._parse_disjunction()
 7862            if not persisted:
 7863                if self._match_text_seq("PERSISTED"):
 7864                    persisted = True
 7865                elif self._match_texts(("STORED", "VIRTUAL")):
 7866                    persisted = self._prev.text.upper() == "STORED"
 7867            constraint_kind = exp.ComputedColumnConstraint(
 7868                this=expression,
 7869                persisted=persisted,
 7870                data_type=exp.Var(this="AUTO")
 7871                if self._match_text_seq("AUTO")
 7872                else self._parse_types(),
 7873                not_null=self._match_pair(TokenType.NOT, TokenType.NULL),
 7874            )
 7875            constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind)))
 7876        elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False):
 7877            in_out_constraint = self.expression(
 7878                exp.InOutColumnConstraint(
 7879                    input_=self._match(TokenType.IN), output=self._match(TokenType.OUT)
 7880                )
 7881            )
 7882            constraints.append(in_out_constraint)
 7883            kind = self._parse_types()
 7884        elif (
 7885            kind
 7886            and self._match(TokenType.ALIAS, advance=False)
 7887            and (
 7888                not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
 7889                or self._next.token_type == TokenType.L_PAREN
 7890            )
 7891        ):
 7892            self._advance()
 7893            constraints.append(
 7894                self.expression(
 7895                    exp.ColumnConstraint(
 7896                        kind=exp.ComputedColumnConstraint(
 7897                            this=self._parse_disjunction(),
 7898                            persisted=self._match_texts(("STORED", "VIRTUAL"))
 7899                            and self._prev.text.upper() == "STORED",
 7900                        )
 7901                    )
 7902                )
 7903            )
 7904
 7905        while True:
 7906            constraint = self._parse_column_constraint()
 7907            if not constraint:
 7908                break
 7909            constraints.append(constraint)
 7910
 7911        if not kind and not constraints:
 7912            return this
 7913
 7914        position = None
 7915        if self._match_texts(("FIRST", "AFTER")):
 7916            pos = self._prev.text
 7917            position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos))
 7918
 7919        return self.expression(
 7920            exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position)
 7921        )
 7922
 7923    def _parse_auto_increment(
 7924        self,
 7925    ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint:
 7926        start = None
 7927        increment = None
 7928        order = None
 7929
 7930        if self._match(TokenType.L_PAREN, advance=False):
 7931            args = self._parse_wrapped_csv(self._parse_bitwise)
 7932            start = seq_get(args, 0)
 7933            increment = seq_get(args, 1)
 7934
 7935        # The remaining parts form an unordered bag and any of them can be omitted, in which
 7936        # case the engine falls back to its own default, so they're parsed independently.
 7937        while True:
 7938            if self._match_text_seq("START"):
 7939                start = self._parse_bitwise()
 7940            elif self._match_text_seq("INCREMENT"):
 7941                increment = self._parse_bitwise()
 7942            elif self._match_text_seq("ORDER"):
 7943                order = True
 7944            elif self._match_text_seq("NOORDER"):
 7945                order = False
 7946            else:
 7947                break
 7948
 7949        if start or increment or order is not None:
 7950            return exp.GeneratedAsIdentityColumnConstraint(
 7951                start=start, increment=increment, this=False, order=order
 7952            )
 7953
 7954        return exp.AutoIncrementColumnConstraint()
 7955
 7956    def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None:
 7957        if not self._match(TokenType.L_PAREN, advance=False):
 7958            return None
 7959
 7960        return self.expression(
 7961            exp.CheckColumnConstraint(
 7962                this=self._parse_wrapped(self._parse_assignment),
 7963                enforced=self._match_text_seq("ENFORCED"),
 7964            )
 7965        )
 7966
 7967    def _parse_auto_property(self) -> exp.AutoRefreshProperty | None:
 7968        if not self._match_text_seq("REFRESH"):
 7969            self._retreat(self._index - 1)
 7970            return None
 7971        return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True)))
 7972
 7973    def _parse_compress(self) -> exp.CompressColumnConstraint:
 7974        if self._match(TokenType.L_PAREN, advance=False):
 7975            return self.expression(
 7976                exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise))
 7977            )
 7978
 7979        return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise()))
 7980
 7981    def _parse_generated_as_identity(
 7982        self,
 7983    ) -> (
 7984        exp.GeneratedAsIdentityColumnConstraint
 7985        | exp.ComputedColumnConstraint
 7986        | exp.GeneratedAsRowColumnConstraint
 7987    ):
 7988        if self._match_text_seq("BY", "DEFAULT"):
 7989            on_null = self._match_pair(TokenType.ON, TokenType.NULL)
 7990            this = self.expression(
 7991                exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null)
 7992            )
 7993        else:
 7994            self._match_text_seq("ALWAYS")
 7995            this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True))
 7996
 7997        self._match(TokenType.ALIAS)
 7998
 7999        if self._match_text_seq("ROW"):
 8000            start = self._match_text_seq("START")
 8001            if not start:
 8002                self._match(TokenType.END)
 8003            hidden = self._match_text_seq("HIDDEN")
 8004            return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden))
 8005
 8006        identity = self._match_text_seq("IDENTITY")
 8007
 8008        if self._match(TokenType.L_PAREN):
 8009            if self._match_text_seq("START", "WITH"):
 8010                this.set("start", self._parse_bitwise())
 8011            if self._match_text_seq("INCREMENT", "BY"):
 8012                this.set("increment", self._parse_bitwise())
 8013            if self._match_text_seq("MINVALUE"):
 8014                this.set("minvalue", self._parse_bitwise())
 8015            if self._match_text_seq("MAXVALUE"):
 8016                this.set("maxvalue", self._parse_bitwise())
 8017
 8018            if self._match_text_seq("CYCLE"):
 8019                this.set("cycle", True)
 8020            elif self._match_text_seq("NO", "CYCLE"):
 8021                this.set("cycle", False)
 8022
 8023            if not identity:
 8024                this.set("expression", self._parse_disjunction())
 8025            elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False):
 8026                args = self._parse_csv(self._parse_bitwise)
 8027                this.set("start", seq_get(args, 0))
 8028                this.set("increment", seq_get(args, 1))
 8029
 8030            self._match_r_paren()
 8031
 8032        return this
 8033
 8034    def _parse_inline(self) -> exp.InlineLengthColumnConstraint:
 8035        self._match_text_seq("LENGTH")
 8036        return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise()))
 8037
 8038    def _parse_not_constraint(self) -> exp.Expr | None:
 8039        if self._match_text_seq("NULL"):
 8040            return self.expression(exp.NotNullColumnConstraint())
 8041        if self._match_text_seq("CASESPECIFIC"):
 8042            return self.expression(exp.CaseSpecificColumnConstraint(not_=True))
 8043        if self._match_text_seq("FOR", "REPLICATION"):
 8044            return self.expression(exp.NotForReplicationColumnConstraint())
 8045
 8046        # Unconsume the `NOT` token
 8047        self._retreat(self._index - 1)
 8048        return None
 8049
 8050    def _parse_column_constraint(self) -> exp.Expr | None:
 8051        this = (
 8052            self._parse_id_var()
 8053            if self._match(TokenType.CONSTRAINT)
 8054            and not self._match_texts(self.RESERVED_CONSTRAINT_KINDS, advance=False)
 8055            else None
 8056        )
 8057
 8058        procedure_option_follows = (
 8059            self._match(TokenType.WITH, advance=False)
 8060            and self._next
 8061            and self._next.text.upper() in self.PROCEDURE_OPTIONS
 8062        )
 8063
 8064        index = self._index
 8065        if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS):
 8066            constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self)
 8067            if not constraint:
 8068                self._retreat(index)
 8069                return None
 8070
 8071            return self.expression(exp.ColumnConstraint(this=this, kind=constraint))
 8072
 8073        if self._match(TokenType.CHARACTER_SET) or self._match_pair(TokenType.CHAR, TokenType.SET):
 8074            return self.expression(
 8075                exp.ColumnConstraint(
 8076                    this=this,
 8077                    kind=self.expression(
 8078                        exp.CharacterSetColumnConstraint(this=self._parse_var_or_string())
 8079                    ),
 8080                )
 8081            )
 8082
 8083        return this
 8084
 8085    def _parse_constraint(self) -> exp.Expr | None:
 8086        if not self._match(TokenType.CONSTRAINT) or self._match_texts(
 8087            self.RESERVED_CONSTRAINT_KINDS, advance=False
 8088        ):
 8089            return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS)
 8090
 8091        return self.expression(
 8092            exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints())
 8093        )
 8094
 8095    def _parse_unnamed_constraints(self) -> list[exp.Expr]:
 8096        constraints = []
 8097        while True:
 8098            constraint = self._parse_unnamed_constraint() or self._parse_function()
 8099            if not constraint:
 8100                break
 8101            constraints.append(constraint)
 8102
 8103        return constraints
 8104
 8105    def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None:
 8106        index = self._index
 8107
 8108        if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts(
 8109            constraints or self.CONSTRAINT_PARSERS
 8110        ):
 8111            return None
 8112
 8113        constraint_key = self._prev.text.upper()
 8114        if constraint_key not in self.CONSTRAINT_PARSERS:
 8115            self.raise_error(f"No parser found for schema constraint {constraint_key}.")
 8116
 8117        result = self.CONSTRAINT_PARSERS[constraint_key](self)
 8118        if not result:
 8119            self._retreat(index)
 8120
 8121        return result
 8122
 8123    def _parse_unique_key(self) -> exp.Expr | None:
 8124        if (
 8125            self._curr
 8126            and self._curr.token_type != TokenType.IDENTIFIER
 8127            and self._curr.text.upper() in self.CONSTRAINT_PARSERS
 8128        ):
 8129            return None
 8130        return self._parse_id_var(any_token=False)
 8131
 8132    def _parse_unique(self) -> exp.UniqueColumnConstraint:
 8133        self._match_texts(("KEY", "INDEX"))
 8134        return self.expression(
 8135            exp.UniqueColumnConstraint(
 8136                nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"),
 8137                this=self._parse_schema(self._parse_unique_key()),
 8138                index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text,
 8139                on_conflict=self._parse_on_conflict(),
 8140                options=self._parse_key_constraint_options(),
 8141            )
 8142        )
 8143
 8144    def _parse_key_constraint_options(self) -> list[str]:
 8145        options = []
 8146        while True:
 8147            if not self._curr:
 8148                break
 8149
 8150            if self._match(TokenType.ON):
 8151                action = None
 8152                on = self._advance_any() and self._prev.text
 8153
 8154                if self._match_text_seq("NO", "ACTION"):
 8155                    action = "NO ACTION"
 8156                elif self._match_text_seq("CASCADE"):
 8157                    action = "CASCADE"
 8158                elif self._match_text_seq("RESTRICT"):
 8159                    action = "RESTRICT"
 8160                elif self._match_pair(TokenType.SET, TokenType.NULL):
 8161                    action = "SET NULL"
 8162                elif self._match_pair(TokenType.SET, TokenType.DEFAULT):
 8163                    action = "SET DEFAULT"
 8164                else:
 8165                    self.raise_error("Invalid key constraint")
 8166
 8167                options.append(f"ON {on} {action}")
 8168            else:
 8169                var = self._parse_var_from_options(
 8170                    self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False
 8171                )
 8172                if not var:
 8173                    break
 8174                options.append(var.name)
 8175
 8176        return options
 8177
 8178    def _parse_references(self, match: bool = True) -> exp.Reference | None:
 8179        if match and not self._match(TokenType.REFERENCES):
 8180            return None
 8181
 8182        expressions: list | None = None
 8183        this = self._parse_table(schema=True)
 8184        options = self._parse_key_constraint_options()
 8185        return self.expression(exp.Reference(this=this, expressions=expressions, options=options))
 8186
 8187    def _parse_foreign_key(self) -> exp.ForeignKey:
 8188        expressions = (
 8189            self._parse_wrapped_id_vars()
 8190            if not self._match(TokenType.REFERENCES, advance=False)
 8191            else None
 8192        )
 8193        reference = self._parse_references()
 8194        on_options = {}
 8195
 8196        while self._match(TokenType.ON):
 8197            if not self._match_set((TokenType.DELETE, TokenType.UPDATE)):
 8198                self.raise_error("Expected DELETE or UPDATE")
 8199
 8200            kind = self._prev.text.lower()
 8201
 8202            if self._match_text_seq("NO", "ACTION"):
 8203                action = "NO ACTION"
 8204            elif self._match(TokenType.SET):
 8205                self._match_set((TokenType.NULL, TokenType.DEFAULT))
 8206                action = "SET " + self._prev.text.upper()
 8207            else:
 8208                self._advance()
 8209                action = self._prev.text.upper()
 8210
 8211            on_options[kind] = action
 8212
 8213        return self.expression(
 8214            exp.ForeignKey(
 8215                expressions=expressions,
 8216                reference=reference,
 8217                options=self._parse_key_constraint_options(),
 8218                **on_options,
 8219            )
 8220        )
 8221
 8222    def _parse_primary_key_part(self) -> exp.Expr | None:
 8223        return self._parse_field()
 8224
 8225    def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None:
 8226        if not self._match_text_seq("FOR", "SYSTEM_TIME"):
 8227            self._retreat(self._index - 1)
 8228            return None
 8229
 8230        id_vars = self._parse_wrapped_id_vars()
 8231        return self.expression(
 8232            exp.PeriodForSystemTimeConstraint(
 8233                this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1)
 8234            )
 8235        )
 8236
 8237    def _parse_primary_key(
 8238        self,
 8239        wrapped_optional: bool = False,
 8240        in_props: bool = False,
 8241        named_primary_key: bool = False,
 8242    ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:
 8243        desc = (
 8244            self._prev.token_type == TokenType.DESC
 8245            if self._match_set((TokenType.ASC, TokenType.DESC))
 8246            else None
 8247        )
 8248
 8249        this = None
 8250        if (
 8251            named_primary_key
 8252            and self._curr.text.upper() not in self.CONSTRAINT_PARSERS
 8253            and self._next
 8254            and self._next.token_type == TokenType.L_PAREN
 8255        ):
 8256            this = self._parse_id_var()
 8257
 8258        if not in_props and not self._match(TokenType.L_PAREN, advance=False):
 8259            return self.expression(
 8260                exp.PrimaryKeyColumnConstraint(
 8261                    desc=desc, options=self._parse_key_constraint_options()
 8262                )
 8263            )
 8264
 8265        expressions = self._parse_wrapped_csv(
 8266            self._parse_primary_key_part, optional=wrapped_optional
 8267        )
 8268
 8269        return self.expression(
 8270            exp.PrimaryKey(
 8271                this=this,
 8272                expressions=expressions,
 8273                include=self._parse_index_params(),
 8274                options=self._parse_key_constraint_options(),
 8275            )
 8276        )
 8277
 8278    def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None:
 8279        return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True))
 8280
 8281    def _parse_odbc_datetime_literal(self) -> exp.Expr:
 8282        """
 8283        Parses a datetime column in ODBC format. We parse the column into the corresponding
 8284        types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the
 8285        same as we did for `DATE('yyyy-mm-dd')`.
 8286
 8287        Reference:
 8288        https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals
 8289        """
 8290        self._match(TokenType.VAR)
 8291        exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()]
 8292        expression = self.expression(exp_class(this=self._parse_string()))
 8293        if not self._match(TokenType.R_BRACE):
 8294            self.raise_error("Expected }")
 8295        return expression
 8296
 8297    def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None:
 8298        if not self._match_set(self.BRACKETS):
 8299            return this
 8300
 8301        if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS:
 8302            map_token = seq_get(self._tokens, self._index - 2)
 8303            parse_map = map_token is not None and map_token.text.upper() == "MAP"
 8304        else:
 8305            parse_map = False
 8306
 8307        bracket_kind = self._prev.token_type
 8308        if (
 8309            bracket_kind == TokenType.L_BRACE
 8310            and self._curr
 8311            and self._curr.token_type == TokenType.VAR
 8312            and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS
 8313        ):
 8314            return self._parse_odbc_datetime_literal()
 8315
 8316        expressions = self._parse_csv(
 8317            lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE)
 8318        )
 8319
 8320        if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET):
 8321            self.raise_error("Expected ]")
 8322        elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE):
 8323            self.raise_error("Expected }")
 8324
 8325        # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs
 8326        if bracket_kind == TokenType.L_BRACE:
 8327            this = self.expression(
 8328                exp.Struct(
 8329                    expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map)
 8330                )
 8331            )
 8332        elif not this:
 8333            this = build_array_constructor(
 8334                exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect
 8335            )
 8336        else:
 8337            constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper())
 8338            if constructor_type:
 8339                return build_array_constructor(
 8340                    constructor_type,
 8341                    args=expressions,
 8342                    bracket_kind=bracket_kind,
 8343                    dialect=self.dialect,
 8344                )
 8345
 8346            expressions = apply_index_offset(
 8347                this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect
 8348            )
 8349            this = self.expression(
 8350                exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments()
 8351            )
 8352
 8353        self._add_comments(this)
 8354        return self._parse_bracket(this)
 8355
 8356    def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None:
 8357        if not self._match(TokenType.COLON):
 8358            return this
 8359
 8360        # DuckDB's `-` in `[a:-:step]` is the default end, which depends on the sign of step
 8361        if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False):
 8362            self._advance()
 8363            end: exp.Expr | None = None
 8364        else:
 8365            end = self._parse_assignment()
 8366        step = self._parse_unary() if self._match(TokenType.COLON) else None
 8367        return self.expression(exp.Slice(this=this, expression=end, step=step))
 8368
 8369    def _parse_case(self) -> exp.Expr | None:
 8370        if self._match(TokenType.DOT, advance=False):
 8371            # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake
 8372            self._retreat(self._index - 1)
 8373            return None
 8374
 8375        ifs = []
 8376        default = None
 8377
 8378        comments = self._prev_comments
 8379        expression = self._parse_disjunction()
 8380
 8381        while self._match(TokenType.WHEN):
 8382            this = self._parse_disjunction()
 8383            self._match(TokenType.THEN)
 8384            then = self._parse_disjunction()
 8385            ifs.append(self.expression(exp.If(this=this, true=then)))
 8386
 8387        if self._match(TokenType.ELSE):
 8388            default = self._parse_disjunction()
 8389
 8390        if not self._match(TokenType.END):
 8391            if isinstance(default, exp.Interval) and default.this.sql().upper() == "END":
 8392                default = exp.column("interval")
 8393            else:
 8394                self.raise_error("Expected END after CASE", self._prev)
 8395
 8396        return self.expression(
 8397            exp.Case(this=expression, ifs=ifs, default=default), comments=comments
 8398        )
 8399
 8400    def _parse_if(self) -> exp.Expr | None:
 8401        if self._match(TokenType.L_PAREN):
 8402            args = self._parse_csv(
 8403                lambda: self._parse_alias(self._parse_assignment(), explicit=True)
 8404            )
 8405            this = self.validate_expression(exp.If.from_arg_list(args), args)
 8406            self._match_r_paren()
 8407        else:
 8408            index = self._index - 1
 8409
 8410            if self.NO_PAREN_IF_COMMANDS and index == 0:
 8411                return self._parse_as_command(self._prev)
 8412
 8413            condition = self._parse_disjunction()
 8414
 8415            if not condition or not self._match(TokenType.THEN):
 8416                self._retreat(index)
 8417                return None
 8418
 8419            true = self._parse_disjunction()
 8420            false = self._parse_disjunction() if self._match(TokenType.ELSE) else None
 8421            self._match(TokenType.END)
 8422            this = self.expression(exp.If(this=condition, true=true, false=false))
 8423
 8424        return this
 8425
 8426    def _parse_next_value_for(self) -> exp.Expr | None:
 8427        if not self._match_text_seq("VALUE", "FOR"):
 8428            self._retreat(self._index - 1)
 8429            return None
 8430
 8431        return self.expression(
 8432            exp.NextValueFor(
 8433                this=self._parse_column(),
 8434                order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order),
 8435            )
 8436        )
 8437
 8438    def _parse_extract(self) -> exp.Extract | exp.Anonymous:
 8439        this = self._parse_function() or self._parse_var_or_string(upper=True)
 8440
 8441        if self._match(TokenType.FROM):
 8442            return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8443
 8444        if not self._match(TokenType.COMMA):
 8445            self.raise_error("Expected FROM or comma after EXTRACT", self._prev)
 8446
 8447        return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8448
 8449    def _parse_gap_fill(self) -> exp.GapFill:
 8450        self._match(TokenType.TABLE)
 8451        this = self._parse_table()
 8452
 8453        self._match(TokenType.COMMA)
 8454        args = [this, *self._parse_csv(self._parse_lambda)]
 8455
 8456        gap_fill = exp.GapFill.from_arg_list(args)
 8457        return self.validate_expression(gap_fill, args)
 8458
 8459    def _parse_char(self) -> exp.Chr:
 8460        return self.expression(
 8461            exp.Chr(
 8462                expressions=self._parse_csv(self._parse_assignment),
 8463                charset=self._match(TokenType.USING) and self._parse_charset_name(),
 8464            )
 8465        )
 8466
 8467    def _parse_charset_name(self) -> exp.Expr | None:
 8468        """
 8469        Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting
 8470        for specific name shapes override this.
 8471        """
 8472        return self._parse_var(
 8473            tokens={TokenType.BINARY, TokenType.IDENTIFIER},
 8474        )
 8475
 8476    def _parse_weight_string(self) -> exp.WeightString | exp.Anonymous:
 8477        name = self._tokens[self._index - 2].text
 8478        args = self._parse_csv(self._parse_assignment)
 8479
 8480        # MySQL also accepts an undocumented WEIGHT_STRING(str, n, n, n) form
 8481        if len(args) != 1:
 8482            return self.expression(exp.Anonymous(this=name, expressions=args))
 8483
 8484        return self.expression(
 8485            exp.WeightString(this=args[0], to=self._match(TokenType.ALIAS) and self._parse_types())
 8486        )
 8487
 8488    def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr:
 8489        this = self._parse_assignment()
 8490
 8491        if not self._match(TokenType.ALIAS):
 8492            if self._match(TokenType.COMMA):
 8493                return self.expression(exp.CastToStrType(this=this, to=self._parse_string()))
 8494
 8495            self.raise_error("Expected AS after CAST")
 8496
 8497        fmt = None
 8498        to = self._parse_types(with_collation=True)
 8499
 8500        default = None
 8501        if self._match(TokenType.DEFAULT):
 8502            default = self._parse_bitwise()
 8503            self._match_text_seq("ON", "CONVERSION", "ERROR")
 8504
 8505        if self._match_set((TokenType.FORMAT, TokenType.COMMA)):
 8506            fmt_string = self._parse_wrapped(self._parse_string, optional=True)
 8507            fmt = self._parse_at_time_zone(fmt_string)
 8508
 8509            if not to:
 8510                to = exp.DType.UNKNOWN.into_expr()
 8511            if to.this in exp.DataType.TEMPORAL_TYPES:
 8512                this = self.expression(
 8513                    (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)(
 8514                        this=this,
 8515                        format=exp.Literal.string(
 8516                            format_time(
 8517                                fmt_string.this if fmt_string else "",
 8518                                self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING,
 8519                                self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE,
 8520                            )
 8521                        ),
 8522                        safe=safe,
 8523                    )
 8524                )
 8525
 8526                if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime):
 8527                    this.set("zone", fmt.args["zone"])
 8528                return this
 8529        elif not to:
 8530            self.raise_error("Expected TYPE after CAST")
 8531        elif isinstance(to, exp.Identifier):
 8532            to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True)
 8533        else:
 8534            to = self._parse_char_charset(to)
 8535
 8536        return self.build_cast(
 8537            strict=strict,
 8538            this=this,
 8539            to=to,
 8540            format=fmt,
 8541            safe=safe,
 8542            action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False),
 8543            default=default,
 8544        )
 8545
 8546    def _parse_string_agg(self) -> exp.GroupConcat:
 8547        if self._match(TokenType.DISTINCT):
 8548            args: list[exp.Expr | None] = [
 8549                self.expression(exp.Distinct(expressions=[self._parse_disjunction()]))
 8550            ]
 8551            if self._match(TokenType.COMMA):
 8552                args.extend(self._parse_csv(self._parse_disjunction))
 8553        else:
 8554            self._match(TokenType.ALL)
 8555            args = self._parse_csv(self._parse_disjunction)  # type: ignore
 8556
 8557        if self._match_text_seq("ON", "OVERFLOW"):
 8558            # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior])
 8559            if self._match_text_seq("ERROR"):
 8560                on_overflow: exp.Expr | None = exp.var("ERROR")
 8561            else:
 8562                self._match_text_seq("TRUNCATE")
 8563                on_overflow = self.expression(
 8564                    exp.OverflowTruncateBehavior(
 8565                        this=self._parse_string(),
 8566                        with_count=(
 8567                            self._match_text_seq("WITH", "COUNT")
 8568                            or not self._match_text_seq("WITHOUT", "COUNT")
 8569                        ),
 8570                    )
 8571                )
 8572        else:
 8573            on_overflow = None
 8574
 8575        index = self._index
 8576        if not self._match(TokenType.R_PAREN) and args:
 8577            # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]])
 8578            # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n])
 8579            # The order is parsed through `this` as a canonicalization for WITHIN GROUPs
 8580            args[0] = self._parse_limit(this=self._parse_order(this=args[0]))
 8581            return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1)))
 8582
 8583        # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]).
 8584        # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that
 8585        # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them.
 8586        if not self._match_text_seq("WITHIN", "GROUP"):
 8587            self._retreat(index)
 8588            return self.validate_expression(exp.GroupConcat.from_arg_list(args), args)
 8589
 8590        # The corresponding match_r_paren will be called in parse_function (caller)
 8591        self._match_l_paren()
 8592
 8593        return self.expression(
 8594            exp.GroupConcat(
 8595                this=self._parse_order(this=seq_get(args, 0)),
 8596                separator=seq_get(args, 1),
 8597                on_overflow=on_overflow,
 8598            )
 8599        )
 8600
 8601    def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None:
 8602        this = self._parse_bitwise()
 8603
 8604        if self._match(TokenType.USING):
 8605            to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name())
 8606        elif self._match(TokenType.COMMA):
 8607            to = self._parse_char_charset(self._parse_types())
 8608        else:
 8609            to = None
 8610
 8611        return self.build_cast(strict=strict, this=this, to=to, safe=safe)
 8612
 8613    def _parse_char_charset(self, to: exp.Expr | None) -> exp.Expr | None:
 8614        # MySQL: CHAR[(N)] CHARACTER SET charset_name, the length is kept
 8615        if (
 8616            isinstance(to, exp.DataType)
 8617            and to.this == exp.DType.CHAR
 8618            and (self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET"))
 8619        ):
 8620            to.set("this", exp.DType.CHARACTER_SET)
 8621            to.set("kind", self._parse_var_or_string())
 8622        return to
 8623
 8624    def _parse_xml_element(self) -> exp.XMLElement:
 8625        if self._match_text_seq("EVALNAME"):
 8626            evalname = True
 8627            this = self._parse_bitwise()
 8628        else:
 8629            evalname = None
 8630            self._match_text_seq("NAME")
 8631            this = self._parse_id_var()
 8632
 8633        return self.expression(
 8634            exp.XMLElement(
 8635                this=this,
 8636                expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise),
 8637                evalname=evalname,
 8638            )
 8639        )
 8640
 8641    def _parse_xml_table(self) -> exp.XMLTable:
 8642        namespaces = None
 8643        passing = None
 8644        columns = None
 8645
 8646        if self._match_text_seq("XMLNAMESPACES", "("):
 8647            namespaces = self._parse_xml_namespace()
 8648            self._match_text_seq(")", ",")
 8649
 8650        this = self._parse_string()
 8651
 8652        if self._match_text_seq("PASSING"):
 8653            # The BY VALUE keywords are optional and are provided for semantic clarity
 8654            self._match_text_seq("BY", "VALUE")
 8655            passing = self._parse_csv(self._parse_column)
 8656
 8657        by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF")
 8658
 8659        if self._match_text_seq("COLUMNS"):
 8660            columns = self._parse_csv(self._parse_field_def)
 8661
 8662        return self.expression(
 8663            exp.XMLTable(
 8664                this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref
 8665            )
 8666        )
 8667
 8668    def _parse_xml_namespace(self) -> list[exp.XMLNamespace]:
 8669        namespaces = []
 8670
 8671        while True:
 8672            if self._match(TokenType.DEFAULT):
 8673                uri = self._parse_string()
 8674            else:
 8675                uri = self._parse_alias(self._parse_string())
 8676            namespaces.append(self.expression(exp.XMLNamespace(this=uri)))
 8677            if not self._match(TokenType.COMMA):
 8678                break
 8679
 8680        return namespaces
 8681
 8682    def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None:
 8683        args = self._parse_csv(self._parse_disjunction)
 8684
 8685        if len(args) < 3:
 8686            return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1)))
 8687
 8688        return self.expression(exp.DecodeCase(expressions=args))
 8689
 8690    def _parse_json_key_value(self) -> exp.JSONKeyValue | None:
 8691        self._match_text_seq("KEY")
 8692        key = self._parse_column()
 8693        self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS)
 8694        self._match_text_seq("VALUE")
 8695        value = self._parse_bitwise()
 8696
 8697        if not key and not value:
 8698            return None
 8699        return self.expression(exp.JSONKeyValue(this=key, expression=value))
 8700
 8701    def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None:
 8702        if not this or not self._match_text_seq("FORMAT", "JSON"):
 8703            return this
 8704
 8705        return self.expression(exp.FormatJson(this=this))
 8706
 8707    def _parse_on_condition(self) -> exp.OnCondition | None:
 8708        # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS)
 8709        if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR:
 8710            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8711            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8712        else:
 8713            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8714            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8715
 8716        null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS)
 8717
 8718        if not empty and not error and not null:
 8719            return None
 8720
 8721        return self.expression(exp.OnCondition(empty=empty, error=error, null=null))
 8722
 8723    def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None:
 8724        # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL)
 8725        for value in values:
 8726            if self._match_text_seq(value, "ON", on):
 8727                return f"{value} ON {on}"
 8728
 8729        index = self._index
 8730        if self._match(TokenType.DEFAULT):
 8731            default_value = self._parse_bitwise()
 8732            if self._match_text_seq("ON", on):
 8733                return default_value
 8734
 8735            self._retreat(index)
 8736
 8737        return None
 8738
 8739    @t.overload
 8740    def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ...
 8741
 8742    @t.overload
 8743    def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ...
 8744
 8745    def _parse_json_object(self, agg=False):
 8746        star = self._parse_star()
 8747        expressions = (
 8748            [star]
 8749            if star
 8750            else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value()))
 8751        )
 8752        null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT")
 8753
 8754        unique_keys = None
 8755        if self._match_text_seq("WITH", "UNIQUE"):
 8756            unique_keys = True
 8757        elif self._match_text_seq("WITHOUT", "UNIQUE"):
 8758            unique_keys = False
 8759
 8760        self._match_text_seq("KEYS")
 8761
 8762        return_type = self._match_text_seq("RETURNING") and self._parse_format_json(
 8763            self._parse_type()
 8764        )
 8765        encoding = self._match_text_seq("ENCODING") and self._parse_var()
 8766
 8767        return self.expression(
 8768            (exp.JSONObjectAgg if agg else exp.JSONObject)(
 8769                expressions=expressions,
 8770                null_handling=null_handling,
 8771                unique_keys=unique_keys,
 8772                return_type=return_type,
 8773                encoding=encoding,
 8774            )
 8775        )
 8776
 8777    # Note: this is currently incomplete; it only implements the "JSON_value_column" part
 8778    def _parse_json_column_def(self) -> exp.JSONColumnDef:
 8779        if not self._match_text_seq("NESTED"):
 8780            this = self._parse_id_var()
 8781            ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY)
 8782            kind = self._parse_types(allow_identifiers=False)
 8783            nested = None
 8784        else:
 8785            this = None
 8786            ordinality = None
 8787            kind = None
 8788            nested = True
 8789
 8790        format_json = self._match_text_seq("FORMAT", "JSON")
 8791        path = self._match_text_seq("PATH") and self._parse_string()
 8792        nested_schema = nested and self._parse_json_schema()
 8793
 8794        return self.expression(
 8795            exp.JSONColumnDef(
 8796                this=this,
 8797                kind=kind,
 8798                path=path,
 8799                nested_schema=nested_schema,
 8800                ordinality=ordinality,
 8801                format_json=format_json,
 8802            )
 8803        )
 8804
 8805    def _parse_json_schema(self) -> exp.JSONSchema:
 8806        self._match_text_seq("COLUMNS")
 8807        return self.expression(
 8808            exp.JSONSchema(
 8809                expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True)
 8810            )
 8811        )
 8812
 8813    def _parse_json_table(self) -> exp.JSONTable:
 8814        this = self._parse_format_json(self._parse_bitwise())
 8815        path = self._match(TokenType.COMMA) and self._parse_string()
 8816        error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL")
 8817        empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL")
 8818        schema = self._parse_json_schema()
 8819
 8820        return exp.JSONTable(
 8821            this=this,
 8822            schema=schema,
 8823            path=path,
 8824            error_handling=error_handling,
 8825            empty_handling=empty_handling,
 8826        )
 8827
 8828    def _parse_match_against(self) -> exp.MatchAgainst:
 8829        if self._match_text_seq("TABLE"):
 8830            # parse SingleStore MATCH(TABLE ...) syntax
 8831            # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/
 8832            expressions = []
 8833            table = self._parse_table()
 8834            if table:
 8835                expressions = [table]
 8836        else:
 8837            expressions = self._parse_csv(self._parse_column)
 8838
 8839        self._match_text_seq(")", "AGAINST", "(")
 8840
 8841        this = self._parse_string()
 8842
 8843        if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"):
 8844            modifier = "IN NATURAL LANGUAGE MODE"
 8845            if self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8846                modifier = f"{modifier} WITH QUERY EXPANSION"
 8847        elif self._match_text_seq("IN", "BOOLEAN", "MODE"):
 8848            modifier = "IN BOOLEAN MODE"
 8849        elif self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8850            modifier = "WITH QUERY EXPANSION"
 8851        else:
 8852            modifier = None
 8853
 8854        return self.expression(
 8855            exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier)
 8856        )
 8857
 8858    # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16
 8859    def _parse_open_json(self) -> exp.OpenJSON:
 8860        this = self._parse_bitwise()
 8861        path = self._match(TokenType.COMMA) and self._parse_string()
 8862
 8863        def _parse_open_json_column_def() -> exp.OpenJSONColumnDef:
 8864            this = self._parse_field(any_token=True)
 8865            kind = self._parse_types()
 8866            path = self._parse_string()
 8867            as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON)
 8868
 8869            return self.expression(
 8870                exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json)
 8871            )
 8872
 8873        expressions = None
 8874        if self._match_pair(TokenType.R_PAREN, TokenType.WITH):
 8875            self._match_l_paren()
 8876            expressions = self._parse_csv(_parse_open_json_column_def)
 8877
 8878        return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions))
 8879
 8880    def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition:
 8881        args = self._parse_csv(self._parse_bitwise)
 8882
 8883        if self._match(TokenType.IN):
 8884            return self.expression(
 8885                exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0))
 8886            )
 8887
 8888        if haystack_first:
 8889            haystack = seq_get(args, 0)
 8890            needle = seq_get(args, 1)
 8891        else:
 8892            haystack = seq_get(args, 1)
 8893            needle = seq_get(args, 0)
 8894
 8895        return self.expression(
 8896            exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2))
 8897        )
 8898
 8899    def _parse_join_hint(self, func_name: str) -> exp.JoinHint:
 8900        args = self._parse_csv(self._parse_table)
 8901        return exp.JoinHint(this=func_name.upper(), expressions=args)
 8902
 8903    def _parse_substring(self) -> exp.Substring:
 8904        # Postgres supports the form: substring(string [from int] [for int])
 8905        # (despite being undocumented, the reverse order also works)
 8906        # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6
 8907
 8908        args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise))
 8909
 8910        start, length = None, None
 8911
 8912        while self._curr:
 8913            if self._match(TokenType.FROM):
 8914                start = self._parse_bitwise()
 8915            elif self._match(TokenType.FOR):
 8916                if not start:
 8917                    start = exp.Literal.number(1)
 8918                length = self._parse_bitwise()
 8919            else:
 8920                break
 8921
 8922        if start:
 8923            args.append(start)
 8924        if length:
 8925            args.append(length)
 8926
 8927        return self.validate_expression(exp.Substring.from_arg_list(args), args)
 8928
 8929    def _parse_trim(self) -> exp.Trim:
 8930        # https://www.w3resource.com/sql/character-functions/trim.php
 8931        # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html
 8932
 8933        position = None
 8934        collation = None
 8935        expression = None
 8936
 8937        if self._match_texts(self.TRIM_TYPES):
 8938            position = self._prev.text.upper()
 8939
 8940        this = self._parse_bitwise()
 8941        if self._match_set((TokenType.FROM, TokenType.COMMA)):
 8942            invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST
 8943            expression = self._parse_bitwise()
 8944
 8945            if invert_order:
 8946                this, expression = expression, this
 8947
 8948        if self._match(TokenType.COLLATE):
 8949            collation = self._parse_bitwise()
 8950
 8951        return self.expression(
 8952            exp.Trim(this=this, position=position, expression=expression, collation=collation)
 8953        )
 8954
 8955    def _parse_window_clause(self) -> list[exp.Expr] | None:
 8956        return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None
 8957
 8958    def _parse_named_window(self) -> exp.Expr | None:
 8959        return self._parse_window(self._parse_id_var(), alias=True)
 8960
 8961    def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None:
 8962        if self._curr.token_type == TokenType.VAR:
 8963            if self._match_text_seq("IGNORE", "NULLS"):
 8964                return self.expression(exp.IgnoreNulls(this=this))
 8965            if self._match_text_seq("RESPECT", "NULLS"):
 8966                return self.expression(exp.RespectNulls(this=this))
 8967        return this
 8968
 8969    def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None:
 8970        if self._match(TokenType.HAVING):
 8971            self._match_texts(("MAX", "MIN"))
 8972            max = self._prev.text.upper() != "MIN"
 8973            return self.expression(
 8974                exp.HavingMax(this=this, expression=self._parse_column(), max=max)
 8975            )
 8976
 8977        return this
 8978
 8979    def _parse_per(self, this: exp.Expr | None) -> exp.Expr | None:
 8980        if self._next.token_type == TokenType.L_PAREN and self._match_text_seq("PER"):
 8981            return self.expression(
 8982                exp.Per(this=this, expressions=self._parse_wrapped_csv(self._parse_disjunction))
 8983            )
 8984
 8985        return this
 8986
 8987    def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None:
 8988        func = this
 8989        comments = func.comments if isinstance(func, exp.Expr) else None
 8990
 8991        # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html
 8992        if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue):
 8993            if self._match_text_seq("FROM", "FIRST"):
 8994                this.set("from_first", True)
 8995            elif self._match_text_seq("FROM", "LAST"):
 8996                this.set("from_first", False)
 8997
 8998        # T-SQL allows the OVER (...) syntax after WITHIN GROUP.
 8999        # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16
 9000        if self._match_text_seq("WITHIN", "GROUP"):
 9001            order = self._parse_wrapped(self._parse_order)
 9002            this = self.expression(exp.WithinGroup(this=this, expression=order))
 9003
 9004        if self._match_pair(TokenType.FILTER, TokenType.L_PAREN):
 9005            self._match(TokenType.WHERE)
 9006            this = self.expression(
 9007                exp.Filter(this=this, expression=self._parse_where(skip_where_token=True))
 9008            )
 9009            self._match_r_paren()
 9010
 9011        # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER
 9012        # Some dialects choose to implement and some do not.
 9013        # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html
 9014
 9015        # There is some code above in _parse_lambda that handles
 9016        #   SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ...
 9017
 9018        # The below changes handle
 9019        #   SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ...
 9020
 9021        # Oracle allows both formats
 9022        #   (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html)
 9023        #   and Snowflake chose to do the same for familiarity
 9024        #   https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes
 9025        if isinstance(this, exp.AggFunc):
 9026            ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls)
 9027
 9028            if ignore_respect and ignore_respect is not this:
 9029                ignore_respect.replace(ignore_respect.this)
 9030                this = self.expression(ignore_respect.__class__(this=this))
 9031
 9032        this = self._parse_respect_or_ignore_nulls(this)
 9033
 9034        # bigquery select from window x AS (partition by ...)
 9035        if alias:
 9036            over = None
 9037            self._match(TokenType.ALIAS)
 9038        elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS):
 9039            return self._parse_per(this)
 9040        else:
 9041            over = self._prev.text.upper()
 9042
 9043        if comments and isinstance(func, exp.Expr):
 9044            func.pop_comments()
 9045
 9046        if not self._match(TokenType.L_PAREN):
 9047            return self._parse_per(
 9048                self.expression(
 9049                    exp.Window(this=this, alias=self._parse_id_var(False), over=over),
 9050                    comments=comments,
 9051                )
 9052            )
 9053
 9054        window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS)
 9055
 9056        first: bool | None = True if self._match(TokenType.FIRST) else None
 9057        if self._match_text_seq("LAST"):
 9058            first = False
 9059
 9060        partition, order = self._parse_partition_and_order()
 9061        kind = (
 9062            self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS")
 9063        ) and self._prev.text
 9064
 9065        if kind:
 9066            self._match(TokenType.BETWEEN)
 9067            start = self._parse_window_spec()
 9068
 9069            end = self._parse_window_spec() if self._match(TokenType.AND) else {}
 9070            exclude = (
 9071                self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS)
 9072                if self._match_text_seq("EXCLUDE")
 9073                else None
 9074            )
 9075
 9076            spec = self.expression(
 9077                exp.WindowSpec(
 9078                    kind=kind,
 9079                    start=start["value"],
 9080                    start_side=start["side"],
 9081                    end=end.get("value"),
 9082                    end_side=end.get("side"),
 9083                    exclude=exclude,
 9084                )
 9085            )
 9086        else:
 9087            spec = None
 9088
 9089        self._match_r_paren()
 9090
 9091        window = self.expression(
 9092            exp.Window(
 9093                this=this,
 9094                partition_by=partition,
 9095                order=order,
 9096                spec=spec,
 9097                alias=window_alias,
 9098                over=over,
 9099                first=first,
 9100            ),
 9101            comments=comments,
 9102        )
 9103
 9104        # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...)
 9105        if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False):
 9106            return self._parse_window(window, alias=alias)
 9107
 9108        return self._parse_per(window)
 9109
 9110    def _parse_partition_and_order(
 9111        self,
 9112    ) -> tuple[list[exp.Expr], exp.Expr | None]:
 9113        return self._parse_partition_by(), self._parse_order()
 9114
 9115    def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]:
 9116        self._match(TokenType.BETWEEN)
 9117
 9118        return {
 9119            "value": (
 9120                (self._match_text_seq("UNBOUNDED") and "UNBOUNDED")
 9121                or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW")
 9122                or self._parse_bitwise()
 9123            ),
 9124            "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None,
 9125        }
 9126
 9127    def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None:
 9128        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 9129        # so this section tries to parse the clause version and if it fails, it treats the token
 9130        # as an identifier (alias)
 9131        if self._can_parse_limit_or_offset():
 9132            return this
 9133
 9134        # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the
 9135        # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it.
 9136        if self._can_parse_named_window():
 9137            return this
 9138
 9139        any_token = self._match(TokenType.ALIAS)
 9140        comments = self._prev_comments
 9141
 9142        if explicit and not any_token:
 9143            return this
 9144
 9145        if self._match(TokenType.L_PAREN):
 9146            aliases = self.expression(
 9147                exp.Aliases(
 9148                    this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token))
 9149                ),
 9150                comments=comments,
 9151            )
 9152            self._match_r_paren(aliases)
 9153            return aliases
 9154
 9155        alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or (
 9156            self.STRING_ALIASES and self._parse_string_as_identifier()
 9157        )
 9158
 9159        if alias:
 9160            comments.extend(alias.pop_comments())
 9161            this = self.expression(exp.Alias(this=this, alias=alias), comments=comments)
 9162            column = this.this
 9163
 9164            # Moves the comment next to the alias in `expr /* comment */ AS alias`
 9165            if not this.comments and column and column.comments:
 9166                this.comments = column.pop_comments()
 9167
 9168        return this
 9169
 9170    def _parse_id_var(
 9171        self,
 9172        any_token: bool = True,
 9173        tokens: t.Collection[TokenType] | None = None,
 9174    ) -> exp.Expr | None:
 9175        expression = self._parse_identifier()
 9176        if not expression and (
 9177            (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS)
 9178        ):
 9179            quoted = self._prev.token_type == TokenType.STRING
 9180            expression = self._identifier_expression(quoted=quoted)
 9181
 9182        return expression
 9183
 9184    def _parse_string(self) -> exp.Expr | None:
 9185        if self._match_set(self.STRING_PARSERS):
 9186            return self.STRING_PARSERS[self._prev.token_type](self, self._prev)
 9187        return self._parse_placeholder()
 9188
 9189    def _parse_string_as_identifier(self) -> exp.Identifier | None:
 9190        if not self._match(TokenType.STRING):
 9191            return None
 9192        output = exp.to_identifier(self._prev.text, quoted=True)
 9193        output.update_positions(self._prev)
 9194        return output
 9195
 9196    def _parse_number(self) -> exp.Expr | None:
 9197        if self._match_set(self.NUMERIC_PARSERS):
 9198            return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev)
 9199        return self._parse_placeholder()
 9200
 9201    def _parse_identifier(self) -> exp.Expr | None:
 9202        if self._match(TokenType.IDENTIFIER):
 9203            return self._identifier_expression(quoted=True)
 9204        return self._parse_placeholder()
 9205
 9206    def _parse_var(
 9207        self,
 9208        any_token: bool = False,
 9209        tokens: t.Collection[TokenType] | None = None,
 9210        upper: bool = False,
 9211    ) -> exp.Expr | None:
 9212        if (
 9213            (any_token and self._advance_any())
 9214            or self._match(TokenType.VAR)
 9215            or (self._match_set(tokens) if tokens else False)
 9216        ):
 9217            return self.expression(
 9218                exp.Var(this=self._prev.text.upper() if upper else self._prev.text)
 9219            )
 9220        return self._parse_placeholder()
 9221
 9222    def _advance_any(self, ignore_reserved: bool = False) -> Token | None:
 9223        if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS):
 9224            self._advance()
 9225            return self._prev
 9226        return None
 9227
 9228    def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None:
 9229        return self._parse_string() or self._parse_var(any_token=True, upper=upper)
 9230
 9231    def _parse_primary_or_var(self) -> exp.Expr | None:
 9232        return self._parse_primary() or self._parse_var(any_token=True)
 9233
 9234    def _parse_null(self) -> exp.Expr | None:
 9235        if self._match_set((TokenType.NULL, TokenType.UNKNOWN)):
 9236            return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev)
 9237        return self._parse_placeholder()
 9238
 9239    def _parse_boolean(self) -> exp.Expr | None:
 9240        if self._match(TokenType.TRUE):
 9241            return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev)
 9242        if self._match(TokenType.FALSE):
 9243            return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev)
 9244        return self._parse_placeholder()
 9245
 9246    def _parse_star(self) -> exp.Expr | None:
 9247        if self._match(TokenType.STAR):
 9248            return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev)
 9249        return self._parse_placeholder()
 9250
 9251    def _parse_parameter(self) -> exp.Parameter:
 9252        this = self._parse_identifier() or self._parse_primary_or_var()
 9253        return self.expression(exp.Parameter(this=this))
 9254
 9255    def _parse_placeholder(self) -> exp.Expr | None:
 9256        if self._match_set(self.PLACEHOLDER_PARSERS):
 9257            placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self)
 9258            if placeholder:
 9259                return placeholder
 9260            self._advance(-1)
 9261        return None
 9262
 9263    def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None:
 9264        if not self._match_texts(keywords):
 9265            return None
 9266        if self._match(TokenType.L_PAREN, advance=False):
 9267            return self._parse_wrapped_csv(self._parse_expression)
 9268
 9269        expression = self._parse_alias(self._parse_disjunction(), explicit=True)
 9270        return [expression] if expression else None
 9271
 9272    def _parse_csv(
 9273        self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA
 9274    ) -> list[T]:
 9275        parse_result = parse_method()
 9276        items = [parse_result] if parse_result is not None else []
 9277
 9278        while self._match(sep):
 9279            if isinstance(parse_result, exp.Expr):
 9280                self._add_comments(parse_result)
 9281            parse_result = parse_method()
 9282            if parse_result is not None:
 9283                items.append(parse_result)
 9284
 9285        return items
 9286
 9287    def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]:
 9288        return self._parse_wrapped_csv(self._parse_id_var, optional=optional)
 9289
 9290    def _parse_wrapped_csv(
 9291        self,
 9292        parse_method: t.Callable[[], T | None],
 9293        sep: TokenType = TokenType.COMMA,
 9294        optional: bool = False,
 9295    ) -> list[T]:
 9296        return self._parse_wrapped(
 9297            lambda: self._parse_csv(parse_method, sep=sep), optional=optional
 9298        )
 9299
 9300    def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T:
 9301        wrapped = self._match(TokenType.L_PAREN)
 9302        if not wrapped and not optional:
 9303            self.raise_error("Expecting (")
 9304        parse_result = parse_method()
 9305        if wrapped:
 9306            self._match_r_paren()
 9307        return parse_result
 9308
 9309    def _parse_expressions(self) -> list[exp.Expr]:
 9310        return self._parse_csv(self._parse_expression)
 9311
 9312    def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None:
 9313        return (
 9314            self._parse_set_operations(
 9315                self._parse_alias(self._parse_assignment(), explicit=True)
 9316                if alias
 9317                else self._parse_assignment()
 9318            )
 9319            or self._parse_select()
 9320        )
 9321
 9322    def _parse_ddl_select(self) -> exp.Expr | None:
 9323        return self._parse_query_modifiers(
 9324            self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False))
 9325        )
 9326
 9327    def _parse_transaction(self) -> exp.Transaction | exp.Command:
 9328        this = None
 9329        if self._match_texts(self.TRANSACTION_KIND):
 9330            this = self._prev.text
 9331
 9332        self._match_texts(("TRANSACTION", "WORK"))
 9333
 9334        modes = []
 9335        while True:
 9336            mode = []
 9337            while self._match(TokenType.VAR) or self._match(TokenType.NOT):
 9338                mode.append(self._prev.text)
 9339
 9340            if mode:
 9341                modes.append(" ".join(mode))
 9342            if not self._match(TokenType.COMMA):
 9343                break
 9344
 9345        return self.expression(exp.Transaction(this=this, modes=modes))
 9346
 9347    def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback:
 9348        chain = None
 9349        savepoint = None
 9350        is_rollback = self._prev.token_type == TokenType.ROLLBACK
 9351
 9352        self._match_texts(("TRANSACTION", "WORK"))
 9353
 9354        if self._match_text_seq("TO"):
 9355            self._match_text_seq("SAVEPOINT")
 9356            savepoint = self._parse_id_var()
 9357
 9358        if self._match(TokenType.AND):
 9359            chain = not self._match_text_seq("NO")
 9360            self._match_text_seq("CHAIN")
 9361
 9362        if is_rollback:
 9363            return self.expression(exp.Rollback(savepoint=savepoint))
 9364
 9365        return self.expression(exp.Commit(chain=chain))
 9366
 9367    def _parse_refresh(self) -> exp.Refresh | exp.Command:
 9368        start = self._prev
 9369
 9370        if self._match_text_seq("EXTERNAL", "TABLE"):
 9371            kind = "EXTERNAL TABLE"
 9372        elif self._match(TokenType.TABLE):
 9373            kind = "TABLE"
 9374        elif self._match_text_seq("MATERIALIZED", "VIEW"):
 9375            kind = "MATERIALIZED VIEW"
 9376        else:
 9377            kind = ""
 9378
 9379        this = self._parse_string()
 9380        if not kind and not isinstance(this, exp.Literal):
 9381            return self._parse_as_command(start)
 9382
 9383        return self.expression(exp.Refresh(this=this or self._parse_table(), kind=kind))
 9384
 9385    def _parse_column_def_with_exists(self):
 9386        start = self._index
 9387        self._match(TokenType.COLUMN)
 9388
 9389        exists_column = self._parse_exists(not_=True)
 9390        expression = self._parse_field_def()
 9391
 9392        if not isinstance(expression, exp.ColumnDef):
 9393            self._retreat(start)
 9394            return None
 9395
 9396        expression.set("exists", exists_column)
 9397
 9398        return expression
 9399
 9400    def _parse_add_column(self) -> exp.ColumnDef | None:
 9401        if not self._prev.text.upper() == "ADD":
 9402            return None
 9403
 9404        return self._parse_column_def_with_exists()
 9405
 9406    def _parse_drop_column(self) -> exp.Drop | exp.Command | None:
 9407        if not self._match(TokenType.DROP):
 9408            return None
 9409
 9410        kind = None
 9411        if not self.ALTER_DROP_REQUIRES_COLUMN:
 9412            # A bare `DROP [IF EXISTS] c [CASCADE | RESTRICT]` drops a column
 9413            index = self._index
 9414            self._parse_exists()
 9415            if (
 9416                self._curr.token_type not in (TokenType.COLUMN, TokenType.CONSTRAINT)
 9417                and self._match_set(self.ID_VAR_TOKENS)
 9418                and (
 9419                    not self._curr
 9420                    or self._match(TokenType.COMMA, advance=False)
 9421                    or self._match_texts(("CASCADE", "RESTRICT"), advance=False)
 9422                )
 9423            ):
 9424                kind = "COLUMN"
 9425            self._retreat(index)
 9426
 9427        return self._parse_drop(kind=kind)
 9428
 9429    def _parse_alter_drop_action(self) -> exp.Expr | None:
 9430        drop = self._parse_drop_column()
 9431
 9432        if (
 9433            isinstance(drop, exp.Drop)
 9434            and drop.kind == "COLUMN"
 9435            and not self.dialect.ALTER_TABLE_DROP_REQUIRED_FOR_EACH_COLUMN
 9436            and self._match(TokenType.COMMA)
 9437        ):
 9438            drop.set("tables", [*drop.args["tables"], *self._parse_csv(self._parse_column)])
 9439
 9440        return drop
 9441
 9442    # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html
 9443    def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition:
 9444        return self.expression(
 9445            exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists)
 9446        )
 9447
 9448    def _parse_alter_table_add(self) -> list[exp.Expr]:
 9449        def _parse_add_alteration() -> exp.Expr | None:
 9450            self._match_text_seq("ADD")
 9451            if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) or self._match_texts(
 9452                self.ADD_CONSTRAINT_KEYWORDS, advance=False
 9453            ):
 9454                return self.expression(
 9455                    exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint))
 9456                )
 9457
 9458            column_def = self._parse_add_column()
 9459            if isinstance(column_def, exp.ColumnDef):
 9460                return column_def
 9461
 9462            exists = self._parse_exists(not_=True)
 9463            if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False):
 9464                return self.expression(
 9465                    exp.AddPartition(
 9466                        exists=exists,
 9467                        this=self._parse_field(any_token=True),
 9468                        location=self._match_text_seq("LOCATION", advance=False)
 9469                        and self._parse_property(),
 9470                    )
 9471                )
 9472
 9473            return None
 9474
 9475        if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and (
 9476            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
 9477            or self._match_text_seq("COLUMNS")
 9478        ):
 9479            schema = self._parse_schema()
 9480
 9481            return (
 9482                ensure_list(schema)
 9483                if schema
 9484                else self._parse_csv(self._parse_column_def_with_exists)
 9485            )
 9486
 9487        return self._parse_csv(_parse_add_alteration)
 9488
 9489    def _parse_alter_table_alter(self) -> exp.Expr | None:
 9490        if self._match_texts(self.ALTER_ALTER_PARSERS):
 9491            return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self)
 9492
 9493        # Many dialects support the ALTER [COLUMN] syntax, so if there is no
 9494        # keyword after ALTER we default to parsing this statement
 9495        self._match(TokenType.COLUMN)
 9496        exists = self._parse_exists()
 9497        column = self._parse_field(any_token=True)
 9498
 9499        if self._match_pair(TokenType.DROP, TokenType.DEFAULT):
 9500            return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None))
 9501        if self._match_pair(TokenType.SET, TokenType.DEFAULT):
 9502            return self.expression(
 9503                exp.AlterColumn(
 9504                    this=column, default=self._parse_disjunction(), exists=exists or None
 9505                )
 9506            )
 9507        if self._match(TokenType.COMMENT):
 9508            return self.expression(
 9509                exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None)
 9510            )
 9511        if self._match_text_seq("DROP", "NOT", "NULL"):
 9512            return self.expression(
 9513                exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None)
 9514            )
 9515        if self._match_text_seq("SET", "NOT", "NULL"):
 9516            return self.expression(
 9517                exp.AlterColumn(this=column, allow_null=False, exists=exists or None)
 9518            )
 9519
 9520        if self._match_text_seq("SET", "VISIBLE"):
 9521            return self.expression(
 9522                exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None)
 9523            )
 9524        if self._match_text_seq("SET", "INVISIBLE"):
 9525            return self.expression(
 9526                exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None)
 9527            )
 9528
 9529        self._match_text_seq("SET", "DATA")
 9530        dtype = (
 9531            self._parse_types()
 9532            if self._match_text_seq("TYPE") or not self.ALTER_COLUMN_TYPE_REQUIRES_KEYWORD
 9533            else None
 9534        )
 9535        if not dtype:
 9536            return None
 9537
 9538        return self.expression(
 9539            exp.AlterColumn(
 9540                this=column,
 9541                dtype=dtype,
 9542                collate=self._match(TokenType.COLLATE) and self._parse_term(),
 9543                using=self._match(TokenType.USING) and self._parse_disjunction(),
 9544                exists=exists or None,
 9545            )
 9546        )
 9547
 9548    def _parse_alter_diststyle(self) -> exp.AlterDistStyle:
 9549        if self._match_texts(("ALL", "EVEN", "AUTO")):
 9550            return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper())))
 9551
 9552        self._match_text_seq("KEY", "DISTKEY")
 9553        return self.expression(exp.AlterDistStyle(this=self._parse_column()))
 9554
 9555    def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey:
 9556        if compound:
 9557            self._match_text_seq("SORTKEY")
 9558
 9559        if self._match(TokenType.L_PAREN, advance=False):
 9560            return self.expression(
 9561                exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound)
 9562            )
 9563
 9564        self._match_texts(("AUTO", "NONE"))
 9565        return self.expression(
 9566            exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound)
 9567        )
 9568
 9569    def _parse_alter_table_drop(self) -> list[exp.Expr]:
 9570        index = self._index - 1
 9571
 9572        partition_exists = self._parse_exists()
 9573        if self._match(TokenType.PARTITION, advance=False):
 9574            return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists))
 9575
 9576        self._retreat(index)
 9577        return self._parse_csv(self._parse_alter_drop_action)
 9578
 9579    def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None:
 9580        if self._match(TokenType.COLUMN) or (
 9581            not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False)
 9582        ):
 9583            exists = self._parse_exists()
 9584            old_column = self._parse_column()
 9585            to = self._match_text_seq("TO")
 9586            new_column = self._parse_column()
 9587
 9588            if old_column is None or not to or new_column is None:
 9589                return None
 9590
 9591            return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists))
 9592
 9593        self._match_text_seq("TO")
 9594        return self.expression(exp.AlterRename(this=self._parse_table(schema=True)))
 9595
 9596    def _parse_alter_table_set(self) -> exp.AlterSet:
 9597        alter_set = self.expression(exp.AlterSet())
 9598
 9599        if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq(
 9600            "TABLE", "PROPERTIES"
 9601        ):
 9602            alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment))
 9603        elif self._match_text_seq("FILESTREAM_ON", advance=False):
 9604            alter_set.set("expressions", [self._parse_assignment()])
 9605        elif self._match_texts(("LOGGED", "UNLOGGED")):
 9606            alter_set.set("option", exp.var(self._prev.text.upper()))
 9607        elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")):
 9608            alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}"))
 9609        elif self._match_text_seq("LOCATION"):
 9610            alter_set.set("location", self._parse_field())
 9611        elif self._match_text_seq("ACCESS", "METHOD"):
 9612            alter_set.set("access_method", self._parse_field())
 9613        elif self._match_text_seq("TABLESPACE"):
 9614            alter_set.set("tablespace", self._parse_field())
 9615        elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"):
 9616            alter_set.set("file_format", [self._parse_field()])
 9617        elif self._match_text_seq("STAGE_FILE_FORMAT"):
 9618            alter_set.set("file_format", self._parse_wrapped_options())
 9619        elif self._match_text_seq("STAGE_COPY_OPTIONS"):
 9620            alter_set.set("copy_options", self._parse_wrapped_options())
 9621        elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"):
 9622            alter_set.set("tag", self._parse_csv(self._parse_assignment))
 9623        else:
 9624            if self._match_text_seq("SERDE"):
 9625                alter_set.set("serde", self._parse_field())
 9626
 9627            properties = self._parse_wrapped(self._parse_properties, optional=True)
 9628            alter_set.set("expressions", [properties])
 9629
 9630        return alter_set
 9631
 9632    def _parse_alter_session(self) -> exp.AlterSession:
 9633        """Parse ALTER SESSION SET/UNSET statements."""
 9634        if self._match(TokenType.SET):
 9635            expressions = self._parse_csv(lambda: self._parse_set_item_assignment())
 9636            return self.expression(exp.AlterSession(expressions=expressions, unset=False))
 9637
 9638        self._match_text_seq("UNSET")
 9639        expressions = self._parse_csv(
 9640            lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True)))
 9641        )
 9642        return self.expression(exp.AlterSession(expressions=expressions, unset=True))
 9643
 9644    def _parse_alter(self) -> exp.Alter | exp.Command:
 9645        start = self._prev
 9646
 9647        iceberg = self._match_text_seq("ICEBERG")
 9648
 9649        alter_token = self._match_set(self.ALTERABLES) and self._prev
 9650        if not alter_token:
 9651            return self._parse_as_command(start)
 9652        if iceberg and alter_token.token_type != TokenType.TABLE:
 9653            return self._parse_as_command(start)
 9654
 9655        exists = self._parse_exists()
 9656        only = self._match_text_seq("ONLY")
 9657
 9658        if alter_token.token_type == TokenType.SESSION:
 9659            this = None
 9660            check = None
 9661            cluster = None
 9662            parser = self.ALTER_PARSERS.get(alter_token.text.upper())
 9663        else:
 9664            this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS)
 9665            check = self._match_text_seq("WITH", "CHECK")
 9666            cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9667            parser = (
 9668                self.ALTER_PARSERS[self._prev.text.upper()]
 9669                if self._match_texts(self.ALTER_PARSERS)
 9670                else None
 9671            )
 9672
 9673        options: list[exp.Expr] = []
 9674        if (
 9675            not parser
 9676            and alter_token.token_type == TokenType.TABLE
 9677            and not self.ALTER_TABLE_REQUIRES_ACTION
 9678        ):
 9679            # Table options may precede the actions, e.g. MySQL's ENGINE=InnoDB, ADD COLUMN a INT.
 9680            # A clause the property parser rejects, like PARTITION BY RANGE ... MAXVALUE, stays a Command
 9681            properties = self._try_parse(self._parse_properties)
 9682            options = properties.expressions if properties else []
 9683
 9684        actions = ensure_list(parser(self) if parser else None)
 9685        if actions or options:
 9686            # Actions of different kinds and table options may alternate, e.g. MySQL's
 9687            # ADD c INT, AUTO_INCREMENT=5, ADD d INT. The comma before an item may have already
 9688            # been consumed by the previous parser
 9689            while self.ALTER_TABLE_MIXED_ACTIONS and (
 9690                self._match(TokenType.COMMA) or self._prev.token_type == TokenType.COMMA
 9691            ):
 9692                if not self._match_texts(
 9693                    self.PROPERTY_PARSERS, advance=False
 9694                ) and self._match_texts(self.ALTER_PARSERS):
 9695                    parser = self.ALTER_PARSERS[self._prev.text.upper()]
 9696                    parsed = ensure_list(self._try_parse(lambda: parser(self)))
 9697                    if not parsed or any(isinstance(action, exp.Command) for action in parsed):
 9698                        return self._parse_as_command(start)
 9699
 9700                    actions.extend(parsed)
 9701                elif properties := self._try_parse(self._parse_properties):
 9702                    options.extend(properties.expressions)
 9703                else:
 9704                    break
 9705
 9706            not_valid = self._match_text_seq("NOT", "VALID")
 9707            if properties := self._try_parse(self._parse_properties):
 9708                options.extend(properties.expressions)
 9709            cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE")
 9710
 9711            if not self._curr:
 9712                return self.expression(
 9713                    exp.Alter(
 9714                        this=this,
 9715                        kind=alter_token.text.upper(),
 9716                        exists=exists,
 9717                        actions=actions,
 9718                        only=only,
 9719                        options=options,
 9720                        cluster=cluster,
 9721                        not_valid=not_valid,
 9722                        check=check,
 9723                        cascade=cascade,
 9724                        iceberg=iceberg,
 9725                    )
 9726                )
 9727
 9728        return self._parse_as_command(start)
 9729
 9730    def _parse_analyze(self) -> exp.Analyze | exp.Command:
 9731        start = self._prev
 9732        # https://duckdb.org/docs/sql/statements/analyze
 9733        if not self._curr:
 9734            return self.expression(exp.Analyze())
 9735
 9736        options = []
 9737        while self._match_texts(self.ANALYZE_STYLES):
 9738            if self._prev.text.upper() == "BUFFER_USAGE_LIMIT":
 9739                options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}")
 9740            else:
 9741                options.append(self._prev.text.upper())
 9742
 9743        tables: exp.Expr | list[exp.Expr] | None = None
 9744        inner_expression: exp.Expr | None = None
 9745
 9746        kind = self._curr.text.upper() if self._curr else None
 9747
 9748        if self._match(TokenType.TABLE):
 9749            tables = self._parse_csv(self._parse_table_parts)
 9750        elif self._match(TokenType.INDEX):
 9751            tables = self._parse_table_parts()
 9752        elif self._match_text_seq("TABLES"):
 9753            if self._match_set((TokenType.FROM, TokenType.IN)):
 9754                kind = f"{kind} {self._prev.text.upper()}"
 9755                tables = self._parse_table(schema=True, is_db_reference=True)
 9756        elif self._match_text_seq("DATABASE"):
 9757            tables = self._parse_table(schema=True, is_db_reference=True)
 9758        elif self._match_text_seq("CLUSTER"):
 9759            tables = self._parse_table()
 9760        # Try matching inner expr keywords before fallback to parse table.
 9761        elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9762            kind = None
 9763            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9764        else:
 9765            # Empty kind  https://prestodb.io/docs/current/sql/analyze.html
 9766            kind = None
 9767            tables = self._parse_csv(self._parse_table_parts)
 9768
 9769        partition = self._try_parse(self._parse_partition)
 9770        if not partition and self._match_texts(self.PARTITION_KEYWORDS):
 9771            return self._parse_as_command(start)
 9772
 9773        # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9774        if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq(
 9775            "WITH", "ASYNC", "MODE"
 9776        ):
 9777            mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE"
 9778        else:
 9779            mode = None
 9780
 9781        if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9782            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9783
 9784        properties = self._parse_properties()
 9785        return self.expression(
 9786            exp.Analyze(
 9787                kind=kind,
 9788                tables=ensure_list(tables),
 9789                mode=mode,
 9790                partition=partition,
 9791                properties=properties,
 9792                expression=inner_expression,
 9793                options=options,
 9794            )
 9795        )
 9796
 9797    # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html
 9798    def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics:
 9799        this = None
 9800        kind = self._prev.text.upper()
 9801        option = self._prev.text.upper() if self._match_text_seq("DELTA") else None
 9802        expressions = []
 9803
 9804        if not self._match_text_seq("STATISTICS"):
 9805            self.raise_error("Expecting token STATISTICS")
 9806
 9807        if self._match_text_seq("NOSCAN"):
 9808            this = "NOSCAN"
 9809        elif self._match(TokenType.FOR):
 9810            if self._match_text_seq("ALL", "COLUMNS"):
 9811                this = "FOR ALL COLUMNS"
 9812            if self._match_text_seq("COLUMNS"):
 9813                this = "FOR COLUMNS"
 9814                expressions = self._parse_csv(self._parse_column_reference)
 9815        elif self._match_text_seq("SAMPLE"):
 9816            sample = self._parse_number()
 9817            expressions = [
 9818                self.expression(
 9819                    exp.AnalyzeSample(
 9820                        sample=sample,
 9821                        kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None,
 9822                    )
 9823                )
 9824            ]
 9825
 9826        return self.expression(
 9827            exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions)
 9828        )
 9829
 9830    # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html
 9831    def _parse_analyze_validate(self) -> exp.AnalyzeValidate:
 9832        kind = None
 9833        this = None
 9834        expression: exp.Expr | None = None
 9835        if self._match_text_seq("REF", "UPDATE"):
 9836            kind = "REF"
 9837            this = "UPDATE"
 9838            if self._match_text_seq("SET", "DANGLING", "TO", "NULL"):
 9839                this = "UPDATE SET DANGLING TO NULL"
 9840        elif self._match_text_seq("STRUCTURE"):
 9841            kind = "STRUCTURE"
 9842            if self._match_text_seq("CASCADE", "FAST"):
 9843                this = "CASCADE FAST"
 9844            elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts(
 9845                ("ONLINE", "OFFLINE")
 9846            ):
 9847                this = f"CASCADE COMPLETE {self._prev.text.upper()}"
 9848                expression = self._parse_into()
 9849
 9850        return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression))
 9851
 9852    def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None:
 9853        this = self._prev.text.upper()
 9854        if self._match_text_seq("COLUMNS"):
 9855            return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}"))
 9856        return None
 9857
 9858    def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None:
 9859        kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None
 9860        if self._match_text_seq("STATISTICS"):
 9861            return self.expression(exp.AnalyzeDelete(kind=kind))
 9862        return None
 9863
 9864    def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None:
 9865        if self._match_text_seq("CHAINED", "ROWS"):
 9866            return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into()))
 9867        return None
 9868
 9869    # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html
 9870    def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram:
 9871        this = self._prev.text.upper()
 9872        expression: exp.Expr | None = None
 9873        expressions = []
 9874        update_options = None
 9875
 9876        if self._match_text_seq("HISTOGRAM", "ON"):
 9877            expressions = self._parse_csv(self._parse_column_reference)
 9878            with_expressions = []
 9879            while self._match(TokenType.WITH):
 9880                # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9881                if self._match_texts(("SYNC", "ASYNC")):
 9882                    if self._match_text_seq("MODE", advance=False):
 9883                        with_expressions.append(f"{self._prev.text.upper()} MODE")
 9884                        self._advance()
 9885                else:
 9886                    buckets = self._parse_number()
 9887                    if self._match_text_seq("BUCKETS"):
 9888                        with_expressions.append(f"{buckets} BUCKETS")
 9889            if with_expressions:
 9890                expression = self.expression(exp.AnalyzeWith(expressions=with_expressions))
 9891
 9892            if self._match_texts(("MANUAL", "AUTO")) and self._match(
 9893                TokenType.UPDATE, advance=False
 9894            ):
 9895                update_options = self._prev.text.upper()
 9896                self._advance()
 9897            elif self._match_text_seq("USING", "DATA"):
 9898                expression = self.expression(exp.UsingData(this=self._parse_string()))
 9899
 9900        return self.expression(
 9901            exp.AnalyzeHistogram(
 9902                this=this,
 9903                expressions=expressions,
 9904                expression=expression,
 9905                update_options=update_options,
 9906            )
 9907        )
 9908
 9909    def _parse_merge(self) -> exp.Merge:
 9910        self._match(TokenType.INTO)
 9911        target = self._parse_table()
 9912
 9913        if target and self._match(TokenType.ALIAS, advance=False):
 9914            target.set("alias", self._parse_table_alias())
 9915
 9916        self._match(TokenType.USING)
 9917        using = self._parse_table()
 9918
 9919        return self.expression(
 9920            exp.Merge(
 9921                this=target,
 9922                using=using,
 9923                on=self._match(TokenType.ON) and self._parse_disjunction(),
 9924                using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(),
 9925                whens=self._parse_when_matched(),
 9926                returning=self._parse_returning(),
 9927            )
 9928        )
 9929
 9930    def _parse_when_matched(self) -> exp.Whens:
 9931        whens = []
 9932
 9933        while self._match(TokenType.WHEN):
 9934            matched = not self._match(TokenType.NOT)
 9935            self._match_text_seq("MATCHED")
 9936            source = (
 9937                False
 9938                if self._match_text_seq("BY", "TARGET")
 9939                else self._match_text_seq("BY", "SOURCE")
 9940            )
 9941            condition = self._parse_disjunction() if self._match(TokenType.AND) else None
 9942
 9943            self._match(TokenType.THEN)
 9944
 9945            if self._match(TokenType.INSERT):
 9946                this = (
 9947                    exp.Star() if self._match_text_seq("ALL", "BY", "NAME") else self._parse_star()
 9948                )
 9949                if this:
 9950                    then: exp.Expr | None = self.expression(exp.Insert(this=this))
 9951                else:
 9952                    then = self.expression(
 9953                        exp.Insert(
 9954                            this=exp.var("ROW")
 9955                            if self._match_text_seq("ROW")
 9956                            else self._parse_value(values=False),
 9957                            expression=self._match_text_seq("VALUES") and self._parse_value(),
 9958                            where=self._parse_where(),
 9959                        )
 9960                    )
 9961            elif self._match(TokenType.UPDATE):
 9962                expressions = self._parse_star()
 9963                if expressions:
 9964                    then = self.expression(exp.Update(expressions=expressions))
 9965                elif self._match_text_seq("ALL", "BY", "NAME"):
 9966                    then = self.expression(exp.Update(expressions=[exp.Star()]))
 9967                else:
 9968                    then = self.expression(
 9969                        exp.Update(
 9970                            expressions=self._match(TokenType.SET)
 9971                            and self._parse_csv(self._parse_update_assignment),
 9972                            where=self._parse_where(),
 9973                        )
 9974                    )
 9975            elif self._match(TokenType.DELETE):
 9976                then = self.expression(exp.Var(this=self._prev.text))
 9977            else:
 9978                then = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 9979
 9980            whens.append(
 9981                self.expression(
 9982                    exp.When(matched=matched, source=source, condition=condition, then=then)
 9983                )
 9984            )
 9985        return self.expression(exp.Whens(expressions=whens))
 9986
 9987    def _parse_show(self) -> exp.Expr | None:
 9988        parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE)
 9989        if parser:
 9990            return parser(self)
 9991        return self._parse_as_command(self._prev)
 9992
 9993    def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None:
 9994        index = self._index
 9995
 9996        if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"):
 9997            return self._parse_set_transaction(global_=kind == "GLOBAL")
 9998
 9999        left = self._parse_primary() or self._parse_column()
10000        assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS)
10001
10002        if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter):
10003            self._retreat(index)
10004            return None
10005
10006        right = self._parse_statement() or self._parse_id_var()
10007        if isinstance(right, (exp.Column, exp.Identifier)):
10008            right = exp.var(right.name)
10009
10010        this = self.expression(exp.EQ(this=left, expression=right))
10011        return self.expression(exp.SetItem(this=this, kind=kind))
10012
10013    def _parse_set_transaction(self, global_: bool = False) -> exp.Expr:
10014        self._match_text_seq("TRANSACTION")
10015        characteristics = self._parse_csv(
10016            lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS)
10017        )
10018        return self.expression(
10019            exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_)
10020        )
10021
10022    def _parse_set_item(self) -> exp.Expr | None:
10023        parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE)
10024        return parser(self) if parser else self._parse_set_item_assignment(kind=None)
10025
10026    def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command:
10027        index = self._index
10028        set_ = self.expression(
10029            exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag)
10030        )
10031
10032        if self._curr:
10033            self._retreat(index)
10034            return self._parse_as_command(self._prev)
10035
10036        return set_
10037
10038    def _parse_var_from_options(
10039        self, options: OPTIONS_TYPE, raise_unmatched: bool = True
10040    ) -> exp.Var | None:
10041        start = self._curr
10042        if not start:
10043            return None
10044
10045        option = start.text.upper()
10046        continuations = (
10047            None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option)
10048        )
10049
10050        index = self._index
10051        self._advance()
10052        for keywords in continuations or []:
10053            if isinstance(keywords, str):
10054                keywords = (keywords,)
10055
10056            if self._match_text_seq(*keywords):
10057                option = f"{option} {' '.join(keywords)}"
10058                break
10059        else:
10060            if continuations or continuations is None:
10061                if raise_unmatched:
10062                    self.raise_error(f"Unknown option {option}")
10063
10064                self._retreat(index)
10065                return None
10066
10067        return exp.var(option)
10068
10069    def _parse_as_command(self, start: Token) -> exp.Command:
10070        while self._curr:
10071            self._advance()
10072        text = self._find_sql(start, self._prev)
10073        size = len(start.text)
10074        self._warn_unsupported()
10075        return exp.Command(this=text[:size], expression=text[size:])
10076
10077    def _parse_dict_property(self, this: str) -> exp.DictProperty:
10078        settings = []
10079
10080        self._match_l_paren()
10081        kind = self._parse_id_var()
10082
10083        if self._match(TokenType.L_PAREN):
10084            while True:
10085                key = self._parse_id_var()
10086                value = self._parse_function() or self._parse_primary_or_var()
10087                if not key and value is None:
10088                    break
10089                settings.append(self.expression(exp.DictSubProperty(this=key, value=value)))
10090            self._match(TokenType.R_PAREN)
10091
10092        self._match_r_paren()
10093
10094        return self.expression(
10095            exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings)
10096        )
10097
10098    def _parse_dict_range(self, this: str) -> exp.DictRange:
10099        self._match_l_paren()
10100        has_min = self._match_text_seq("MIN")
10101        if has_min:
10102            min = self._parse_var() or self._parse_primary()
10103            self._match_text_seq("MAX")
10104            max = self._parse_var() or self._parse_primary()
10105        else:
10106            max = self._parse_var() or self._parse_primary()
10107            min = exp.Literal.number(0)
10108        self._match_r_paren()
10109        return self.expression(exp.DictRange(this=this, min=min, max=max))
10110
10111    def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None:
10112        index = self._index
10113        expression = self._parse_id_var()
10114        position = self._parse_id_var() if self._match(TokenType.COMMA) else None
10115
10116        if not self._match(TokenType.IN):
10117            self._retreat(index - 1)
10118            return None
10119        iterator = self._parse_column()
10120        condition = self._parse_disjunction() if self._match_text_seq("IF") else None
10121        return self.expression(
10122            exp.Comprehension(
10123                this=this,
10124                expression=expression,
10125                position=position,
10126                iterator=iterator,
10127                condition=condition,
10128            )
10129        )
10130
10131    def _parse_heredoc(self) -> exp.Heredoc | None:
10132        if self._match(TokenType.HEREDOC_STRING):
10133            return self.expression(exp.Heredoc(this=self._prev.text))
10134
10135        if not self._match_text_seq("$"):
10136            return None
10137
10138        tags = ["$"]
10139        tag_text = None
10140
10141        if self._is_connected():
10142            self._advance()
10143            tags.append(self._prev.text.upper())
10144        else:
10145            self.raise_error("No closing $ found")
10146
10147        if tags[-1] != "$":
10148            if self._is_connected() and self._match_text_seq("$"):
10149                tag_text = tags[-1]
10150                tags.append("$")
10151            else:
10152                self.raise_error("No closing $ found")
10153
10154        heredoc_start = self._curr
10155
10156        while self._curr:
10157            if self._match_text_seq(*tags, advance=False):
10158                this = self._find_sql(heredoc_start, self._prev)
10159                self._advance(len(tags))
10160                return self.expression(exp.Heredoc(this=this, tag=tag_text))
10161
10162            self._advance()
10163
10164        self.raise_error(f"No closing {''.join(tags)} found")
10165        return None
10166
10167    def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None:
10168        if not self._curr:
10169            return None
10170
10171        index = self._index
10172        this = []
10173        while True:
10174            # The current token might be multiple words
10175            curr = self._curr.text.upper()
10176            key = curr.split(" ")
10177            this.append(curr)
10178
10179            self._advance()
10180            result, trie = in_trie(trie, key)
10181            if result == TrieResult.FAILED:
10182                break
10183
10184            if result == TrieResult.EXISTS:
10185                subparser = parsers[" ".join(this)]
10186                return subparser
10187
10188        self._retreat(index)
10189        return None
10190
10191    def _match_l_paren(self, expression: exp.Expr | None = None) -> None:
10192        if not self._match(TokenType.L_PAREN, expression=expression):
10193            self.raise_error("Expecting (")
10194
10195    def _match_r_paren(self, expression: exp.Expr | None = None) -> None:
10196        if not self._match(TokenType.R_PAREN, expression=expression):
10197            self.raise_error("Expecting )")
10198
10199    def _replace_lambda(
10200        self, node: exp.Expr | None, expressions: list[exp.Expr]
10201    ) -> exp.Expr | None:
10202        if not node:
10203            return node
10204
10205        lambda_types = {e.name: e.args.get("to") or False for e in expressions}
10206
10207        for column in node.find_all(exp.Column):
10208            typ = lambda_types.get(column.parts[0].name)
10209            if typ is not None:
10210                dot_or_id = column.to_dot() if column.table else column.this
10211
10212                if isinstance(typ, exp.DataType):
10213                    dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ.copy()))
10214
10215                parent = column.parent
10216
10217                while isinstance(parent, exp.Dot):
10218                    if not isinstance(parent.parent, exp.Dot):
10219                        parent.replace(dot_or_id)
10220                        break
10221                    parent = parent.parent
10222                else:
10223                    if column is node:
10224                        node = dot_or_id
10225                    else:
10226                        column.replace(dot_or_id)
10227        return node
10228
10229    def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr:
10230        start = self._prev
10231
10232        # Not to be confused with TRUNCATE(number, decimals) function call
10233        if self._match(TokenType.L_PAREN):
10234            self._retreat(self._index - 2)
10235            return self._parse_function()
10236
10237        # Clickhouse supports TRUNCATE DATABASE as well
10238        is_database = self._match(TokenType.DATABASE)
10239
10240        self._match(TokenType.TABLE)
10241
10242        exists = self._parse_exists(not_=False)
10243
10244        expressions = self._parse_csv(
10245            lambda: self._parse_table(schema=True, is_db_reference=is_database)
10246        )
10247
10248        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
10249
10250        if self._match_text_seq("RESTART", "IDENTITY"):
10251            identity = "RESTART"
10252        elif self._match_text_seq("CONTINUE", "IDENTITY"):
10253            identity = "CONTINUE"
10254        else:
10255            identity = None
10256
10257        if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"):
10258            option = self._prev.text
10259        else:
10260            option = None
10261
10262        partition = self._parse_partition()
10263
10264        # Fallback case
10265        if self._curr:
10266            return self._parse_as_command(start)
10267
10268        return self.expression(
10269            exp.TruncateTable(
10270                expressions=expressions,
10271                is_database=is_database,
10272                exists=exists,
10273                cluster=cluster,
10274                identity=identity,
10275                option=option,
10276                partition=partition,
10277            )
10278        )
10279
10280    def _parse_indexed_column(self) -> exp.Expr | None:
10281        return self._parse_ordered(self._parse_opclass)
10282
10283    def _parse_with_operator(self) -> exp.Expr | None:
10284        this = self._parse_indexed_column()
10285
10286        if not self._match(TokenType.WITH):
10287            return this
10288
10289        op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS)
10290
10291        return self.expression(exp.WithOperator(this=this, op=op))
10292
10293    def _parse_wrapped_options(self) -> list[exp.Expr]:
10294        self._match(TokenType.EQ)
10295        self._match(TokenType.L_PAREN)
10296
10297        opts: list[exp.Expr] = []
10298        option: exp.Expr | list[exp.Expr] | None
10299        while self._curr and not self._match(TokenType.R_PAREN):
10300            if self._match_text_seq("FORMAT_NAME", "="):
10301                # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL
10302                option = self._parse_format_name()
10303            else:
10304                option = self._parse_property()
10305
10306            if option is None:
10307                self.raise_error("Unable to parse option")
10308                break
10309
10310            opts.extend(ensure_list(option))
10311
10312        return opts
10313
10314    def _parse_copy_parameters(self) -> list[exp.CopyParameter]:
10315        sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None
10316
10317        options = []
10318        while self._curr and not self._match(TokenType.R_PAREN, advance=False):
10319            option = self._parse_var(any_token=True)
10320            if option is None:
10321                break
10322
10323            prev = self._prev.text.upper()
10324
10325            # Different dialects might separate options and values by white space, "=" and "AS"
10326            self._match(TokenType.EQ)
10327            self._match(TokenType.ALIAS)
10328
10329            param = self.expression(exp.CopyParameter(this=option))
10330
10331            if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match(
10332                TokenType.L_PAREN, advance=False
10333            ):
10334                # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options
10335                param.set("expressions", self._parse_wrapped_options())
10336            elif prev == "FILE_FORMAT":
10337                # T-SQL's external file format case
10338                param.set("expression", self._parse_field())
10339            elif (
10340                prev == "FORMAT"
10341                and self._prev.token_type == TokenType.ALIAS
10342                and self._match_texts(("AVRO", "JSON"))
10343            ):
10344                param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}"))
10345                param.set("expression", self._parse_field())
10346            else:
10347                param.set("expression", self._parse_unquoted_field() or self._parse_bracket())
10348
10349            options.append(param)
10350
10351            if sep:
10352                self._match(sep)
10353
10354        return options
10355
10356    def _parse_credentials(self) -> exp.Credentials | None:
10357        expr = self.expression(exp.Credentials())
10358
10359        if self._match_text_seq("STORAGE_INTEGRATION", "="):
10360            expr.set("storage", self._parse_field())
10361        if self._match_text_seq("CREDENTIALS"):
10362            # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string>
10363            creds = (
10364                self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field()
10365            )
10366            expr.set("credentials", creds)
10367        if self._match_text_seq("ENCRYPTION"):
10368            expr.set("encryption", self._parse_wrapped_options())
10369        if self._match_text_seq("IAM_ROLE"):
10370            expr.set(
10371                "iam_role",
10372                exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(),
10373            )
10374        if self._match_text_seq("REGION"):
10375            expr.set("region", self._parse_field())
10376
10377        return expr
10378
10379    def _parse_file_location(self) -> exp.Expr | None:
10380        return self._parse_field()
10381
10382    def _parse_copy(self) -> exp.Copy | exp.Command:
10383        start = self._prev
10384
10385        self._match(TokenType.INTO)
10386
10387        this = (
10388            self._parse_select(nested=True, parse_subquery_alias=False)
10389            if self._match(TokenType.L_PAREN, advance=False)
10390            else self._parse_table(schema=True)
10391        )
10392
10393        kind = self._match(TokenType.FROM) or not self._match_text_seq("TO")
10394
10395        files = self._parse_csv(self._parse_file_location)
10396        if self._match(TokenType.EQ, advance=False):
10397            # Backtrack one token since we've consumed the lhs of a parameter assignment here.
10398            # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter
10399            # list via `_parse_wrapped(..)` below.
10400            self._advance(-1)
10401            files = []
10402
10403        credentials = self._parse_credentials()
10404
10405        self._match_text_seq("WITH")
10406
10407        params = self._parse_wrapped(self._parse_copy_parameters, optional=True)
10408
10409        # Fallback case
10410        if self._curr:
10411            return self._parse_as_command(start)
10412
10413        return self.expression(
10414            exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params)
10415        )
10416
10417    def _parse_normalize(self) -> exp.Normalize:
10418        return self.expression(
10419            exp.Normalize(
10420                this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var()
10421            )
10422        )
10423
10424    def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor:
10425        args = self._parse_csv(lambda: self._parse_lambda())
10426
10427        this = seq_get(args, 0)
10428        decimals = seq_get(args, 1)
10429
10430        return expr_type(
10431            this=this,
10432            decimals=decimals,
10433            to=self._parse_var() if self._match_text_seq("TO") else None,
10434        )
10435
10436    def _parse_star_ops(self) -> exp.Expr | None:
10437        star_token = self._prev
10438
10439        if self._match_text_seq("COLUMNS", "(", advance=False):
10440            this = self._parse_function()
10441            if isinstance(this, exp.Columns):
10442                this.set("unpack", True)
10443            return this
10444
10445        index = self._index
10446        ilike = self._parse_string() if self._match(TokenType.ILIKE) else None
10447        if not ilike:
10448            # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)`
10449            self._retreat(index)
10450
10451        return self.expression(
10452            exp.Star(
10453                ilike=ilike,
10454                except_=self._parse_star_op("EXCEPT", "EXCLUDE"),
10455                replace=self._parse_star_op("REPLACE"),
10456                rename=self._parse_star_op("RENAME"),
10457            )
10458        ).update_positions(star_token)
10459
10460    def _parse_grant_privilege(self) -> exp.GrantPrivilege | None:
10461        privilege_parts = []
10462
10463        # Keep consuming consecutive keywords until comma (end of this privilege) or ON
10464        # (end of privilege list) or L_PAREN (start of column list) are met
10465        while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False):
10466            privilege_parts.append(self._curr.text.upper())
10467            self._advance()
10468
10469        if not privilege_parts:
10470            self.raise_error("Expected privilege")
10471            return None
10472
10473        this = exp.var(" ".join(privilege_parts))
10474        expressions = (
10475            self._parse_wrapped_csv(self._parse_column)
10476            if self._match(TokenType.L_PAREN, advance=False)
10477            else None
10478        )
10479
10480        return self.expression(exp.GrantPrivilege(this=this, expressions=expressions))
10481
10482    def _parse_grant_principal(self) -> exp.GrantPrincipal | None:
10483        kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper()
10484        principal = self._parse_id_var()
10485
10486        if not principal:
10487            return None
10488
10489        return self.expression(exp.GrantPrincipal(this=principal, kind=kind))
10490
10491    def _parse_grant_revoke_common(
10492        self,
10493    ) -> tuple[list | None, str | None, exp.Expr | None]:
10494        privileges = self._parse_csv(self._parse_grant_privilege)
10495
10496        self._match(TokenType.ON)
10497        kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None
10498
10499        # Attempt to parse the securable e.g. MySQL allows names
10500        # such as "foo.*", "*.*" which are not easily parseable yet
10501        securable = self._try_parse(self._parse_table_parts)
10502
10503        return privileges, kind, securable
10504
10505    def _parse_grant(self) -> exp.Grant | exp.Command:
10506        start = self._prev
10507
10508        privileges, kind, securable = self._parse_grant_revoke_common()
10509
10510        if not securable or not self._match_text_seq("TO"):
10511            return self._parse_as_command(start)
10512
10513        principals = self._parse_csv(self._parse_grant_principal)
10514
10515        grant_option = self._match_text_seq("WITH", "GRANT", "OPTION")
10516
10517        if self._curr:
10518            return self._parse_as_command(start)
10519
10520        return self.expression(
10521            exp.Grant(
10522                privileges=privileges,
10523                kind=kind,
10524                securable=securable,
10525                principals=principals,
10526                grant_option=grant_option,
10527            )
10528        )
10529
10530    def _parse_revoke(self) -> exp.Revoke | exp.Command:
10531        start = self._prev
10532
10533        grant_option = self._match_text_seq("GRANT", "OPTION", "FOR")
10534
10535        privileges, kind, securable = self._parse_grant_revoke_common()
10536
10537        if not securable or not self._match_text_seq("FROM"):
10538            return self._parse_as_command(start)
10539
10540        principals = self._parse_csv(self._parse_grant_principal)
10541
10542        cascade = None
10543        if self._match_texts(("CASCADE", "RESTRICT")):
10544            cascade = self._prev.text.upper()
10545
10546        if self._curr:
10547            return self._parse_as_command(start)
10548
10549        return self.expression(
10550            exp.Revoke(
10551                privileges=privileges,
10552                kind=kind,
10553                securable=securable,
10554                principals=principals,
10555                grant_option=grant_option,
10556                cascade=cascade,
10557            )
10558        )
10559
10560    def _parse_overlay(self) -> exp.Overlay:
10561        def _parse_overlay_arg(text: str) -> exp.Expr | None:
10562            return (
10563                self._parse_bitwise()
10564                if self._match(TokenType.COMMA) or self._match_text_seq(text)
10565                else None
10566            )
10567
10568        return self.expression(
10569            exp.Overlay(
10570                this=self._parse_bitwise(),
10571                expression=_parse_overlay_arg("PLACING"),
10572                from_=_parse_overlay_arg("FROM"),
10573                for_=_parse_overlay_arg("FOR"),
10574            )
10575        )
10576
10577    def _parse_format_name(self) -> exp.Property:
10578        # Note: Although not specified in the docs, Snowflake does accept a string/identifier
10579        # for FILE_FORMAT = <format_name>
10580        return self.expression(
10581            exp.Property(
10582                this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts()
10583            )
10584        )
10585
10586    def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F:
10587        is_distinct = self._match(TokenType.DISTINCT)
10588        if not is_distinct:
10589            self._match(TokenType.ALL)
10590
10591        args = [self._parse_lambda()]
10592        if self._match(TokenType.COMMA):
10593            args.extend(self._parse_function_args())
10594
10595        target = seq_get(args, distinct_index)
10596        if is_distinct and target:
10597            args[distinct_index] = self.expression(exp.Distinct(expressions=[target]))
10598
10599        return func.from_arg_list(args)
10600
10601    def _identifier_expression(
10602        self, token: Token | None = None, quoted: bool | None = None
10603    ) -> exp.Identifier:
10604        token = token or self._prev
10605        return self.expression(exp.Identifier(this=token.text, quoted=quoted), token)
10606
10607    def _build_pipe_cte(
10608        self,
10609        query: exp.Query,
10610        expressions: list[exp.Expr],
10611        alias_cte: exp.TableAlias | None = None,
10612    ) -> exp.Select:
10613        new_cte: str | exp.TableAlias | None
10614        if alias_cte:
10615            new_cte = alias_cte
10616        else:
10617            self._pipe_cte_counter += 1
10618            new_cte = f"__tmp{self._pipe_cte_counter}"
10619
10620        with_ = query.args.get("with_")
10621        ctes = with_.pop() if with_ else None
10622
10623        new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False)
10624        if ctes:
10625            new_select.set("with_", ctes)
10626
10627        return new_select.with_(new_cte, as_=query, copy=False)
10628
10629    def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select:
10630        select = self._parse_select(consume_pipe=False)
10631        if not select:
10632            return query
10633
10634        return self._build_pipe_cte(
10635            query=query.select(*select.expressions, append=False), expressions=[exp.Star()]
10636        )
10637
10638    def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select:
10639        limit = self._parse_limit()
10640        offset = self._parse_offset()
10641        if isinstance(limit, exp.Limit):
10642            curr_limit = query.args.get("limit", limit)
10643            curr_is_limit_all = isinstance(curr_limit, exp.Limit) and curr_limit.is_limit_all
10644
10645            # LIMIT ALL means "no cap", so it's never tighter than a numeric limit.
10646            if limit.is_limit_all:
10647                should_replace = curr_is_limit_all
10648            else:
10649                should_replace = (
10650                    curr_is_limit_all or curr_limit.expression.to_py() >= limit.expression.to_py()
10651                )
10652
10653            if should_replace:
10654                query.limit(limit, copy=False)
10655        if offset:
10656            curr_offset = query.args.get("offset")
10657            curr_offset = curr_offset.expression.to_py() if curr_offset else 0
10658            query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False)
10659
10660        return query
10661
10662    def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None:
10663        this = self._parse_disjunction()
10664        if self._match_text_seq("GROUP", "AND", advance=False):
10665            return this
10666
10667        this = self._parse_alias(this)
10668
10669        if self._match_set((TokenType.ASC, TokenType.DESC), advance=False):
10670            return self._parse_ordered(lambda: this)
10671
10672        return this
10673
10674    def _parse_pipe_syntax_aggregate_group_order_by(
10675        self, query: exp.Select, group_by_exists: bool = True
10676    ) -> exp.Select:
10677        expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields)
10678        aggregates_or_groups, orders = [], []
10679        for element in expr:
10680            if isinstance(element, exp.Ordered):
10681                this = element.this
10682                if isinstance(this, exp.Alias):
10683                    element.set("this", this.args["alias"])
10684                orders.append(element)
10685            else:
10686                this = element
10687            aggregates_or_groups.append(this)
10688
10689        if group_by_exists:
10690            query.select(
10691                *aggregates_or_groups, *query.expressions, append=False, copy=False
10692            ).group_by(
10693                *[projection.args.get("alias", projection) for projection in aggregates_or_groups],
10694                copy=False,
10695            )
10696        else:
10697            query.select(*aggregates_or_groups, append=False, copy=False)
10698
10699        if orders:
10700            return query.order_by(*orders, append=False, copy=False)
10701
10702        return query
10703
10704    def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select:
10705        self._match_text_seq("AGGREGATE")
10706        query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False)
10707
10708        if self._match(TokenType.GROUP_BY) or (
10709            self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY)
10710        ):
10711            query = self._parse_pipe_syntax_aggregate_group_order_by(query)
10712
10713        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10714
10715    def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None:
10716        first_setop = self.parse_set_operation(this=query)
10717        if not first_setop:
10718            return None
10719
10720        def _parse_and_unwrap_query() -> exp.Expr | None:
10721            expr = self._parse_paren()
10722            return expr.assert_is(exp.Subquery).unnest() if expr else None
10723
10724        first_setop.this.pop()
10725
10726        setops = [
10727            first_setop.expression.pop().assert_is(exp.Subquery).unnest(),
10728            *self._parse_csv(_parse_and_unwrap_query),
10729        ]
10730
10731        query = self._build_pipe_cte(query=query, expressions=[exp.Star()])
10732        with_ = query.args.get("with_")
10733        ctes = with_.pop() if with_ else None
10734
10735        if isinstance(first_setop, exp.Union):
10736            query = query.union(*setops, copy=False, **first_setop.args)
10737        elif isinstance(first_setop, exp.Except):
10738            query = query.except_(*setops, copy=False, **first_setop.args)
10739        else:
10740            query = query.intersect(*setops, copy=False, **first_setop.args)
10741
10742        query.set("with_", ctes)
10743
10744        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10745
10746    def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None:
10747        join = self._parse_join()
10748        if not join:
10749            return None
10750
10751        if isinstance(query, exp.Select):
10752            return query.join(join, copy=False)
10753
10754        return query
10755
10756    def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select:
10757        pivots = self._parse_pivots()
10758        if not pivots:
10759            return query
10760
10761        from_ = query.args.get("from_")
10762        if from_:
10763            from_.this.set("pivots", pivots)
10764
10765        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10766
10767    def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select:
10768        self._match_text_seq("EXTEND")
10769        query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False)
10770        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10771
10772    def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select:
10773        sample = self._parse_table_sample()
10774
10775        with_ = query.args.get("with_")
10776        if with_:
10777            with_.expressions[-1].this.set("sample", sample)
10778        else:
10779            query.set("sample", sample)
10780
10781        return query
10782
10783    def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None:
10784        if isinstance(query, exp.Subquery):
10785            query = exp.select("*").from_(query, copy=False)
10786
10787        if not query.args.get("from_"):
10788            query = exp.select("*").from_(query.subquery(copy=False), copy=False)
10789
10790        while self._match(TokenType.PIPE_GT):
10791            start_index = self._index
10792            start_text = self._curr.text.upper()
10793            parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text)
10794            if not parser:
10795                # The set operators (UNION, etc) and the JOIN operator have a few common starting
10796                # keywords, making it tricky to disambiguate them without lookahead. The approach
10797                # here is to try and parse a set operation and if that fails, then try to parse a
10798                # join operator. If that fails as well, then the operator is not supported.
10799                parsed_query = self._parse_pipe_syntax_set_operator(query)
10800                parsed_query = parsed_query or self._parse_pipe_syntax_join(query)
10801                if not parsed_query:
10802                    self._retreat(start_index)
10803                    self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.")
10804                    break
10805                query = parsed_query
10806            else:
10807                query = parser(self, query)
10808
10809        return query
10810
10811    def _parse_declareitem(self) -> exp.DeclareItem | None:
10812        self._match_texts(("VAR", "VARIABLE"))
10813
10814        vars = self._parse_csv(self._parse_id_var)
10815        if not vars:
10816            return None
10817
10818        self._match(TokenType.ALIAS)
10819        kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types()
10820        default = (
10821            self._match(TokenType.DEFAULT) or self._match(TokenType.EQ)
10822        ) and self._parse_bitwise()
10823
10824        return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default))
10825
10826    def _parse_declare(self) -> exp.Declare | exp.Command:
10827        start = self._prev
10828        replace = self._match_text_seq("OR", "REPLACE")
10829        expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem))
10830
10831        if not expressions or self._curr:
10832            return self._parse_as_command(start)
10833
10834        return self.expression(exp.Declare(expressions=expressions, replace=replace))
10835
10836    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10837        exp_class = exp.Cast if strict else exp.TryCast
10838
10839        if exp_class == exp.TryCast:
10840            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10841
10842        return self.expression(exp_class(**kwargs))
10843
10844    def _parse_json_value(self, json_path: bool = True) -> exp.JSONValue:
10845        this = self._parse_bitwise()
10846        self._match(TokenType.COMMA)
10847        path = self._parse_bitwise()
10848
10849        returning = self._match(TokenType.RETURNING) and self._parse_types()
10850
10851        return self.expression(
10852            exp.JSONValue(
10853                this=this,
10854                path=self.dialect.to_json_path(path) if json_path else path,
10855                returning=returning,
10856                on_condition=self._parse_on_condition(),
10857            )
10858        )
10859
10860    def _parse_group_concat(self) -> exp.Expr | None:
10861        def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr:
10862            if isinstance(node, exp.Distinct) and len(node.expressions) > 1:
10863                concat_exprs = [
10864                    self.expression(
10865                        exp.Concat(
10866                            expressions=node.expressions,
10867                            safe=True,
10868                            coalesce=self.dialect.CONCAT_COALESCE,
10869                        )
10870                    )
10871                ]
10872                node.set("expressions", concat_exprs)
10873                return node
10874            if len(exprs) == 1:
10875                return exprs[0]
10876            return self.expression(
10877                exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE)
10878            )
10879
10880        args = self._parse_csv(self._parse_lambda)
10881
10882        if args:
10883            order = args[-1] if isinstance(args[-1], exp.Order) else None
10884
10885            if order:
10886                # Order By is the last (or only) expression in the list and has consumed the 'expr' before it,
10887                # remove 'expr' from exp.Order and add it back to args
10888                args[-1] = order.this
10889                order.set("this", concat_exprs(order.this, args))
10890
10891            this = order or concat_exprs(args[0], args)
10892        else:
10893            this = None
10894
10895        separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None
10896
10897        return self.expression(exp.GroupConcat(this=this, separator=separator))
10898
10899    def _parse_initcap(self) -> exp.Initcap:
10900        expr = exp.Initcap.from_arg_list(self._parse_function_args())
10901
10902        # attach dialect's default delimiters
10903        if expr.args.get("expression") is None:
10904            expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS))
10905
10906        return expr
10907
10908    def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None:
10909        if not self._match(TokenType.L_PAREN):
10910            self._retreat(self._index - 1)
10911            return None
10912
10913        op = ""
10914        while self._curr and not self._match(TokenType.R_PAREN):
10915            op += self._curr.text
10916            self._advance()
10917
10918        comments = self._prev_comments
10919        return self.expression(
10920            exp.Operator(this=this, operator=op, expression=self._parse_bitwise()),
10921            comments=comments,
10922        )
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.REF,
  651        TokenType.INET,
  652        TokenType.IPADDRESS,
  653        TokenType.IPPREFIX,
  654        TokenType.IPV4,
  655        TokenType.IPV6,
  656        TokenType.UNKNOWN,
  657        TokenType.NOTHING,
  658        TokenType.NULL,
  659        TokenType.NAME,
  660        TokenType.TDIGEST,
  661        TokenType.DYNAMIC,
  662        *ENUM_TYPE_TOKENS,
  663        *NESTED_TYPE_TOKENS,
  664        *AGGREGATE_TYPE_TOKENS,
  665    }
  666
  667    SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = {
  668        TokenType.BIGINT: TokenType.UBIGINT,
  669        TokenType.INT: TokenType.UINT,
  670        TokenType.MEDIUMINT: TokenType.UMEDIUMINT,
  671        TokenType.SMALLINT: TokenType.USMALLINT,
  672        TokenType.TINYINT: TokenType.UTINYINT,
  673        TokenType.DECIMAL: TokenType.UDECIMAL,
  674        TokenType.DOUBLE: TokenType.UDOUBLE,
  675    }
  676
  677    SUBQUERY_PREDICATES: t.ClassVar = {
  678        TokenType.ANY: exp.Any,
  679        TokenType.ALL: exp.All,
  680        TokenType.EXISTS: exp.Exists,
  681        TokenType.SOME: exp.Any,
  682    }
  683
  684    SUBQUERY_TOKENS: t.ClassVar = {
  685        TokenType.SELECT,
  686        TokenType.WITH,
  687        TokenType.FROM,
  688    }
  689
  690    RESERVED_TOKENS: t.ClassVar = {
  691        *Tokenizer.SINGLE_TOKENS.values(),
  692        TokenType.SELECT,
  693    } - {TokenType.IDENTIFIER}
  694
  695    # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers,
  696    # string literals), so they must never be treated as keywords when matching by text
  697    TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset(
  698        {
  699            TokenType.BIT_STRING,
  700            TokenType.BYTE_STRING,
  701            TokenType.HEREDOC_STRING,
  702            TokenType.HEX_STRING,
  703            TokenType.IDENTIFIER,
  704            TokenType.NATIONAL_RAW_STRING,
  705            TokenType.NATIONAL_STRING,
  706            TokenType.RAW_STRING,
  707            TokenType.STRING,
  708            TokenType.UNICODE_STRING,
  709        }
  710    )
  711
  712    DB_CREATABLES: t.ClassVar = {
  713        TokenType.DATABASE,
  714        TokenType.DICTIONARY,
  715        TokenType.FILE_FORMAT,
  716        TokenType.MODEL,
  717        TokenType.NAMESPACE,
  718        TokenType.SCHEMA,
  719        TokenType.SEMANTIC_VIEW,
  720        TokenType.SEQUENCE,
  721        TokenType.SINK,
  722        TokenType.SOURCE,
  723        TokenType.STAGE,
  724        TokenType.STORAGE_INTEGRATION,
  725        TokenType.STREAMLIT,
  726        TokenType.TABLE,
  727        TokenType.TAG,
  728        TokenType.VIEW,
  729        TokenType.WAREHOUSE,
  730    }
  731
  732    CREATABLES: t.ClassVar = {
  733        TokenType.COLUMN,
  734        TokenType.CONSTRAINT,
  735        TokenType.FOREIGN_KEY,
  736        TokenType.FUNCTION,
  737        TokenType.INDEX,
  738        TokenType.PROCEDURE,
  739        TokenType.TRIGGER,
  740        TokenType.TYPE,
  741        *DB_CREATABLES,
  742    }
  743
  744    TRIGGER_EVENTS: t.ClassVar = {
  745        TokenType.INSERT,
  746        TokenType.UPDATE,
  747        TokenType.DELETE,
  748        TokenType.TRUNCATE,
  749    }
  750
  751    ALTERABLES: t.ClassVar = {
  752        TokenType.INDEX,
  753        TokenType.TABLE,
  754        TokenType.VIEW,
  755        TokenType.SESSION,
  756    }
  757
  758    # Tokens that can represent identifiers
  759    ID_VAR_TOKENS: t.ClassVar[set] = {
  760        TokenType.ALL,
  761        TokenType.ANALYZE,
  762        TokenType.ATTACH,
  763        TokenType.VAR,
  764        TokenType.ANTI,
  765        TokenType.APPLY,
  766        TokenType.ASC,
  767        TokenType.ASOF,
  768        TokenType.AUTO_INCREMENT,
  769        TokenType.BEGIN,
  770        TokenType.BPCHAR,
  771        TokenType.CACHE,
  772        TokenType.CASE,
  773        TokenType.COLLATE,
  774        TokenType.COMMAND,
  775        TokenType.COMMENT,
  776        TokenType.COMMIT,
  777        TokenType.CONNECT,
  778        TokenType.CONSTRAINT,
  779        TokenType.COPY,
  780        TokenType.CUBE,
  781        TokenType.CURRENT_SCHEMA,
  782        TokenType.DECLARE,
  783        TokenType.DEFAULT,
  784        TokenType.DELETE,
  785        TokenType.DESC,
  786        TokenType.DESCRIBE,
  787        TokenType.DETACH,
  788        TokenType.DICTIONARY,
  789        TokenType.DISCONNECT,
  790        TokenType.DIV,
  791        TokenType.END,
  792        TokenType.EXECUTE,
  793        TokenType.EXPORT,
  794        TokenType.ESCAPE,
  795        TokenType.FALSE,
  796        TokenType.FIRST,
  797        TokenType.FILE,
  798        TokenType.FILTER,
  799        TokenType.FINAL,
  800        TokenType.FORMAT,
  801        TokenType.FULL,
  802        TokenType.GET,
  803        TokenType.IDENTIFIER,
  804        TokenType.IN,
  805        TokenType.INOUT,
  806        TokenType.IS,
  807        TokenType.ISNULL,
  808        TokenType.INTERVAL,
  809        TokenType.KEEP,
  810        TokenType.KILL,
  811        TokenType.LEFT,
  812        TokenType.LIMIT,
  813        TokenType.LOAD,
  814        TokenType.LOCK,
  815        TokenType.MATCH,
  816        TokenType.MERGE,
  817        TokenType.NATURAL,
  818        TokenType.NEXT,
  819        TokenType.OFFSET,
  820        TokenType.OPERATOR,
  821        TokenType.ORDINALITY,
  822        TokenType.OUT,
  823        TokenType.OVER,
  824        TokenType.OVERLAPS,
  825        TokenType.OVERWRITE,
  826        TokenType.PARTITION,
  827        TokenType.PASTE,
  828        TokenType.PERCENT,
  829        TokenType.PIVOT,
  830        TokenType.PROJECTION,
  831        TokenType.PRAGMA,
  832        TokenType.PUT,
  833        TokenType.RANGE,
  834        TokenType.RECURSIVE,
  835        TokenType.REFERENCES,
  836        TokenType.REFRESH,
  837        TokenType.RENAME,
  838        TokenType.REPLACE,
  839        TokenType.RIGHT,
  840        TokenType.ROLLUP,
  841        TokenType.ROW,
  842        TokenType.ROWS,
  843        TokenType.SEMI,
  844        TokenType.SET,
  845        TokenType.SETTINGS,
  846        TokenType.SHOW,
  847        TokenType.STREAM,
  848        TokenType.STREAMLIT,
  849        TokenType.TEMPORARY,
  850        TokenType.TOP,
  851        TokenType.TRUE,
  852        TokenType.TRUNCATE,
  853        TokenType.UNIQUE,
  854        TokenType.UNNEST,
  855        TokenType.UNPIVOT,
  856        TokenType.UPDATE,
  857        TokenType.USE,
  858        TokenType.VOLATILE,
  859        TokenType.WINDOW,
  860        TokenType.CURRENT_CATALOG,
  861        TokenType.LOCALTIME,
  862        TokenType.LOCALTIMESTAMP,
  863        TokenType.SESSION_USER,
  864        TokenType.STRAIGHT_JOIN,
  865        *ALTERABLES,
  866        *CREATABLES,
  867        *SUBQUERY_PREDICATES,
  868        *TYPE_TOKENS,
  869        *NO_PAREN_FUNCTIONS,
  870    } - {TokenType.UNION}
  871
  872    TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - {
  873        TokenType.ANTI,
  874        TokenType.ASOF,
  875        TokenType.FULL,
  876        TokenType.LEFT,
  877        TokenType.LOCK,
  878        TokenType.NATURAL,
  879        TokenType.RIGHT,
  880        TokenType.SEMI,
  881        TokenType.WINDOW,
  882    }
  883
  884    ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS
  885
  886    COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS
  887
  888    ARRAY_CONSTRUCTORS: t.ClassVar = {
  889        "ARRAY": exp.Array,
  890        "LIST": exp.List,
  891    }
  892
  893    COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS}
  894
  895    UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET}
  896
  897    TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"}
  898
  899    # Tokens that indicate a simple column reference
  900    IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER})
  901
  902    BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE})
  903
  904    # Postfix tokens that prevent the bare column fast path
  905    COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  906        {
  907            TokenType.L_PAREN,
  908            TokenType.L_BRACKET,
  909            TokenType.L_BRACE,
  910            TokenType.COLON,
  911            TokenType.JOIN_MARKER,
  912        }
  913    )
  914
  915    TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  916        {
  917            TokenType.L_PAREN,
  918            TokenType.L_BRACKET,
  919            TokenType.L_BRACE,
  920            TokenType.PIVOT,
  921            TokenType.UNPIVOT,
  922            TokenType.TABLE_SAMPLE,
  923        }
  924    )
  925
  926    FUNC_TOKENS: t.ClassVar = {
  927        TokenType.COLLATE,
  928        TokenType.COMMAND,
  929        TokenType.CURRENT_DATE,
  930        TokenType.CURRENT_DATETIME,
  931        TokenType.CURRENT_SCHEMA,
  932        TokenType.CURRENT_TIMESTAMP,
  933        TokenType.CURRENT_TIME,
  934        TokenType.CURRENT_USER,
  935        TokenType.CURRENT_CATALOG,
  936        TokenType.DECLARE,
  937        TokenType.FILTER,
  938        TokenType.FIRST,
  939        TokenType.FORMAT,
  940        TokenType.GET,
  941        TokenType.GLOB,
  942        TokenType.IDENTIFIER,
  943        TokenType.INDEX,
  944        TokenType.ISNULL,
  945        TokenType.ILIKE,
  946        TokenType.INSERT,
  947        TokenType.LIKE,
  948        TokenType.LOCALTIME,
  949        TokenType.LOCALTIMESTAMP,
  950        TokenType.MERGE,
  951        TokenType.NEXT,
  952        TokenType.OFFSET,
  953        TokenType.PRIMARY_KEY,
  954        TokenType.RANGE,
  955        TokenType.REPLACE,
  956        TokenType.RLIKE,
  957        TokenType.ROW,
  958        TokenType.SESSION_USER,
  959        TokenType.UNNEST,
  960        TokenType.VAR,
  961        TokenType.LEFT,
  962        TokenType.RIGHT,
  963        TokenType.SEQUENCE,
  964        TokenType.DATE,
  965        TokenType.DATETIME,
  966        TokenType.TABLE,
  967        TokenType.TIMESTAMP,
  968        TokenType.TIMESTAMPTZ,
  969        TokenType.TRUNCATE,
  970        TokenType.UTC_DATE,
  971        TokenType.UTC_TIME,
  972        TokenType.UTC_TIMESTAMP,
  973        TokenType.WINDOW,
  974        TokenType.XOR,
  975        *TYPE_TOKENS,
  976        *SUBQUERY_PREDICATES,
  977    }
  978
  979    CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  980        TokenType.AND: exp.And,
  981    }
  982
  983    ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  984        TokenType.COLON_EQ: exp.PropertyEQ,
  985    }
  986
  987    DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  988        TokenType.OR: exp.Or,
  989    }
  990
  991    EQUALITY: t.ClassVar = {
  992        TokenType.EQ: exp.EQ,
  993        TokenType.NEQ: exp.NEQ,
  994        TokenType.NULLSAFE_EQ: exp.NullSafeEQ,
  995    }
  996
  997    COMPARISON: t.ClassVar = {
  998        TokenType.GT: exp.GT,
  999        TokenType.GTE: exp.GTE,
 1000        TokenType.LT: exp.LT,
 1001        TokenType.LTE: exp.LTE,
 1002    }
 1003
 1004    BITWISE: t.ClassVar = {
 1005        TokenType.AMP: exp.BitwiseAnd,
 1006        TokenType.CARET: exp.BitwiseXor,
 1007        TokenType.PIPE: exp.BitwiseOr,
 1008    }
 1009
 1010    TERM: t.ClassVar = {
 1011        TokenType.DASH: exp.Sub,
 1012        TokenType.PLUS: exp.Add,
 1013        TokenType.COLLATE: exp.Collate,
 1014    }
 1015
 1016    FACTOR: t.ClassVar = {
 1017        TokenType.DIV: exp.IntDiv,
 1018        TokenType.LR_ARROW: exp.Distance,
 1019        TokenType.LLRR_ARROW: exp.DistanceNd,
 1020        TokenType.MOD: exp.Mod,
 1021        TokenType.SLASH: exp.Div,
 1022        TokenType.STAR: exp.Mul,
 1023    }
 1024
 1025    EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {}
 1026
 1027    TIMES: t.ClassVar = {
 1028        TokenType.TIME,
 1029        TokenType.TIMETZ,
 1030    }
 1031
 1032    TIMESTAMPS: t.ClassVar = {
 1033        TokenType.TIMESTAMP,
 1034        TokenType.TIMESTAMPNTZ,
 1035        TokenType.TIMESTAMPTZ,
 1036        TokenType.TIMESTAMPLTZ,
 1037        *TIMES,
 1038    }
 1039
 1040    SET_OPERATIONS: t.ClassVar = {
 1041        TokenType.UNION,
 1042        TokenType.INTERSECT,
 1043        TokenType.EXCEPT,
 1044    }
 1045
 1046    JOIN_METHODS: t.ClassVar = {
 1047        TokenType.ASOF,
 1048        TokenType.NATURAL,
 1049        TokenType.POSITIONAL,
 1050    }
 1051
 1052    JOIN_SIDES: t.ClassVar = {
 1053        TokenType.LEFT,
 1054        TokenType.RIGHT,
 1055        TokenType.FULL,
 1056    }
 1057
 1058    JOIN_KINDS: t.ClassVar = {
 1059        TokenType.ANTI,
 1060        TokenType.CROSS,
 1061        TokenType.INNER,
 1062        TokenType.OUTER,
 1063        TokenType.SEMI,
 1064        TokenType.STRAIGHT_JOIN,
 1065    }
 1066
 1067    JOIN_HINTS: t.ClassVar[set[str]] = set()
 1068
 1069    SUPPORTS_DIRECTED_JOIN = True
 1070
 1071    # Tokens that unambiguously end a table reference on the fast path
 1072    TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset(
 1073        {
 1074            TokenType.COMMA,
 1075            TokenType.GROUP_BY,
 1076            TokenType.HAVING,
 1077            TokenType.JOIN,
 1078            TokenType.LIMIT,
 1079            TokenType.ON,
 1080            TokenType.ORDER_BY,
 1081            TokenType.R_PAREN,
 1082            TokenType.SEMICOLON,
 1083            TokenType.SENTINEL,
 1084            TokenType.WHERE,
 1085            *SET_OPERATIONS,
 1086            *JOIN_KINDS,
 1087            *JOIN_METHODS,
 1088            *JOIN_SIDES,
 1089        }
 1090    )
 1091
 1092    LAMBDAS: t.ClassVar = {
 1093        TokenType.ARROW: lambda self, expressions: self.expression(
 1094            exp.Lambda(
 1095                this=self._replace_lambda(
 1096                    self._parse_disjunction(),
 1097                    expressions,
 1098                ),
 1099                expressions=expressions,
 1100            )
 1101        ),
 1102        TokenType.FARROW: lambda self, expressions: self.expression(
 1103            exp.Kwarg(
 1104                this=exp.var(expressions[0].name),
 1105                expression=self._parse_disjunction() or self._parse_select(),
 1106            )
 1107        ),
 1108    }
 1109
 1110    # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake
 1111    TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False
 1112
 1113    LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN})
 1114
 1115    COLUMN_OPERATORS: t.ClassVar = {
 1116        TokenType.DOT: None,
 1117        TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)),
 1118        TokenType.DCOLON: lambda self, this, to: self.build_cast(
 1119            strict=self.STRICT_CAST, this=this, to=to
 1120        ),
 1121        TokenType.ARROW: lambda self, this, path: self.expression(
 1122            exp.JSONExtract(
 1123                this=this,
 1124                expression=self.dialect.to_json_path(path),
 1125                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1126            )
 1127        ),
 1128        TokenType.DARROW: lambda self, this, path: self.expression(
 1129            exp.JSONExtractScalar(
 1130                this=this,
 1131                expression=self.dialect.to_json_path(path),
 1132                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1133                scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY,
 1134            )
 1135        ),
 1136        TokenType.HASH_ARROW: lambda self, this, path: self.expression(
 1137            exp.JSONBExtract(this=this, expression=path)
 1138        ),
 1139        TokenType.DHASH_ARROW: lambda self, this, path: self.expression(
 1140            exp.JSONBExtractScalar(this=this, expression=path)
 1141        ),
 1142        TokenType.PLACEHOLDER: lambda self, this, key: self.expression(
 1143            exp.JSONBContainsTopKey(this=this, expression=key)
 1144        ),
 1145    }
 1146
 1147    # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator"
 1148    # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs).
 1149    JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {}
 1150
 1151    CAST_COLUMN_OPERATORS: t.ClassVar = {
 1152        TokenType.DOTCOLON,
 1153        TokenType.DCOLON,
 1154    }
 1155
 1156    EXPRESSION_PARSERS: t.ClassVar = {
 1157        exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY),
 1158        exp.Column: lambda self: self._parse_column(),
 1159        exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()),
 1160        exp.Condition: lambda self: self._parse_disjunction(),
 1161        exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True),
 1162        exp.Expr: lambda self: self._parse_expression(),
 1163        exp.From: lambda self: self._parse_from(joins=True),
 1164        exp.GrantPrincipal: lambda self: self._parse_grant_principal(),
 1165        exp.GrantPrivilege: lambda self: self._parse_grant_privilege(),
 1166        exp.Group: lambda self: self._parse_group(),
 1167        exp.Having: lambda self: self._parse_having(),
 1168        exp.Hint: lambda self: self._parse_hint_body(),
 1169        exp.Identifier: lambda self: self._parse_id_var(),
 1170        exp.Join: lambda self: self._parse_join(),
 1171        exp.Lambda: lambda self: self._parse_lambda(),
 1172        exp.Lateral: lambda self: self._parse_lateral(),
 1173        exp.Limit: lambda self: self._parse_limit(),
 1174        exp.Offset: lambda self: self._parse_offset(),
 1175        exp.Order: lambda self: self._parse_order(),
 1176        exp.Ordered: lambda self: self._parse_ordered(),
 1177        exp.Properties: lambda self: self._parse_properties(),
 1178        exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(),
 1179        exp.Qualify: lambda self: self._parse_qualify(),
 1180        exp.Returning: lambda self: self._parse_returning(),
 1181        exp.Select: lambda self: self._parse_select(),
 1182        exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY),
 1183        exp.Table: lambda self: self._parse_table_parts(),
 1184        exp.TableAlias: lambda self: self._parse_table_alias(),
 1185        exp.Tuple: lambda self: self._parse_value(values=False),
 1186        exp.Whens: lambda self: self._parse_when_matched(),
 1187        exp.Where: lambda self: self._parse_where(),
 1188        exp.Window: lambda self: self._parse_named_window(),
 1189        exp.With: lambda self: self._parse_with(),
 1190    }
 1191
 1192    STATEMENT_PARSERS: t.ClassVar = {
 1193        TokenType.ALTER: lambda self: self._parse_alter(),
 1194        TokenType.ANALYZE: lambda self: self._parse_analyze(),
 1195        TokenType.BEGIN: lambda self: self._parse_transaction(),
 1196        TokenType.CACHE: lambda self: self._parse_cache(),
 1197        TokenType.COMMENT: lambda self: self._parse_comment(),
 1198        TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(),
 1199        TokenType.COPY: lambda self: self._parse_copy(),
 1200        TokenType.CREATE: lambda self: self._parse_create(),
 1201        TokenType.DECLARE: lambda self: self._parse_declare(),
 1202        TokenType.DELETE: lambda self: self._parse_delete(),
 1203        TokenType.DESC: lambda self: self._parse_describe(),
 1204        TokenType.DESCRIBE: lambda self: self._parse_describe(),
 1205        TokenType.DROP: lambda self: self._parse_drop(),
 1206        TokenType.GRANT: lambda self: self._parse_grant(),
 1207        TokenType.REVOKE: lambda self: self._parse_revoke(),
 1208        TokenType.INSERT: lambda self: self._parse_insert(),
 1209        TokenType.KILL: lambda self: self._parse_kill(),
 1210        TokenType.LOAD: lambda self: self._parse_load(),
 1211        TokenType.MERGE: lambda self: self._parse_merge(),
 1212        TokenType.PIVOT: lambda self: self._parse_simplified_pivot(),
 1213        TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())),
 1214        TokenType.REFRESH: lambda self: self._parse_refresh(),
 1215        TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(),
 1216        TokenType.SET: lambda self: self._parse_set(),
 1217        TokenType.TRUNCATE: lambda self: self._parse_truncate_table(),
 1218        TokenType.UNCACHE: lambda self: self._parse_uncache(),
 1219        TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True),
 1220        TokenType.UPDATE: lambda self: self._parse_update(),
 1221        TokenType.USE: lambda self: self._parse_use(),
 1222        TokenType.SEMICOLON: lambda self: exp.Semicolon(),
 1223    }
 1224
 1225    UNARY_PARSERS: t.ClassVar = {
 1226        TokenType.PLUS: lambda self: self._parse_unary(),  # Unary + is handled as a no-op
 1227        TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())),
 1228        TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())),
 1229        TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())),
 1230        TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())),
 1231        TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())),
 1232    }
 1233
 1234    STRING_PARSERS: t.ClassVar = {
 1235        TokenType.HEREDOC_STRING: lambda self, token: self.expression(
 1236            exp.RawString(this=token.text), token
 1237        ),
 1238        **{
 1239            token_type: lambda self, token: self.expression(exp.National(this=token.text), token)
 1240            for token_type in (TokenType.NATIONAL_STRING, TokenType.NATIONAL_RAW_STRING)
 1241        },
 1242        TokenType.RAW_STRING: lambda self, token: self.expression(
 1243            exp.RawString(this=token.text), token
 1244        ),
 1245        TokenType.STRING: lambda self, token: self.expression(
 1246            exp.Literal(this=token.text, is_string=True), token
 1247        ),
 1248        TokenType.UNICODE_STRING: lambda self, token: self.expression(
 1249            exp.UnicodeString(
 1250                this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string()
 1251            ),
 1252            token,
 1253        ),
 1254    }
 1255
 1256    NUMERIC_PARSERS: t.ClassVar = {
 1257        TokenType.BIT_STRING: lambda self, token: self.expression(
 1258            exp.BitString(this=token.text), token
 1259        ),
 1260        TokenType.BYTE_STRING: lambda self, token: self.expression(
 1261            exp.ByteString(
 1262                this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None
 1263            ),
 1264            token,
 1265        ),
 1266        TokenType.HEX_STRING: lambda self, token: self.expression(
 1267            exp.HexString(
 1268                this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None
 1269            ),
 1270            token,
 1271        ),
 1272        TokenType.NUMBER: lambda self, token: self.expression(
 1273            exp.Literal(this=token.text, is_string=False), token
 1274        ),
 1275    }
 1276
 1277    PRIMARY_PARSERS: t.ClassVar = {
 1278        **STRING_PARSERS,
 1279        **NUMERIC_PARSERS,
 1280        TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token),
 1281        TokenType.NULL: lambda self, _: self.expression(exp.Null()),
 1282        TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)),
 1283        TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)),
 1284        TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(),
 1285        TokenType.STAR: lambda self, _: self._parse_star_ops(),
 1286    }
 1287
 1288    PLACEHOLDER_PARSERS: t.ClassVar = {
 1289        TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()),
 1290        TokenType.PARAMETER: lambda self: self._parse_parameter(),
 1291        TokenType.COLON: lambda self: (
 1292            self.expression(exp.Placeholder(this=self._prev.text))
 1293            if self._match_set(self.COLON_PLACEHOLDER_TOKENS)
 1294            else None
 1295        ),
 1296    }
 1297
 1298    RANGE_PARSERS: t.ClassVar = {
 1299        TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll),
 1300        TokenType.BETWEEN: lambda self, this: self._parse_between(this),
 1301        TokenType.GLOB: binary_range_parser(exp.Glob),
 1302        TokenType.ILIKE: binary_range_parser(exp.ILike),
 1303        TokenType.IN: lambda self, this: self._parse_in(this),
 1304        TokenType.IRLIKE: binary_range_parser(exp.RegexpILike),
 1305        TokenType.IS: lambda self, this: self._parse_is(this),
 1306        TokenType.LIKE: binary_range_parser(exp.Like),
 1307        TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy),
 1308        TokenType.OVERLAPS: binary_range_parser(exp.Overlaps),
 1309        TokenType.RLIKE: binary_range_parser(exp.RegexpLike),
 1310        TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo),
 1311        TokenType.FOR: lambda self, this: self._parse_comprehension(this),
 1312        TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys),
 1313        TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys),
 1314        TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath),
 1315        TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists),
 1316        TokenType.ADJACENT: binary_range_parser(exp.Adjacent),
 1317        TokenType.OPERATOR: lambda self, this: self._parse_operator(this),
 1318        TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft),
 1319        TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight),
 1320        TokenType.CARET_AT: binary_range_parser(exp.StartsWith),
 1321    }
 1322
 1323    PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = {
 1324        "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query),
 1325        "AS": lambda self, query: self._build_pipe_cte(
 1326            query, [exp.Star()], self._parse_table_alias()
 1327        ),
 1328        "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False),
 1329        "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query),
 1330        "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query),
 1331        "ORDER BY": lambda self, query: query.order_by(
 1332            self._parse_order(), append=False, copy=False
 1333        ),
 1334        "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1335        "SELECT": lambda self, query: self._parse_pipe_syntax_select(query),
 1336        "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query),
 1337        "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1338        "WHERE": lambda self, query: query.where(self._parse_where(), copy=False),
 1339    }
 1340
 1341    PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1342        "ALLOWED_VALUES": lambda self: self.expression(
 1343            exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary))
 1344        ),
 1345        "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty),
 1346        "AUTO": lambda self: self._parse_auto_property(),
 1347        "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty),
 1348        "BACKUP": lambda self: self.expression(
 1349            exp.BackupProperty(this=self._parse_var(any_token=True))
 1350        ),
 1351        "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(),
 1352        "CALLED": lambda self: self._parse_called_on_null_input_property(),
 1353        "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs),
 1354        "CHECKSUM": lambda self: self._parse_checksum(),
 1355        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1356        "CLUSTERED": lambda self: self._parse_clustered_by(),
 1357        "COLLATE": lambda self, **kwargs: self._parse_property_assignment(
 1358            exp.CollateProperty, **kwargs
 1359        ),
 1360        "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty),
 1361        "CONTAINS": lambda self: self._parse_contains_property(),
 1362        "COPY": lambda self: self._parse_copy_property(),
 1363        "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs),
 1364        "DATA_DELETION": lambda self: self._parse_data_deletion_property(),
 1365        "DEFINER": lambda self: self._parse_definer(),
 1366        "DETERMINISTIC": lambda self: self.expression(
 1367            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1368        ),
 1369        "DISTRIBUTED": lambda self: self._parse_distributed_property(),
 1370        "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty),
 1371        "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()),
 1372        "DISTKEY": lambda self: self._parse_distkey(),
 1373        "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty),
 1374        "EMPTY": lambda self: self.expression(exp.EmptyProperty()),
 1375        "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty),
 1376        "ENVIRONMENT": lambda self: self.expression(
 1377            exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment))
 1378        ),
 1379        "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty),
 1380        "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty),
 1381        "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()),
 1382        "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs),
 1383        "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1384        "FREESPACE": lambda self: self._parse_freespace(),
 1385        "GLOBAL": lambda self: self.expression(exp.GlobalProperty()),
 1386        "HEAP": lambda self: self.expression(exp.HeapProperty()),
 1387        "ICEBERG": lambda self: self.expression(exp.IcebergProperty()),
 1388        "IMMUTABLE": lambda self: self.expression(
 1389            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1390        ),
 1391        "INHERITS": lambda self: self.expression(
 1392            exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table))
 1393        ),
 1394        "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())),
 1395        "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs),
 1396        "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty),
 1397        "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"),
 1398        "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"),
 1399        "LIKE": lambda self: self._parse_create_like(),
 1400        "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty),
 1401        "LOCK": lambda self: self._parse_locking(),
 1402        "LOCKING": lambda self: self._parse_locking(),
 1403        "LOG": lambda self, **kwargs: self._parse_log(**kwargs),
 1404        "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()),
 1405        "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs),
 1406        "MODIFIES": lambda self: self._parse_modifies_property(),
 1407        "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)),
 1408        "NO": lambda self: self._parse_no_property(),
 1409        "ON": lambda self: self._parse_on_property(),
 1410        "ORDER BY": lambda self: self._parse_order(skip_order_token=True),
 1411        "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())),
 1412        "PARTITION": lambda self: self._parse_partitioned_of(),
 1413        "PARTITION BY": lambda self: self._parse_partitioned_by(),
 1414        "PARTITIONED BY": lambda self: self._parse_partitioned_by(),
 1415        "PARTITIONED_BY": lambda self: self._parse_partitioned_by(),
 1416        "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True),
 1417        "RANGE": lambda self: self._parse_dict_range(this="RANGE"),
 1418        "READS": lambda self: self._parse_reads_property(),
 1419        "REMOTE": lambda self: self._parse_remote_with_connection(),
 1420        "RETURNS": lambda self: self._parse_returns(),
 1421        "STRICT": lambda self: self.expression(exp.StrictProperty()),
 1422        "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()),
 1423        "ROW": lambda self: self._parse_row(),
 1424        "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty),
 1425        "SAMPLE": lambda self: self.expression(
 1426            exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise())
 1427        ),
 1428        "SECURE": lambda self: self.expression(exp.SecureProperty()),
 1429        "SECURITY": lambda self: self._parse_sql_security(),
 1430        "SQL SECURITY": lambda self: self._parse_sql_security(),
 1431        "SET": lambda self: self.expression(exp.SetProperty(multi=False)),
 1432        "SETTINGS": lambda self: self._parse_settings_property(),
 1433        "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty),
 1434        "SORTKEY": lambda self: self._parse_sortkey(),
 1435        "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"),
 1436        "STABLE": lambda self: self.expression(
 1437            exp.StabilityProperty(this=exp.Literal.string("STABLE"))
 1438        ),
 1439        "STORED": lambda self: self._parse_stored(),
 1440        "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(),
 1441        "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(),
 1442        "TEMP": lambda self: self.expression(exp.TemporaryProperty()),
 1443        "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()),
 1444        "TO": lambda self: self._parse_to_table(),
 1445        "TRANSIENT": lambda self: self.expression(exp.TransientProperty()),
 1446        "TRANSFORM": lambda self: self.expression(
 1447            exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression))
 1448        ),
 1449        "TTL": lambda self: self._parse_ttl(),
 1450        "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1451        "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()),
 1452        "VOLATILE": lambda self: self._parse_volatile_property(),
 1453        "WITH": lambda self: self._parse_with_property(),
 1454    }
 1455
 1456    CONSTRAINT_PARSERS: t.ClassVar = {
 1457        "AUTOINCREMENT": lambda self: self._parse_auto_increment(),
 1458        "AUTO_INCREMENT": lambda self: self._parse_auto_increment(),
 1459        "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)),
 1460        "CHECK": lambda self: self._parse_check_constraint(),
 1461        "COLLATE": lambda self: self.expression(
 1462            exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column())
 1463        ),
 1464        "COMMENT": lambda self: self.expression(
 1465            exp.CommentColumnConstraint(this=self._parse_string())
 1466        ),
 1467        "COMPRESS": lambda self: self._parse_compress(),
 1468        "CLUSTERED": lambda self: self.expression(
 1469            exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1470        ),
 1471        "NONCLUSTERED": lambda self: self.expression(
 1472            exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1473        ),
 1474        "DEFAULT": lambda self: self.expression(
 1475            exp.DefaultColumnConstraint(this=self._parse_bitwise())
 1476        ),
 1477        "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())),
 1478        "EPHEMERAL": lambda self: self.expression(
 1479            exp.EphemeralColumnConstraint(this=self._parse_bitwise())
 1480        ),
 1481        "EXCLUDE": lambda self: self.expression(
 1482            exp.ExcludeColumnConstraint(this=self._parse_index_params())
 1483        ),
 1484        "FOREIGN KEY": lambda self: self._parse_foreign_key(),
 1485        "FORMAT": lambda self: self.expression(
 1486            exp.DateFormatColumnConstraint(this=self._parse_var_or_string())
 1487        ),
 1488        "GENERATED": lambda self: self._parse_generated_as_identity(),
 1489        "IDENTITY": lambda self: self._parse_auto_increment(),
 1490        "INLINE": lambda self: self._parse_inline(),
 1491        "LIKE": lambda self: self._parse_create_like(),
 1492        "NOT": lambda self: self._parse_not_constraint(),
 1493        "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)),
 1494        "ON": lambda self: (
 1495            (
 1496                self._match(TokenType.UPDATE)
 1497                and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function()))
 1498            )
 1499            or self.expression(exp.OnProperty(this=self._parse_id_var()))
 1500        ),
 1501        "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())),
 1502        "PERIOD": lambda self: self._parse_period_for_system_time(),
 1503        "PRIMARY KEY": lambda self: self._parse_primary_key(),
 1504        "REFERENCES": lambda self: self._parse_references(match=False),
 1505        "TITLE": lambda self: self.expression(
 1506            exp.TitleColumnConstraint(this=self._parse_var_or_string())
 1507        ),
 1508        "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])),
 1509        "UNIQUE": lambda self: self._parse_unique(),
 1510        "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()),
 1511        "WITH": lambda self: self.expression(
 1512            exp.Properties(expressions=self._parse_wrapped_properties())
 1513        ),
 1514        "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1515        "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1516    }
 1517
 1518    def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None:
 1519        if not self._match(TokenType.L_PAREN, advance=False):
 1520            # Partitioning by bucket or truncate follows the syntax:
 1521            # PARTITION BY (BUCKET(..) | TRUNCATE(..))
 1522            # If we don't have parenthesis after each keyword, we should instead parse this as an identifier
 1523            self._retreat(self._index - 1)
 1524            return None
 1525
 1526        klass = (
 1527            exp.PartitionedByBucket
 1528            if self._prev.text.upper() == "BUCKET"
 1529            else exp.PartitionByTruncate
 1530        )
 1531
 1532        args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column())
 1533        this, expression = seq_get(args, 0), seq_get(args, 1)
 1534
 1535        if isinstance(this, exp.Literal):
 1536            # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order
 1537            #  - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)`
 1538            #  - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)`
 1539            # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)`
 1540            #
 1541            # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning
 1542            # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties
 1543            this, expression = expression, this
 1544
 1545        return self.expression(klass(this=this, expression=expression))
 1546
 1547    ALTER_PARSERS: t.ClassVar = {
 1548        "ADD": lambda self: self._parse_alter_table_add(),
 1549        "AS": lambda self: self._parse_select(),
 1550        "ALTER": lambda self: self._parse_alter_table_alter(),
 1551        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1552        "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())),
 1553        "DROP": lambda self: self._parse_alter_table_drop(),
 1554        "RENAME": lambda self: self._parse_alter_table_rename(),
 1555        "SET": lambda self: self._parse_alter_table_set(),
 1556        "SWAP": lambda self: self.expression(
 1557            exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True))
 1558        ),
 1559    }
 1560
 1561    ALTER_ALTER_PARSERS: t.ClassVar = {
 1562        "DISTKEY": lambda self: self._parse_alter_diststyle(),
 1563        "DISTSTYLE": lambda self: self._parse_alter_diststyle(),
 1564        "SORTKEY": lambda self: self._parse_alter_sortkey(),
 1565        "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True),
 1566    }
 1567
 1568    SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = {
 1569        "CHECK",
 1570        "EXCLUDE",
 1571        "FOREIGN KEY",
 1572        "LIKE",
 1573        "PERIOD",
 1574        "PRIMARY KEY",
 1575        "UNIQUE",
 1576        "BUCKET",
 1577        "TRUNCATE",
 1578    }
 1579
 1580    # Kinds that can't be a constraint name, so `CONSTRAINT` directly followed by one is unnamed
 1581    RESERVED_CONSTRAINT_KINDS: t.ClassVar[set[str]] = set()
 1582
 1583    NO_PAREN_FUNCTION_PARSERS: t.ClassVar = {
 1584        "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())),
 1585        "CASE": lambda self: self._parse_case(),
 1586        "CONNECT_BY_ROOT": lambda self: self.expression(
 1587            exp.ConnectByRoot(this=self._parse_column())
 1588        ),
 1589        "IF": lambda self: self._parse_if(),
 1590    }
 1591
 1592    INVALID_FUNC_NAME_TOKENS: t.ClassVar = {
 1593        TokenType.IDENTIFIER,
 1594        TokenType.STRING,
 1595    }
 1596
 1597    FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"}
 1598
 1599    KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice)
 1600
 1601    FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1602        **{
 1603            name: lambda self: self._parse_distinct_arg_function(exp.ArgMax)
 1604            for name in exp.ArgMax.sql_names()
 1605        },
 1606        **{
 1607            name: lambda self: self._parse_distinct_arg_function(exp.ArgMin)
 1608            for name in exp.ArgMin.sql_names()
 1609        },
 1610        "CAST": lambda self: self._parse_cast(self.STRICT_CAST),
 1611        "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil),
 1612        "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST),
 1613        "CHAR": lambda self: self._parse_char(),
 1614        "CHR": lambda self: self._parse_char(),
 1615        "DECODE": lambda self: self._parse_decode(),
 1616        "EXTRACT": lambda self: self._parse_extract(),
 1617        "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor),
 1618        "GAP_FILL": lambda self: self._parse_gap_fill(),
 1619        "INITCAP": lambda self: self._parse_initcap(),
 1620        "JSON_OBJECT": lambda self: self._parse_json_object(),
 1621        "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True),
 1622        "JSON_TABLE": lambda self: self._parse_json_table(),
 1623        "MATCH": lambda self: self._parse_match_against(),
 1624        "NORMALIZE": lambda self: self._parse_normalize(),
 1625        "OPENJSON": lambda self: self._parse_open_json(),
 1626        "OVERLAY": lambda self: self._parse_overlay(),
 1627        "POSITION": lambda self: self._parse_position(),
 1628        "SAFE_CAST": lambda self: self._parse_cast(False, safe=True),
 1629        "STRING_AGG": lambda self: self._parse_string_agg(),
 1630        "SUBSTRING": lambda self: self._parse_substring(),
 1631        "TRIM": lambda self: self._parse_trim(),
 1632        "TRY_CAST": lambda self: self._parse_cast(False, safe=True),
 1633        "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True),
 1634        "WEIGHT_STRING": lambda self: self._parse_weight_string(),
 1635        "XMLELEMENT": lambda self: self._parse_xml_element(),
 1636        "XMLTABLE": lambda self: self._parse_xml_table(),
 1637    }
 1638
 1639    QUERY_MODIFIER_PARSERS: t.ClassVar = {
 1640        TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()),
 1641        TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()),
 1642        TokenType.WHERE: lambda self: ("where", self._parse_where()),
 1643        TokenType.GROUP_BY: lambda self: ("group", self._parse_group()),
 1644        TokenType.HAVING: lambda self: ("having", self._parse_having()),
 1645        TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()),
 1646        TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()),
 1647        TokenType.ORDER_BY: lambda self: ("order", self._parse_order()),
 1648        TokenType.LIMIT: lambda self: ("limit", self._parse_limit()),
 1649        TokenType.FETCH: lambda self: ("limit", self._parse_limit()),
 1650        TokenType.OFFSET: lambda self: ("offset", self._parse_offset()),
 1651        TokenType.FOR: lambda self: ("locks", self._parse_locks()),
 1652        TokenType.LOCK: lambda self: ("locks", self._parse_locks()),
 1653        TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1654        TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1655        TokenType.CLUSTER_BY: lambda self: (
 1656            "cluster",
 1657            self._parse_cluster(),
 1658        ),
 1659        TokenType.DISTRIBUTE_BY: lambda self: (
 1660            "distribute",
 1661            self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY),
 1662        ),
 1663        TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)),
 1664        TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)),
 1665    }
 1666    QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS)
 1667
 1668    SET_PARSERS: t.ClassVar = {
 1669        "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"),
 1670        "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"),
 1671        "SESSION": lambda self: self._parse_set_item_assignment("SESSION"),
 1672        "TRANSACTION": lambda self: self._parse_set_transaction(),
 1673    }
 1674
 1675    SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {}
 1676
 1677    TYPE_LITERAL_PARSERS: t.ClassVar = {
 1678        exp.DType.JSON: lambda self, this, _: self.expression(
 1679            exp.ParseJSON(this=this, is_literal=True)
 1680        ),
 1681    }
 1682
 1683    TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {}
 1684
 1685    DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN}
 1686
 1687    PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE}
 1688
 1689    TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"}
 1690    TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = {
 1691        "ISOLATION": (
 1692            ("LEVEL", "REPEATABLE", "READ"),
 1693            ("LEVEL", "READ", "COMMITTED"),
 1694            ("LEVEL", "READ", "UNCOMITTED"),
 1695            ("LEVEL", "SERIALIZABLE"),
 1696        ),
 1697        "READ": ("WRITE", "ONLY"),
 1698    }
 1699
 1700    CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1701        **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()),
 1702        "DO": ("NOTHING", "UPDATE"),
 1703    }
 1704
 1705    TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = {
 1706        "INSTEAD": (("OF",),),
 1707        "BEFORE": tuple(),
 1708        "AFTER": tuple(),
 1709    }
 1710
 1711    TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = {
 1712        "NOT": (("DEFERRABLE",),),
 1713        "DEFERRABLE": tuple(),
 1714    }
 1715
 1716    CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = {
 1717        "SCALE": ("EXTEND", "NOEXTEND"),
 1718        "SHARD": ("EXTEND", "NOEXTEND"),
 1719        "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"),
 1720        **dict.fromkeys(
 1721            (
 1722                "SESSION",
 1723                "GLOBAL",
 1724                "KEEP",
 1725                "NOKEEP",
 1726                "ORDER",
 1727                "NOORDER",
 1728                "NOCACHE",
 1729                "CYCLE",
 1730                "NOCYCLE",
 1731                "NOMINVALUE",
 1732                "NOMAXVALUE",
 1733                "NOSCALE",
 1734                "NOSHARD",
 1735            ),
 1736            tuple(),
 1737        ),
 1738    }
 1739
 1740    ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")}
 1741
 1742    USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1743        ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple()
 1744    )
 1745
 1746    CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",))
 1747
 1748    SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1749        "TYPE": ("EVOLUTION",),
 1750        **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()),
 1751    }
 1752
 1753    PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {}
 1754
 1755    EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1756        ("CALLER", "SELF", "OWNER"), tuple()
 1757    )
 1758
 1759    KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1760        "NOT": ("ENFORCED",),
 1761        "MATCH": (
 1762            "FULL",
 1763            "PARTIAL",
 1764            "SIMPLE",
 1765        ),
 1766        "INITIALLY": ("DEFERRED", "IMMEDIATE"),
 1767        "USING": (
 1768            "BTREE",
 1769            "HASH",
 1770        ),
 1771        **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()),
 1772    }
 1773
 1774    WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1775        "NO": ("OTHERS",),
 1776        "CURRENT": ("ROW",),
 1777        **dict.fromkeys(("GROUP", "TIES"), tuple()),
 1778    }
 1779
 1780    INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"}
 1781
 1782    CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"}
 1783    # Time travel clause prefixes, mapped to whether they pin a timestamp or a version
 1784    VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = {
 1785        ("FOR", "SYSTEM_TIME"): "TIMESTAMP",
 1786        ("FOR", "SYSTEM", "TIME"): "TIMESTAMP",
 1787        ("FOR", "TIMESTAMP"): "TIMESTAMP",
 1788        ("FOR", "VERSION"): "VERSION",
 1789        ("TIMESTAMP", "AS", "OF"): "TIMESTAMP",
 1790        ("VERSION", "AS", "OF"): "VERSION",
 1791    }
 1792
 1793    HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"}
 1794    HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"}
 1795
 1796    OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"}
 1797
 1798    OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN}
 1799
 1800    TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE}
 1801
 1802    VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"}
 1803
 1804    WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS}
 1805    WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER}
 1806    WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"}
 1807
 1808    JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS}
 1809
 1810    FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT}
 1811
 1812    ADD_CONSTRAINT_TOKENS: t.ClassVar = {
 1813        TokenType.CONSTRAINT,
 1814        TokenType.FOREIGN_KEY,
 1815        TokenType.INDEX,
 1816        TokenType.KEY,
 1817        TokenType.PRIMARY_KEY,
 1818        TokenType.UNIQUE,
 1819    }
 1820
 1821    # Constraint kinds that start an ALTER ... ADD action but aren't tokens of their own
 1822    ADD_CONSTRAINT_KEYWORDS: t.ClassVar[set[str]] = set()
 1823
 1824    DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT}
 1825
 1826    UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS
 1827
 1828    SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT}
 1829
 1830    COPY_INTO_VARLEN_OPTIONS: t.ClassVar = {
 1831        "FILE_FORMAT",
 1832        "COPY_OPTIONS",
 1833        "FORMAT_OPTIONS",
 1834        "CREDENTIAL",
 1835    }
 1836
 1837    # IS binds above comparisons (high), alongside ordering comparisons (comparison),
 1838    # alongside equality (equality), or below both (low). Preserve the historical
 1839    # high precedence unless the dialect specifies otherwise.
 1840    IS_PREDICATE_PRECEDENCE: t.ClassVar[IsPredicatePrecedence] = IsPredicatePrecedence.HIGH
 1841    IS_PREDICATE_TOKENS: t.ClassVar = {
 1842        TokenType.IS,
 1843        TokenType.ISNULL,
 1844        TokenType.NOTNULL,
 1845        TokenType.NOT,
 1846    }
 1847
 1848    IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"}
 1849
 1850    ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {}
 1851
 1852    ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"}
 1853
 1854    PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN}
 1855
 1856    # The style options for the DESCRIBE statement
 1857    DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"}
 1858
 1859    SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"}
 1860
 1861    # The style options for the ANALYZE statement
 1862    ANALYZE_STYLES: t.ClassVar = {
 1863        "BUFFER_USAGE_LIMIT",
 1864        "FULL",
 1865        "LOCAL",
 1866        "NO_WRITE_TO_BINLOG",
 1867        "SAMPLE",
 1868        "SKIP_LOCKED",
 1869        "VERBOSE",
 1870    }
 1871
 1872    ANALYZE_EXPRESSION_PARSERS: t.ClassVar = {
 1873        "ALL": lambda self: self._parse_analyze_columns(),
 1874        "COMPUTE": lambda self: self._parse_analyze_statistics(),
 1875        "DELETE": lambda self: self._parse_analyze_delete(),
 1876        "DROP": lambda self: self._parse_analyze_histogram(),
 1877        "ESTIMATE": lambda self: self._parse_analyze_statistics(),
 1878        "LIST": lambda self: self._parse_analyze_list(),
 1879        "PREDICATE": lambda self: self._parse_analyze_columns(),
 1880        "UPDATE": lambda self: self._parse_analyze_histogram(),
 1881        "VALIDATE": lambda self: self._parse_analyze_validate(),
 1882    }
 1883
 1884    PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"}
 1885
 1886    AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET)
 1887
 1888    OPERATION_MODIFIERS: t.ClassVar[set[str]] = set()
 1889
 1890    RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"}
 1891
 1892    SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"}
 1893
 1894    MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values)
 1895
 1896    STRICT_CAST: t.ClassVar = True
 1897
 1898    PREFIXED_PIVOT_COLUMNS: t.ClassVar = False
 1899    IDENTIFY_PIVOT_STRINGS: t.ClassVar = False
 1900    # Whether an UNPIVOT outputs its value column(s) before the name column
 1901    UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False
 1902    # Controls when an aggregation's name is included in a pivoted column's name:
 1903    # "agg_name_if_aliased" - only for aggregations that carry an explicit alias
 1904    # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations
 1905    # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only)
 1906    PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased"
 1907
 1908    LOG_DEFAULTS_TO_LN: t.ClassVar = False
 1909
 1910    # Whether the table sample clause expects CSV syntax
 1911    TABLESAMPLE_CSV: t.ClassVar = False
 1912
 1913    # The default method used for table sampling
 1914    DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None
 1915
 1916    # Whether the SET command needs a delimiter (e.g. "=") for assignments
 1917    SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True
 1918
 1919    # Whether the TRIM function expects the characters to trim as its first argument
 1920    TRIM_PATTERN_FIRST: t.ClassVar = False
 1921
 1922    # Whether string aliases are supported `SELECT COUNT(*) 'count'`
 1923    STRING_ALIASES: t.ClassVar = False
 1924
 1925    # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand)
 1926    MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True
 1927    SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset", "sort", "distribute", "cluster"}
 1928
 1929    # Whether to parse IF statements that aren't followed by a left parenthesis as commands
 1930    NO_PAREN_IF_COMMANDS: t.ClassVar = True
 1931
 1932    # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres)
 1933    JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False
 1934
 1935    # Whether the `:` operator is used to extract a value from a VARIANT column
 1936    COLON_IS_VARIANT_EXTRACT: t.ClassVar = False
 1937
 1938    # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged
 1939    # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks)
 1940    COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True
 1941
 1942    # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause.
 1943    # If this is True and '(' is not found, the keyword will be treated as an identifier
 1944    VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True
 1945
 1946    # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift)
 1947    SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False
 1948
 1949    # Whether field names can be digit-prefixed, e.g. data.144A_FLAG or data.144 (BigQuery)
 1950    SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: t.ClassVar = False
 1951
 1952    # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS
 1953    INTERVAL_SPANS: t.ClassVar = True
 1954
 1955    # Whether a PARTITION clause can follow a table reference
 1956    SUPPORTS_PARTITION_SELECTION: t.ClassVar = False
 1957
 1958    # Whether the `name AS expr` schema/column constraint requires parentheses around `expr`
 1959    WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True
 1960
 1961    # Whether the 'AS' keyword is optional in the CTE definition syntax
 1962    OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True
 1963
 1964    # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword
 1965    ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True
 1966
 1967    # Whether dropping a column with an ALTER statement requires the presence of the COLUMN keyword
 1968    ALTER_DROP_REQUIRES_COLUMN: t.ClassVar = True
 1969
 1970    # Whether Alter statements are allowed to contain Partition specifications
 1971    ALTER_TABLE_PARTITIONS: t.ClassVar = False
 1972
 1973    # Whether an ALTER can combine different actions, e.g. ADD COLUMN a INT, DROP COLUMN b
 1974    ALTER_TABLE_MIXED_ACTIONS: t.ClassVar = False
 1975
 1976    # Whether an ALTER TABLE needs an action, e.g. MySQL's ALTER TABLE t ENGINE=InnoDB has none
 1977    ALTER_TABLE_REQUIRES_ACTION: t.ClassVar = True
 1978
 1979    # Whether changing a column's type with ALTER COLUMN requires the TYPE keyword
 1980    ALTER_COLUMN_TYPE_REQUIRES_KEYWORD: t.ClassVar = False
 1981
 1982    # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree.
 1983    # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is
 1984    # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such
 1985    # as BigQuery, where all joins have the same precedence.
 1986    JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False
 1987
 1988    # Whether TIMESTAMP <literal> can produce a zone-aware timestamp
 1989    ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False
 1990
 1991    # Whether map literals support arbitrary expressions as keys.
 1992    # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB).
 1993    # When False, keys are typically restricted to identifiers.
 1994    MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False
 1995
 1996    # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this
 1997    # is true for Snowflake but not for BigQuery which can also process strings
 1998    JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False
 1999
 2000    # Dialects like Databricks support JOINS without join criteria
 2001    # Adding an ON TRUE, makes transpilation semantically correct for other dialects
 2002    ADD_JOIN_ON_TRUE: t.ClassVar = False
 2003
 2004    # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]'
 2005    # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND`
 2006    SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False
 2007
 2008    # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them
 2009    # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation.
 2010    ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False
 2011
 2012    # Whether NTH_VALUE accepts the FROM FIRST | LAST modifier before its OVER clause,
 2013    # e.g. NTH_VALUE(x, 2) FROM LAST IGNORE NULLS OVER (...) (Oracle, Snowflake)
 2014    SUPPORTS_NTH_VALUE_FROM_MODIFIER: t.ClassVar = False
 2015
 2016    # Type names that denote a different type when they're quoted, so quoting has to be
 2017    # preserved instead of resolving them into the built-in type of the same name. These
 2018    # are matched case sensitively, e.g. PostgreSQL's one-byte "char" is not CHAR
 2019    QUOTED_TYPES_TO_PRESERVE: t.ClassVar[set[str]] = set()
 2020
 2021    SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS)
 2022    SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS)
 2023
 2024    def __init__(
 2025        self,
 2026        error_level: ErrorLevel | None = None,
 2027        error_message_context: int = 100,
 2028        max_errors: int = 3,
 2029        max_nodes: int = -1,
 2030        dialect: DialectType = None,
 2031    ):
 2032        self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE
 2033        self.error_message_context: int = error_message_context
 2034        self.max_errors: int = max_errors
 2035        self.max_nodes: int = max_nodes
 2036        self.dialect: t.Any = _resolve_dialect(dialect)
 2037        self.sql: str = ""
 2038        self.errors: list[ParseError] = []
 2039        self._tokens: list[Token] = []
 2040        self._tokens_size: i64 = 0
 2041        self._index: i64 = 0
 2042        self._curr: Token = SENTINEL_NONE
 2043        self._next: Token = SENTINEL_NONE
 2044        self._prev: Token = SENTINEL_NONE
 2045        self._prev_comments: list[str] = []
 2046        self._pipe_cte_counter: int = 0
 2047        self._chunks: list[list[Token]] = []
 2048        self._chunk_index: i64 = 0
 2049        self._node_count: int = 0
 2050
 2051    def reset(self) -> None:
 2052        self.sql = ""
 2053        self.errors = []
 2054        self._tokens = []
 2055        self._tokens_size = 0
 2056        self._index = 0
 2057        self._curr = SENTINEL_NONE
 2058        self._next = SENTINEL_NONE
 2059        self._prev = SENTINEL_NONE
 2060        self._prev_comments = []
 2061        self._pipe_cte_counter = 0
 2062        self._chunks = []
 2063        self._chunk_index = 0
 2064        self._node_count = 0
 2065
 2066    def _advance(self, times: i64 = 1) -> None:
 2067        index = self._index + times
 2068        self._index = index
 2069        tokens = self._tokens
 2070        size = self._tokens_size
 2071        self._curr = tokens[index] if index < size else SENTINEL_NONE
 2072        self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE
 2073
 2074        if index > 0:
 2075            prev = tokens[index - 1]
 2076            self._prev = prev
 2077            self._prev_comments = prev.comments
 2078        else:
 2079            self._prev = SENTINEL_NONE
 2080            self._prev_comments = []
 2081
 2082    def _advance_chunk(self) -> None:
 2083        self._index = -1
 2084        self._tokens = self._chunks[self._chunk_index]
 2085        self._tokens_size = i64(len(self._tokens))
 2086        self._chunk_index += 1
 2087        self._advance()
 2088
 2089    def _retreat(self, index: i64) -> None:
 2090        if index != self._index:
 2091            self._advance(index - self._index)
 2092
 2093    def _add_comments(self, expression: exp.Expr | None) -> None:
 2094        if expression and self._prev_comments:
 2095            expression.add_comments(self._prev_comments)
 2096            self._prev_comments = []
 2097
 2098    def _match(
 2099        self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None
 2100    ) -> bool:
 2101        if self._curr.token_type == token_type:
 2102            if advance:
 2103                self._advance()
 2104            self._add_comments(expression)
 2105            return True
 2106        return False
 2107
 2108    def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool:
 2109        if self._curr.token_type in types:
 2110            if advance:
 2111                self._advance()
 2112            return True
 2113        return False
 2114
 2115    def _match_pair(
 2116        self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True
 2117    ) -> bool:
 2118        if self._curr.token_type == token_type_a and self._next.token_type == token_type_b:
 2119            if advance:
 2120                self._advance(2)
 2121            return True
 2122        return False
 2123
 2124    def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool:
 2125        if (
 2126            self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS
 2127            and self._curr.text.upper() in texts
 2128        ):
 2129            if advance:
 2130                self._advance()
 2131            return True
 2132        return False
 2133
 2134    def _match_text_seq(self, *texts: str, advance: bool = True) -> bool:
 2135        index = self._index
 2136        excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS
 2137        for text in texts:
 2138            if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text:
 2139                self._advance()
 2140            else:
 2141                self._retreat(index)
 2142                return False
 2143
 2144        if not advance:
 2145            self._retreat(index)
 2146
 2147        return True
 2148
 2149    def _is_connected(self) -> bool:
 2150        prev = self._prev
 2151        curr = self._curr
 2152        return bool(prev and curr and prev.end + 1 == curr.start)
 2153
 2154    def _find_sql(self, start: Token, end: Token) -> str:
 2155        return self.sql[start.start : end.end + 1]
 2156
 2157    def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None:
 2158        token = token or self._curr or self._prev or Token.string("")
 2159        formatted_sql, start_context, highlight, end_context = highlight_sql(
 2160            sql=self.sql,
 2161            positions=[(token.start, token.end)],
 2162            context_length=self.error_message_context,
 2163        )
 2164        formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n  {formatted_sql}"
 2165
 2166        error = ParseError.new(
 2167            formatted_message,
 2168            description=message,
 2169            line=token.line,
 2170            col=token.col,
 2171            start_context=start_context,
 2172            highlight=highlight,
 2173            end_context=end_context,
 2174        )
 2175
 2176        if self.error_level == ErrorLevel.IMMEDIATE:
 2177            raise error
 2178
 2179        self.errors.append(error)
 2180
 2181    def validate_expression(self, expression: E, args: list | None = None) -> E:
 2182        if self.max_nodes > -1:
 2183            self._node_count += 1
 2184            if self._node_count > self.max_nodes:
 2185                self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded")
 2186        if self.error_level != ErrorLevel.IGNORE:
 2187            for error_message in expression.error_messages(args):
 2188                self.raise_error(error_message)
 2189        return expression
 2190
 2191    def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None:
 2192        index = self._index
 2193        error_level = self.error_level
 2194        this: T | None = None
 2195
 2196        self.error_level = ErrorLevel.IMMEDIATE
 2197        try:
 2198            this = parse_method()
 2199        except ParseError:
 2200            this = None
 2201        finally:
 2202            if not this or retreat:
 2203                self._retreat(index)
 2204            self.error_level = error_level
 2205
 2206        return this
 2207
 2208    def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]:
 2209        """
 2210        Parses a list of tokens and returns a list of syntax trees, one tree
 2211        per parsed SQL statement.
 2212
 2213        Args:
 2214            raw_tokens: The list of tokens.
 2215            sql: The original SQL string.
 2216
 2217        Returns:
 2218            The list of the produced syntax trees.
 2219        """
 2220        return self._parse(
 2221            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
 2222        )
 2223
 2224    def parse_into(
 2225        self,
 2226        expression_types: exp.IntoType,
 2227        raw_tokens: list[Token],
 2228        sql: str | None = None,
 2229    ) -> list[exp.Expr | None]:
 2230        """
 2231        Parses a list of tokens into a given Expr type. If a collection of Expr
 2232        types is given instead, this method will try to parse the token list into each one
 2233        of them, stopping at the first for which the parsing succeeds.
 2234
 2235        Args:
 2236            expression_types: The expression type(s) to try and parse the token list into.
 2237            raw_tokens: The list of tokens.
 2238            sql: The original SQL string, used to produce helpful debug messages.
 2239
 2240        Returns:
 2241            The target Expr.
 2242        """
 2243        errors = []
 2244        for expression_type in ensure_list(expression_types):
 2245            parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type))
 2246            if not parser:
 2247                raise TypeError(f"No parser registered for {expression_type}")
 2248
 2249            try:
 2250                return self._parse(parser, raw_tokens, sql)
 2251            except ParseError as e:
 2252                e.errors[0]["into_expression"] = expression_type
 2253                errors.append(e)
 2254
 2255        raise ParseError(
 2256            f"Failed to parse '{sql or raw_tokens}' into {expression_types}",
 2257            errors=merge_errors(errors),
 2258        ) from errors[-1]
 2259
 2260    def check_errors(self) -> None:
 2261        """Logs or raises any found errors, depending on the chosen error level setting."""
 2262        if self.error_level == ErrorLevel.WARN:
 2263            for error in self.errors:
 2264                logger.error(str(error))
 2265        elif self.error_level == ErrorLevel.RAISE and self.errors:
 2266            raise ParseError(
 2267                concat_messages(self.errors, self.max_errors),
 2268                errors=merge_errors(self.errors),
 2269            )
 2270
 2271    def expression(
 2272        self,
 2273        instance: E,
 2274        token: Token | None = None,
 2275        comments: list[str] | None = None,
 2276    ) -> E:
 2277        if token:
 2278            instance.update_positions(token)
 2279        instance.add_comments(comments) if comments else self._add_comments(instance)
 2280        if not instance.is_primitive:
 2281            instance = self.validate_expression(instance)
 2282        return instance
 2283
 2284    def _parse_batch_statements(
 2285        self,
 2286        parse_method: t.Callable[[Parser], exp.Expr | None],
 2287        sep_first_statement: bool = True,
 2288    ) -> list[exp.Expr | None]:
 2289        expressions = []
 2290
 2291        # Chunkification binds if/while statements with the first statement of the body
 2292        if sep_first_statement:
 2293            self._match(TokenType.BEGIN)
 2294            expressions.append(parse_method(self))
 2295
 2296        chunks_length = len(self._chunks)
 2297        while self._chunk_index < chunks_length:
 2298            self._advance_chunk()
 2299
 2300            if self._match(TokenType.ELSE, advance=False):
 2301                return expressions
 2302
 2303            if expressions and not self._next and self._match(TokenType.END):
 2304                expressions.append(exp.EndStatement())
 2305                continue
 2306
 2307            expressions.append(parse_method(self))
 2308
 2309            if self._index < self._tokens_size:
 2310                self.raise_error("Invalid expression / Unexpected token")
 2311
 2312            self.check_errors()
 2313
 2314        return expressions
 2315
 2316    def _parse(
 2317        self,
 2318        parse_method: t.Callable[[Parser], exp.Expr | None],
 2319        raw_tokens: list[Token],
 2320        sql: str | None = None,
 2321    ) -> list[exp.Expr | None]:
 2322        self.reset()
 2323        self.sql = sql or ""
 2324
 2325        total = len(raw_tokens)
 2326        chunks: list[list[Token]] = [[]]
 2327
 2328        for i, token in enumerate(raw_tokens):
 2329            if token.token_type == TokenType.SEMICOLON:
 2330                if token.comments:
 2331                    chunks.append([token])
 2332
 2333                if i < total - 1:
 2334                    chunks.append([])
 2335            else:
 2336                chunks[-1].append(token)
 2337
 2338        self._chunks = chunks
 2339
 2340        return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False)
 2341
 2342    def _warn_unsupported(self) -> None:
 2343        if self._tokens_size <= 1:
 2344            return
 2345
 2346        # We use _find_sql because self.sql may comprise multiple chunks, and we're only
 2347        # interested in emitting a warning for the one being currently processed.
 2348        sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context]
 2349
 2350        logger.warning(
 2351            f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'."
 2352        )
 2353
 2354    def _parse_command(self) -> exp.Command:
 2355        self._warn_unsupported()
 2356        comments = self._prev_comments
 2357        return self.expression(
 2358            exp.Command(this=self._prev.text.upper(), expression=self._parse_string()),
 2359            comments=comments,
 2360        )
 2361
 2362    def _parse_comment(self, allow_exists: bool = True) -> exp.Expr:
 2363        start = self._prev
 2364        exists = self._parse_exists() if allow_exists else None
 2365
 2366        self._match(TokenType.ON)
 2367
 2368        materialized = self._match_text_seq("MATERIALIZED")
 2369        kind = self._match_set(self.CREATABLES) and self._prev
 2370        if not kind:
 2371            return self._parse_as_command(start)
 2372
 2373        if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2374            this = self._parse_user_defined_function(kind=kind.token_type)
 2375        elif kind.token_type == TokenType.TABLE:
 2376            this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS)
 2377        elif kind.token_type == TokenType.COLUMN:
 2378            this = self._parse_column()
 2379        else:
 2380            this = self._parse_table_parts(schema=True)
 2381
 2382        self._match(TokenType.IS)
 2383
 2384        return self.expression(
 2385            exp.Comment(
 2386                this=this,
 2387                kind=kind.text,
 2388                expression=self._parse_string(),
 2389                exists=exists,
 2390                materialized=materialized,
 2391            )
 2392        )
 2393
 2394    def _parse_to_table(
 2395        self,
 2396    ) -> exp.ToTableProperty:
 2397        table = self._parse_table_parts(schema=True)
 2398        return self.expression(exp.ToTableProperty(this=table))
 2399
 2400    # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl
 2401    def _parse_ttl(self) -> exp.Expr:
 2402        def _parse_ttl_action() -> exp.Expr | None:
 2403            this = self._parse_bitwise()
 2404
 2405            if self._match_text_seq("DELETE"):
 2406                return self.expression(exp.MergeTreeTTLAction(this=this, delete=True))
 2407            if self._match_text_seq("RECOMPRESS"):
 2408                return self.expression(
 2409                    exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise())
 2410                )
 2411            if self._match_text_seq("TO", "DISK"):
 2412                return self.expression(
 2413                    exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string())
 2414                )
 2415            if self._match_text_seq("TO", "VOLUME"):
 2416                return self.expression(
 2417                    exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string())
 2418                )
 2419
 2420            return this
 2421
 2422        expressions = self._parse_csv(_parse_ttl_action)
 2423        where = self._parse_where()
 2424        group = self._parse_group()
 2425
 2426        aggregates = None
 2427        if group and self._match(TokenType.SET):
 2428            aggregates = self._parse_csv(self._parse_set_item)
 2429
 2430        return self.expression(
 2431            exp.MergeTreeTTL(
 2432                expressions=expressions, where=where, group=group, aggregates=aggregates
 2433            )
 2434        )
 2435
 2436    def _parse_condition(self) -> exp.Expr | None:
 2437        return self._parse_wrapped(parse_method=self._parse_expression, optional=True)
 2438
 2439    def _parse_block(self) -> exp.Block:
 2440        return self.expression(
 2441            exp.Block(
 2442                expressions=self._parse_batch_statements(
 2443                    parse_method=lambda self: self._parse_statement()
 2444                )
 2445            )
 2446        )
 2447
 2448    def _parse_whileblock(self) -> exp.WhileBlock:
 2449        return self.expression(
 2450            exp.WhileBlock(this=self._parse_condition(), body=self._parse_block())
 2451        )
 2452
 2453    def _parse_statement(self) -> exp.Expr | None:
 2454        if not self._curr:
 2455            return None
 2456
 2457        if self._match_set(self.STATEMENT_PARSERS):
 2458            comments = self._prev_comments
 2459            stmt = self.STATEMENT_PARSERS[self._prev.token_type](self)
 2460            stmt.add_comments(comments, prepend=True)
 2461            return stmt
 2462
 2463        if self._match_set(self.dialect.tokenizer_class.COMMANDS):
 2464            return self._parse_command()
 2465
 2466        if self._match_text_seq("WHILE"):
 2467            return self._parse_whileblock()
 2468
 2469        expression = self._parse_expression()
 2470        expression = self._parse_set_operations(expression) if expression else self._parse_select()
 2471
 2472        if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False):
 2473            expression = self._parse_pipe_syntax_query(expression)
 2474
 2475        return self._parse_query_modifiers(expression)
 2476
 2477    def _parse_drop(self, exists: bool = False, kind: str | None = None) -> exp.Drop | exp.Command:
 2478        start = self._prev
 2479
 2480        if not kind:
 2481            temporary = self._match(TokenType.TEMPORARY)
 2482            materialized = self._match_text_seq("MATERIALIZED")
 2483            iceberg = self._match_text_seq("ICEBERG")
 2484            kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None
 2485        else:
 2486            temporary = False
 2487            materialized = False
 2488            iceberg = False
 2489
 2490        if not kind or (iceberg and kind != "TABLE"):
 2491            return self._parse_as_command(start)
 2492
 2493        concurrently = (
 2494            kind == "INDEX"
 2495            and self._next.token_type in self.ID_VAR_TOKENS
 2496            and self._match_text_seq("CONCURRENTLY")
 2497        )
 2498
 2499        if_exists = exists or self._parse_exists()
 2500
 2501        tables: exp.Expr | list[exp.Expr] | None
 2502        if kind == "COLUMN":
 2503            # A dropped column named after a keyword, like `any` or `charset`, is only a name
 2504            name = (
 2505                None
 2506                if self._curr.text.upper() in self.NO_PAREN_FUNCTION_PARSERS
 2507                else self._parse_column()
 2508            ) or self._parse_id_var()
 2509            tables = (
 2510                self.expression(exp.Column(this=name)) if isinstance(name, exp.Identifier) else name
 2511            )
 2512        elif kind in ("TABLE", "VIEW"):
 2513            tables = self._parse_csv(lambda: self._parse_table_parts(schema=True))
 2514        else:
 2515            tables = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA")
 2516
 2517        cluster: exp.Expr | None = None
 2518        if self._match(TokenType.ON):
 2519            if kind in ("INDEX", "TRIGGER"):
 2520                cluster = self.expression(exp.OnProperty(this=self._parse_table_parts()))
 2521            else:
 2522                cluster = self._parse_on_property()
 2523
 2524        if self._match(TokenType.L_PAREN, advance=False):
 2525            expressions = self._parse_wrapped_csv(self._parse_types)
 2526        else:
 2527            expressions = None
 2528
 2529        cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper()
 2530
 2531        return self.expression(
 2532            exp.Drop(
 2533                exists=if_exists,
 2534                tables=ensure_list(tables),
 2535                expressions=expressions,
 2536                kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind,
 2537                temporary=temporary,
 2538                materialized=materialized,
 2539                cascade=cascade_or_restrict == "CASCADE",
 2540                restrict=cascade_or_restrict == "RESTRICT",
 2541                constraints=self._match_text_seq("CONSTRAINTS"),
 2542                purge=self._match_text_seq("PURGE"),
 2543                cluster=cluster,
 2544                concurrently=concurrently,
 2545                sync=self._match_text_seq("SYNC"),
 2546                iceberg=iceberg,
 2547                force=self._match_text_seq("FORCE"),
 2548            )
 2549        )
 2550
 2551    def _parse_exists(self, not_: bool = False) -> bool | None:
 2552        if not_:
 2553            return self._match_text_seq("IF", "NOT", "EXISTS")
 2554        return self._match_text_seq("IF", "EXISTS")
 2555
 2556    def _parse_create(self) -> exp.Create | exp.Command:
 2557        # Note: this can't be None because we've matched a statement parser
 2558        start = self._prev
 2559
 2560        replace = (
 2561            start.token_type == TokenType.REPLACE
 2562            or self._match_pair(TokenType.OR, TokenType.REPLACE)
 2563            or self._match_pair(TokenType.OR, TokenType.ALTER)
 2564        )
 2565        refresh = self._match_pair(TokenType.OR, TokenType.REFRESH)
 2566
 2567        unique = self._match(TokenType.UNIQUE)
 2568
 2569        if self._match_text_seq("CLUSTERED", "COLUMNSTORE"):
 2570            clustered = True
 2571        elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq(
 2572            "COLUMNSTORE"
 2573        ):
 2574            clustered = False
 2575        else:
 2576            clustered = None
 2577
 2578        if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False):
 2579            self._advance()
 2580
 2581        properties = None
 2582        create_token = self._match_set(self.CREATABLES) and self._prev
 2583
 2584        if not create_token:
 2585            # exp.Properties.Location.POST_CREATE
 2586            properties = self._parse_properties()
 2587            create_token = self._match_set(self.CREATABLES) and self._prev
 2588
 2589            if not properties or not create_token:
 2590                return self._parse_as_command(start)
 2591
 2592        create_token_type = t.cast(Token, create_token).token_type
 2593
 2594        concurrently = create_token_type == TokenType.INDEX and self._match_text_seq("CONCURRENTLY")
 2595        exists = self._parse_exists(not_=True)
 2596        this = None
 2597        expression: exp.Expr | None = None
 2598        indexes = None
 2599        no_schema_binding = None
 2600        begin = None
 2601        clone = None
 2602
 2603        def extend_props(temp_props: exp.Properties | None) -> None:
 2604            nonlocal properties
 2605            if properties and temp_props:
 2606                properties.expressions.extend(temp_props.expressions)
 2607            elif temp_props:
 2608                properties = temp_props
 2609
 2610        if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2611            this = self._parse_user_defined_function(kind=create_token_type)
 2612
 2613            # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature)
 2614            extend_props(self._parse_properties())
 2615
 2616            expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None
 2617
 2618            if (
 2619                not expression
 2620                and create_token_type == TokenType.FUNCTION
 2621                and isinstance(this, exp.UserDefinedFunction)
 2622                and this.args.get("wrapped")
 2623            ):
 2624                pre_table_index = self._index
 2625                is_table = self._match(TokenType.TABLE)
 2626
 2627                expression = self._parse_expression()
 2628                overload_mode = bool(
 2629                    expression
 2630                    and self._curr.token_type == TokenType.COMMA
 2631                    and self._next.token_type == TokenType.L_PAREN
 2632                )
 2633                if not overload_mode:
 2634                    self._retreat(pre_table_index)
 2635                    is_table = False
 2636                    expression = None
 2637            else:
 2638                is_table = False
 2639                overload_mode = False
 2640
 2641            extend_props(self._parse_function_properties())
 2642
 2643            if not expression:
 2644                if self._match(TokenType.COMMAND):
 2645                    expression = self._parse_as_command(self._prev)
 2646                else:
 2647                    begin = self._match(TokenType.BEGIN)
 2648                    return_ = self._match_text_seq("RETURN")
 2649
 2650                    if self._match(TokenType.STRING, advance=False):
 2651                        # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property
 2652                        # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement
 2653                        expression = self._parse_string()
 2654                        extend_props(self._parse_properties())
 2655                    else:
 2656                        expression = (
 2657                            self._parse_user_defined_function_expression()
 2658                            if create_token_type == TokenType.FUNCTION
 2659                            else self._parse_block()
 2660                        )
 2661
 2662                    if return_:
 2663                        expression = self.expression(exp.Return(this=expression))
 2664
 2665            if overload_mode and expression:
 2666                expression = self._parse_macro_overloads(
 2667                    t.cast(exp.UserDefinedFunction, this), expression, is_table
 2668                )
 2669        elif create_token_type == TokenType.INDEX:
 2670            # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c)
 2671            if not self._match(TokenType.ON):
 2672                index = self._parse_id_var()
 2673                anonymous = False
 2674            else:
 2675                index = None
 2676                anonymous = True
 2677
 2678            this = self._parse_index(index=index, anonymous=anonymous)
 2679        elif (
 2680            create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER)
 2681        ) or create_token_type == TokenType.TRIGGER:
 2682            if is_constraint := (create_token_type == TokenType.CONSTRAINT):
 2683                create_token = self._prev
 2684
 2685            trigger_name = self._parse_id_var()
 2686            if not trigger_name:
 2687                return self._parse_as_command(start)
 2688
 2689            timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False)
 2690            timing = timing_var.this if timing_var else None
 2691            if not timing:
 2692                return self._parse_as_command(start)
 2693
 2694            events = self._parse_trigger_events()
 2695            if not self._match(TokenType.ON):
 2696                self.raise_error("Expected ON in trigger definition")
 2697
 2698            table = self._parse_table_parts()
 2699            referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None
 2700            deferrable, initially = self._parse_trigger_deferrable()
 2701            referencing = self._parse_trigger_referencing()
 2702            for_each = self._parse_trigger_for_each()
 2703            when = self._match_text_seq("WHEN") and self._parse_wrapped(
 2704                self._parse_disjunction, optional=True
 2705            )
 2706            execute = self._parse_trigger_execute()
 2707
 2708            if execute is None:
 2709                return self._parse_as_command(start)
 2710
 2711            trigger_props = self.expression(
 2712                exp.TriggerProperties(
 2713                    table=table,
 2714                    timing=timing,
 2715                    events=events,
 2716                    execute=execute,
 2717                    constraint=is_constraint,
 2718                    referenced_table=referenced_table,
 2719                    deferrable=deferrable,
 2720                    initially=initially,
 2721                    referencing=referencing,
 2722                    for_each=for_each,
 2723                    when=when,
 2724                )
 2725            )
 2726
 2727            this = trigger_name
 2728            extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else []))
 2729        elif create_token_type == TokenType.TYPE:
 2730            this = self._parse_table_parts(schema=True)
 2731            if not this or not self._match(TokenType.ALIAS):
 2732                return self._parse_as_command(start)
 2733
 2734            if self._match(TokenType.ENUM):
 2735                expression = exp.DataType(
 2736                    this=exp.DType.ENUM,
 2737                    expressions=self._parse_wrapped_csv(self._parse_string),
 2738                )
 2739            elif self._match(TokenType.L_PAREN, advance=False):
 2740                expression = self._parse_schema()
 2741            else:
 2742                return self._parse_as_command(start)
 2743        elif create_token_type in self.DB_CREATABLES:
 2744            table_parts = self._parse_table_parts(
 2745                schema=True, is_db_reference=create_token_type == TokenType.SCHEMA
 2746            )
 2747
 2748            # exp.Properties.Location.POST_NAME
 2749            self._match(TokenType.COMMA)
 2750            extend_props(self._parse_properties(before=True))
 2751
 2752            this = self._parse_schema(this=table_parts)
 2753
 2754            # exp.Properties.Location.POST_SCHEMA and POST_WITH
 2755            extend_props(self._parse_properties())
 2756
 2757            has_alias = self._match(TokenType.ALIAS)
 2758            if not self._match_set(self.DDL_SELECT_TOKENS, advance=False):
 2759                # exp.Properties.Location.POST_ALIAS
 2760                extend_props(self._parse_properties())
 2761
 2762            if create_token_type == TokenType.SEQUENCE:
 2763                expression = self._parse_types()
 2764                props = self._parse_properties()
 2765                if props:
 2766                    sequence_props = exp.SequenceProperties()
 2767                    options = []
 2768                    for prop in props:
 2769                        if isinstance(prop, exp.SequenceProperties):
 2770                            for arg, value in prop.args.items():
 2771                                if arg == "options":
 2772                                    options.extend(value)
 2773                                else:
 2774                                    sequence_props.set(arg, value)
 2775                            prop.pop()
 2776
 2777                    if options:
 2778                        sequence_props.set("options", options)
 2779
 2780                    props.append("expressions", sequence_props)
 2781                    extend_props(props)
 2782            else:
 2783                expression = self._parse_ddl_select()
 2784
 2785                # Some dialects also support using a table as an alias instead of a SELECT.
 2786                # Here we fallback to this as an alternative.
 2787                if not expression and has_alias:
 2788                    expression = self._try_parse(self._parse_table_parts)
 2789
 2790            if create_token_type == TokenType.TABLE:
 2791                # exp.Properties.Location.POST_EXPRESSION
 2792                extend_props(self._parse_properties())
 2793
 2794                indexes = []
 2795                while True:
 2796                    index = self._parse_index()
 2797
 2798                    # exp.Properties.Location.POST_INDEX
 2799                    extend_props(self._parse_properties())
 2800                    if not index:
 2801                        break
 2802                    else:
 2803                        self._match(TokenType.COMMA)
 2804                        indexes.append(index)
 2805            elif create_token_type == TokenType.VIEW:
 2806                if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"):
 2807                    no_schema_binding = True
 2808            elif create_token_type in (TokenType.SINK, TokenType.SOURCE):
 2809                extend_props(self._parse_properties())
 2810
 2811            shallow = self._match_text_seq("SHALLOW")
 2812
 2813            if self._match_texts(self.CLONE_KEYWORDS):
 2814                copy = self._prev.text.lower() == "copy"
 2815                clone = self.expression(
 2816                    exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy)
 2817                )
 2818
 2819        if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False):
 2820            return self._parse_as_command(start)
 2821
 2822        create_kind_text = create_token.text.upper()
 2823        return self.expression(
 2824            exp.Create(
 2825                this=this,
 2826                kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text,
 2827                replace=replace,
 2828                refresh=refresh,
 2829                unique=unique,
 2830                expression=expression,
 2831                exists=exists,
 2832                properties=properties,
 2833                indexes=indexes,
 2834                no_schema_binding=no_schema_binding,
 2835                begin=begin,
 2836                clone=clone,
 2837                concurrently=concurrently,
 2838                clustered=clustered,
 2839            )
 2840        )
 2841
 2842    def _parse_sequence_properties(self) -> exp.SequenceProperties | None:
 2843        seq = exp.SequenceProperties()
 2844
 2845        options = []
 2846        index = self._index
 2847
 2848        while self._curr:
 2849            self._match(TokenType.COMMA)
 2850            if self._match_text_seq("INCREMENT"):
 2851                self._match_text_seq("BY")
 2852                self._match_text_seq("=")
 2853                seq.set("increment", self._parse_term())
 2854            elif self._match_text_seq("MINVALUE"):
 2855                seq.set("minvalue", self._parse_term())
 2856            elif self._match_text_seq("MAXVALUE"):
 2857                seq.set("maxvalue", self._parse_term())
 2858            elif self._match_text_seq("START"):
 2859                self._match_text_seq("WITH")
 2860                self._match_text_seq("=")
 2861                seq.set("start", self._parse_term())
 2862            elif self._match_text_seq("CACHE"):
 2863                # T-SQL allows empty CACHE which is initialized dynamically
 2864                seq.set("cache", self._parse_number() or True)
 2865            elif self._match_text_seq("OWNED", "BY"):
 2866                # "OWNED BY NONE" is the default
 2867                seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column())
 2868            else:
 2869                opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False)
 2870                if opt:
 2871                    options.append(opt)
 2872                else:
 2873                    break
 2874
 2875        seq.set("options", options if options else None)
 2876        return None if self._index == index else seq
 2877
 2878    def _parse_trigger_events(self) -> list[exp.TriggerEvent]:
 2879        events = []
 2880
 2881        while True:
 2882            event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper()
 2883
 2884            if not event_type:
 2885                self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)")
 2886
 2887            columns = (
 2888                self._parse_csv(self._parse_column)
 2889                if event_type == "UPDATE" and self._match_text_seq("OF")
 2890                else None
 2891            )
 2892
 2893            events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns)))
 2894
 2895            if not self._match(TokenType.OR):
 2896                break
 2897
 2898        return events
 2899
 2900    def _parse_trigger_deferrable(
 2901        self,
 2902    ) -> tuple[str | None, str | None]:
 2903        deferrable_var = self._parse_var_from_options(
 2904            self.TRIGGER_DEFERRABLE, raise_unmatched=False
 2905        )
 2906        deferrable = deferrable_var.this if deferrable_var else None
 2907
 2908        initially = None
 2909        if deferrable and self._match_text_seq("INITIALLY"):
 2910            initially = (
 2911                self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None
 2912            )
 2913
 2914        return deferrable, initially
 2915
 2916    def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None:
 2917        if not self._match_text_seq(keyword):
 2918            return None
 2919        if not self._match_text_seq("TABLE"):
 2920            self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause")
 2921        self._match_text_seq("AS")
 2922        return self._parse_id_var()
 2923
 2924    def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None:
 2925        if not self._match_text_seq("REFERENCING"):
 2926            return None
 2927
 2928        old_alias = None
 2929        new_alias = None
 2930
 2931        while True:
 2932            if alias := self._parse_trigger_referencing_clause("OLD"):
 2933                if old_alias is not None:
 2934                    self.raise_error("Duplicate OLD clause in REFERENCING")
 2935                old_alias = alias
 2936            elif alias := self._parse_trigger_referencing_clause("NEW"):
 2937                if new_alias is not None:
 2938                    self.raise_error("Duplicate NEW clause in REFERENCING")
 2939                new_alias = alias
 2940            else:
 2941                break
 2942
 2943        if old_alias is None and new_alias is None:
 2944            self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE")
 2945
 2946        return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias))
 2947
 2948    def _parse_trigger_for_each(self) -> str | None:
 2949        if not self._match_text_seq("FOR", "EACH"):
 2950            return None
 2951
 2952        return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None
 2953
 2954    def _parse_trigger_execute(self) -> exp.Expr | None:
 2955        if not self._match(TokenType.EXECUTE):
 2956            return None
 2957
 2958        if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)):
 2959            self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE")
 2960
 2961        func_call = self._parse_column()
 2962        return self.expression(exp.TriggerExecute(this=func_call))
 2963
 2964    def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None:
 2965        # only used for teradata currently
 2966        self._match(TokenType.COMMA)
 2967
 2968        kwargs = {
 2969            "no": self._match_text_seq("NO"),
 2970            "dual": self._match_text_seq("DUAL"),
 2971            "before": self._match_text_seq("BEFORE"),
 2972            "default": self._match_text_seq("DEFAULT"),
 2973            "local": (self._match_text_seq("LOCAL") and "LOCAL")
 2974            or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"),
 2975            "after": self._match_text_seq("AFTER"),
 2976            "minimum": self._match_texts(("MIN", "MINIMUM")),
 2977            "maximum": self._match_texts(("MAX", "MAXIMUM")),
 2978        }
 2979
 2980        if self._match_texts(self.PROPERTY_PARSERS):
 2981            parser = self.PROPERTY_PARSERS[self._prev.text.upper()]
 2982            try:
 2983                return parser(self, **{k: v for k, v in kwargs.items() if v})
 2984            except TypeError:
 2985                self.raise_error(f"Cannot parse property '{self._prev.text}'")
 2986
 2987        if self._match_pair(TokenType.CHAR, TokenType.SET):
 2988            return self._parse_character_set(default=bool(kwargs["default"]))
 2989
 2990        return None
 2991
 2992    def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]:
 2993        return self._parse_wrapped_csv(self._parse_property)
 2994
 2995    def _parse_property(self) -> exp.Expr | list[exp.Expr] | None:
 2996        if self._match_texts(self.PROPERTY_PARSERS):
 2997            return self.PROPERTY_PARSERS[self._prev.text.upper()](self)
 2998
 2999        if self._match_pair(TokenType.CHAR, TokenType.SET):
 3000            return self._parse_character_set()
 3001
 3002        if self._match(TokenType.DEFAULT):
 3003            if self._match_texts(self.PROPERTY_PARSERS):
 3004                return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True)
 3005
 3006            if self._match_pair(TokenType.CHAR, TokenType.SET):
 3007                return self._parse_character_set(default=True)
 3008
 3009        if self._match_text_seq("COMPOUND", "SORTKEY"):
 3010            return self._parse_sortkey(compound=True)
 3011
 3012        if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"):
 3013            return self.expression(exp.ParameterStyleProperty(this="PANDAS"))
 3014
 3015        if self._match_text_seq("NOT", "DETERMINISTIC"):
 3016            return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 3017
 3018        index = self._index
 3019
 3020        seq_props = self._parse_sequence_properties()
 3021        if seq_props:
 3022            return seq_props
 3023
 3024        self._retreat(index)
 3025        return self._parse_key_value_property()
 3026
 3027    def _parse_key_value_property(
 3028        self, parse_value: t.Callable[[], exp.Expr | None] | None = None
 3029    ) -> exp.Property | None:
 3030        index = self._index
 3031        key = self._parse_column()
 3032
 3033        if not self._match(TokenType.EQ):
 3034            self._retreat(index)
 3035            return None
 3036
 3037        # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise
 3038        if isinstance(key, exp.Column):
 3039            key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name)
 3040
 3041        value = (
 3042            parse_value()
 3043            if parse_value
 3044            else self._parse_bitwise() or self._parse_var(any_token=True)
 3045        )
 3046
 3047        # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier())
 3048        if isinstance(value, exp.Column):
 3049            value = exp.var(value.name)
 3050
 3051        return self.expression(exp.Property(this=key, value=value))
 3052
 3053    def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty:
 3054        if self._match_text_seq("BY"):
 3055            return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string()))
 3056
 3057        self._match(TokenType.ALIAS)
 3058        input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None
 3059        output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None
 3060
 3061        return self.expression(
 3062            exp.FileFormatProperty(
 3063                this=(
 3064                    self.expression(
 3065                        exp.InputOutputFormat(
 3066                            input_format=input_format, output_format=output_format
 3067                        )
 3068                    )
 3069                    if input_format or output_format
 3070                    else self._parse_var_or_string() or self._parse_number() or self._parse_id_var()
 3071                ),
 3072                hive_format=True,
 3073            )
 3074        )
 3075
 3076    def _parse_unquoted_field(self) -> exp.Expr | None:
 3077        field = self._parse_field()
 3078        if isinstance(field, exp.Identifier) and not field.quoted:
 3079            field = exp.var(field)
 3080
 3081        return field
 3082
 3083    def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E:
 3084        self._match(TokenType.EQ)
 3085        self._match(TokenType.ALIAS)
 3086
 3087        return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs))
 3088
 3089    def _parse_properties(self, before: bool | None = None) -> exp.Properties | None:
 3090        properties = []
 3091        while True:
 3092            if before:
 3093                prop = self._parse_property_before()
 3094            else:
 3095                prop = self._parse_property()
 3096            if not prop:
 3097                break
 3098            for p in ensure_list(prop):
 3099                properties.append(p)
 3100
 3101            self._match(TokenType.COMMA, expression=properties[-1])
 3102
 3103        if properties:
 3104            return self.expression(exp.Properties(expressions=properties))
 3105
 3106        return None
 3107
 3108    def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty:
 3109        return self.expression(
 3110            exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION"))
 3111        )
 3112
 3113    def _parse_sql_security(self) -> exp.SqlSecurityProperty:
 3114        return self.expression(
 3115            exp.SqlSecurityProperty(
 3116                this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper()
 3117            )
 3118        )
 3119
 3120    def _parse_settings_property(self) -> exp.SettingsProperty:
 3121        return self.expression(
 3122            exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment))
 3123        )
 3124
 3125    def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None:
 3126        if not self._match_text_seq("ON", "NULL", "INPUT"):
 3127            self._retreat(self._index - 1)
 3128            return None
 3129
 3130        return self.expression(exp.CalledOnNullInputProperty())
 3131
 3132    def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty:
 3133        if self._index >= 2:
 3134            pre_volatile_token = self._tokens[self._index - 2]
 3135        else:
 3136            pre_volatile_token = None
 3137
 3138        if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS:
 3139            return exp.VolatileProperty()
 3140
 3141        return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 3142
 3143    def _parse_retention_period(self) -> exp.Var:
 3144        # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...}
 3145        number = self._parse_number()
 3146        number_str = f"{number} " if number else ""
 3147        unit = self._parse_var(any_token=True)
 3148        return exp.var(f"{number_str}{unit}")
 3149
 3150    def _parse_system_versioning_property(
 3151        self, with_: bool = False
 3152    ) -> exp.WithSystemVersioningProperty:
 3153        self._match(TokenType.EQ)
 3154        prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_))
 3155
 3156        if self._match_text_seq("OFF"):
 3157            prop.set("on", False)
 3158            return prop
 3159
 3160        self._match(TokenType.ON)
 3161        if self._match(TokenType.L_PAREN):
 3162            while self._curr and not self._match(TokenType.R_PAREN):
 3163                if self._match_text_seq("HISTORY_TABLE", "="):
 3164                    prop.set("this", self._parse_table_parts())
 3165                elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="):
 3166                    prop.set("data_consistency", self._advance_any() and self._prev.text.upper())
 3167                elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="):
 3168                    prop.set("retention_period", self._parse_retention_period())
 3169
 3170                self._match(TokenType.COMMA)
 3171
 3172        return prop
 3173
 3174    def _parse_data_deletion_property(self) -> exp.DataDeletionProperty:
 3175        self._match(TokenType.EQ)
 3176        on = self._match_text_seq("ON") or not self._match_text_seq("OFF")
 3177        prop = self.expression(exp.DataDeletionProperty(on=on))
 3178
 3179        if self._match(TokenType.L_PAREN):
 3180            while self._curr and not self._match(TokenType.R_PAREN):
 3181                if self._match_text_seq("FILTER_COLUMN", "="):
 3182                    prop.set("filter_column", self._parse_column())
 3183                elif self._match_text_seq("RETENTION_PERIOD", "="):
 3184                    prop.set("retention_period", self._parse_retention_period())
 3185
 3186                self._match(TokenType.COMMA)
 3187
 3188        return prop
 3189
 3190    def _parse_distributed_property(self) -> exp.DistributedByProperty:
 3191        kind = "HASH"
 3192        expressions: list[exp.Expr] | None = None
 3193        if self._match_text_seq("BY", "HASH"):
 3194            expressions = self._parse_wrapped_csv(self._parse_id_var)
 3195        elif self._match_text_seq("BY", "RANDOM"):
 3196            kind = "RANDOM"
 3197
 3198        # If the BUCKETS keyword is not present, the number of buckets is AUTO
 3199        buckets: exp.Expr | None = None
 3200        if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"):
 3201            buckets = self._parse_number()
 3202
 3203        return self.expression(
 3204            exp.DistributedByProperty(
 3205                expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order()
 3206            )
 3207        )
 3208
 3209    def _parse_composite_key_property(self, expr_type: type[E]) -> E:
 3210        self._match_text_seq("KEY")
 3211        expressions = self._parse_wrapped_id_vars()
 3212        return self.expression(expr_type(expressions=expressions))
 3213
 3214    def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]:
 3215        if self._match_text_seq("(", "SYSTEM_VERSIONING"):
 3216            prop = self._parse_system_versioning_property(with_=True)
 3217            self._match_r_paren()
 3218            return prop
 3219
 3220        if self._match(TokenType.L_PAREN, advance=False):
 3221            result: list[exp.Expr] = []
 3222            for i in self._parse_wrapped_properties():
 3223                result.extend(i) if isinstance(i, list) else result.append(i)
 3224            return result
 3225
 3226        if self._match_text_seq("JOURNAL"):
 3227            return self._parse_withjournaltable()
 3228
 3229        if self._match_texts(self.VIEW_ATTRIBUTES):
 3230            return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper()))
 3231
 3232        if self._match_text_seq("DATA"):
 3233            return self._parse_withdata(no=False)
 3234        elif self._match_text_seq("NO", "DATA"):
 3235            return self._parse_withdata(no=True)
 3236
 3237        if self._match(TokenType.SERDE_PROPERTIES, advance=False):
 3238            return self._parse_serde_properties(with_=True)
 3239
 3240        if self._match(TokenType.SCHEMA):
 3241            return self.expression(
 3242                exp.WithSchemaBindingProperty(
 3243                    this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS)
 3244                )
 3245            )
 3246
 3247        if self._match_texts(self.PROCEDURE_OPTIONS, advance=False):
 3248            return self.expression(
 3249                exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option))
 3250            )
 3251
 3252        if not self._next:
 3253            return None
 3254
 3255        return self._parse_withisolatedloading()
 3256
 3257    def _parse_procedure_option(self) -> exp.Expr | None:
 3258        if self._match_text_seq("EXECUTE", "AS"):
 3259            return self.expression(
 3260                exp.ExecuteAsProperty(
 3261                    this=self._parse_var_from_options(
 3262                        self.EXECUTE_AS_OPTIONS, raise_unmatched=False
 3263                    )
 3264                    or self._parse_string()
 3265                )
 3266            )
 3267
 3268        return self._parse_var_from_options(self.PROCEDURE_OPTIONS)
 3269
 3270    # https://dev.mysql.com/doc/refman/8.0/en/create-view.html
 3271    def _parse_definer(self) -> exp.DefinerProperty | None:
 3272        self._match(TokenType.EQ)
 3273
 3274        user = self._parse_id_var()
 3275        self._match(TokenType.PARAMETER)
 3276        host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text)
 3277
 3278        if not user or not host:
 3279            return None
 3280
 3281        return exp.DefinerProperty(this=f"{user}@{host}")
 3282
 3283    def _parse_withjournaltable(self) -> exp.WithJournalTableProperty:
 3284        self._match(TokenType.TABLE)
 3285        self._match(TokenType.EQ)
 3286        return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts()))
 3287
 3288    def _parse_log(self, no: bool = False) -> exp.LogProperty:
 3289        return self.expression(exp.LogProperty(no=no))
 3290
 3291    def _parse_journal(self, **kwargs) -> exp.JournalProperty:
 3292        return self.expression(exp.JournalProperty(**kwargs))
 3293
 3294    def _parse_checksum(self) -> exp.ChecksumProperty:
 3295        self._match(TokenType.EQ)
 3296
 3297        on = None
 3298        if self._match(TokenType.ON):
 3299            on = True
 3300        elif self._match_text_seq("OFF"):
 3301            on = False
 3302
 3303        return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT)))
 3304
 3305    def _parse_cluster(self) -> exp.Cluster:
 3306        self._match(TokenType.CLUSTER_BY)
 3307        return self.expression(
 3308            exp.Cluster(
 3309                expressions=self._parse_csv(self._parse_column),
 3310            )
 3311        )
 3312
 3313    def _parse_cluster_property(self) -> exp.ClusterProperty:
 3314        return self.expression(
 3315            exp.ClusterProperty(
 3316                expressions=self._parse_wrapped_csv(self._parse_column),
 3317            )
 3318        )
 3319
 3320    def _parse_clustered_by(self) -> exp.ClusteredByProperty:
 3321        self._match_text_seq("BY")
 3322
 3323        self._match_l_paren()
 3324        expressions = self._parse_csv(self._parse_column)
 3325        self._match_r_paren()
 3326
 3327        if self._match_text_seq("SORTED", "BY"):
 3328            self._match_l_paren()
 3329            sorted_by = self._parse_csv(self._parse_ordered)
 3330            self._match_r_paren()
 3331        else:
 3332            sorted_by = None
 3333
 3334        self._match(TokenType.INTO)
 3335        buckets = self._parse_number()
 3336        self._match_text_seq("BUCKETS")
 3337
 3338        return self.expression(
 3339            exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets)
 3340        )
 3341
 3342    def _parse_copy_property(self) -> exp.Property | None:
 3343        if not self._match_text_seq("GRANTS"):
 3344            self._retreat(self._index - 1)
 3345            return None
 3346
 3347        return self.expression(exp.CopyGrantsProperty())
 3348
 3349    def _parse_freespace(self) -> exp.FreespaceProperty:
 3350        self._match(TokenType.EQ)
 3351        return self.expression(
 3352            exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT))
 3353        )
 3354
 3355    def _parse_mergeblockratio(
 3356        self, no: bool = False, default: bool = False
 3357    ) -> exp.MergeBlockRatioProperty:
 3358        if self._match(TokenType.EQ):
 3359            return self.expression(
 3360                exp.MergeBlockRatioProperty(
 3361                    this=self._parse_number(), percent=self._match(TokenType.PERCENT)
 3362                )
 3363            )
 3364
 3365        return self.expression(exp.MergeBlockRatioProperty(no=no, default=default))
 3366
 3367    def _parse_datablocksize(
 3368        self,
 3369        default: bool | None = None,
 3370        minimum: bool | None = None,
 3371        maximum: bool | None = None,
 3372    ) -> exp.DataBlocksizeProperty:
 3373        self._match(TokenType.EQ)
 3374        size = self._parse_number()
 3375
 3376        units = None
 3377        if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")):
 3378            units = self._prev.text
 3379
 3380        return self.expression(
 3381            exp.DataBlocksizeProperty(
 3382                size=size, units=units, default=default, minimum=minimum, maximum=maximum
 3383            )
 3384        )
 3385
 3386    def _parse_blockcompression(self) -> exp.BlockCompressionProperty:
 3387        self._match(TokenType.EQ)
 3388        always = self._match_text_seq("ALWAYS")
 3389        manual = self._match_text_seq("MANUAL")
 3390        never = self._match_text_seq("NEVER")
 3391        default = self._match_text_seq("DEFAULT")
 3392
 3393        autotemp = None
 3394        if self._match_text_seq("AUTOTEMP"):
 3395            autotemp = self._parse_schema()
 3396
 3397        return self.expression(
 3398            exp.BlockCompressionProperty(
 3399                always=always, manual=manual, never=never, default=default, autotemp=autotemp
 3400            )
 3401        )
 3402
 3403    def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None:
 3404        index = self._index
 3405        no = self._match_text_seq("NO")
 3406        concurrent = self._match_text_seq("CONCURRENT")
 3407
 3408        if not self._match_text_seq("ISOLATED", "LOADING"):
 3409            self._retreat(index)
 3410            return None
 3411
 3412        target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False)
 3413        return self.expression(
 3414            exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target)
 3415        )
 3416
 3417    def _parse_locking(self) -> exp.LockingProperty:
 3418        if self._match(TokenType.TABLE):
 3419            kind = "TABLE"
 3420        elif self._match(TokenType.VIEW):
 3421            kind = "VIEW"
 3422        elif self._match(TokenType.ROW):
 3423            kind = "ROW"
 3424        elif self._match_text_seq("DATABASE"):
 3425            kind = "DATABASE"
 3426        else:
 3427            kind = None
 3428
 3429        if kind in ("DATABASE", "TABLE", "VIEW"):
 3430            this = self._parse_table_parts()
 3431        else:
 3432            this = None
 3433
 3434        if self._match(TokenType.FOR):
 3435            for_or_in = "FOR"
 3436        elif self._match(TokenType.IN):
 3437            for_or_in = "IN"
 3438        else:
 3439            for_or_in = None
 3440
 3441        if self._match_text_seq("ACCESS"):
 3442            lock_type = "ACCESS"
 3443        elif self._match_texts(("EXCL", "EXCLUSIVE")):
 3444            lock_type = "EXCLUSIVE"
 3445        elif self._match_text_seq("SHARE"):
 3446            lock_type = "SHARE"
 3447        elif self._match_text_seq("READ"):
 3448            lock_type = "READ"
 3449        elif self._match_text_seq("WRITE"):
 3450            lock_type = "WRITE"
 3451        elif self._match_text_seq("CHECKSUM"):
 3452            lock_type = "CHECKSUM"
 3453        else:
 3454            lock_type = None
 3455
 3456        override = self._match_text_seq("OVERRIDE")
 3457
 3458        return self.expression(
 3459            exp.LockingProperty(
 3460                this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override
 3461            )
 3462        )
 3463
 3464    def _parse_partition_by(self) -> list[exp.Expr]:
 3465        if not self._match(TokenType.PARTITION_BY):
 3466            return []
 3467
 3468        if self._next.token_type in self.ID_VAR_TOKENS and self._match_text_seq("EXCLUDING"):
 3469            return [
 3470                self.expression(
 3471                    exp.PartitionByExcluding(expressions=self._parse_csv(self._parse_disjunction))
 3472                )
 3473            ]
 3474
 3475        return self._parse_csv(self._parse_disjunction)
 3476
 3477    def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec:
 3478        def _parse_partition_bound_expr() -> exp.Expr | None:
 3479            if self._match_text_seq("MINVALUE"):
 3480                return exp.var("MINVALUE")
 3481            if self._match_text_seq("MAXVALUE"):
 3482                return exp.var("MAXVALUE")
 3483            return self._parse_bitwise()
 3484
 3485        this: exp.Expr | list[exp.Expr] | None = None
 3486        expression = None
 3487        from_expressions = None
 3488        to_expressions = None
 3489
 3490        if self._match(TokenType.IN):
 3491            this = self._parse_wrapped_csv(self._parse_bitwise)
 3492        elif self._match(TokenType.FROM):
 3493            from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3494            self._match_text_seq("TO")
 3495            to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3496        elif self._match_text_seq("WITH", "(", "MODULUS"):
 3497            this = self._parse_number()
 3498            self._match_text_seq(",", "REMAINDER")
 3499            expression = self._parse_number()
 3500            self._match_r_paren()
 3501        else:
 3502            self.raise_error("Failed to parse partition bound spec.")
 3503
 3504        return self.expression(
 3505            exp.PartitionBoundSpec(
 3506                this=this,
 3507                expression=expression,
 3508                from_expressions=from_expressions,
 3509                to_expressions=to_expressions,
 3510            )
 3511        )
 3512
 3513    # https://www.postgresql.org/docs/current/sql-createtable.html
 3514    def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None:
 3515        if not self._match_text_seq("OF"):
 3516            self._retreat(self._index - 1)
 3517            return None
 3518
 3519        this = self._parse_table(schema=True)
 3520
 3521        if self._match(TokenType.DEFAULT):
 3522            expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT")
 3523        elif self._match_text_seq("FOR", "VALUES"):
 3524            expression = self._parse_partition_bound_spec()
 3525        else:
 3526            self.raise_error("Expecting either DEFAULT or FOR VALUES clause.")
 3527
 3528        return self.expression(exp.PartitionedOfProperty(this=this, expression=expression))
 3529
 3530    def _parse_partitioned_by(self) -> exp.PartitionedByProperty:
 3531        self._match(TokenType.EQ)
 3532        return self.expression(
 3533            exp.PartitionedByProperty(
 3534                this=self._parse_schema() or self._parse_bracket(self._parse_field())
 3535            )
 3536        )
 3537
 3538    def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty:
 3539        if self._match_text_seq("AND", "STATISTICS"):
 3540            statistics = True
 3541        elif self._match_text_seq("AND", "NO", "STATISTICS"):
 3542            statistics = False
 3543        else:
 3544            statistics = None
 3545
 3546        return self.expression(exp.WithDataProperty(no=no, statistics=statistics))
 3547
 3548    def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None:
 3549        if self._match_text_seq("SQL"):
 3550            return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL"))
 3551        return None
 3552
 3553    def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None:
 3554        if self._match_text_seq("SQL", "DATA"):
 3555            return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA"))
 3556        return None
 3557
 3558    def _parse_no_property(self) -> exp.Expr | None:
 3559        if self._match_text_seq("PRIMARY", "INDEX"):
 3560            return exp.NoPrimaryIndexProperty()
 3561        if self._match_text_seq("SQL"):
 3562            return self.expression(exp.SqlReadWriteProperty(this="NO SQL"))
 3563        return None
 3564
 3565    def _parse_on_property(self) -> exp.Expr | None:
 3566        if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"):
 3567            return exp.OnCommitProperty()
 3568        if self._match_text_seq("COMMIT", "DELETE", "ROWS"):
 3569            return exp.OnCommitProperty(delete=True)
 3570        return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var())))
 3571
 3572    def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None:
 3573        if self._match_text_seq("SQL", "DATA"):
 3574            return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA"))
 3575        return None
 3576
 3577    def _parse_distkey(self) -> exp.DistKeyProperty:
 3578        return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var)))
 3579
 3580    def _parse_create_like(self) -> exp.LikeProperty | None:
 3581        table = self._parse_table(schema=True)
 3582
 3583        options = []
 3584        while self._match_texts(("INCLUDING", "EXCLUDING")):
 3585            this = self._prev.text.upper()
 3586
 3587            id_var = self._parse_id_var()
 3588            if not id_var:
 3589                return None
 3590
 3591            options.append(
 3592                self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper())))
 3593            )
 3594
 3595        return self.expression(exp.LikeProperty(this=table, expressions=options))
 3596
 3597    def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty:
 3598        return self.expression(
 3599            exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound)
 3600        )
 3601
 3602    def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty:
 3603        self._match(TokenType.EQ)
 3604        return self.expression(
 3605            exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default)
 3606        )
 3607
 3608    def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty:
 3609        self._match_text_seq("WITH", "CONNECTION")
 3610        return self.expression(
 3611            exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts())
 3612        )
 3613
 3614    def _parse_returns(self) -> exp.ReturnsProperty:
 3615        value: exp.Expr | None
 3616        null = None
 3617        is_table = self._match(TokenType.TABLE)
 3618
 3619        if is_table:
 3620            if self._match(TokenType.LT):
 3621                value = self.expression(
 3622                    exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types))
 3623                )
 3624                if not self._match(TokenType.GT):
 3625                    self.raise_error("Expecting >")
 3626            else:
 3627                value = self._parse_schema(exp.var("TABLE"))
 3628        elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"):
 3629            null = True
 3630            value = None
 3631        else:
 3632            value = self._parse_types()
 3633
 3634        return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null))
 3635
 3636    def _parse_describe(self) -> exp.Describe:
 3637        kind = self._prev.text if self._match_set(self.CREATABLES) else None
 3638        style: str | None = (
 3639            self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None
 3640        )
 3641        if self._match(TokenType.DOT):
 3642            style = None
 3643            self._retreat(self._index - 2)
 3644
 3645        format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None
 3646
 3647        if self._match_set(self.STATEMENT_PARSERS, advance=False):
 3648            this = self._parse_statement()
 3649        else:
 3650            this = self._parse_table(schema=True)
 3651
 3652        properties = self._parse_properties()
 3653        expressions = properties.expressions if properties else None
 3654        partition = self._parse_partition()
 3655        return self.expression(
 3656            exp.Describe(
 3657                this=this,
 3658                style=style,
 3659                kind=kind,
 3660                expressions=expressions,
 3661                partition=partition,
 3662                format=format,
 3663                as_json=self._match_text_seq("AS", "JSON"),
 3664            )
 3665        )
 3666
 3667    def _parse_multitable_inserts(
 3668        self, comments: list[str] | None, kind: str | None = None
 3669    ) -> exp.MultitableInserts:
 3670        kind = kind or self._prev.text.upper()
 3671        expressions = []
 3672
 3673        def parse_conditional_insert() -> exp.ConditionalInsert | None:
 3674            if self._match(TokenType.WHEN):
 3675                expression = self._parse_disjunction()
 3676                self._match(TokenType.THEN)
 3677            else:
 3678                expression = None
 3679
 3680            else_ = self._match(TokenType.ELSE)
 3681
 3682            if not self._match(TokenType.INTO):
 3683                return None
 3684
 3685            return self.expression(
 3686                exp.ConditionalInsert(
 3687                    this=self.expression(
 3688                        exp.Insert(
 3689                            this=self._parse_table(schema=True),
 3690                            expression=self._parse_derived_table_values(),
 3691                        )
 3692                    ),
 3693                    expression=expression,
 3694                    else_=else_,
 3695                )
 3696            )
 3697
 3698        expression = parse_conditional_insert()
 3699        while expression is not None:
 3700            expressions.append(expression)
 3701            expression = parse_conditional_insert()
 3702
 3703        return self.expression(
 3704            exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()),
 3705            comments=comments,
 3706        )
 3707
 3708    def _parse_insert(self) -> exp.Insert | exp.MultitableInserts:
 3709        comments: list[str] = []
 3710        hint = self._parse_hint()
 3711        overwrite = self._match(TokenType.OVERWRITE)
 3712        ignore = self._match(TokenType.IGNORE)
 3713        local = self._match_text_seq("LOCAL")
 3714        alternative = None
 3715        is_function = None
 3716
 3717        if self._match_text_seq("DIRECTORY"):
 3718            this: exp.Expr | None = self.expression(
 3719                exp.Directory(
 3720                    this=self._parse_var_or_string(),
 3721                    local=local,
 3722                    row_format=self._parse_row_format(match_row=True),
 3723                )
 3724            )
 3725        else:
 3726            if self._match_set((TokenType.FIRST, TokenType.ALL)):
 3727                comments += ensure_list(self._prev_comments)
 3728                return self._parse_multitable_inserts(comments)
 3729
 3730            # Oracle's conditional form defaults to ALL when the keyword is omitted
 3731            if self._match(TokenType.WHEN, advance=False):
 3732                return self._parse_multitable_inserts(comments, kind="ALL")
 3733
 3734            if self._match(TokenType.OR):
 3735                alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text
 3736
 3737            self._match(TokenType.INTO)
 3738            comments += ensure_list(self._prev_comments)
 3739            self._match(TokenType.TABLE)
 3740            is_function = self._match(TokenType.FUNCTION)
 3741
 3742            this = self._parse_function() if is_function else self._parse_insert_table()
 3743
 3744        # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant
 3745        set_values = None
 3746        if self._match(TokenType.SET):
 3747            columns = []
 3748            values = []
 3749
 3750            def _parse_set_assignment() -> exp.Expr | None:
 3751                target = self._parse_column()
 3752                if isinstance(target, exp.Column) and self._match(TokenType.EQ):
 3753                    if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3754                        value: exp.Expr | None = exp.var(self._prev.text.upper())
 3755                    else:
 3756                        value = self._parse_disjunction()
 3757
 3758                    if value:
 3759                        columns.append(target.this)
 3760                        values.append(value)
 3761                        return value
 3762
 3763                self.raise_error("Expected column assignment in INSERT ... SET")
 3764                return None
 3765
 3766            self._parse_csv(_parse_set_assignment)
 3767
 3768            this = self.expression(exp.Schema(this=this, expressions=columns))
 3769            set_values = self.expression(
 3770                exp.Values(
 3771                    expressions=[exp.Tuple(expressions=values)],
 3772                    alias=self._parse_table_alias(),
 3773                )
 3774            )
 3775
 3776        returning = self._parse_returning()  # TSQL allows RETURNING before source
 3777
 3778        stored = self._match_text_seq("STORED") and self._parse_stored()
 3779        by_name = self._match_text_seq("BY", "NAME")
 3780        exists = self._parse_exists()
 3781        replace_where = None
 3782        replace_using = None
 3783
 3784        if self._match(TokenType.REPLACE):
 3785            if self._match(TokenType.WHERE):
 3786                replace_where = self._parse_disjunction()
 3787            elif self._match(TokenType.USING):
 3788                replace_using = self._parse_using_identifiers()
 3789
 3790        return self.expression(
 3791            exp.Insert(
 3792                hint=hint,
 3793                is_function=is_function,
 3794                this=this,
 3795                stored=stored,
 3796                by_name=by_name,
 3797                exists=exists,
 3798                where=replace_where,
 3799                using=replace_using,
 3800                partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(),
 3801                settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(),
 3802                default=self._match_text_seq("DEFAULT", "VALUES"),
 3803                expression=set_values
 3804                or self._parse_derived_table_values(allow_value_synonym=True)
 3805                or self._parse_ddl_select(),
 3806                conflict=self._parse_on_conflict(),
 3807                returning=returning or self._parse_returning(),
 3808                overwrite=overwrite,
 3809                alternative=alternative,
 3810                ignore=ignore,
 3811                source=self._match(TokenType.TABLE) and self._parse_table(),
 3812            ),
 3813            comments=comments,
 3814        )
 3815
 3816    def _parse_insert_table(self) -> exp.Expr | None:
 3817        this = self._parse_table(schema=True, parse_partition=True)
 3818        if isinstance(this, exp.Table) and self._match(TokenType.ALIAS):
 3819            # The column list after the alias belongs to the INSERT: INSERT INTO t AS s (a, b)
 3820            alias = self._parse_id_var()
 3821            if alias:
 3822                this.set(
 3823                    "alias",
 3824                    self.expression(exp.TableAlias(this=alias), comments=alias.pop_comments()),
 3825                )
 3826            return self._parse_schema(this=this)
 3827        return this
 3828
 3829    def _parse_kill(self) -> exp.Kill:
 3830        kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None
 3831
 3832        return self.expression(exp.Kill(this=self._parse_primary(), kind=kind))
 3833
 3834    def _parse_on_conflict(self) -> exp.OnConflict | None:
 3835        conflict = self._match_text_seq("ON", "CONFLICT")
 3836        duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY")
 3837
 3838        if not conflict and not duplicate:
 3839            return None
 3840
 3841        conflict_keys = None
 3842        constraint = None
 3843
 3844        if conflict:
 3845            if self._match_text_seq("ON", "CONSTRAINT"):
 3846                constraint = self._parse_id_var()
 3847            elif self._match(TokenType.L_PAREN):
 3848                conflict_keys = self._parse_csv(self._parse_indexed_column)
 3849                self._match_r_paren()
 3850
 3851        index_predicate = self._parse_where()
 3852
 3853        action = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 3854        if self._prev.token_type == TokenType.UPDATE:
 3855            self._match(TokenType.SET)
 3856            expressions = self._parse_csv(self._parse_update_assignment)
 3857        else:
 3858            expressions = None
 3859
 3860        return self.expression(
 3861            exp.OnConflict(
 3862                duplicate=duplicate,
 3863                expressions=expressions,
 3864                action=action,
 3865                conflict_keys=conflict_keys,
 3866                index_predicate=index_predicate,
 3867                constraint=constraint,
 3868                where=self._parse_where(),
 3869            )
 3870        )
 3871
 3872    def _parse_returning(self) -> exp.Returning | None:
 3873        if not self._match(TokenType.RETURNING):
 3874            return None
 3875        return self.expression(
 3876            exp.Returning(
 3877                expressions=self._parse_csv(self._parse_expression),
 3878                into=self._match(TokenType.INTO) and self._parse_table_part(),
 3879            )
 3880        )
 3881
 3882    def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3883        if not self._match(TokenType.FORMAT):
 3884            return None
 3885        return self._parse_row_format()
 3886
 3887    def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None:
 3888        index = self._index
 3889        with_ = with_ or self._match_text_seq("WITH")
 3890
 3891        if not self._match(TokenType.SERDE_PROPERTIES):
 3892            self._retreat(index)
 3893            return None
 3894        return self.expression(
 3895            exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_)
 3896        )
 3897
 3898    def _parse_row_format(
 3899        self, match_row: bool = False
 3900    ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3901        if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT):
 3902            return None
 3903
 3904        if self._match_text_seq("SERDE"):
 3905            this = self._parse_string()
 3906
 3907            serde_properties = self._parse_serde_properties()
 3908
 3909            return self.expression(
 3910                exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties)
 3911            )
 3912
 3913        self._match_text_seq("DELIMITED")
 3914
 3915        kwargs = {}
 3916
 3917        if self._match_text_seq("FIELDS", "TERMINATED", "BY"):
 3918            kwargs["fields"] = self._parse_string()
 3919            if self._match_text_seq("ESCAPED", "BY"):
 3920                kwargs["escaped"] = self._parse_string()
 3921        if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"):
 3922            kwargs["collection_items"] = self._parse_string()
 3923        if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"):
 3924            kwargs["map_keys"] = self._parse_string()
 3925        if self._match_text_seq("LINES", "TERMINATED", "BY"):
 3926            kwargs["lines"] = self._parse_string()
 3927        if self._match_text_seq("NULL", "DEFINED", "AS"):
 3928            kwargs["null"] = self._parse_string()
 3929
 3930        return self.expression(exp.RowFormatDelimitedProperty(**kwargs))  # type: ignore
 3931
 3932    def _parse_load(self) -> exp.LoadData | exp.Command:
 3933        if self._match_text_seq("DATA"):
 3934            local = self._match_text_seq("LOCAL")
 3935            self._match_text_seq("INPATH")
 3936            inpath = self._parse_string()
 3937            overwrite = self._match(TokenType.OVERWRITE)
 3938            temp: bool | None = None
 3939            if self._match(TokenType.INTO):
 3940                temp = self._match(TokenType.TEMPORARY)
 3941                self._match(TokenType.TABLE)
 3942
 3943            return self.expression(
 3944                exp.LoadData(
 3945                    this=self._parse_table(schema=True),
 3946                    local=local,
 3947                    overwrite=overwrite,
 3948                    temp=temp,
 3949                    inpath=inpath,
 3950                    files=self._match_text_seq("FROM", "FILES")
 3951                    and exp.Properties(expressions=self._parse_wrapped_properties()),
 3952                    partition=self._parse_partition(),
 3953                    input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(),
 3954                    serde=self._match_text_seq("SERDE") and self._parse_string(),
 3955                )
 3956            )
 3957        return self._parse_as_command(self._prev)
 3958
 3959    def _parse_delete(self) -> exp.Delete:
 3960        hint = self._parse_hint()
 3961
 3962        # This handles MySQL's "Multiple-Table Syntax"
 3963        # https://dev.mysql.com/doc/refman/8.0/en/delete.html
 3964        tables = None
 3965        if not self._match(TokenType.FROM, advance=False):
 3966            tables = self._parse_csv(self._parse_table) or None
 3967
 3968        returning = self._parse_returning()
 3969
 3970        return self.expression(
 3971            exp.Delete(
 3972                hint=hint,
 3973                tables=tables,
 3974                this=self._match(TokenType.FROM) and self._parse_table(joins=True),
 3975                using=self._match(TokenType.USING)
 3976                and self._parse_csv(lambda: self._parse_table(joins=True)),
 3977                cluster=self._match(TokenType.ON) and self._parse_on_property(),
 3978                where=self._parse_where(),
 3979                returning=returning or self._parse_returning(),
 3980                order=self._parse_order(),
 3981                limit=self._parse_limit(),
 3982            )
 3983        )
 3984
 3985    def _parse_update_assignment(self) -> exp.Expr | None:
 3986        this = self._parse_comparison()
 3987        if self._match(TokenType.EQ):
 3988            comments = self._prev_comments
 3989            this = self.expression(
 3990                exp.EQ(this=this, expression=self._parse_disjunction()), comments=comments
 3991            )
 3992        return this
 3993
 3994    def _parse_update(self) -> exp.Update:
 3995        hint = self._parse_hint()
 3996        kwargs: dict[str, object] = {
 3997            "hint": hint,
 3998            "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS),
 3999        }
 4000        while self._curr:
 4001            if self._match(TokenType.SET):
 4002                kwargs["expressions"] = self._parse_csv(self._parse_update_assignment)
 4003            elif self._match(TokenType.RETURNING, advance=False):
 4004                kwargs["returning"] = self._parse_returning()
 4005            elif self._match(TokenType.FROM, advance=False):
 4006                from_ = self._parse_from(joins=True)
 4007                table = from_.this if from_ else None
 4008                if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False):
 4009                    table.set("joins", list(self._parse_joins()) or None)
 4010
 4011                kwargs["from_"] = from_
 4012            elif self._match(TokenType.WHERE, advance=False):
 4013                kwargs["where"] = self._parse_where()
 4014            elif self._match(TokenType.ORDER_BY, advance=False):
 4015                kwargs["order"] = self._parse_order()
 4016            elif self._match(TokenType.LIMIT, advance=False):
 4017                kwargs["limit"] = self._parse_limit()
 4018            else:
 4019                break
 4020
 4021        return self.expression(exp.Update(**kwargs))
 4022
 4023    def _parse_use(self) -> exp.Use:
 4024        return self.expression(
 4025            exp.Use(
 4026                kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False),
 4027                this=self._parse_table(schema=False),
 4028            )
 4029        )
 4030
 4031    def _parse_uncache(self) -> exp.Uncache:
 4032        if not self._match(TokenType.TABLE):
 4033            self.raise_error("Expecting TABLE after UNCACHE")
 4034
 4035        return self.expression(
 4036            exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True))
 4037        )
 4038
 4039    def _parse_cache(self) -> exp.Cache:
 4040        lazy = self._match_text_seq("LAZY")
 4041        self._match(TokenType.TABLE)
 4042        table = self._parse_table(schema=True)
 4043
 4044        options = []
 4045        if self._match_text_seq("OPTIONS"):
 4046            self._match_l_paren()
 4047            k = self._parse_string()
 4048            self._match(TokenType.EQ)
 4049            v = self._parse_string()
 4050            options = [k, v]
 4051            self._match_r_paren()
 4052
 4053        self._match(TokenType.ALIAS)
 4054        return self.expression(
 4055            exp.Cache(
 4056                this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True)
 4057            )
 4058        )
 4059
 4060    def _parse_partition(self) -> exp.Partition | None:
 4061        if not self._match_texts(self.PARTITION_KEYWORDS):
 4062            return None
 4063
 4064        return self.expression(
 4065            exp.Partition(
 4066                subpartition=self._prev.text.upper() == "SUBPARTITION",
 4067                expressions=self._parse_wrapped_csv(self._parse_disjunction),
 4068            )
 4069        )
 4070
 4071    def _parse_value(self, values: bool = True) -> exp.Tuple | None:
 4072        def _parse_value_expression() -> exp.Expr | None:
 4073            if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 4074                return exp.var(self._prev.text.upper())
 4075            return self._parse_expression()
 4076
 4077        if self._match(TokenType.L_PAREN):
 4078            expressions = self._parse_csv(_parse_value_expression)
 4079            self._match_r_paren()
 4080            return self.expression(exp.Tuple(expressions=expressions))
 4081
 4082        # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows.
 4083        expression = self._parse_expression()
 4084        if expression:
 4085            return self.expression(exp.Tuple(expressions=[expression]))
 4086        return None
 4087
 4088    def _parse_projections(
 4089        self,
 4090    ) -> tuple[list[exp.Expr], list[exp.Expr] | None]:
 4091        return self._parse_expressions(), None
 4092
 4093    def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None:
 4094        if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)):
 4095            this: exp.Expr | None = self._parse_simplified_pivot(
 4096                is_unpivot=self._prev.token_type == TokenType.UNPIVOT
 4097            )
 4098        else:
 4099            # Support parentheses for duckdb FROM-first syntax
 4100            this = (
 4101                self._parse_table(consume_pipe=True)
 4102                if table and not self._match(TokenType.FROM, advance=False)
 4103                else self._parse_select(nested=True, parse_set_operation=False)
 4104            )
 4105
 4106            # Transform exp.Values into a exp.Table to pass through parse_query_modifiers
 4107            # in case a modifier (e.g. join) is following
 4108            if table and isinstance(this, exp.Values) and this.alias:
 4109                alias = this.args["alias"].pop()
 4110                this = exp.Table(this=this, alias=alias)
 4111
 4112            this = self._parse_query_modifiers(self._parse_set_operations(this))
 4113
 4114        return this
 4115
 4116    def _parse_select(
 4117        self,
 4118        nested: bool = False,
 4119        table: bool = False,
 4120        parse_subquery_alias: bool = True,
 4121        parse_set_operation: bool = True,
 4122        consume_pipe: bool = True,
 4123    ) -> exp.Expr | None:
 4124        query = self._parse_select_query(
 4125            nested=nested,
 4126            table=table,
 4127            parse_subquery_alias=parse_subquery_alias,
 4128            parse_set_operation=parse_set_operation,
 4129        )
 4130
 4131        if consume_pipe and self._match(TokenType.PIPE_GT, advance=False):
 4132            if isinstance(query, exp.Query):
 4133                query = self._parse_pipe_syntax_query(query)
 4134                query = query.subquery(copy=False) if query and table else query
 4135
 4136        return query
 4137
 4138    def _parse_select_query(
 4139        self,
 4140        nested: bool = False,
 4141        table: bool = False,
 4142        parse_subquery_alias: bool = True,
 4143        parse_set_operation: bool = True,
 4144    ) -> exp.Expr | None:
 4145        cte = self._parse_with()
 4146
 4147        if cte:
 4148            this = self._parse_statement()
 4149
 4150            if not this:
 4151                self.raise_error("Failed to parse any statement following CTE")
 4152                return cte
 4153
 4154            while isinstance(this, exp.Subquery) and this.is_wrapper:
 4155                this = this.this
 4156
 4157            assert this is not None
 4158            if "with_" in this.arg_types:
 4159                if inner_cte := this.args.get("with_"):
 4160                    cte.set("expressions", cte.expressions + inner_cte.expressions)
 4161                    if inner_cte.args.get("recursive"):
 4162                        cte.set("recursive", True)
 4163                this.set("with_", cte)
 4164            else:
 4165                self.raise_error(f"{this.key} does not support CTE")
 4166                this = cte
 4167
 4168            return this
 4169
 4170        # duckdb supports leading with FROM x
 4171        from_ = (
 4172            self._parse_from(joins=True, consume_pipe=True)
 4173            if self._match(TokenType.FROM, advance=False)
 4174            else None
 4175        )
 4176        laterals = list(iter(self._parse_lateral, None)) if from_ else None
 4177
 4178        # Hive supports multi-table inserts: FROM x INSERT ... SELECT ... [INSERT ... SELECT ...]
 4179        if from_ and self._match(TokenType.INSERT, advance=False):
 4180            source = from_.this
 4181            source.set("laterals", laterals or None)
 4182
 4183            inserts = []
 4184            while self._match(TokenType.INSERT, advance=False):
 4185                inserts.append(self._parse_statement())
 4186
 4187            return self.expression(
 4188                exp.MultitableInserts(expressions=inserts, source=source),
 4189                comments=from_.comments,
 4190            )
 4191
 4192        if self._match(TokenType.SELECT):
 4193            comments = self._prev_comments
 4194
 4195            hint = self._parse_hint()
 4196
 4197            if self._next and not self._next.token_type == TokenType.DOT:
 4198                all_ = self._match(TokenType.ALL)
 4199                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4200            else:
 4201                all_, matched_distinct = None, False
 4202
 4203            kind = (
 4204                self._prev.text.upper()
 4205                if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE"))
 4206                else None
 4207            )
 4208
 4209            distinct: exp.Expr | None = (
 4210                self.expression(
 4211                    exp.Distinct(
 4212                        on=self._parse_value(values=False) if self._match(TokenType.ON) else None
 4213                    )
 4214                )
 4215                if matched_distinct
 4216                else None
 4217            )
 4218
 4219            operation_modifiers = []
 4220            while self._curr and self._match_texts(self.OPERATION_MODIFIERS):
 4221                operation_modifiers.append(exp.var(self._prev.text.upper()))
 4222
 4223            limit = self._parse_limit(top=True)
 4224
 4225            # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ...
 4226            if limit and not matched_distinct and not all_:
 4227                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4228                if matched_distinct:
 4229                    distinct = self.expression(
 4230                        exp.Distinct(
 4231                            on=self._parse_value(values=False)
 4232                            if self._match(TokenType.ON)
 4233                            else None
 4234                        )
 4235                    )
 4236                else:
 4237                    all_ = self._match(TokenType.ALL)
 4238
 4239            if all_ and distinct:
 4240                self.raise_error("Cannot specify both ALL and DISTINCT after SELECT")
 4241
 4242            projections, exclude = self._parse_projections()
 4243
 4244            this = self.expression(
 4245                exp.Select(
 4246                    kind=kind,
 4247                    hint=hint,
 4248                    distinct=distinct,
 4249                    expressions=projections,
 4250                    limit=limit,
 4251                    exclude=exclude,
 4252                    operation_modifiers=operation_modifiers or None,
 4253                )
 4254            )
 4255            this.comments = comments
 4256
 4257            into = self._parse_into()
 4258            if into:
 4259                this.set("into", into)
 4260
 4261            if not from_:
 4262                from_ = self._parse_from()
 4263
 4264            if from_:
 4265                this.set("from_", from_)
 4266
 4267            this = self._parse_query_modifiers(this)
 4268        elif (table or nested) and self._match(TokenType.L_PAREN):
 4269            comments = self._prev_comments
 4270            this = self._parse_wrapped_select(table=table)
 4271
 4272            if this:
 4273                this.add_comments(comments, prepend=True)
 4274
 4275            # We return early here so that the UNION isn't attached to the subquery by the
 4276            # following call to _parse_set_operations, but instead becomes the parent node
 4277            self._match_r_paren()
 4278            return self._parse_subquery(this, parse_alias=parse_subquery_alias)
 4279        elif self._match(TokenType.VALUES, advance=False):
 4280            this = self._parse_derived_table_values()
 4281        elif from_:
 4282            this = exp.select("*").from_(from_.this, copy=False)
 4283            this = self._parse_query_modifiers(this)
 4284        elif self._match(TokenType.SUMMARIZE):
 4285            table = self._match(TokenType.TABLE)
 4286            this = self._parse_select() or self._parse_string() or self._parse_table()
 4287            return self.expression(exp.Summarize(this=this, table=table))
 4288        elif self._match(TokenType.DESCRIBE):
 4289            this = self._parse_describe()
 4290        else:
 4291            this = None
 4292
 4293        if laterals and isinstance(this, exp.Select):
 4294            this.set("laterals", [*laterals, *(this.args.get("laterals") or [])])
 4295
 4296        return self._parse_set_operations(this) if parse_set_operation else this
 4297
 4298    def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None:
 4299        self._match_text_seq("SEARCH")
 4300
 4301        kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper()
 4302
 4303        if not kind:
 4304            return None
 4305
 4306        self._match_text_seq("FIRST", "BY")
 4307
 4308        return self.expression(
 4309            exp.RecursiveWithSearch(
 4310                kind=kind,
 4311                this=self._parse_id_var(),
 4312                expression=self._match_text_seq("SET") and self._parse_id_var(),
 4313                to=self._match_text_seq("TO") and self._parse_bitwise(),
 4314                default=self._match(TokenType.DEFAULT) and self._parse_bitwise(),
 4315                using=self._match_text_seq("USING") and self._parse_id_var(),
 4316            )
 4317        )
 4318
 4319    def _parse_with(self, skip_with_token: bool = False) -> exp.With | None:
 4320        if not skip_with_token and not self._match(TokenType.WITH):
 4321            return None
 4322
 4323        comments = self._prev_comments
 4324        recursive = self._match(TokenType.RECURSIVE)
 4325
 4326        last_comments = None
 4327        expressions = []
 4328        udfs = []
 4329        while True:
 4330            cte = self._parse_cte()
 4331            if cte:
 4332                if isinstance(cte, exp.FunctionSpecification):
 4333                    udfs.append(cte)
 4334                else:
 4335                    expressions.append(cte)
 4336
 4337                if last_comments:
 4338                    cte.add_comments(last_comments)
 4339
 4340            if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH):
 4341                break
 4342            else:
 4343                self._match(TokenType.WITH)
 4344                recursive = self._match(TokenType.RECURSIVE) or recursive
 4345
 4346            last_comments = self._prev_comments
 4347
 4348        search = self._parse_recursive_with_search()
 4349
 4350        # A SEARCH clause can be followed by a CYCLE clause
 4351        cycle = (
 4352            self._parse_recursive_with_search()
 4353            if search
 4354            and search.args.get("kind") != "CYCLE"
 4355            and self._match_text_seq("CYCLE", advance=False)
 4356            else None
 4357        )
 4358
 4359        return self.expression(
 4360            exp.With(
 4361                expressions=expressions,
 4362                recursive=recursive or None,
 4363                search=search,
 4364                cycle=cycle,
 4365                udfs=udfs or None,
 4366            ),
 4367            comments=comments,
 4368        )
 4369
 4370    def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None:
 4371        index = self._index
 4372
 4373        alias = self._parse_table_alias(self.ID_VAR_TOKENS)
 4374        if not alias or not alias.this:
 4375            self.raise_error("Expected CTE to have alias")
 4376
 4377        key_expressions = (
 4378            self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None
 4379        )
 4380
 4381        if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE:
 4382            self._retreat(index)
 4383            return None
 4384
 4385        comments = self._prev_comments
 4386
 4387        if self._match_text_seq("NOT", "MATERIALIZED"):
 4388            materialized = False
 4389        elif self._match_text_seq("MATERIALIZED"):
 4390            materialized = True
 4391        else:
 4392            materialized = None
 4393
 4394        cte = self.expression(
 4395            exp.CTE(
 4396                this=self._parse_wrapped(self._parse_statement),
 4397                alias=alias,
 4398                materialized=materialized,
 4399                key_expressions=key_expressions,
 4400            ),
 4401            comments=comments,
 4402        )
 4403
 4404        values = cte.this
 4405        if isinstance(values, exp.Values):
 4406            cte.set("this", self._values_to_select(values))
 4407
 4408        return cte
 4409
 4410    def _values_to_select(self, values: exp.Values) -> exp.Select:
 4411        select = exp.select("*")
 4412        for key in exp.QUERY_MODIFIERS:
 4413            if modifier := values.args.get(key):
 4414                values.set(key, None)
 4415                select.set(key, modifier)
 4416        return select.from_(values if values.alias else exp.alias_(values, "_values", table=True))
 4417
 4418    def _parse_table_alias(
 4419        self, alias_tokens: t.Collection[TokenType] | None = None
 4420    ) -> exp.TableAlias | None:
 4421        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 4422        # so this section tries to parse the clause version and if it fails, it treats the token
 4423        # as an identifier (alias)
 4424        if self._can_parse_limit_or_offset():
 4425            return None
 4426
 4427        # START is never treated as an implicit alias when followed by WITH, since that
 4428        # would swallow the beginning of a START WITH ... CONNECT BY clause
 4429        if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH":
 4430            return None
 4431
 4432        any_token = self._match(TokenType.ALIAS)
 4433        alias = (
 4434            self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 4435            or self._parse_string_as_identifier()
 4436        )
 4437
 4438        index = self._index
 4439        if self._match(TokenType.L_PAREN):
 4440            columns = self._parse_csv(self._parse_function_parameter)
 4441            self._match_r_paren() if columns else self._retreat(index)
 4442        else:
 4443            columns = None
 4444
 4445        if not alias and not columns:
 4446            return None
 4447
 4448        table_alias = self.expression(exp.TableAlias(this=alias, columns=columns))
 4449
 4450        # We bubble up comments from the Identifier to the TableAlias
 4451        if isinstance(alias, exp.Identifier):
 4452            table_alias.add_comments(alias.pop_comments())
 4453
 4454        return table_alias
 4455
 4456    def _parse_subquery(
 4457        self, this: exp.Expr | None, parse_alias: bool = True
 4458    ) -> exp.Subquery | None:
 4459        if not this:
 4460            return None
 4461
 4462        return self.expression(
 4463            exp.Subquery(
 4464                this=this,
 4465                pivots=self._parse_pivots(),
 4466                alias=self._parse_table_alias() if parse_alias else None,
 4467                sample=self._parse_table_sample(),
 4468            )
 4469        )
 4470
 4471    def _implicit_unnests_to_explicit(self, this: E) -> E:
 4472        from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm
 4473
 4474        refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name}
 4475        for i, join in enumerate(this.args.get("joins") or []):
 4476            table = join.this
 4477            normalized_table = table.copy()
 4478            normalized_table.meta["maybe_column"] = True
 4479            normalized_table = _norm(normalized_table, dialect=self.dialect)
 4480
 4481            if isinstance(table, exp.Table) and not join.args.get("on"):
 4482                if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs:
 4483                    table_as_column = table.to_column()
 4484                    unnest = exp.Unnest(expressions=[table_as_column])
 4485
 4486                    # Table.to_column creates a parent Alias node that we want to convert to
 4487                    # a TableAlias and attach to the Unnest, so it matches the parser's output
 4488                    if isinstance(table.args.get("alias"), exp.TableAlias):
 4489                        table_as_column.replace(table_as_column.this)
 4490                        exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False)
 4491
 4492                    table.replace(unnest)
 4493
 4494            refs.add(normalized_table.alias_or_name)
 4495
 4496        return this
 4497
 4498    @t.overload
 4499    def _parse_query_modifiers(self, this: E) -> E: ...
 4500
 4501    @t.overload
 4502    def _parse_query_modifiers(self, this: None) -> None: ...
 4503
 4504    def _parse_query_modifiers(self, this):
 4505        if isinstance(this, self.MODIFIABLES):
 4506            for join in self._parse_joins():
 4507                this.append("joins", join)
 4508            for lateral in iter(self._parse_lateral, None):
 4509                this.append("laterals", lateral)
 4510
 4511            while True:
 4512                if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):
 4513                    modifier_token = self._curr
 4514
 4515                    # Defer LIMIT/FETCH after TOP until a set op is built so it applies to the whole result
 4516                    # e.g., SELECT 1 AS x UNION ALL SELECT TOP 2 2 AS x LIMIT 1 -> limit applies to union
 4517                    if (
 4518                        modifier_token.token_type in (TokenType.LIMIT, TokenType.FETCH)
 4519                        and (limit := this.args.get("limit"))
 4520                        and limit.meta.get("top")
 4521                    ):
 4522                        break
 4523
 4524                    parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type]
 4525                    key, expression = parser(self)
 4526
 4527                    if expression:
 4528                        if this.args.get(key):
 4529                            self.raise_error(
 4530                                f"Found multiple '{modifier_token.text.upper()}' clauses",
 4531                                token=modifier_token,
 4532                            )
 4533
 4534                        this.set(key, expression)
 4535                        if key == "limit":
 4536                            offset = expression.args.get("offset")
 4537                            expression.set("offset", None)
 4538
 4539                            if offset:
 4540                                if this.args.get("offset"):
 4541                                    self.raise_error(
 4542                                        "Found multiple 'OFFSET' clauses", token=modifier_token
 4543                                    )
 4544
 4545                                offset = exp.Offset(expression=offset)
 4546                                this.set("offset", offset)
 4547
 4548                                limit_by_expressions = expression.expressions
 4549                                expression.set("expressions", None)
 4550                                offset.set("expressions", limit_by_expressions)
 4551                        continue
 4552
 4553                if self._curr.text.upper() == "START":
 4554                    modifier_token = self._curr
 4555                    connect = self._parse_connect()
 4556                    if connect:
 4557                        if this.args.get("connect"):
 4558                            self.raise_error(
 4559                                "Found multiple 'START WITH' clauses", token=modifier_token
 4560                            )
 4561
 4562                        this.set("connect", connect)
 4563                        continue
 4564                break
 4565
 4566        if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"):
 4567            this = self._implicit_unnests_to_explicit(this)
 4568
 4569        return this
 4570
 4571    def _parse_hint_fallback_to_string(self) -> exp.Hint | None:
 4572        start = self._curr
 4573        while self._curr:
 4574            self._advance()
 4575
 4576        end = self._tokens[self._index - 1]
 4577        return exp.Hint(expressions=[self._find_sql(start, end)])
 4578
 4579    def _parse_hint_function_call(self) -> exp.Expr | None:
 4580        return self._parse_function_call()
 4581
 4582    def _parse_hint_body(self) -> exp.Hint | None:
 4583        start_index = self._index
 4584        should_fallback_to_string = False
 4585
 4586        hints = []
 4587        try:
 4588            for hint in iter(
 4589                lambda: self._parse_csv(
 4590                    lambda: self._parse_hint_function_call() or self._parse_var(upper=True),
 4591                ),
 4592                [],
 4593            ):
 4594                hints.extend(hint)
 4595        except ParseError:
 4596            should_fallback_to_string = True
 4597
 4598        if should_fallback_to_string or self._curr:
 4599            self._retreat(start_index)
 4600            return self._parse_hint_fallback_to_string()
 4601
 4602        return self.expression(exp.Hint(expressions=hints))
 4603
 4604    def _parse_hint(self) -> exp.Hint | None:
 4605        if self._match(TokenType.HINT) and self._prev_comments:
 4606            return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect)
 4607
 4608        return None
 4609
 4610    def _parse_into(self) -> exp.Into | None:
 4611        if not self._match(TokenType.INTO):
 4612            return None
 4613
 4614        temp = self._match(TokenType.TEMPORARY)
 4615        unlogged = self._match_text_seq("UNLOGGED")
 4616        self._match(TokenType.TABLE)
 4617
 4618        return self.expression(
 4619            exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged)
 4620        )
 4621
 4622    def _parse_from(
 4623        self,
 4624        joins: bool = False,
 4625        skip_from_token: bool = False,
 4626        consume_pipe: bool = False,
 4627    ) -> exp.From | None:
 4628        if not skip_from_token and not self._match(TokenType.FROM):
 4629            return None
 4630
 4631        comments = self._prev_comments
 4632        return self.expression(
 4633            exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)),
 4634            comments=comments,
 4635        )
 4636
 4637    def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure:
 4638        return self.expression(
 4639            exp.MatchRecognizeMeasure(
 4640                window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(),
 4641                this=self._parse_expression(),
 4642            )
 4643        )
 4644
 4645    def _parse_match_recognize(self) -> exp.MatchRecognize | None:
 4646        if not self._match(TokenType.MATCH_RECOGNIZE):
 4647            return None
 4648
 4649        self._match_l_paren()
 4650
 4651        partition = self._parse_partition_by()
 4652        order = self._parse_order()
 4653
 4654        measures = (
 4655            self._parse_csv(self._parse_match_recognize_measure)
 4656            if self._match_text_seq("MEASURES")
 4657            else None
 4658        )
 4659
 4660        if self._match_text_seq("ONE", "ROW", "PER", "MATCH"):
 4661            rows = exp.var("ONE ROW PER MATCH")
 4662        elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"):
 4663            text = "ALL ROWS PER MATCH"
 4664            if self._match_text_seq("SHOW", "EMPTY", "MATCHES"):
 4665                text += " SHOW EMPTY MATCHES"
 4666            elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"):
 4667                text += " OMIT EMPTY MATCHES"
 4668            elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"):
 4669                text += " WITH UNMATCHED ROWS"
 4670            rows = exp.var(text)
 4671        else:
 4672            rows = None
 4673
 4674        if self._match_text_seq("AFTER", "MATCH", "SKIP"):
 4675            text = "AFTER MATCH SKIP"
 4676            if self._match_text_seq("PAST", "LAST", "ROW"):
 4677                text += " PAST LAST ROW"
 4678            elif self._match_text_seq("TO", "NEXT", "ROW"):
 4679                text += " TO NEXT ROW"
 4680            elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"):
 4681                direction = self._prev.text.upper()
 4682                pattern_var = self._advance_any()
 4683                if not pattern_var:
 4684                    self.raise_error(
 4685                        f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}"
 4686                    )
 4687                text += f" TO {direction} {pattern_var.text if pattern_var else ''}"
 4688            after = exp.var(text)
 4689        else:
 4690            after = None
 4691
 4692        if self._match_text_seq("PATTERN"):
 4693            self._match_l_paren()
 4694
 4695            if not self._curr:
 4696                self.raise_error("Expecting )", self._curr)
 4697
 4698            paren = 1
 4699            start = self._curr
 4700
 4701            while self._curr and paren > 0:
 4702                if self._curr.token_type == TokenType.L_PAREN:
 4703                    paren += 1
 4704                if self._curr.token_type == TokenType.R_PAREN:
 4705                    paren -= 1
 4706
 4707                end = self._prev
 4708                self._advance()
 4709
 4710            if paren > 0:
 4711                self.raise_error("Expecting )", self._curr)
 4712
 4713            pattern = exp.var(self._find_sql(start, end))
 4714        else:
 4715            pattern = None
 4716
 4717        define = (
 4718            self._parse_csv(self._parse_name_as_expression)
 4719            if self._match_text_seq("DEFINE")
 4720            else None
 4721        )
 4722
 4723        self._match_r_paren()
 4724
 4725        return self.expression(
 4726            exp.MatchRecognize(
 4727                partition_by=partition,
 4728                order=order,
 4729                measures=measures,
 4730                rows=rows,
 4731                after=after,
 4732                pattern=pattern,
 4733                define=define,
 4734                alias=self._parse_table_alias(),
 4735            )
 4736        )
 4737
 4738    def _parse_lateral(self) -> exp.Lateral | None:
 4739        cross_apply: bool | None = None
 4740        if self._match_pair(TokenType.CROSS, TokenType.APPLY):
 4741            cross_apply = True
 4742        elif self._match_pair(TokenType.OUTER, TokenType.APPLY):
 4743            cross_apply = False
 4744
 4745        if cross_apply is not None:
 4746            this = self._parse_select(table=True)
 4747            view = None
 4748            outer = None
 4749        elif self._match(TokenType.LATERAL):
 4750            this = self._parse_select(table=True)
 4751            view = self._match(TokenType.VIEW)
 4752            outer = self._match(TokenType.OUTER)
 4753        else:
 4754            return None
 4755
 4756        if not this:
 4757            this = (
 4758                self._parse_unnest()
 4759                or self._parse_function()
 4760                or self._parse_id_var(any_token=False)
 4761            )
 4762
 4763            while self._match(TokenType.DOT):
 4764                this = exp.Dot(
 4765                    this=this,
 4766                    expression=self._parse_function() or self._parse_id_var(any_token=False),
 4767                )
 4768
 4769        ordinality: bool | None = None
 4770
 4771        if view:
 4772            table = self._parse_id_var(any_token=False)
 4773            columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else []
 4774            table_alias: exp.TableAlias | None = self.expression(
 4775                exp.TableAlias(this=table, columns=columns)
 4776            )
 4777        elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias:
 4778            # We move the alias from the lateral's child node to the lateral itself
 4779            table_alias = this.args["alias"].pop()
 4780        else:
 4781            ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 4782            table_alias = self._parse_table_alias()
 4783
 4784        return self.expression(
 4785            exp.Lateral(
 4786                this=this,
 4787                view=view,
 4788                outer=outer,
 4789                alias=table_alias,
 4790                cross_apply=cross_apply,
 4791                ordinality=ordinality,
 4792            )
 4793        )
 4794
 4795    def _parse_stream(self) -> exp.Stream | None:
 4796        index = self._index
 4797        if self._match(TokenType.STREAM):
 4798            if this := self._try_parse(self._parse_table):
 4799                return self.expression(exp.Stream(this=this))
 4800            self._retreat(index)
 4801        return None
 4802
 4803    def _parse_join_parts(
 4804        self,
 4805    ) -> tuple[Token | None, Token | None, Token | None]:
 4806        return (
 4807            self._prev if self._match_set(self.JOIN_METHODS) else None,
 4808            self._prev if self._match_set(self.JOIN_SIDES) else None,
 4809            self._prev if self._match_set(self.JOIN_KINDS) else None,
 4810        )
 4811
 4812    def _parse_using_identifiers(self) -> list[exp.Expr]:
 4813        def _parse_column_as_identifier() -> exp.Expr | None:
 4814            this = self._parse_column()
 4815            if isinstance(this, exp.Column):
 4816                return this.this
 4817            return this
 4818
 4819        return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True)
 4820
 4821    def _parse_join(
 4822        self,
 4823        skip_join_token: bool = False,
 4824        parse_bracket: bool = False,
 4825        alias_tokens: t.Collection[TokenType] | None = None,
 4826    ) -> exp.Join | None:
 4827        if self._match(TokenType.COMMA):
 4828            table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens))
 4829            cross_join = self.expression(exp.Join(this=table)) if table else None
 4830
 4831            if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE:
 4832                cross_join.set("kind", "CROSS")
 4833
 4834            return cross_join
 4835
 4836        index = self._index
 4837        method, side, kind = self._parse_join_parts()
 4838        directed = self.SUPPORTS_DIRECTED_JOIN and self._match_text_seq("DIRECTED")
 4839        hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None
 4840        join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN)
 4841        join_comments = self._prev_comments
 4842
 4843        if not skip_join_token and not join:
 4844            self._retreat(index)
 4845            kind = None
 4846            method = None
 4847            side = None
 4848
 4849        outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False)
 4850        cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False)
 4851
 4852        if not skip_join_token and not join and not outer_apply and not cross_apply:
 4853            return None
 4854
 4855        kwargs: dict[str, t.Any] = {
 4856            "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4857        }
 4858        if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA):
 4859            kwargs["expressions"] = self._parse_csv(
 4860                lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4861            )
 4862
 4863        if method:
 4864            kwargs["method"] = method.text.upper()
 4865        if side:
 4866            kwargs["side"] = side.text.upper()
 4867        if kind:
 4868            kwargs["kind"] = kind.text.upper()
 4869        if hint:
 4870            kwargs["hint"] = hint
 4871
 4872        if self._match(TokenType.MATCH_CONDITION):
 4873            kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison)
 4874
 4875        if self._match(TokenType.ON):
 4876            kwargs["on"] = self._parse_disjunction()
 4877        elif self._match(TokenType.USING):
 4878            kwargs["using"] = self._parse_using_identifiers()
 4879        elif (
 4880            not method
 4881            and not (outer_apply or cross_apply)
 4882            and not isinstance(kwargs["this"], exp.Unnest)
 4883            and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY))
 4884        ):
 4885            index = self._index
 4886            joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens))
 4887
 4888            if joins and self._match(TokenType.ON):
 4889                kwargs["on"] = self._parse_disjunction()
 4890            elif joins and self._match(TokenType.USING):
 4891                kwargs["using"] = self._parse_using_identifiers()
 4892            else:
 4893                joins = None
 4894                self._retreat(index)
 4895
 4896            kwargs["this"].set("joins", joins if joins else None)
 4897
 4898        kwargs["pivots"] = self._parse_pivots()
 4899
 4900        comments = [c for token in (method, side, kind) if token for c in token.comments]
 4901        comments = (join_comments or []) + comments
 4902
 4903        if (
 4904            self.ADD_JOIN_ON_TRUE
 4905            and not kwargs.get("on")
 4906            and not kwargs.get("using")
 4907            and not kwargs.get("method")
 4908            and kwargs.get("kind") in (None, "INNER", "OUTER")
 4909        ):
 4910            kwargs["on"] = exp.true()
 4911
 4912        if directed:
 4913            kwargs["directed"] = directed
 4914
 4915        return self.expression(exp.Join(**kwargs), comments=comments)
 4916
 4917    def _parse_opclass(self) -> exp.Expr | None:
 4918        this = self._parse_disjunction()
 4919
 4920        if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False):
 4921            return this
 4922
 4923        if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False):
 4924            return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts()))
 4925
 4926        return this
 4927
 4928    def _parse_index_params(self) -> exp.IndexParameters:
 4929        using = (
 4930            self._parse_var(any_token=True)
 4931            if not self._match_text_seq("USING", "INDEX", "TABLESPACE", advance=False)
 4932            and self._match(TokenType.USING)
 4933            else None
 4934        )
 4935
 4936        if self._match(TokenType.L_PAREN, advance=False):
 4937            columns = self._parse_wrapped_csv(self._parse_with_operator)
 4938        else:
 4939            columns = None
 4940
 4941        include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None
 4942        partition_by = self._parse_partition_by()
 4943        with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties()
 4944        tablespace = (
 4945            self._parse_id_var()
 4946            if self._match_text_seq("USING", "INDEX", "TABLESPACE")
 4947            or self._match_text_seq("TABLESPACE")
 4948            else None
 4949        )
 4950        where = self._parse_where()
 4951
 4952        on = self._parse_field() if self._match(TokenType.ON) else None
 4953
 4954        return self.expression(
 4955            exp.IndexParameters(
 4956                using=using,
 4957                columns=columns,
 4958                include=include,
 4959                partition_by=partition_by,
 4960                where=where,
 4961                with_storage=with_storage,
 4962                tablespace=tablespace,
 4963                on=on,
 4964            )
 4965        )
 4966
 4967    def _parse_index(
 4968        self, index: exp.Expr | None = None, anonymous: bool = False
 4969    ) -> exp.Index | None:
 4970        if index or anonymous:
 4971            unique = None
 4972            primary = None
 4973            amp = None
 4974
 4975            self._match(TokenType.ON)
 4976            self._match(TokenType.TABLE)  # hive
 4977            table = self._parse_table_parts(schema=True)
 4978        else:
 4979            unique = self._match(TokenType.UNIQUE)
 4980            primary = self._match_text_seq("PRIMARY")
 4981            amp = self._match_text_seq("AMP")
 4982
 4983            if not self._match(TokenType.INDEX):
 4984                return None
 4985
 4986            index = self._parse_id_var()
 4987            table = None
 4988
 4989        params = self._parse_index_params()
 4990
 4991        return self.expression(
 4992            exp.Index(
 4993                this=index, table=table, unique=unique, primary=primary, amp=amp, params=params
 4994            )
 4995        )
 4996
 4997    def _parse_table_hints(self) -> list[exp.Expr] | None:
 4998        hints: list[exp.Expr] = []
 4999        if self._match_pair(TokenType.WITH, TokenType.L_PAREN):
 5000            # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16
 5001            hints.append(
 5002                self.expression(
 5003                    exp.WithTableHint(
 5004                        expressions=self._parse_csv(
 5005                            lambda: self._parse_function() or self._parse_var(any_token=True)
 5006                        )
 5007                    )
 5008                )
 5009            )
 5010            self._match_r_paren()
 5011        else:
 5012            # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
 5013            while self._match_set(self.TABLE_INDEX_HINT_TOKENS):
 5014                hint = exp.IndexTableHint(this=self._prev.text.upper())
 5015
 5016                self._match_set((TokenType.INDEX, TokenType.KEY))
 5017                if self._match(TokenType.FOR):
 5018                    hint.set("target", self._advance_any() and self._prev.text.upper())
 5019
 5020                hint.set("expressions", self._parse_wrapped_id_vars())
 5021                hints.append(hint)
 5022
 5023        return hints or None
 5024
 5025    def _parse_table_part(self, schema: bool = False) -> exp.Expr | None:
 5026        return (
 5027            (not schema and self._parse_function(optional_parens=False))
 5028            or self._parse_id_var(any_token=False)
 5029            or self._parse_string_as_identifier()
 5030            or self._parse_placeholder()
 5031        )
 5032
 5033    def _parse_table_parts_fast(self) -> exp.Table | None:
 5034        index = self._index
 5035        parts: list[exp.Identifier] | None = None
 5036        all_comments: list[str] | None = None
 5037
 5038        while self._match_set(self.IDENTIFIER_TOKENS):
 5039            token = self._prev
 5040            comments = self._prev_comments
 5041
 5042            has_dot = self._match(TokenType.DOT)
 5043            curr_tt = self._curr.token_type
 5044
 5045            if not has_dot:
 5046                if curr_tt in self.TABLE_POSTFIX_TOKENS:
 5047                    self._retreat(index)
 5048                    return None
 5049            elif curr_tt not in self.IDENTIFIER_TOKENS:
 5050                self._retreat(index)
 5051                return None
 5052
 5053            if parts is None:
 5054                parts = []
 5055
 5056            if comments:
 5057                if all_comments is None:
 5058                    all_comments = []
 5059                all_comments.extend(comments)
 5060                self._prev_comments = []
 5061
 5062            parts.append(
 5063                self.expression(
 5064                    exp.Identifier(
 5065                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 5066                    ),
 5067                    token,
 5068                )
 5069            )
 5070
 5071            if not has_dot:
 5072                break
 5073
 5074        if parts is None:
 5075            return None
 5076
 5077        n = len(parts)
 5078
 5079        if n == 1:
 5080            table: exp.Table = exp.Table(this=parts[0])
 5081        elif n == 2:
 5082            table = exp.Table(this=parts[1], db=parts[0])
 5083        elif n >= 3:
 5084            this: exp.Identifier | exp.Dot = parts[2]
 5085            for i in range(3, n):
 5086                this = exp.Dot(this=this, expression=parts[i])
 5087
 5088            table = exp.Table(this=this, db=parts[1], catalog=parts[0])
 5089
 5090        if table is None:
 5091            self._retreat(index)
 5092        elif all_comments:
 5093            table.add_comments(all_comments)
 5094        return table
 5095
 5096    def _parse_table_parts(
 5097        self,
 5098        schema: bool = False,
 5099        is_db_reference: bool = False,
 5100        wildcard: bool = False,
 5101        fast: bool = False,
 5102    ) -> exp.Table | exp.Dot | None:
 5103        if fast:
 5104            return self._parse_table_parts_fast()
 5105
 5106        catalog: exp.Expr | str | None = None
 5107        db: exp.Expr | str | None = None
 5108        table: exp.Expr | str | None = self._parse_table_part(schema=schema)
 5109
 5110        while self._match(TokenType.DOT):
 5111            if catalog:
 5112                # This allows nesting the table in arbitrarily many dot expressions if needed
 5113                table = self.expression(
 5114                    exp.Dot(this=table, expression=self._parse_table_part(schema=schema))
 5115                )
 5116            else:
 5117                catalog = db
 5118                db = table
 5119                # "" used for tsql FROM a..b case
 5120                table = self._parse_table_part(schema=schema) or ""
 5121
 5122        if (
 5123            wildcard
 5124            and self._is_connected()
 5125            and (isinstance(table, exp.Identifier) or not table)
 5126            and self._match(TokenType.STAR)
 5127        ):
 5128            if isinstance(table, exp.Identifier):
 5129                table.args["this"] += "*"
 5130            else:
 5131                table = exp.Identifier(this="*")
 5132
 5133        if is_db_reference:
 5134            catalog = db
 5135            db = table
 5136            table = None
 5137
 5138        if not table and not is_db_reference:
 5139            self.raise_error(f"Expected table name but got {self._curr}")
 5140        if not db and is_db_reference:
 5141            self.raise_error(f"Expected database name but got {self._curr}")
 5142
 5143        table = self.expression(exp.Table(this=table, db=db, catalog=catalog))
 5144
 5145        # Bubble up comments from identifier parts to the Table
 5146        comments = []
 5147        for part in table.parts:
 5148            if part_comments := part.pop_comments():
 5149                comments.extend(part_comments)
 5150        if comments:
 5151            table.add_comments(comments)
 5152
 5153        changes = self._parse_changes()
 5154        if changes:
 5155            table.set("changes", changes)
 5156
 5157        at_before = self._parse_historical_data()
 5158        if at_before:
 5159            table.set("when", at_before)
 5160
 5161        pivots = self._parse_pivots()
 5162        if pivots:
 5163            table.set("pivots", pivots)
 5164
 5165        return table
 5166
 5167    def _parse_table(
 5168        self,
 5169        schema: bool = False,
 5170        joins: bool = False,
 5171        alias_tokens: t.Collection[TokenType] | None = None,
 5172        parse_bracket: bool = False,
 5173        is_db_reference: bool = False,
 5174        parse_partition: bool = False,
 5175        consume_pipe: bool = False,
 5176    ) -> exp.Expr | None:
 5177        if not schema and not is_db_reference and not consume_pipe and not joins:
 5178            index = self._index
 5179            table = self._parse_table_parts(fast=True)
 5180
 5181            if table is not None:
 5182                curr_tt = self._curr.token_type
 5183                next_tt = self._next.token_type
 5184
 5185                fast_terminators = self.TABLE_TERMINATORS
 5186
 5187                # only return the table if we're sure there are no other operators
 5188                # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT
 5189                if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION:
 5190                    return table
 5191
 5192                postfix_tokens = self.TABLE_POSTFIX_TOKENS
 5193
 5194                if curr_tt not in postfix_tokens and next_tt not in postfix_tokens:
 5195                    if alias := self._parse_table_alias(
 5196                        alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS
 5197                    ):
 5198                        table.set("alias", alias)
 5199
 5200                    if self._curr.token_type in fast_terminators:
 5201                        return table
 5202
 5203                self._retreat(index)
 5204
 5205        if stream := self._parse_stream():
 5206            return stream
 5207
 5208        if lateral := self._parse_lateral():
 5209            return lateral
 5210
 5211        if unnest := self._parse_unnest():
 5212            return unnest
 5213
 5214        if values := self._parse_derived_table_values():
 5215            return values
 5216
 5217        if subquery := self._parse_select(table=True, consume_pipe=consume_pipe):
 5218            if not subquery.args.get("pivots"):
 5219                subquery.set("pivots", self._parse_pivots())
 5220            if joins:
 5221                for join in self._parse_joins():
 5222                    subquery.append("joins", join)
 5223            return subquery
 5224
 5225        bracket = parse_bracket and self._parse_bracket(None)
 5226        bracket = self.expression(exp.Table(this=bracket)) if bracket else None
 5227
 5228        rows_from_tables = (
 5229            self._parse_wrapped_csv(self._parse_table)
 5230            if self._match_text_seq("ROWS", "FROM")
 5231            else None
 5232        )
 5233        rows_from = (
 5234            self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None
 5235        )
 5236
 5237        only = self._match(TokenType.ONLY)
 5238
 5239        this = t.cast(
 5240            exp.Expr,
 5241            bracket
 5242            or rows_from
 5243            or self._parse_bracket(
 5244                self._parse_table_parts(schema=schema, is_db_reference=is_db_reference)
 5245            ),
 5246        )
 5247
 5248        if only:
 5249            this.set("only", only)
 5250
 5251        # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context
 5252        self._match(TokenType.STAR)
 5253
 5254        parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION
 5255        if parse_partition and self._match(TokenType.PARTITION, advance=False):
 5256            this.set("partition", self._parse_partition())
 5257
 5258        if schema:
 5259            return self._parse_schema(this=this)
 5260
 5261        if self.dialect.ALIAS_POST_VERSION:
 5262            this.set("version", self._parse_version())
 5263
 5264        if self.dialect.ALIAS_POST_TABLESAMPLE:
 5265            this.set("sample", self._parse_table_sample())
 5266            if (
 5267                this.args.get("sample")
 5268                and self.dialect.ALIAS_POST_VERSION
 5269                and not this.args.get("version")
 5270            ):
 5271                this.set("version", self._parse_version())
 5272
 5273        alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 5274        if alias:
 5275            this.set("alias", alias)
 5276
 5277            # DuckDB requires the time-travel clause to come after the alias, e.g.
 5278            # SELECT * FROM t AS a AT (VERSION => 1)
 5279            if isinstance(this, exp.Table) and not this.args.get("when"):
 5280                this.set("when", self._parse_historical_data())
 5281
 5282        if self._match(TokenType.INDEXED_BY):
 5283            this.set("indexed", self._parse_table_parts())
 5284        elif self._match_text_seq("NOT", "INDEXED"):
 5285            this.set("indexed", False)
 5286
 5287        if isinstance(this, exp.Table) and self._match_text_seq("AT"):
 5288            return self.expression(
 5289                exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var())
 5290            )
 5291
 5292        this.set("hints", self._parse_table_hints())
 5293
 5294        if not this.args.get("pivots"):
 5295            this.set("pivots", self._parse_pivots())
 5296
 5297        if not self.dialect.ALIAS_POST_TABLESAMPLE:
 5298            this.set("sample", self._parse_table_sample())
 5299
 5300        if not self.dialect.ALIAS_POST_VERSION:
 5301            this.set("version", self._parse_version())
 5302
 5303        if joins:
 5304            for join in self._parse_joins(alias_tokens=alias_tokens):
 5305                this.append("joins", join)
 5306
 5307        if self._match_pair(TokenType.WITH, TokenType.ORDINALITY):
 5308            this.set("ordinality", True)
 5309            this.set("alias", self._parse_table_alias())
 5310
 5311        # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we
 5312        # hoist the table args onto the latter and return it instead
 5313        if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows):
 5314            table_from_rows = this.this
 5315            for arg in exp.TableFromRows.arg_types:
 5316                if arg != "this":
 5317                    table_from_rows.set(arg, this.args.get(arg))
 5318
 5319            this = table_from_rows
 5320
 5321        return this
 5322
 5323    def _parse_version(self) -> exp.Version | None:
 5324        for phrase, this in self.VERSION_PHRASES.items():
 5325            if self._match_text_seq(*phrase):
 5326                break
 5327        else:
 5328            return None
 5329
 5330        if self._match_set((TokenType.FROM, TokenType.BETWEEN)):
 5331            kind = self._prev.text.upper()
 5332            start = self._parse_bitwise()
 5333            self._match_texts(("TO", "AND"))
 5334            end = self._parse_bitwise()
 5335            expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end]))
 5336        elif self._match_text_seq("CONTAINED", "IN"):
 5337            kind = "CONTAINED IN"
 5338            expression = self.expression(
 5339                exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise))
 5340            )
 5341        elif self._match(TokenType.ALL):
 5342            kind = "ALL"
 5343            expression = None
 5344        else:
 5345            self._match_text_seq("AS", "OF")
 5346            kind = "AS OF"
 5347            expression = self._parse_bitwise()
 5348
 5349        return self.expression(exp.Version(this=this, expression=expression, kind=kind))
 5350
 5351    def _parse_historical_data(self) -> exp.HistoricalData | None:
 5352        # https://docs.snowflake.com/en/sql-reference/constructs/at-before
 5353        index = self._index
 5354        historical_data = None
 5355        if self._match_texts(self.HISTORICAL_DATA_PREFIX):
 5356            this = self._prev.text.upper()
 5357            kind = (
 5358                self._match(TokenType.L_PAREN)
 5359                and self._match_texts(self.HISTORICAL_DATA_KIND)
 5360                and self._prev.text.upper()
 5361            )
 5362            expression = self._match(TokenType.FARROW) and self._parse_bitwise()
 5363
 5364            if expression:
 5365                self._match_r_paren()
 5366                historical_data = self.expression(
 5367                    exp.HistoricalData(this=this, kind=kind, expression=expression)
 5368                )
 5369            else:
 5370                self._retreat(index)
 5371
 5372        return historical_data
 5373
 5374    def _parse_changes(self) -> exp.Changes | None:
 5375        if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"):
 5376            return None
 5377
 5378        information = self._parse_var(any_token=True)
 5379        self._match_r_paren()
 5380
 5381        return self.expression(
 5382            exp.Changes(
 5383                information=information,
 5384                at_before=self._parse_historical_data(),
 5385                end=self._parse_historical_data(),
 5386            )
 5387        )
 5388
 5389    def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None:
 5390        if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False):
 5391            return None
 5392
 5393        self._advance()
 5394
 5395        expressions = self._parse_wrapped_csv(self._parse_equality)
 5396        offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 5397
 5398        alias = self._parse_table_alias() if with_alias else None
 5399
 5400        if alias:
 5401            if self.dialect.UNNEST_COLUMN_ONLY:
 5402                if alias.args.get("columns"):
 5403                    self.raise_error("Unexpected extra column alias in unnest.")
 5404
 5405                alias.set("columns", [alias.this])
 5406                alias.set("this", None)
 5407
 5408            columns = alias.args.get("columns") or []
 5409            if offset and len(expressions) < len(columns):
 5410                offset = columns.pop()
 5411
 5412        if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET):
 5413            self._match(TokenType.ALIAS)
 5414            offset = self._parse_id_var(
 5415                any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS
 5416            ) or exp.to_identifier("offset")
 5417
 5418        return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset))
 5419
 5420    def _parse_derived_table_values(self, allow_value_synonym: bool = False) -> exp.Values | None:
 5421        is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES)
 5422        if not is_derived and not (
 5423            # ClickHouse's `FORMAT Values` is equivalent to `VALUES`
 5424            self._match_text_seq("VALUES")
 5425            or self._match_text_seq("FORMAT", "VALUES")
 5426            # MySQL accepts VALUE as a synonym for VALUES in INSERT statements.
 5427            or (allow_value_synonym and self._match_text_seq("VALUE"))
 5428        ):
 5429            return None
 5430
 5431        expressions = self._parse_csv(self._parse_value)
 5432        alias = self._parse_table_alias()
 5433
 5434        if is_derived:
 5435            self._match_r_paren()
 5436
 5437        return self.expression(
 5438            exp.Values(expressions=expressions, alias=alias or self._parse_table_alias())
 5439        )
 5440
 5441    def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None:
 5442        if not self._match(TokenType.TABLE_SAMPLE) and not (
 5443            as_modifier and self._match_text_seq("USING", "SAMPLE")
 5444        ):
 5445            return None
 5446
 5447        bucket_numerator = None
 5448        bucket_denominator = None
 5449        bucket_field = None
 5450        percent = None
 5451        size = None
 5452        seed = None
 5453
 5454        method = self._parse_var(tokens=(TokenType.ROW,), upper=True)
 5455        matched_l_paren = self._match(TokenType.L_PAREN)
 5456
 5457        if self.TABLESAMPLE_CSV:
 5458            num = None
 5459            expressions = self._parse_csv(self._parse_primary)
 5460        else:
 5461            expressions = None
 5462            num = (
 5463                self._parse_factor(parse_mod=False)
 5464                if self._match(TokenType.NUMBER, advance=False)
 5465                else self._parse_primary() or self._parse_placeholder()
 5466            )
 5467
 5468        if self._match_text_seq("BUCKET"):
 5469            bucket_numerator = self._parse_number()
 5470            self._match_text_seq("OUT", "OF")
 5471            bucket_denominator = bucket_denominator = self._parse_number()
 5472            self._match(TokenType.ON)
 5473            bucket_field = self._parse_field()
 5474        elif self._match_set((TokenType.PERCENT, TokenType.MOD)):
 5475            percent = num
 5476        elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
 5477            size = num
 5478        else:
 5479            percent = num
 5480
 5481        if matched_l_paren:
 5482            self._match_r_paren()
 5483
 5484        if self._match(TokenType.L_PAREN):
 5485            method = self._parse_var(upper=True)
 5486            seed = self._match(TokenType.COMMA) and self._parse_number()
 5487            self._match_r_paren()
 5488        elif self._match_texts(("SEED", "REPEATABLE")):
 5489            seed = self._parse_wrapped(self._parse_number)
 5490
 5491        if not method and self.DEFAULT_SAMPLING_METHOD:
 5492            method = exp.var(self.DEFAULT_SAMPLING_METHOD)
 5493
 5494        return self.expression(
 5495            exp.TableSample(
 5496                expressions=expressions,
 5497                method=method,
 5498                bucket_numerator=bucket_numerator,
 5499                bucket_denominator=bucket_denominator,
 5500                bucket_field=bucket_field,
 5501                percent=percent,
 5502                size=size,
 5503                seed=seed,
 5504            )
 5505        )
 5506
 5507    def _parse_pivots(self) -> list[exp.Pivot] | None:
 5508        if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT):
 5509            return None
 5510        return list(iter(self._parse_pivot, None)) or None
 5511
 5512    def _parse_joins(
 5513        self, alias_tokens: t.Collection[TokenType] | None = None
 5514    ) -> t.Iterator[exp.Join]:
 5515        return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None)
 5516
 5517    def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None:
 5518        if not self._match(TokenType.INTO):
 5519            return None
 5520
 5521        return self.expression(
 5522            exp.UnpivotColumns(
 5523                this=self._match_text_seq("NAME") and self._parse_column(),
 5524                expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column),
 5525            )
 5526        )
 5527
 5528    # https://duckdb.org/docs/sql/statements/pivot
 5529    def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot:
 5530        def _parse_on() -> exp.Expr | None:
 5531            this = self._parse_bitwise()
 5532
 5533            if self._match(TokenType.IN):
 5534                # PIVOT ... ON col IN (row_val1, row_val2)
 5535                return self._parse_in(this)
 5536            if self._match(TokenType.ALIAS, advance=False):
 5537                # UNPIVOT ... ON (col1, col2, col3) AS row_val
 5538                return self._parse_alias(this)
 5539
 5540            return this
 5541
 5542        this = self._parse_table()
 5543        expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on)
 5544        into = self._parse_unpivot_columns()
 5545        using = self._match(TokenType.USING) and self._parse_csv(
 5546            lambda: self._parse_alias(self._parse_column())
 5547        )
 5548        group = self._parse_group()
 5549
 5550        return self.expression(
 5551            exp.Pivot(
 5552                this=this,
 5553                expressions=expressions,
 5554                using=using,
 5555                group=group,
 5556                unpivot=is_unpivot,
 5557                into=into,
 5558            )
 5559        )
 5560
 5561    def _parse_pivot_in(self) -> exp.In:
 5562        def _parse_aliased_expression() -> exp.Expr | None:
 5563            this = self._parse_select_or_expression()
 5564
 5565            self._match(TokenType.ALIAS)
 5566            alias = self._parse_bitwise()
 5567            if alias:
 5568                if isinstance(alias, exp.Column) and not alias.db:
 5569                    alias = alias.this
 5570                return self.expression(exp.PivotAlias(this=this, alias=alias))
 5571
 5572            return this
 5573
 5574        value = self._parse_column()
 5575
 5576        if not self._match(TokenType.IN):
 5577            self.raise_error("Expecting IN")
 5578
 5579        if self._match(TokenType.L_PAREN):
 5580            if self._match(TokenType.ANY):
 5581                exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order()))
 5582            else:
 5583                exprs = self._parse_csv(_parse_aliased_expression)
 5584            self._match_r_paren()
 5585            return self.expression(exp.In(this=value, expressions=exprs))
 5586
 5587        return self.expression(exp.In(this=value, field=self._parse_id_var()))
 5588
 5589    def _parse_pivot_aggregation(self) -> exp.Expr | None:
 5590        func = self._parse_function()
 5591        if not func:
 5592            if self._prev.token_type == TokenType.COMMA:
 5593                return None
 5594            self.raise_error("Expecting an aggregation function in PIVOT")
 5595
 5596        return self._parse_alias(func)
 5597
 5598    def _parse_pivot(self) -> exp.Pivot | None:
 5599        index = self._index
 5600        include_nulls = None
 5601
 5602        if self._match(TokenType.PIVOT):
 5603            unpivot = False
 5604        elif self._match(TokenType.UNPIVOT):
 5605            unpivot = True
 5606
 5607            # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax
 5608            if self._match_text_seq("INCLUDE", "NULLS"):
 5609                include_nulls = True
 5610            elif self._match_text_seq("EXCLUDE", "NULLS"):
 5611                include_nulls = False
 5612        else:
 5613            return None
 5614
 5615        expressions = []
 5616
 5617        if not self._match(TokenType.L_PAREN):
 5618            self._retreat(index)
 5619            return None
 5620
 5621        if unpivot:
 5622            expressions = self._parse_csv(self._parse_column)
 5623        else:
 5624            expressions = self._parse_csv(self._parse_pivot_aggregation)
 5625
 5626        if not expressions:
 5627            self.raise_error("Failed to parse PIVOT's aggregation list")
 5628
 5629        if not self._match(TokenType.FOR):
 5630            self.raise_error("Expecting FOR")
 5631
 5632        fields = []
 5633        while True:
 5634            field = self._try_parse(self._parse_pivot_in)
 5635            if not field:
 5636                break
 5637            fields.append(field)
 5638
 5639        default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped(
 5640            self._parse_bitwise
 5641        )
 5642
 5643        group = self._parse_group()
 5644
 5645        self._match_r_paren()
 5646
 5647        pivot = self.expression(
 5648            exp.Pivot(
 5649                expressions=expressions,
 5650                fields=fields,
 5651                unpivot=unpivot,
 5652                include_nulls=include_nulls,
 5653                default_on_null=default_on_null,
 5654                group=group,
 5655            )
 5656        )
 5657
 5658        if unpivot:
 5659            pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions])
 5660            for pivot_field in pivot.fields:
 5661                if isinstance(pivot_field, exp.In):
 5662                    pivot_field.set("this", _unpivot_target(pivot_field.this))
 5663
 5664            pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST)
 5665
 5666        if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False):
 5667            pivot.set("alias", self._parse_table_alias())
 5668
 5669        if not unpivot:
 5670            names = self._pivot_column_names(t.cast(list[exp.Expr], expressions))
 5671
 5672            columns: list[exp.Expr] = []
 5673            all_fields = []
 5674            for pivot_field in pivot.fields:
 5675                pivot_field_expressions = pivot_field.expressions
 5676
 5677                # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case.
 5678                if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny):
 5679                    continue
 5680
 5681                all_fields.append(
 5682                    [
 5683                        # An explicit `<field> AS <alias>` names the output column directly,
 5684                        # so it wins over the dialect's string-identifying convention
 5685                        fld.sql()
 5686                        if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias)
 5687                        else fld.alias_or_name
 5688                        for fld in pivot_field_expressions
 5689                    ]
 5690                )
 5691
 5692            if all_fields:
 5693                if names:
 5694                    all_fields.append(names)
 5695
 5696                # Generate all possible combinations of the pivot columns
 5697                # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US'))
 5698                # generates the product between [[2000, 2010], ['NL', 'US'], ['total']]
 5699                for fld_parts_tuple in itertools.product(*all_fields):
 5700                    fld_parts = list(fld_parts_tuple)
 5701
 5702                    if names and self.PREFIXED_PIVOT_COLUMNS:
 5703                        # Move the "name" to the front of the list
 5704                        fld_parts.insert(0, fld_parts.pop(-1))
 5705
 5706                    columns.append(exp.to_identifier("_".join(fld_parts)))
 5707
 5708            pivot.set("columns", columns)
 5709            pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS)
 5710            pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS)
 5711            pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING)
 5712
 5713        return pivot
 5714
 5715    def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]:
 5716        return [agg.alias for agg in aggregations if agg.alias]
 5717
 5718    def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None:
 5719        if not skip_where_token and not self._match(TokenType.PREWHERE):
 5720            return None
 5721
 5722        comments = self._prev_comments
 5723        return self.expression(
 5724            exp.PreWhere(this=self._parse_disjunction()),
 5725            comments=comments,
 5726        )
 5727
 5728    def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None:
 5729        if not skip_where_token and not self._match(TokenType.WHERE):
 5730            return None
 5731
 5732        comments = self._prev_comments
 5733        return self.expression(
 5734            exp.Where(this=self._parse_disjunction()),
 5735            comments=comments,
 5736        )
 5737
 5738    def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None:
 5739        if not skip_group_by_token and not self._match(TokenType.GROUP_BY):
 5740            return None
 5741        comments = self._prev_comments
 5742
 5743        elements: dict[str, t.Any] = defaultdict(list)
 5744
 5745        if self._match(TokenType.ALL):
 5746            elements["all"] = True
 5747        elif self._match(TokenType.DISTINCT):
 5748            elements["all"] = False
 5749
 5750        while True:
 5751            # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW,
 5752            # which are also valid identifiers
 5753            if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False):
 5754                break
 5755
 5756            elements["expressions"].extend(
 5757                self._parse_csv(
 5758                    lambda: (
 5759                        self._parse_grouping_sets()
 5760                        or self._parse_cube_or_rollup()
 5761                        or self._parse_disjunction()
 5762                    )
 5763                )
 5764            )
 5765
 5766            before_with_index = self._index
 5767
 5768            if self._match(TokenType.WITH) and (
 5769                cube_or_rollup := self._parse_cube_or_rollup(with_prefix=True)
 5770            ):
 5771                key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube"
 5772                elements[key].append(cube_or_rollup)
 5773            elif grouping_sets := self._parse_grouping_sets():
 5774                # Hive-style suffix syntax: GROUP BY a, b GROUPING SETS (...)
 5775                elements["grouping_sets"].append(grouping_sets)
 5776                break
 5777            elif self._match_text_seq("TOTALS"):
 5778                elements["totals"] = True  # type: ignore
 5779
 5780            if before_with_index <= self._index <= before_with_index + 1:
 5781                self._retreat(before_with_index)
 5782                break
 5783
 5784        return self.expression(exp.Group(**elements), comments=comments)  # type: ignore
 5785
 5786    def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None:
 5787        if self._match(TokenType.CUBE):
 5788            kind: type[exp.Cube | exp.Rollup] = exp.Cube
 5789        elif self._match(TokenType.ROLLUP):
 5790            kind = exp.Rollup
 5791        else:
 5792            return None
 5793
 5794        return self.expression(
 5795            kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise))
 5796        )
 5797
 5798    def _parse_grouping_sets(self) -> exp.GroupingSets | None:
 5799        if self._match(TokenType.GROUPING_SETS):
 5800            return self.expression(
 5801                exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set))
 5802            )
 5803        return None
 5804
 5805    def _parse_grouping_set(self) -> exp.Expr | None:
 5806        return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise()
 5807
 5808    def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None:
 5809        if not skip_having_token and not self._match(TokenType.HAVING):
 5810            return None
 5811        comments = self._prev_comments
 5812        return self.expression(
 5813            exp.Having(this=self._parse_disjunction()),
 5814            comments=comments,
 5815        )
 5816
 5817    def _parse_qualify(self) -> exp.Qualify | None:
 5818        if not self._match(TokenType.QUALIFY):
 5819            return None
 5820        return self.expression(exp.Qualify(this=self._parse_disjunction()))
 5821
 5822    def _parse_connect_with_prior(self) -> exp.Expr | None:
 5823        self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression(
 5824            exp.Prior(this=self._parse_bitwise())
 5825        )
 5826        connect = self._parse_disjunction()
 5827        self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR")
 5828        return connect
 5829
 5830    def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None:
 5831        if skip_start_token:
 5832            start = None
 5833        elif self._match_text_seq("START", "WITH"):
 5834            start = self._parse_disjunction()
 5835        else:
 5836            return None
 5837
 5838        self._match(TokenType.CONNECT_BY)
 5839        nocycle = self._match_text_seq("NOCYCLE")
 5840        connect = self._parse_connect_with_prior()
 5841
 5842        if not start and self._match_text_seq("START", "WITH"):
 5843            start = self._parse_disjunction()
 5844
 5845        return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle))
 5846
 5847    def _parse_name_as_expression(self) -> exp.Expr | None:
 5848        this = self._parse_id_var(any_token=True)
 5849        if self._match(TokenType.ALIAS):
 5850            this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction()))
 5851        return this
 5852
 5853    def _parse_interpolate(self) -> list[exp.Expr] | None:
 5854        if self._match_text_seq("INTERPOLATE"):
 5855            return self._parse_wrapped_csv(self._parse_name_as_expression)
 5856        return None
 5857
 5858    def _parse_order(
 5859        self, this: exp.Expr | None = None, skip_order_token: bool = False
 5860    ) -> exp.Expr | None:
 5861        siblings = None
 5862        if not skip_order_token and not self._match(TokenType.ORDER_BY):
 5863            if not self._match(TokenType.ORDER_SIBLINGS_BY):
 5864                return this
 5865
 5866            siblings = True
 5867
 5868        comments = self._prev_comments
 5869        return self.expression(
 5870            exp.Order(
 5871                this=this,
 5872                expressions=self._parse_csv(self._parse_ordered),
 5873                siblings=siblings,
 5874            ),
 5875            comments=comments,
 5876        )
 5877
 5878    def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None:
 5879        if not self._match(token):
 5880            return None
 5881        return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered)))
 5882
 5883    def _parse_ordered(
 5884        self, parse_method: t.Callable[[], exp.Expr | None] | None = None
 5885    ) -> exp.Ordered | None:
 5886        this = parse_method() if parse_method else self._parse_disjunction()
 5887        if not this:
 5888            return None
 5889
 5890        if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL:
 5891            this = exp.var("ALL")
 5892
 5893        asc = self._match(TokenType.ASC)
 5894        desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None)
 5895
 5896        is_nulls_first = self._match_text_seq("NULLS", "FIRST")
 5897        is_nulls_last = self._match_text_seq("NULLS", "LAST")
 5898
 5899        nulls_first = is_nulls_first or False
 5900        explicitly_null_ordered = is_nulls_first or is_nulls_last
 5901
 5902        if (
 5903            not explicitly_null_ordered
 5904            and (
 5905                (not desc and self.dialect.NULL_ORDERING == "nulls_are_small")
 5906                or (desc and self.dialect.NULL_ORDERING != "nulls_are_small")
 5907            )
 5908            and self.dialect.NULL_ORDERING != "nulls_are_last"
 5909        ):
 5910            nulls_first = True
 5911
 5912        if self._match_text_seq("WITH", "FILL"):
 5913            with_fill = self.expression(
 5914                exp.WithFill(
 5915                    from_=self._match(TokenType.FROM) and self._parse_bitwise(),
 5916                    to=self._match_text_seq("TO") and self._parse_bitwise(),
 5917                    step=self._match_text_seq("STEP") and self._parse_bitwise(),
 5918                    interpolate=self._parse_interpolate(),
 5919                )
 5920            )
 5921        else:
 5922            with_fill = None
 5923
 5924        return self.expression(
 5925            exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill)
 5926        )
 5927
 5928    def _parse_limit_options(self) -> exp.LimitOptions | None:
 5929        percent = self._match_set((TokenType.PERCENT, TokenType.MOD))
 5930        rows = self._match_texts(("ROW", "ROWS"))
 5931        self._match_text_seq("ONLY")
 5932        with_ties = self._match_text_seq("WITH", "TIES")
 5933
 5934        if not (percent or rows or with_ties):
 5935            return None
 5936
 5937        return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties))
 5938
 5939    def _parse_limit(
 5940        self,
 5941        this: exp.Expr | None = None,
 5942        top: bool = False,
 5943        skip_limit_token: bool = False,
 5944    ) -> exp.Expr | None:
 5945        if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT):
 5946            comments = self._prev_comments
 5947            if top:
 5948                limit_paren = self._match(TokenType.L_PAREN)
 5949                expression = (
 5950                    self._parse_term() or self._parse_select()
 5951                    if limit_paren
 5952                    else self._parse_number()
 5953                )
 5954
 5955                if limit_paren:
 5956                    self._match_r_paren()
 5957
 5958            else:
 5959                if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL):
 5960                    expression = exp.var("ALL")
 5961                else:
 5962                    expression = self._parse_term(parse_mod=False)
 5963            limit_options = self._parse_limit_options()
 5964
 5965            if self._match(TokenType.COMMA):
 5966                offset = expression
 5967                expression = self._parse_term()
 5968            else:
 5969                offset = None
 5970
 5971            limit_exp = self.expression(
 5972                exp.Limit(
 5973                    this=this,
 5974                    expression=expression,
 5975                    offset=offset,
 5976                    limit_options=limit_options,
 5977                    expressions=self._parse_limit_by(),
 5978                ),
 5979                comments=comments,
 5980            )
 5981
 5982            if top:
 5983                limit_exp.meta["top"] = True
 5984
 5985            return limit_exp
 5986
 5987        if self._match(TokenType.FETCH):
 5988            direction = (
 5989                self._prev.text.upper()
 5990                if self._match_set((TokenType.FIRST, TokenType.NEXT))
 5991                else "FIRST"
 5992            )
 5993
 5994            count = (
 5995                None
 5996                if self._match_texts(("ROW", "ROWS"), advance=False)
 5997                else self._parse_field(tokens=self.FETCH_TOKENS)
 5998            )
 5999
 6000            return self.expression(
 6001                exp.Fetch(
 6002                    direction=direction, count=count, limit_options=self._parse_limit_options()
 6003                )
 6004            )
 6005
 6006        return this
 6007
 6008    def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6009        if not self._match(TokenType.OFFSET):
 6010            return this
 6011
 6012        count = self._parse_term()
 6013        self._match_texts(("ROW", "ROWS"))
 6014
 6015        return self.expression(
 6016            exp.Offset(this=this, expression=count, expressions=self._parse_limit_by())
 6017        )
 6018
 6019    def _can_parse_limit_or_offset(self) -> bool:
 6020        if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False):
 6021            return False
 6022
 6023        index = self._index
 6024        result = bool(
 6025            self._try_parse(self._parse_limit, retreat=True)
 6026            or self._try_parse(self._parse_offset, retreat=True)
 6027        )
 6028        self._retreat(index)
 6029
 6030        # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset
 6031        if self._next.token_type == TokenType.MATCH_CONDITION:
 6032            result = False
 6033
 6034        return result
 6035
 6036    def _can_parse_named_window(self) -> bool:
 6037        # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias.
 6038        # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`.
 6039        if not self._match(TokenType.WINDOW, advance=False):
 6040            return False
 6041
 6042        name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None
 6043        if name is None or name.token_type not in self.ID_VAR_TOKENS:
 6044            return False
 6045
 6046        alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None
 6047        if alias_tok is None or alias_tok.token_type != TokenType.ALIAS:
 6048            return False
 6049
 6050        body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None
 6051        return body is not None and body.token_type == TokenType.L_PAREN
 6052
 6053    def _parse_limit_by(self) -> list[exp.Expr] | None:
 6054        return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None
 6055
 6056    def _parse_locks(self) -> list[exp.Lock]:
 6057        locks = []
 6058        while True:
 6059            update, key = None, None
 6060            if self._match_text_seq("FOR", "UPDATE"):
 6061                update = True
 6062            elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq(
 6063                "LOCK", "IN", "SHARE", "MODE"
 6064            ):
 6065                update = False
 6066            elif self._match_text_seq("FOR", "KEY", "SHARE"):
 6067                update, key = False, True
 6068            elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"):
 6069                update, key = True, True
 6070            else:
 6071                break
 6072
 6073            expressions = None
 6074            if self._match_text_seq("OF"):
 6075                expressions = self._parse_csv(lambda: self._parse_table(schema=True))
 6076
 6077            wait: bool | exp.Expr | None = None
 6078            if self._match_text_seq("NOWAIT"):
 6079                wait = True
 6080            elif self._match_text_seq("WAIT"):
 6081                wait = self._parse_primary()
 6082            elif self._match_text_seq("SKIP", "LOCKED"):
 6083                wait = False
 6084
 6085            locks.append(
 6086                self.expression(
 6087                    exp.Lock(update=update, expressions=expressions, wait=wait, key=key)
 6088                )
 6089            )
 6090
 6091        return locks
 6092
 6093    def parse_set_operation(
 6094        self, this: exp.Expr | None, consume_pipe: bool = False, intersect_only: bool = False
 6095    ) -> exp.SetOperation | None:
 6096        start = self._index
 6097        _, side_token, kind_token = self._parse_join_parts()
 6098
 6099        side = side_token.text if side_token else None
 6100        kind = kind_token.text if kind_token else None
 6101
 6102        if not self._match_set(self.SET_OPERATIONS) or (
 6103            intersect_only and self._prev.token_type != TokenType.INTERSECT
 6104        ):
 6105            self._retreat(start)
 6106            return None
 6107
 6108        token_type = self._prev.token_type
 6109
 6110        if token_type == TokenType.UNION:
 6111            operation: type[exp.SetOperation] = exp.Union
 6112        elif token_type == TokenType.EXCEPT:
 6113            operation = exp.Except
 6114        else:
 6115            operation = exp.Intersect
 6116
 6117        comments = self._prev.comments
 6118
 6119        if self._match(TokenType.DISTINCT):
 6120            distinct: bool | None = True
 6121        elif self._match(TokenType.ALL):
 6122            distinct = False
 6123        else:
 6124            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
 6125            if distinct is None:
 6126                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
 6127
 6128        by_name = (
 6129            self._match_text_seq("BY", "NAME")
 6130            or self._match_text_seq("STRICT", "CORRESPONDING")
 6131            or None
 6132        )
 6133        if self._match_text_seq("CORRESPONDING"):
 6134            by_name = True
 6135            if not side and not kind:
 6136                kind = "INNER"
 6137
 6138        on_column_list = None
 6139        if by_name and self._match_texts(("ON", "BY")):
 6140            on_column_list = self._parse_wrapped_csv(self._parse_column)
 6141
 6142        expression = self._parse_select(
 6143            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
 6144        )
 6145
 6146        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
 6147        # in _parse_cte and so that alias pushdown can reach into set operation branches
 6148        if isinstance(this, exp.Values):
 6149            this = self._values_to_select(this)
 6150        if isinstance(expression, exp.Values):
 6151            expression = self._values_to_select(expression)
 6152        for operand in (this, expression):
 6153            if isinstance(operand, exp.Subquery) and isinstance(
 6154                values := operand.unnest(), exp.Values
 6155            ):
 6156                wrapper = t.cast(exp.Subquery, values.parent)
 6157                wrapper.set("this", self._values_to_select(values))
 6158
 6159        if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery):
 6160            subquery = this.this
 6161            subquery.set("alias", exp.TableAlias(this=this.args["alias"]))
 6162            subquery.add_comments(this.pop_comments())
 6163            this = subquery
 6164
 6165        return self.expression(
 6166            operation(
 6167                this=this,
 6168                distinct=distinct,
 6169                by_name=by_name,
 6170                expression=expression,
 6171                side=side,
 6172                kind=kind,
 6173                on=on_column_list,
 6174            ),
 6175            comments=comments,
 6176        )
 6177
 6178    def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None:
 6179        intersect_binds_tighter = self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT
 6180        last_setop: exp.SetOperation | None = None
 6181
 6182        while this:
 6183            setop = self.parse_set_operation(this)
 6184            if not setop:
 6185                break
 6186
 6187            last_setop = setop
 6188            if (
 6189                isinstance(setop, exp.Intersect)
 6190                and isinstance(setop.this, (exp.Union, exp.Except))
 6191                and not intersect_binds_tighter
 6192            ):
 6193                setop.set("this", setop.this.subquery(copy=False))
 6194            elif not isinstance(setop, exp.Intersect) and intersect_binds_tighter:
 6195                # INTERSECT binds tighter than this UNION / EXCEPT, so any INTERSECTs that
 6196                # follow it group with its right operand first:
 6197                # a UNION b INTERSECT c INTERSECT d -> a UNION (b INTERSECT c INTERSECT d)
 6198                operand = setop.expression
 6199                while intersect := self.parse_set_operation(operand, intersect_only=True):
 6200                    last_setop = operand = intersect
 6201                if operand is not setop.expression:
 6202                    setop.set("expression", operand.subquery(copy=False))
 6203
 6204            this = setop
 6205
 6206        if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP:
 6207            expression = last_setop.expression if last_setop else None
 6208
 6209            if expression:
 6210                for arg in self.SET_OP_MODIFIERS:
 6211                    expr = expression.args.get(arg)
 6212                    if expr and not (arg == "limit" and expr.meta.get("top")):
 6213                        expression.set(arg, None)
 6214                        this.set(arg, expr)
 6215
 6216            # Consume modifiers after a parenthesized final operand (e.g., SELECT 1 UNION (SELECT 2)
 6217            # ORDER BY 1), or a LIMIT/FETCH that a final operand with TOP leaves for the whole
 6218            # result (e.g., SELECT 1 UNION SELECT TOP 2 2 LIMIT 1).
 6219            if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):
 6220                this = self._parse_query_modifiers(this)
 6221
 6222        return this
 6223
 6224    def _parse_expression(self) -> exp.Expr | None:
 6225        return self._parse_alias(self._parse_assignment())
 6226
 6227    def _parse_assignment(self) -> exp.Expr | None:
 6228        this = self._parse_disjunction()
 6229        if not this and self._next.token_type in self.ASSIGNMENT:
 6230            # This allows us to parse <non-identifier token> := <expr>
 6231            this = exp.column(
 6232                t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text)
 6233            )
 6234
 6235        while self._match_set(self.ASSIGNMENT):
 6236            if isinstance(this, exp.Column) and len(this.parts) == 1:
 6237                this = this.this
 6238
 6239            comments = self._prev_comments
 6240            this = self.expression(
 6241                self.ASSIGNMENT[self._prev.token_type](
 6242                    this=this, expression=self._parse_assignment()
 6243                ),
 6244                comments=comments,
 6245            )
 6246
 6247        return this
 6248
 6249    def _parse_disjunction(self) -> exp.Expr | None:
 6250        this = self._parse_conjunction()
 6251        while self._match_set(self.DISJUNCTION):
 6252            comments = self._prev_comments
 6253            this = self.expression(
 6254                self.DISJUNCTION[self._prev.token_type](
 6255                    this=this, expression=self._parse_conjunction()
 6256                ),
 6257                comments=comments,
 6258            )
 6259        return this
 6260
 6261    def _parse_conjunction(self) -> exp.Expr | None:
 6262        this = self._parse_equality()
 6263        while self._match_set(self.CONJUNCTION):
 6264            comments = self._prev_comments
 6265            this = self.expression(
 6266                self.CONJUNCTION[self._prev.token_type](
 6267                    this=this, expression=self._parse_equality()
 6268                ),
 6269                comments=comments,
 6270            )
 6271        return this
 6272
 6273    def _build_predicate(
 6274        self,
 6275        klass: Type[exp.Binary],
 6276        this: exp.Expr | None,
 6277        expression: exp.Expr | None,
 6278        comments: list[str] | None = None,
 6279        negate: bool = False,
 6280    ) -> exp.Expr:
 6281        is_predicates = (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ)
 6282        if klass in is_predicates:
 6283            if (
 6284                isinstance(this, (exp.Connector, exp.Not, exp.NullSafeNEQ))
 6285                or type(this) in self.EQUALITY.values()
 6286                or type(this) in self.COMPARISON.values()
 6287                or (klass is not exp.Is and isinstance(this, exp.Is))
 6288            ):
 6289                this = exp.Paren(this=this)
 6290            if isinstance(expression, (exp.Predicate, exp.Connector, exp.Not, exp.Escape)):
 6291                expression = exp.Paren(this=expression)
 6292        else:
 6293            if isinstance(this.this if isinstance(this, exp.Not) else this, is_predicates):
 6294                this = exp.Paren(this=this)
 6295            if isinstance(
 6296                expression.this if isinstance(expression, exp.Not) else expression, is_predicates
 6297            ):
 6298                expression = exp.Paren(this=expression)
 6299
 6300        predicate = klass(this=this, expression=expression)
 6301        if negate:
 6302            predicate.set("negate", True)
 6303        return self.expression(predicate, comments=comments)
 6304
 6305    def _parse_equality(self, parse_is: bool = True) -> exp.Expr | None:
 6306        this = self._parse_comparison()
 6307        while True:
 6308            if self._match_set(self.EQUALITY):
 6309                comments = self._prev_comments
 6310                this = self._build_predicate(
 6311                    self.EQUALITY[self._prev.token_type],
 6312                    this,
 6313                    self._parse_comparison(),
 6314                    comments=comments,
 6315                )
 6316            elif parse_is and self._is_predicate_ahead():
 6317                index = self._index
 6318                # A postfix predicate can itself be an operand, e.g. x IS NULL = FALSE.
 6319                this = self._parse_comparison(self._parse_range(this, parse_is=True))
 6320                if self._index == index:
 6321                    break
 6322            else:
 6323                break
 6324        return this
 6325
 6326    def _parse_comparison(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6327        this = this or self._parse_range()
 6328        while self._match_set(self.COMPARISON):
 6329            comments = self._prev_comments
 6330            this = self._build_predicate(
 6331                self.COMPARISON[self._prev.token_type],
 6332                this,
 6333                self._parse_range(),
 6334                comments=comments,
 6335            )
 6336        return this
 6337
 6338    def _is_predicate_ahead(self) -> bool:
 6339        if (
 6340            self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.HIGH
 6341            or self._curr.token_type not in self.IS_PREDICATE_TOKENS
 6342        ):
 6343            return False
 6344        return self._curr.token_type != TokenType.NOT or self._next.token_type == TokenType.NULL
 6345
 6346    def _parse_range(self, this: exp.Expr | None = None, parse_is: bool = False) -> exp.Expr | None:
 6347        this = this or self._parse_bitwise()
 6348
 6349        while True:
 6350            if not parse_is and self._is_predicate_ahead():
 6351                break
 6352            negate = self._match(TokenType.NOT)
 6353            if self._match_set(self.RANGE_PARSERS):
 6354                expression = self.RANGE_PARSERS[self._prev.token_type](self, this)
 6355                if not expression:
 6356                    return this
 6357
 6358                this = expression
 6359            elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)):
 6360                this = self._build_predicate(exp.Is, this, exp.Null())
 6361            elif self._match(TokenType.NOTNULL):
 6362                # Postgres supports ISNULL and NOTNULL for conditions.
 6363                # https://blog.andreiavram.ro/postgresql-null-composite-type/
 6364                if self.dialect.NORMALIZE_NOT_NULL:
 6365                    this = self._build_predicate(exp.Is, this, exp.Null())
 6366                    this = self.expression(exp.Not(this=this))
 6367                else:
 6368                    this = self._build_predicate(exp.Is, this, exp.Null(), negate=True)
 6369            else:
 6370                if negate:
 6371                    self._retreat(self._index - 1)
 6372                break
 6373
 6374            if negate:
 6375                this = self._negate_range(this)
 6376            if (negate or (isinstance(this, exp.Not) and isinstance(this.this, exp.Is))) and (
 6377                self._curr.token_type == TokenType.NOT
 6378                or self._curr.token_type in self.RANGE_PARSERS
 6379            ):
 6380                this = self.expression(exp.Paren(this=this))
 6381
 6382        return this
 6383
 6384    def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6385        if not this:
 6386            return this
 6387
 6388        expression = this.this if isinstance(this, exp.Escape) else this
 6389        if isinstance(expression, (exp.Like, exp.ILike)):
 6390            expression.set("negate", True)
 6391            return this
 6392
 6393        return self.expression(exp.Not(this=this))
 6394
 6395    def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None:
 6396        index = self._index - 1
 6397        negate = self._match(TokenType.NOT)
 6398
 6399        if self._match_text_seq("DISTINCT", "FROM"):
 6400            klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ
 6401            return self._build_predicate(klass, this, self._parse_is_rhs(distinct=True))
 6402
 6403        if self._match(TokenType.JSON):
 6404            kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper()
 6405
 6406            if self._match_text_seq("WITH"):
 6407                _with = True
 6408            elif self._match_text_seq("WITHOUT"):
 6409                _with = False
 6410            else:
 6411                _with = None
 6412
 6413            unique = self._match(TokenType.UNIQUE)
 6414            self._match_text_seq("KEYS")
 6415            expression: exp.Expr | None = self.expression(
 6416                exp.JSON(this=kind, with_=_with, unique=unique)
 6417            )
 6418        else:
 6419            expression = self._parse_is_rhs()
 6420            if not expression:
 6421                self._retreat(index)
 6422                return None
 6423
 6424        if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL:
 6425            this = self._build_predicate(exp.Is, this, expression, negate=True)
 6426        else:
 6427            this = self._build_predicate(exp.Is, this, expression)
 6428            this = self.expression(exp.Not(this=this)) if negate else this
 6429
 6430        return self._parse_column_ops(this)
 6431
 6432    def _parse_is_rhs(self, distinct: bool = False) -> exp.Expr | None:
 6433        if distinct:
 6434            if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.LOW:
 6435                return self._parse_equality(parse_is=False)
 6436            if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.EQUALITY:
 6437                return self._parse_comparison()
 6438            if self.IS_PREDICATE_PRECEDENCE is IsPredicatePrecedence.COMPARISON:
 6439                return self._parse_range()
 6440            return self._parse_bitwise()
 6441        return self._parse_null() or self._parse_bitwise()
 6442
 6443    def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In:
 6444        unnest = self._parse_unnest(with_alias=False)
 6445        if unnest:
 6446            this = self.expression(exp.In(this=this, unnest=unnest))
 6447        elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)):
 6448            matched_l_paren = self._prev.token_type == TokenType.L_PAREN
 6449            expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias))
 6450
 6451            if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query):
 6452                this = self.expression(
 6453                    exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False))
 6454                )
 6455            else:
 6456                this = self.expression(exp.In(this=this, expressions=expressions))
 6457
 6458            if matched_l_paren:
 6459                self._match_r_paren(this)
 6460            elif not self._match(TokenType.R_BRACKET, expression=this):
 6461                self.raise_error("Expecting ]")
 6462        else:
 6463            this = self.expression(exp.In(this=this, field=self._parse_column()))
 6464
 6465        return this
 6466
 6467    def _parse_between(self, this: exp.Expr | None) -> exp.Between:
 6468        # Preserve grouping when BETWEEN is rewritten into comparisons.
 6469        if isinstance(this, (exp.Is, exp.NullSafeEQ, exp.NullSafeNEQ)):
 6470            this = exp.Paren(this=this)
 6471
 6472        symmetric = None
 6473        if self._match_text_seq("SYMMETRIC"):
 6474            symmetric = True
 6475        elif self._match_text_seq("ASYMMETRIC"):
 6476            symmetric = False
 6477
 6478        low = self._parse_bitwise()
 6479        self._match(TokenType.AND)
 6480        high = self._parse_bitwise()
 6481
 6482        return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric))
 6483
 6484    def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None:
 6485        if not self._match(TokenType.ESCAPE):
 6486            return this
 6487        return self.expression(
 6488            exp.Escape(this=this, expression=self._parse_string() or self._parse_null())
 6489        )
 6490
 6491    def _parse_interval_span(
 6492        self, this: exp.Expr, parse_function_unit: bool = True
 6493    ) -> exp.Interval:
 6494        # handle day-time format interval span with omitted units:
 6495        #   INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`>
 6496        interval_span_units_omitted = None
 6497        fraction_in_string = False
 6498        if (
 6499            this
 6500            and this.is_string
 6501            and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
 6502            and exp.INTERVAL_DAY_TIME_RE.match(this.name)
 6503        ):
 6504            index = self._index
 6505
 6506            # Var "TO" Var
 6507            first_unit = self._parse_var(any_token=True, upper=True)
 6508            second_unit = None
 6509            if first_unit and self._match_text_seq("TO"):
 6510                second_unit = self._parse_var(any_token=True, upper=True)
 6511
 6512            interval_span_units_omitted = not (first_unit and second_unit)
 6513
 6514            self._retreat(index)
 6515
 6516        unit_index = self._index
 6517        if interval_span_units_omitted:
 6518            unit = None
 6519        else:
 6520            # Only attempt to parse a unit if the current token can actually be one, so that a
 6521            # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x)
 6522            is_unit = self._curr is not None and (
 6523                self._curr.token_type == TokenType.VAR
 6524                or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS
 6525            )
 6526            unit = self._parse_function() if parse_function_unit and is_unit else None
 6527            if not unit and is_unit:
 6528                unit = self._parse_var(any_token=True, upper=True)
 6529
 6530        # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse
 6531        # each INTERVAL expression into this canonical form so it's easy to transpile
 6532        if this and this.is_number:
 6533            try:
 6534                this = exp.Literal.string(this.to_py())
 6535            except ValueError:
 6536                self.raise_error(f"Invalid numeric interval literal: {this.name!r}")
 6537        elif this and this.is_string:
 6538            parts = exp.INTERVAL_STRING_RE.findall(this.name)
 6539            if parts and unit:
 6540                # Unconsume the eagerly-parsed unit, since the real unit was part of the string
 6541                unit = None
 6542                self._retreat(unit_index)
 6543
 6544            if len(parts) == 1:
 6545                this = exp.Literal.string(parts[0][0])
 6546                unit = self.expression(exp.Var(this=parts[0][1].upper()))
 6547                fraction_in_string = "." in parts[0][0]
 6548
 6549        if self.INTERVAL_SPANS and self._match_text_seq("TO"):
 6550            unit = self.expression(
 6551                exp.IntervalSpan(
 6552                    this=unit,
 6553                    expression=self._parse_function()
 6554                    or self._parse_var(any_token=True, upper=True),
 6555                )
 6556            )
 6557
 6558        return self.expression(
 6559            exp.Interval(this=this, unit=unit, fraction_in_string=fraction_in_string or None)
 6560        )
 6561
 6562    def _parse_interval(
 6563        self, require_interval: bool = True, parse_function_unit: bool = True
 6564    ) -> exp.Add | exp.Interval | None:
 6565        index = self._index
 6566
 6567        if not self._match(TokenType.INTERVAL) and require_interval:
 6568            return None
 6569
 6570        if self._match(TokenType.STRING, advance=False):
 6571            this = self._parse_primary()
 6572        else:
 6573            this = self._parse_term()
 6574
 6575        if not this or (
 6576            isinstance(this, exp.Column)
 6577            and not this.table
 6578            and not this.this.quoted
 6579            and self._curr
 6580            and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS
 6581        ):
 6582            self._retreat(index)
 6583            return None
 6584
 6585        interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit)
 6586
 6587        index = self._index
 6588        self._match(TokenType.PLUS)
 6589
 6590        # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals
 6591        if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False):
 6592            return self.expression(
 6593                exp.Add(
 6594                    this=interval,
 6595                    expression=self._parse_interval(False, parse_function_unit=parse_function_unit),
 6596                )
 6597            )
 6598
 6599        self._retreat(index)
 6600        return interval
 6601
 6602    def _parse_bitwise(self) -> exp.Expr | None:
 6603        this = self._parse_term()
 6604
 6605        while True:
 6606            if self._match_set(self.BITWISE):
 6607                this = self.expression(
 6608                    self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term())
 6609                )
 6610            elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE):
 6611                this = self.expression(
 6612                    exp.DPipe(
 6613                        this=this,
 6614                        expression=self._parse_term(),
 6615                        safe=not self.dialect.STRICT_STRING_CONCAT,
 6616                    )
 6617                )
 6618            elif self._match(TokenType.DQMARK):
 6619                this = self.expression(
 6620                    exp.Coalesce(this=this, expressions=ensure_list(self._parse_term()))
 6621                )
 6622            elif self._match_pair(TokenType.LT, TokenType.LT):
 6623                this = self.expression(
 6624                    exp.BitwiseLeftShift(this=this, expression=self._parse_term())
 6625                )
 6626            elif self._match_pair(TokenType.GT, TokenType.GT):
 6627                this = self.expression(
 6628                    exp.BitwiseRightShift(this=this, expression=self._parse_term())
 6629                )
 6630            elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS):
 6631                this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term())
 6632            else:
 6633                break
 6634
 6635        return this
 6636
 6637    def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None:
 6638        this = self._parse_factor(parse_mod=parse_mod)
 6639
 6640        while self._match_set(self.TERM):
 6641            klass = self.TERM[self._prev.token_type]
 6642            comments = self._prev_comments
 6643            expression = self._parse_factor(parse_mod=parse_mod)
 6644
 6645            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6646
 6647            if isinstance(this, exp.Collate):
 6648                self._normalize_collate(this)
 6649
 6650        return this
 6651
 6652    def _normalize_collate(self, collate: exp.Collate) -> None:
 6653        expr = collate.expression
 6654
 6655        # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise
 6656        # fallback to Identifier / Var
 6657        if isinstance(expr, exp.Column) and len(expr.parts) == 1:
 6658            ident = expr.this
 6659            if isinstance(ident, exp.Identifier):
 6660                collate.set("expression", ident if ident.quoted else exp.var(ident.name))
 6661
 6662    def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None:
 6663        parse_method = self._parse_factor_operand
 6664        this = self._parse_at_time_zone(parse_method())
 6665
 6666        while self._match_set(self.FACTOR, advance=False):
 6667            if not parse_mod and self._curr.token_type == TokenType.MOD:
 6668                break
 6669
 6670            self._advance()
 6671            klass = self.FACTOR[self._prev.token_type]
 6672            comments = self._prev_comments
 6673            expression = parse_method()
 6674
 6675            if not expression and klass is exp.IntDiv and self._prev.text.isalpha():
 6676                self._retreat(self._index - 1)
 6677                return this
 6678
 6679            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6680
 6681            if isinstance(this, exp.Div):
 6682                this.set("typed", self.dialect.TYPED_DIVISION)
 6683                this.set("safe", self.dialect.SAFE_DIVISION)
 6684
 6685        return this
 6686
 6687    def _parse_factor_operand(self) -> exp.Expr | None:
 6688        return self._parse_exponent() if self.EXPONENT else self._parse_unary()
 6689
 6690    def _parse_exponent(self) -> exp.Expr | None:
 6691        this = self._parse_unary()
 6692        while self._match_set(self.EXPONENT):
 6693            comments = self._prev_comments
 6694            this = self.expression(
 6695                self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()),
 6696                comments=comments,
 6697            )
 6698        return this
 6699
 6700    def _parse_unary(self) -> exp.Expr | None:
 6701        if self._match_set(self.UNARY_PARSERS):
 6702            return self.UNARY_PARSERS[self._prev.token_type](self)
 6703        return self._parse_type()
 6704
 6705    def _parse_type(
 6706        self, parse_interval: bool = True, fallback_to_identifier: bool = False
 6707    ) -> exp.Expr | None:
 6708        if not fallback_to_identifier and (atom := self._parse_atom()) is not None:
 6709            return atom
 6710
 6711        if interval := parse_interval and self._parse_interval():
 6712            return self._parse_column_ops(interval)
 6713
 6714        index = self._index
 6715        data_type = self._parse_types(check_func=True, allow_identifiers=False)
 6716
 6717        # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g.
 6718        # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>)
 6719        if isinstance(data_type, exp.Cast):
 6720            # This constructor can contain ops directly after it, for instance struct unnesting:
 6721            # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).*
 6722            return self._parse_column_ops(data_type)
 6723
 6724        if data_type:
 6725            index2 = self._index
 6726            this = self._parse_primary()
 6727
 6728            if isinstance(this, exp.Literal):
 6729                literal = this.name
 6730                this = self._parse_column_ops(this)
 6731
 6732                if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal):
 6733                    if data_type.is_type(exp.DType.TIMESTAMP):
 6734                        data_type = exp.DType.TIMESTAMPTZ.into_expr()
 6735                    elif data_type.is_type(exp.DType.TIME):
 6736                        data_type = exp.DType.TIMETZ.into_expr()
 6737
 6738                parser = self.TYPE_LITERAL_PARSERS.get(data_type.this)
 6739                if parser:
 6740                    return parser(self, this, data_type)
 6741
 6742                return self.expression(exp.Cast(this=this, to=data_type))
 6743
 6744            # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0)
 6745            # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 )
 6746            #
 6747            # If the index difference here is greater than 1, that means the parser itself must have
 6748            # consumed additional tokens such as the DECIMAL scale and precision in the above example.
 6749            #
 6750            # If it's not greater than 1, then it must be 1, because we've consumed at least the type
 6751            # keyword, meaning that the expressions arg of the DataType must have gotten set by a
 6752            # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to
 6753            # DECIMAL(38, 0)) in order to facilitate the data type's transpilation.
 6754            #
 6755            # In these cases, we don't really want to return the converted type, but instead retreat
 6756            # and try to parse a Column or Identifier in the section below.
 6757            if data_type.expressions and index2 - index > 1:
 6758                self._retreat(index2)
 6759                return self._parse_column_ops(data_type)
 6760
 6761            self._retreat(index)
 6762
 6763        if fallback_to_identifier:
 6764            return self._parse_id_var()
 6765
 6766        return self._parse_column()
 6767
 6768    def _parse_type_size(self) -> exp.DataTypeParam | None:
 6769        this = self._parse_type()
 6770        if not this:
 6771            return None
 6772
 6773        if isinstance(this, exp.Column) and not this.table:
 6774            this = exp.var(this.name.upper())
 6775
 6776        return self.expression(
 6777            exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True))
 6778        )
 6779
 6780    def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None:
 6781        type_name = identifier.name
 6782
 6783        while self._match(TokenType.DOT):
 6784            type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
 6785
 6786        return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True)
 6787
 6788    def _parse_types(
 6789        self,
 6790        check_func: bool = False,
 6791        schema: bool = False,
 6792        allow_identifiers: bool = True,
 6793        with_collation: bool = False,
 6794    ) -> exp.Expr | None:
 6795        index = self._index
 6796        this: exp.Expr | None = None
 6797
 6798        if self._match_set(self.TYPE_TOKENS):
 6799            type_token = self._prev.token_type
 6800        else:
 6801            type_token = None
 6802            identifier = allow_identifiers and self._parse_id_var(
 6803                any_token=False, tokens=(TokenType.VAR,)
 6804            )
 6805            if isinstance(identifier, exp.Identifier):
 6806                if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE:
 6807                    this = exp.DataType.build(identifier, udt=True)
 6808                else:
 6809                    try:
 6810                        tokens = self.dialect.tokenize(identifier.name)
 6811                    except TokenError:
 6812                        tokens = None
 6813
 6814                    if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS:
 6815                        if len(tokens) > 1:
 6816                            return exp.DataType.from_str(identifier.name, dialect=self.dialect)
 6817                    elif self.dialect.SUPPORTS_USER_DEFINED_TYPES:
 6818                        this = self._parse_user_defined_type(identifier)
 6819                    else:
 6820                        self._retreat(self._index - 1)
 6821                        return None
 6822            else:
 6823                return None
 6824
 6825        if type_token == TokenType.PSEUDO_TYPE:
 6826            return self.expression(exp.PseudoType(this=self._prev.text.upper()))
 6827
 6828        if type_token == TokenType.OBJECT_IDENTIFIER:
 6829            return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper()))
 6830
 6831        # https://materialize.com/docs/sql/types/map/
 6832        if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET):
 6833            key_type = self._parse_types(
 6834                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6835            )
 6836            if not self._match(TokenType.FARROW):
 6837                self._retreat(index)
 6838                return None
 6839
 6840            value_type = self._parse_types(
 6841                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6842            )
 6843            if not self._match(TokenType.R_BRACKET):
 6844                self._retreat(index)
 6845                return None
 6846
 6847            return exp.DataType(
 6848                this=exp.DType.MAP,
 6849                expressions=[key_type, value_type],
 6850                nested=True,
 6851            )
 6852
 6853        nested = type_token in self.NESTED_TYPE_TOKENS
 6854        is_struct = type_token in self.STRUCT_TYPE_TOKENS
 6855        is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS
 6856        expressions = None
 6857        maybe_func = False
 6858
 6859        if self._match(TokenType.L_PAREN):
 6860            if is_struct:
 6861                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6862            elif nested:
 6863                expressions = self._parse_csv(
 6864                    lambda: self._parse_types(
 6865                        check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6866                    )
 6867                )
 6868                if type_token == TokenType.NULLABLE and len(expressions) == 1:
 6869                    this = expressions[0]
 6870                    this.set("nullable", True)
 6871                    self._match_r_paren()
 6872                    return this
 6873            elif type_token in self.ENUM_TYPE_TOKENS:
 6874                expressions = self._parse_csv(self._parse_equality)
 6875            elif type_token == TokenType.REF:
 6876                # REF(schema.customer) names a target; REF(1) can still be a function call.
 6877                target = self._try_parse(lambda: self._parse_table_parts(schema=True))
 6878                if target is None:
 6879                    self._retreat(index)
 6880                    return None
 6881                expressions = [target]
 6882            elif type_token == TokenType.JSON:
 6883                # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value)
 6884                # https://clickhouse.com/docs/sql-reference/data-types/newjson
 6885                expressions = self._parse_csv(self._parse_json_type_arg)
 6886            elif is_aggregate:
 6887                func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var(
 6888                    any_token=False, tokens=(TokenType.VAR, TokenType.ANY)
 6889                )
 6890                if not func_or_ident:
 6891                    return None
 6892                expressions = [func_or_ident]
 6893                if self._match(TokenType.COMMA):
 6894                    expressions.extend(
 6895                        self._parse_csv(
 6896                            lambda: self._parse_types(
 6897                                check_func=check_func,
 6898                                schema=schema,
 6899                                allow_identifiers=allow_identifiers,
 6900                            )
 6901                        )
 6902                    )
 6903            else:
 6904                expressions = self._parse_csv(self._parse_type_size)
 6905
 6906                # https://docs.snowflake.com/en/sql-reference/data-types-vector
 6907                if type_token == TokenType.VECTOR and len(expressions) == 2:
 6908                    expressions = self._parse_vector_expressions(expressions)
 6909
 6910            if not self._match(TokenType.R_PAREN):
 6911                self._retreat(index)
 6912                return None
 6913
 6914            maybe_func = True
 6915
 6916        values: list[exp.Expr] | None = None
 6917
 6918        if nested and self._match(TokenType.LT):
 6919            if is_struct:
 6920                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6921            else:
 6922                expressions = self._parse_csv(
 6923                    lambda: self._parse_types(
 6924                        check_func=check_func,
 6925                        schema=schema,
 6926                        allow_identifiers=allow_identifiers,
 6927                        with_collation=True,
 6928                    )
 6929                )
 6930
 6931            if not self._match(TokenType.GT):
 6932                self.raise_error("Expecting >")
 6933
 6934            if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)):
 6935                values = self._parse_csv(self._parse_disjunction)
 6936                if not values and is_struct:
 6937                    values = None
 6938                    self._retreat(self._index - 1)
 6939                else:
 6940                    self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN))
 6941
 6942        if type_token in self.TIMESTAMPS:
 6943            if self._match_text_seq("WITH", "TIME", "ZONE"):
 6944                maybe_func = False
 6945                tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ
 6946                this = exp.DataType(this=tz_type, expressions=expressions)
 6947            elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"):
 6948                maybe_func = False
 6949                this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions)
 6950            elif self._match_text_seq("WITHOUT", "TIME", "ZONE"):
 6951                maybe_func = False
 6952        elif type_token == TokenType.INTERVAL:
 6953            if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS:
 6954                unit = self._parse_var(upper=True)
 6955                if self._match_text_seq("TO"):
 6956                    unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True))
 6957
 6958                this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit))))
 6959            else:
 6960                this = self.expression(exp.DataType(this=exp.DType.INTERVAL))
 6961        elif type_token == TokenType.VOID:
 6962            this = exp.DataType(this=exp.DType.NULL)
 6963
 6964        if maybe_func and check_func:
 6965            index2 = self._index
 6966            peek = self._parse_string()
 6967
 6968            if not peek:
 6969                self._retreat(index)
 6970                return None
 6971
 6972            self._retreat(index2)
 6973
 6974        if not this:
 6975            assert type_token is not None
 6976            if self._match_text_seq("UNSIGNED"):
 6977                unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token)
 6978                if not unsigned_type_token:
 6979                    self.raise_error(f"Cannot convert {type_token.name} to unsigned.")
 6980
 6981                type_token = unsigned_type_token or type_token
 6982
 6983            # NULLABLE without parentheses can be a column (Presto/Trino)
 6984            if type_token == TokenType.NULLABLE and not expressions:
 6985                self._retreat(index)
 6986                return None
 6987
 6988            this = exp.DataType(
 6989                this=exp.DType[type_token.name],
 6990                expressions=expressions,
 6991                nested=nested,
 6992            )
 6993
 6994            # Empty arrays/structs are allowed
 6995            if values is not None:
 6996                cls = exp.Struct if is_struct else exp.Array
 6997                this = exp.cast(cls(expressions=values), this, copy=False)
 6998
 6999        elif expressions:
 7000            this.set("expressions", expressions)
 7001
 7002        # https://materialize.com/docs/sql/types/list/#type-name
 7003        while self._match(TokenType.LIST):
 7004            this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True)
 7005
 7006        index = self._index
 7007
 7008        # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3]
 7009        matched_array = self._match(TokenType.ARRAY)
 7010
 7011        while self._curr:
 7012            datatype_token = self._prev.token_type
 7013            matched_l_bracket = self._match(TokenType.L_BRACKET)
 7014
 7015            if (not matched_l_bracket and not matched_array) or (
 7016                datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET)
 7017            ):
 7018                # Postgres allows casting empty arrays such as ARRAY[]::INT[],
 7019                # not to be confused with the fixed size array parsing
 7020                break
 7021
 7022            matched_array = False
 7023            values = self._parse_csv(self._parse_disjunction) or None
 7024            if (
 7025                values
 7026                and not schema
 7027                and (
 7028                    not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS
 7029                    or datatype_token == TokenType.ARRAY
 7030                    or not self._match(TokenType.R_BRACKET, advance=False)
 7031                )
 7032            ):
 7033                # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB
 7034                # 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
 7035                self._retreat(index)
 7036                break
 7037
 7038            this = exp.DataType(
 7039                this=exp.DType.ARRAY, expressions=[this], values=values, nested=True
 7040            )
 7041            self._match(TokenType.R_BRACKET)
 7042
 7043        if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType):
 7044            converter = self.TYPE_CONVERTERS.get(this.this)
 7045            if converter:
 7046                this = converter(t.cast(exp.DataType, this))
 7047
 7048        if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE):
 7049            this.set("collate", self._parse_identifier() or self._parse_column())
 7050
 7051        return this
 7052
 7053    def _parse_json_type_arg(self) -> exp.Expr | None:
 7054        """Parse a single argument to ClickHouse's JSON type."""
 7055
 7056        # SKIP col or SKIP REGEXP 'pattern'
 7057        if self._match_text_seq("SKIP"):
 7058            regexp = self._match(TokenType.RLIKE)
 7059            arg = self._parse_column()
 7060            if isinstance(arg, exp.Column):
 7061                arg = arg.to_dot()
 7062            return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg))
 7063
 7064        param_or_col = self._parse_column()
 7065        if not isinstance(param_or_col, exp.Column):
 7066            return None
 7067
 7068        # Parameter: name=value (e.g., max_dynamic_paths=2)
 7069        if len(param_or_col.parts) == 1 and self._match(TokenType.EQ):
 7070            param = param_or_col.name
 7071            value = self._parse_primary()
 7072            return self.expression(exp.EQ(this=exp.var(param), expression=value))
 7073
 7074        # Column type hint: col_name Type
 7075        col = param_or_col.to_dot()
 7076        kind = self._parse_types(check_func=False, allow_identifiers=False)
 7077        return self.expression(exp.ColumnDef(this=col, kind=kind))
 7078
 7079    def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]:
 7080        return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]]
 7081
 7082    def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None:
 7083        index = self._index
 7084
 7085        if (
 7086            self._curr
 7087            and self._next
 7088            and self._curr.token_type in self.TYPE_TOKENS
 7089            and self._next.token_type in self.TYPE_TOKENS
 7090        ):
 7091            # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a
 7092            # type token. Without this, the list will be parsed as a type and we'll eventually crash
 7093            this = self._parse_id_var()
 7094        else:
 7095            this = (
 7096                self._parse_type(parse_interval=False, fallback_to_identifier=True)
 7097                or self._parse_id_var()
 7098            )
 7099
 7100        self._match(TokenType.COLON)
 7101
 7102        if (
 7103            type_required
 7104            and not isinstance(this, exp.DataType)
 7105            and not self._match_set(self.TYPE_TOKENS, advance=False)
 7106        ):
 7107            self._retreat(index)
 7108            return self._parse_types()
 7109
 7110        return self._parse_column_def(this)
 7111
 7112    def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None:
 7113        if not self._match_text_seq("AT", "TIME", "ZONE"):
 7114            return this
 7115        return self._parse_at_time_zone(
 7116            self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary()))
 7117        )
 7118
 7119    def _parse_atom(self) -> exp.Expr | None:
 7120        if (
 7121            self._curr.token_type in self.IDENTIFIER_TOKENS
 7122            and (column := self._parse_column()) is not None
 7123        ):
 7124            return column
 7125
 7126        token = self._curr
 7127        token_type = token.token_type
 7128
 7129        if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)):
 7130            return None
 7131
 7132        next_type = self._next.token_type
 7133
 7134        if (
 7135            next_type in self.COLUMN_OPERATORS
 7136            or next_type in self.COLUMN_POSTFIX_TOKENS
 7137            or (token_type == TokenType.STRING and next_type == TokenType.STRING)
 7138        ):
 7139            return None
 7140
 7141        self._advance()
 7142        return primary_parser(self, token)
 7143
 7144    def _parse_column(self) -> exp.Expr | None:
 7145        column: exp.Expr | None = self._parse_column_parts_fast()
 7146        if column is None:
 7147            this = self._parse_column_reference()
 7148            if not this:
 7149                this = self._parse_bracket(this)
 7150            column = self._parse_column_ops(this) if this else this
 7151
 7152        if column:
 7153            if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS and self._match(TokenType.JOIN_MARKER):
 7154                column.set("join_mark", True)
 7155            if self.COLON_IS_VARIANT_EXTRACT:
 7156                column = self._parse_colon_as_variant_extract(column)
 7157
 7158        return column
 7159
 7160    def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None:
 7161        """Fast path for simple column and dot references (a, a.b, ...).
 7162
 7163        Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks
 7164        that nothing complex follows. If it does, retreats and returns None so
 7165        the slow path can handle it. For >4 parts, wraps in exp.Dot nodes.
 7166        """
 7167        index = self._index
 7168        parts: list[exp.Identifier] | None = None
 7169        all_comments: list[str] | None = None
 7170
 7171        while self._match_set(self.IDENTIFIER_TOKENS):
 7172            token = self._prev
 7173            comments = self._prev_comments
 7174
 7175            if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS:
 7176                self._retreat(index)
 7177                return None
 7178
 7179            has_dot = self._match(TokenType.DOT)
 7180            curr_tt = self._curr.token_type
 7181
 7182            if not has_dot:
 7183                if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS:
 7184                    self._retreat(index)
 7185                    return None
 7186            elif curr_tt not in self.IDENTIFIER_TOKENS:
 7187                self._retreat(index)
 7188                return None
 7189
 7190            if parts is None:
 7191                parts = []
 7192
 7193            if comments:
 7194                if all_comments is None:
 7195                    all_comments = []
 7196                all_comments.extend(comments)
 7197                self._prev_comments = []
 7198
 7199            parts.append(
 7200                self.expression(
 7201                    exp.Identifier(
 7202                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 7203                    ),
 7204                    token,
 7205                )
 7206            )
 7207
 7208            if not has_dot:
 7209                break
 7210
 7211        if parts is None:
 7212            return None
 7213
 7214        n = len(parts)
 7215
 7216        if n == 1:
 7217            column: exp.Column | exp.Dot = exp.Column(this=parts[0])
 7218        elif n == 2:
 7219            column = exp.Column(this=parts[1], table=parts[0])
 7220        elif n == 3:
 7221            column = exp.Column(this=parts[2], table=parts[1], db=parts[0])
 7222        else:
 7223            column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0])
 7224
 7225            for i in range(4, n):
 7226                column = exp.Dot(this=column, expression=parts[i])
 7227
 7228        if all_comments:
 7229            column.add_comments(all_comments)
 7230
 7231        return column
 7232
 7233    def _parse_column_reference(self) -> exp.Expr | None:
 7234        this = self._parse_field()
 7235        if (
 7236            not this
 7237            and self._match(TokenType.VALUES, advance=False)
 7238            and self.VALUES_FOLLOWED_BY_PAREN
 7239            and (not self._next or self._next.token_type != TokenType.L_PAREN)
 7240        ):
 7241            this = self._parse_id_var()
 7242
 7243        if isinstance(this, exp.Identifier):
 7244            # We bubble up comments from the Identifier to the Column
 7245            this = self.expression(exp.Column(this=this), comments=this.pop_comments())
 7246
 7247        return this
 7248
 7249    def _build_json_extract(
 7250        self,
 7251        this: exp.Expr | None,
 7252        path_parts: list[exp.JSONPathPart],
 7253    ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]:
 7254        if len(path_parts) > 1:
 7255            this = self.expression(
 7256                exp.JSONExtract(
 7257                    this=this,
 7258                    expression=exp.JSONPath(expressions=path_parts),
 7259                    variant_extract=True,
 7260                    requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION,
 7261                )
 7262            )
 7263            path_parts = [exp.JSONPathRoot()]
 7264
 7265        return this, path_parts
 7266
 7267    def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None:
 7268        path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()]
 7269
 7270        while self._match(TokenType.COLON):
 7271            if not self.COLON_CHAIN_IS_SINGLE_EXTRACT:
 7272                this, path_parts = self._build_json_extract(this, path_parts)
 7273
 7274            key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 7275
 7276            if key:
 7277                quoted = isinstance(key, exp.Identifier) and key.quoted
 7278                path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted))
 7279
 7280            while True:
 7281                if self._match(TokenType.DOT):
 7282                    next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 7283
 7284                    if next_key:
 7285                        quoted = isinstance(next_key, exp.Identifier) and next_key.quoted
 7286                        path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted))
 7287                elif self._match(TokenType.L_BRACKET):
 7288                    bracket_expr = self._parse_bracket_key_value()
 7289
 7290                    if not self._match(TokenType.R_BRACKET):
 7291                        self.raise_error("Expected ]")
 7292
 7293                    if bracket_expr:
 7294                        if bracket_expr.is_string:
 7295                            path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True))
 7296                        elif bracket_expr.is_star:
 7297                            path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard()))
 7298                        elif bracket_expr.is_number:
 7299                            path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py()))
 7300                        else:
 7301                            this, path_parts = self._build_json_extract(this, path_parts)
 7302
 7303                            this = self.expression(
 7304                                exp.Bracket(
 7305                                    this=this, expressions=[bracket_expr], json_access=True
 7306                                ),
 7307                            )
 7308
 7309                elif self._match(TokenType.DCOLON):
 7310                    this, path_parts = self._build_json_extract(this, path_parts)
 7311
 7312                    cast_type = self._parse_types()
 7313                    if cast_type:
 7314                        this = self.expression(exp.Cast(this=this, to=cast_type))
 7315                    else:
 7316                        self.raise_error("Expected type after '::'")
 7317                else:
 7318                    break
 7319
 7320        this, _ = self._build_json_extract(this, path_parts)
 7321
 7322        return this
 7323
 7324    def _parse_dcolon(self) -> exp.Expr | None:
 7325        return self._parse_types()
 7326
 7327    def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None:
 7328        while self._curr.token_type in self.BRACKETS:
 7329            this = self._parse_bracket(this)
 7330
 7331        column_operators = self.COLUMN_OPERATORS
 7332        cast_column_operators = self.CAST_COLUMN_OPERATORS
 7333        while self._curr:
 7334            op_token = self._curr.token_type
 7335
 7336            if op_token not in column_operators:
 7337                break
 7338            op = column_operators[op_token]
 7339            self._advance()
 7340
 7341            if op_token in cast_column_operators:
 7342                field = self._parse_dcolon()
 7343                if not field:
 7344                    self.raise_error("Expected type")
 7345            elif op and self._curr:
 7346                field = self._parse_column_reference() or self._parse_bitwise()
 7347                if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False):
 7348                    field = self._parse_column_ops(field)
 7349            else:
 7350                dot = self._is_connected() and self._prev.token_type == TokenType.DOT
 7351                field = self._parse_field(any_token=True, anonymous_func=True)
 7352
 7353                # In t.true, t.null we should produce an Identifier node
 7354                if dot and isinstance(field, (exp.Null, exp.Boolean)):
 7355                    field = self.expression(
 7356                        exp.Identifier(this=self._prev.text),
 7357                        comments=field.comments,
 7358                    )
 7359
 7360            # Function calls can be qualified, e.g., x.y.FOO()
 7361            # This converts the final AST to a series of Dots leading to the function call
 7362            # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules
 7363            if isinstance(field, (exp.Func, exp.Window)) and this:
 7364                this = this.transform(
 7365                    lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n
 7366                )
 7367
 7368            if op:
 7369                this = op(self, this, field)
 7370            elif isinstance(this, exp.Column) and not this.args.get("catalog"):
 7371                this = self.expression(
 7372                    exp.Column(
 7373                        this=field,
 7374                        table=this.this,
 7375                        db=this.args.get("table"),
 7376                        catalog=this.args.get("db"),
 7377                    ),
 7378                    comments=this.comments,
 7379                )
 7380            elif isinstance(field, exp.Window):
 7381                # Move the exp.Dot's to the window's function
 7382                window_func = self.expression(exp.Dot(this=this, expression=field.this))
 7383                field.set("this", window_func)
 7384                this = field
 7385            else:
 7386                this = self.expression(exp.Dot(this=this, expression=field))
 7387
 7388            if field and field.comments:
 7389                t.cast(exp.Expr, this).add_comments(field.pop_comments())
 7390
 7391            this = self._parse_bracket(this)
 7392
 7393        return this
 7394
 7395    def _parse_paren(self) -> exp.Expr | None:
 7396        if not self._match(TokenType.L_PAREN):
 7397            return None
 7398
 7399        comments = self._prev_comments
 7400        query = self._parse_select()
 7401
 7402        if query:
 7403            expressions = [query]
 7404        else:
 7405            expressions = self._parse_expressions()
 7406
 7407        this = seq_get(expressions, 0)
 7408
 7409        if not this and self._match(TokenType.R_PAREN, advance=False):
 7410            this = self.expression(exp.Tuple())
 7411        elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA:
 7412            this = self.expression(exp.Tuple(expressions=expressions))
 7413        elif isinstance(this, exp.UNWRAPPED_QUERIES):
 7414            this = self._parse_subquery(this=this, parse_alias=False)
 7415        elif isinstance(this, (exp.Subquery, exp.Values)):
 7416            this = self._parse_subquery(
 7417                this=self._parse_query_modifiers(self._parse_set_operations(this)),
 7418                parse_alias=False,
 7419            )
 7420        else:
 7421            this = self.expression(exp.Paren(this=this))
 7422
 7423        if this:
 7424            this.add_comments(comments)
 7425
 7426        self._match_r_paren(expression=this)
 7427
 7428        if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc):
 7429            return self._parse_window(this)
 7430
 7431        return this
 7432
 7433    def _parse_primary(self) -> exp.Expr | None:
 7434        if self._match_set(self.PRIMARY_PARSERS):
 7435            token_type = self._prev.token_type
 7436            primary = self.PRIMARY_PARSERS[token_type](self, self._prev)
 7437
 7438            if token_type == TokenType.STRING:
 7439                expressions = [primary]
 7440                while self._match(TokenType.STRING, advance=False):
 7441                    if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED:
 7442                        self.raise_error(
 7443                            "Adjacent string literals need to be separated by whitespace or comments"
 7444                        )
 7445
 7446                    self._advance()
 7447                    expressions.append(exp.Literal.string(self._prev.text))
 7448
 7449                if len(expressions) > 1:
 7450                    return self.expression(
 7451                        exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE)
 7452                    )
 7453
 7454            return primary
 7455
 7456        if self._match_pair(TokenType.DOT, TokenType.NUMBER):
 7457            return exp.Literal.number(f"0.{self._prev.text}")
 7458
 7459        return self._parse_paren()
 7460
 7461    def _parse_field(
 7462        self,
 7463        any_token: bool = False,
 7464        tokens: t.Collection[TokenType] | None = None,
 7465        anonymous_func: bool = False,
 7466    ) -> exp.Expr | None:
 7467        after_dot = (
 7468            self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT
 7469        )
 7470
 7471        if anonymous_func:
 7472            field = (
 7473                self._parse_function(anonymous=anonymous_func, any_token=any_token)
 7474                or self._parse_primary()
 7475            )
 7476        else:
 7477            field = self._parse_primary() or self._parse_function(
 7478                anonymous=anonymous_func, any_token=any_token
 7479            )
 7480
 7481        field = field or self._parse_id_var(any_token=any_token, tokens=tokens)
 7482
 7483        if after_dot and isinstance(field, exp.Literal) and field.is_number:
 7484            name = field.name
 7485            if self._is_connected() and self._parse_var(any_token=True):
 7486                name += self._prev.text
 7487
 7488            field = exp.Identifier(this=name, quoted=True).update_positions(field)
 7489
 7490        return field
 7491
 7492    def _parse_function(
 7493        self,
 7494        functions: dict[str, t.Callable] | None = None,
 7495        anonymous: bool = False,
 7496        optional_parens: bool = True,
 7497        any_token: bool = False,
 7498    ) -> exp.Expr | None:
 7499        # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this)
 7500        # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences
 7501        fn_syntax = False
 7502        if (
 7503            self._match(TokenType.L_BRACE, advance=False)
 7504            and self._next
 7505            and self._next.text.upper() == "FN"
 7506        ):
 7507            self._advance(2)
 7508            fn_syntax = True
 7509
 7510        func = self._parse_function_call(
 7511            functions=functions,
 7512            anonymous=anonymous,
 7513            optional_parens=optional_parens,
 7514            any_token=any_token,
 7515        )
 7516
 7517        if fn_syntax:
 7518            self._match(TokenType.R_BRACE)
 7519
 7520        return func
 7521
 7522    def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]:
 7523        return self._parse_csv(lambda: self._parse_lambda(alias=alias))
 7524
 7525    def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren:
 7526        args = self._parse_function_args(alias=False)
 7527        if not args:
 7528            self.raise_error("Expected at least one argument")
 7529
 7530        # Wrapped so the connector keeps its precedence in the parent context
 7531        return exp.Paren(this=connector(*args, copy=False))
 7532
 7533    def _parse_function_call(
 7534        self,
 7535        functions: dict[str, t.Callable] | None = None,
 7536        anonymous: bool = False,
 7537        optional_parens: bool = True,
 7538        any_token: bool = False,
 7539    ) -> exp.Expr | None:
 7540        if not self._curr:
 7541            return None
 7542
 7543        comments = self._curr.comments
 7544        prev = self._prev
 7545        token = self._curr
 7546        token_type = self._curr.token_type
 7547        this: str | exp.Expr = self._curr.text
 7548        upper = self._curr.text.upper()
 7549
 7550        after_dot = prev.token_type == TokenType.DOT
 7551        parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper)
 7552        if (
 7553            optional_parens
 7554            and parser
 7555            and token_type not in self.INVALID_FUNC_NAME_TOKENS
 7556            and not after_dot
 7557        ):
 7558            self._advance()
 7559            return self._parse_window(parser(self))
 7560
 7561        if self._next.token_type != TokenType.L_PAREN:
 7562            if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot:
 7563                self._advance()
 7564                return self.expression(self.NO_PAREN_FUNCTIONS[token_type]())
 7565
 7566            return None
 7567
 7568        if any_token:
 7569            if token_type in self.RESERVED_TOKENS:
 7570                return None
 7571        elif token_type not in self.FUNC_TOKENS:
 7572            return None
 7573
 7574        self._advance(2)
 7575
 7576        parser = self.FUNCTION_PARSERS.get(upper)
 7577        if parser and not anonymous:
 7578            result = parser(self)
 7579        else:
 7580            subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type)
 7581
 7582            if subquery_predicate:
 7583                expr = None
 7584                if self._curr.token_type in self.SUBQUERY_TOKENS:
 7585                    expr = self._parse_select()
 7586                    self._match_r_paren()
 7587                elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE):
 7588                    # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like
 7589                    # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren
 7590                    self._advance(-1)
 7591                    expr = self._parse_bitwise()
 7592
 7593                if expr:
 7594                    return self.expression(subquery_predicate(this=expr), comments=comments)
 7595
 7596            if functions is None:
 7597                functions = self.FUNCTIONS
 7598
 7599            function = functions.get(upper)
 7600            known_function = function and not anonymous
 7601
 7602            alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS
 7603            args = self._parse_function_args(alias)
 7604
 7605            post_func_comments = self._curr.comments if self._curr else None
 7606            if known_function and post_func_comments:
 7607                # If the user-inputted comment "/* sqlglot.anonymous */" is following the function
 7608                # call we'll construct it as exp.Anonymous, even if it's "known"
 7609                if any(
 7610                    comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS)
 7611                    for comment in post_func_comments
 7612                ):
 7613                    known_function = False
 7614
 7615            if alias and known_function:
 7616                args = self._kv_to_prop_eq(args)
 7617
 7618            if known_function:
 7619                func_builder = t.cast(t.Callable, function)
 7620
 7621                # mypyc compiled functions don't have __code__, so we use
 7622                # try/except to check if func_builder accepts 'dialect'.
 7623                try:
 7624                    func = func_builder(args)
 7625                except TypeError:
 7626                    func = func_builder(args, dialect=self.dialect)
 7627
 7628                func = self.validate_expression(func, args)
 7629                if name_key := self.dialect.ORIGINAL_NAME_META_KEY:
 7630                    func.meta[name_key] = this
 7631
 7632                result = func
 7633            else:
 7634                if token_type == TokenType.IDENTIFIER:
 7635                    this = exp.Identifier(this=this, quoted=True).update_positions(token)
 7636
 7637                result = self.expression(exp.Anonymous(this=this, expressions=args))
 7638
 7639            result = result.update_positions(token)
 7640
 7641        if isinstance(result, exp.Expr):
 7642            result.add_comments(comments)
 7643
 7644        if parser:
 7645            self._match(TokenType.R_PAREN, expression=result)
 7646        else:
 7647            self._match_r_paren(result)
 7648        return self._parse_window(result)
 7649
 7650    def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr:
 7651        return expression
 7652
 7653    def _kv_to_prop_eq(
 7654        self, expressions: list[exp.Expr], parse_map: bool = False
 7655    ) -> list[exp.Expr]:
 7656        transformed = []
 7657
 7658        for index, e in enumerate(expressions):
 7659            if isinstance(e, self.KEY_VALUE_DEFINITIONS):
 7660                if isinstance(e, exp.Alias):
 7661                    e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this))
 7662
 7663                if not isinstance(e, exp.PropertyEQ):
 7664                    e = self.expression(
 7665                        exp.PropertyEQ(
 7666                            this=e.this if parse_map else exp.to_identifier(e.this.name),
 7667                            expression=e.expression,
 7668                        )
 7669                    )
 7670
 7671                if not parse_map and isinstance(e.this, exp.Column):
 7672                    e.this.replace(e.this.this)
 7673            else:
 7674                e = self._to_prop_eq(e, index)
 7675
 7676            transformed.append(e)
 7677
 7678        return transformed
 7679
 7680    def _parse_function_properties(self) -> exp.Properties | None:
 7681        # Skip the generic `key = value` fallback in _parse_property since this
 7682        # runs post-AS where a function body like `name = expr` can be misread
 7683        # as a property.
 7684        properties = []
 7685        while True:
 7686            if self._match_texts(self.PROPERTY_PARSERS):
 7687                keyword = self._prev.text.upper()
 7688                prop = self.PROPERTY_PARSERS[keyword](self)
 7689            elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS):
 7690                keyword = self._prev.text.upper()
 7691                prop = self.PROPERTY_PARSERS[keyword](self, default=True)
 7692            else:
 7693                break
 7694            if not prop:
 7695                self.raise_error(f"Failed to parse property '{keyword}'")
 7696                break
 7697            for p in ensure_list(prop):
 7698                properties.append(p)
 7699
 7700        return self.expression(exp.Properties(expressions=properties)) if properties else None
 7701
 7702    def _parse_user_defined_function_expression(self) -> exp.Expr | None:
 7703        return self._parse_statement()
 7704
 7705    def _parse_function_parameter(self) -> exp.Expr | None:
 7706        return self._parse_column_def(this=self._parse_id_var(), computed_column=False)
 7707
 7708    def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None:
 7709        this = self._parse_table_parts(schema=True)
 7710
 7711        if not self._match(TokenType.L_PAREN):
 7712            return this
 7713
 7714        expressions = self._parse_csv(self._parse_function_parameter)
 7715        self._match_r_paren()
 7716        return self.expression(
 7717            exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True)
 7718        )
 7719
 7720    def _parse_macro_overloads(
 7721        self,
 7722        this: exp.UserDefinedFunction,
 7723        first_body: exp.Expr,
 7724        first_is_table: bool = False,
 7725    ) -> exp.MacroOverloads:
 7726        overloads = [
 7727            self.expression(
 7728                exp.MacroOverload(
 7729                    this=first_body,
 7730                    expressions=this.expressions or None,
 7731                    is_table=first_is_table,
 7732                )
 7733            )
 7734        ]
 7735        this.set("expressions", None)
 7736        this.set("wrapped", False)
 7737
 7738        while self._match(TokenType.COMMA):
 7739            if not self._match(TokenType.L_PAREN):
 7740                break
 7741
 7742            params = self._parse_csv(self._parse_function_parameter)
 7743            self._match_r_paren()
 7744
 7745            if not self._match(TokenType.ALIAS):
 7746                break
 7747
 7748            is_table = self._match(TokenType.TABLE)
 7749            body = self._parse_expression()
 7750            macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table)
 7751            overloads.append(self.expression(macro))
 7752
 7753        return self.expression(exp.MacroOverloads(expressions=overloads))
 7754
 7755    def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier:
 7756        literal = self._parse_primary()
 7757        if literal:
 7758            return self.expression(exp.Introducer(this=token.text, expression=literal), token)
 7759
 7760        return self._identifier_expression(token)
 7761
 7762    def _parse_session_parameter(self) -> exp.SessionParameter:
 7763        kind = None
 7764        this = self._parse_id_var() or self._parse_primary()
 7765
 7766        if this and self._match(TokenType.DOT):
 7767            kind = this.name
 7768            this = self._parse_var() or self._parse_primary()
 7769
 7770        return self.expression(exp.SessionParameter(this=this, kind=kind))
 7771
 7772    def _parse_lambda_arg(self) -> exp.Expr | None:
 7773        return self._parse_id_var()
 7774
 7775    def _parse_lambda(self, alias: bool = False) -> exp.Expr | None:
 7776        next_token_type = self._next.token_type
 7777
 7778        # Fast path: simple atom (column, literal, null, bool) followed by , or )
 7779        if (
 7780            next_token_type in self.LAMBDA_ARG_TERMINATORS
 7781            and (atom := self._parse_atom()) is not None
 7782        ):
 7783            return atom
 7784
 7785        index = self._index
 7786
 7787        if self._match(TokenType.L_PAREN):
 7788            expressions = t.cast(
 7789                list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg)
 7790            )
 7791
 7792            if not self._match(TokenType.R_PAREN):
 7793                self._retreat(index)
 7794            elif self._match_set(self.LAMBDAS):
 7795                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7796            else:
 7797                self._retreat(index)
 7798        elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS:
 7799            expressions = [self._parse_lambda_arg()]
 7800
 7801            if self._match_set(self.LAMBDAS):
 7802                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7803
 7804            self._retreat(index)
 7805
 7806        this: exp.Expr | None
 7807
 7808        if self._match(TokenType.DISTINCT):
 7809            this = self.expression(
 7810                exp.Distinct(expressions=self._parse_csv(self._parse_disjunction))
 7811            )
 7812        else:
 7813            self._match(TokenType.ALL)  # ALL is the default/no-op aggregate modifier (SQL-92)
 7814            this = self._parse_select_or_expression(alias=alias)
 7815
 7816        return self._parse_limit(
 7817            self._parse_respect_or_ignore_nulls(
 7818                self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this)))
 7819            )
 7820        )
 7821
 7822    def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7823        index = self._index
 7824        if not self._match(TokenType.L_PAREN):
 7825            return this
 7826
 7827        # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>),
 7828        # expr can be of both types
 7829        if self._match_set(self.SELECT_START_TOKENS):
 7830            self._retreat(index)
 7831            return this
 7832        args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def())
 7833        self._match_r_paren()
 7834        return self.expression(exp.Schema(this=this, expressions=args))
 7835
 7836    def _parse_field_def(self) -> exp.Expr | None:
 7837        return self._parse_column_def(self._parse_field(any_token=True))
 7838
 7839    def _parse_column_def(
 7840        self, this: exp.Expr | None, computed_column: bool = True
 7841    ) -> exp.Expr | None:
 7842        # column defs are not really columns, they're identifiers
 7843        if isinstance(this, exp.Column):
 7844            this = this.this
 7845
 7846        if not computed_column:
 7847            self._match(TokenType.ALIAS)
 7848
 7849        kind = self._parse_types(schema=True)
 7850
 7851        if self._match_text_seq("FOR", "ORDINALITY"):
 7852            return self.expression(exp.ColumnDef(this=this, ordinality=True))
 7853
 7854        constraints: list[exp.Expr] = []
 7855
 7856        if (not kind and self._match(TokenType.ALIAS)) or self._match_texts(
 7857            ("ALIAS", "MATERIALIZED")
 7858        ):
 7859            # Match storage before _parse_types so STORED is not treated as a data type
 7860            # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`).
 7861            persisted = self._prev.text.upper() == "MATERIALIZED"
 7862            expression = self._parse_disjunction()
 7863            if not persisted:
 7864                if self._match_text_seq("PERSISTED"):
 7865                    persisted = True
 7866                elif self._match_texts(("STORED", "VIRTUAL")):
 7867                    persisted = self._prev.text.upper() == "STORED"
 7868            constraint_kind = exp.ComputedColumnConstraint(
 7869                this=expression,
 7870                persisted=persisted,
 7871                data_type=exp.Var(this="AUTO")
 7872                if self._match_text_seq("AUTO")
 7873                else self._parse_types(),
 7874                not_null=self._match_pair(TokenType.NOT, TokenType.NULL),
 7875            )
 7876            constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind)))
 7877        elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False):
 7878            in_out_constraint = self.expression(
 7879                exp.InOutColumnConstraint(
 7880                    input_=self._match(TokenType.IN), output=self._match(TokenType.OUT)
 7881                )
 7882            )
 7883            constraints.append(in_out_constraint)
 7884            kind = self._parse_types()
 7885        elif (
 7886            kind
 7887            and self._match(TokenType.ALIAS, advance=False)
 7888            and (
 7889                not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
 7890                or self._next.token_type == TokenType.L_PAREN
 7891            )
 7892        ):
 7893            self._advance()
 7894            constraints.append(
 7895                self.expression(
 7896                    exp.ColumnConstraint(
 7897                        kind=exp.ComputedColumnConstraint(
 7898                            this=self._parse_disjunction(),
 7899                            persisted=self._match_texts(("STORED", "VIRTUAL"))
 7900                            and self._prev.text.upper() == "STORED",
 7901                        )
 7902                    )
 7903                )
 7904            )
 7905
 7906        while True:
 7907            constraint = self._parse_column_constraint()
 7908            if not constraint:
 7909                break
 7910            constraints.append(constraint)
 7911
 7912        if not kind and not constraints:
 7913            return this
 7914
 7915        position = None
 7916        if self._match_texts(("FIRST", "AFTER")):
 7917            pos = self._prev.text
 7918            position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos))
 7919
 7920        return self.expression(
 7921            exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position)
 7922        )
 7923
 7924    def _parse_auto_increment(
 7925        self,
 7926    ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint:
 7927        start = None
 7928        increment = None
 7929        order = None
 7930
 7931        if self._match(TokenType.L_PAREN, advance=False):
 7932            args = self._parse_wrapped_csv(self._parse_bitwise)
 7933            start = seq_get(args, 0)
 7934            increment = seq_get(args, 1)
 7935
 7936        # The remaining parts form an unordered bag and any of them can be omitted, in which
 7937        # case the engine falls back to its own default, so they're parsed independently.
 7938        while True:
 7939            if self._match_text_seq("START"):
 7940                start = self._parse_bitwise()
 7941            elif self._match_text_seq("INCREMENT"):
 7942                increment = self._parse_bitwise()
 7943            elif self._match_text_seq("ORDER"):
 7944                order = True
 7945            elif self._match_text_seq("NOORDER"):
 7946                order = False
 7947            else:
 7948                break
 7949
 7950        if start or increment or order is not None:
 7951            return exp.GeneratedAsIdentityColumnConstraint(
 7952                start=start, increment=increment, this=False, order=order
 7953            )
 7954
 7955        return exp.AutoIncrementColumnConstraint()
 7956
 7957    def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None:
 7958        if not self._match(TokenType.L_PAREN, advance=False):
 7959            return None
 7960
 7961        return self.expression(
 7962            exp.CheckColumnConstraint(
 7963                this=self._parse_wrapped(self._parse_assignment),
 7964                enforced=self._match_text_seq("ENFORCED"),
 7965            )
 7966        )
 7967
 7968    def _parse_auto_property(self) -> exp.AutoRefreshProperty | None:
 7969        if not self._match_text_seq("REFRESH"):
 7970            self._retreat(self._index - 1)
 7971            return None
 7972        return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True)))
 7973
 7974    def _parse_compress(self) -> exp.CompressColumnConstraint:
 7975        if self._match(TokenType.L_PAREN, advance=False):
 7976            return self.expression(
 7977                exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise))
 7978            )
 7979
 7980        return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise()))
 7981
 7982    def _parse_generated_as_identity(
 7983        self,
 7984    ) -> (
 7985        exp.GeneratedAsIdentityColumnConstraint
 7986        | exp.ComputedColumnConstraint
 7987        | exp.GeneratedAsRowColumnConstraint
 7988    ):
 7989        if self._match_text_seq("BY", "DEFAULT"):
 7990            on_null = self._match_pair(TokenType.ON, TokenType.NULL)
 7991            this = self.expression(
 7992                exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null)
 7993            )
 7994        else:
 7995            self._match_text_seq("ALWAYS")
 7996            this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True))
 7997
 7998        self._match(TokenType.ALIAS)
 7999
 8000        if self._match_text_seq("ROW"):
 8001            start = self._match_text_seq("START")
 8002            if not start:
 8003                self._match(TokenType.END)
 8004            hidden = self._match_text_seq("HIDDEN")
 8005            return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden))
 8006
 8007        identity = self._match_text_seq("IDENTITY")
 8008
 8009        if self._match(TokenType.L_PAREN):
 8010            if self._match_text_seq("START", "WITH"):
 8011                this.set("start", self._parse_bitwise())
 8012            if self._match_text_seq("INCREMENT", "BY"):
 8013                this.set("increment", self._parse_bitwise())
 8014            if self._match_text_seq("MINVALUE"):
 8015                this.set("minvalue", self._parse_bitwise())
 8016            if self._match_text_seq("MAXVALUE"):
 8017                this.set("maxvalue", self._parse_bitwise())
 8018
 8019            if self._match_text_seq("CYCLE"):
 8020                this.set("cycle", True)
 8021            elif self._match_text_seq("NO", "CYCLE"):
 8022                this.set("cycle", False)
 8023
 8024            if not identity:
 8025                this.set("expression", self._parse_disjunction())
 8026            elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False):
 8027                args = self._parse_csv(self._parse_bitwise)
 8028                this.set("start", seq_get(args, 0))
 8029                this.set("increment", seq_get(args, 1))
 8030
 8031            self._match_r_paren()
 8032
 8033        return this
 8034
 8035    def _parse_inline(self) -> exp.InlineLengthColumnConstraint:
 8036        self._match_text_seq("LENGTH")
 8037        return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise()))
 8038
 8039    def _parse_not_constraint(self) -> exp.Expr | None:
 8040        if self._match_text_seq("NULL"):
 8041            return self.expression(exp.NotNullColumnConstraint())
 8042        if self._match_text_seq("CASESPECIFIC"):
 8043            return self.expression(exp.CaseSpecificColumnConstraint(not_=True))
 8044        if self._match_text_seq("FOR", "REPLICATION"):
 8045            return self.expression(exp.NotForReplicationColumnConstraint())
 8046
 8047        # Unconsume the `NOT` token
 8048        self._retreat(self._index - 1)
 8049        return None
 8050
 8051    def _parse_column_constraint(self) -> exp.Expr | None:
 8052        this = (
 8053            self._parse_id_var()
 8054            if self._match(TokenType.CONSTRAINT)
 8055            and not self._match_texts(self.RESERVED_CONSTRAINT_KINDS, advance=False)
 8056            else None
 8057        )
 8058
 8059        procedure_option_follows = (
 8060            self._match(TokenType.WITH, advance=False)
 8061            and self._next
 8062            and self._next.text.upper() in self.PROCEDURE_OPTIONS
 8063        )
 8064
 8065        index = self._index
 8066        if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS):
 8067            constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self)
 8068            if not constraint:
 8069                self._retreat(index)
 8070                return None
 8071
 8072            return self.expression(exp.ColumnConstraint(this=this, kind=constraint))
 8073
 8074        if self._match(TokenType.CHARACTER_SET) or self._match_pair(TokenType.CHAR, TokenType.SET):
 8075            return self.expression(
 8076                exp.ColumnConstraint(
 8077                    this=this,
 8078                    kind=self.expression(
 8079                        exp.CharacterSetColumnConstraint(this=self._parse_var_or_string())
 8080                    ),
 8081                )
 8082            )
 8083
 8084        return this
 8085
 8086    def _parse_constraint(self) -> exp.Expr | None:
 8087        if not self._match(TokenType.CONSTRAINT) or self._match_texts(
 8088            self.RESERVED_CONSTRAINT_KINDS, advance=False
 8089        ):
 8090            return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS)
 8091
 8092        return self.expression(
 8093            exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints())
 8094        )
 8095
 8096    def _parse_unnamed_constraints(self) -> list[exp.Expr]:
 8097        constraints = []
 8098        while True:
 8099            constraint = self._parse_unnamed_constraint() or self._parse_function()
 8100            if not constraint:
 8101                break
 8102            constraints.append(constraint)
 8103
 8104        return constraints
 8105
 8106    def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None:
 8107        index = self._index
 8108
 8109        if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts(
 8110            constraints or self.CONSTRAINT_PARSERS
 8111        ):
 8112            return None
 8113
 8114        constraint_key = self._prev.text.upper()
 8115        if constraint_key not in self.CONSTRAINT_PARSERS:
 8116            self.raise_error(f"No parser found for schema constraint {constraint_key}.")
 8117
 8118        result = self.CONSTRAINT_PARSERS[constraint_key](self)
 8119        if not result:
 8120            self._retreat(index)
 8121
 8122        return result
 8123
 8124    def _parse_unique_key(self) -> exp.Expr | None:
 8125        if (
 8126            self._curr
 8127            and self._curr.token_type != TokenType.IDENTIFIER
 8128            and self._curr.text.upper() in self.CONSTRAINT_PARSERS
 8129        ):
 8130            return None
 8131        return self._parse_id_var(any_token=False)
 8132
 8133    def _parse_unique(self) -> exp.UniqueColumnConstraint:
 8134        self._match_texts(("KEY", "INDEX"))
 8135        return self.expression(
 8136            exp.UniqueColumnConstraint(
 8137                nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"),
 8138                this=self._parse_schema(self._parse_unique_key()),
 8139                index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text,
 8140                on_conflict=self._parse_on_conflict(),
 8141                options=self._parse_key_constraint_options(),
 8142            )
 8143        )
 8144
 8145    def _parse_key_constraint_options(self) -> list[str]:
 8146        options = []
 8147        while True:
 8148            if not self._curr:
 8149                break
 8150
 8151            if self._match(TokenType.ON):
 8152                action = None
 8153                on = self._advance_any() and self._prev.text
 8154
 8155                if self._match_text_seq("NO", "ACTION"):
 8156                    action = "NO ACTION"
 8157                elif self._match_text_seq("CASCADE"):
 8158                    action = "CASCADE"
 8159                elif self._match_text_seq("RESTRICT"):
 8160                    action = "RESTRICT"
 8161                elif self._match_pair(TokenType.SET, TokenType.NULL):
 8162                    action = "SET NULL"
 8163                elif self._match_pair(TokenType.SET, TokenType.DEFAULT):
 8164                    action = "SET DEFAULT"
 8165                else:
 8166                    self.raise_error("Invalid key constraint")
 8167
 8168                options.append(f"ON {on} {action}")
 8169            else:
 8170                var = self._parse_var_from_options(
 8171                    self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False
 8172                )
 8173                if not var:
 8174                    break
 8175                options.append(var.name)
 8176
 8177        return options
 8178
 8179    def _parse_references(self, match: bool = True) -> exp.Reference | None:
 8180        if match and not self._match(TokenType.REFERENCES):
 8181            return None
 8182
 8183        expressions: list | None = None
 8184        this = self._parse_table(schema=True)
 8185        options = self._parse_key_constraint_options()
 8186        return self.expression(exp.Reference(this=this, expressions=expressions, options=options))
 8187
 8188    def _parse_foreign_key(self) -> exp.ForeignKey:
 8189        expressions = (
 8190            self._parse_wrapped_id_vars()
 8191            if not self._match(TokenType.REFERENCES, advance=False)
 8192            else None
 8193        )
 8194        reference = self._parse_references()
 8195        on_options = {}
 8196
 8197        while self._match(TokenType.ON):
 8198            if not self._match_set((TokenType.DELETE, TokenType.UPDATE)):
 8199                self.raise_error("Expected DELETE or UPDATE")
 8200
 8201            kind = self._prev.text.lower()
 8202
 8203            if self._match_text_seq("NO", "ACTION"):
 8204                action = "NO ACTION"
 8205            elif self._match(TokenType.SET):
 8206                self._match_set((TokenType.NULL, TokenType.DEFAULT))
 8207                action = "SET " + self._prev.text.upper()
 8208            else:
 8209                self._advance()
 8210                action = self._prev.text.upper()
 8211
 8212            on_options[kind] = action
 8213
 8214        return self.expression(
 8215            exp.ForeignKey(
 8216                expressions=expressions,
 8217                reference=reference,
 8218                options=self._parse_key_constraint_options(),
 8219                **on_options,
 8220            )
 8221        )
 8222
 8223    def _parse_primary_key_part(self) -> exp.Expr | None:
 8224        return self._parse_field()
 8225
 8226    def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None:
 8227        if not self._match_text_seq("FOR", "SYSTEM_TIME"):
 8228            self._retreat(self._index - 1)
 8229            return None
 8230
 8231        id_vars = self._parse_wrapped_id_vars()
 8232        return self.expression(
 8233            exp.PeriodForSystemTimeConstraint(
 8234                this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1)
 8235            )
 8236        )
 8237
 8238    def _parse_primary_key(
 8239        self,
 8240        wrapped_optional: bool = False,
 8241        in_props: bool = False,
 8242        named_primary_key: bool = False,
 8243    ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:
 8244        desc = (
 8245            self._prev.token_type == TokenType.DESC
 8246            if self._match_set((TokenType.ASC, TokenType.DESC))
 8247            else None
 8248        )
 8249
 8250        this = None
 8251        if (
 8252            named_primary_key
 8253            and self._curr.text.upper() not in self.CONSTRAINT_PARSERS
 8254            and self._next
 8255            and self._next.token_type == TokenType.L_PAREN
 8256        ):
 8257            this = self._parse_id_var()
 8258
 8259        if not in_props and not self._match(TokenType.L_PAREN, advance=False):
 8260            return self.expression(
 8261                exp.PrimaryKeyColumnConstraint(
 8262                    desc=desc, options=self._parse_key_constraint_options()
 8263                )
 8264            )
 8265
 8266        expressions = self._parse_wrapped_csv(
 8267            self._parse_primary_key_part, optional=wrapped_optional
 8268        )
 8269
 8270        return self.expression(
 8271            exp.PrimaryKey(
 8272                this=this,
 8273                expressions=expressions,
 8274                include=self._parse_index_params(),
 8275                options=self._parse_key_constraint_options(),
 8276            )
 8277        )
 8278
 8279    def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None:
 8280        return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True))
 8281
 8282    def _parse_odbc_datetime_literal(self) -> exp.Expr:
 8283        """
 8284        Parses a datetime column in ODBC format. We parse the column into the corresponding
 8285        types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the
 8286        same as we did for `DATE('yyyy-mm-dd')`.
 8287
 8288        Reference:
 8289        https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals
 8290        """
 8291        self._match(TokenType.VAR)
 8292        exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()]
 8293        expression = self.expression(exp_class(this=self._parse_string()))
 8294        if not self._match(TokenType.R_BRACE):
 8295            self.raise_error("Expected }")
 8296        return expression
 8297
 8298    def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None:
 8299        if not self._match_set(self.BRACKETS):
 8300            return this
 8301
 8302        if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS:
 8303            map_token = seq_get(self._tokens, self._index - 2)
 8304            parse_map = map_token is not None and map_token.text.upper() == "MAP"
 8305        else:
 8306            parse_map = False
 8307
 8308        bracket_kind = self._prev.token_type
 8309        if (
 8310            bracket_kind == TokenType.L_BRACE
 8311            and self._curr
 8312            and self._curr.token_type == TokenType.VAR
 8313            and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS
 8314        ):
 8315            return self._parse_odbc_datetime_literal()
 8316
 8317        expressions = self._parse_csv(
 8318            lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE)
 8319        )
 8320
 8321        if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET):
 8322            self.raise_error("Expected ]")
 8323        elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE):
 8324            self.raise_error("Expected }")
 8325
 8326        # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs
 8327        if bracket_kind == TokenType.L_BRACE:
 8328            this = self.expression(
 8329                exp.Struct(
 8330                    expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map)
 8331                )
 8332            )
 8333        elif not this:
 8334            this = build_array_constructor(
 8335                exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect
 8336            )
 8337        else:
 8338            constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper())
 8339            if constructor_type:
 8340                return build_array_constructor(
 8341                    constructor_type,
 8342                    args=expressions,
 8343                    bracket_kind=bracket_kind,
 8344                    dialect=self.dialect,
 8345                )
 8346
 8347            expressions = apply_index_offset(
 8348                this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect
 8349            )
 8350            this = self.expression(
 8351                exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments()
 8352            )
 8353
 8354        self._add_comments(this)
 8355        return self._parse_bracket(this)
 8356
 8357    def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None:
 8358        if not self._match(TokenType.COLON):
 8359            return this
 8360
 8361        # DuckDB's `-` in `[a:-:step]` is the default end, which depends on the sign of step
 8362        if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False):
 8363            self._advance()
 8364            end: exp.Expr | None = None
 8365        else:
 8366            end = self._parse_assignment()
 8367        step = self._parse_unary() if self._match(TokenType.COLON) else None
 8368        return self.expression(exp.Slice(this=this, expression=end, step=step))
 8369
 8370    def _parse_case(self) -> exp.Expr | None:
 8371        if self._match(TokenType.DOT, advance=False):
 8372            # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake
 8373            self._retreat(self._index - 1)
 8374            return None
 8375
 8376        ifs = []
 8377        default = None
 8378
 8379        comments = self._prev_comments
 8380        expression = self._parse_disjunction()
 8381
 8382        while self._match(TokenType.WHEN):
 8383            this = self._parse_disjunction()
 8384            self._match(TokenType.THEN)
 8385            then = self._parse_disjunction()
 8386            ifs.append(self.expression(exp.If(this=this, true=then)))
 8387
 8388        if self._match(TokenType.ELSE):
 8389            default = self._parse_disjunction()
 8390
 8391        if not self._match(TokenType.END):
 8392            if isinstance(default, exp.Interval) and default.this.sql().upper() == "END":
 8393                default = exp.column("interval")
 8394            else:
 8395                self.raise_error("Expected END after CASE", self._prev)
 8396
 8397        return self.expression(
 8398            exp.Case(this=expression, ifs=ifs, default=default), comments=comments
 8399        )
 8400
 8401    def _parse_if(self) -> exp.Expr | None:
 8402        if self._match(TokenType.L_PAREN):
 8403            args = self._parse_csv(
 8404                lambda: self._parse_alias(self._parse_assignment(), explicit=True)
 8405            )
 8406            this = self.validate_expression(exp.If.from_arg_list(args), args)
 8407            self._match_r_paren()
 8408        else:
 8409            index = self._index - 1
 8410
 8411            if self.NO_PAREN_IF_COMMANDS and index == 0:
 8412                return self._parse_as_command(self._prev)
 8413
 8414            condition = self._parse_disjunction()
 8415
 8416            if not condition or not self._match(TokenType.THEN):
 8417                self._retreat(index)
 8418                return None
 8419
 8420            true = self._parse_disjunction()
 8421            false = self._parse_disjunction() if self._match(TokenType.ELSE) else None
 8422            self._match(TokenType.END)
 8423            this = self.expression(exp.If(this=condition, true=true, false=false))
 8424
 8425        return this
 8426
 8427    def _parse_next_value_for(self) -> exp.Expr | None:
 8428        if not self._match_text_seq("VALUE", "FOR"):
 8429            self._retreat(self._index - 1)
 8430            return None
 8431
 8432        return self.expression(
 8433            exp.NextValueFor(
 8434                this=self._parse_column(),
 8435                order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order),
 8436            )
 8437        )
 8438
 8439    def _parse_extract(self) -> exp.Extract | exp.Anonymous:
 8440        this = self._parse_function() or self._parse_var_or_string(upper=True)
 8441
 8442        if self._match(TokenType.FROM):
 8443            return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8444
 8445        if not self._match(TokenType.COMMA):
 8446            self.raise_error("Expected FROM or comma after EXTRACT", self._prev)
 8447
 8448        return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8449
 8450    def _parse_gap_fill(self) -> exp.GapFill:
 8451        self._match(TokenType.TABLE)
 8452        this = self._parse_table()
 8453
 8454        self._match(TokenType.COMMA)
 8455        args = [this, *self._parse_csv(self._parse_lambda)]
 8456
 8457        gap_fill = exp.GapFill.from_arg_list(args)
 8458        return self.validate_expression(gap_fill, args)
 8459
 8460    def _parse_char(self) -> exp.Chr:
 8461        return self.expression(
 8462            exp.Chr(
 8463                expressions=self._parse_csv(self._parse_assignment),
 8464                charset=self._match(TokenType.USING) and self._parse_charset_name(),
 8465            )
 8466        )
 8467
 8468    def _parse_charset_name(self) -> exp.Expr | None:
 8469        """
 8470        Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting
 8471        for specific name shapes override this.
 8472        """
 8473        return self._parse_var(
 8474            tokens={TokenType.BINARY, TokenType.IDENTIFIER},
 8475        )
 8476
 8477    def _parse_weight_string(self) -> exp.WeightString | exp.Anonymous:
 8478        name = self._tokens[self._index - 2].text
 8479        args = self._parse_csv(self._parse_assignment)
 8480
 8481        # MySQL also accepts an undocumented WEIGHT_STRING(str, n, n, n) form
 8482        if len(args) != 1:
 8483            return self.expression(exp.Anonymous(this=name, expressions=args))
 8484
 8485        return self.expression(
 8486            exp.WeightString(this=args[0], to=self._match(TokenType.ALIAS) and self._parse_types())
 8487        )
 8488
 8489    def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr:
 8490        this = self._parse_assignment()
 8491
 8492        if not self._match(TokenType.ALIAS):
 8493            if self._match(TokenType.COMMA):
 8494                return self.expression(exp.CastToStrType(this=this, to=self._parse_string()))
 8495
 8496            self.raise_error("Expected AS after CAST")
 8497
 8498        fmt = None
 8499        to = self._parse_types(with_collation=True)
 8500
 8501        default = None
 8502        if self._match(TokenType.DEFAULT):
 8503            default = self._parse_bitwise()
 8504            self._match_text_seq("ON", "CONVERSION", "ERROR")
 8505
 8506        if self._match_set((TokenType.FORMAT, TokenType.COMMA)):
 8507            fmt_string = self._parse_wrapped(self._parse_string, optional=True)
 8508            fmt = self._parse_at_time_zone(fmt_string)
 8509
 8510            if not to:
 8511                to = exp.DType.UNKNOWN.into_expr()
 8512            if to.this in exp.DataType.TEMPORAL_TYPES:
 8513                this = self.expression(
 8514                    (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)(
 8515                        this=this,
 8516                        format=exp.Literal.string(
 8517                            format_time(
 8518                                fmt_string.this if fmt_string else "",
 8519                                self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING,
 8520                                self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE,
 8521                            )
 8522                        ),
 8523                        safe=safe,
 8524                    )
 8525                )
 8526
 8527                if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime):
 8528                    this.set("zone", fmt.args["zone"])
 8529                return this
 8530        elif not to:
 8531            self.raise_error("Expected TYPE after CAST")
 8532        elif isinstance(to, exp.Identifier):
 8533            to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True)
 8534        else:
 8535            to = self._parse_char_charset(to)
 8536
 8537        return self.build_cast(
 8538            strict=strict,
 8539            this=this,
 8540            to=to,
 8541            format=fmt,
 8542            safe=safe,
 8543            action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False),
 8544            default=default,
 8545        )
 8546
 8547    def _parse_string_agg(self) -> exp.GroupConcat:
 8548        if self._match(TokenType.DISTINCT):
 8549            args: list[exp.Expr | None] = [
 8550                self.expression(exp.Distinct(expressions=[self._parse_disjunction()]))
 8551            ]
 8552            if self._match(TokenType.COMMA):
 8553                args.extend(self._parse_csv(self._parse_disjunction))
 8554        else:
 8555            self._match(TokenType.ALL)
 8556            args = self._parse_csv(self._parse_disjunction)  # type: ignore
 8557
 8558        if self._match_text_seq("ON", "OVERFLOW"):
 8559            # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior])
 8560            if self._match_text_seq("ERROR"):
 8561                on_overflow: exp.Expr | None = exp.var("ERROR")
 8562            else:
 8563                self._match_text_seq("TRUNCATE")
 8564                on_overflow = self.expression(
 8565                    exp.OverflowTruncateBehavior(
 8566                        this=self._parse_string(),
 8567                        with_count=(
 8568                            self._match_text_seq("WITH", "COUNT")
 8569                            or not self._match_text_seq("WITHOUT", "COUNT")
 8570                        ),
 8571                    )
 8572                )
 8573        else:
 8574            on_overflow = None
 8575
 8576        index = self._index
 8577        if not self._match(TokenType.R_PAREN) and args:
 8578            # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]])
 8579            # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n])
 8580            # The order is parsed through `this` as a canonicalization for WITHIN GROUPs
 8581            args[0] = self._parse_limit(this=self._parse_order(this=args[0]))
 8582            return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1)))
 8583
 8584        # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]).
 8585        # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that
 8586        # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them.
 8587        if not self._match_text_seq("WITHIN", "GROUP"):
 8588            self._retreat(index)
 8589            return self.validate_expression(exp.GroupConcat.from_arg_list(args), args)
 8590
 8591        # The corresponding match_r_paren will be called in parse_function (caller)
 8592        self._match_l_paren()
 8593
 8594        return self.expression(
 8595            exp.GroupConcat(
 8596                this=self._parse_order(this=seq_get(args, 0)),
 8597                separator=seq_get(args, 1),
 8598                on_overflow=on_overflow,
 8599            )
 8600        )
 8601
 8602    def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None:
 8603        this = self._parse_bitwise()
 8604
 8605        if self._match(TokenType.USING):
 8606            to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name())
 8607        elif self._match(TokenType.COMMA):
 8608            to = self._parse_char_charset(self._parse_types())
 8609        else:
 8610            to = None
 8611
 8612        return self.build_cast(strict=strict, this=this, to=to, safe=safe)
 8613
 8614    def _parse_char_charset(self, to: exp.Expr | None) -> exp.Expr | None:
 8615        # MySQL: CHAR[(N)] CHARACTER SET charset_name, the length is kept
 8616        if (
 8617            isinstance(to, exp.DataType)
 8618            and to.this == exp.DType.CHAR
 8619            and (self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET"))
 8620        ):
 8621            to.set("this", exp.DType.CHARACTER_SET)
 8622            to.set("kind", self._parse_var_or_string())
 8623        return to
 8624
 8625    def _parse_xml_element(self) -> exp.XMLElement:
 8626        if self._match_text_seq("EVALNAME"):
 8627            evalname = True
 8628            this = self._parse_bitwise()
 8629        else:
 8630            evalname = None
 8631            self._match_text_seq("NAME")
 8632            this = self._parse_id_var()
 8633
 8634        return self.expression(
 8635            exp.XMLElement(
 8636                this=this,
 8637                expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise),
 8638                evalname=evalname,
 8639            )
 8640        )
 8641
 8642    def _parse_xml_table(self) -> exp.XMLTable:
 8643        namespaces = None
 8644        passing = None
 8645        columns = None
 8646
 8647        if self._match_text_seq("XMLNAMESPACES", "("):
 8648            namespaces = self._parse_xml_namespace()
 8649            self._match_text_seq(")", ",")
 8650
 8651        this = self._parse_string()
 8652
 8653        if self._match_text_seq("PASSING"):
 8654            # The BY VALUE keywords are optional and are provided for semantic clarity
 8655            self._match_text_seq("BY", "VALUE")
 8656            passing = self._parse_csv(self._parse_column)
 8657
 8658        by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF")
 8659
 8660        if self._match_text_seq("COLUMNS"):
 8661            columns = self._parse_csv(self._parse_field_def)
 8662
 8663        return self.expression(
 8664            exp.XMLTable(
 8665                this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref
 8666            )
 8667        )
 8668
 8669    def _parse_xml_namespace(self) -> list[exp.XMLNamespace]:
 8670        namespaces = []
 8671
 8672        while True:
 8673            if self._match(TokenType.DEFAULT):
 8674                uri = self._parse_string()
 8675            else:
 8676                uri = self._parse_alias(self._parse_string())
 8677            namespaces.append(self.expression(exp.XMLNamespace(this=uri)))
 8678            if not self._match(TokenType.COMMA):
 8679                break
 8680
 8681        return namespaces
 8682
 8683    def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None:
 8684        args = self._parse_csv(self._parse_disjunction)
 8685
 8686        if len(args) < 3:
 8687            return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1)))
 8688
 8689        return self.expression(exp.DecodeCase(expressions=args))
 8690
 8691    def _parse_json_key_value(self) -> exp.JSONKeyValue | None:
 8692        self._match_text_seq("KEY")
 8693        key = self._parse_column()
 8694        self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS)
 8695        self._match_text_seq("VALUE")
 8696        value = self._parse_bitwise()
 8697
 8698        if not key and not value:
 8699            return None
 8700        return self.expression(exp.JSONKeyValue(this=key, expression=value))
 8701
 8702    def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None:
 8703        if not this or not self._match_text_seq("FORMAT", "JSON"):
 8704            return this
 8705
 8706        return self.expression(exp.FormatJson(this=this))
 8707
 8708    def _parse_on_condition(self) -> exp.OnCondition | None:
 8709        # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS)
 8710        if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR:
 8711            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8712            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8713        else:
 8714            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8715            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8716
 8717        null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS)
 8718
 8719        if not empty and not error and not null:
 8720            return None
 8721
 8722        return self.expression(exp.OnCondition(empty=empty, error=error, null=null))
 8723
 8724    def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None:
 8725        # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL)
 8726        for value in values:
 8727            if self._match_text_seq(value, "ON", on):
 8728                return f"{value} ON {on}"
 8729
 8730        index = self._index
 8731        if self._match(TokenType.DEFAULT):
 8732            default_value = self._parse_bitwise()
 8733            if self._match_text_seq("ON", on):
 8734                return default_value
 8735
 8736            self._retreat(index)
 8737
 8738        return None
 8739
 8740    @t.overload
 8741    def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ...
 8742
 8743    @t.overload
 8744    def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ...
 8745
 8746    def _parse_json_object(self, agg=False):
 8747        star = self._parse_star()
 8748        expressions = (
 8749            [star]
 8750            if star
 8751            else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value()))
 8752        )
 8753        null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT")
 8754
 8755        unique_keys = None
 8756        if self._match_text_seq("WITH", "UNIQUE"):
 8757            unique_keys = True
 8758        elif self._match_text_seq("WITHOUT", "UNIQUE"):
 8759            unique_keys = False
 8760
 8761        self._match_text_seq("KEYS")
 8762
 8763        return_type = self._match_text_seq("RETURNING") and self._parse_format_json(
 8764            self._parse_type()
 8765        )
 8766        encoding = self._match_text_seq("ENCODING") and self._parse_var()
 8767
 8768        return self.expression(
 8769            (exp.JSONObjectAgg if agg else exp.JSONObject)(
 8770                expressions=expressions,
 8771                null_handling=null_handling,
 8772                unique_keys=unique_keys,
 8773                return_type=return_type,
 8774                encoding=encoding,
 8775            )
 8776        )
 8777
 8778    # Note: this is currently incomplete; it only implements the "JSON_value_column" part
 8779    def _parse_json_column_def(self) -> exp.JSONColumnDef:
 8780        if not self._match_text_seq("NESTED"):
 8781            this = self._parse_id_var()
 8782            ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY)
 8783            kind = self._parse_types(allow_identifiers=False)
 8784            nested = None
 8785        else:
 8786            this = None
 8787            ordinality = None
 8788            kind = None
 8789            nested = True
 8790
 8791        format_json = self._match_text_seq("FORMAT", "JSON")
 8792        path = self._match_text_seq("PATH") and self._parse_string()
 8793        nested_schema = nested and self._parse_json_schema()
 8794
 8795        return self.expression(
 8796            exp.JSONColumnDef(
 8797                this=this,
 8798                kind=kind,
 8799                path=path,
 8800                nested_schema=nested_schema,
 8801                ordinality=ordinality,
 8802                format_json=format_json,
 8803            )
 8804        )
 8805
 8806    def _parse_json_schema(self) -> exp.JSONSchema:
 8807        self._match_text_seq("COLUMNS")
 8808        return self.expression(
 8809            exp.JSONSchema(
 8810                expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True)
 8811            )
 8812        )
 8813
 8814    def _parse_json_table(self) -> exp.JSONTable:
 8815        this = self._parse_format_json(self._parse_bitwise())
 8816        path = self._match(TokenType.COMMA) and self._parse_string()
 8817        error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL")
 8818        empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL")
 8819        schema = self._parse_json_schema()
 8820
 8821        return exp.JSONTable(
 8822            this=this,
 8823            schema=schema,
 8824            path=path,
 8825            error_handling=error_handling,
 8826            empty_handling=empty_handling,
 8827        )
 8828
 8829    def _parse_match_against(self) -> exp.MatchAgainst:
 8830        if self._match_text_seq("TABLE"):
 8831            # parse SingleStore MATCH(TABLE ...) syntax
 8832            # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/
 8833            expressions = []
 8834            table = self._parse_table()
 8835            if table:
 8836                expressions = [table]
 8837        else:
 8838            expressions = self._parse_csv(self._parse_column)
 8839
 8840        self._match_text_seq(")", "AGAINST", "(")
 8841
 8842        this = self._parse_string()
 8843
 8844        if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"):
 8845            modifier = "IN NATURAL LANGUAGE MODE"
 8846            if self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8847                modifier = f"{modifier} WITH QUERY EXPANSION"
 8848        elif self._match_text_seq("IN", "BOOLEAN", "MODE"):
 8849            modifier = "IN BOOLEAN MODE"
 8850        elif self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8851            modifier = "WITH QUERY EXPANSION"
 8852        else:
 8853            modifier = None
 8854
 8855        return self.expression(
 8856            exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier)
 8857        )
 8858
 8859    # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16
 8860    def _parse_open_json(self) -> exp.OpenJSON:
 8861        this = self._parse_bitwise()
 8862        path = self._match(TokenType.COMMA) and self._parse_string()
 8863
 8864        def _parse_open_json_column_def() -> exp.OpenJSONColumnDef:
 8865            this = self._parse_field(any_token=True)
 8866            kind = self._parse_types()
 8867            path = self._parse_string()
 8868            as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON)
 8869
 8870            return self.expression(
 8871                exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json)
 8872            )
 8873
 8874        expressions = None
 8875        if self._match_pair(TokenType.R_PAREN, TokenType.WITH):
 8876            self._match_l_paren()
 8877            expressions = self._parse_csv(_parse_open_json_column_def)
 8878
 8879        return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions))
 8880
 8881    def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition:
 8882        args = self._parse_csv(self._parse_bitwise)
 8883
 8884        if self._match(TokenType.IN):
 8885            return self.expression(
 8886                exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0))
 8887            )
 8888
 8889        if haystack_first:
 8890            haystack = seq_get(args, 0)
 8891            needle = seq_get(args, 1)
 8892        else:
 8893            haystack = seq_get(args, 1)
 8894            needle = seq_get(args, 0)
 8895
 8896        return self.expression(
 8897            exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2))
 8898        )
 8899
 8900    def _parse_join_hint(self, func_name: str) -> exp.JoinHint:
 8901        args = self._parse_csv(self._parse_table)
 8902        return exp.JoinHint(this=func_name.upper(), expressions=args)
 8903
 8904    def _parse_substring(self) -> exp.Substring:
 8905        # Postgres supports the form: substring(string [from int] [for int])
 8906        # (despite being undocumented, the reverse order also works)
 8907        # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6
 8908
 8909        args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise))
 8910
 8911        start, length = None, None
 8912
 8913        while self._curr:
 8914            if self._match(TokenType.FROM):
 8915                start = self._parse_bitwise()
 8916            elif self._match(TokenType.FOR):
 8917                if not start:
 8918                    start = exp.Literal.number(1)
 8919                length = self._parse_bitwise()
 8920            else:
 8921                break
 8922
 8923        if start:
 8924            args.append(start)
 8925        if length:
 8926            args.append(length)
 8927
 8928        return self.validate_expression(exp.Substring.from_arg_list(args), args)
 8929
 8930    def _parse_trim(self) -> exp.Trim:
 8931        # https://www.w3resource.com/sql/character-functions/trim.php
 8932        # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html
 8933
 8934        position = None
 8935        collation = None
 8936        expression = None
 8937
 8938        if self._match_texts(self.TRIM_TYPES):
 8939            position = self._prev.text.upper()
 8940
 8941        this = self._parse_bitwise()
 8942        if self._match_set((TokenType.FROM, TokenType.COMMA)):
 8943            invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST
 8944            expression = self._parse_bitwise()
 8945
 8946            if invert_order:
 8947                this, expression = expression, this
 8948
 8949        if self._match(TokenType.COLLATE):
 8950            collation = self._parse_bitwise()
 8951
 8952        return self.expression(
 8953            exp.Trim(this=this, position=position, expression=expression, collation=collation)
 8954        )
 8955
 8956    def _parse_window_clause(self) -> list[exp.Expr] | None:
 8957        return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None
 8958
 8959    def _parse_named_window(self) -> exp.Expr | None:
 8960        return self._parse_window(self._parse_id_var(), alias=True)
 8961
 8962    def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None:
 8963        if self._curr.token_type == TokenType.VAR:
 8964            if self._match_text_seq("IGNORE", "NULLS"):
 8965                return self.expression(exp.IgnoreNulls(this=this))
 8966            if self._match_text_seq("RESPECT", "NULLS"):
 8967                return self.expression(exp.RespectNulls(this=this))
 8968        return this
 8969
 8970    def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None:
 8971        if self._match(TokenType.HAVING):
 8972            self._match_texts(("MAX", "MIN"))
 8973            max = self._prev.text.upper() != "MIN"
 8974            return self.expression(
 8975                exp.HavingMax(this=this, expression=self._parse_column(), max=max)
 8976            )
 8977
 8978        return this
 8979
 8980    def _parse_per(self, this: exp.Expr | None) -> exp.Expr | None:
 8981        if self._next.token_type == TokenType.L_PAREN and self._match_text_seq("PER"):
 8982            return self.expression(
 8983                exp.Per(this=this, expressions=self._parse_wrapped_csv(self._parse_disjunction))
 8984            )
 8985
 8986        return this
 8987
 8988    def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None:
 8989        func = this
 8990        comments = func.comments if isinstance(func, exp.Expr) else None
 8991
 8992        # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html
 8993        if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue):
 8994            if self._match_text_seq("FROM", "FIRST"):
 8995                this.set("from_first", True)
 8996            elif self._match_text_seq("FROM", "LAST"):
 8997                this.set("from_first", False)
 8998
 8999        # T-SQL allows the OVER (...) syntax after WITHIN GROUP.
 9000        # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16
 9001        if self._match_text_seq("WITHIN", "GROUP"):
 9002            order = self._parse_wrapped(self._parse_order)
 9003            this = self.expression(exp.WithinGroup(this=this, expression=order))
 9004
 9005        if self._match_pair(TokenType.FILTER, TokenType.L_PAREN):
 9006            self._match(TokenType.WHERE)
 9007            this = self.expression(
 9008                exp.Filter(this=this, expression=self._parse_where(skip_where_token=True))
 9009            )
 9010            self._match_r_paren()
 9011
 9012        # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER
 9013        # Some dialects choose to implement and some do not.
 9014        # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html
 9015
 9016        # There is some code above in _parse_lambda that handles
 9017        #   SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ...
 9018
 9019        # The below changes handle
 9020        #   SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ...
 9021
 9022        # Oracle allows both formats
 9023        #   (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html)
 9024        #   and Snowflake chose to do the same for familiarity
 9025        #   https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes
 9026        if isinstance(this, exp.AggFunc):
 9027            ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls)
 9028
 9029            if ignore_respect and ignore_respect is not this:
 9030                ignore_respect.replace(ignore_respect.this)
 9031                this = self.expression(ignore_respect.__class__(this=this))
 9032
 9033        this = self._parse_respect_or_ignore_nulls(this)
 9034
 9035        # bigquery select from window x AS (partition by ...)
 9036        if alias:
 9037            over = None
 9038            self._match(TokenType.ALIAS)
 9039        elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS):
 9040            return self._parse_per(this)
 9041        else:
 9042            over = self._prev.text.upper()
 9043
 9044        if comments and isinstance(func, exp.Expr):
 9045            func.pop_comments()
 9046
 9047        if not self._match(TokenType.L_PAREN):
 9048            return self._parse_per(
 9049                self.expression(
 9050                    exp.Window(this=this, alias=self._parse_id_var(False), over=over),
 9051                    comments=comments,
 9052                )
 9053            )
 9054
 9055        window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS)
 9056
 9057        first: bool | None = True if self._match(TokenType.FIRST) else None
 9058        if self._match_text_seq("LAST"):
 9059            first = False
 9060
 9061        partition, order = self._parse_partition_and_order()
 9062        kind = (
 9063            self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS")
 9064        ) and self._prev.text
 9065
 9066        if kind:
 9067            self._match(TokenType.BETWEEN)
 9068            start = self._parse_window_spec()
 9069
 9070            end = self._parse_window_spec() if self._match(TokenType.AND) else {}
 9071            exclude = (
 9072                self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS)
 9073                if self._match_text_seq("EXCLUDE")
 9074                else None
 9075            )
 9076
 9077            spec = self.expression(
 9078                exp.WindowSpec(
 9079                    kind=kind,
 9080                    start=start["value"],
 9081                    start_side=start["side"],
 9082                    end=end.get("value"),
 9083                    end_side=end.get("side"),
 9084                    exclude=exclude,
 9085                )
 9086            )
 9087        else:
 9088            spec = None
 9089
 9090        self._match_r_paren()
 9091
 9092        window = self.expression(
 9093            exp.Window(
 9094                this=this,
 9095                partition_by=partition,
 9096                order=order,
 9097                spec=spec,
 9098                alias=window_alias,
 9099                over=over,
 9100                first=first,
 9101            ),
 9102            comments=comments,
 9103        )
 9104
 9105        # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...)
 9106        if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False):
 9107            return self._parse_window(window, alias=alias)
 9108
 9109        return self._parse_per(window)
 9110
 9111    def _parse_partition_and_order(
 9112        self,
 9113    ) -> tuple[list[exp.Expr], exp.Expr | None]:
 9114        return self._parse_partition_by(), self._parse_order()
 9115
 9116    def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]:
 9117        self._match(TokenType.BETWEEN)
 9118
 9119        return {
 9120            "value": (
 9121                (self._match_text_seq("UNBOUNDED") and "UNBOUNDED")
 9122                or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW")
 9123                or self._parse_bitwise()
 9124            ),
 9125            "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None,
 9126        }
 9127
 9128    def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None:
 9129        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 9130        # so this section tries to parse the clause version and if it fails, it treats the token
 9131        # as an identifier (alias)
 9132        if self._can_parse_limit_or_offset():
 9133            return this
 9134
 9135        # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the
 9136        # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it.
 9137        if self._can_parse_named_window():
 9138            return this
 9139
 9140        any_token = self._match(TokenType.ALIAS)
 9141        comments = self._prev_comments
 9142
 9143        if explicit and not any_token:
 9144            return this
 9145
 9146        if self._match(TokenType.L_PAREN):
 9147            aliases = self.expression(
 9148                exp.Aliases(
 9149                    this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token))
 9150                ),
 9151                comments=comments,
 9152            )
 9153            self._match_r_paren(aliases)
 9154            return aliases
 9155
 9156        alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or (
 9157            self.STRING_ALIASES and self._parse_string_as_identifier()
 9158        )
 9159
 9160        if alias:
 9161            comments.extend(alias.pop_comments())
 9162            this = self.expression(exp.Alias(this=this, alias=alias), comments=comments)
 9163            column = this.this
 9164
 9165            # Moves the comment next to the alias in `expr /* comment */ AS alias`
 9166            if not this.comments and column and column.comments:
 9167                this.comments = column.pop_comments()
 9168
 9169        return this
 9170
 9171    def _parse_id_var(
 9172        self,
 9173        any_token: bool = True,
 9174        tokens: t.Collection[TokenType] | None = None,
 9175    ) -> exp.Expr | None:
 9176        expression = self._parse_identifier()
 9177        if not expression and (
 9178            (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS)
 9179        ):
 9180            quoted = self._prev.token_type == TokenType.STRING
 9181            expression = self._identifier_expression(quoted=quoted)
 9182
 9183        return expression
 9184
 9185    def _parse_string(self) -> exp.Expr | None:
 9186        if self._match_set(self.STRING_PARSERS):
 9187            return self.STRING_PARSERS[self._prev.token_type](self, self._prev)
 9188        return self._parse_placeholder()
 9189
 9190    def _parse_string_as_identifier(self) -> exp.Identifier | None:
 9191        if not self._match(TokenType.STRING):
 9192            return None
 9193        output = exp.to_identifier(self._prev.text, quoted=True)
 9194        output.update_positions(self._prev)
 9195        return output
 9196
 9197    def _parse_number(self) -> exp.Expr | None:
 9198        if self._match_set(self.NUMERIC_PARSERS):
 9199            return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev)
 9200        return self._parse_placeholder()
 9201
 9202    def _parse_identifier(self) -> exp.Expr | None:
 9203        if self._match(TokenType.IDENTIFIER):
 9204            return self._identifier_expression(quoted=True)
 9205        return self._parse_placeholder()
 9206
 9207    def _parse_var(
 9208        self,
 9209        any_token: bool = False,
 9210        tokens: t.Collection[TokenType] | None = None,
 9211        upper: bool = False,
 9212    ) -> exp.Expr | None:
 9213        if (
 9214            (any_token and self._advance_any())
 9215            or self._match(TokenType.VAR)
 9216            or (self._match_set(tokens) if tokens else False)
 9217        ):
 9218            return self.expression(
 9219                exp.Var(this=self._prev.text.upper() if upper else self._prev.text)
 9220            )
 9221        return self._parse_placeholder()
 9222
 9223    def _advance_any(self, ignore_reserved: bool = False) -> Token | None:
 9224        if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS):
 9225            self._advance()
 9226            return self._prev
 9227        return None
 9228
 9229    def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None:
 9230        return self._parse_string() or self._parse_var(any_token=True, upper=upper)
 9231
 9232    def _parse_primary_or_var(self) -> exp.Expr | None:
 9233        return self._parse_primary() or self._parse_var(any_token=True)
 9234
 9235    def _parse_null(self) -> exp.Expr | None:
 9236        if self._match_set((TokenType.NULL, TokenType.UNKNOWN)):
 9237            return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev)
 9238        return self._parse_placeholder()
 9239
 9240    def _parse_boolean(self) -> exp.Expr | None:
 9241        if self._match(TokenType.TRUE):
 9242            return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev)
 9243        if self._match(TokenType.FALSE):
 9244            return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev)
 9245        return self._parse_placeholder()
 9246
 9247    def _parse_star(self) -> exp.Expr | None:
 9248        if self._match(TokenType.STAR):
 9249            return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev)
 9250        return self._parse_placeholder()
 9251
 9252    def _parse_parameter(self) -> exp.Parameter:
 9253        this = self._parse_identifier() or self._parse_primary_or_var()
 9254        return self.expression(exp.Parameter(this=this))
 9255
 9256    def _parse_placeholder(self) -> exp.Expr | None:
 9257        if self._match_set(self.PLACEHOLDER_PARSERS):
 9258            placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self)
 9259            if placeholder:
 9260                return placeholder
 9261            self._advance(-1)
 9262        return None
 9263
 9264    def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None:
 9265        if not self._match_texts(keywords):
 9266            return None
 9267        if self._match(TokenType.L_PAREN, advance=False):
 9268            return self._parse_wrapped_csv(self._parse_expression)
 9269
 9270        expression = self._parse_alias(self._parse_disjunction(), explicit=True)
 9271        return [expression] if expression else None
 9272
 9273    def _parse_csv(
 9274        self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA
 9275    ) -> list[T]:
 9276        parse_result = parse_method()
 9277        items = [parse_result] if parse_result is not None else []
 9278
 9279        while self._match(sep):
 9280            if isinstance(parse_result, exp.Expr):
 9281                self._add_comments(parse_result)
 9282            parse_result = parse_method()
 9283            if parse_result is not None:
 9284                items.append(parse_result)
 9285
 9286        return items
 9287
 9288    def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]:
 9289        return self._parse_wrapped_csv(self._parse_id_var, optional=optional)
 9290
 9291    def _parse_wrapped_csv(
 9292        self,
 9293        parse_method: t.Callable[[], T | None],
 9294        sep: TokenType = TokenType.COMMA,
 9295        optional: bool = False,
 9296    ) -> list[T]:
 9297        return self._parse_wrapped(
 9298            lambda: self._parse_csv(parse_method, sep=sep), optional=optional
 9299        )
 9300
 9301    def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T:
 9302        wrapped = self._match(TokenType.L_PAREN)
 9303        if not wrapped and not optional:
 9304            self.raise_error("Expecting (")
 9305        parse_result = parse_method()
 9306        if wrapped:
 9307            self._match_r_paren()
 9308        return parse_result
 9309
 9310    def _parse_expressions(self) -> list[exp.Expr]:
 9311        return self._parse_csv(self._parse_expression)
 9312
 9313    def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None:
 9314        return (
 9315            self._parse_set_operations(
 9316                self._parse_alias(self._parse_assignment(), explicit=True)
 9317                if alias
 9318                else self._parse_assignment()
 9319            )
 9320            or self._parse_select()
 9321        )
 9322
 9323    def _parse_ddl_select(self) -> exp.Expr | None:
 9324        return self._parse_query_modifiers(
 9325            self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False))
 9326        )
 9327
 9328    def _parse_transaction(self) -> exp.Transaction | exp.Command:
 9329        this = None
 9330        if self._match_texts(self.TRANSACTION_KIND):
 9331            this = self._prev.text
 9332
 9333        self._match_texts(("TRANSACTION", "WORK"))
 9334
 9335        modes = []
 9336        while True:
 9337            mode = []
 9338            while self._match(TokenType.VAR) or self._match(TokenType.NOT):
 9339                mode.append(self._prev.text)
 9340
 9341            if mode:
 9342                modes.append(" ".join(mode))
 9343            if not self._match(TokenType.COMMA):
 9344                break
 9345
 9346        return self.expression(exp.Transaction(this=this, modes=modes))
 9347
 9348    def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback:
 9349        chain = None
 9350        savepoint = None
 9351        is_rollback = self._prev.token_type == TokenType.ROLLBACK
 9352
 9353        self._match_texts(("TRANSACTION", "WORK"))
 9354
 9355        if self._match_text_seq("TO"):
 9356            self._match_text_seq("SAVEPOINT")
 9357            savepoint = self._parse_id_var()
 9358
 9359        if self._match(TokenType.AND):
 9360            chain = not self._match_text_seq("NO")
 9361            self._match_text_seq("CHAIN")
 9362
 9363        if is_rollback:
 9364            return self.expression(exp.Rollback(savepoint=savepoint))
 9365
 9366        return self.expression(exp.Commit(chain=chain))
 9367
 9368    def _parse_refresh(self) -> exp.Refresh | exp.Command:
 9369        start = self._prev
 9370
 9371        if self._match_text_seq("EXTERNAL", "TABLE"):
 9372            kind = "EXTERNAL TABLE"
 9373        elif self._match(TokenType.TABLE):
 9374            kind = "TABLE"
 9375        elif self._match_text_seq("MATERIALIZED", "VIEW"):
 9376            kind = "MATERIALIZED VIEW"
 9377        else:
 9378            kind = ""
 9379
 9380        this = self._parse_string()
 9381        if not kind and not isinstance(this, exp.Literal):
 9382            return self._parse_as_command(start)
 9383
 9384        return self.expression(exp.Refresh(this=this or self._parse_table(), kind=kind))
 9385
 9386    def _parse_column_def_with_exists(self):
 9387        start = self._index
 9388        self._match(TokenType.COLUMN)
 9389
 9390        exists_column = self._parse_exists(not_=True)
 9391        expression = self._parse_field_def()
 9392
 9393        if not isinstance(expression, exp.ColumnDef):
 9394            self._retreat(start)
 9395            return None
 9396
 9397        expression.set("exists", exists_column)
 9398
 9399        return expression
 9400
 9401    def _parse_add_column(self) -> exp.ColumnDef | None:
 9402        if not self._prev.text.upper() == "ADD":
 9403            return None
 9404
 9405        return self._parse_column_def_with_exists()
 9406
 9407    def _parse_drop_column(self) -> exp.Drop | exp.Command | None:
 9408        if not self._match(TokenType.DROP):
 9409            return None
 9410
 9411        kind = None
 9412        if not self.ALTER_DROP_REQUIRES_COLUMN:
 9413            # A bare `DROP [IF EXISTS] c [CASCADE | RESTRICT]` drops a column
 9414            index = self._index
 9415            self._parse_exists()
 9416            if (
 9417                self._curr.token_type not in (TokenType.COLUMN, TokenType.CONSTRAINT)
 9418                and self._match_set(self.ID_VAR_TOKENS)
 9419                and (
 9420                    not self._curr
 9421                    or self._match(TokenType.COMMA, advance=False)
 9422                    or self._match_texts(("CASCADE", "RESTRICT"), advance=False)
 9423                )
 9424            ):
 9425                kind = "COLUMN"
 9426            self._retreat(index)
 9427
 9428        return self._parse_drop(kind=kind)
 9429
 9430    def _parse_alter_drop_action(self) -> exp.Expr | None:
 9431        drop = self._parse_drop_column()
 9432
 9433        if (
 9434            isinstance(drop, exp.Drop)
 9435            and drop.kind == "COLUMN"
 9436            and not self.dialect.ALTER_TABLE_DROP_REQUIRED_FOR_EACH_COLUMN
 9437            and self._match(TokenType.COMMA)
 9438        ):
 9439            drop.set("tables", [*drop.args["tables"], *self._parse_csv(self._parse_column)])
 9440
 9441        return drop
 9442
 9443    # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html
 9444    def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition:
 9445        return self.expression(
 9446            exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists)
 9447        )
 9448
 9449    def _parse_alter_table_add(self) -> list[exp.Expr]:
 9450        def _parse_add_alteration() -> exp.Expr | None:
 9451            self._match_text_seq("ADD")
 9452            if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) or self._match_texts(
 9453                self.ADD_CONSTRAINT_KEYWORDS, advance=False
 9454            ):
 9455                return self.expression(
 9456                    exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint))
 9457                )
 9458
 9459            column_def = self._parse_add_column()
 9460            if isinstance(column_def, exp.ColumnDef):
 9461                return column_def
 9462
 9463            exists = self._parse_exists(not_=True)
 9464            if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False):
 9465                return self.expression(
 9466                    exp.AddPartition(
 9467                        exists=exists,
 9468                        this=self._parse_field(any_token=True),
 9469                        location=self._match_text_seq("LOCATION", advance=False)
 9470                        and self._parse_property(),
 9471                    )
 9472                )
 9473
 9474            return None
 9475
 9476        if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and (
 9477            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
 9478            or self._match_text_seq("COLUMNS")
 9479        ):
 9480            schema = self._parse_schema()
 9481
 9482            return (
 9483                ensure_list(schema)
 9484                if schema
 9485                else self._parse_csv(self._parse_column_def_with_exists)
 9486            )
 9487
 9488        return self._parse_csv(_parse_add_alteration)
 9489
 9490    def _parse_alter_table_alter(self) -> exp.Expr | None:
 9491        if self._match_texts(self.ALTER_ALTER_PARSERS):
 9492            return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self)
 9493
 9494        # Many dialects support the ALTER [COLUMN] syntax, so if there is no
 9495        # keyword after ALTER we default to parsing this statement
 9496        self._match(TokenType.COLUMN)
 9497        exists = self._parse_exists()
 9498        column = self._parse_field(any_token=True)
 9499
 9500        if self._match_pair(TokenType.DROP, TokenType.DEFAULT):
 9501            return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None))
 9502        if self._match_pair(TokenType.SET, TokenType.DEFAULT):
 9503            return self.expression(
 9504                exp.AlterColumn(
 9505                    this=column, default=self._parse_disjunction(), exists=exists or None
 9506                )
 9507            )
 9508        if self._match(TokenType.COMMENT):
 9509            return self.expression(
 9510                exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None)
 9511            )
 9512        if self._match_text_seq("DROP", "NOT", "NULL"):
 9513            return self.expression(
 9514                exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None)
 9515            )
 9516        if self._match_text_seq("SET", "NOT", "NULL"):
 9517            return self.expression(
 9518                exp.AlterColumn(this=column, allow_null=False, exists=exists or None)
 9519            )
 9520
 9521        if self._match_text_seq("SET", "VISIBLE"):
 9522            return self.expression(
 9523                exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None)
 9524            )
 9525        if self._match_text_seq("SET", "INVISIBLE"):
 9526            return self.expression(
 9527                exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None)
 9528            )
 9529
 9530        self._match_text_seq("SET", "DATA")
 9531        dtype = (
 9532            self._parse_types()
 9533            if self._match_text_seq("TYPE") or not self.ALTER_COLUMN_TYPE_REQUIRES_KEYWORD
 9534            else None
 9535        )
 9536        if not dtype:
 9537            return None
 9538
 9539        return self.expression(
 9540            exp.AlterColumn(
 9541                this=column,
 9542                dtype=dtype,
 9543                collate=self._match(TokenType.COLLATE) and self._parse_term(),
 9544                using=self._match(TokenType.USING) and self._parse_disjunction(),
 9545                exists=exists or None,
 9546            )
 9547        )
 9548
 9549    def _parse_alter_diststyle(self) -> exp.AlterDistStyle:
 9550        if self._match_texts(("ALL", "EVEN", "AUTO")):
 9551            return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper())))
 9552
 9553        self._match_text_seq("KEY", "DISTKEY")
 9554        return self.expression(exp.AlterDistStyle(this=self._parse_column()))
 9555
 9556    def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey:
 9557        if compound:
 9558            self._match_text_seq("SORTKEY")
 9559
 9560        if self._match(TokenType.L_PAREN, advance=False):
 9561            return self.expression(
 9562                exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound)
 9563            )
 9564
 9565        self._match_texts(("AUTO", "NONE"))
 9566        return self.expression(
 9567            exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound)
 9568        )
 9569
 9570    def _parse_alter_table_drop(self) -> list[exp.Expr]:
 9571        index = self._index - 1
 9572
 9573        partition_exists = self._parse_exists()
 9574        if self._match(TokenType.PARTITION, advance=False):
 9575            return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists))
 9576
 9577        self._retreat(index)
 9578        return self._parse_csv(self._parse_alter_drop_action)
 9579
 9580    def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None:
 9581        if self._match(TokenType.COLUMN) or (
 9582            not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False)
 9583        ):
 9584            exists = self._parse_exists()
 9585            old_column = self._parse_column()
 9586            to = self._match_text_seq("TO")
 9587            new_column = self._parse_column()
 9588
 9589            if old_column is None or not to or new_column is None:
 9590                return None
 9591
 9592            return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists))
 9593
 9594        self._match_text_seq("TO")
 9595        return self.expression(exp.AlterRename(this=self._parse_table(schema=True)))
 9596
 9597    def _parse_alter_table_set(self) -> exp.AlterSet:
 9598        alter_set = self.expression(exp.AlterSet())
 9599
 9600        if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq(
 9601            "TABLE", "PROPERTIES"
 9602        ):
 9603            alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment))
 9604        elif self._match_text_seq("FILESTREAM_ON", advance=False):
 9605            alter_set.set("expressions", [self._parse_assignment()])
 9606        elif self._match_texts(("LOGGED", "UNLOGGED")):
 9607            alter_set.set("option", exp.var(self._prev.text.upper()))
 9608        elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")):
 9609            alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}"))
 9610        elif self._match_text_seq("LOCATION"):
 9611            alter_set.set("location", self._parse_field())
 9612        elif self._match_text_seq("ACCESS", "METHOD"):
 9613            alter_set.set("access_method", self._parse_field())
 9614        elif self._match_text_seq("TABLESPACE"):
 9615            alter_set.set("tablespace", self._parse_field())
 9616        elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"):
 9617            alter_set.set("file_format", [self._parse_field()])
 9618        elif self._match_text_seq("STAGE_FILE_FORMAT"):
 9619            alter_set.set("file_format", self._parse_wrapped_options())
 9620        elif self._match_text_seq("STAGE_COPY_OPTIONS"):
 9621            alter_set.set("copy_options", self._parse_wrapped_options())
 9622        elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"):
 9623            alter_set.set("tag", self._parse_csv(self._parse_assignment))
 9624        else:
 9625            if self._match_text_seq("SERDE"):
 9626                alter_set.set("serde", self._parse_field())
 9627
 9628            properties = self._parse_wrapped(self._parse_properties, optional=True)
 9629            alter_set.set("expressions", [properties])
 9630
 9631        return alter_set
 9632
 9633    def _parse_alter_session(self) -> exp.AlterSession:
 9634        """Parse ALTER SESSION SET/UNSET statements."""
 9635        if self._match(TokenType.SET):
 9636            expressions = self._parse_csv(lambda: self._parse_set_item_assignment())
 9637            return self.expression(exp.AlterSession(expressions=expressions, unset=False))
 9638
 9639        self._match_text_seq("UNSET")
 9640        expressions = self._parse_csv(
 9641            lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True)))
 9642        )
 9643        return self.expression(exp.AlterSession(expressions=expressions, unset=True))
 9644
 9645    def _parse_alter(self) -> exp.Alter | exp.Command:
 9646        start = self._prev
 9647
 9648        iceberg = self._match_text_seq("ICEBERG")
 9649
 9650        alter_token = self._match_set(self.ALTERABLES) and self._prev
 9651        if not alter_token:
 9652            return self._parse_as_command(start)
 9653        if iceberg and alter_token.token_type != TokenType.TABLE:
 9654            return self._parse_as_command(start)
 9655
 9656        exists = self._parse_exists()
 9657        only = self._match_text_seq("ONLY")
 9658
 9659        if alter_token.token_type == TokenType.SESSION:
 9660            this = None
 9661            check = None
 9662            cluster = None
 9663            parser = self.ALTER_PARSERS.get(alter_token.text.upper())
 9664        else:
 9665            this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS)
 9666            check = self._match_text_seq("WITH", "CHECK")
 9667            cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9668            parser = (
 9669                self.ALTER_PARSERS[self._prev.text.upper()]
 9670                if self._match_texts(self.ALTER_PARSERS)
 9671                else None
 9672            )
 9673
 9674        options: list[exp.Expr] = []
 9675        if (
 9676            not parser
 9677            and alter_token.token_type == TokenType.TABLE
 9678            and not self.ALTER_TABLE_REQUIRES_ACTION
 9679        ):
 9680            # Table options may precede the actions, e.g. MySQL's ENGINE=InnoDB, ADD COLUMN a INT.
 9681            # A clause the property parser rejects, like PARTITION BY RANGE ... MAXVALUE, stays a Command
 9682            properties = self._try_parse(self._parse_properties)
 9683            options = properties.expressions if properties else []
 9684
 9685        actions = ensure_list(parser(self) if parser else None)
 9686        if actions or options:
 9687            # Actions of different kinds and table options may alternate, e.g. MySQL's
 9688            # ADD c INT, AUTO_INCREMENT=5, ADD d INT. The comma before an item may have already
 9689            # been consumed by the previous parser
 9690            while self.ALTER_TABLE_MIXED_ACTIONS and (
 9691                self._match(TokenType.COMMA) or self._prev.token_type == TokenType.COMMA
 9692            ):
 9693                if not self._match_texts(
 9694                    self.PROPERTY_PARSERS, advance=False
 9695                ) and self._match_texts(self.ALTER_PARSERS):
 9696                    parser = self.ALTER_PARSERS[self._prev.text.upper()]
 9697                    parsed = ensure_list(self._try_parse(lambda: parser(self)))
 9698                    if not parsed or any(isinstance(action, exp.Command) for action in parsed):
 9699                        return self._parse_as_command(start)
 9700
 9701                    actions.extend(parsed)
 9702                elif properties := self._try_parse(self._parse_properties):
 9703                    options.extend(properties.expressions)
 9704                else:
 9705                    break
 9706
 9707            not_valid = self._match_text_seq("NOT", "VALID")
 9708            if properties := self._try_parse(self._parse_properties):
 9709                options.extend(properties.expressions)
 9710            cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE")
 9711
 9712            if not self._curr:
 9713                return self.expression(
 9714                    exp.Alter(
 9715                        this=this,
 9716                        kind=alter_token.text.upper(),
 9717                        exists=exists,
 9718                        actions=actions,
 9719                        only=only,
 9720                        options=options,
 9721                        cluster=cluster,
 9722                        not_valid=not_valid,
 9723                        check=check,
 9724                        cascade=cascade,
 9725                        iceberg=iceberg,
 9726                    )
 9727                )
 9728
 9729        return self._parse_as_command(start)
 9730
 9731    def _parse_analyze(self) -> exp.Analyze | exp.Command:
 9732        start = self._prev
 9733        # https://duckdb.org/docs/sql/statements/analyze
 9734        if not self._curr:
 9735            return self.expression(exp.Analyze())
 9736
 9737        options = []
 9738        while self._match_texts(self.ANALYZE_STYLES):
 9739            if self._prev.text.upper() == "BUFFER_USAGE_LIMIT":
 9740                options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}")
 9741            else:
 9742                options.append(self._prev.text.upper())
 9743
 9744        tables: exp.Expr | list[exp.Expr] | None = None
 9745        inner_expression: exp.Expr | None = None
 9746
 9747        kind = self._curr.text.upper() if self._curr else None
 9748
 9749        if self._match(TokenType.TABLE):
 9750            tables = self._parse_csv(self._parse_table_parts)
 9751        elif self._match(TokenType.INDEX):
 9752            tables = self._parse_table_parts()
 9753        elif self._match_text_seq("TABLES"):
 9754            if self._match_set((TokenType.FROM, TokenType.IN)):
 9755                kind = f"{kind} {self._prev.text.upper()}"
 9756                tables = self._parse_table(schema=True, is_db_reference=True)
 9757        elif self._match_text_seq("DATABASE"):
 9758            tables = self._parse_table(schema=True, is_db_reference=True)
 9759        elif self._match_text_seq("CLUSTER"):
 9760            tables = self._parse_table()
 9761        # Try matching inner expr keywords before fallback to parse table.
 9762        elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9763            kind = None
 9764            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9765        else:
 9766            # Empty kind  https://prestodb.io/docs/current/sql/analyze.html
 9767            kind = None
 9768            tables = self._parse_csv(self._parse_table_parts)
 9769
 9770        partition = self._try_parse(self._parse_partition)
 9771        if not partition and self._match_texts(self.PARTITION_KEYWORDS):
 9772            return self._parse_as_command(start)
 9773
 9774        # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9775        if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq(
 9776            "WITH", "ASYNC", "MODE"
 9777        ):
 9778            mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE"
 9779        else:
 9780            mode = None
 9781
 9782        if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9783            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9784
 9785        properties = self._parse_properties()
 9786        return self.expression(
 9787            exp.Analyze(
 9788                kind=kind,
 9789                tables=ensure_list(tables),
 9790                mode=mode,
 9791                partition=partition,
 9792                properties=properties,
 9793                expression=inner_expression,
 9794                options=options,
 9795            )
 9796        )
 9797
 9798    # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html
 9799    def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics:
 9800        this = None
 9801        kind = self._prev.text.upper()
 9802        option = self._prev.text.upper() if self._match_text_seq("DELTA") else None
 9803        expressions = []
 9804
 9805        if not self._match_text_seq("STATISTICS"):
 9806            self.raise_error("Expecting token STATISTICS")
 9807
 9808        if self._match_text_seq("NOSCAN"):
 9809            this = "NOSCAN"
 9810        elif self._match(TokenType.FOR):
 9811            if self._match_text_seq("ALL", "COLUMNS"):
 9812                this = "FOR ALL COLUMNS"
 9813            if self._match_text_seq("COLUMNS"):
 9814                this = "FOR COLUMNS"
 9815                expressions = self._parse_csv(self._parse_column_reference)
 9816        elif self._match_text_seq("SAMPLE"):
 9817            sample = self._parse_number()
 9818            expressions = [
 9819                self.expression(
 9820                    exp.AnalyzeSample(
 9821                        sample=sample,
 9822                        kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None,
 9823                    )
 9824                )
 9825            ]
 9826
 9827        return self.expression(
 9828            exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions)
 9829        )
 9830
 9831    # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html
 9832    def _parse_analyze_validate(self) -> exp.AnalyzeValidate:
 9833        kind = None
 9834        this = None
 9835        expression: exp.Expr | None = None
 9836        if self._match_text_seq("REF", "UPDATE"):
 9837            kind = "REF"
 9838            this = "UPDATE"
 9839            if self._match_text_seq("SET", "DANGLING", "TO", "NULL"):
 9840                this = "UPDATE SET DANGLING TO NULL"
 9841        elif self._match_text_seq("STRUCTURE"):
 9842            kind = "STRUCTURE"
 9843            if self._match_text_seq("CASCADE", "FAST"):
 9844                this = "CASCADE FAST"
 9845            elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts(
 9846                ("ONLINE", "OFFLINE")
 9847            ):
 9848                this = f"CASCADE COMPLETE {self._prev.text.upper()}"
 9849                expression = self._parse_into()
 9850
 9851        return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression))
 9852
 9853    def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None:
 9854        this = self._prev.text.upper()
 9855        if self._match_text_seq("COLUMNS"):
 9856            return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}"))
 9857        return None
 9858
 9859    def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None:
 9860        kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None
 9861        if self._match_text_seq("STATISTICS"):
 9862            return self.expression(exp.AnalyzeDelete(kind=kind))
 9863        return None
 9864
 9865    def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None:
 9866        if self._match_text_seq("CHAINED", "ROWS"):
 9867            return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into()))
 9868        return None
 9869
 9870    # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html
 9871    def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram:
 9872        this = self._prev.text.upper()
 9873        expression: exp.Expr | None = None
 9874        expressions = []
 9875        update_options = None
 9876
 9877        if self._match_text_seq("HISTOGRAM", "ON"):
 9878            expressions = self._parse_csv(self._parse_column_reference)
 9879            with_expressions = []
 9880            while self._match(TokenType.WITH):
 9881                # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9882                if self._match_texts(("SYNC", "ASYNC")):
 9883                    if self._match_text_seq("MODE", advance=False):
 9884                        with_expressions.append(f"{self._prev.text.upper()} MODE")
 9885                        self._advance()
 9886                else:
 9887                    buckets = self._parse_number()
 9888                    if self._match_text_seq("BUCKETS"):
 9889                        with_expressions.append(f"{buckets} BUCKETS")
 9890            if with_expressions:
 9891                expression = self.expression(exp.AnalyzeWith(expressions=with_expressions))
 9892
 9893            if self._match_texts(("MANUAL", "AUTO")) and self._match(
 9894                TokenType.UPDATE, advance=False
 9895            ):
 9896                update_options = self._prev.text.upper()
 9897                self._advance()
 9898            elif self._match_text_seq("USING", "DATA"):
 9899                expression = self.expression(exp.UsingData(this=self._parse_string()))
 9900
 9901        return self.expression(
 9902            exp.AnalyzeHistogram(
 9903                this=this,
 9904                expressions=expressions,
 9905                expression=expression,
 9906                update_options=update_options,
 9907            )
 9908        )
 9909
 9910    def _parse_merge(self) -> exp.Merge:
 9911        self._match(TokenType.INTO)
 9912        target = self._parse_table()
 9913
 9914        if target and self._match(TokenType.ALIAS, advance=False):
 9915            target.set("alias", self._parse_table_alias())
 9916
 9917        self._match(TokenType.USING)
 9918        using = self._parse_table()
 9919
 9920        return self.expression(
 9921            exp.Merge(
 9922                this=target,
 9923                using=using,
 9924                on=self._match(TokenType.ON) and self._parse_disjunction(),
 9925                using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(),
 9926                whens=self._parse_when_matched(),
 9927                returning=self._parse_returning(),
 9928            )
 9929        )
 9930
 9931    def _parse_when_matched(self) -> exp.Whens:
 9932        whens = []
 9933
 9934        while self._match(TokenType.WHEN):
 9935            matched = not self._match(TokenType.NOT)
 9936            self._match_text_seq("MATCHED")
 9937            source = (
 9938                False
 9939                if self._match_text_seq("BY", "TARGET")
 9940                else self._match_text_seq("BY", "SOURCE")
 9941            )
 9942            condition = self._parse_disjunction() if self._match(TokenType.AND) else None
 9943
 9944            self._match(TokenType.THEN)
 9945
 9946            if self._match(TokenType.INSERT):
 9947                this = (
 9948                    exp.Star() if self._match_text_seq("ALL", "BY", "NAME") else self._parse_star()
 9949                )
 9950                if this:
 9951                    then: exp.Expr | None = self.expression(exp.Insert(this=this))
 9952                else:
 9953                    then = self.expression(
 9954                        exp.Insert(
 9955                            this=exp.var("ROW")
 9956                            if self._match_text_seq("ROW")
 9957                            else self._parse_value(values=False),
 9958                            expression=self._match_text_seq("VALUES") and self._parse_value(),
 9959                            where=self._parse_where(),
 9960                        )
 9961                    )
 9962            elif self._match(TokenType.UPDATE):
 9963                expressions = self._parse_star()
 9964                if expressions:
 9965                    then = self.expression(exp.Update(expressions=expressions))
 9966                elif self._match_text_seq("ALL", "BY", "NAME"):
 9967                    then = self.expression(exp.Update(expressions=[exp.Star()]))
 9968                else:
 9969                    then = self.expression(
 9970                        exp.Update(
 9971                            expressions=self._match(TokenType.SET)
 9972                            and self._parse_csv(self._parse_update_assignment),
 9973                            where=self._parse_where(),
 9974                        )
 9975                    )
 9976            elif self._match(TokenType.DELETE):
 9977                then = self.expression(exp.Var(this=self._prev.text))
 9978            else:
 9979                then = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 9980
 9981            whens.append(
 9982                self.expression(
 9983                    exp.When(matched=matched, source=source, condition=condition, then=then)
 9984                )
 9985            )
 9986        return self.expression(exp.Whens(expressions=whens))
 9987
 9988    def _parse_show(self) -> exp.Expr | None:
 9989        parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE)
 9990        if parser:
 9991            return parser(self)
 9992        return self._parse_as_command(self._prev)
 9993
 9994    def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None:
 9995        index = self._index
 9996
 9997        if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"):
 9998            return self._parse_set_transaction(global_=kind == "GLOBAL")
 9999
10000        left = self._parse_primary() or self._parse_column()
10001        assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS)
10002
10003        if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter):
10004            self._retreat(index)
10005            return None
10006
10007        right = self._parse_statement() or self._parse_id_var()
10008        if isinstance(right, (exp.Column, exp.Identifier)):
10009            right = exp.var(right.name)
10010
10011        this = self.expression(exp.EQ(this=left, expression=right))
10012        return self.expression(exp.SetItem(this=this, kind=kind))
10013
10014    def _parse_set_transaction(self, global_: bool = False) -> exp.Expr:
10015        self._match_text_seq("TRANSACTION")
10016        characteristics = self._parse_csv(
10017            lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS)
10018        )
10019        return self.expression(
10020            exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_)
10021        )
10022
10023    def _parse_set_item(self) -> exp.Expr | None:
10024        parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE)
10025        return parser(self) if parser else self._parse_set_item_assignment(kind=None)
10026
10027    def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command:
10028        index = self._index
10029        set_ = self.expression(
10030            exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag)
10031        )
10032
10033        if self._curr:
10034            self._retreat(index)
10035            return self._parse_as_command(self._prev)
10036
10037        return set_
10038
10039    def _parse_var_from_options(
10040        self, options: OPTIONS_TYPE, raise_unmatched: bool = True
10041    ) -> exp.Var | None:
10042        start = self._curr
10043        if not start:
10044            return None
10045
10046        option = start.text.upper()
10047        continuations = (
10048            None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option)
10049        )
10050
10051        index = self._index
10052        self._advance()
10053        for keywords in continuations or []:
10054            if isinstance(keywords, str):
10055                keywords = (keywords,)
10056
10057            if self._match_text_seq(*keywords):
10058                option = f"{option} {' '.join(keywords)}"
10059                break
10060        else:
10061            if continuations or continuations is None:
10062                if raise_unmatched:
10063                    self.raise_error(f"Unknown option {option}")
10064
10065                self._retreat(index)
10066                return None
10067
10068        return exp.var(option)
10069
10070    def _parse_as_command(self, start: Token) -> exp.Command:
10071        while self._curr:
10072            self._advance()
10073        text = self._find_sql(start, self._prev)
10074        size = len(start.text)
10075        self._warn_unsupported()
10076        return exp.Command(this=text[:size], expression=text[size:])
10077
10078    def _parse_dict_property(self, this: str) -> exp.DictProperty:
10079        settings = []
10080
10081        self._match_l_paren()
10082        kind = self._parse_id_var()
10083
10084        if self._match(TokenType.L_PAREN):
10085            while True:
10086                key = self._parse_id_var()
10087                value = self._parse_function() or self._parse_primary_or_var()
10088                if not key and value is None:
10089                    break
10090                settings.append(self.expression(exp.DictSubProperty(this=key, value=value)))
10091            self._match(TokenType.R_PAREN)
10092
10093        self._match_r_paren()
10094
10095        return self.expression(
10096            exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings)
10097        )
10098
10099    def _parse_dict_range(self, this: str) -> exp.DictRange:
10100        self._match_l_paren()
10101        has_min = self._match_text_seq("MIN")
10102        if has_min:
10103            min = self._parse_var() or self._parse_primary()
10104            self._match_text_seq("MAX")
10105            max = self._parse_var() or self._parse_primary()
10106        else:
10107            max = self._parse_var() or self._parse_primary()
10108            min = exp.Literal.number(0)
10109        self._match_r_paren()
10110        return self.expression(exp.DictRange(this=this, min=min, max=max))
10111
10112    def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None:
10113        index = self._index
10114        expression = self._parse_id_var()
10115        position = self._parse_id_var() if self._match(TokenType.COMMA) else None
10116
10117        if not self._match(TokenType.IN):
10118            self._retreat(index - 1)
10119            return None
10120        iterator = self._parse_column()
10121        condition = self._parse_disjunction() if self._match_text_seq("IF") else None
10122        return self.expression(
10123            exp.Comprehension(
10124                this=this,
10125                expression=expression,
10126                position=position,
10127                iterator=iterator,
10128                condition=condition,
10129            )
10130        )
10131
10132    def _parse_heredoc(self) -> exp.Heredoc | None:
10133        if self._match(TokenType.HEREDOC_STRING):
10134            return self.expression(exp.Heredoc(this=self._prev.text))
10135
10136        if not self._match_text_seq("$"):
10137            return None
10138
10139        tags = ["$"]
10140        tag_text = None
10141
10142        if self._is_connected():
10143            self._advance()
10144            tags.append(self._prev.text.upper())
10145        else:
10146            self.raise_error("No closing $ found")
10147
10148        if tags[-1] != "$":
10149            if self._is_connected() and self._match_text_seq("$"):
10150                tag_text = tags[-1]
10151                tags.append("$")
10152            else:
10153                self.raise_error("No closing $ found")
10154
10155        heredoc_start = self._curr
10156
10157        while self._curr:
10158            if self._match_text_seq(*tags, advance=False):
10159                this = self._find_sql(heredoc_start, self._prev)
10160                self._advance(len(tags))
10161                return self.expression(exp.Heredoc(this=this, tag=tag_text))
10162
10163            self._advance()
10164
10165        self.raise_error(f"No closing {''.join(tags)} found")
10166        return None
10167
10168    def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None:
10169        if not self._curr:
10170            return None
10171
10172        index = self._index
10173        this = []
10174        while True:
10175            # The current token might be multiple words
10176            curr = self._curr.text.upper()
10177            key = curr.split(" ")
10178            this.append(curr)
10179
10180            self._advance()
10181            result, trie = in_trie(trie, key)
10182            if result == TrieResult.FAILED:
10183                break
10184
10185            if result == TrieResult.EXISTS:
10186                subparser = parsers[" ".join(this)]
10187                return subparser
10188
10189        self._retreat(index)
10190        return None
10191
10192    def _match_l_paren(self, expression: exp.Expr | None = None) -> None:
10193        if not self._match(TokenType.L_PAREN, expression=expression):
10194            self.raise_error("Expecting (")
10195
10196    def _match_r_paren(self, expression: exp.Expr | None = None) -> None:
10197        if not self._match(TokenType.R_PAREN, expression=expression):
10198            self.raise_error("Expecting )")
10199
10200    def _replace_lambda(
10201        self, node: exp.Expr | None, expressions: list[exp.Expr]
10202    ) -> exp.Expr | None:
10203        if not node:
10204            return node
10205
10206        lambda_types = {e.name: e.args.get("to") or False for e in expressions}
10207
10208        for column in node.find_all(exp.Column):
10209            typ = lambda_types.get(column.parts[0].name)
10210            if typ is not None:
10211                dot_or_id = column.to_dot() if column.table else column.this
10212
10213                if isinstance(typ, exp.DataType):
10214                    dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ.copy()))
10215
10216                parent = column.parent
10217
10218                while isinstance(parent, exp.Dot):
10219                    if not isinstance(parent.parent, exp.Dot):
10220                        parent.replace(dot_or_id)
10221                        break
10222                    parent = parent.parent
10223                else:
10224                    if column is node:
10225                        node = dot_or_id
10226                    else:
10227                        column.replace(dot_or_id)
10228        return node
10229
10230    def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr:
10231        start = self._prev
10232
10233        # Not to be confused with TRUNCATE(number, decimals) function call
10234        if self._match(TokenType.L_PAREN):
10235            self._retreat(self._index - 2)
10236            return self._parse_function()
10237
10238        # Clickhouse supports TRUNCATE DATABASE as well
10239        is_database = self._match(TokenType.DATABASE)
10240
10241        self._match(TokenType.TABLE)
10242
10243        exists = self._parse_exists(not_=False)
10244
10245        expressions = self._parse_csv(
10246            lambda: self._parse_table(schema=True, is_db_reference=is_database)
10247        )
10248
10249        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
10250
10251        if self._match_text_seq("RESTART", "IDENTITY"):
10252            identity = "RESTART"
10253        elif self._match_text_seq("CONTINUE", "IDENTITY"):
10254            identity = "CONTINUE"
10255        else:
10256            identity = None
10257
10258        if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"):
10259            option = self._prev.text
10260        else:
10261            option = None
10262
10263        partition = self._parse_partition()
10264
10265        # Fallback case
10266        if self._curr:
10267            return self._parse_as_command(start)
10268
10269        return self.expression(
10270            exp.TruncateTable(
10271                expressions=expressions,
10272                is_database=is_database,
10273                exists=exists,
10274                cluster=cluster,
10275                identity=identity,
10276                option=option,
10277                partition=partition,
10278            )
10279        )
10280
10281    def _parse_indexed_column(self) -> exp.Expr | None:
10282        return self._parse_ordered(self._parse_opclass)
10283
10284    def _parse_with_operator(self) -> exp.Expr | None:
10285        this = self._parse_indexed_column()
10286
10287        if not self._match(TokenType.WITH):
10288            return this
10289
10290        op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS)
10291
10292        return self.expression(exp.WithOperator(this=this, op=op))
10293
10294    def _parse_wrapped_options(self) -> list[exp.Expr]:
10295        self._match(TokenType.EQ)
10296        self._match(TokenType.L_PAREN)
10297
10298        opts: list[exp.Expr] = []
10299        option: exp.Expr | list[exp.Expr] | None
10300        while self._curr and not self._match(TokenType.R_PAREN):
10301            if self._match_text_seq("FORMAT_NAME", "="):
10302                # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL
10303                option = self._parse_format_name()
10304            else:
10305                option = self._parse_property()
10306
10307            if option is None:
10308                self.raise_error("Unable to parse option")
10309                break
10310
10311            opts.extend(ensure_list(option))
10312
10313        return opts
10314
10315    def _parse_copy_parameters(self) -> list[exp.CopyParameter]:
10316        sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None
10317
10318        options = []
10319        while self._curr and not self._match(TokenType.R_PAREN, advance=False):
10320            option = self._parse_var(any_token=True)
10321            if option is None:
10322                break
10323
10324            prev = self._prev.text.upper()
10325
10326            # Different dialects might separate options and values by white space, "=" and "AS"
10327            self._match(TokenType.EQ)
10328            self._match(TokenType.ALIAS)
10329
10330            param = self.expression(exp.CopyParameter(this=option))
10331
10332            if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match(
10333                TokenType.L_PAREN, advance=False
10334            ):
10335                # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options
10336                param.set("expressions", self._parse_wrapped_options())
10337            elif prev == "FILE_FORMAT":
10338                # T-SQL's external file format case
10339                param.set("expression", self._parse_field())
10340            elif (
10341                prev == "FORMAT"
10342                and self._prev.token_type == TokenType.ALIAS
10343                and self._match_texts(("AVRO", "JSON"))
10344            ):
10345                param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}"))
10346                param.set("expression", self._parse_field())
10347            else:
10348                param.set("expression", self._parse_unquoted_field() or self._parse_bracket())
10349
10350            options.append(param)
10351
10352            if sep:
10353                self._match(sep)
10354
10355        return options
10356
10357    def _parse_credentials(self) -> exp.Credentials | None:
10358        expr = self.expression(exp.Credentials())
10359
10360        if self._match_text_seq("STORAGE_INTEGRATION", "="):
10361            expr.set("storage", self._parse_field())
10362        if self._match_text_seq("CREDENTIALS"):
10363            # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string>
10364            creds = (
10365                self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field()
10366            )
10367            expr.set("credentials", creds)
10368        if self._match_text_seq("ENCRYPTION"):
10369            expr.set("encryption", self._parse_wrapped_options())
10370        if self._match_text_seq("IAM_ROLE"):
10371            expr.set(
10372                "iam_role",
10373                exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(),
10374            )
10375        if self._match_text_seq("REGION"):
10376            expr.set("region", self._parse_field())
10377
10378        return expr
10379
10380    def _parse_file_location(self) -> exp.Expr | None:
10381        return self._parse_field()
10382
10383    def _parse_copy(self) -> exp.Copy | exp.Command:
10384        start = self._prev
10385
10386        self._match(TokenType.INTO)
10387
10388        this = (
10389            self._parse_select(nested=True, parse_subquery_alias=False)
10390            if self._match(TokenType.L_PAREN, advance=False)
10391            else self._parse_table(schema=True)
10392        )
10393
10394        kind = self._match(TokenType.FROM) or not self._match_text_seq("TO")
10395
10396        files = self._parse_csv(self._parse_file_location)
10397        if self._match(TokenType.EQ, advance=False):
10398            # Backtrack one token since we've consumed the lhs of a parameter assignment here.
10399            # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter
10400            # list via `_parse_wrapped(..)` below.
10401            self._advance(-1)
10402            files = []
10403
10404        credentials = self._parse_credentials()
10405
10406        self._match_text_seq("WITH")
10407
10408        params = self._parse_wrapped(self._parse_copy_parameters, optional=True)
10409
10410        # Fallback case
10411        if self._curr:
10412            return self._parse_as_command(start)
10413
10414        return self.expression(
10415            exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params)
10416        )
10417
10418    def _parse_normalize(self) -> exp.Normalize:
10419        return self.expression(
10420            exp.Normalize(
10421                this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var()
10422            )
10423        )
10424
10425    def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor:
10426        args = self._parse_csv(lambda: self._parse_lambda())
10427
10428        this = seq_get(args, 0)
10429        decimals = seq_get(args, 1)
10430
10431        return expr_type(
10432            this=this,
10433            decimals=decimals,
10434            to=self._parse_var() if self._match_text_seq("TO") else None,
10435        )
10436
10437    def _parse_star_ops(self) -> exp.Expr | None:
10438        star_token = self._prev
10439
10440        if self._match_text_seq("COLUMNS", "(", advance=False):
10441            this = self._parse_function()
10442            if isinstance(this, exp.Columns):
10443                this.set("unpack", True)
10444            return this
10445
10446        index = self._index
10447        ilike = self._parse_string() if self._match(TokenType.ILIKE) else None
10448        if not ilike:
10449            # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)`
10450            self._retreat(index)
10451
10452        return self.expression(
10453            exp.Star(
10454                ilike=ilike,
10455                except_=self._parse_star_op("EXCEPT", "EXCLUDE"),
10456                replace=self._parse_star_op("REPLACE"),
10457                rename=self._parse_star_op("RENAME"),
10458            )
10459        ).update_positions(star_token)
10460
10461    def _parse_grant_privilege(self) -> exp.GrantPrivilege | None:
10462        privilege_parts = []
10463
10464        # Keep consuming consecutive keywords until comma (end of this privilege) or ON
10465        # (end of privilege list) or L_PAREN (start of column list) are met
10466        while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False):
10467            privilege_parts.append(self._curr.text.upper())
10468            self._advance()
10469
10470        if not privilege_parts:
10471            self.raise_error("Expected privilege")
10472            return None
10473
10474        this = exp.var(" ".join(privilege_parts))
10475        expressions = (
10476            self._parse_wrapped_csv(self._parse_column)
10477            if self._match(TokenType.L_PAREN, advance=False)
10478            else None
10479        )
10480
10481        return self.expression(exp.GrantPrivilege(this=this, expressions=expressions))
10482
10483    def _parse_grant_principal(self) -> exp.GrantPrincipal | None:
10484        kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper()
10485        principal = self._parse_id_var()
10486
10487        if not principal:
10488            return None
10489
10490        return self.expression(exp.GrantPrincipal(this=principal, kind=kind))
10491
10492    def _parse_grant_revoke_common(
10493        self,
10494    ) -> tuple[list | None, str | None, exp.Expr | None]:
10495        privileges = self._parse_csv(self._parse_grant_privilege)
10496
10497        self._match(TokenType.ON)
10498        kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None
10499
10500        # Attempt to parse the securable e.g. MySQL allows names
10501        # such as "foo.*", "*.*" which are not easily parseable yet
10502        securable = self._try_parse(self._parse_table_parts)
10503
10504        return privileges, kind, securable
10505
10506    def _parse_grant(self) -> exp.Grant | exp.Command:
10507        start = self._prev
10508
10509        privileges, kind, securable = self._parse_grant_revoke_common()
10510
10511        if not securable or not self._match_text_seq("TO"):
10512            return self._parse_as_command(start)
10513
10514        principals = self._parse_csv(self._parse_grant_principal)
10515
10516        grant_option = self._match_text_seq("WITH", "GRANT", "OPTION")
10517
10518        if self._curr:
10519            return self._parse_as_command(start)
10520
10521        return self.expression(
10522            exp.Grant(
10523                privileges=privileges,
10524                kind=kind,
10525                securable=securable,
10526                principals=principals,
10527                grant_option=grant_option,
10528            )
10529        )
10530
10531    def _parse_revoke(self) -> exp.Revoke | exp.Command:
10532        start = self._prev
10533
10534        grant_option = self._match_text_seq("GRANT", "OPTION", "FOR")
10535
10536        privileges, kind, securable = self._parse_grant_revoke_common()
10537
10538        if not securable or not self._match_text_seq("FROM"):
10539            return self._parse_as_command(start)
10540
10541        principals = self._parse_csv(self._parse_grant_principal)
10542
10543        cascade = None
10544        if self._match_texts(("CASCADE", "RESTRICT")):
10545            cascade = self._prev.text.upper()
10546
10547        if self._curr:
10548            return self._parse_as_command(start)
10549
10550        return self.expression(
10551            exp.Revoke(
10552                privileges=privileges,
10553                kind=kind,
10554                securable=securable,
10555                principals=principals,
10556                grant_option=grant_option,
10557                cascade=cascade,
10558            )
10559        )
10560
10561    def _parse_overlay(self) -> exp.Overlay:
10562        def _parse_overlay_arg(text: str) -> exp.Expr | None:
10563            return (
10564                self._parse_bitwise()
10565                if self._match(TokenType.COMMA) or self._match_text_seq(text)
10566                else None
10567            )
10568
10569        return self.expression(
10570            exp.Overlay(
10571                this=self._parse_bitwise(),
10572                expression=_parse_overlay_arg("PLACING"),
10573                from_=_parse_overlay_arg("FROM"),
10574                for_=_parse_overlay_arg("FOR"),
10575            )
10576        )
10577
10578    def _parse_format_name(self) -> exp.Property:
10579        # Note: Although not specified in the docs, Snowflake does accept a string/identifier
10580        # for FILE_FORMAT = <format_name>
10581        return self.expression(
10582            exp.Property(
10583                this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts()
10584            )
10585        )
10586
10587    def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F:
10588        is_distinct = self._match(TokenType.DISTINCT)
10589        if not is_distinct:
10590            self._match(TokenType.ALL)
10591
10592        args = [self._parse_lambda()]
10593        if self._match(TokenType.COMMA):
10594            args.extend(self._parse_function_args())
10595
10596        target = seq_get(args, distinct_index)
10597        if is_distinct and target:
10598            args[distinct_index] = self.expression(exp.Distinct(expressions=[target]))
10599
10600        return func.from_arg_list(args)
10601
10602    def _identifier_expression(
10603        self, token: Token | None = None, quoted: bool | None = None
10604    ) -> exp.Identifier:
10605        token = token or self._prev
10606        return self.expression(exp.Identifier(this=token.text, quoted=quoted), token)
10607
10608    def _build_pipe_cte(
10609        self,
10610        query: exp.Query,
10611        expressions: list[exp.Expr],
10612        alias_cte: exp.TableAlias | None = None,
10613    ) -> exp.Select:
10614        new_cte: str | exp.TableAlias | None
10615        if alias_cte:
10616            new_cte = alias_cte
10617        else:
10618            self._pipe_cte_counter += 1
10619            new_cte = f"__tmp{self._pipe_cte_counter}"
10620
10621        with_ = query.args.get("with_")
10622        ctes = with_.pop() if with_ else None
10623
10624        new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False)
10625        if ctes:
10626            new_select.set("with_", ctes)
10627
10628        return new_select.with_(new_cte, as_=query, copy=False)
10629
10630    def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select:
10631        select = self._parse_select(consume_pipe=False)
10632        if not select:
10633            return query
10634
10635        return self._build_pipe_cte(
10636            query=query.select(*select.expressions, append=False), expressions=[exp.Star()]
10637        )
10638
10639    def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select:
10640        limit = self._parse_limit()
10641        offset = self._parse_offset()
10642        if isinstance(limit, exp.Limit):
10643            curr_limit = query.args.get("limit", limit)
10644            curr_is_limit_all = isinstance(curr_limit, exp.Limit) and curr_limit.is_limit_all
10645
10646            # LIMIT ALL means "no cap", so it's never tighter than a numeric limit.
10647            if limit.is_limit_all:
10648                should_replace = curr_is_limit_all
10649            else:
10650                should_replace = (
10651                    curr_is_limit_all or curr_limit.expression.to_py() >= limit.expression.to_py()
10652                )
10653
10654            if should_replace:
10655                query.limit(limit, copy=False)
10656        if offset:
10657            curr_offset = query.args.get("offset")
10658            curr_offset = curr_offset.expression.to_py() if curr_offset else 0
10659            query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False)
10660
10661        return query
10662
10663    def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None:
10664        this = self._parse_disjunction()
10665        if self._match_text_seq("GROUP", "AND", advance=False):
10666            return this
10667
10668        this = self._parse_alias(this)
10669
10670        if self._match_set((TokenType.ASC, TokenType.DESC), advance=False):
10671            return self._parse_ordered(lambda: this)
10672
10673        return this
10674
10675    def _parse_pipe_syntax_aggregate_group_order_by(
10676        self, query: exp.Select, group_by_exists: bool = True
10677    ) -> exp.Select:
10678        expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields)
10679        aggregates_or_groups, orders = [], []
10680        for element in expr:
10681            if isinstance(element, exp.Ordered):
10682                this = element.this
10683                if isinstance(this, exp.Alias):
10684                    element.set("this", this.args["alias"])
10685                orders.append(element)
10686            else:
10687                this = element
10688            aggregates_or_groups.append(this)
10689
10690        if group_by_exists:
10691            query.select(
10692                *aggregates_or_groups, *query.expressions, append=False, copy=False
10693            ).group_by(
10694                *[projection.args.get("alias", projection) for projection in aggregates_or_groups],
10695                copy=False,
10696            )
10697        else:
10698            query.select(*aggregates_or_groups, append=False, copy=False)
10699
10700        if orders:
10701            return query.order_by(*orders, append=False, copy=False)
10702
10703        return query
10704
10705    def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select:
10706        self._match_text_seq("AGGREGATE")
10707        query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False)
10708
10709        if self._match(TokenType.GROUP_BY) or (
10710            self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY)
10711        ):
10712            query = self._parse_pipe_syntax_aggregate_group_order_by(query)
10713
10714        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10715
10716    def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None:
10717        first_setop = self.parse_set_operation(this=query)
10718        if not first_setop:
10719            return None
10720
10721        def _parse_and_unwrap_query() -> exp.Expr | None:
10722            expr = self._parse_paren()
10723            return expr.assert_is(exp.Subquery).unnest() if expr else None
10724
10725        first_setop.this.pop()
10726
10727        setops = [
10728            first_setop.expression.pop().assert_is(exp.Subquery).unnest(),
10729            *self._parse_csv(_parse_and_unwrap_query),
10730        ]
10731
10732        query = self._build_pipe_cte(query=query, expressions=[exp.Star()])
10733        with_ = query.args.get("with_")
10734        ctes = with_.pop() if with_ else None
10735
10736        if isinstance(first_setop, exp.Union):
10737            query = query.union(*setops, copy=False, **first_setop.args)
10738        elif isinstance(first_setop, exp.Except):
10739            query = query.except_(*setops, copy=False, **first_setop.args)
10740        else:
10741            query = query.intersect(*setops, copy=False, **first_setop.args)
10742
10743        query.set("with_", ctes)
10744
10745        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10746
10747    def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None:
10748        join = self._parse_join()
10749        if not join:
10750            return None
10751
10752        if isinstance(query, exp.Select):
10753            return query.join(join, copy=False)
10754
10755        return query
10756
10757    def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select:
10758        pivots = self._parse_pivots()
10759        if not pivots:
10760            return query
10761
10762        from_ = query.args.get("from_")
10763        if from_:
10764            from_.this.set("pivots", pivots)
10765
10766        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10767
10768    def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select:
10769        self._match_text_seq("EXTEND")
10770        query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False)
10771        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10772
10773    def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select:
10774        sample = self._parse_table_sample()
10775
10776        with_ = query.args.get("with_")
10777        if with_:
10778            with_.expressions[-1].this.set("sample", sample)
10779        else:
10780            query.set("sample", sample)
10781
10782        return query
10783
10784    def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None:
10785        if isinstance(query, exp.Subquery):
10786            query = exp.select("*").from_(query, copy=False)
10787
10788        if not query.args.get("from_"):
10789            query = exp.select("*").from_(query.subquery(copy=False), copy=False)
10790
10791        while self._match(TokenType.PIPE_GT):
10792            start_index = self._index
10793            start_text = self._curr.text.upper()
10794            parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text)
10795            if not parser:
10796                # The set operators (UNION, etc) and the JOIN operator have a few common starting
10797                # keywords, making it tricky to disambiguate them without lookahead. The approach
10798                # here is to try and parse a set operation and if that fails, then try to parse a
10799                # join operator. If that fails as well, then the operator is not supported.
10800                parsed_query = self._parse_pipe_syntax_set_operator(query)
10801                parsed_query = parsed_query or self._parse_pipe_syntax_join(query)
10802                if not parsed_query:
10803                    self._retreat(start_index)
10804                    self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.")
10805                    break
10806                query = parsed_query
10807            else:
10808                query = parser(self, query)
10809
10810        return query
10811
10812    def _parse_declareitem(self) -> exp.DeclareItem | None:
10813        self._match_texts(("VAR", "VARIABLE"))
10814
10815        vars = self._parse_csv(self._parse_id_var)
10816        if not vars:
10817            return None
10818
10819        self._match(TokenType.ALIAS)
10820        kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types()
10821        default = (
10822            self._match(TokenType.DEFAULT) or self._match(TokenType.EQ)
10823        ) and self._parse_bitwise()
10824
10825        return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default))
10826
10827    def _parse_declare(self) -> exp.Declare | exp.Command:
10828        start = self._prev
10829        replace = self._match_text_seq("OR", "REPLACE")
10830        expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem))
10831
10832        if not expressions or self._curr:
10833            return self._parse_as_command(start)
10834
10835        return self.expression(exp.Declare(expressions=expressions, replace=replace))
10836
10837    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10838        exp_class = exp.Cast if strict else exp.TryCast
10839
10840        if exp_class == exp.TryCast:
10841            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10842
10843        return self.expression(exp_class(**kwargs))
10844
10845    def _parse_json_value(self, json_path: bool = True) -> exp.JSONValue:
10846        this = self._parse_bitwise()
10847        self._match(TokenType.COMMA)
10848        path = self._parse_bitwise()
10849
10850        returning = self._match(TokenType.RETURNING) and self._parse_types()
10851
10852        return self.expression(
10853            exp.JSONValue(
10854                this=this,
10855                path=self.dialect.to_json_path(path) if json_path else path,
10856                returning=returning,
10857                on_condition=self._parse_on_condition(),
10858            )
10859        )
10860
10861    def _parse_group_concat(self) -> exp.Expr | None:
10862        def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr:
10863            if isinstance(node, exp.Distinct) and len(node.expressions) > 1:
10864                concat_exprs = [
10865                    self.expression(
10866                        exp.Concat(
10867                            expressions=node.expressions,
10868                            safe=True,
10869                            coalesce=self.dialect.CONCAT_COALESCE,
10870                        )
10871                    )
10872                ]
10873                node.set("expressions", concat_exprs)
10874                return node
10875            if len(exprs) == 1:
10876                return exprs[0]
10877            return self.expression(
10878                exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE)
10879            )
10880
10881        args = self._parse_csv(self._parse_lambda)
10882
10883        if args:
10884            order = args[-1] if isinstance(args[-1], exp.Order) else None
10885
10886            if order:
10887                # Order By is the last (or only) expression in the list and has consumed the 'expr' before it,
10888                # remove 'expr' from exp.Order and add it back to args
10889                args[-1] = order.this
10890                order.set("this", concat_exprs(order.this, args))
10891
10892            this = order or concat_exprs(args[0], args)
10893        else:
10894            this = None
10895
10896        separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None
10897
10898        return self.expression(exp.GroupConcat(this=this, separator=separator))
10899
10900    def _parse_initcap(self) -> exp.Initcap:
10901        expr = exp.Initcap.from_arg_list(self._parse_function_args())
10902
10903        # attach dialect's default delimiters
10904        if expr.args.get("expression") is None:
10905            expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS))
10906
10907        return expr
10908
10909    def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None:
10910        if not self._match(TokenType.L_PAREN):
10911            self._retreat(self._index - 1)
10912            return None
10913
10914        op = ""
10915        while self._curr and not self._match(TokenType.R_PAREN):
10916            op += self._curr.text
10917            self._advance()
10918
10919        comments = self._prev_comments
10920        return self.expression(
10921            exp.Operator(this=this, operator=op, expression=self._parse_bitwise()),
10922            comments=comments,
10923        )

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)
2024    def __init__(
2025        self,
2026        error_level: ErrorLevel | None = None,
2027        error_message_context: int = 100,
2028        max_errors: int = 3,
2029        max_nodes: int = -1,
2030        dialect: DialectType = None,
2031    ):
2032        self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE
2033        self.error_message_context: int = error_message_context
2034        self.max_errors: int = max_errors
2035        self.max_nodes: int = max_nodes
2036        self.dialect: t.Any = _resolve_dialect(dialect)
2037        self.sql: str = ""
2038        self.errors: list[ParseError] = []
2039        self._tokens: list[Token] = []
2040        self._tokens_size: i64 = 0
2041        self._index: i64 = 0
2042        self._curr: Token = SENTINEL_NONE
2043        self._next: Token = SENTINEL_NONE
2044        self._prev: Token = SENTINEL_NONE
2045        self._prev_comments: list[str] = []
2046        self._pipe_cte_counter: int = 0
2047        self._chunks: list[list[Token]] = []
2048        self._chunk_index: i64 = 0
2049        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_SPLIT_TO_TABLE': <bound method Func.from_arg_list of <class 'sqlglot.expressions.array.StrtokSplitToTable'>>, '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: 249>: <class 'sqlglot.expressions.temporal.CurrentDate'>, <TokenType.CURRENT_DATETIME: 250>: <class 'sqlglot.expressions.temporal.CurrentDate'>, <TokenType.CURRENT_TIME: 252>: <class 'sqlglot.expressions.temporal.CurrentTime'>, <TokenType.CURRENT_TIMESTAMP: 253>: <class 'sqlglot.expressions.temporal.CurrentTimestamp'>, <TokenType.CURRENT_USER: 254>: <class 'sqlglot.expressions.functions.CurrentUser'>, <TokenType.CURRENT_ROLE: 256>: <class 'sqlglot.expressions.functions.CurrentRole'>}
STRUCT_TYPE_TOKENS: ClassVar = {<TokenType.OBJECT: 200>, <TokenType.NESTED: 212>, <TokenType.STRUCT: 408>, <TokenType.UNION: 421>}
NESTED_TYPE_TOKENS: ClassVar = {<TokenType.ARRAY: 226>, <TokenType.LIST: 324>, <TokenType.STRUCT: 408>, <TokenType.UNION: 421>, <TokenType.MAP: 327>, <TokenType.OBJECT: 200>, <TokenType.NULLABLE: 175>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.RANGE: 376>}
ENUM_TYPE_TOKENS: ClassVar = {<TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.DYNAMIC: 218>, <TokenType.ENUM: 207>}
AGGREGATE_TYPE_TOKENS: ClassVar = {<TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>}
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.REF: 201>, <TokenType.INET: 202>, <TokenType.IPADDRESS: 203>, <TokenType.IPPREFIX: 204>, <TokenType.IPV4: 205>, <TokenType.IPV6: 206>, <TokenType.ENUM: 207>, <TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.FIXEDSTRING: 210>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>, <TokenType.TDIGEST: 215>, <TokenType.UNKNOWN: 216>, <TokenType.VECTOR: 217>, <TokenType.DYNAMIC: 218>, <TokenType.VOID: 219>, <TokenType.ARRAY: 226>, <TokenType.INTERVAL: 308>, <TokenType.LIST: 324>, <TokenType.MAP: 327>, <TokenType.NOTHING: 337>, <TokenType.NULL: 339>, <TokenType.OBJECT_IDENTIFIER: 340>, <TokenType.PSEUDO_TYPE: 371>, <TokenType.RANGE: 376>, <TokenType.STRUCT: 408>, <TokenType.UNION: 421>}
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: 224>: <class 'sqlglot.expressions.core.Any'>, <TokenType.ALL: 222>: <class 'sqlglot.expressions.core.All'>, <TokenType.EXISTS: 275>: <class 'sqlglot.expressions.functions.Exists'>, <TokenType.SOME: 401>: <class 'sqlglot.expressions.core.Any'>}
SUBQUERY_TOKENS: ClassVar = {<TokenType.SELECT: 392>, <TokenType.WITH: 436>, <TokenType.FROM: 287>}
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: 392>, <TokenType.MOD: 333>, <TokenType.SEMICOLON: 19>, <TokenType.STAR: 20>, <TokenType.BACKSLASH: 21>, <TokenType.SLASH: 22>, <TokenType.LT: 23>, <TokenType.UNKNOWN: 216>, <TokenType.GT: 25>, <TokenType.NOT: 27>, <TokenType.EQ: 28>, <TokenType.AMP: 36>, <TokenType.PIPE: 39>, <TokenType.PLACEHOLDER: 361>, <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.STREAMLIT: 88>, <TokenType.DICTIONARY: 265>, <TokenType.SEQUENCE: 395>, <TokenType.VIEW: 429>, <TokenType.MODEL: 334>, <TokenType.SEMANTIC_VIEW: 430>, <TokenType.DATABASE: 80>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.TAG: 411>, <TokenType.STAGE: 86>, <TokenType.FILE_FORMAT: 279>, <TokenType.STORAGE_INTEGRATION: 406>, <TokenType.WAREHOUSE: 85>, <TokenType.SINK: 442>, <TokenType.SOURCE: 443>, <TokenType.NAMESPACE: 445>}
CREATABLES: ClassVar = {<TokenType.DICTIONARY: 265>, <TokenType.SEQUENCE: 395>, <TokenType.MODEL: 334>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.FILE_FORMAT: 279>, <TokenType.STREAMLIT: 88>, <TokenType.STORAGE_INTEGRATION: 406>, <TokenType.TAG: 411>, <TokenType.FOREIGN_KEY: 285>, <TokenType.FUNCTION: 289>, <TokenType.TRIGGER: 417>, <TokenType.TYPE: 418>, <TokenType.INDEX: 301>, <TokenType.VIEW: 429>, <TokenType.SEMANTIC_VIEW: 430>, <TokenType.PROCEDURE: 368>, <TokenType.CONSTRAINT: 244>, <TokenType.SINK: 442>, <TokenType.SOURCE: 443>, <TokenType.NAMESPACE: 445>}
TRIGGER_EVENTS: ClassVar = {<TokenType.INSERT: 304>, <TokenType.DELETE: 260>, <TokenType.UPDATE: 424>, <TokenType.TRUNCATE: 416>}
ALTERABLES: ClassVar = {<TokenType.SESSION: 59>, <TokenType.TABLE: 84>, <TokenType.INDEX: 301>, <TokenType.VIEW: 429>}
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.REF: 201>, <TokenType.INET: 202>, <TokenType.IPADDRESS: 203>, <TokenType.IPPREFIX: 204>, <TokenType.IPV4: 205>, <TokenType.IPV6: 206>, <TokenType.ENUM: 207>, <TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.FIXEDSTRING: 210>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>, <TokenType.TDIGEST: 215>, <TokenType.UNKNOWN: 216>, <TokenType.VECTOR: 217>, <TokenType.DYNAMIC: 218>, <TokenType.VOID: 219>, <TokenType.ALL: 222>, <TokenType.ANTI: 223>, <TokenType.ANY: 224>, <TokenType.APPLY: 225>, <TokenType.ARRAY: 226>, <TokenType.ASC: 227>, <TokenType.ASOF: 228>, <TokenType.ATTACH: 229>, <TokenType.AUTO_INCREMENT: 230>, <TokenType.BEGIN: 231>, <TokenType.CACHE: 234>, <TokenType.CASE: 235>, <TokenType.COLLATE: 238>, <TokenType.COMMAND: 239>, <TokenType.COMMENT: 240>, <TokenType.COMMIT: 241>, <TokenType.CONNECT: 242>, <TokenType.CONSTRAINT: 244>, <TokenType.COPY: 245>, <TokenType.CUBE: 248>, <TokenType.CURRENT_DATE: 249>, <TokenType.CURRENT_DATETIME: 250>, <TokenType.CURRENT_SCHEMA: 251>, <TokenType.CURRENT_TIME: 252>, <TokenType.CURRENT_TIMESTAMP: 253>, <TokenType.CURRENT_USER: 254>, <TokenType.CURRENT_ROLE: 256>, <TokenType.CURRENT_CATALOG: 257>, <TokenType.DECLARE: 258>, <TokenType.DEFAULT: 259>, <TokenType.DELETE: 260>, <TokenType.DESC: 261>, <TokenType.DESCRIBE: 262>, <TokenType.DETACH: 263>, <TokenType.DISCONNECT: 264>, <TokenType.DICTIONARY: 265>, <TokenType.DIV: 268>, <TokenType.END: 271>, <TokenType.ESCAPE: 272>, <TokenType.EXECUTE: 274>, <TokenType.EXISTS: 275>, <TokenType.FALSE: 276>, <TokenType.FILE: 278>, <TokenType.FILE_FORMAT: 279>, <TokenType.FILTER: 280>, <TokenType.FINAL: 281>, <TokenType.FIRST: 282>, <TokenType.FOREIGN_KEY: 285>, <TokenType.FORMAT: 286>, <TokenType.FULL: 288>, <TokenType.FUNCTION: 289>, <TokenType.GET: 290>, <TokenType.IN: 300>, <TokenType.INDEX: 301>, <TokenType.INTERVAL: 308>, <TokenType.IS: 312>, <TokenType.ISNULL: 313>, <TokenType.KEEP: 316>, <TokenType.KILL: 318>, <TokenType.LEFT: 321>, <TokenType.LIMIT: 323>, <TokenType.LIST: 324>, <TokenType.LOAD: 325>, <TokenType.LOCK: 326>, <TokenType.MAP: 327>, <TokenType.MATCH: 328>, <TokenType.MERGE: 332>, <TokenType.MODEL: 334>, <TokenType.NATURAL: 335>, <TokenType.NEXT: 336>, <TokenType.NOTHING: 337>, <TokenType.NULL: 339>, <TokenType.OBJECT_IDENTIFIER: 340>, <TokenType.OFFSET: 341>, <TokenType.OPERATOR: 344>, <TokenType.ORDINALITY: 348>, <TokenType.OUT: 349>, <TokenType.INOUT: 350>, <TokenType.OVER: 352>, <TokenType.OVERLAPS: 353>, <TokenType.OVERWRITE: 354>, <TokenType.PARTITION: 356>, <TokenType.PASTE: 358>, <TokenType.PERCENT: 359>, <TokenType.PIVOT: 360>, <TokenType.PRAGMA: 365>, <TokenType.PROCEDURE: 368>, <TokenType.PROJECTION: 370>, <TokenType.PSEUDO_TYPE: 371>, <TokenType.PUT: 372>, <TokenType.RANGE: 376>, <TokenType.RECURSIVE: 377>, <TokenType.REFRESH: 378>, <TokenType.RENAME: 379>, <TokenType.REPLACE: 380>, <TokenType.REFERENCES: 383>, <TokenType.RIGHT: 384>, <TokenType.ROLLUP: 388>, <TokenType.ROW: 389>, <TokenType.ROWS: 390>, <TokenType.SEMI: 393>, <TokenType.SEQUENCE: 395>, <TokenType.SET: 397>, <TokenType.SETTINGS: 398>, <TokenType.SHOW: 399>, <TokenType.SOME: 401>, <TokenType.STORAGE_INTEGRATION: 406>, <TokenType.STRAIGHT_JOIN: 407>, <TokenType.STRUCT: 408>, <TokenType.TAG: 411>, <TokenType.TEMPORARY: 412>, <TokenType.TOP: 413>, <TokenType.TRUE: 415>, <TokenType.TRUNCATE: 416>, <TokenType.TRIGGER: 417>, <TokenType.TYPE: 418>, <TokenType.UNNEST: 422>, <TokenType.UNPIVOT: 423>, <TokenType.UPDATE: 424>, <TokenType.USE: 425>, <TokenType.VIEW: 429>, <TokenType.SEMANTIC_VIEW: 430>, <TokenType.VOLATILE: 431>, <TokenType.WINDOW: 435>, <TokenType.UNIQUE: 437>, <TokenType.SINK: 442>, <TokenType.SOURCE: 443>, <TokenType.ANALYZE: 444>, <TokenType.NAMESPACE: 445>, <TokenType.EXPORT: 446>}
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.REF: 201>, <TokenType.INET: 202>, <TokenType.IPADDRESS: 203>, <TokenType.IPPREFIX: 204>, <TokenType.IPV4: 205>, <TokenType.IPV6: 206>, <TokenType.ENUM: 207>, <TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.FIXEDSTRING: 210>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>, <TokenType.TDIGEST: 215>, <TokenType.UNKNOWN: 216>, <TokenType.VECTOR: 217>, <TokenType.DYNAMIC: 218>, <TokenType.VOID: 219>, <TokenType.ALL: 222>, <TokenType.ANY: 224>, <TokenType.APPLY: 225>, <TokenType.ARRAY: 226>, <TokenType.ASC: 227>, <TokenType.ATTACH: 229>, <TokenType.AUTO_INCREMENT: 230>, <TokenType.BEGIN: 231>, <TokenType.CACHE: 234>, <TokenType.CASE: 235>, <TokenType.COLLATE: 238>, <TokenType.COMMAND: 239>, <TokenType.COMMENT: 240>, <TokenType.COMMIT: 241>, <TokenType.CONNECT: 242>, <TokenType.CONSTRAINT: 244>, <TokenType.COPY: 245>, <TokenType.CUBE: 248>, <TokenType.CURRENT_DATE: 249>, <TokenType.CURRENT_DATETIME: 250>, <TokenType.CURRENT_SCHEMA: 251>, <TokenType.CURRENT_TIME: 252>, <TokenType.CURRENT_TIMESTAMP: 253>, <TokenType.CURRENT_USER: 254>, <TokenType.CURRENT_ROLE: 256>, <TokenType.CURRENT_CATALOG: 257>, <TokenType.DECLARE: 258>, <TokenType.DEFAULT: 259>, <TokenType.DELETE: 260>, <TokenType.DESC: 261>, <TokenType.DESCRIBE: 262>, <TokenType.DETACH: 263>, <TokenType.DISCONNECT: 264>, <TokenType.DICTIONARY: 265>, <TokenType.DIV: 268>, <TokenType.END: 271>, <TokenType.ESCAPE: 272>, <TokenType.EXECUTE: 274>, <TokenType.EXISTS: 275>, <TokenType.FALSE: 276>, <TokenType.FILE: 278>, <TokenType.FILE_FORMAT: 279>, <TokenType.FILTER: 280>, <TokenType.FINAL: 281>, <TokenType.FIRST: 282>, <TokenType.FOREIGN_KEY: 285>, <TokenType.FORMAT: 286>, <TokenType.FUNCTION: 289>, <TokenType.GET: 290>, <TokenType.IN: 300>, <TokenType.INDEX: 301>, <TokenType.INTERVAL: 308>, <TokenType.IS: 312>, <TokenType.ISNULL: 313>, <TokenType.KEEP: 316>, <TokenType.KILL: 318>, <TokenType.LIMIT: 323>, <TokenType.LIST: 324>, <TokenType.LOAD: 325>, <TokenType.MAP: 327>, <TokenType.MATCH: 328>, <TokenType.MERGE: 332>, <TokenType.MODEL: 334>, <TokenType.NEXT: 336>, <TokenType.NOTHING: 337>, <TokenType.NULL: 339>, <TokenType.OBJECT_IDENTIFIER: 340>, <TokenType.OFFSET: 341>, <TokenType.OPERATOR: 344>, <TokenType.ORDINALITY: 348>, <TokenType.OUT: 349>, <TokenType.INOUT: 350>, <TokenType.OVER: 352>, <TokenType.OVERLAPS: 353>, <TokenType.OVERWRITE: 354>, <TokenType.PARTITION: 356>, <TokenType.PASTE: 358>, <TokenType.PERCENT: 359>, <TokenType.PIVOT: 360>, <TokenType.PRAGMA: 365>, <TokenType.PROCEDURE: 368>, <TokenType.PROJECTION: 370>, <TokenType.PSEUDO_TYPE: 371>, <TokenType.PUT: 372>, <TokenType.RANGE: 376>, <TokenType.RECURSIVE: 377>, <TokenType.REFRESH: 378>, <TokenType.RENAME: 379>, <TokenType.REPLACE: 380>, <TokenType.REFERENCES: 383>, <TokenType.ROLLUP: 388>, <TokenType.ROW: 389>, <TokenType.ROWS: 390>, <TokenType.SEQUENCE: 395>, <TokenType.SET: 397>, <TokenType.SETTINGS: 398>, <TokenType.SHOW: 399>, <TokenType.SOME: 401>, <TokenType.STORAGE_INTEGRATION: 406>, <TokenType.STRAIGHT_JOIN: 407>, <TokenType.STRUCT: 408>, <TokenType.TAG: 411>, <TokenType.TEMPORARY: 412>, <TokenType.TOP: 413>, <TokenType.TRUE: 415>, <TokenType.TRUNCATE: 416>, <TokenType.TRIGGER: 417>, <TokenType.TYPE: 418>, <TokenType.UNNEST: 422>, <TokenType.UNPIVOT: 423>, <TokenType.UPDATE: 424>, <TokenType.USE: 425>, <TokenType.VIEW: 429>, <TokenType.SEMANTIC_VIEW: 430>, <TokenType.VOLATILE: 431>, <TokenType.UNIQUE: 437>, <TokenType.SINK: 442>, <TokenType.SOURCE: 443>, <TokenType.ANALYZE: 444>, <TokenType.NAMESPACE: 445>, <TokenType.EXPORT: 446>}
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.REF: 201>, <TokenType.INET: 202>, <TokenType.IPADDRESS: 203>, <TokenType.IPPREFIX: 204>, <TokenType.IPV4: 205>, <TokenType.IPV6: 206>, <TokenType.ENUM: 207>, <TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.FIXEDSTRING: 210>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>, <TokenType.TDIGEST: 215>, <TokenType.UNKNOWN: 216>, <TokenType.VECTOR: 217>, <TokenType.DYNAMIC: 218>, <TokenType.VOID: 219>, <TokenType.ALL: 222>, <TokenType.ANTI: 223>, <TokenType.ANY: 224>, <TokenType.APPLY: 225>, <TokenType.ARRAY: 226>, <TokenType.ASC: 227>, <TokenType.ASOF: 228>, <TokenType.ATTACH: 229>, <TokenType.AUTO_INCREMENT: 230>, <TokenType.BEGIN: 231>, <TokenType.CACHE: 234>, <TokenType.CASE: 235>, <TokenType.COLLATE: 238>, <TokenType.COMMAND: 239>, <TokenType.COMMENT: 240>, <TokenType.COMMIT: 241>, <TokenType.CONNECT: 242>, <TokenType.CONSTRAINT: 244>, <TokenType.COPY: 245>, <TokenType.CUBE: 248>, <TokenType.CURRENT_DATE: 249>, <TokenType.CURRENT_DATETIME: 250>, <TokenType.CURRENT_SCHEMA: 251>, <TokenType.CURRENT_TIME: 252>, <TokenType.CURRENT_TIMESTAMP: 253>, <TokenType.CURRENT_USER: 254>, <TokenType.CURRENT_ROLE: 256>, <TokenType.CURRENT_CATALOG: 257>, <TokenType.DECLARE: 258>, <TokenType.DEFAULT: 259>, <TokenType.DELETE: 260>, <TokenType.DESC: 261>, <TokenType.DESCRIBE: 262>, <TokenType.DETACH: 263>, <TokenType.DISCONNECT: 264>, <TokenType.DICTIONARY: 265>, <TokenType.DIV: 268>, <TokenType.END: 271>, <TokenType.ESCAPE: 272>, <TokenType.EXECUTE: 274>, <TokenType.EXISTS: 275>, <TokenType.FALSE: 276>, <TokenType.FILE: 278>, <TokenType.FILE_FORMAT: 279>, <TokenType.FILTER: 280>, <TokenType.FINAL: 281>, <TokenType.FIRST: 282>, <TokenType.FOREIGN_KEY: 285>, <TokenType.FORMAT: 286>, <TokenType.FULL: 288>, <TokenType.FUNCTION: 289>, <TokenType.GET: 290>, <TokenType.IN: 300>, <TokenType.INDEX: 301>, <TokenType.INTERVAL: 308>, <TokenType.IS: 312>, <TokenType.ISNULL: 313>, <TokenType.KEEP: 316>, <TokenType.KILL: 318>, <TokenType.LEFT: 321>, <TokenType.LIMIT: 323>, <TokenType.LIST: 324>, <TokenType.LOAD: 325>, <TokenType.LOCK: 326>, <TokenType.MAP: 327>, <TokenType.MATCH: 328>, <TokenType.MERGE: 332>, <TokenType.MODEL: 334>, <TokenType.NATURAL: 335>, <TokenType.NEXT: 336>, <TokenType.NOTHING: 337>, <TokenType.NULL: 339>, <TokenType.OBJECT_IDENTIFIER: 340>, <TokenType.OFFSET: 341>, <TokenType.OPERATOR: 344>, <TokenType.ORDINALITY: 348>, <TokenType.OUT: 349>, <TokenType.INOUT: 350>, <TokenType.OVER: 352>, <TokenType.OVERLAPS: 353>, <TokenType.OVERWRITE: 354>, <TokenType.PARTITION: 356>, <TokenType.PASTE: 358>, <TokenType.PERCENT: 359>, <TokenType.PIVOT: 360>, <TokenType.PRAGMA: 365>, <TokenType.PROCEDURE: 368>, <TokenType.PROJECTION: 370>, <TokenType.PSEUDO_TYPE: 371>, <TokenType.PUT: 372>, <TokenType.RANGE: 376>, <TokenType.RECURSIVE: 377>, <TokenType.REFRESH: 378>, <TokenType.RENAME: 379>, <TokenType.REPLACE: 380>, <TokenType.REFERENCES: 383>, <TokenType.RIGHT: 384>, <TokenType.ROLLUP: 388>, <TokenType.ROW: 389>, <TokenType.ROWS: 390>, <TokenType.SEMI: 393>, <TokenType.SEQUENCE: 395>, <TokenType.SET: 397>, <TokenType.SETTINGS: 398>, <TokenType.SHOW: 399>, <TokenType.SOME: 401>, <TokenType.STORAGE_INTEGRATION: 406>, <TokenType.STRAIGHT_JOIN: 407>, <TokenType.STRUCT: 408>, <TokenType.TAG: 411>, <TokenType.TEMPORARY: 412>, <TokenType.TOP: 413>, <TokenType.TRUE: 415>, <TokenType.TRUNCATE: 416>, <TokenType.TRIGGER: 417>, <TokenType.TYPE: 418>, <TokenType.UNNEST: 422>, <TokenType.UNPIVOT: 423>, <TokenType.UPDATE: 424>, <TokenType.USE: 425>, <TokenType.VIEW: 429>, <TokenType.SEMANTIC_VIEW: 430>, <TokenType.VOLATILE: 431>, <TokenType.WINDOW: 435>, <TokenType.UNIQUE: 437>, <TokenType.SINK: 442>, <TokenType.SOURCE: 443>, <TokenType.ANALYZE: 444>, <TokenType.NAMESPACE: 445>, <TokenType.EXPORT: 446>}
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.REF: 201>, <TokenType.INET: 202>, <TokenType.IPADDRESS: 203>, <TokenType.IPPREFIX: 204>, <TokenType.IPV4: 205>, <TokenType.IPV6: 206>, <TokenType.ENUM: 207>, <TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.FIXEDSTRING: 210>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>, <TokenType.TDIGEST: 215>, <TokenType.UNKNOWN: 216>, <TokenType.VECTOR: 217>, <TokenType.DYNAMIC: 218>, <TokenType.VOID: 219>, <TokenType.ALL: 222>, <TokenType.ANTI: 223>, <TokenType.ANY: 224>, <TokenType.APPLY: 225>, <TokenType.ARRAY: 226>, <TokenType.ASC: 227>, <TokenType.ASOF: 228>, <TokenType.ATTACH: 229>, <TokenType.AUTO_INCREMENT: 230>, <TokenType.BEGIN: 231>, <TokenType.CACHE: 234>, <TokenType.CASE: 235>, <TokenType.COLLATE: 238>, <TokenType.COMMAND: 239>, <TokenType.COMMENT: 240>, <TokenType.COMMIT: 241>, <TokenType.CONNECT: 242>, <TokenType.CONSTRAINT: 244>, <TokenType.COPY: 245>, <TokenType.CUBE: 248>, <TokenType.CURRENT_DATE: 249>, <TokenType.CURRENT_DATETIME: 250>, <TokenType.CURRENT_SCHEMA: 251>, <TokenType.CURRENT_TIME: 252>, <TokenType.CURRENT_TIMESTAMP: 253>, <TokenType.CURRENT_USER: 254>, <TokenType.CURRENT_ROLE: 256>, <TokenType.CURRENT_CATALOG: 257>, <TokenType.DECLARE: 258>, <TokenType.DEFAULT: 259>, <TokenType.DELETE: 260>, <TokenType.DESC: 261>, <TokenType.DESCRIBE: 262>, <TokenType.DETACH: 263>, <TokenType.DISCONNECT: 264>, <TokenType.DICTIONARY: 265>, <TokenType.DIV: 268>, <TokenType.END: 271>, <TokenType.ESCAPE: 272>, <TokenType.EXECUTE: 274>, <TokenType.EXISTS: 275>, <TokenType.FALSE: 276>, <TokenType.FILE: 278>, <TokenType.FILE_FORMAT: 279>, <TokenType.FILTER: 280>, <TokenType.FINAL: 281>, <TokenType.FIRST: 282>, <TokenType.FOREIGN_KEY: 285>, <TokenType.FORMAT: 286>, <TokenType.FULL: 288>, <TokenType.FUNCTION: 289>, <TokenType.GET: 290>, <TokenType.IN: 300>, <TokenType.INDEX: 301>, <TokenType.INTERVAL: 308>, <TokenType.IS: 312>, <TokenType.ISNULL: 313>, <TokenType.KEEP: 316>, <TokenType.KILL: 318>, <TokenType.LEFT: 321>, <TokenType.LIMIT: 323>, <TokenType.LIST: 324>, <TokenType.LOAD: 325>, <TokenType.LOCK: 326>, <TokenType.MAP: 327>, <TokenType.MATCH: 328>, <TokenType.MERGE: 332>, <TokenType.MODEL: 334>, <TokenType.NATURAL: 335>, <TokenType.NEXT: 336>, <TokenType.NOTHING: 337>, <TokenType.NULL: 339>, <TokenType.OBJECT_IDENTIFIER: 340>, <TokenType.OFFSET: 341>, <TokenType.OPERATOR: 344>, <TokenType.ORDINALITY: 348>, <TokenType.OUT: 349>, <TokenType.INOUT: 350>, <TokenType.OVER: 352>, <TokenType.OVERLAPS: 353>, <TokenType.OVERWRITE: 354>, <TokenType.PARTITION: 356>, <TokenType.PASTE: 358>, <TokenType.PERCENT: 359>, <TokenType.PIVOT: 360>, <TokenType.PRAGMA: 365>, <TokenType.PROCEDURE: 368>, <TokenType.PROJECTION: 370>, <TokenType.PSEUDO_TYPE: 371>, <TokenType.PUT: 372>, <TokenType.RANGE: 376>, <TokenType.RECURSIVE: 377>, <TokenType.REFRESH: 378>, <TokenType.RENAME: 379>, <TokenType.REPLACE: 380>, <TokenType.REFERENCES: 383>, <TokenType.RIGHT: 384>, <TokenType.ROLLUP: 388>, <TokenType.ROW: 389>, <TokenType.ROWS: 390>, <TokenType.SEMI: 393>, <TokenType.SEQUENCE: 395>, <TokenType.SET: 397>, <TokenType.SETTINGS: 398>, <TokenType.SHOW: 399>, <TokenType.SOME: 401>, <TokenType.STORAGE_INTEGRATION: 406>, <TokenType.STRAIGHT_JOIN: 407>, <TokenType.STRUCT: 408>, <TokenType.TAG: 411>, <TokenType.TEMPORARY: 412>, <TokenType.TOP: 413>, <TokenType.TRUE: 415>, <TokenType.TRUNCATE: 416>, <TokenType.TRIGGER: 417>, <TokenType.TYPE: 418>, <TokenType.UNNEST: 422>, <TokenType.UNPIVOT: 423>, <TokenType.UPDATE: 424>, <TokenType.USE: 425>, <TokenType.VIEW: 429>, <TokenType.SEMANTIC_VIEW: 430>, <TokenType.VOLATILE: 431>, <TokenType.WINDOW: 435>, <TokenType.UNIQUE: 437>, <TokenType.SINK: 442>, <TokenType.SOURCE: 443>, <TokenType.ANALYZE: 444>, <TokenType.NAMESPACE: 445>, <TokenType.EXPORT: 446>}
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.REF: 201>, <TokenType.INET: 202>, <TokenType.IPADDRESS: 203>, <TokenType.IPPREFIX: 204>, <TokenType.IPV4: 205>, <TokenType.IPV6: 206>, <TokenType.ENUM: 207>, <TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.FIXEDSTRING: 210>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>, <TokenType.TDIGEST: 215>, <TokenType.UNKNOWN: 216>, <TokenType.VECTOR: 217>, <TokenType.DYNAMIC: 218>, <TokenType.VOID: 219>, <TokenType.ALL: 222>, <TokenType.ANY: 224>, <TokenType.APPLY: 225>, <TokenType.ARRAY: 226>, <TokenType.ASC: 227>, <TokenType.ATTACH: 229>, <TokenType.AUTO_INCREMENT: 230>, <TokenType.BEGIN: 231>, <TokenType.CACHE: 234>, <TokenType.CASE: 235>, <TokenType.COLLATE: 238>, <TokenType.COMMAND: 239>, <TokenType.COMMENT: 240>, <TokenType.COMMIT: 241>, <TokenType.CONNECT: 242>, <TokenType.CONSTRAINT: 244>, <TokenType.COPY: 245>, <TokenType.CUBE: 248>, <TokenType.CURRENT_DATE: 249>, <TokenType.CURRENT_DATETIME: 250>, <TokenType.CURRENT_SCHEMA: 251>, <TokenType.CURRENT_TIME: 252>, <TokenType.CURRENT_TIMESTAMP: 253>, <TokenType.CURRENT_USER: 254>, <TokenType.CURRENT_ROLE: 256>, <TokenType.CURRENT_CATALOG: 257>, <TokenType.DECLARE: 258>, <TokenType.DEFAULT: 259>, <TokenType.DELETE: 260>, <TokenType.DESC: 261>, <TokenType.DESCRIBE: 262>, <TokenType.DETACH: 263>, <TokenType.DISCONNECT: 264>, <TokenType.DICTIONARY: 265>, <TokenType.DIV: 268>, <TokenType.END: 271>, <TokenType.ESCAPE: 272>, <TokenType.EXECUTE: 274>, <TokenType.EXISTS: 275>, <TokenType.FALSE: 276>, <TokenType.FILE: 278>, <TokenType.FILE_FORMAT: 279>, <TokenType.FILTER: 280>, <TokenType.FINAL: 281>, <TokenType.FIRST: 282>, <TokenType.FOREIGN_KEY: 285>, <TokenType.FORMAT: 286>, <TokenType.FUNCTION: 289>, <TokenType.GET: 290>, <TokenType.IN: 300>, <TokenType.INDEX: 301>, <TokenType.INTERVAL: 308>, <TokenType.ISNULL: 313>, <TokenType.KEEP: 316>, <TokenType.KILL: 318>, <TokenType.LIMIT: 323>, <TokenType.LIST: 324>, <TokenType.LOAD: 325>, <TokenType.MAP: 327>, <TokenType.MATCH: 328>, <TokenType.MERGE: 332>, <TokenType.MODEL: 334>, <TokenType.NEXT: 336>, <TokenType.NOTHING: 337>, <TokenType.NULL: 339>, <TokenType.OBJECT_IDENTIFIER: 340>, <TokenType.OFFSET: 341>, <TokenType.OPERATOR: 344>, <TokenType.ORDINALITY: 348>, <TokenType.OUT: 349>, <TokenType.INOUT: 350>, <TokenType.OVER: 352>, <TokenType.OVERLAPS: 353>, <TokenType.OVERWRITE: 354>, <TokenType.PARTITION: 356>, <TokenType.PASTE: 358>, <TokenType.PERCENT: 359>, <TokenType.PIVOT: 360>, <TokenType.PRAGMA: 365>, <TokenType.PROCEDURE: 368>, <TokenType.PROJECTION: 370>, <TokenType.PSEUDO_TYPE: 371>, <TokenType.PUT: 372>, <TokenType.RANGE: 376>, <TokenType.RECURSIVE: 377>, <TokenType.REFRESH: 378>, <TokenType.RENAME: 379>, <TokenType.REPLACE: 380>, <TokenType.REFERENCES: 383>, <TokenType.ROLLUP: 388>, <TokenType.ROW: 389>, <TokenType.ROWS: 390>, <TokenType.SEQUENCE: 395>, <TokenType.SET: 397>, <TokenType.SETTINGS: 398>, <TokenType.SHOW: 399>, <TokenType.SOME: 401>, <TokenType.STORAGE_INTEGRATION: 406>, <TokenType.STRAIGHT_JOIN: 407>, <TokenType.STRUCT: 408>, <TokenType.TAG: 411>, <TokenType.TEMPORARY: 412>, <TokenType.TOP: 413>, <TokenType.TRUE: 415>, <TokenType.TRUNCATE: 416>, <TokenType.TRIGGER: 417>, <TokenType.TYPE: 418>, <TokenType.UNNEST: 422>, <TokenType.UNPIVOT: 423>, <TokenType.UPDATE: 424>, <TokenType.USE: 425>, <TokenType.VIEW: 429>, <TokenType.SEMANTIC_VIEW: 430>, <TokenType.VOLATILE: 431>, <TokenType.UNIQUE: 437>, <TokenType.SINK: 442>, <TokenType.SOURCE: 443>, <TokenType.ANALYZE: 444>, <TokenType.NAMESPACE: 445>, <TokenType.EXPORT: 446>}
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.REF: 201>, <TokenType.INET: 202>, <TokenType.IPADDRESS: 203>, <TokenType.IPPREFIX: 204>, <TokenType.IPV4: 205>, <TokenType.IPV6: 206>, <TokenType.ENUM: 207>, <TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.FIXEDSTRING: 210>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>, <TokenType.TDIGEST: 215>, <TokenType.UNKNOWN: 216>, <TokenType.VECTOR: 217>, <TokenType.DYNAMIC: 218>, <TokenType.VOID: 219>, <TokenType.ALL: 222>, <TokenType.ANY: 224>, <TokenType.APPLY: 225>, <TokenType.ARRAY: 226>, <TokenType.ASC: 227>, <TokenType.ATTACH: 229>, <TokenType.AUTO_INCREMENT: 230>, <TokenType.BEGIN: 231>, <TokenType.CACHE: 234>, <TokenType.CASE: 235>, <TokenType.COLLATE: 238>, <TokenType.COMMAND: 239>, <TokenType.COMMENT: 240>, <TokenType.COMMIT: 241>, <TokenType.CONNECT: 242>, <TokenType.CONSTRAINT: 244>, <TokenType.COPY: 245>, <TokenType.CUBE: 248>, <TokenType.CURRENT_DATE: 249>, <TokenType.CURRENT_DATETIME: 250>, <TokenType.CURRENT_SCHEMA: 251>, <TokenType.CURRENT_TIME: 252>, <TokenType.CURRENT_TIMESTAMP: 253>, <TokenType.CURRENT_USER: 254>, <TokenType.CURRENT_ROLE: 256>, <TokenType.CURRENT_CATALOG: 257>, <TokenType.DECLARE: 258>, <TokenType.DEFAULT: 259>, <TokenType.DELETE: 260>, <TokenType.DESC: 261>, <TokenType.DESCRIBE: 262>, <TokenType.DETACH: 263>, <TokenType.DISCONNECT: 264>, <TokenType.DICTIONARY: 265>, <TokenType.DIV: 268>, <TokenType.END: 271>, <TokenType.ESCAPE: 272>, <TokenType.EXECUTE: 274>, <TokenType.EXISTS: 275>, <TokenType.FALSE: 276>, <TokenType.FILE: 278>, <TokenType.FILE_FORMAT: 279>, <TokenType.FILTER: 280>, <TokenType.FINAL: 281>, <TokenType.FIRST: 282>, <TokenType.FOREIGN_KEY: 285>, <TokenType.FORMAT: 286>, <TokenType.FUNCTION: 289>, <TokenType.GET: 290>, <TokenType.IN: 300>, <TokenType.INDEX: 301>, <TokenType.INTERVAL: 308>, <TokenType.IS: 312>, <TokenType.ISNULL: 313>, <TokenType.KEEP: 316>, <TokenType.KILL: 318>, <TokenType.LIMIT: 323>, <TokenType.LIST: 324>, <TokenType.LOAD: 325>, <TokenType.MAP: 327>, <TokenType.MATCH: 328>, <TokenType.MERGE: 332>, <TokenType.MODEL: 334>, <TokenType.NEXT: 336>, <TokenType.NOTHING: 337>, <TokenType.NULL: 339>, <TokenType.OBJECT_IDENTIFIER: 340>, <TokenType.OFFSET: 341>, <TokenType.OPERATOR: 344>, <TokenType.ORDINALITY: 348>, <TokenType.OUT: 349>, <TokenType.INOUT: 350>, <TokenType.OVER: 352>, <TokenType.OVERLAPS: 353>, <TokenType.OVERWRITE: 354>, <TokenType.PARTITION: 356>, <TokenType.PASTE: 358>, <TokenType.PERCENT: 359>, <TokenType.PIVOT: 360>, <TokenType.PRAGMA: 365>, <TokenType.PROCEDURE: 368>, <TokenType.PROJECTION: 370>, <TokenType.PSEUDO_TYPE: 371>, <TokenType.PUT: 372>, <TokenType.RANGE: 376>, <TokenType.RECURSIVE: 377>, <TokenType.REFRESH: 378>, <TokenType.RENAME: 379>, <TokenType.REPLACE: 380>, <TokenType.REFERENCES: 383>, <TokenType.ROLLUP: 388>, <TokenType.ROW: 389>, <TokenType.ROWS: 390>, <TokenType.SEQUENCE: 395>, <TokenType.SETTINGS: 398>, <TokenType.SHOW: 399>, <TokenType.SOME: 401>, <TokenType.STORAGE_INTEGRATION: 406>, <TokenType.STRAIGHT_JOIN: 407>, <TokenType.STRUCT: 408>, <TokenType.TAG: 411>, <TokenType.TEMPORARY: 412>, <TokenType.TOP: 413>, <TokenType.TRUE: 415>, <TokenType.TRUNCATE: 416>, <TokenType.TRIGGER: 417>, <TokenType.TYPE: 418>, <TokenType.UNNEST: 422>, <TokenType.UNPIVOT: 423>, <TokenType.UPDATE: 424>, <TokenType.USE: 425>, <TokenType.VIEW: 429>, <TokenType.SEMANTIC_VIEW: 430>, <TokenType.VOLATILE: 431>, <TokenType.UNIQUE: 437>, <TokenType.SINK: 442>, <TokenType.SOURCE: 443>, <TokenType.ANALYZE: 444>, <TokenType.NAMESPACE: 445>, <TokenType.EXPORT: 446>}
TRIM_TYPES: ClassVar = {'LEADING', 'TRAILING', 'BOTH'}
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.JOIN_MARKER: 315>, <TokenType.L_BRACKET: 3>, <TokenType.L_BRACE: 5>, <TokenType.COLON: 11>})
TABLE_POSTFIX_TOKENS: ClassVar[frozenset] = frozenset({<TokenType.L_PAREN: 1>, <TokenType.L_BRACKET: 3>, <TokenType.L_BRACE: 5>, <TokenType.UNPIVOT: 423>, <TokenType.PIVOT: 360>, <TokenType.TABLE_SAMPLE: 410>})
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.REF: 201>, <TokenType.INET: 202>, <TokenType.IPADDRESS: 203>, <TokenType.IPPREFIX: 204>, <TokenType.IPV4: 205>, <TokenType.IPV6: 206>, <TokenType.ENUM: 207>, <TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.FIXEDSTRING: 210>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>, <TokenType.TDIGEST: 215>, <TokenType.UNKNOWN: 216>, <TokenType.VECTOR: 217>, <TokenType.DYNAMIC: 218>, <TokenType.VOID: 219>, <TokenType.ALL: 222>, <TokenType.ANY: 224>, <TokenType.ARRAY: 226>, <TokenType.COLLATE: 238>, <TokenType.COMMAND: 239>, <TokenType.CURRENT_DATE: 249>, <TokenType.CURRENT_DATETIME: 250>, <TokenType.CURRENT_SCHEMA: 251>, <TokenType.CURRENT_TIME: 252>, <TokenType.CURRENT_TIMESTAMP: 253>, <TokenType.CURRENT_USER: 254>, <TokenType.CURRENT_CATALOG: 257>, <TokenType.DECLARE: 258>, <TokenType.EXISTS: 275>, <TokenType.FILTER: 280>, <TokenType.FIRST: 282>, <TokenType.FORMAT: 286>, <TokenType.GET: 290>, <TokenType.GLOB: 291>, <TokenType.ILIKE: 299>, <TokenType.INDEX: 301>, <TokenType.INSERT: 304>, <TokenType.INTERVAL: 308>, <TokenType.ISNULL: 313>, <TokenType.LEFT: 321>, <TokenType.LIKE: 322>, <TokenType.LIST: 324>, <TokenType.MAP: 327>, <TokenType.MERGE: 332>, <TokenType.NEXT: 336>, <TokenType.NOTHING: 337>, <TokenType.NULL: 339>, <TokenType.OBJECT_IDENTIFIER: 340>, <TokenType.OFFSET: 341>, <TokenType.PRIMARY_KEY: 367>, <TokenType.PSEUDO_TYPE: 371>, <TokenType.RANGE: 376>, <TokenType.REPLACE: 380>, <TokenType.RIGHT: 384>, <TokenType.RLIKE: 385>, <TokenType.ROW: 389>, <TokenType.SEQUENCE: 395>, <TokenType.SOME: 401>, <TokenType.STRUCT: 408>, <TokenType.TRUNCATE: 416>, <TokenType.UNION: 421>, <TokenType.UNNEST: 422>, <TokenType.WINDOW: 435>, <TokenType.UTC_DATE: 438>, <TokenType.UTC_TIME: 439>, <TokenType.UTC_TIMESTAMP: 440>}
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: 238>: <class 'sqlglot.expressions.functions.Collate'>}
FACTOR: ClassVar = {<TokenType.DIV: 268>: <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: 333>: <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.EXCEPT: 273>, <TokenType.INTERSECT: 307>, <TokenType.UNION: 421>}
JOIN_METHODS: ClassVar = {<TokenType.POSITIONAL: 364>, <TokenType.ASOF: 228>, <TokenType.NATURAL: 335>}
JOIN_SIDES: ClassVar = {<TokenType.RIGHT: 384>, <TokenType.LEFT: 321>, <TokenType.FULL: 288>}
JOIN_KINDS: ClassVar = {<TokenType.STRAIGHT_JOIN: 407>, <TokenType.ANTI: 223>, <TokenType.SEMI: 393>, <TokenType.INNER: 303>, <TokenType.CROSS: 247>, <TokenType.OUTER: 351>}
JOIN_HINTS: ClassVar[set[str]] = set()
SUPPORTS_DIRECTED_JOIN = True
TABLE_TERMINATORS: ClassVar[frozenset] = frozenset({<TokenType.SENTINEL: 448>, <TokenType.RIGHT: 384>, <TokenType.R_PAREN: 2>, <TokenType.LIMIT: 323>, <TokenType.LEFT: 321>, <TokenType.COMMA: 7>, <TokenType.SEMI: 393>, <TokenType.NATURAL: 335>, <TokenType.EXCEPT: 273>, <TokenType.SEMICOLON: 19>, <TokenType.ON: 342>, <TokenType.STRAIGHT_JOIN: 407>, <TokenType.ORDER_BY: 345>, <TokenType.ANTI: 223>, <TokenType.OUTER: 351>, <TokenType.FULL: 288>, <TokenType.ASOF: 228>, <TokenType.UNION: 421>, <TokenType.GROUP_BY: 294>, <TokenType.HAVING: 296>, <TokenType.POSITIONAL: 364>, <TokenType.INNER: 303>, <TokenType.WHERE: 434>, <TokenType.INTERSECT: 307>, <TokenType.CROSS: 247>, <TokenType.JOIN: 314>})
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: 361>: <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: 221>: <function Parser.<lambda>>, <TokenType.ANALYZE: 444>: <function Parser.<lambda>>, <TokenType.BEGIN: 231>: <function Parser.<lambda>>, <TokenType.CACHE: 234>: <function Parser.<lambda>>, <TokenType.COMMENT: 240>: <function Parser.<lambda>>, <TokenType.COMMIT: 241>: <function Parser.<lambda>>, <TokenType.COPY: 245>: <function Parser.<lambda>>, <TokenType.CREATE: 246>: <function Parser.<lambda>>, <TokenType.DECLARE: 258>: <function Parser.<lambda>>, <TokenType.DELETE: 260>: <function Parser.<lambda>>, <TokenType.DESC: 261>: <function Parser.<lambda>>, <TokenType.DESCRIBE: 262>: <function Parser.<lambda>>, <TokenType.DROP: 269>: <function Parser.<lambda>>, <TokenType.GRANT: 293>: <function Parser.<lambda>>, <TokenType.REVOKE: 382>: <function Parser.<lambda>>, <TokenType.INSERT: 304>: <function Parser.<lambda>>, <TokenType.KILL: 318>: <function Parser.<lambda>>, <TokenType.LOAD: 325>: <function Parser.<lambda>>, <TokenType.MERGE: 332>: <function Parser.<lambda>>, <TokenType.PIVOT: 360>: <function Parser.<lambda>>, <TokenType.PRAGMA: 365>: <function Parser.<lambda>>, <TokenType.REFRESH: 378>: <function Parser.<lambda>>, <TokenType.ROLLBACK: 387>: <function Parser.<lambda>>, <TokenType.SET: 397>: <function Parser.<lambda>>, <TokenType.TRUNCATE: 416>: <function Parser.<lambda>>, <TokenType.UNCACHE: 419>: <function Parser.<lambda>>, <TokenType.UNPIVOT: 423>: <function Parser.<lambda>>, <TokenType.UPDATE: 424>: <function Parser.<lambda>>, <TokenType.USE: 425>: <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: 310>: <function Parser.<lambda>>, <TokenType.NULL: 339>: <function Parser.<lambda>>, <TokenType.TRUE: 415>: <function Parser.<lambda>>, <TokenType.FALSE: 276>: <function Parser.<lambda>>, <TokenType.SESSION_PARAMETER: 60>: <function Parser.<lambda>>, <TokenType.STAR: 20>: <function Parser.<lambda>>}
PLACEHOLDER_PARSERS: ClassVar = {<TokenType.PLACEHOLDER: 361>: <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: 232>: <function Parser.<lambda>>, <TokenType.GLOB: 291>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.ILIKE: 299>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.IN: 300>: <function Parser.<lambda>>, <TokenType.IRLIKE: 311>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.IS: 312>: <function Parser.<lambda>>, <TokenType.LIKE: 322>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.LT_AT: 55>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.OVERLAPS: 353>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.RLIKE: 385>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.SIMILAR_TO: 400>: <function binary_range_parser.<locals>._parse_binary_range>, <TokenType.FOR: 283>: <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: 344>: <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', 'PERIOD', 'FOREIGN KEY', 'BUCKET', 'LIKE', 'TRUNCATE', 'CHECK', 'PRIMARY KEY', 'EXCLUDE'}
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: 330>: <function Parser.<lambda>>, <TokenType.PREWHERE: 366>: <function Parser.<lambda>>, <TokenType.WHERE: 434>: <function Parser.<lambda>>, <TokenType.GROUP_BY: 294>: <function Parser.<lambda>>, <TokenType.HAVING: 296>: <function Parser.<lambda>>, <TokenType.QUALIFY: 373>: <function Parser.<lambda>>, <TokenType.WINDOW: 435>: <function Parser.<lambda>>, <TokenType.ORDER_BY: 345>: <function Parser.<lambda>>, <TokenType.LIMIT: 323>: <function Parser.<lambda>>, <TokenType.FETCH: 277>: <function Parser.<lambda>>, <TokenType.OFFSET: 341>: <function Parser.<lambda>>, <TokenType.FOR: 283>: <function Parser.<lambda>>, <TokenType.LOCK: 326>: <function Parser.<lambda>>, <TokenType.TABLE_SAMPLE: 410>: <function Parser.<lambda>>, <TokenType.USING: 426>: <function Parser.<lambda>>, <TokenType.CLUSTER_BY: 237>: <function Parser.<lambda>>, <TokenType.DISTRIBUTE_BY: 267>: <function Parser.<lambda>>, <TokenType.SORT_BY: 402>: <function Parser.<lambda>>, <TokenType.CONNECT_BY: 243>: <function Parser.<lambda>>}
QUERY_MODIFIER_TOKENS: ClassVar = {<TokenType.LIMIT: 323>, <TokenType.LOCK: 326>, <TokenType.MATCH_RECOGNIZE: 330>, <TokenType.DISTRIBUTE_BY: 267>, <TokenType.SORT_BY: 402>, <TokenType.FETCH: 277>, <TokenType.OFFSET: 341>, <TokenType.ORDER_BY: 345>, <TokenType.TABLE_SAMPLE: 410>, <TokenType.FOR: 283>, <TokenType.GROUP_BY: 294>, <TokenType.HAVING: 296>, <TokenType.USING: 426>, <TokenType.CLUSTER_BY: 237>, <TokenType.PREWHERE: 366>, <TokenType.WHERE: 434>, <TokenType.WINDOW: 435>, <TokenType.CONNECT_BY: 243>, <TokenType.QUALIFY: 373>}
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.SELECT: 392>, <TokenType.L_PAREN: 1>, <TokenType.WITH: 436>}
PRE_VOLATILE_TOKENS: ClassVar = {<TokenType.REPLACE: 380>, <TokenType.UNIQUE: 437>, <TokenType.CREATE: 246>}
TRANSACTION_KIND: ClassVar = {'DEFERRED', 'EXCLUSIVE', 'IMMEDIATE'}
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', 'ABORT', 'FAIL', 'REPLACE', 'IGNORE'}
CLONE_KEYWORDS: ClassVar = {'COPY', 'CLONE'}
VERSION_PHRASES: ClassVar[dict[tuple[str, ...], str]] = {('FOR', 'SYSTEM_TIME'): 'TIMESTAMP', ('FOR', 'SYSTEM', 'TIME'): 'TIMESTAMP', ('FOR', 'TIMESTAMP'): 'TIMESTAMP', ('FOR', 'VERSION'): 'VERSION', ('TIMESTAMP', 'AS', 'OF'): 'TIMESTAMP', ('VERSION', 'AS', 'OF'): 'VERSION'}
HISTORICAL_DATA_PREFIX: ClassVar = {'AT', 'END', 'BEFORE'}
HISTORICAL_DATA_KIND: ClassVar = {'OFFSET', 'TIMESTAMP', 'STREAM', 'VERSION', 'STATEMENT'}
OPCLASS_FOLLOW_KEYWORDS: ClassVar = {'ASC', 'NULLS', 'DESC', 'WITH'}
OPTYPE_FOLLOW_TOKENS: ClassVar = {<TokenType.R_PAREN: 2>, <TokenType.COMMA: 7>}
TABLE_INDEX_HINT_TOKENS: ClassVar = {<TokenType.USE: 425>, <TokenType.IGNORE: 298>, <TokenType.FORCE: 284>}
VIEW_ATTRIBUTES: ClassVar = {'VIEW_METADATA', 'ENCRYPTION', 'SCHEMABINDING'}
WINDOW_ALIAS_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 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.REF: 201>, <TokenType.INET: 202>, <TokenType.IPADDRESS: 203>, <TokenType.IPPREFIX: 204>, <TokenType.IPV4: 205>, <TokenType.IPV6: 206>, <TokenType.ENUM: 207>, <TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.FIXEDSTRING: 210>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>, <TokenType.TDIGEST: 215>, <TokenType.UNKNOWN: 216>, <TokenType.VECTOR: 217>, <TokenType.DYNAMIC: 218>, <TokenType.VOID: 219>, <TokenType.ALL: 222>, <TokenType.ANTI: 223>, <TokenType.ANY: 224>, <TokenType.APPLY: 225>, <TokenType.ARRAY: 226>, <TokenType.ASC: 227>, <TokenType.ASOF: 228>, <TokenType.ATTACH: 229>, <TokenType.AUTO_INCREMENT: 230>, <TokenType.BEGIN: 231>, <TokenType.CACHE: 234>, <TokenType.CASE: 235>, <TokenType.COLLATE: 238>, <TokenType.COMMAND: 239>, <TokenType.COMMENT: 240>, <TokenType.COMMIT: 241>, <TokenType.CONNECT: 242>, <TokenType.CONSTRAINT: 244>, <TokenType.COPY: 245>, <TokenType.CUBE: 248>, <TokenType.CURRENT_DATE: 249>, <TokenType.CURRENT_DATETIME: 250>, <TokenType.CURRENT_SCHEMA: 251>, <TokenType.CURRENT_TIME: 252>, <TokenType.CURRENT_TIMESTAMP: 253>, <TokenType.CURRENT_USER: 254>, <TokenType.CURRENT_ROLE: 256>, <TokenType.CURRENT_CATALOG: 257>, <TokenType.DECLARE: 258>, <TokenType.DEFAULT: 259>, <TokenType.DELETE: 260>, <TokenType.DESC: 261>, <TokenType.DESCRIBE: 262>, <TokenType.DETACH: 263>, <TokenType.DISCONNECT: 264>, <TokenType.DICTIONARY: 265>, <TokenType.DIV: 268>, <TokenType.END: 271>, <TokenType.ESCAPE: 272>, <TokenType.EXECUTE: 274>, <TokenType.EXISTS: 275>, <TokenType.FALSE: 276>, <TokenType.FILE: 278>, <TokenType.FILE_FORMAT: 279>, <TokenType.FILTER: 280>, <TokenType.FINAL: 281>, <TokenType.FIRST: 282>, <TokenType.FOREIGN_KEY: 285>, <TokenType.FORMAT: 286>, <TokenType.FULL: 288>, <TokenType.FUNCTION: 289>, <TokenType.GET: 290>, <TokenType.IN: 300>, <TokenType.INDEX: 301>, <TokenType.INTERVAL: 308>, <TokenType.IS: 312>, <TokenType.ISNULL: 313>, <TokenType.KEEP: 316>, <TokenType.KILL: 318>, <TokenType.LEFT: 321>, <TokenType.LIMIT: 323>, <TokenType.LIST: 324>, <TokenType.LOAD: 325>, <TokenType.LOCK: 326>, <TokenType.MAP: 327>, <TokenType.MATCH: 328>, <TokenType.MERGE: 332>, <TokenType.MODEL: 334>, <TokenType.NATURAL: 335>, <TokenType.NEXT: 336>, <TokenType.NOTHING: 337>, <TokenType.NULL: 339>, <TokenType.OBJECT_IDENTIFIER: 340>, <TokenType.OFFSET: 341>, <TokenType.OPERATOR: 344>, <TokenType.ORDINALITY: 348>, <TokenType.OUT: 349>, <TokenType.INOUT: 350>, <TokenType.OVER: 352>, <TokenType.OVERLAPS: 353>, <TokenType.OVERWRITE: 354>, <TokenType.PARTITION: 356>, <TokenType.PASTE: 358>, <TokenType.PERCENT: 359>, <TokenType.PIVOT: 360>, <TokenType.PRAGMA: 365>, <TokenType.PROCEDURE: 368>, <TokenType.PROJECTION: 370>, <TokenType.PSEUDO_TYPE: 371>, <TokenType.PUT: 372>, <TokenType.RECURSIVE: 377>, <TokenType.REFRESH: 378>, <TokenType.RENAME: 379>, <TokenType.REPLACE: 380>, <TokenType.REFERENCES: 383>, <TokenType.RIGHT: 384>, <TokenType.ROLLUP: 388>, <TokenType.ROW: 389>, <TokenType.SEMI: 393>, <TokenType.SEQUENCE: 395>, <TokenType.SET: 397>, <TokenType.SETTINGS: 398>, <TokenType.SHOW: 399>, <TokenType.SOME: 401>, <TokenType.STORAGE_INTEGRATION: 406>, <TokenType.STRAIGHT_JOIN: 407>, <TokenType.STRUCT: 408>, <TokenType.TAG: 411>, <TokenType.TEMPORARY: 412>, <TokenType.TOP: 413>, <TokenType.TRUE: 415>, <TokenType.TRUNCATE: 416>, <TokenType.TRIGGER: 417>, <TokenType.TYPE: 418>, <TokenType.UNNEST: 422>, <TokenType.UNPIVOT: 423>, <TokenType.UPDATE: 424>, <TokenType.USE: 425>, <TokenType.VIEW: 429>, <TokenType.SEMANTIC_VIEW: 430>, <TokenType.VOLATILE: 431>, <TokenType.WINDOW: 435>, <TokenType.UNIQUE: 437>, <TokenType.SINK: 442>, <TokenType.SOURCE: 443>, <TokenType.ANALYZE: 444>, <TokenType.NAMESPACE: 445>, <TokenType.EXPORT: 446>}
WINDOW_BEFORE_PAREN_TOKENS: ClassVar = {<TokenType.OVER: 352>}
WINDOW_SIDES: ClassVar = {'PRECEDING', 'FOLLOWING'}
JSON_KEY_VALUE_SEPARATOR_TOKENS: ClassVar = {<TokenType.IS: 312>, <TokenType.COLON: 11>, <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.REF: 201>, <TokenType.INET: 202>, <TokenType.IPADDRESS: 203>, <TokenType.IPPREFIX: 204>, <TokenType.IPV4: 205>, <TokenType.IPV6: 206>, <TokenType.ENUM: 207>, <TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.FIXEDSTRING: 210>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>, <TokenType.TDIGEST: 215>, <TokenType.UNKNOWN: 216>, <TokenType.VECTOR: 217>, <TokenType.DYNAMIC: 218>, <TokenType.VOID: 219>, <TokenType.ALL: 222>, <TokenType.ANTI: 223>, <TokenType.ANY: 224>, <TokenType.APPLY: 225>, <TokenType.ARRAY: 226>, <TokenType.ASC: 227>, <TokenType.ASOF: 228>, <TokenType.ATTACH: 229>, <TokenType.AUTO_INCREMENT: 230>, <TokenType.BEGIN: 231>, <TokenType.CACHE: 234>, <TokenType.CASE: 235>, <TokenType.COLLATE: 238>, <TokenType.COMMAND: 239>, <TokenType.COMMENT: 240>, <TokenType.COMMIT: 241>, <TokenType.CONNECT: 242>, <TokenType.CONSTRAINT: 244>, <TokenType.COPY: 245>, <TokenType.CUBE: 248>, <TokenType.CURRENT_DATE: 249>, <TokenType.CURRENT_DATETIME: 250>, <TokenType.CURRENT_SCHEMA: 251>, <TokenType.CURRENT_TIME: 252>, <TokenType.CURRENT_TIMESTAMP: 253>, <TokenType.CURRENT_USER: 254>, <TokenType.CURRENT_ROLE: 256>, <TokenType.CURRENT_CATALOG: 257>, <TokenType.DECLARE: 258>, <TokenType.DEFAULT: 259>, <TokenType.DELETE: 260>, <TokenType.DESC: 261>, <TokenType.DESCRIBE: 262>, <TokenType.DETACH: 263>, <TokenType.DISCONNECT: 264>, <TokenType.DICTIONARY: 265>, <TokenType.DIV: 268>, <TokenType.END: 271>, <TokenType.ESCAPE: 272>, <TokenType.EXECUTE: 274>, <TokenType.EXISTS: 275>, <TokenType.FALSE: 276>, <TokenType.FILE: 278>, <TokenType.FILE_FORMAT: 279>, <TokenType.FILTER: 280>, <TokenType.FINAL: 281>, <TokenType.FIRST: 282>, <TokenType.FOREIGN_KEY: 285>, <TokenType.FORMAT: 286>, <TokenType.FULL: 288>, <TokenType.FUNCTION: 289>, <TokenType.GET: 290>, <TokenType.IN: 300>, <TokenType.INDEX: 301>, <TokenType.INTERVAL: 308>, <TokenType.IS: 312>, <TokenType.ISNULL: 313>, <TokenType.KEEP: 316>, <TokenType.KILL: 318>, <TokenType.LEFT: 321>, <TokenType.LIMIT: 323>, <TokenType.LIST: 324>, <TokenType.LOAD: 325>, <TokenType.LOCK: 326>, <TokenType.MAP: 327>, <TokenType.MATCH: 328>, <TokenType.MERGE: 332>, <TokenType.MODEL: 334>, <TokenType.NATURAL: 335>, <TokenType.NEXT: 336>, <TokenType.NOTHING: 337>, <TokenType.NULL: 339>, <TokenType.OBJECT_IDENTIFIER: 340>, <TokenType.OFFSET: 341>, <TokenType.OPERATOR: 344>, <TokenType.ORDINALITY: 348>, <TokenType.OUT: 349>, <TokenType.INOUT: 350>, <TokenType.OVER: 352>, <TokenType.OVERLAPS: 353>, <TokenType.OVERWRITE: 354>, <TokenType.PARTITION: 356>, <TokenType.PASTE: 358>, <TokenType.PIVOT: 360>, <TokenType.PRAGMA: 365>, <TokenType.PROCEDURE: 368>, <TokenType.PROJECTION: 370>, <TokenType.PSEUDO_TYPE: 371>, <TokenType.PUT: 372>, <TokenType.RANGE: 376>, <TokenType.RECURSIVE: 377>, <TokenType.REFRESH: 378>, <TokenType.RENAME: 379>, <TokenType.REPLACE: 380>, <TokenType.REFERENCES: 383>, <TokenType.RIGHT: 384>, <TokenType.ROLLUP: 388>, <TokenType.SEMI: 393>, <TokenType.SEQUENCE: 395>, <TokenType.SET: 397>, <TokenType.SETTINGS: 398>, <TokenType.SHOW: 399>, <TokenType.SOME: 401>, <TokenType.STORAGE_INTEGRATION: 406>, <TokenType.STRAIGHT_JOIN: 407>, <TokenType.STRUCT: 408>, <TokenType.TAG: 411>, <TokenType.TEMPORARY: 412>, <TokenType.TOP: 413>, <TokenType.TRUE: 415>, <TokenType.TRUNCATE: 416>, <TokenType.TRIGGER: 417>, <TokenType.TYPE: 418>, <TokenType.UNNEST: 422>, <TokenType.UNPIVOT: 423>, <TokenType.UPDATE: 424>, <TokenType.USE: 425>, <TokenType.VIEW: 429>, <TokenType.SEMANTIC_VIEW: 430>, <TokenType.VOLATILE: 431>, <TokenType.WINDOW: 435>, <TokenType.UNIQUE: 437>, <TokenType.SINK: 442>, <TokenType.SOURCE: 443>, <TokenType.ANALYZE: 444>, <TokenType.NAMESPACE: 445>, <TokenType.EXPORT: 446>}
ADD_CONSTRAINT_TOKENS: ClassVar = {<TokenType.INDEX: 301>, <TokenType.PRIMARY_KEY: 367>, <TokenType.CONSTRAINT: 244>, <TokenType.UNIQUE: 437>, <TokenType.FOREIGN_KEY: 285>, <TokenType.KEY: 317>}
ADD_CONSTRAINT_KEYWORDS: ClassVar[set[str]] = set()
DISTINCT_TOKENS: ClassVar = {<TokenType.DISTINCT: 266>}
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.REF: 201>, <TokenType.INET: 202>, <TokenType.IPADDRESS: 203>, <TokenType.IPPREFIX: 204>, <TokenType.IPV4: 205>, <TokenType.IPV6: 206>, <TokenType.ENUM: 207>, <TokenType.ENUM8: 208>, <TokenType.ENUM16: 209>, <TokenType.FIXEDSTRING: 210>, <TokenType.LOWCARDINALITY: 211>, <TokenType.NESTED: 212>, <TokenType.AGGREGATEFUNCTION: 213>, <TokenType.SIMPLEAGGREGATEFUNCTION: 214>, <TokenType.TDIGEST: 215>, <TokenType.UNKNOWN: 216>, <TokenType.VECTOR: 217>, <TokenType.DYNAMIC: 218>, <TokenType.VOID: 219>, <TokenType.ALL: 222>, <TokenType.ANY: 224>, <TokenType.APPLY: 225>, <TokenType.ARRAY: 226>, <TokenType.ASC: 227>, <TokenType.ATTACH: 229>, <TokenType.AUTO_INCREMENT: 230>, <TokenType.BEGIN: 231>, <TokenType.CACHE: 234>, <TokenType.CASE: 235>, <TokenType.COLLATE: 238>, <TokenType.COMMAND: 239>, <TokenType.COMMENT: 240>, <TokenType.COMMIT: 241>, <TokenType.CONNECT: 242>, <TokenType.CONSTRAINT: 244>, <TokenType.COPY: 245>, <TokenType.CUBE: 248>, <TokenType.CURRENT_DATE: 249>, <TokenType.CURRENT_DATETIME: 250>, <TokenType.CURRENT_SCHEMA: 251>, <TokenType.CURRENT_TIME: 252>, <TokenType.CURRENT_TIMESTAMP: 253>, <TokenType.CURRENT_USER: 254>, <TokenType.CURRENT_ROLE: 256>, <TokenType.CURRENT_CATALOG: 257>, <TokenType.DECLARE: 258>, <TokenType.DEFAULT: 259>, <TokenType.DELETE: 260>, <TokenType.DESC: 261>, <TokenType.DESCRIBE: 262>, <TokenType.DETACH: 263>, <TokenType.DISCONNECT: 264>, <TokenType.DICTIONARY: 265>, <TokenType.DIV: 268>, <TokenType.END: 271>, <TokenType.ESCAPE: 272>, <TokenType.EXECUTE: 274>, <TokenType.EXISTS: 275>, <TokenType.FALSE: 276>, <TokenType.FILE: 278>, <TokenType.FILE_FORMAT: 279>, <TokenType.FILTER: 280>, <TokenType.FINAL: 281>, <TokenType.FIRST: 282>, <TokenType.FOREIGN_KEY: 285>, <TokenType.FORMAT: 286>, <TokenType.FUNCTION: 289>, <TokenType.GET: 290>, <TokenType.IN: 300>, <TokenType.INDEX: 301>, <TokenType.INTERVAL: 308>, <TokenType.IS: 312>, <TokenType.ISNULL: 313>, <TokenType.KEEP: 316>, <TokenType.KILL: 318>, <TokenType.LIMIT: 323>, <TokenType.LIST: 324>, <TokenType.LOAD: 325>, <TokenType.MAP: 327>, <TokenType.MATCH: 328>, <TokenType.MERGE: 332>, <TokenType.MODEL: 334>, <TokenType.NEXT: 336>, <TokenType.NOTHING: 337>, <TokenType.NULL: 339>, <TokenType.OBJECT_IDENTIFIER: 340>, <TokenType.OFFSET: 341>, <TokenType.OPERATOR: 344>, <TokenType.ORDINALITY: 348>, <TokenType.OUT: 349>, <TokenType.INOUT: 350>, <TokenType.OVER: 352>, <TokenType.OVERLAPS: 353>, <TokenType.OVERWRITE: 354>, <TokenType.PARTITION: 356>, <TokenType.PASTE: 358>, <TokenType.PERCENT: 359>, <TokenType.PIVOT: 360>, <TokenType.PRAGMA: 365>, <TokenType.PROCEDURE: 368>, <TokenType.PROJECTION: 370>, <TokenType.PSEUDO_TYPE: 371>, <TokenType.PUT: 372>, <TokenType.RANGE: 376>, <TokenType.RECURSIVE: 377>, <TokenType.REFRESH: 378>, <TokenType.RENAME: 379>, <TokenType.REPLACE: 380>, <TokenType.REFERENCES: 383>, <TokenType.ROLLUP: 388>, <TokenType.ROW: 389>, <TokenType.ROWS: 390>, <TokenType.SEQUENCE: 395>, <TokenType.SET: 397>, <TokenType.SETTINGS: 398>, <TokenType.SHOW: 399>, <TokenType.SOME: 401>, <TokenType.STORAGE_INTEGRATION: 406>, <TokenType.STRAIGHT_JOIN: 407>, <TokenType.STRUCT: 408>, <TokenType.TAG: 411>, <TokenType.TEMPORARY: 412>, <TokenType.TOP: 413>, <TokenType.TRUE: 415>, <TokenType.TRUNCATE: 416>, <TokenType.TRIGGER: 417>, <TokenType.TYPE: 418>, <TokenType.UNNEST: 422>, <TokenType.UNPIVOT: 423>, <TokenType.UPDATE: 424>, <TokenType.USE: 425>, <TokenType.VIEW: 429>, <TokenType.SEMANTIC_VIEW: 430>, <TokenType.VOLATILE: 431>, <TokenType.UNIQUE: 437>, <TokenType.SINK: 442>, <TokenType.SOURCE: 443>, <TokenType.ANALYZE: 444>, <TokenType.NAMESPACE: 445>, <TokenType.EXPORT: 446>}
SELECT_START_TOKENS: ClassVar = {<TokenType.SELECT: 392>, <TokenType.L_PAREN: 1>, <TokenType.WITH: 436>}
COPY_INTO_VARLEN_OPTIONS: ClassVar = {'FILE_FORMAT', 'CREDENTIAL', 'FORMAT_OPTIONS', 'COPY_OPTIONS'}
IS_PREDICATE_PRECEDENCE: ClassVar[IsPredicatePrecedence] = <IsPredicatePrecedence.HIGH: 1>
IS_PREDICATE_TOKENS: ClassVar = {<TokenType.IS: 312>, <TokenType.ISNULL: 313>, <TokenType.NOTNULL: 338>, <TokenType.NOT: 27>}
IS_JSON_PREDICATE_KIND: ClassVar = {'SCALAR', 'VALUE', 'ARRAY', 'OBJECT'}
ODBC_DATETIME_LITERALS: ClassVar[dict[str, type[sqlglot.expressions.core.Expr]]] = {}
ON_CONDITION_TOKENS: ClassVar = {'NULL', 'TRUE', 'ERROR', 'EMPTY', 'FALSE'}
PRIVILEGE_FOLLOW_TOKENS: ClassVar = {<TokenType.L_PAREN: 1>, <TokenType.ON: 342>, <TokenType.COMMA: 7>}
DESCRIBE_STYLES: ClassVar = {'EXTENDED', 'ANALYZE', 'HISTORY', 'FORMATTED'}
SET_ASSIGNMENT_DELIMITERS: ClassVar = {'TO', ':=', '='}
ANALYZE_STYLES: ClassVar = {'BUFFER_USAGE_LIMIT', 'LOCAL', 'NO_WRITE_TO_BINLOG', 'SKIP_LOCKED', 'VERBOSE', 'FULL', 'SAMPLE'}
ANALYZE_EXPRESSION_PARSERS: ClassVar = {'ALL': <function Parser.<lambda>>, 'COMPUTE': <function Parser.<lambda>>, 'DELETE': <function Parser.<lambda>>, 'DROP': <function Parser.<lambda>>, 'ESTIMATE': <function Parser.<lambda>>, 'LIST': <function Parser.<lambda>>, 'PREDICATE': <function Parser.<lambda>>, 'UPDATE': <function Parser.<lambda>>, 'VALIDATE': <function Parser.<lambda>>}
PARTITION_KEYWORDS: ClassVar = {'PARTITION', 'SUBPARTITION'}
AMBIGUOUS_ALIAS_TOKENS: ClassVar = (<TokenType.LIMIT: 323>, <TokenType.OFFSET: 341>)
OPERATION_MODIFIERS: ClassVar[set[str]] = set()
RECURSIVE_CTE_SEARCH_KIND: ClassVar = {'CYCLE', 'BREADTH', 'DEPTH'}
SECURITY_PROPERTY_KEYWORDS: ClassVar = {'DEFINER', 'INVOKER', '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 = {'offset', 'order', 'cluster', 'sort', 'distribute', 'limit'}
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:
2051    def reset(self) -> None:
2052        self.sql = ""
2053        self.errors = []
2054        self._tokens = []
2055        self._tokens_size = 0
2056        self._index = 0
2057        self._curr = SENTINEL_NONE
2058        self._next = SENTINEL_NONE
2059        self._prev = SENTINEL_NONE
2060        self._prev_comments = []
2061        self._pipe_cte_counter = 0
2062        self._chunks = []
2063        self._chunk_index = 0
2064        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:
2157    def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None:
2158        token = token or self._curr or self._prev or Token.string("")
2159        formatted_sql, start_context, highlight, end_context = highlight_sql(
2160            sql=self.sql,
2161            positions=[(token.start, token.end)],
2162            context_length=self.error_message_context,
2163        )
2164        formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n  {formatted_sql}"
2165
2166        error = ParseError.new(
2167            formatted_message,
2168            description=message,
2169            line=token.line,
2170            col=token.col,
2171            start_context=start_context,
2172            highlight=highlight,
2173            end_context=end_context,
2174        )
2175
2176        if self.error_level == ErrorLevel.IMMEDIATE:
2177            raise error
2178
2179        self.errors.append(error)
def validate_expression(self, expression: ~E, args: list | None = None) -> ~E:
2181    def validate_expression(self, expression: E, args: list | None = None) -> E:
2182        if self.max_nodes > -1:
2183            self._node_count += 1
2184            if self._node_count > self.max_nodes:
2185                self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded")
2186        if self.error_level != ErrorLevel.IGNORE:
2187            for error_message in expression.error_messages(args):
2188                self.raise_error(error_message)
2189        return expression
def parse( self, raw_tokens: list[sqlglot.tokenizer_core.Token], sql: str) -> list[sqlglot.expressions.core.Expr | None]:
2208    def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]:
2209        """
2210        Parses a list of tokens and returns a list of syntax trees, one tree
2211        per parsed SQL statement.
2212
2213        Args:
2214            raw_tokens: The list of tokens.
2215            sql: The original SQL string.
2216
2217        Returns:
2218            The list of the produced syntax trees.
2219        """
2220        return self._parse(
2221            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
2222        )

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

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:
2271    def expression(
2272        self,
2273        instance: E,
2274        token: Token | None = None,
2275        comments: list[str] | None = None,
2276    ) -> E:
2277        if token:
2278            instance.update_positions(token)
2279        instance.add_comments(comments) if comments else self._add_comments(instance)
2280        if not instance.is_primitive:
2281            instance = self.validate_expression(instance)
2282        return instance
def parse_set_operation( self, this: sqlglot.expressions.core.Expr | None, consume_pipe: bool = False, intersect_only: bool = False) -> sqlglot.expressions.query.SetOperation | None:
6093    def parse_set_operation(
6094        self, this: exp.Expr | None, consume_pipe: bool = False, intersect_only: bool = False
6095    ) -> exp.SetOperation | None:
6096        start = self._index
6097        _, side_token, kind_token = self._parse_join_parts()
6098
6099        side = side_token.text if side_token else None
6100        kind = kind_token.text if kind_token else None
6101
6102        if not self._match_set(self.SET_OPERATIONS) or (
6103            intersect_only and self._prev.token_type != TokenType.INTERSECT
6104        ):
6105            self._retreat(start)
6106            return None
6107
6108        token_type = self._prev.token_type
6109
6110        if token_type == TokenType.UNION:
6111            operation: type[exp.SetOperation] = exp.Union
6112        elif token_type == TokenType.EXCEPT:
6113            operation = exp.Except
6114        else:
6115            operation = exp.Intersect
6116
6117        comments = self._prev.comments
6118
6119        if self._match(TokenType.DISTINCT):
6120            distinct: bool | None = True
6121        elif self._match(TokenType.ALL):
6122            distinct = False
6123        else:
6124            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
6125            if distinct is None:
6126                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
6127
6128        by_name = (
6129            self._match_text_seq("BY", "NAME")
6130            or self._match_text_seq("STRICT", "CORRESPONDING")
6131            or None
6132        )
6133        if self._match_text_seq("CORRESPONDING"):
6134            by_name = True
6135            if not side and not kind:
6136                kind = "INNER"
6137
6138        on_column_list = None
6139        if by_name and self._match_texts(("ON", "BY")):
6140            on_column_list = self._parse_wrapped_csv(self._parse_column)
6141
6142        expression = self._parse_select(
6143            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
6144        )
6145
6146        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
6147        # in _parse_cte and so that alias pushdown can reach into set operation branches
6148        if isinstance(this, exp.Values):
6149            this = self._values_to_select(this)
6150        if isinstance(expression, exp.Values):
6151            expression = self._values_to_select(expression)
6152        for operand in (this, expression):
6153            if isinstance(operand, exp.Subquery) and isinstance(
6154                values := operand.unnest(), exp.Values
6155            ):
6156                wrapper = t.cast(exp.Subquery, values.parent)
6157                wrapper.set("this", self._values_to_select(values))
6158
6159        if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery):
6160            subquery = this.this
6161            subquery.set("alias", exp.TableAlias(this=this.args["alias"]))
6162            subquery.add_comments(this.pop_comments())
6163            this = subquery
6164
6165        return self.expression(
6166            operation(
6167                this=this,
6168                distinct=distinct,
6169                by_name=by_name,
6170                expression=expression,
6171                side=side,
6172                kind=kind,
6173                on=on_column_list,
6174            ),
6175            comments=comments,
6176        )
def build_cast(self, strict: bool, **kwargs) -> sqlglot.expressions.core.Expr:
10837    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10838        exp_class = exp.Cast if strict else exp.TryCast
10839
10840        if exp_class == exp.TryCast:
10841            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10842
10843        return self.expression(exp_class(**kwargs))