Edit on GitHub

sqlglot.parser

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

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

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

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

ArrayAppend expression with appropriate null_propagation flag

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

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

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

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

ArrayPrepend expression with appropriate null_propagation flag

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

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

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

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

ArrayConcat expression with appropriate null_propagation flag

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

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

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

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

ArrayRemove expression with appropriate null_propagation flag

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

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

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]:
2155    def parse_into(
2156        self,
2157        expression_types: exp.IntoType,
2158        raw_tokens: list[Token],
2159        sql: str | None = None,
2160    ) -> list[exp.Expr | None]:
2161        """
2162        Parses a list of tokens into a given Expr type. If a collection of Expr
2163        types is given instead, this method will try to parse the token list into each one
2164        of them, stopping at the first for which the parsing succeeds.
2165
2166        Args:
2167            expression_types: The expression type(s) to try and parse the token list into.
2168            raw_tokens: The list of tokens.
2169            sql: The original SQL string, used to produce helpful debug messages.
2170
2171        Returns:
2172            The target Expr.
2173        """
2174        errors = []
2175        for expression_type in ensure_list(expression_types):
2176            parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type))
2177            if not parser:
2178                raise TypeError(f"No parser registered for {expression_type}")
2179
2180            try:
2181                return self._parse(parser, raw_tokens, sql)
2182            except ParseError as e:
2183                e.errors[0]["into_expression"] = expression_type
2184                errors.append(e)
2185
2186        raise ParseError(
2187            f"Failed to parse '{sql or raw_tokens}' into {expression_types}",
2188            errors=merge_errors(errors),
2189        ) 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:
2191    def check_errors(self) -> None:
2192        """Logs or raises any found errors, depending on the chosen error level setting."""
2193        if self.error_level == ErrorLevel.WARN:
2194            for error in self.errors:
2195                logger.error(str(error))
2196        elif self.error_level == ErrorLevel.RAISE and self.errors:
2197            raise ParseError(
2198                concat_messages(self.errors, self.max_errors),
2199                errors=merge_errors(self.errors),
2200            )

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:
2202    def expression(
2203        self,
2204        instance: E,
2205        token: Token | None = None,
2206        comments: list[str] | None = None,
2207    ) -> E:
2208        if token:
2209            instance.update_positions(token)
2210        instance.add_comments(comments) if comments else self._add_comments(instance)
2211        if not instance.is_primitive:
2212            instance = self.validate_expression(instance)
2213        return instance
def parse_set_operation( self, this: sqlglot.expressions.core.Expr | None, consume_pipe: bool = False) -> sqlglot.expressions.core.Expr | None:
5929    def parse_set_operation(
5930        self, this: exp.Expr | None, consume_pipe: bool = False
5931    ) -> exp.Expr | None:
5932        start = self._index
5933        _, side_token, kind_token = self._parse_join_parts()
5934
5935        side = side_token.text if side_token else None
5936        kind = kind_token.text if kind_token else None
5937
5938        if not self._match_set(self.SET_OPERATIONS):
5939            self._retreat(start)
5940            return None
5941
5942        token_type = self._prev.token_type
5943
5944        if token_type == TokenType.UNION:
5945            operation: type[exp.SetOperation] = exp.Union
5946        elif token_type == TokenType.EXCEPT:
5947            operation = exp.Except
5948        else:
5949            operation = exp.Intersect
5950
5951        comments = self._prev.comments
5952
5953        if self._match(TokenType.DISTINCT):
5954            distinct: bool | None = True
5955        elif self._match(TokenType.ALL):
5956            distinct = False
5957        else:
5958            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
5959            if distinct is None:
5960                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
5961
5962        by_name = (
5963            self._match_text_seq("BY", "NAME")
5964            or self._match_text_seq("STRICT", "CORRESPONDING")
5965            or None
5966        )
5967        if self._match_text_seq("CORRESPONDING"):
5968            by_name = True
5969            if not side and not kind:
5970                kind = "INNER"
5971
5972        on_column_list = None
5973        if by_name and self._match_texts(("ON", "BY")):
5974            on_column_list = self._parse_wrapped_csv(self._parse_column)
5975
5976        expression = self._parse_select(
5977            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
5978        )
5979
5980        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
5981        # in _parse_cte and so that alias pushdown can reach into set operation branches
5982        if isinstance(this, exp.Values):
5983            this = self._values_to_select(this)
5984        if isinstance(expression, exp.Values):
5985            expression = self._values_to_select(expression)
5986
5987        if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery):
5988            subquery = this.this
5989            subquery.set("alias", exp.TableAlias(this=this.args["alias"]))
5990            subquery.add_comments(this.pop_comments())
5991            this = subquery
5992
5993        return self.expression(
5994            operation(
5995                this=this,
5996                distinct=distinct,
5997                by_name=by_name,
5998                expression=expression,
5999                side=side,
6000                kind=kind,
6001                on=on_column_list,
6002            ),
6003            comments=comments,
6004        )
def build_cast(self, strict: bool, **kwargs) -> sqlglot.expressions.core.Expr:
10433    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10434        exp_class = exp.Cast if strict else exp.TryCast
10435
10436        if exp_class == exp.TryCast:
10437            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10438
10439        return self.expression(exp_class(**kwargs))