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"}
 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        index = self._index
 2924
 2925        seq_props = self._parse_sequence_properties()
 2926        if seq_props:
 2927            return seq_props
 2928
 2929        self._retreat(index)
 2930        return self._parse_key_value_property()
 2931
 2932    def _parse_key_value_property(
 2933        self, parse_value: t.Callable[[], exp.Expr | None] | None = None
 2934    ) -> exp.Property | None:
 2935        index = self._index
 2936        key = self._parse_column()
 2937
 2938        if not self._match(TokenType.EQ):
 2939            self._retreat(index)
 2940            return None
 2941
 2942        # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise
 2943        if isinstance(key, exp.Column):
 2944            key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name)
 2945
 2946        value = (
 2947            parse_value()
 2948            if parse_value
 2949            else self._parse_bitwise() or self._parse_var(any_token=True)
 2950        )
 2951
 2952        # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier())
 2953        if isinstance(value, exp.Column):
 2954            value = exp.var(value.name)
 2955
 2956        return self.expression(exp.Property(this=key, value=value))
 2957
 2958    def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty:
 2959        if self._match_text_seq("BY"):
 2960            return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string()))
 2961
 2962        self._match(TokenType.ALIAS)
 2963        input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None
 2964        output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None
 2965
 2966        return self.expression(
 2967            exp.FileFormatProperty(
 2968                this=(
 2969                    self.expression(
 2970                        exp.InputOutputFormat(
 2971                            input_format=input_format, output_format=output_format
 2972                        )
 2973                    )
 2974                    if input_format or output_format
 2975                    else self._parse_var_or_string() or self._parse_number() or self._parse_id_var()
 2976                ),
 2977                hive_format=True,
 2978            )
 2979        )
 2980
 2981    def _parse_unquoted_field(self) -> exp.Expr | None:
 2982        field = self._parse_field()
 2983        if isinstance(field, exp.Identifier) and not field.quoted:
 2984            field = exp.var(field)
 2985
 2986        return field
 2987
 2988    def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E:
 2989        self._match(TokenType.EQ)
 2990        self._match(TokenType.ALIAS)
 2991
 2992        return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs))
 2993
 2994    def _parse_properties(self, before: bool | None = None) -> exp.Properties | None:
 2995        properties = []
 2996        while True:
 2997            if before:
 2998                prop = self._parse_property_before()
 2999            else:
 3000                prop = self._parse_property()
 3001            if not prop:
 3002                break
 3003            for p in ensure_list(prop):
 3004                properties.append(p)
 3005
 3006        if properties:
 3007            return self.expression(exp.Properties(expressions=properties))
 3008
 3009        return None
 3010
 3011    def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty:
 3012        return self.expression(
 3013            exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION"))
 3014        )
 3015
 3016    def _parse_sql_security(self) -> exp.SqlSecurityProperty:
 3017        return self.expression(
 3018            exp.SqlSecurityProperty(
 3019                this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper()
 3020            )
 3021        )
 3022
 3023    def _parse_settings_property(self) -> exp.SettingsProperty:
 3024        return self.expression(
 3025            exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment))
 3026        )
 3027
 3028    def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None:
 3029        if not self._match_text_seq("ON", "NULL", "INPUT"):
 3030            self._retreat(self._index - 1)
 3031            return None
 3032
 3033        return self.expression(exp.CalledOnNullInputProperty())
 3034
 3035    def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty:
 3036        if self._index >= 2:
 3037            pre_volatile_token = self._tokens[self._index - 2]
 3038        else:
 3039            pre_volatile_token = None
 3040
 3041        if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS:
 3042            return exp.VolatileProperty()
 3043
 3044        return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 3045
 3046    def _parse_retention_period(self) -> exp.Var:
 3047        # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...}
 3048        number = self._parse_number()
 3049        number_str = f"{number} " if number else ""
 3050        unit = self._parse_var(any_token=True)
 3051        return exp.var(f"{number_str}{unit}")
 3052
 3053    def _parse_system_versioning_property(
 3054        self, with_: bool = False
 3055    ) -> exp.WithSystemVersioningProperty:
 3056        self._match(TokenType.EQ)
 3057        prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_))
 3058
 3059        if self._match_text_seq("OFF"):
 3060            prop.set("on", False)
 3061            return prop
 3062
 3063        self._match(TokenType.ON)
 3064        if self._match(TokenType.L_PAREN):
 3065            while self._curr and not self._match(TokenType.R_PAREN):
 3066                if self._match_text_seq("HISTORY_TABLE", "="):
 3067                    prop.set("this", self._parse_table_parts())
 3068                elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="):
 3069                    prop.set("data_consistency", self._advance_any() and self._prev.text.upper())
 3070                elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="):
 3071                    prop.set("retention_period", self._parse_retention_period())
 3072
 3073                self._match(TokenType.COMMA)
 3074
 3075        return prop
 3076
 3077    def _parse_data_deletion_property(self) -> exp.DataDeletionProperty:
 3078        self._match(TokenType.EQ)
 3079        on = self._match_text_seq("ON") or not self._match_text_seq("OFF")
 3080        prop = self.expression(exp.DataDeletionProperty(on=on))
 3081
 3082        if self._match(TokenType.L_PAREN):
 3083            while self._curr and not self._match(TokenType.R_PAREN):
 3084                if self._match_text_seq("FILTER_COLUMN", "="):
 3085                    prop.set("filter_column", self._parse_column())
 3086                elif self._match_text_seq("RETENTION_PERIOD", "="):
 3087                    prop.set("retention_period", self._parse_retention_period())
 3088
 3089                self._match(TokenType.COMMA)
 3090
 3091        return prop
 3092
 3093    def _parse_distributed_property(self) -> exp.DistributedByProperty:
 3094        kind = "HASH"
 3095        expressions: list[exp.Expr] | None = None
 3096        if self._match_text_seq("BY", "HASH"):
 3097            expressions = self._parse_wrapped_csv(self._parse_id_var)
 3098        elif self._match_text_seq("BY", "RANDOM"):
 3099            kind = "RANDOM"
 3100
 3101        # If the BUCKETS keyword is not present, the number of buckets is AUTO
 3102        buckets: exp.Expr | None = None
 3103        if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"):
 3104            buckets = self._parse_number()
 3105
 3106        return self.expression(
 3107            exp.DistributedByProperty(
 3108                expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order()
 3109            )
 3110        )
 3111
 3112    def _parse_composite_key_property(self, expr_type: type[E]) -> E:
 3113        self._match_text_seq("KEY")
 3114        expressions = self._parse_wrapped_id_vars()
 3115        return self.expression(expr_type(expressions=expressions))
 3116
 3117    def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]:
 3118        if self._match_text_seq("(", "SYSTEM_VERSIONING"):
 3119            prop = self._parse_system_versioning_property(with_=True)
 3120            self._match_r_paren()
 3121            return prop
 3122
 3123        if self._match(TokenType.L_PAREN, advance=False):
 3124            result: list[exp.Expr] = []
 3125            for i in self._parse_wrapped_properties():
 3126                result.extend(i) if isinstance(i, list) else result.append(i)
 3127            return result
 3128
 3129        if self._match_text_seq("JOURNAL"):
 3130            return self._parse_withjournaltable()
 3131
 3132        if self._match_texts(self.VIEW_ATTRIBUTES):
 3133            return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper()))
 3134
 3135        if self._match_text_seq("DATA"):
 3136            return self._parse_withdata(no=False)
 3137        elif self._match_text_seq("NO", "DATA"):
 3138            return self._parse_withdata(no=True)
 3139
 3140        if self._match(TokenType.SERDE_PROPERTIES, advance=False):
 3141            return self._parse_serde_properties(with_=True)
 3142
 3143        if self._match(TokenType.SCHEMA):
 3144            return self.expression(
 3145                exp.WithSchemaBindingProperty(
 3146                    this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS)
 3147                )
 3148            )
 3149
 3150        if self._match_texts(self.PROCEDURE_OPTIONS, advance=False):
 3151            return self.expression(
 3152                exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option))
 3153            )
 3154
 3155        if not self._next:
 3156            return None
 3157
 3158        return self._parse_withisolatedloading()
 3159
 3160    def _parse_procedure_option(self) -> exp.Expr | None:
 3161        if self._match_text_seq("EXECUTE", "AS"):
 3162            return self.expression(
 3163                exp.ExecuteAsProperty(
 3164                    this=self._parse_var_from_options(
 3165                        self.EXECUTE_AS_OPTIONS, raise_unmatched=False
 3166                    )
 3167                    or self._parse_string()
 3168                )
 3169            )
 3170
 3171        return self._parse_var_from_options(self.PROCEDURE_OPTIONS)
 3172
 3173    # https://dev.mysql.com/doc/refman/8.0/en/create-view.html
 3174    def _parse_definer(self) -> exp.DefinerProperty | None:
 3175        self._match(TokenType.EQ)
 3176
 3177        user = self._parse_id_var()
 3178        self._match(TokenType.PARAMETER)
 3179        host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text)
 3180
 3181        if not user or not host:
 3182            return None
 3183
 3184        return exp.DefinerProperty(this=f"{user}@{host}")
 3185
 3186    def _parse_withjournaltable(self) -> exp.WithJournalTableProperty:
 3187        self._match(TokenType.TABLE)
 3188        self._match(TokenType.EQ)
 3189        return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts()))
 3190
 3191    def _parse_log(self, no: bool = False) -> exp.LogProperty:
 3192        return self.expression(exp.LogProperty(no=no))
 3193
 3194    def _parse_journal(self, **kwargs) -> exp.JournalProperty:
 3195        return self.expression(exp.JournalProperty(**kwargs))
 3196
 3197    def _parse_checksum(self) -> exp.ChecksumProperty:
 3198        self._match(TokenType.EQ)
 3199
 3200        on = None
 3201        if self._match(TokenType.ON):
 3202            on = True
 3203        elif self._match_text_seq("OFF"):
 3204            on = False
 3205
 3206        return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT)))
 3207
 3208    def _parse_cluster(self) -> exp.Cluster:
 3209        self._match(TokenType.CLUSTER_BY)
 3210        return self.expression(
 3211            exp.Cluster(
 3212                expressions=self._parse_csv(self._parse_column),
 3213            )
 3214        )
 3215
 3216    def _parse_cluster_property(self) -> exp.ClusterProperty:
 3217        return self.expression(
 3218            exp.ClusterProperty(
 3219                expressions=self._parse_wrapped_csv(self._parse_column),
 3220            )
 3221        )
 3222
 3223    def _parse_clustered_by(self) -> exp.ClusteredByProperty:
 3224        self._match_text_seq("BY")
 3225
 3226        self._match_l_paren()
 3227        expressions = self._parse_csv(self._parse_column)
 3228        self._match_r_paren()
 3229
 3230        if self._match_text_seq("SORTED", "BY"):
 3231            self._match_l_paren()
 3232            sorted_by = self._parse_csv(self._parse_ordered)
 3233            self._match_r_paren()
 3234        else:
 3235            sorted_by = None
 3236
 3237        self._match(TokenType.INTO)
 3238        buckets = self._parse_number()
 3239        self._match_text_seq("BUCKETS")
 3240
 3241        return self.expression(
 3242            exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets)
 3243        )
 3244
 3245    def _parse_copy_property(self) -> exp.CopyGrantsProperty | None:
 3246        if not self._match_text_seq("GRANTS"):
 3247            self._retreat(self._index - 1)
 3248            return None
 3249
 3250        return self.expression(exp.CopyGrantsProperty())
 3251
 3252    def _parse_freespace(self) -> exp.FreespaceProperty:
 3253        self._match(TokenType.EQ)
 3254        return self.expression(
 3255            exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT))
 3256        )
 3257
 3258    def _parse_mergeblockratio(
 3259        self, no: bool = False, default: bool = False
 3260    ) -> exp.MergeBlockRatioProperty:
 3261        if self._match(TokenType.EQ):
 3262            return self.expression(
 3263                exp.MergeBlockRatioProperty(
 3264                    this=self._parse_number(), percent=self._match(TokenType.PERCENT)
 3265                )
 3266            )
 3267
 3268        return self.expression(exp.MergeBlockRatioProperty(no=no, default=default))
 3269
 3270    def _parse_datablocksize(
 3271        self,
 3272        default: bool | None = None,
 3273        minimum: bool | None = None,
 3274        maximum: bool | None = None,
 3275    ) -> exp.DataBlocksizeProperty:
 3276        self._match(TokenType.EQ)
 3277        size = self._parse_number()
 3278
 3279        units = None
 3280        if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")):
 3281            units = self._prev.text
 3282
 3283        return self.expression(
 3284            exp.DataBlocksizeProperty(
 3285                size=size, units=units, default=default, minimum=minimum, maximum=maximum
 3286            )
 3287        )
 3288
 3289    def _parse_blockcompression(self) -> exp.BlockCompressionProperty:
 3290        self._match(TokenType.EQ)
 3291        always = self._match_text_seq("ALWAYS")
 3292        manual = self._match_text_seq("MANUAL")
 3293        never = self._match_text_seq("NEVER")
 3294        default = self._match_text_seq("DEFAULT")
 3295
 3296        autotemp = None
 3297        if self._match_text_seq("AUTOTEMP"):
 3298            autotemp = self._parse_schema()
 3299
 3300        return self.expression(
 3301            exp.BlockCompressionProperty(
 3302                always=always, manual=manual, never=never, default=default, autotemp=autotemp
 3303            )
 3304        )
 3305
 3306    def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None:
 3307        index = self._index
 3308        no = self._match_text_seq("NO")
 3309        concurrent = self._match_text_seq("CONCURRENT")
 3310
 3311        if not self._match_text_seq("ISOLATED", "LOADING"):
 3312            self._retreat(index)
 3313            return None
 3314
 3315        target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False)
 3316        return self.expression(
 3317            exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target)
 3318        )
 3319
 3320    def _parse_locking(self) -> exp.LockingProperty:
 3321        if self._match(TokenType.TABLE):
 3322            kind = "TABLE"
 3323        elif self._match(TokenType.VIEW):
 3324            kind = "VIEW"
 3325        elif self._match(TokenType.ROW):
 3326            kind = "ROW"
 3327        elif self._match_text_seq("DATABASE"):
 3328            kind = "DATABASE"
 3329        else:
 3330            kind = None
 3331
 3332        if kind in ("DATABASE", "TABLE", "VIEW"):
 3333            this = self._parse_table_parts()
 3334        else:
 3335            this = None
 3336
 3337        if self._match(TokenType.FOR):
 3338            for_or_in = "FOR"
 3339        elif self._match(TokenType.IN):
 3340            for_or_in = "IN"
 3341        else:
 3342            for_or_in = None
 3343
 3344        if self._match_text_seq("ACCESS"):
 3345            lock_type = "ACCESS"
 3346        elif self._match_texts(("EXCL", "EXCLUSIVE")):
 3347            lock_type = "EXCLUSIVE"
 3348        elif self._match_text_seq("SHARE"):
 3349            lock_type = "SHARE"
 3350        elif self._match_text_seq("READ"):
 3351            lock_type = "READ"
 3352        elif self._match_text_seq("WRITE"):
 3353            lock_type = "WRITE"
 3354        elif self._match_text_seq("CHECKSUM"):
 3355            lock_type = "CHECKSUM"
 3356        else:
 3357            lock_type = None
 3358
 3359        override = self._match_text_seq("OVERRIDE")
 3360
 3361        return self.expression(
 3362            exp.LockingProperty(
 3363                this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override
 3364            )
 3365        )
 3366
 3367    def _parse_partition_by(self) -> list[exp.Expr]:
 3368        if self._match(TokenType.PARTITION_BY):
 3369            return self._parse_csv(self._parse_disjunction)
 3370        return []
 3371
 3372    def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec:
 3373        def _parse_partition_bound_expr() -> exp.Expr | None:
 3374            if self._match_text_seq("MINVALUE"):
 3375                return exp.var("MINVALUE")
 3376            if self._match_text_seq("MAXVALUE"):
 3377                return exp.var("MAXVALUE")
 3378            return self._parse_bitwise()
 3379
 3380        this: exp.Expr | list[exp.Expr] | None = None
 3381        expression = None
 3382        from_expressions = None
 3383        to_expressions = None
 3384
 3385        if self._match(TokenType.IN):
 3386            this = self._parse_wrapped_csv(self._parse_bitwise)
 3387        elif self._match(TokenType.FROM):
 3388            from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3389            self._match_text_seq("TO")
 3390            to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3391        elif self._match_text_seq("WITH", "(", "MODULUS"):
 3392            this = self._parse_number()
 3393            self._match_text_seq(",", "REMAINDER")
 3394            expression = self._parse_number()
 3395            self._match_r_paren()
 3396        else:
 3397            self.raise_error("Failed to parse partition bound spec.")
 3398
 3399        return self.expression(
 3400            exp.PartitionBoundSpec(
 3401                this=this,
 3402                expression=expression,
 3403                from_expressions=from_expressions,
 3404                to_expressions=to_expressions,
 3405            )
 3406        )
 3407
 3408    # https://www.postgresql.org/docs/current/sql-createtable.html
 3409    def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None:
 3410        if not self._match_text_seq("OF"):
 3411            self._retreat(self._index - 1)
 3412            return None
 3413
 3414        this = self._parse_table(schema=True)
 3415
 3416        if self._match(TokenType.DEFAULT):
 3417            expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT")
 3418        elif self._match_text_seq("FOR", "VALUES"):
 3419            expression = self._parse_partition_bound_spec()
 3420        else:
 3421            self.raise_error("Expecting either DEFAULT or FOR VALUES clause.")
 3422
 3423        return self.expression(exp.PartitionedOfProperty(this=this, expression=expression))
 3424
 3425    def _parse_partitioned_by(self) -> exp.PartitionedByProperty:
 3426        self._match(TokenType.EQ)
 3427        return self.expression(
 3428            exp.PartitionedByProperty(
 3429                this=self._parse_schema() or self._parse_bracket(self._parse_field())
 3430            )
 3431        )
 3432
 3433    def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty:
 3434        if self._match_text_seq("AND", "STATISTICS"):
 3435            statistics = True
 3436        elif self._match_text_seq("AND", "NO", "STATISTICS"):
 3437            statistics = False
 3438        else:
 3439            statistics = None
 3440
 3441        return self.expression(exp.WithDataProperty(no=no, statistics=statistics))
 3442
 3443    def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None:
 3444        if self._match_text_seq("SQL"):
 3445            return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL"))
 3446        return None
 3447
 3448    def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None:
 3449        if self._match_text_seq("SQL", "DATA"):
 3450            return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA"))
 3451        return None
 3452
 3453    def _parse_no_property(self) -> exp.Expr | None:
 3454        if self._match_text_seq("PRIMARY", "INDEX"):
 3455            return exp.NoPrimaryIndexProperty()
 3456        if self._match_text_seq("SQL"):
 3457            return self.expression(exp.SqlReadWriteProperty(this="NO SQL"))
 3458        return None
 3459
 3460    def _parse_on_property(self) -> exp.Expr | None:
 3461        if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"):
 3462            return exp.OnCommitProperty()
 3463        if self._match_text_seq("COMMIT", "DELETE", "ROWS"):
 3464            return exp.OnCommitProperty(delete=True)
 3465        return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var())))
 3466
 3467    def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None:
 3468        if self._match_text_seq("SQL", "DATA"):
 3469            return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA"))
 3470        return None
 3471
 3472    def _parse_distkey(self) -> exp.DistKeyProperty:
 3473        return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var)))
 3474
 3475    def _parse_create_like(self) -> exp.LikeProperty | None:
 3476        table = self._parse_table(schema=True)
 3477
 3478        options = []
 3479        while self._match_texts(("INCLUDING", "EXCLUDING")):
 3480            this = self._prev.text.upper()
 3481
 3482            id_var = self._parse_id_var()
 3483            if not id_var:
 3484                return None
 3485
 3486            options.append(
 3487                self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper())))
 3488            )
 3489
 3490        return self.expression(exp.LikeProperty(this=table, expressions=options))
 3491
 3492    def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty:
 3493        return self.expression(
 3494            exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound)
 3495        )
 3496
 3497    def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty:
 3498        self._match(TokenType.EQ)
 3499        return self.expression(
 3500            exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default)
 3501        )
 3502
 3503    def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty:
 3504        self._match_text_seq("WITH", "CONNECTION")
 3505        return self.expression(
 3506            exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts())
 3507        )
 3508
 3509    def _parse_returns(self) -> exp.ReturnsProperty:
 3510        value: exp.Expr | None
 3511        null = None
 3512        is_table = self._match(TokenType.TABLE)
 3513
 3514        if is_table:
 3515            if self._match(TokenType.LT):
 3516                value = self.expression(
 3517                    exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types))
 3518                )
 3519                if not self._match(TokenType.GT):
 3520                    self.raise_error("Expecting >")
 3521            else:
 3522                value = self._parse_schema(exp.var("TABLE"))
 3523        elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"):
 3524            null = True
 3525            value = None
 3526        else:
 3527            value = self._parse_types()
 3528
 3529        return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null))
 3530
 3531    def _parse_describe(self) -> exp.Describe:
 3532        kind = self._prev.text if self._match_set(self.CREATABLES) else None
 3533        style: str | None = (
 3534            self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None
 3535        )
 3536        if self._match(TokenType.DOT):
 3537            style = None
 3538            self._retreat(self._index - 2)
 3539
 3540        format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None
 3541
 3542        if self._match_set(self.STATEMENT_PARSERS, advance=False):
 3543            this = self._parse_statement()
 3544        else:
 3545            this = self._parse_table(schema=True)
 3546
 3547        properties = self._parse_properties()
 3548        expressions = properties.expressions if properties else None
 3549        partition = self._parse_partition()
 3550        return self.expression(
 3551            exp.Describe(
 3552                this=this,
 3553                style=style,
 3554                kind=kind,
 3555                expressions=expressions,
 3556                partition=partition,
 3557                format=format,
 3558                as_json=self._match_text_seq("AS", "JSON"),
 3559            )
 3560        )
 3561
 3562    def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts:
 3563        kind = self._prev.text.upper()
 3564        expressions = []
 3565
 3566        def parse_conditional_insert() -> exp.ConditionalInsert | None:
 3567            if self._match(TokenType.WHEN):
 3568                expression = self._parse_disjunction()
 3569                self._match(TokenType.THEN)
 3570            else:
 3571                expression = None
 3572
 3573            else_ = self._match(TokenType.ELSE)
 3574
 3575            if not self._match(TokenType.INTO):
 3576                return None
 3577
 3578            return self.expression(
 3579                exp.ConditionalInsert(
 3580                    this=self.expression(
 3581                        exp.Insert(
 3582                            this=self._parse_table(schema=True),
 3583                            expression=self._parse_derived_table_values(),
 3584                        )
 3585                    ),
 3586                    expression=expression,
 3587                    else_=else_,
 3588                )
 3589            )
 3590
 3591        expression = parse_conditional_insert()
 3592        while expression is not None:
 3593            expressions.append(expression)
 3594            expression = parse_conditional_insert()
 3595
 3596        return self.expression(
 3597            exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()),
 3598            comments=comments,
 3599        )
 3600
 3601    def _parse_insert(self) -> exp.Insert | exp.MultitableInserts:
 3602        comments: list[str] = []
 3603        hint = self._parse_hint()
 3604        overwrite = self._match(TokenType.OVERWRITE)
 3605        ignore = self._match(TokenType.IGNORE)
 3606        local = self._match_text_seq("LOCAL")
 3607        alternative = None
 3608        is_function = None
 3609
 3610        if self._match_text_seq("DIRECTORY"):
 3611            this: exp.Expr | None = self.expression(
 3612                exp.Directory(
 3613                    this=self._parse_var_or_string(),
 3614                    local=local,
 3615                    row_format=self._parse_row_format(match_row=True),
 3616                )
 3617            )
 3618        else:
 3619            if self._match_set((TokenType.FIRST, TokenType.ALL)):
 3620                comments += ensure_list(self._prev_comments)
 3621                return self._parse_multitable_inserts(comments)
 3622
 3623            if self._match(TokenType.OR):
 3624                alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text
 3625
 3626            self._match(TokenType.INTO)
 3627            comments += ensure_list(self._prev_comments)
 3628            self._match(TokenType.TABLE)
 3629            is_function = self._match(TokenType.FUNCTION)
 3630
 3631            this = self._parse_function() if is_function else self._parse_insert_table()
 3632
 3633        # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant
 3634        set_values = None
 3635        if self._match(TokenType.SET):
 3636            columns = []
 3637            values = []
 3638
 3639            def _parse_set_assignment() -> exp.Expr | None:
 3640                target = self._parse_column()
 3641                if isinstance(target, exp.Column) and self._match(TokenType.EQ):
 3642                    if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3643                        value: exp.Expr | None = exp.var(self._prev.text.upper())
 3644                    else:
 3645                        value = self._parse_disjunction()
 3646
 3647                    if value:
 3648                        columns.append(target.this)
 3649                        values.append(value)
 3650                        return value
 3651
 3652                self.raise_error("Expected column assignment in INSERT ... SET")
 3653                return None
 3654
 3655            self._parse_csv(_parse_set_assignment)
 3656
 3657            this = self.expression(exp.Schema(this=this, expressions=columns))
 3658            set_values = self.expression(
 3659                exp.Values(
 3660                    expressions=[exp.Tuple(expressions=values)],
 3661                    alias=self._parse_table_alias(),
 3662                )
 3663            )
 3664
 3665        returning = self._parse_returning()  # TSQL allows RETURNING before source
 3666
 3667        stored = self._match_text_seq("STORED") and self._parse_stored()
 3668        by_name = self._match_text_seq("BY", "NAME")
 3669        exists = self._parse_exists()
 3670        replace_where = None
 3671        replace_using = None
 3672
 3673        if self._match(TokenType.REPLACE):
 3674            if self._match(TokenType.WHERE):
 3675                replace_where = self._parse_disjunction()
 3676            elif self._match(TokenType.USING):
 3677                replace_using = self._parse_using_identifiers()
 3678
 3679        return self.expression(
 3680            exp.Insert(
 3681                hint=hint,
 3682                is_function=is_function,
 3683                this=this,
 3684                stored=stored,
 3685                by_name=by_name,
 3686                exists=exists,
 3687                where=replace_where,
 3688                using=replace_using,
 3689                partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(),
 3690                settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(),
 3691                default=self._match_text_seq("DEFAULT", "VALUES"),
 3692                expression=set_values
 3693                or self._parse_derived_table_values()
 3694                or self._parse_ddl_select(),
 3695                conflict=self._parse_on_conflict(),
 3696                returning=returning or self._parse_returning(),
 3697                overwrite=overwrite,
 3698                alternative=alternative,
 3699                ignore=ignore,
 3700                source=self._match(TokenType.TABLE) and self._parse_table(),
 3701            ),
 3702            comments=comments,
 3703        )
 3704
 3705    def _parse_insert_table(self) -> exp.Expr | None:
 3706        this = self._parse_table(schema=True, parse_partition=True)
 3707        if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False):
 3708            this.set("alias", self._parse_table_alias())
 3709        return this
 3710
 3711    def _parse_kill(self) -> exp.Kill:
 3712        kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None
 3713
 3714        return self.expression(exp.Kill(this=self._parse_primary(), kind=kind))
 3715
 3716    def _parse_on_conflict(self) -> exp.OnConflict | None:
 3717        conflict = self._match_text_seq("ON", "CONFLICT")
 3718        duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY")
 3719
 3720        if not conflict and not duplicate:
 3721            return None
 3722
 3723        conflict_keys = None
 3724        constraint = None
 3725
 3726        if conflict:
 3727            if self._match_text_seq("ON", "CONSTRAINT"):
 3728                constraint = self._parse_id_var()
 3729            elif self._match(TokenType.L_PAREN):
 3730                conflict_keys = self._parse_csv(self._parse_indexed_column)
 3731                self._match_r_paren()
 3732
 3733        index_predicate = self._parse_where()
 3734
 3735        action = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 3736        if self._prev.token_type == TokenType.UPDATE:
 3737            self._match(TokenType.SET)
 3738            expressions = self._parse_csv(self._parse_equality)
 3739        else:
 3740            expressions = None
 3741
 3742        return self.expression(
 3743            exp.OnConflict(
 3744                duplicate=duplicate,
 3745                expressions=expressions,
 3746                action=action,
 3747                conflict_keys=conflict_keys,
 3748                index_predicate=index_predicate,
 3749                constraint=constraint,
 3750                where=self._parse_where(),
 3751            )
 3752        )
 3753
 3754    def _parse_returning(self) -> exp.Returning | None:
 3755        if not self._match(TokenType.RETURNING):
 3756            return None
 3757        return self.expression(
 3758            exp.Returning(
 3759                expressions=self._parse_csv(self._parse_expression),
 3760                into=self._match(TokenType.INTO) and self._parse_table_part(),
 3761            )
 3762        )
 3763
 3764    def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3765        if not self._match(TokenType.FORMAT):
 3766            return None
 3767        return self._parse_row_format()
 3768
 3769    def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None:
 3770        index = self._index
 3771        with_ = with_ or self._match_text_seq("WITH")
 3772
 3773        if not self._match(TokenType.SERDE_PROPERTIES):
 3774            self._retreat(index)
 3775            return None
 3776        return self.expression(
 3777            exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_)
 3778        )
 3779
 3780    def _parse_row_format(
 3781        self, match_row: bool = False
 3782    ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3783        if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT):
 3784            return None
 3785
 3786        if self._match_text_seq("SERDE"):
 3787            this = self._parse_string()
 3788
 3789            serde_properties = self._parse_serde_properties()
 3790
 3791            return self.expression(
 3792                exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties)
 3793            )
 3794
 3795        self._match_text_seq("DELIMITED")
 3796
 3797        kwargs = {}
 3798
 3799        if self._match_text_seq("FIELDS", "TERMINATED", "BY"):
 3800            kwargs["fields"] = self._parse_string()
 3801            if self._match_text_seq("ESCAPED", "BY"):
 3802                kwargs["escaped"] = self._parse_string()
 3803        if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"):
 3804            kwargs["collection_items"] = self._parse_string()
 3805        if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"):
 3806            kwargs["map_keys"] = self._parse_string()
 3807        if self._match_text_seq("LINES", "TERMINATED", "BY"):
 3808            kwargs["lines"] = self._parse_string()
 3809        if self._match_text_seq("NULL", "DEFINED", "AS"):
 3810            kwargs["null"] = self._parse_string()
 3811
 3812        return self.expression(exp.RowFormatDelimitedProperty(**kwargs))  # type: ignore
 3813
 3814    def _parse_load(self) -> exp.LoadData | exp.Command:
 3815        if self._match_text_seq("DATA"):
 3816            local = self._match_text_seq("LOCAL")
 3817            self._match_text_seq("INPATH")
 3818            inpath = self._parse_string()
 3819            overwrite = self._match(TokenType.OVERWRITE)
 3820            temp: bool | None = None
 3821            if self._match(TokenType.INTO):
 3822                temp = self._match(TokenType.TEMPORARY)
 3823                self._match(TokenType.TABLE)
 3824
 3825            return self.expression(
 3826                exp.LoadData(
 3827                    this=self._parse_table(schema=True),
 3828                    local=local,
 3829                    overwrite=overwrite,
 3830                    temp=temp,
 3831                    inpath=inpath,
 3832                    files=self._match_text_seq("FROM", "FILES")
 3833                    and exp.Properties(expressions=self._parse_wrapped_properties()),
 3834                    partition=self._parse_partition(),
 3835                    input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(),
 3836                    serde=self._match_text_seq("SERDE") and self._parse_string(),
 3837                )
 3838            )
 3839        return self._parse_as_command(self._prev)
 3840
 3841    def _parse_delete(self) -> exp.Delete:
 3842        hint = self._parse_hint()
 3843
 3844        # This handles MySQL's "Multiple-Table Syntax"
 3845        # https://dev.mysql.com/doc/refman/8.0/en/delete.html
 3846        tables = None
 3847        if not self._match(TokenType.FROM, advance=False):
 3848            tables = self._parse_csv(self._parse_table) or None
 3849
 3850        returning = self._parse_returning()
 3851
 3852        return self.expression(
 3853            exp.Delete(
 3854                hint=hint,
 3855                tables=tables,
 3856                this=self._match(TokenType.FROM) and self._parse_table(joins=True),
 3857                using=self._match(TokenType.USING)
 3858                and self._parse_csv(lambda: self._parse_table(joins=True)),
 3859                cluster=self._match(TokenType.ON) and self._parse_on_property(),
 3860                where=self._parse_where(),
 3861                returning=returning or self._parse_returning(),
 3862                order=self._parse_order(),
 3863                limit=self._parse_limit(),
 3864            )
 3865        )
 3866
 3867    def _parse_update(self) -> exp.Update:
 3868        hint = self._parse_hint()
 3869        kwargs: dict[str, object] = {
 3870            "hint": hint,
 3871            "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS),
 3872        }
 3873        while self._curr:
 3874            if self._match(TokenType.SET):
 3875                kwargs["expressions"] = self._parse_csv(self._parse_equality)
 3876            elif self._match(TokenType.RETURNING, advance=False):
 3877                kwargs["returning"] = self._parse_returning()
 3878            elif self._match(TokenType.FROM, advance=False):
 3879                from_ = self._parse_from(joins=True)
 3880                table = from_.this if from_ else None
 3881                if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False):
 3882                    table.set("joins", list(self._parse_joins()) or None)
 3883
 3884                kwargs["from_"] = from_
 3885            elif self._match(TokenType.WHERE, advance=False):
 3886                kwargs["where"] = self._parse_where()
 3887            elif self._match(TokenType.ORDER_BY, advance=False):
 3888                kwargs["order"] = self._parse_order()
 3889            elif self._match(TokenType.LIMIT, advance=False):
 3890                kwargs["limit"] = self._parse_limit()
 3891            else:
 3892                break
 3893
 3894        return self.expression(exp.Update(**kwargs))
 3895
 3896    def _parse_use(self) -> exp.Use:
 3897        return self.expression(
 3898            exp.Use(
 3899                kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False),
 3900                this=self._parse_table(schema=False),
 3901            )
 3902        )
 3903
 3904    def _parse_uncache(self) -> exp.Uncache:
 3905        if not self._match(TokenType.TABLE):
 3906            self.raise_error("Expecting TABLE after UNCACHE")
 3907
 3908        return self.expression(
 3909            exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True))
 3910        )
 3911
 3912    def _parse_cache(self) -> exp.Cache:
 3913        lazy = self._match_text_seq("LAZY")
 3914        self._match(TokenType.TABLE)
 3915        table = self._parse_table(schema=True)
 3916
 3917        options = []
 3918        if self._match_text_seq("OPTIONS"):
 3919            self._match_l_paren()
 3920            k = self._parse_string()
 3921            self._match(TokenType.EQ)
 3922            v = self._parse_string()
 3923            options = [k, v]
 3924            self._match_r_paren()
 3925
 3926        self._match(TokenType.ALIAS)
 3927        return self.expression(
 3928            exp.Cache(
 3929                this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True)
 3930            )
 3931        )
 3932
 3933    def _parse_partition(self) -> exp.Partition | None:
 3934        if not self._match_texts(self.PARTITION_KEYWORDS):
 3935            return None
 3936
 3937        return self.expression(
 3938            exp.Partition(
 3939                subpartition=self._prev.text.upper() == "SUBPARTITION",
 3940                expressions=self._parse_wrapped_csv(self._parse_disjunction),
 3941            )
 3942        )
 3943
 3944    def _parse_value(self, values: bool = True) -> exp.Tuple | None:
 3945        def _parse_value_expression() -> exp.Expr | None:
 3946            if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3947                return exp.var(self._prev.text.upper())
 3948            return self._parse_expression()
 3949
 3950        if self._match(TokenType.L_PAREN):
 3951            expressions = self._parse_csv(_parse_value_expression)
 3952            self._match_r_paren()
 3953            return self.expression(exp.Tuple(expressions=expressions))
 3954
 3955        # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows.
 3956        expression = self._parse_expression()
 3957        if expression:
 3958            return self.expression(exp.Tuple(expressions=[expression]))
 3959        return None
 3960
 3961    def _parse_projections(
 3962        self,
 3963    ) -> tuple[list[exp.Expr], list[exp.Expr] | None]:
 3964        return self._parse_expressions(), None
 3965
 3966    def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None:
 3967        if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)):
 3968            this: exp.Expr | None = self._parse_simplified_pivot(
 3969                is_unpivot=self._prev.token_type == TokenType.UNPIVOT
 3970            )
 3971        elif self._match(TokenType.FROM):
 3972            from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True)
 3973            # Support parentheses for duckdb FROM-first syntax
 3974            select = self._parse_select(from_=from_)
 3975            if select:
 3976                if not select.args.get("from_"):
 3977                    select.set("from_", from_)
 3978                this = select
 3979            else:
 3980                this = exp.select("*").from_(t.cast(exp.From, from_))
 3981                this = self._parse_query_modifiers(self._parse_set_operations(this))
 3982        else:
 3983            this = (
 3984                self._parse_table(consume_pipe=True)
 3985                if table
 3986                else self._parse_select(nested=True, parse_set_operation=False)
 3987            )
 3988
 3989            # Transform exp.Values into a exp.Table to pass through parse_query_modifiers
 3990            # in case a modifier (e.g. join) is following
 3991            if table and isinstance(this, exp.Values) and this.alias:
 3992                alias = this.args["alias"].pop()
 3993                this = exp.Table(this=this, alias=alias)
 3994
 3995            this = self._parse_query_modifiers(self._parse_set_operations(this))
 3996
 3997        return this
 3998
 3999    def _parse_select(
 4000        self,
 4001        nested: bool = False,
 4002        table: bool = False,
 4003        parse_subquery_alias: bool = True,
 4004        parse_set_operation: bool = True,
 4005        consume_pipe: bool = True,
 4006        from_: exp.From | None = None,
 4007    ) -> exp.Expr | None:
 4008        query = self._parse_select_query(
 4009            nested=nested,
 4010            table=table,
 4011            parse_subquery_alias=parse_subquery_alias,
 4012            parse_set_operation=parse_set_operation,
 4013        )
 4014
 4015        if consume_pipe and self._match(TokenType.PIPE_GT, advance=False):
 4016            if not query and from_:
 4017                query = exp.select("*").from_(from_)
 4018            if isinstance(query, exp.Query):
 4019                query = self._parse_pipe_syntax_query(query)
 4020                query = query.subquery(copy=False) if query and table else query
 4021
 4022        return query
 4023
 4024    def _parse_select_query(
 4025        self,
 4026        nested: bool = False,
 4027        table: bool = False,
 4028        parse_subquery_alias: bool = True,
 4029        parse_set_operation: bool = True,
 4030    ) -> exp.Expr | None:
 4031        cte = self._parse_with()
 4032
 4033        if cte:
 4034            this = self._parse_statement()
 4035
 4036            if not this:
 4037                self.raise_error("Failed to parse any statement following CTE")
 4038                return cte
 4039
 4040            while isinstance(this, exp.Subquery) and this.is_wrapper:
 4041                this = this.this
 4042
 4043            assert this is not None
 4044            if "with_" in this.arg_types:
 4045                if inner_cte := this.args.get("with_"):
 4046                    cte.set("expressions", cte.expressions + inner_cte.expressions)
 4047                    if inner_cte.args.get("recursive"):
 4048                        cte.set("recursive", True)
 4049                this.set("with_", cte)
 4050            else:
 4051                self.raise_error(f"{this.key} does not support CTE")
 4052                this = cte
 4053
 4054            return this
 4055
 4056        # duckdb supports leading with FROM x
 4057        from_ = (
 4058            self._parse_from(joins=True, consume_pipe=True)
 4059            if self._match(TokenType.FROM, advance=False)
 4060            else None
 4061        )
 4062
 4063        if self._match(TokenType.SELECT):
 4064            comments = self._prev_comments
 4065
 4066            hint = self._parse_hint()
 4067
 4068            if self._next and not self._next.token_type == TokenType.DOT:
 4069                all_ = self._match(TokenType.ALL)
 4070                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4071            else:
 4072                all_, matched_distinct = None, False
 4073
 4074            kind = (
 4075                self._prev.text.upper()
 4076                if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE"))
 4077                else None
 4078            )
 4079
 4080            distinct: exp.Expr | None = (
 4081                self.expression(
 4082                    exp.Distinct(
 4083                        on=self._parse_value(values=False) if self._match(TokenType.ON) else None
 4084                    )
 4085                )
 4086                if matched_distinct
 4087                else None
 4088            )
 4089
 4090            operation_modifiers = []
 4091            while self._curr and self._match_texts(self.OPERATION_MODIFIERS):
 4092                operation_modifiers.append(exp.var(self._prev.text.upper()))
 4093
 4094            limit = self._parse_limit(top=True)
 4095
 4096            # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ...
 4097            if limit and not matched_distinct and not all_:
 4098                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4099                if matched_distinct:
 4100                    distinct = self.expression(
 4101                        exp.Distinct(
 4102                            on=self._parse_value(values=False)
 4103                            if self._match(TokenType.ON)
 4104                            else None
 4105                        )
 4106                    )
 4107                else:
 4108                    all_ = self._match(TokenType.ALL)
 4109
 4110            if all_ and distinct:
 4111                self.raise_error("Cannot specify both ALL and DISTINCT after SELECT")
 4112
 4113            projections, exclude = self._parse_projections()
 4114
 4115            this = self.expression(
 4116                exp.Select(
 4117                    kind=kind,
 4118                    hint=hint,
 4119                    distinct=distinct,
 4120                    expressions=projections,
 4121                    limit=limit,
 4122                    exclude=exclude,
 4123                    operation_modifiers=operation_modifiers or None,
 4124                )
 4125            )
 4126            this.comments = comments
 4127
 4128            into = self._parse_into()
 4129            if into:
 4130                this.set("into", into)
 4131
 4132            if not from_:
 4133                from_ = self._parse_from()
 4134
 4135            if from_:
 4136                this.set("from_", from_)
 4137
 4138            this = self._parse_query_modifiers(this)
 4139        elif (table or nested) and self._match(TokenType.L_PAREN):
 4140            comments = self._prev_comments
 4141            this = self._parse_wrapped_select(table=table)
 4142
 4143            if this:
 4144                this.add_comments(comments, prepend=True)
 4145
 4146            # We return early here so that the UNION isn't attached to the subquery by the
 4147            # following call to _parse_set_operations, but instead becomes the parent node
 4148            self._match_r_paren()
 4149            return self._parse_subquery(this, parse_alias=parse_subquery_alias)
 4150        elif self._match(TokenType.VALUES, advance=False):
 4151            this = self._parse_derived_table_values()
 4152        elif from_:
 4153            this = exp.select("*").from_(from_.this, copy=False)
 4154            this = self._parse_query_modifiers(this)
 4155        elif self._match(TokenType.SUMMARIZE):
 4156            table = self._match(TokenType.TABLE)
 4157            this = self._parse_select() or self._parse_string() or self._parse_table()
 4158            return self.expression(exp.Summarize(this=this, table=table))
 4159        elif self._match(TokenType.DESCRIBE):
 4160            this = self._parse_describe()
 4161        else:
 4162            this = None
 4163
 4164        return self._parse_set_operations(this) if parse_set_operation else this
 4165
 4166    def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None:
 4167        self._match_text_seq("SEARCH")
 4168
 4169        kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper()
 4170
 4171        if not kind:
 4172            return None
 4173
 4174        self._match_text_seq("FIRST", "BY")
 4175
 4176        return self.expression(
 4177            exp.RecursiveWithSearch(
 4178                kind=kind,
 4179                this=self._parse_id_var(),
 4180                expression=self._match_text_seq("SET") and self._parse_id_var(),
 4181                using=self._match_text_seq("USING") and self._parse_id_var(),
 4182            )
 4183        )
 4184
 4185    def _parse_with(self, skip_with_token: bool = False) -> exp.With | None:
 4186        if not skip_with_token and not self._match(TokenType.WITH):
 4187            return None
 4188
 4189        comments = self._prev_comments
 4190        recursive = self._match(TokenType.RECURSIVE)
 4191
 4192        last_comments = None
 4193        expressions = []
 4194        udfs = []
 4195        while True:
 4196            cte = self._parse_cte()
 4197            if cte:
 4198                if isinstance(cte, exp.FunctionSpecification):
 4199                    udfs.append(cte)
 4200                else:
 4201                    expressions.append(cte)
 4202
 4203                if last_comments:
 4204                    cte.add_comments(last_comments)
 4205
 4206            if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH):
 4207                break
 4208            else:
 4209                self._match(TokenType.WITH)
 4210                recursive = self._match(TokenType.RECURSIVE) or recursive
 4211
 4212            last_comments = self._prev_comments
 4213
 4214        return self.expression(
 4215            exp.With(
 4216                expressions=expressions,
 4217                recursive=recursive or None,
 4218                search=self._parse_recursive_with_search(),
 4219                udfs=udfs or None,
 4220            ),
 4221            comments=comments,
 4222        )
 4223
 4224    def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None:
 4225        index = self._index
 4226
 4227        alias = self._parse_table_alias(self.ID_VAR_TOKENS)
 4228        if not alias or not alias.this:
 4229            self.raise_error("Expected CTE to have alias")
 4230
 4231        key_expressions = (
 4232            self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None
 4233        )
 4234
 4235        if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE:
 4236            self._retreat(index)
 4237            return None
 4238
 4239        comments = self._prev_comments
 4240
 4241        if self._match_text_seq("NOT", "MATERIALIZED"):
 4242            materialized = False
 4243        elif self._match_text_seq("MATERIALIZED"):
 4244            materialized = True
 4245        else:
 4246            materialized = None
 4247
 4248        cte = self.expression(
 4249            exp.CTE(
 4250                this=self._parse_wrapped(self._parse_statement),
 4251                alias=alias,
 4252                materialized=materialized,
 4253                key_expressions=key_expressions,
 4254            ),
 4255            comments=comments,
 4256        )
 4257
 4258        values = cte.this
 4259        if isinstance(values, exp.Values):
 4260            cte.set("this", self._values_to_select(values))
 4261
 4262        return cte
 4263
 4264    def _values_to_select(self, values: exp.Values) -> exp.Select:
 4265        if values.alias:
 4266            return exp.select("*").from_(values)
 4267        return exp.select("*").from_(exp.alias_(values, "_values", table=True))
 4268
 4269    def _parse_table_alias(
 4270        self, alias_tokens: t.Collection[TokenType] | None = None
 4271    ) -> exp.TableAlias | None:
 4272        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 4273        # so this section tries to parse the clause version and if it fails, it treats the token
 4274        # as an identifier (alias)
 4275        if self._can_parse_limit_or_offset():
 4276            return None
 4277
 4278        # START is never treated as an implicit alias when followed by WITH, since that
 4279        # would swallow the beginning of a START WITH ... CONNECT BY clause
 4280        if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH":
 4281            return None
 4282
 4283        any_token = self._match(TokenType.ALIAS)
 4284        alias = (
 4285            self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 4286            or self._parse_string_as_identifier()
 4287        )
 4288
 4289        index = self._index
 4290        if self._match(TokenType.L_PAREN):
 4291            columns = self._parse_csv(self._parse_function_parameter)
 4292            self._match_r_paren() if columns else self._retreat(index)
 4293        else:
 4294            columns = None
 4295
 4296        if not alias and not columns:
 4297            return None
 4298
 4299        table_alias = self.expression(exp.TableAlias(this=alias, columns=columns))
 4300
 4301        # We bubble up comments from the Identifier to the TableAlias
 4302        if isinstance(alias, exp.Identifier):
 4303            table_alias.add_comments(alias.pop_comments())
 4304
 4305        return table_alias
 4306
 4307    def _parse_subquery(
 4308        self, this: exp.Expr | None, parse_alias: bool = True
 4309    ) -> exp.Subquery | None:
 4310        if not this:
 4311            return None
 4312
 4313        return self.expression(
 4314            exp.Subquery(
 4315                this=this,
 4316                pivots=self._parse_pivots(),
 4317                alias=self._parse_table_alias() if parse_alias else None,
 4318                sample=self._parse_table_sample(),
 4319            )
 4320        )
 4321
 4322    def _implicit_unnests_to_explicit(self, this: E) -> E:
 4323        from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm
 4324
 4325        refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name}
 4326        for i, join in enumerate(this.args.get("joins") or []):
 4327            table = join.this
 4328            normalized_table = table.copy()
 4329            normalized_table.meta["maybe_column"] = True
 4330            normalized_table = _norm(normalized_table, dialect=self.dialect)
 4331
 4332            if isinstance(table, exp.Table) and not join.args.get("on"):
 4333                if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs:
 4334                    table_as_column = table.to_column()
 4335                    unnest = exp.Unnest(expressions=[table_as_column])
 4336
 4337                    # Table.to_column creates a parent Alias node that we want to convert to
 4338                    # a TableAlias and attach to the Unnest, so it matches the parser's output
 4339                    if isinstance(table.args.get("alias"), exp.TableAlias):
 4340                        table_as_column.replace(table_as_column.this)
 4341                        exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False)
 4342
 4343                    table.replace(unnest)
 4344
 4345            refs.add(normalized_table.alias_or_name)
 4346
 4347        return this
 4348
 4349    @t.overload
 4350    def _parse_query_modifiers(self, this: E) -> E: ...
 4351
 4352    @t.overload
 4353    def _parse_query_modifiers(self, this: None) -> None: ...
 4354
 4355    def _parse_query_modifiers(self, this):
 4356        if isinstance(this, self.MODIFIABLES):
 4357            for join in self._parse_joins():
 4358                this.append("joins", join)
 4359            for lateral in iter(self._parse_lateral, None):
 4360                this.append("laterals", lateral)
 4361
 4362            while True:
 4363                if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):
 4364                    modifier_token = self._curr
 4365                    parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type]
 4366                    key, expression = parser(self)
 4367
 4368                    if expression:
 4369                        if this.args.get(key):
 4370                            self.raise_error(
 4371                                f"Found multiple '{modifier_token.text.upper()}' clauses",
 4372                                token=modifier_token,
 4373                            )
 4374
 4375                        this.set(key, expression)
 4376                        if key == "limit":
 4377                            offset = expression.args.get("offset")
 4378                            expression.set("offset", None)
 4379
 4380                            if offset:
 4381                                offset = exp.Offset(expression=offset)
 4382                                this.set("offset", offset)
 4383
 4384                                limit_by_expressions = expression.expressions
 4385                                expression.set("expressions", None)
 4386                                offset.set("expressions", limit_by_expressions)
 4387                        continue
 4388
 4389                if self._curr.text.upper() == "START":
 4390                    modifier_token = self._curr
 4391                    connect = self._parse_connect()
 4392                    if connect:
 4393                        if this.args.get("connect"):
 4394                            self.raise_error(
 4395                                "Found multiple 'START WITH' clauses", token=modifier_token
 4396                            )
 4397
 4398                        this.set("connect", connect)
 4399                        continue
 4400                break
 4401
 4402        if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"):
 4403            this = self._implicit_unnests_to_explicit(this)
 4404
 4405        return this
 4406
 4407    def _parse_hint_fallback_to_string(self) -> exp.Hint | None:
 4408        start = self._curr
 4409        while self._curr:
 4410            self._advance()
 4411
 4412        end = self._tokens[self._index - 1]
 4413        return exp.Hint(expressions=[self._find_sql(start, end)])
 4414
 4415    def _parse_hint_function_call(self) -> exp.Expr | None:
 4416        return self._parse_function_call()
 4417
 4418    def _parse_hint_body(self) -> exp.Hint | None:
 4419        start_index = self._index
 4420        should_fallback_to_string = False
 4421
 4422        hints = []
 4423        try:
 4424            for hint in iter(
 4425                lambda: self._parse_csv(
 4426                    lambda: self._parse_hint_function_call() or self._parse_var(upper=True),
 4427                ),
 4428                [],
 4429            ):
 4430                hints.extend(hint)
 4431        except ParseError:
 4432            should_fallback_to_string = True
 4433
 4434        if should_fallback_to_string or self._curr:
 4435            self._retreat(start_index)
 4436            return self._parse_hint_fallback_to_string()
 4437
 4438        return self.expression(exp.Hint(expressions=hints))
 4439
 4440    def _parse_hint(self) -> exp.Hint | None:
 4441        if self._match(TokenType.HINT) and self._prev_comments:
 4442            return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect)
 4443
 4444        return None
 4445
 4446    def _parse_into(self) -> exp.Into | None:
 4447        if not self._match(TokenType.INTO):
 4448            return None
 4449
 4450        temp = self._match(TokenType.TEMPORARY)
 4451        unlogged = self._match_text_seq("UNLOGGED")
 4452        self._match(TokenType.TABLE)
 4453
 4454        return self.expression(
 4455            exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged)
 4456        )
 4457
 4458    def _parse_from(
 4459        self,
 4460        joins: bool = False,
 4461        skip_from_token: bool = False,
 4462        consume_pipe: bool = False,
 4463    ) -> exp.From | None:
 4464        if not skip_from_token and not self._match(TokenType.FROM):
 4465            return None
 4466
 4467        comments = self._prev_comments
 4468        return self.expression(
 4469            exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)),
 4470            comments=comments,
 4471        )
 4472
 4473    def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure:
 4474        return self.expression(
 4475            exp.MatchRecognizeMeasure(
 4476                window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(),
 4477                this=self._parse_expression(),
 4478            )
 4479        )
 4480
 4481    def _parse_match_recognize(self) -> exp.MatchRecognize | None:
 4482        if not self._match(TokenType.MATCH_RECOGNIZE):
 4483            return None
 4484
 4485        self._match_l_paren()
 4486
 4487        partition = self._parse_partition_by()
 4488        order = self._parse_order()
 4489
 4490        measures = (
 4491            self._parse_csv(self._parse_match_recognize_measure)
 4492            if self._match_text_seq("MEASURES")
 4493            else None
 4494        )
 4495
 4496        if self._match_text_seq("ONE", "ROW", "PER", "MATCH"):
 4497            rows = exp.var("ONE ROW PER MATCH")
 4498        elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"):
 4499            text = "ALL ROWS PER MATCH"
 4500            if self._match_text_seq("SHOW", "EMPTY", "MATCHES"):
 4501                text += " SHOW EMPTY MATCHES"
 4502            elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"):
 4503                text += " OMIT EMPTY MATCHES"
 4504            elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"):
 4505                text += " WITH UNMATCHED ROWS"
 4506            rows = exp.var(text)
 4507        else:
 4508            rows = None
 4509
 4510        if self._match_text_seq("AFTER", "MATCH", "SKIP"):
 4511            text = "AFTER MATCH SKIP"
 4512            if self._match_text_seq("PAST", "LAST", "ROW"):
 4513                text += " PAST LAST ROW"
 4514            elif self._match_text_seq("TO", "NEXT", "ROW"):
 4515                text += " TO NEXT ROW"
 4516            elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"):
 4517                direction = self._prev.text.upper()
 4518                pattern_var = self._advance_any()
 4519                if not pattern_var:
 4520                    self.raise_error(
 4521                        f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}"
 4522                    )
 4523                text += f" TO {direction} {pattern_var.text if pattern_var else ''}"
 4524            after = exp.var(text)
 4525        else:
 4526            after = None
 4527
 4528        if self._match_text_seq("PATTERN"):
 4529            self._match_l_paren()
 4530
 4531            if not self._curr:
 4532                self.raise_error("Expecting )", self._curr)
 4533
 4534            paren = 1
 4535            start = self._curr
 4536
 4537            while self._curr and paren > 0:
 4538                if self._curr.token_type == TokenType.L_PAREN:
 4539                    paren += 1
 4540                if self._curr.token_type == TokenType.R_PAREN:
 4541                    paren -= 1
 4542
 4543                end = self._prev
 4544                self._advance()
 4545
 4546            if paren > 0:
 4547                self.raise_error("Expecting )", self._curr)
 4548
 4549            pattern = exp.var(self._find_sql(start, end))
 4550        else:
 4551            pattern = None
 4552
 4553        define = (
 4554            self._parse_csv(self._parse_name_as_expression)
 4555            if self._match_text_seq("DEFINE")
 4556            else None
 4557        )
 4558
 4559        self._match_r_paren()
 4560
 4561        return self.expression(
 4562            exp.MatchRecognize(
 4563                partition_by=partition,
 4564                order=order,
 4565                measures=measures,
 4566                rows=rows,
 4567                after=after,
 4568                pattern=pattern,
 4569                define=define,
 4570                alias=self._parse_table_alias(),
 4571            )
 4572        )
 4573
 4574    def _parse_lateral(self) -> exp.Lateral | None:
 4575        cross_apply: bool | None = None
 4576        if self._match_pair(TokenType.CROSS, TokenType.APPLY):
 4577            cross_apply = True
 4578        elif self._match_pair(TokenType.OUTER, TokenType.APPLY):
 4579            cross_apply = False
 4580
 4581        if cross_apply is not None:
 4582            this = self._parse_select(table=True)
 4583            view = None
 4584            outer = None
 4585        elif self._match(TokenType.LATERAL):
 4586            this = self._parse_select(table=True)
 4587            view = self._match(TokenType.VIEW)
 4588            outer = self._match(TokenType.OUTER)
 4589        else:
 4590            return None
 4591
 4592        if not this:
 4593            this = (
 4594                self._parse_unnest()
 4595                or self._parse_function()
 4596                or self._parse_id_var(any_token=False)
 4597            )
 4598
 4599            while self._match(TokenType.DOT):
 4600                this = exp.Dot(
 4601                    this=this,
 4602                    expression=self._parse_function() or self._parse_id_var(any_token=False),
 4603                )
 4604
 4605        ordinality: bool | None = None
 4606
 4607        if view:
 4608            table = self._parse_id_var(any_token=False)
 4609            columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else []
 4610            table_alias: exp.TableAlias | None = self.expression(
 4611                exp.TableAlias(this=table, columns=columns)
 4612            )
 4613        elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias:
 4614            # We move the alias from the lateral's child node to the lateral itself
 4615            table_alias = this.args["alias"].pop()
 4616        else:
 4617            ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 4618            table_alias = self._parse_table_alias()
 4619
 4620        return self.expression(
 4621            exp.Lateral(
 4622                this=this,
 4623                view=view,
 4624                outer=outer,
 4625                alias=table_alias,
 4626                cross_apply=cross_apply,
 4627                ordinality=ordinality,
 4628            )
 4629        )
 4630
 4631    def _parse_stream(self) -> exp.Stream | None:
 4632        index = self._index
 4633        if self._match(TokenType.STREAM):
 4634            if this := self._try_parse(self._parse_table):
 4635                return self.expression(exp.Stream(this=this))
 4636            self._retreat(index)
 4637        return None
 4638
 4639    def _parse_join_parts(
 4640        self,
 4641    ) -> tuple[Token | None, Token | None, Token | None]:
 4642        return (
 4643            self._prev if self._match_set(self.JOIN_METHODS) else None,
 4644            self._prev if self._match_set(self.JOIN_SIDES) else None,
 4645            self._prev if self._match_set(self.JOIN_KINDS) else None,
 4646        )
 4647
 4648    def _parse_using_identifiers(self) -> list[exp.Expr]:
 4649        def _parse_column_as_identifier() -> exp.Expr | None:
 4650            this = self._parse_column()
 4651            if isinstance(this, exp.Column):
 4652                return this.this
 4653            return this
 4654
 4655        return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True)
 4656
 4657    def _parse_join(
 4658        self,
 4659        skip_join_token: bool = False,
 4660        parse_bracket: bool = False,
 4661        alias_tokens: t.Collection[TokenType] | None = None,
 4662    ) -> exp.Join | None:
 4663        if self._match(TokenType.COMMA):
 4664            table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens))
 4665            cross_join = self.expression(exp.Join(this=table)) if table else None
 4666
 4667            if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE:
 4668                cross_join.set("kind", "CROSS")
 4669
 4670            return cross_join
 4671
 4672        index = self._index
 4673        method, side, kind = self._parse_join_parts()
 4674        directed = self._match_text_seq("DIRECTED")
 4675        hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None
 4676        join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN)
 4677        join_comments = self._prev_comments
 4678
 4679        if not skip_join_token and not join:
 4680            self._retreat(index)
 4681            kind = None
 4682            method = None
 4683            side = None
 4684
 4685        outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False)
 4686        cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False)
 4687
 4688        if not skip_join_token and not join and not outer_apply and not cross_apply:
 4689            return None
 4690
 4691        kwargs: dict[str, t.Any] = {
 4692            "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4693        }
 4694        if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA):
 4695            kwargs["expressions"] = self._parse_csv(
 4696                lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4697            )
 4698
 4699        if method:
 4700            kwargs["method"] = method.text.upper()
 4701        if side:
 4702            kwargs["side"] = side.text.upper()
 4703        if kind:
 4704            kwargs["kind"] = kind.text.upper()
 4705        if hint:
 4706            kwargs["hint"] = hint
 4707
 4708        if self._match(TokenType.MATCH_CONDITION):
 4709            kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison)
 4710
 4711        if self._match(TokenType.ON):
 4712            kwargs["on"] = self._parse_disjunction()
 4713        elif self._match(TokenType.USING):
 4714            kwargs["using"] = self._parse_using_identifiers()
 4715        elif (
 4716            not method
 4717            and not (outer_apply or cross_apply)
 4718            and not isinstance(kwargs["this"], exp.Unnest)
 4719            and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY))
 4720        ):
 4721            index = self._index
 4722            joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens))
 4723
 4724            if joins and self._match(TokenType.ON):
 4725                kwargs["on"] = self._parse_disjunction()
 4726            elif joins and self._match(TokenType.USING):
 4727                kwargs["using"] = self._parse_using_identifiers()
 4728            else:
 4729                joins = None
 4730                self._retreat(index)
 4731
 4732            kwargs["this"].set("joins", joins if joins else None)
 4733
 4734        kwargs["pivots"] = self._parse_pivots()
 4735
 4736        comments = [c for token in (method, side, kind) if token for c in token.comments]
 4737        comments = (join_comments or []) + comments
 4738
 4739        if (
 4740            self.ADD_JOIN_ON_TRUE
 4741            and not kwargs.get("on")
 4742            and not kwargs.get("using")
 4743            and not kwargs.get("method")
 4744            and kwargs.get("kind") in (None, "INNER", "OUTER")
 4745        ):
 4746            kwargs["on"] = exp.true()
 4747
 4748        if directed:
 4749            kwargs["directed"] = directed
 4750
 4751        return self.expression(exp.Join(**kwargs), comments=comments)
 4752
 4753    def _parse_opclass(self) -> exp.Expr | None:
 4754        this = self._parse_disjunction()
 4755
 4756        if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False):
 4757            return this
 4758
 4759        if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False):
 4760            return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts()))
 4761
 4762        return this
 4763
 4764    def _parse_index_params(self) -> exp.IndexParameters:
 4765        using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None
 4766
 4767        if self._match(TokenType.L_PAREN, advance=False):
 4768            columns = self._parse_wrapped_csv(self._parse_with_operator)
 4769        else:
 4770            columns = None
 4771
 4772        include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None
 4773        partition_by = self._parse_partition_by()
 4774        with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties()
 4775        tablespace = (
 4776            self._parse_var(any_token=True)
 4777            if self._match_text_seq("USING", "INDEX", "TABLESPACE")
 4778            else None
 4779        )
 4780        where = self._parse_where()
 4781
 4782        on = self._parse_field() if self._match(TokenType.ON) else None
 4783
 4784        return self.expression(
 4785            exp.IndexParameters(
 4786                using=using,
 4787                columns=columns,
 4788                include=include,
 4789                partition_by=partition_by,
 4790                where=where,
 4791                with_storage=with_storage,
 4792                tablespace=tablespace,
 4793                on=on,
 4794            )
 4795        )
 4796
 4797    def _parse_index(
 4798        self, index: exp.Expr | None = None, anonymous: bool = False
 4799    ) -> exp.Index | None:
 4800        if index or anonymous:
 4801            unique = None
 4802            primary = None
 4803            amp = None
 4804
 4805            self._match(TokenType.ON)
 4806            self._match(TokenType.TABLE)  # hive
 4807            table = self._parse_table_parts(schema=True)
 4808        else:
 4809            unique = self._match(TokenType.UNIQUE)
 4810            primary = self._match_text_seq("PRIMARY")
 4811            amp = self._match_text_seq("AMP")
 4812
 4813            if not self._match(TokenType.INDEX):
 4814                return None
 4815
 4816            index = self._parse_id_var()
 4817            table = None
 4818
 4819        params = self._parse_index_params()
 4820
 4821        return self.expression(
 4822            exp.Index(
 4823                this=index, table=table, unique=unique, primary=primary, amp=amp, params=params
 4824            )
 4825        )
 4826
 4827    def _parse_table_hints(self) -> list[exp.Expr] | None:
 4828        hints: list[exp.Expr] = []
 4829        if self._match_pair(TokenType.WITH, TokenType.L_PAREN):
 4830            # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16
 4831            hints.append(
 4832                self.expression(
 4833                    exp.WithTableHint(
 4834                        expressions=self._parse_csv(
 4835                            lambda: self._parse_function() or self._parse_var(any_token=True)
 4836                        )
 4837                    )
 4838                )
 4839            )
 4840            self._match_r_paren()
 4841        else:
 4842            # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
 4843            while self._match_set(self.TABLE_INDEX_HINT_TOKENS):
 4844                hint = exp.IndexTableHint(this=self._prev.text.upper())
 4845
 4846                self._match_set((TokenType.INDEX, TokenType.KEY))
 4847                if self._match(TokenType.FOR):
 4848                    hint.set("target", self._advance_any() and self._prev.text.upper())
 4849
 4850                hint.set("expressions", self._parse_wrapped_id_vars())
 4851                hints.append(hint)
 4852
 4853        return hints or None
 4854
 4855    def _parse_table_part(self, schema: bool = False) -> exp.Expr | None:
 4856        return (
 4857            (not schema and self._parse_function(optional_parens=False))
 4858            or self._parse_id_var(any_token=False)
 4859            or self._parse_string_as_identifier()
 4860            or self._parse_placeholder()
 4861        )
 4862
 4863    def _parse_table_parts_fast(self) -> exp.Table | None:
 4864        index = self._index
 4865        parts: list[exp.Identifier] | None = None
 4866        all_comments: list[str] | None = None
 4867
 4868        while self._match_set(self.IDENTIFIER_TOKENS):
 4869            token = self._prev
 4870            comments = self._prev_comments
 4871
 4872            has_dot = self._match(TokenType.DOT)
 4873            curr_tt = self._curr.token_type
 4874
 4875            if not has_dot:
 4876                if curr_tt in self.TABLE_POSTFIX_TOKENS:
 4877                    self._retreat(index)
 4878                    return None
 4879            elif curr_tt not in self.IDENTIFIER_TOKENS:
 4880                self._retreat(index)
 4881                return None
 4882
 4883            if parts is None:
 4884                parts = []
 4885
 4886            if comments:
 4887                if all_comments is None:
 4888                    all_comments = []
 4889                all_comments.extend(comments)
 4890                self._prev_comments = []
 4891
 4892            parts.append(
 4893                self.expression(
 4894                    exp.Identifier(
 4895                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 4896                    ),
 4897                    token,
 4898                )
 4899            )
 4900
 4901            if not has_dot:
 4902                break
 4903
 4904        if parts is None:
 4905            return None
 4906
 4907        n = len(parts)
 4908
 4909        if n == 1:
 4910            table: exp.Table = exp.Table(this=parts[0])
 4911        elif n == 2:
 4912            table = exp.Table(this=parts[1], db=parts[0])
 4913        elif n >= 3:
 4914            this: exp.Identifier | exp.Dot = parts[2]
 4915            for i in range(3, n):
 4916                this = exp.Dot(this=this, expression=parts[i])
 4917
 4918            table = exp.Table(this=this, db=parts[1], catalog=parts[0])
 4919
 4920        if table is None:
 4921            self._retreat(index)
 4922        elif all_comments:
 4923            table.add_comments(all_comments)
 4924        return table
 4925
 4926    def _parse_table_parts(
 4927        self,
 4928        schema: bool = False,
 4929        is_db_reference: bool = False,
 4930        wildcard: bool = False,
 4931        fast: bool = False,
 4932    ) -> exp.Table | exp.Dot | None:
 4933        if fast:
 4934            return self._parse_table_parts_fast()
 4935
 4936        catalog: exp.Expr | str | None = None
 4937        db: exp.Expr | str | None = None
 4938        table: exp.Expr | str | None = self._parse_table_part(schema=schema)
 4939
 4940        while self._match(TokenType.DOT):
 4941            if catalog:
 4942                # This allows nesting the table in arbitrarily many dot expressions if needed
 4943                table = self.expression(
 4944                    exp.Dot(this=table, expression=self._parse_table_part(schema=schema))
 4945                )
 4946            else:
 4947                catalog = db
 4948                db = table
 4949                # "" used for tsql FROM a..b case
 4950                table = self._parse_table_part(schema=schema) or ""
 4951
 4952        if (
 4953            wildcard
 4954            and self._is_connected()
 4955            and (isinstance(table, exp.Identifier) or not table)
 4956            and self._match(TokenType.STAR)
 4957        ):
 4958            if isinstance(table, exp.Identifier):
 4959                table.args["this"] += "*"
 4960            else:
 4961                table = exp.Identifier(this="*")
 4962
 4963        if is_db_reference:
 4964            catalog = db
 4965            db = table
 4966            table = None
 4967
 4968        if not table and not is_db_reference:
 4969            self.raise_error(f"Expected table name but got {self._curr}")
 4970        if not db and is_db_reference:
 4971            self.raise_error(f"Expected database name but got {self._curr}")
 4972
 4973        table = self.expression(exp.Table(this=table, db=db, catalog=catalog))
 4974
 4975        # Bubble up comments from identifier parts to the Table
 4976        comments = []
 4977        for part in table.parts:
 4978            if part_comments := part.pop_comments():
 4979                comments.extend(part_comments)
 4980        if comments:
 4981            table.add_comments(comments)
 4982
 4983        changes = self._parse_changes()
 4984        if changes:
 4985            table.set("changes", changes)
 4986
 4987        at_before = self._parse_historical_data()
 4988        if at_before:
 4989            table.set("when", at_before)
 4990
 4991        pivots = self._parse_pivots()
 4992        if pivots:
 4993            table.set("pivots", pivots)
 4994
 4995        return table
 4996
 4997    def _parse_table(
 4998        self,
 4999        schema: bool = False,
 5000        joins: bool = False,
 5001        alias_tokens: t.Collection[TokenType] | None = None,
 5002        parse_bracket: bool = False,
 5003        is_db_reference: bool = False,
 5004        parse_partition: bool = False,
 5005        consume_pipe: bool = False,
 5006    ) -> exp.Expr | None:
 5007        if not schema and not is_db_reference and not consume_pipe and not joins:
 5008            index = self._index
 5009            table = self._parse_table_parts(fast=True)
 5010
 5011            if table is not None:
 5012                curr_tt = self._curr.token_type
 5013                next_tt = self._next.token_type
 5014
 5015                fast_terminators = self.TABLE_TERMINATORS
 5016
 5017                # only return the table if we're sure there are no other operators
 5018                # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT
 5019                if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION:
 5020                    return table
 5021
 5022                postfix_tokens = self.TABLE_POSTFIX_TOKENS
 5023
 5024                if curr_tt not in postfix_tokens and next_tt not in postfix_tokens:
 5025                    if alias := self._parse_table_alias(
 5026                        alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS
 5027                    ):
 5028                        table.set("alias", alias)
 5029
 5030                    if self._curr.token_type in fast_terminators:
 5031                        return table
 5032
 5033                self._retreat(index)
 5034
 5035        if stream := self._parse_stream():
 5036            return stream
 5037
 5038        if lateral := self._parse_lateral():
 5039            return lateral
 5040
 5041        if unnest := self._parse_unnest():
 5042            return unnest
 5043
 5044        if values := self._parse_derived_table_values():
 5045            return values
 5046
 5047        if subquery := self._parse_select(table=True, consume_pipe=consume_pipe):
 5048            if not subquery.args.get("pivots"):
 5049                subquery.set("pivots", self._parse_pivots())
 5050            if joins:
 5051                for join in self._parse_joins():
 5052                    subquery.append("joins", join)
 5053            return subquery
 5054
 5055        bracket = parse_bracket and self._parse_bracket(None)
 5056        bracket = self.expression(exp.Table(this=bracket)) if bracket else None
 5057
 5058        rows_from_tables = (
 5059            self._parse_wrapped_csv(self._parse_table)
 5060            if self._match_text_seq("ROWS", "FROM")
 5061            else None
 5062        )
 5063        rows_from = (
 5064            self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None
 5065        )
 5066
 5067        only = self._match(TokenType.ONLY)
 5068
 5069        this = t.cast(
 5070            exp.Expr,
 5071            bracket
 5072            or rows_from
 5073            or self._parse_bracket(
 5074                self._parse_table_parts(schema=schema, is_db_reference=is_db_reference)
 5075            ),
 5076        )
 5077
 5078        if only:
 5079            this.set("only", only)
 5080
 5081        # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context
 5082        self._match(TokenType.STAR)
 5083
 5084        parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION
 5085        if parse_partition and self._match(TokenType.PARTITION, advance=False):
 5086            this.set("partition", self._parse_partition())
 5087
 5088        if schema:
 5089            return self._parse_schema(this=this)
 5090
 5091        if self.dialect.ALIAS_POST_VERSION:
 5092            this.set("version", self._parse_version())
 5093
 5094        if self.dialect.ALIAS_POST_TABLESAMPLE:
 5095            this.set("sample", self._parse_table_sample())
 5096
 5097        alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 5098        if alias:
 5099            this.set("alias", alias)
 5100
 5101            # DuckDB requires the time-travel clause to come after the alias, e.g.
 5102            # SELECT * FROM t AS a AT (VERSION => 1)
 5103            if isinstance(this, exp.Table) and not this.args.get("when"):
 5104                this.set("when", self._parse_historical_data())
 5105
 5106        if self._match(TokenType.INDEXED_BY):
 5107            this.set("indexed", self._parse_table_parts())
 5108        elif self._match_text_seq("NOT", "INDEXED"):
 5109            this.set("indexed", False)
 5110
 5111        if isinstance(this, exp.Table) and self._match_text_seq("AT"):
 5112            return self.expression(
 5113                exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var())
 5114            )
 5115
 5116        this.set("hints", self._parse_table_hints())
 5117
 5118        if not this.args.get("pivots"):
 5119            this.set("pivots", self._parse_pivots())
 5120
 5121        if not self.dialect.ALIAS_POST_TABLESAMPLE:
 5122            this.set("sample", self._parse_table_sample())
 5123
 5124        if not self.dialect.ALIAS_POST_VERSION:
 5125            this.set("version", self._parse_version())
 5126
 5127        if joins:
 5128            for join in self._parse_joins(alias_tokens=alias_tokens):
 5129                this.append("joins", join)
 5130
 5131        if self._match_pair(TokenType.WITH, TokenType.ORDINALITY):
 5132            this.set("ordinality", True)
 5133            this.set("alias", self._parse_table_alias())
 5134
 5135        # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we
 5136        # hoist the table args onto the latter and return it instead
 5137        if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows):
 5138            table_from_rows = this.this
 5139            for arg in exp.TableFromRows.arg_types:
 5140                if arg != "this":
 5141                    table_from_rows.set(arg, this.args.get(arg))
 5142
 5143            this = table_from_rows
 5144
 5145        return this
 5146
 5147    def _parse_version(self) -> exp.Version | None:
 5148        for phrase, this in self.VERSION_PHRASES.items():
 5149            if self._match_text_seq(*phrase):
 5150                break
 5151        else:
 5152            return None
 5153
 5154        if self._match_set((TokenType.FROM, TokenType.BETWEEN)):
 5155            kind = self._prev.text.upper()
 5156            start = self._parse_bitwise()
 5157            self._match_texts(("TO", "AND"))
 5158            end = self._parse_bitwise()
 5159            expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end]))
 5160        elif self._match_text_seq("CONTAINED", "IN"):
 5161            kind = "CONTAINED IN"
 5162            expression = self.expression(
 5163                exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise))
 5164            )
 5165        elif self._match(TokenType.ALL):
 5166            kind = "ALL"
 5167            expression = None
 5168        else:
 5169            self._match_text_seq("AS", "OF")
 5170            kind = "AS OF"
 5171            expression = self._parse_type()
 5172
 5173        return self.expression(exp.Version(this=this, expression=expression, kind=kind))
 5174
 5175    def _parse_historical_data(self) -> exp.HistoricalData | None:
 5176        # https://docs.snowflake.com/en/sql-reference/constructs/at-before
 5177        index = self._index
 5178        historical_data = None
 5179        if self._match_texts(self.HISTORICAL_DATA_PREFIX):
 5180            this = self._prev.text.upper()
 5181            kind = (
 5182                self._match(TokenType.L_PAREN)
 5183                and self._match_texts(self.HISTORICAL_DATA_KIND)
 5184                and self._prev.text.upper()
 5185            )
 5186            expression = self._match(TokenType.FARROW) and self._parse_bitwise()
 5187
 5188            if expression:
 5189                self._match_r_paren()
 5190                historical_data = self.expression(
 5191                    exp.HistoricalData(this=this, kind=kind, expression=expression)
 5192                )
 5193            else:
 5194                self._retreat(index)
 5195
 5196        return historical_data
 5197
 5198    def _parse_changes(self) -> exp.Changes | None:
 5199        if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"):
 5200            return None
 5201
 5202        information = self._parse_var(any_token=True)
 5203        self._match_r_paren()
 5204
 5205        return self.expression(
 5206            exp.Changes(
 5207                information=information,
 5208                at_before=self._parse_historical_data(),
 5209                end=self._parse_historical_data(),
 5210            )
 5211        )
 5212
 5213    def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None:
 5214        if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False):
 5215            return None
 5216
 5217        self._advance()
 5218
 5219        expressions = self._parse_wrapped_csv(self._parse_equality)
 5220        offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 5221
 5222        alias = self._parse_table_alias() if with_alias else None
 5223
 5224        if alias:
 5225            if self.dialect.UNNEST_COLUMN_ONLY:
 5226                if alias.args.get("columns"):
 5227                    self.raise_error("Unexpected extra column alias in unnest.")
 5228
 5229                alias.set("columns", [alias.this])
 5230                alias.set("this", None)
 5231
 5232            columns = alias.args.get("columns") or []
 5233            if offset and len(expressions) < len(columns):
 5234                offset = columns.pop()
 5235
 5236        if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET):
 5237            self._match(TokenType.ALIAS)
 5238            offset = self._parse_id_var(
 5239                any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS
 5240            ) or exp.to_identifier("offset")
 5241
 5242        return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset))
 5243
 5244    def _parse_derived_table_values(self) -> exp.Values | None:
 5245        is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES)
 5246        if not is_derived and not (
 5247            # ClickHouse's `FORMAT Values` is equivalent to `VALUES`
 5248            self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES")
 5249        ):
 5250            return None
 5251
 5252        expressions = self._parse_csv(self._parse_value)
 5253        alias = self._parse_table_alias()
 5254
 5255        if is_derived:
 5256            self._match_r_paren()
 5257
 5258        return self.expression(
 5259            exp.Values(expressions=expressions, alias=alias or self._parse_table_alias())
 5260        )
 5261
 5262    def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None:
 5263        if not self._match(TokenType.TABLE_SAMPLE) and not (
 5264            as_modifier and self._match_text_seq("USING", "SAMPLE")
 5265        ):
 5266            return None
 5267
 5268        bucket_numerator = None
 5269        bucket_denominator = None
 5270        bucket_field = None
 5271        percent = None
 5272        size = None
 5273        seed = None
 5274
 5275        method = self._parse_var(tokens=(TokenType.ROW,), upper=True)
 5276        matched_l_paren = self._match(TokenType.L_PAREN)
 5277
 5278        if self.TABLESAMPLE_CSV:
 5279            num = None
 5280            expressions = self._parse_csv(self._parse_primary)
 5281        else:
 5282            expressions = None
 5283            num = (
 5284                self._parse_factor(parse_mod=False)
 5285                if self._match(TokenType.NUMBER, advance=False)
 5286                else self._parse_primary() or self._parse_placeholder()
 5287            )
 5288
 5289        if self._match_text_seq("BUCKET"):
 5290            bucket_numerator = self._parse_number()
 5291            self._match_text_seq("OUT", "OF")
 5292            bucket_denominator = bucket_denominator = self._parse_number()
 5293            self._match(TokenType.ON)
 5294            bucket_field = self._parse_field()
 5295        elif self._match_set((TokenType.PERCENT, TokenType.MOD)):
 5296            percent = num
 5297        elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
 5298            size = num
 5299        else:
 5300            percent = num
 5301
 5302        if matched_l_paren:
 5303            self._match_r_paren()
 5304
 5305        if self._match(TokenType.L_PAREN):
 5306            method = self._parse_var(upper=True)
 5307            seed = self._match(TokenType.COMMA) and self._parse_number()
 5308            self._match_r_paren()
 5309        elif self._match_texts(("SEED", "REPEATABLE")):
 5310            seed = self._parse_wrapped(self._parse_number)
 5311
 5312        if not method and self.DEFAULT_SAMPLING_METHOD:
 5313            method = exp.var(self.DEFAULT_SAMPLING_METHOD)
 5314
 5315        return self.expression(
 5316            exp.TableSample(
 5317                expressions=expressions,
 5318                method=method,
 5319                bucket_numerator=bucket_numerator,
 5320                bucket_denominator=bucket_denominator,
 5321                bucket_field=bucket_field,
 5322                percent=percent,
 5323                size=size,
 5324                seed=seed,
 5325            )
 5326        )
 5327
 5328    def _parse_pivots(self) -> list[exp.Pivot] | None:
 5329        if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT):
 5330            return None
 5331        return list(iter(self._parse_pivot, None)) or None
 5332
 5333    def _parse_joins(
 5334        self, alias_tokens: t.Collection[TokenType] | None = None
 5335    ) -> t.Iterator[exp.Join]:
 5336        return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None)
 5337
 5338    def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None:
 5339        if not self._match(TokenType.INTO):
 5340            return None
 5341
 5342        return self.expression(
 5343            exp.UnpivotColumns(
 5344                this=self._match_text_seq("NAME") and self._parse_column(),
 5345                expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column),
 5346            )
 5347        )
 5348
 5349    # https://duckdb.org/docs/sql/statements/pivot
 5350    def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot:
 5351        def _parse_on() -> exp.Expr | None:
 5352            this = self._parse_bitwise()
 5353
 5354            if self._match(TokenType.IN):
 5355                # PIVOT ... ON col IN (row_val1, row_val2)
 5356                return self._parse_in(this)
 5357            if self._match(TokenType.ALIAS, advance=False):
 5358                # UNPIVOT ... ON (col1, col2, col3) AS row_val
 5359                return self._parse_alias(this)
 5360
 5361            return this
 5362
 5363        this = self._parse_table()
 5364        expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on)
 5365        into = self._parse_unpivot_columns()
 5366        using = self._match(TokenType.USING) and self._parse_csv(
 5367            lambda: self._parse_alias(self._parse_column())
 5368        )
 5369        group = self._parse_group()
 5370
 5371        return self.expression(
 5372            exp.Pivot(
 5373                this=this,
 5374                expressions=expressions,
 5375                using=using,
 5376                group=group,
 5377                unpivot=is_unpivot,
 5378                into=into,
 5379            )
 5380        )
 5381
 5382    def _parse_pivot_in(self) -> exp.In:
 5383        def _parse_aliased_expression() -> exp.Expr | None:
 5384            this = self._parse_select_or_expression()
 5385
 5386            self._match(TokenType.ALIAS)
 5387            alias = self._parse_bitwise()
 5388            if alias:
 5389                if isinstance(alias, exp.Column) and not alias.db:
 5390                    alias = alias.this
 5391                return self.expression(exp.PivotAlias(this=this, alias=alias))
 5392
 5393            return this
 5394
 5395        value = self._parse_column()
 5396
 5397        if not self._match(TokenType.IN):
 5398            self.raise_error("Expecting IN")
 5399
 5400        if self._match(TokenType.L_PAREN):
 5401            if self._match(TokenType.ANY):
 5402                exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order()))
 5403            else:
 5404                exprs = self._parse_csv(_parse_aliased_expression)
 5405            self._match_r_paren()
 5406            return self.expression(exp.In(this=value, expressions=exprs))
 5407
 5408        return self.expression(exp.In(this=value, field=self._parse_id_var()))
 5409
 5410    def _parse_pivot_aggregation(self) -> exp.Expr | None:
 5411        func = self._parse_function()
 5412        if not func:
 5413            if self._prev.token_type == TokenType.COMMA:
 5414                return None
 5415            self.raise_error("Expecting an aggregation function in PIVOT")
 5416
 5417        return self._parse_alias(func)
 5418
 5419    def _parse_pivot(self) -> exp.Pivot | None:
 5420        index = self._index
 5421        include_nulls = None
 5422
 5423        if self._match(TokenType.PIVOT):
 5424            unpivot = False
 5425        elif self._match(TokenType.UNPIVOT):
 5426            unpivot = True
 5427
 5428            # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax
 5429            if self._match_text_seq("INCLUDE", "NULLS"):
 5430                include_nulls = True
 5431            elif self._match_text_seq("EXCLUDE", "NULLS"):
 5432                include_nulls = False
 5433        else:
 5434            return None
 5435
 5436        expressions = []
 5437
 5438        if not self._match(TokenType.L_PAREN):
 5439            self._retreat(index)
 5440            return None
 5441
 5442        if unpivot:
 5443            expressions = self._parse_csv(self._parse_column)
 5444        else:
 5445            expressions = self._parse_csv(self._parse_pivot_aggregation)
 5446
 5447        if not expressions:
 5448            self.raise_error("Failed to parse PIVOT's aggregation list")
 5449
 5450        if not self._match(TokenType.FOR):
 5451            self.raise_error("Expecting FOR")
 5452
 5453        fields = []
 5454        while True:
 5455            field = self._try_parse(self._parse_pivot_in)
 5456            if not field:
 5457                break
 5458            fields.append(field)
 5459
 5460        default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped(
 5461            self._parse_bitwise
 5462        )
 5463
 5464        group = self._parse_group()
 5465
 5466        self._match_r_paren()
 5467
 5468        pivot = self.expression(
 5469            exp.Pivot(
 5470                expressions=expressions,
 5471                fields=fields,
 5472                unpivot=unpivot,
 5473                include_nulls=include_nulls,
 5474                default_on_null=default_on_null,
 5475                group=group,
 5476            )
 5477        )
 5478
 5479        if unpivot:
 5480            pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions])
 5481            for pivot_field in pivot.fields:
 5482                if isinstance(pivot_field, exp.In):
 5483                    pivot_field.set("this", _unpivot_target(pivot_field.this))
 5484
 5485            pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST)
 5486
 5487        if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False):
 5488            pivot.set("alias", self._parse_table_alias())
 5489
 5490        if not unpivot:
 5491            names = self._pivot_column_names(t.cast(list[exp.Expr], expressions))
 5492
 5493            columns: list[exp.Expr] = []
 5494            all_fields = []
 5495            for pivot_field in pivot.fields:
 5496                pivot_field_expressions = pivot_field.expressions
 5497
 5498                # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case.
 5499                if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny):
 5500                    continue
 5501
 5502                all_fields.append(
 5503                    [
 5504                        # An explicit `<field> AS <alias>` names the output column directly,
 5505                        # so it wins over the dialect's string-identifying convention
 5506                        fld.sql()
 5507                        if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias)
 5508                        else fld.alias_or_name
 5509                        for fld in pivot_field_expressions
 5510                    ]
 5511                )
 5512
 5513            if all_fields:
 5514                if names:
 5515                    all_fields.append(names)
 5516
 5517                # Generate all possible combinations of the pivot columns
 5518                # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US'))
 5519                # generates the product between [[2000, 2010], ['NL', 'US'], ['total']]
 5520                for fld_parts_tuple in itertools.product(*all_fields):
 5521                    fld_parts = list(fld_parts_tuple)
 5522
 5523                    if names and self.PREFIXED_PIVOT_COLUMNS:
 5524                        # Move the "name" to the front of the list
 5525                        fld_parts.insert(0, fld_parts.pop(-1))
 5526
 5527                    columns.append(exp.to_identifier("_".join(fld_parts)))
 5528
 5529            pivot.set("columns", columns)
 5530            pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS)
 5531            pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS)
 5532            pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING)
 5533
 5534        return pivot
 5535
 5536    def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]:
 5537        return [agg.alias for agg in aggregations if agg.alias]
 5538
 5539    def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None:
 5540        if not skip_where_token and not self._match(TokenType.PREWHERE):
 5541            return None
 5542
 5543        comments = self._prev_comments
 5544        return self.expression(
 5545            exp.PreWhere(this=self._parse_disjunction()),
 5546            comments=comments,
 5547        )
 5548
 5549    def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None:
 5550        if not skip_where_token and not self._match(TokenType.WHERE):
 5551            return None
 5552
 5553        comments = self._prev_comments
 5554        return self.expression(
 5555            exp.Where(this=self._parse_disjunction()),
 5556            comments=comments,
 5557        )
 5558
 5559    def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None:
 5560        if not skip_group_by_token and not self._match(TokenType.GROUP_BY):
 5561            return None
 5562        comments = self._prev_comments
 5563
 5564        elements: dict[str, t.Any] = defaultdict(list)
 5565
 5566        if self._match(TokenType.ALL):
 5567            elements["all"] = True
 5568        elif self._match(TokenType.DISTINCT):
 5569            elements["all"] = False
 5570
 5571        while True:
 5572            index = self._index
 5573
 5574            # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW,
 5575            # which are also valid identifiers
 5576            if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False):
 5577                break
 5578
 5579            elements["expressions"].extend(
 5580                self._parse_csv(
 5581                    lambda: (
 5582                        None
 5583                        if self._match_set((TokenType.CUBE, TokenType.ROLLUP), advance=False)
 5584                        else self._parse_disjunction()
 5585                    )
 5586                )
 5587            )
 5588            grouping_sets_as_group_by_element = (
 5589                not elements["expressions"] or self._prev.token_type == TokenType.COMMA
 5590            )
 5591
 5592            before_with_index = self._index
 5593            with_prefix = self._match(TokenType.WITH)
 5594
 5595            if cube_or_rollup := self._parse_cube_or_rollup(with_prefix=with_prefix):
 5596                key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube"
 5597                elements[key].append(cube_or_rollup)
 5598            elif grouping_sets := self._parse_grouping_sets():
 5599                elements["grouping_sets"].append(grouping_sets)
 5600                elements["grouping_sets_as_group_by_element"] = grouping_sets_as_group_by_element
 5601                if not grouping_sets_as_group_by_element:
 5602                    break
 5603            elif self._match_text_seq("TOTALS"):
 5604                elements["totals"] = True  # type: ignore
 5605
 5606            if before_with_index <= self._index <= before_with_index + 1:
 5607                self._retreat(before_with_index)
 5608                break
 5609
 5610            if index == self._index:
 5611                break
 5612
 5613        return self.expression(exp.Group(**elements), comments=comments)  # type: ignore
 5614
 5615    def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None:
 5616        if self._match(TokenType.CUBE):
 5617            kind: type[exp.Cube | exp.Rollup] = exp.Cube
 5618        elif self._match(TokenType.ROLLUP):
 5619            kind = exp.Rollup
 5620        else:
 5621            return None
 5622
 5623        return self.expression(
 5624            kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise))
 5625        )
 5626
 5627    def _parse_grouping_sets(self) -> exp.GroupingSets | None:
 5628        if self._match(TokenType.GROUPING_SETS):
 5629            return self.expression(
 5630                exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set))
 5631            )
 5632        return None
 5633
 5634    def _parse_grouping_set(self) -> exp.Expr | None:
 5635        return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise()
 5636
 5637    def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None:
 5638        if not skip_having_token and not self._match(TokenType.HAVING):
 5639            return None
 5640        comments = self._prev_comments
 5641        return self.expression(
 5642            exp.Having(this=self._parse_disjunction()),
 5643            comments=comments,
 5644        )
 5645
 5646    def _parse_qualify(self) -> exp.Qualify | None:
 5647        if not self._match(TokenType.QUALIFY):
 5648            return None
 5649        return self.expression(exp.Qualify(this=self._parse_disjunction()))
 5650
 5651    def _parse_connect_with_prior(self) -> exp.Expr | None:
 5652        self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression(
 5653            exp.Prior(this=self._parse_bitwise())
 5654        )
 5655        connect = self._parse_disjunction()
 5656        self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR")
 5657        return connect
 5658
 5659    def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None:
 5660        if skip_start_token:
 5661            start = None
 5662        elif self._match_text_seq("START", "WITH"):
 5663            start = self._parse_disjunction()
 5664        else:
 5665            return None
 5666
 5667        self._match(TokenType.CONNECT_BY)
 5668        nocycle = self._match_text_seq("NOCYCLE")
 5669        connect = self._parse_connect_with_prior()
 5670
 5671        if not start and self._match_text_seq("START", "WITH"):
 5672            start = self._parse_disjunction()
 5673
 5674        return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle))
 5675
 5676    def _parse_name_as_expression(self) -> exp.Expr | None:
 5677        this = self._parse_id_var(any_token=True)
 5678        if self._match(TokenType.ALIAS):
 5679            this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction()))
 5680        return this
 5681
 5682    def _parse_interpolate(self) -> list[exp.Expr] | None:
 5683        if self._match_text_seq("INTERPOLATE"):
 5684            return self._parse_wrapped_csv(self._parse_name_as_expression)
 5685        return None
 5686
 5687    def _parse_order(
 5688        self, this: exp.Expr | None = None, skip_order_token: bool = False
 5689    ) -> exp.Expr | None:
 5690        siblings = None
 5691        if not skip_order_token and not self._match(TokenType.ORDER_BY):
 5692            if not self._match(TokenType.ORDER_SIBLINGS_BY):
 5693                return this
 5694
 5695            siblings = True
 5696
 5697        comments = self._prev_comments
 5698        return self.expression(
 5699            exp.Order(
 5700                this=this,
 5701                expressions=self._parse_csv(self._parse_ordered),
 5702                siblings=siblings,
 5703            ),
 5704            comments=comments,
 5705        )
 5706
 5707    def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None:
 5708        if not self._match(token):
 5709            return None
 5710        return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered)))
 5711
 5712    def _parse_ordered(
 5713        self, parse_method: t.Callable[[], exp.Expr | None] | None = None
 5714    ) -> exp.Ordered | None:
 5715        this = parse_method() if parse_method else self._parse_disjunction()
 5716        if not this:
 5717            return None
 5718
 5719        if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL:
 5720            this = exp.var("ALL")
 5721
 5722        asc = self._match(TokenType.ASC)
 5723        desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None)
 5724
 5725        is_nulls_first = self._match_text_seq("NULLS", "FIRST")
 5726        is_nulls_last = self._match_text_seq("NULLS", "LAST")
 5727
 5728        nulls_first = is_nulls_first or False
 5729        explicitly_null_ordered = is_nulls_first or is_nulls_last
 5730
 5731        if (
 5732            not explicitly_null_ordered
 5733            and (
 5734                (not desc and self.dialect.NULL_ORDERING == "nulls_are_small")
 5735                or (desc and self.dialect.NULL_ORDERING != "nulls_are_small")
 5736            )
 5737            and self.dialect.NULL_ORDERING != "nulls_are_last"
 5738        ):
 5739            nulls_first = True
 5740
 5741        if self._match_text_seq("WITH", "FILL"):
 5742            with_fill = self.expression(
 5743                exp.WithFill(
 5744                    from_=self._match(TokenType.FROM) and self._parse_bitwise(),
 5745                    to=self._match_text_seq("TO") and self._parse_bitwise(),
 5746                    step=self._match_text_seq("STEP") and self._parse_bitwise(),
 5747                    interpolate=self._parse_interpolate(),
 5748                )
 5749            )
 5750        else:
 5751            with_fill = None
 5752
 5753        return self.expression(
 5754            exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill)
 5755        )
 5756
 5757    def _parse_limit_options(self) -> exp.LimitOptions | None:
 5758        percent = self._match_set((TokenType.PERCENT, TokenType.MOD))
 5759        rows = self._match_set((TokenType.ROW, TokenType.ROWS))
 5760        self._match_text_seq("ONLY")
 5761        with_ties = self._match_text_seq("WITH", "TIES")
 5762
 5763        if not (percent or rows or with_ties):
 5764            return None
 5765
 5766        return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties))
 5767
 5768    def _parse_limit(
 5769        self,
 5770        this: exp.Expr | None = None,
 5771        top: bool = False,
 5772        skip_limit_token: bool = False,
 5773    ) -> exp.Expr | None:
 5774        if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT):
 5775            comments = self._prev_comments
 5776            if top:
 5777                limit_paren = self._match(TokenType.L_PAREN)
 5778                expression = (
 5779                    self._parse_term() or self._parse_select()
 5780                    if limit_paren
 5781                    else self._parse_number()
 5782                )
 5783
 5784                if limit_paren:
 5785                    self._match_r_paren()
 5786
 5787            else:
 5788                if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL):
 5789                    return this
 5790
 5791                expression = self._parse_term(parse_mod=False)
 5792            limit_options = self._parse_limit_options()
 5793
 5794            if self._match(TokenType.COMMA):
 5795                offset = expression
 5796                expression = self._parse_term()
 5797            else:
 5798                offset = None
 5799
 5800            limit_exp = self.expression(
 5801                exp.Limit(
 5802                    this=this,
 5803                    expression=expression,
 5804                    offset=offset,
 5805                    limit_options=limit_options,
 5806                    expressions=self._parse_limit_by(),
 5807                ),
 5808                comments=comments,
 5809            )
 5810
 5811            return limit_exp
 5812
 5813        if self._match(TokenType.FETCH):
 5814            direction = (
 5815                self._prev.text.upper()
 5816                if self._match_set((TokenType.FIRST, TokenType.NEXT))
 5817                else "FIRST"
 5818            )
 5819
 5820            count = self._parse_field(tokens=self.FETCH_TOKENS)
 5821
 5822            return self.expression(
 5823                exp.Fetch(
 5824                    direction=direction, count=count, limit_options=self._parse_limit_options()
 5825                )
 5826            )
 5827
 5828        return this
 5829
 5830    def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None:
 5831        if not self._match(TokenType.OFFSET):
 5832            return this
 5833
 5834        count = self._parse_term()
 5835        self._match_set((TokenType.ROW, TokenType.ROWS))
 5836
 5837        return self.expression(
 5838            exp.Offset(this=this, expression=count, expressions=self._parse_limit_by())
 5839        )
 5840
 5841    def _can_parse_limit_or_offset(self) -> bool:
 5842        if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False):
 5843            return False
 5844
 5845        index = self._index
 5846        result = bool(
 5847            self._try_parse(self._parse_limit, retreat=True)
 5848            or self._try_parse(self._parse_offset, retreat=True)
 5849        )
 5850        self._retreat(index)
 5851
 5852        # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset
 5853        if self._next.token_type == TokenType.MATCH_CONDITION:
 5854            result = False
 5855
 5856        return result
 5857
 5858    def _can_parse_named_window(self) -> bool:
 5859        # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias.
 5860        # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`.
 5861        if not self._match(TokenType.WINDOW, advance=False):
 5862            return False
 5863
 5864        name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None
 5865        if name is None or name.token_type not in self.ID_VAR_TOKENS:
 5866            return False
 5867
 5868        alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None
 5869        if alias_tok is None or alias_tok.token_type != TokenType.ALIAS:
 5870            return False
 5871
 5872        body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None
 5873        return body is not None and body.token_type == TokenType.L_PAREN
 5874
 5875    def _parse_limit_by(self) -> list[exp.Expr] | None:
 5876        return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None
 5877
 5878    def _parse_locks(self) -> list[exp.Lock]:
 5879        locks = []
 5880        while True:
 5881            update, key = None, None
 5882            if self._match_text_seq("FOR", "UPDATE"):
 5883                update = True
 5884            elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq(
 5885                "LOCK", "IN", "SHARE", "MODE"
 5886            ):
 5887                update = False
 5888            elif self._match_text_seq("FOR", "KEY", "SHARE"):
 5889                update, key = False, True
 5890            elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"):
 5891                update, key = True, True
 5892            else:
 5893                break
 5894
 5895            expressions = None
 5896            if self._match_text_seq("OF"):
 5897                expressions = self._parse_csv(lambda: self._parse_table(schema=True))
 5898
 5899            wait: bool | exp.Expr | None = None
 5900            if self._match_text_seq("NOWAIT"):
 5901                wait = True
 5902            elif self._match_text_seq("WAIT"):
 5903                wait = self._parse_primary()
 5904            elif self._match_text_seq("SKIP", "LOCKED"):
 5905                wait = False
 5906
 5907            locks.append(
 5908                self.expression(
 5909                    exp.Lock(update=update, expressions=expressions, wait=wait, key=key)
 5910                )
 5911            )
 5912
 5913        return locks
 5914
 5915    def parse_set_operation(
 5916        self, this: exp.Expr | None, consume_pipe: bool = False
 5917    ) -> exp.Expr | None:
 5918        start = self._index
 5919        _, side_token, kind_token = self._parse_join_parts()
 5920
 5921        side = side_token.text if side_token else None
 5922        kind = kind_token.text if kind_token else None
 5923
 5924        if not self._match_set(self.SET_OPERATIONS):
 5925            self._retreat(start)
 5926            return None
 5927
 5928        token_type = self._prev.token_type
 5929
 5930        if token_type == TokenType.UNION:
 5931            operation: type[exp.SetOperation] = exp.Union
 5932        elif token_type == TokenType.EXCEPT:
 5933            operation = exp.Except
 5934        else:
 5935            operation = exp.Intersect
 5936
 5937        comments = self._prev.comments
 5938
 5939        if self._match(TokenType.DISTINCT):
 5940            distinct: bool | None = True
 5941        elif self._match(TokenType.ALL):
 5942            distinct = False
 5943        else:
 5944            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
 5945            if distinct is None:
 5946                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
 5947
 5948        by_name = (
 5949            self._match_text_seq("BY", "NAME")
 5950            or self._match_text_seq("STRICT", "CORRESPONDING")
 5951            or None
 5952        )
 5953        if self._match_text_seq("CORRESPONDING"):
 5954            by_name = True
 5955            if not side and not kind:
 5956                kind = "INNER"
 5957
 5958        on_column_list = None
 5959        if by_name and self._match_texts(("ON", "BY")):
 5960            on_column_list = self._parse_wrapped_csv(self._parse_column)
 5961
 5962        expression = self._parse_select(
 5963            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
 5964        )
 5965
 5966        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
 5967        # in _parse_cte and so that alias pushdown can reach into set operation branches
 5968        if isinstance(this, exp.Values):
 5969            this = self._values_to_select(this)
 5970        if isinstance(expression, exp.Values):
 5971            expression = self._values_to_select(expression)
 5972
 5973        if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery):
 5974            subquery = this.this
 5975            subquery.set("alias", exp.TableAlias(this=this.args["alias"]))
 5976            subquery.add_comments(this.pop_comments())
 5977            this = subquery
 5978
 5979        return self.expression(
 5980            operation(
 5981                this=this,
 5982                distinct=distinct,
 5983                by_name=by_name,
 5984                expression=expression,
 5985                side=side,
 5986                kind=kind,
 5987                on=on_column_list,
 5988            ),
 5989            comments=comments,
 5990        )
 5991
 5992    def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None:
 5993        while this:
 5994            setop = self.parse_set_operation(this)
 5995            if not setop:
 5996                break
 5997            this = setop
 5998
 5999        if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP:
 6000            expression = this.expression
 6001
 6002            if expression:
 6003                for arg in self.SET_OP_MODIFIERS:
 6004                    expr = expression.args.get(arg)
 6005                    if expr:
 6006                        this.set(arg, expr.pop())
 6007
 6008        return this
 6009
 6010    def _parse_expression(self) -> exp.Expr | None:
 6011        return self._parse_alias(self._parse_assignment())
 6012
 6013    def _parse_assignment(self) -> exp.Expr | None:
 6014        this = self._parse_disjunction()
 6015        if not this and self._next.token_type in self.ASSIGNMENT:
 6016            # This allows us to parse <non-identifier token> := <expr>
 6017            this = exp.column(
 6018                t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text)
 6019            )
 6020
 6021        while self._match_set(self.ASSIGNMENT):
 6022            if isinstance(this, exp.Column) and len(this.parts) == 1:
 6023                this = this.this
 6024
 6025            comments = self._prev_comments
 6026            this = self.expression(
 6027                self.ASSIGNMENT[self._prev.token_type](
 6028                    this=this, expression=self._parse_assignment()
 6029                ),
 6030                comments=comments,
 6031            )
 6032
 6033        return this
 6034
 6035    def _parse_disjunction(self) -> exp.Expr | None:
 6036        this = self._parse_conjunction()
 6037        while self._match_set(self.DISJUNCTION):
 6038            comments = self._prev_comments
 6039            this = self.expression(
 6040                self.DISJUNCTION[self._prev.token_type](
 6041                    this=this, expression=self._parse_conjunction()
 6042                ),
 6043                comments=comments,
 6044            )
 6045        return this
 6046
 6047    def _parse_conjunction(self) -> exp.Expr | None:
 6048        this = self._parse_equality()
 6049        while self._match_set(self.CONJUNCTION):
 6050            comments = self._prev_comments
 6051            this = self.expression(
 6052                self.CONJUNCTION[self._prev.token_type](
 6053                    this=this, expression=self._parse_equality()
 6054                ),
 6055                comments=comments,
 6056            )
 6057        return this
 6058
 6059    def _parse_equality(self) -> exp.Expr | None:
 6060        this = self._parse_comparison()
 6061        while self._match_set(self.EQUALITY):
 6062            comments = self._prev_comments
 6063            this = self.expression(
 6064                self.EQUALITY[self._prev.token_type](
 6065                    this=this, expression=self._parse_comparison()
 6066                ),
 6067                comments=comments,
 6068            )
 6069        return this
 6070
 6071    def _parse_comparison(self) -> exp.Expr | None:
 6072        this = self._parse_range()
 6073        while self._match_set(self.COMPARISON):
 6074            comments = self._prev_comments
 6075            this = self.expression(
 6076                self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()),
 6077                comments=comments,
 6078            )
 6079        return this
 6080
 6081    def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6082        this = this or self._parse_bitwise()
 6083
 6084        while True:
 6085            negate = self._match(TokenType.NOT)
 6086            if self._match_set(self.RANGE_PARSERS):
 6087                expression = self.RANGE_PARSERS[self._prev.token_type](self, this)
 6088                if not expression:
 6089                    return this
 6090
 6091                this = expression
 6092            elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)):
 6093                this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6094            elif self._match(TokenType.NOTNULL):
 6095                # Postgres supports ISNULL and NOTNULL for conditions.
 6096                # https://blog.andreiavram.ro/postgresql-null-composite-type/
 6097                if self.dialect.NORMALIZE_NOT_NULL:
 6098                    this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6099                    this = self.expression(exp.Not(this=this))
 6100                else:
 6101                    this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True))
 6102            else:
 6103                if negate:
 6104                    self._retreat(self._index - 1)
 6105                break
 6106
 6107            if negate:
 6108                this = self._negate_range(this)
 6109                if self._curr and (
 6110                    self._curr.token_type == TokenType.NOT
 6111                    or self._curr.token_type in self.RANGE_PARSERS
 6112                ):
 6113                    this = self.expression(exp.Paren(this=this))
 6114
 6115        return this
 6116
 6117    def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6118        if not this:
 6119            return this
 6120
 6121        expression = this.this if isinstance(this, exp.Escape) else this
 6122        if isinstance(expression, (exp.Like, exp.ILike)):
 6123            expression.set("negate", True)
 6124            return this
 6125
 6126        return self.expression(exp.Not(this=this))
 6127
 6128    def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None:
 6129        index = self._index - 1
 6130        negate = self._match(TokenType.NOT)
 6131
 6132        if self._match_text_seq("DISTINCT", "FROM"):
 6133            klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ
 6134            return self.expression(klass(this=this, expression=self._parse_bitwise()))
 6135
 6136        if self._match(TokenType.JSON):
 6137            kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper()
 6138
 6139            if self._match_text_seq("WITH"):
 6140                _with = True
 6141            elif self._match_text_seq("WITHOUT"):
 6142                _with = False
 6143            else:
 6144                _with = None
 6145
 6146            unique = self._match(TokenType.UNIQUE)
 6147            self._match_text_seq("KEYS")
 6148            expression: exp.Expr | None = self.expression(
 6149                exp.JSON(this=kind, with_=_with, unique=unique)
 6150            )
 6151        else:
 6152            expression = self._parse_null() or self._parse_bitwise()
 6153            if not expression:
 6154                self._retreat(index)
 6155                return None
 6156
 6157        if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL:
 6158            this = self.expression(exp.Is(this=this, expression=expression, negate=True))
 6159        else:
 6160            this = self.expression(exp.Is(this=this, expression=expression))
 6161            this = self.expression(exp.Not(this=this)) if negate else this
 6162
 6163        return self._parse_column_ops(this)
 6164
 6165    def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In:
 6166        unnest = self._parse_unnest(with_alias=False)
 6167        if unnest:
 6168            this = self.expression(exp.In(this=this, unnest=unnest))
 6169        elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)):
 6170            matched_l_paren = self._prev.token_type == TokenType.L_PAREN
 6171            expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias))
 6172
 6173            if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query):
 6174                this = self.expression(
 6175                    exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False))
 6176                )
 6177            else:
 6178                this = self.expression(exp.In(this=this, expressions=expressions))
 6179
 6180            if matched_l_paren:
 6181                self._match_r_paren(this)
 6182            elif not self._match(TokenType.R_BRACKET, expression=this):
 6183                self.raise_error("Expecting ]")
 6184        else:
 6185            this = self.expression(exp.In(this=this, field=self._parse_column()))
 6186
 6187        return this
 6188
 6189    def _parse_between(self, this: exp.Expr | None) -> exp.Between:
 6190        symmetric = None
 6191        if self._match_text_seq("SYMMETRIC"):
 6192            symmetric = True
 6193        elif self._match_text_seq("ASYMMETRIC"):
 6194            symmetric = False
 6195
 6196        low = self._parse_bitwise()
 6197        self._match(TokenType.AND)
 6198        high = self._parse_bitwise()
 6199
 6200        return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric))
 6201
 6202    def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None:
 6203        if not self._match(TokenType.ESCAPE):
 6204            return this
 6205        return self.expression(
 6206            exp.Escape(this=this, expression=self._parse_string() or self._parse_null())
 6207        )
 6208
 6209    def _parse_interval_span(
 6210        self, this: exp.Expr, parse_function_unit: bool = True
 6211    ) -> exp.Interval:
 6212        # handle day-time format interval span with omitted units:
 6213        #   INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`>
 6214        interval_span_units_omitted = None
 6215        if (
 6216            this
 6217            and this.is_string
 6218            and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
 6219            and exp.INTERVAL_DAY_TIME_RE.match(this.name)
 6220        ):
 6221            index = self._index
 6222
 6223            # Var "TO" Var
 6224            first_unit = self._parse_var(any_token=True, upper=True)
 6225            second_unit = None
 6226            if first_unit and self._match_text_seq("TO"):
 6227                second_unit = self._parse_var(any_token=True, upper=True)
 6228
 6229            interval_span_units_omitted = not (first_unit and second_unit)
 6230
 6231            self._retreat(index)
 6232
 6233        unit_index = self._index
 6234        if interval_span_units_omitted:
 6235            unit = None
 6236        else:
 6237            # Only attempt to parse a unit if the current token can actually be one, so that a
 6238            # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x)
 6239            is_unit = self._curr is not None and (
 6240                self._curr.token_type == TokenType.VAR
 6241                or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS
 6242            )
 6243            unit = self._parse_function() if parse_function_unit and is_unit else None
 6244            if not unit and is_unit:
 6245                unit = self._parse_var(any_token=True, upper=True)
 6246
 6247        # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse
 6248        # each INTERVAL expression into this canonical form so it's easy to transpile
 6249        if this and this.is_number:
 6250            try:
 6251                this = exp.Literal.string(this.to_py())
 6252            except ValueError:
 6253                self.raise_error(f"Invalid numeric interval literal: {this.name!r}")
 6254        elif this and this.is_string:
 6255            parts = exp.INTERVAL_STRING_RE.findall(this.name)
 6256            if parts and unit:
 6257                # Unconsume the eagerly-parsed unit, since the real unit was part of the string
 6258                unit = None
 6259                self._retreat(unit_index)
 6260
 6261            if len(parts) == 1:
 6262                this = exp.Literal.string(parts[0][0])
 6263                unit = self.expression(exp.Var(this=parts[0][1].upper()))
 6264
 6265        if self.INTERVAL_SPANS and self._match_text_seq("TO"):
 6266            unit = self.expression(
 6267                exp.IntervalSpan(
 6268                    this=unit,
 6269                    expression=self._parse_function()
 6270                    or self._parse_var(any_token=True, upper=True),
 6271                )
 6272            )
 6273
 6274        return self.expression(exp.Interval(this=this, unit=unit))
 6275
 6276    def _parse_interval(
 6277        self, require_interval: bool = True, parse_function_unit: bool = True
 6278    ) -> exp.Add | exp.Interval | None:
 6279        index = self._index
 6280
 6281        if not self._match(TokenType.INTERVAL) and require_interval:
 6282            return None
 6283
 6284        if self._match(TokenType.STRING, advance=False):
 6285            this = self._parse_primary()
 6286        else:
 6287            this = self._parse_term()
 6288
 6289        if not this or (
 6290            isinstance(this, exp.Column)
 6291            and not this.table
 6292            and not this.this.quoted
 6293            and self._curr
 6294            and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS
 6295        ):
 6296            self._retreat(index)
 6297            return None
 6298
 6299        interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit)
 6300
 6301        index = self._index
 6302        self._match(TokenType.PLUS)
 6303
 6304        # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals
 6305        if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False):
 6306            return self.expression(
 6307                exp.Add(
 6308                    this=interval,
 6309                    expression=self._parse_interval(False, parse_function_unit=parse_function_unit),
 6310                )
 6311            )
 6312
 6313        self._retreat(index)
 6314        return interval
 6315
 6316    def _parse_bitwise(self) -> exp.Expr | None:
 6317        this = self._parse_term()
 6318
 6319        while True:
 6320            if self._match_set(self.BITWISE):
 6321                this = self.expression(
 6322                    self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term())
 6323                )
 6324            elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE):
 6325                this = self.expression(
 6326                    exp.DPipe(
 6327                        this=this,
 6328                        expression=self._parse_term(),
 6329                        safe=not self.dialect.STRICT_STRING_CONCAT,
 6330                    )
 6331                )
 6332            elif self._match(TokenType.DQMARK):
 6333                this = self.expression(
 6334                    exp.Coalesce(this=this, expressions=ensure_list(self._parse_term()))
 6335                )
 6336            elif self._match_pair(TokenType.LT, TokenType.LT):
 6337                this = self.expression(
 6338                    exp.BitwiseLeftShift(this=this, expression=self._parse_term())
 6339                )
 6340            elif self._match_pair(TokenType.GT, TokenType.GT):
 6341                this = self.expression(
 6342                    exp.BitwiseRightShift(this=this, expression=self._parse_term())
 6343                )
 6344            elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS):
 6345                this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term())
 6346            else:
 6347                break
 6348
 6349        return this
 6350
 6351    def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None:
 6352        this = self._parse_factor(parse_mod=parse_mod)
 6353
 6354        while self._match_set(self.TERM):
 6355            klass = self.TERM[self._prev.token_type]
 6356            comments = self._prev_comments
 6357            expression = self._parse_factor(parse_mod=parse_mod)
 6358
 6359            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6360
 6361            if isinstance(this, exp.Collate):
 6362                self._normalize_collate(this)
 6363
 6364        return this
 6365
 6366    def _normalize_collate(self, collate: exp.Collate) -> None:
 6367        expr = collate.expression
 6368
 6369        # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise
 6370        # fallback to Identifier / Var
 6371        if isinstance(expr, exp.Column) and len(expr.parts) == 1:
 6372            ident = expr.this
 6373            if isinstance(ident, exp.Identifier):
 6374                collate.set("expression", ident if ident.quoted else exp.var(ident.name))
 6375
 6376    def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None:
 6377        parse_method = self._parse_factor_operand
 6378        this = self._parse_at_time_zone(parse_method())
 6379
 6380        while self._match_set(self.FACTOR, advance=False):
 6381            if not parse_mod and self._curr.token_type == TokenType.MOD:
 6382                break
 6383
 6384            self._advance()
 6385            klass = self.FACTOR[self._prev.token_type]
 6386            comments = self._prev_comments
 6387            expression = parse_method()
 6388
 6389            if not expression and klass is exp.IntDiv and self._prev.text.isalpha():
 6390                self._retreat(self._index - 1)
 6391                return this
 6392
 6393            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6394
 6395            if isinstance(this, exp.Div):
 6396                this.set("typed", self.dialect.TYPED_DIVISION)
 6397                this.set("safe", self.dialect.SAFE_DIVISION)
 6398
 6399        return this
 6400
 6401    def _parse_factor_operand(self) -> exp.Expr | None:
 6402        return self._parse_exponent() if self.EXPONENT else self._parse_unary()
 6403
 6404    def _parse_exponent(self) -> exp.Expr | None:
 6405        this = self._parse_unary()
 6406        while self._match_set(self.EXPONENT):
 6407            comments = self._prev_comments
 6408            this = self.expression(
 6409                self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()),
 6410                comments=comments,
 6411            )
 6412        return this
 6413
 6414    def _parse_unary(self) -> exp.Expr | None:
 6415        if self._match_set(self.UNARY_PARSERS):
 6416            return self.UNARY_PARSERS[self._prev.token_type](self)
 6417        return self._parse_type()
 6418
 6419    def _parse_type(
 6420        self, parse_interval: bool = True, fallback_to_identifier: bool = False
 6421    ) -> exp.Expr | None:
 6422        if not fallback_to_identifier and (atom := self._parse_atom()) is not None:
 6423            return atom
 6424
 6425        if interval := parse_interval and self._parse_interval():
 6426            return self._parse_column_ops(interval)
 6427
 6428        index = self._index
 6429        data_type = self._parse_types(check_func=True, allow_identifiers=False)
 6430
 6431        # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g.
 6432        # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>)
 6433        if isinstance(data_type, exp.Cast):
 6434            # This constructor can contain ops directly after it, for instance struct unnesting:
 6435            # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).*
 6436            return self._parse_column_ops(data_type)
 6437
 6438        if data_type:
 6439            index2 = self._index
 6440            this = self._parse_primary()
 6441
 6442            if isinstance(this, exp.Literal):
 6443                literal = this.name
 6444                this = self._parse_column_ops(this)
 6445
 6446                parser = self.TYPE_LITERAL_PARSERS.get(data_type.this)
 6447                if parser:
 6448                    return parser(self, this, data_type)
 6449
 6450                if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal):
 6451                    if data_type.is_type(exp.DType.TIMESTAMP):
 6452                        data_type = exp.DType.TIMESTAMPTZ.into_expr()
 6453                    elif data_type.is_type(exp.DType.TIME):
 6454                        data_type = exp.DType.TIMETZ.into_expr()
 6455
 6456                return self.expression(exp.Cast(this=this, to=data_type))
 6457
 6458            # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0)
 6459            # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 )
 6460            #
 6461            # If the index difference here is greater than 1, that means the parser itself must have
 6462            # consumed additional tokens such as the DECIMAL scale and precision in the above example.
 6463            #
 6464            # If it's not greater than 1, then it must be 1, because we've consumed at least the type
 6465            # keyword, meaning that the expressions arg of the DataType must have gotten set by a
 6466            # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to
 6467            # DECIMAL(38, 0)) in order to facilitate the data type's transpilation.
 6468            #
 6469            # In these cases, we don't really want to return the converted type, but instead retreat
 6470            # and try to parse a Column or Identifier in the section below.
 6471            if data_type.expressions and index2 - index > 1:
 6472                self._retreat(index2)
 6473                return self._parse_column_ops(data_type)
 6474
 6475            self._retreat(index)
 6476
 6477        if fallback_to_identifier:
 6478            return self._parse_id_var()
 6479
 6480        return self._parse_column()
 6481
 6482    def _parse_type_size(self) -> exp.DataTypeParam | None:
 6483        this = self._parse_type()
 6484        if not this:
 6485            return None
 6486
 6487        if isinstance(this, exp.Column) and not this.table:
 6488            this = exp.var(this.name.upper())
 6489
 6490        return self.expression(
 6491            exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True))
 6492        )
 6493
 6494    def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None:
 6495        type_name = identifier.name
 6496
 6497        while self._match(TokenType.DOT):
 6498            type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
 6499
 6500        return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True)
 6501
 6502    def _parse_types(
 6503        self,
 6504        check_func: bool = False,
 6505        schema: bool = False,
 6506        allow_identifiers: bool = True,
 6507        with_collation: bool = False,
 6508    ) -> exp.Expr | None:
 6509        index = self._index
 6510        this: exp.Expr | None = None
 6511
 6512        if self._match_set(self.TYPE_TOKENS):
 6513            type_token = self._prev.token_type
 6514        else:
 6515            type_token = None
 6516            identifier = allow_identifiers and self._parse_id_var(
 6517                any_token=False, tokens=(TokenType.VAR,)
 6518            )
 6519            if isinstance(identifier, exp.Identifier):
 6520                if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE:
 6521                    this = exp.DataType.build(identifier, udt=True)
 6522                else:
 6523                    try:
 6524                        tokens = self.dialect.tokenize(identifier.name)
 6525                    except TokenError:
 6526                        tokens = None
 6527
 6528                    if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS:
 6529                        if len(tokens) > 1:
 6530                            return exp.DataType.from_str(identifier.name, dialect=self.dialect)
 6531                    elif self.dialect.SUPPORTS_USER_DEFINED_TYPES:
 6532                        this = self._parse_user_defined_type(identifier)
 6533                    else:
 6534                        self._retreat(self._index - 1)
 6535                        return None
 6536            else:
 6537                return None
 6538
 6539        if type_token == TokenType.PSEUDO_TYPE:
 6540            return self.expression(exp.PseudoType(this=self._prev.text.upper()))
 6541
 6542        if type_token == TokenType.OBJECT_IDENTIFIER:
 6543            return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper()))
 6544
 6545        # https://materialize.com/docs/sql/types/map/
 6546        if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET):
 6547            key_type = self._parse_types(
 6548                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6549            )
 6550            if not self._match(TokenType.FARROW):
 6551                self._retreat(index)
 6552                return None
 6553
 6554            value_type = self._parse_types(
 6555                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6556            )
 6557            if not self._match(TokenType.R_BRACKET):
 6558                self._retreat(index)
 6559                return None
 6560
 6561            return exp.DataType(
 6562                this=exp.DType.MAP,
 6563                expressions=[key_type, value_type],
 6564                nested=True,
 6565            )
 6566
 6567        nested = type_token in self.NESTED_TYPE_TOKENS
 6568        is_struct = type_token in self.STRUCT_TYPE_TOKENS
 6569        is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS
 6570        expressions = None
 6571        maybe_func = False
 6572
 6573        if self._match(TokenType.L_PAREN):
 6574            if is_struct:
 6575                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6576            elif nested:
 6577                expressions = self._parse_csv(
 6578                    lambda: self._parse_types(
 6579                        check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6580                    )
 6581                )
 6582                if type_token == TokenType.NULLABLE and len(expressions) == 1:
 6583                    this = expressions[0]
 6584                    this.set("nullable", True)
 6585                    self._match_r_paren()
 6586                    return this
 6587            elif type_token in self.ENUM_TYPE_TOKENS:
 6588                expressions = self._parse_csv(self._parse_equality)
 6589            elif type_token == TokenType.JSON:
 6590                # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value)
 6591                # https://clickhouse.com/docs/sql-reference/data-types/newjson
 6592                expressions = self._parse_csv(self._parse_json_type_arg)
 6593            elif is_aggregate:
 6594                func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var(
 6595                    any_token=False, tokens=(TokenType.VAR, TokenType.ANY)
 6596                )
 6597                if not func_or_ident:
 6598                    return None
 6599                expressions = [func_or_ident]
 6600                if self._match(TokenType.COMMA):
 6601                    expressions.extend(
 6602                        self._parse_csv(
 6603                            lambda: self._parse_types(
 6604                                check_func=check_func,
 6605                                schema=schema,
 6606                                allow_identifiers=allow_identifiers,
 6607                            )
 6608                        )
 6609                    )
 6610            else:
 6611                expressions = self._parse_csv(self._parse_type_size)
 6612
 6613                # https://docs.snowflake.com/en/sql-reference/data-types-vector
 6614                if type_token == TokenType.VECTOR and len(expressions) == 2:
 6615                    expressions = self._parse_vector_expressions(expressions)
 6616
 6617            if not self._match(TokenType.R_PAREN):
 6618                self._retreat(index)
 6619                return None
 6620
 6621            maybe_func = True
 6622
 6623        values: list[exp.Expr] | None = None
 6624
 6625        if nested and self._match(TokenType.LT):
 6626            if is_struct:
 6627                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6628            else:
 6629                expressions = self._parse_csv(
 6630                    lambda: self._parse_types(
 6631                        check_func=check_func,
 6632                        schema=schema,
 6633                        allow_identifiers=allow_identifiers,
 6634                        with_collation=True,
 6635                    )
 6636                )
 6637
 6638            if not self._match(TokenType.GT):
 6639                self.raise_error("Expecting >")
 6640
 6641            if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)):
 6642                values = self._parse_csv(self._parse_disjunction)
 6643                if not values and is_struct:
 6644                    values = None
 6645                    self._retreat(self._index - 1)
 6646                else:
 6647                    self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN))
 6648
 6649        if type_token in self.TIMESTAMPS:
 6650            if self._match_text_seq("WITH", "TIME", "ZONE"):
 6651                maybe_func = False
 6652                tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ
 6653                this = exp.DataType(this=tz_type, expressions=expressions)
 6654            elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"):
 6655                maybe_func = False
 6656                this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions)
 6657            elif self._match_text_seq("WITHOUT", "TIME", "ZONE"):
 6658                maybe_func = False
 6659        elif type_token == TokenType.INTERVAL:
 6660            if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS:
 6661                unit = self._parse_var(upper=True)
 6662                if self._match_text_seq("TO"):
 6663                    unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True))
 6664
 6665                this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit))))
 6666            else:
 6667                this = self.expression(exp.DataType(this=exp.DType.INTERVAL))
 6668        elif type_token == TokenType.VOID:
 6669            this = exp.DataType(this=exp.DType.NULL)
 6670
 6671        if maybe_func and check_func:
 6672            index2 = self._index
 6673            peek = self._parse_string()
 6674
 6675            if not peek:
 6676                self._retreat(index)
 6677                return None
 6678
 6679            self._retreat(index2)
 6680
 6681        if not this:
 6682            assert type_token is not None
 6683            if self._match_text_seq("UNSIGNED"):
 6684                unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token)
 6685                if not unsigned_type_token:
 6686                    self.raise_error(f"Cannot convert {type_token.name} to unsigned.")
 6687
 6688                type_token = unsigned_type_token or type_token
 6689
 6690            # NULLABLE without parentheses can be a column (Presto/Trino)
 6691            if type_token == TokenType.NULLABLE and not expressions:
 6692                self._retreat(index)
 6693                return None
 6694
 6695            this = exp.DataType(
 6696                this=exp.DType[type_token.name],
 6697                expressions=expressions,
 6698                nested=nested,
 6699            )
 6700
 6701            # Empty arrays/structs are allowed
 6702            if values is not None:
 6703                cls = exp.Struct if is_struct else exp.Array
 6704                this = exp.cast(cls(expressions=values), this, copy=False)
 6705
 6706        elif expressions:
 6707            this.set("expressions", expressions)
 6708
 6709        # https://materialize.com/docs/sql/types/list/#type-name
 6710        while self._match(TokenType.LIST):
 6711            this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True)
 6712
 6713        index = self._index
 6714
 6715        # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3]
 6716        matched_array = self._match(TokenType.ARRAY)
 6717
 6718        while self._curr:
 6719            datatype_token = self._prev.token_type
 6720            matched_l_bracket = self._match(TokenType.L_BRACKET)
 6721
 6722            if (not matched_l_bracket and not matched_array) or (
 6723                datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET)
 6724            ):
 6725                # Postgres allows casting empty arrays such as ARRAY[]::INT[],
 6726                # not to be confused with the fixed size array parsing
 6727                break
 6728
 6729            matched_array = False
 6730            values = self._parse_csv(self._parse_disjunction) or None
 6731            if (
 6732                values
 6733                and not schema
 6734                and (
 6735                    not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS
 6736                    or datatype_token == TokenType.ARRAY
 6737                    or not self._match(TokenType.R_BRACKET, advance=False)
 6738                )
 6739            ):
 6740                # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB
 6741                # 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
 6742                self._retreat(index)
 6743                break
 6744
 6745            this = exp.DataType(
 6746                this=exp.DType.ARRAY, expressions=[this], values=values, nested=True
 6747            )
 6748            self._match(TokenType.R_BRACKET)
 6749
 6750        if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType):
 6751            converter = self.TYPE_CONVERTERS.get(this.this)
 6752            if converter:
 6753                this = converter(t.cast(exp.DataType, this))
 6754
 6755        if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE):
 6756            this.set("collate", self._parse_identifier() or self._parse_column())
 6757
 6758        return this
 6759
 6760    def _parse_json_type_arg(self) -> exp.Expr | None:
 6761        """Parse a single argument to ClickHouse's JSON type."""
 6762
 6763        # SKIP col or SKIP REGEXP 'pattern'
 6764        if self._match_text_seq("SKIP"):
 6765            regexp = self._match(TokenType.RLIKE)
 6766            arg = self._parse_column()
 6767            if isinstance(arg, exp.Column):
 6768                arg = arg.to_dot()
 6769            return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg))
 6770
 6771        param_or_col = self._parse_column()
 6772        if not isinstance(param_or_col, exp.Column):
 6773            return None
 6774
 6775        # Parameter: name=value (e.g., max_dynamic_paths=2)
 6776        if len(param_or_col.parts) == 1 and self._match(TokenType.EQ):
 6777            param = param_or_col.name
 6778            value = self._parse_primary()
 6779            return self.expression(exp.EQ(this=exp.var(param), expression=value))
 6780
 6781        # Column type hint: col_name Type
 6782        col = param_or_col.to_dot()
 6783        kind = self._parse_types(check_func=False, allow_identifiers=False)
 6784        return self.expression(exp.ColumnDef(this=col, kind=kind))
 6785
 6786    def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]:
 6787        return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]]
 6788
 6789    def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None:
 6790        index = self._index
 6791
 6792        if (
 6793            self._curr
 6794            and self._next
 6795            and self._curr.token_type in self.TYPE_TOKENS
 6796            and self._next.token_type in self.TYPE_TOKENS
 6797        ):
 6798            # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a
 6799            # type token. Without this, the list will be parsed as a type and we'll eventually crash
 6800            this = self._parse_id_var()
 6801        else:
 6802            this = (
 6803                self._parse_type(parse_interval=False, fallback_to_identifier=True)
 6804                or self._parse_id_var()
 6805            )
 6806
 6807        self._match(TokenType.COLON)
 6808
 6809        if (
 6810            type_required
 6811            and not isinstance(this, exp.DataType)
 6812            and not self._match_set(self.TYPE_TOKENS, advance=False)
 6813        ):
 6814            self._retreat(index)
 6815            return self._parse_types()
 6816
 6817        return self._parse_column_def(this)
 6818
 6819    def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None:
 6820        if not self._match_text_seq("AT", "TIME", "ZONE"):
 6821            return this
 6822        return self._parse_at_time_zone(
 6823            self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary()))
 6824        )
 6825
 6826    def _parse_atom(self) -> exp.Expr | None:
 6827        if (
 6828            self._curr.token_type in self.IDENTIFIER_TOKENS
 6829            and (column := self._parse_column()) is not None
 6830        ):
 6831            return column
 6832
 6833        token = self._curr
 6834        token_type = token.token_type
 6835
 6836        if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)):
 6837            return None
 6838
 6839        next_type = self._next.token_type
 6840
 6841        if (
 6842            next_type in self.COLUMN_OPERATORS
 6843            or next_type in self.COLUMN_POSTFIX_TOKENS
 6844            or (token_type == TokenType.STRING and next_type == TokenType.STRING)
 6845        ):
 6846            return None
 6847
 6848        self._advance()
 6849        return primary_parser(self, token)
 6850
 6851    def _parse_column(self) -> exp.Expr | None:
 6852        column: exp.Expr | None = self._parse_column_parts_fast()
 6853        if column is None:
 6854            this = self._parse_column_reference()
 6855            if not this:
 6856                this = self._parse_bracket(this)
 6857            column = self._parse_column_ops(this) if this else this
 6858
 6859        if column:
 6860            if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 6861                column.set("join_mark", self._match(TokenType.JOIN_MARKER))
 6862            if self.COLON_IS_VARIANT_EXTRACT:
 6863                column = self._parse_colon_as_variant_extract(column)
 6864
 6865        return column
 6866
 6867    def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None:
 6868        """Fast path for simple column and dot references (a, a.b, ...).
 6869
 6870        Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks
 6871        that nothing complex follows. If it does, retreats and returns None so
 6872        the slow path can handle it. For >4 parts, wraps in exp.Dot nodes.
 6873        """
 6874        index = self._index
 6875        parts: list[exp.Identifier] | None = None
 6876        all_comments: list[str] | None = None
 6877
 6878        while self._match_set(self.IDENTIFIER_TOKENS):
 6879            token = self._prev
 6880            comments = self._prev_comments
 6881
 6882            if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS:
 6883                self._retreat(index)
 6884                return None
 6885
 6886            has_dot = self._match(TokenType.DOT)
 6887            curr_tt = self._curr.token_type
 6888
 6889            if not has_dot:
 6890                if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS:
 6891                    self._retreat(index)
 6892                    return None
 6893            elif curr_tt not in self.IDENTIFIER_TOKENS:
 6894                self._retreat(index)
 6895                return None
 6896
 6897            if parts is None:
 6898                parts = []
 6899
 6900            if comments:
 6901                if all_comments is None:
 6902                    all_comments = []
 6903                all_comments.extend(comments)
 6904                self._prev_comments = []
 6905
 6906            parts.append(
 6907                self.expression(
 6908                    exp.Identifier(
 6909                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 6910                    ),
 6911                    token,
 6912                )
 6913            )
 6914
 6915            if not has_dot:
 6916                break
 6917
 6918        if parts is None:
 6919            return None
 6920
 6921        n = len(parts)
 6922
 6923        if n == 1:
 6924            column: exp.Column | exp.Dot = exp.Column(this=parts[0])
 6925        elif n == 2:
 6926            column = exp.Column(this=parts[1], table=parts[0])
 6927        elif n == 3:
 6928            column = exp.Column(this=parts[2], table=parts[1], db=parts[0])
 6929        else:
 6930            column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0])
 6931
 6932            for i in range(4, n):
 6933                column = exp.Dot(this=column, expression=parts[i])
 6934
 6935        if all_comments:
 6936            column.add_comments(all_comments)
 6937
 6938        return column
 6939
 6940    def _parse_column_reference(self) -> exp.Expr | None:
 6941        this = self._parse_field()
 6942        if (
 6943            not this
 6944            and self._match(TokenType.VALUES, advance=False)
 6945            and self.VALUES_FOLLOWED_BY_PAREN
 6946            and (not self._next or self._next.token_type != TokenType.L_PAREN)
 6947        ):
 6948            this = self._parse_id_var()
 6949
 6950        if isinstance(this, exp.Identifier):
 6951            # We bubble up comments from the Identifier to the Column
 6952            this = self.expression(exp.Column(this=this), comments=this.pop_comments())
 6953
 6954        return this
 6955
 6956    def _build_json_extract(
 6957        self,
 6958        this: exp.Expr | None,
 6959        path_parts: list[exp.JSONPathPart],
 6960    ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]:
 6961        if len(path_parts) > 1:
 6962            this = self.expression(
 6963                exp.JSONExtract(
 6964                    this=this,
 6965                    expression=exp.JSONPath(expressions=path_parts),
 6966                    variant_extract=True,
 6967                    requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION,
 6968                )
 6969            )
 6970            path_parts = [exp.JSONPathRoot()]
 6971
 6972        return this, path_parts
 6973
 6974    def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None:
 6975        path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()]
 6976
 6977        while self._match(TokenType.COLON):
 6978            if not self.COLON_CHAIN_IS_SINGLE_EXTRACT:
 6979                this, path_parts = self._build_json_extract(this, path_parts)
 6980
 6981            key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 6982
 6983            if key:
 6984                quoted = isinstance(key, exp.Identifier) and key.quoted
 6985                path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted))
 6986
 6987            while True:
 6988                if self._match(TokenType.DOT):
 6989                    next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 6990
 6991                    if next_key:
 6992                        quoted = isinstance(next_key, exp.Identifier) and next_key.quoted
 6993                        path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted))
 6994                elif self._match(TokenType.L_BRACKET):
 6995                    bracket_expr = self._parse_bracket_key_value()
 6996
 6997                    if not self._match(TokenType.R_BRACKET):
 6998                        self.raise_error("Expected ]")
 6999
 7000                    if bracket_expr:
 7001                        if bracket_expr.is_string:
 7002                            path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True))
 7003                        elif bracket_expr.is_star:
 7004                            path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard()))
 7005                        elif bracket_expr.is_number:
 7006                            path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py()))
 7007                        else:
 7008                            this, path_parts = self._build_json_extract(this, path_parts)
 7009
 7010                            this = self.expression(
 7011                                exp.Bracket(
 7012                                    this=this, expressions=[bracket_expr], json_access=True
 7013                                ),
 7014                            )
 7015
 7016                elif self._match(TokenType.DCOLON):
 7017                    this, path_parts = self._build_json_extract(this, path_parts)
 7018
 7019                    cast_type = self._parse_types()
 7020                    if cast_type:
 7021                        this = self.expression(exp.Cast(this=this, to=cast_type))
 7022                    else:
 7023                        self.raise_error("Expected type after '::'")
 7024                else:
 7025                    break
 7026
 7027        this, _ = self._build_json_extract(this, path_parts)
 7028
 7029        return this
 7030
 7031    def _parse_dcolon(self) -> exp.Expr | None:
 7032        return self._parse_types()
 7033
 7034    def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None:
 7035        while self._curr.token_type in self.BRACKETS:
 7036            this = self._parse_bracket(this)
 7037
 7038        column_operators = self.COLUMN_OPERATORS
 7039        cast_column_operators = self.CAST_COLUMN_OPERATORS
 7040        while self._curr:
 7041            op_token = self._curr.token_type
 7042
 7043            if op_token not in column_operators:
 7044                break
 7045            op = column_operators[op_token]
 7046            self._advance()
 7047
 7048            if op_token in cast_column_operators:
 7049                field = self._parse_dcolon()
 7050                if not field:
 7051                    self.raise_error("Expected type")
 7052            elif op and self._curr:
 7053                field = self._parse_column_reference() or self._parse_bitwise()
 7054                if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False):
 7055                    field = self._parse_column_ops(field)
 7056            else:
 7057                dot = self._is_connected() and self._prev.token_type == TokenType.DOT
 7058                field = self._parse_field(any_token=True, anonymous_func=True)
 7059
 7060                # In t.true, t.null we should produce an Identifier node
 7061                if dot and isinstance(field, (exp.Null, exp.Boolean)):
 7062                    field = self.expression(
 7063                        exp.Identifier(this=self._prev.text),
 7064                        comments=field.comments,
 7065                    )
 7066
 7067            # Function calls can be qualified, e.g., x.y.FOO()
 7068            # This converts the final AST to a series of Dots leading to the function call
 7069            # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules
 7070            if isinstance(field, (exp.Func, exp.Window)) and this:
 7071                this = this.transform(
 7072                    lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n
 7073                )
 7074
 7075            if op:
 7076                this = op(self, this, field)
 7077            elif isinstance(this, exp.Column) and not this.args.get("catalog"):
 7078                this = self.expression(
 7079                    exp.Column(
 7080                        this=field,
 7081                        table=this.this,
 7082                        db=this.args.get("table"),
 7083                        catalog=this.args.get("db"),
 7084                    ),
 7085                    comments=this.comments,
 7086                )
 7087            elif isinstance(field, exp.Window):
 7088                # Move the exp.Dot's to the window's function
 7089                window_func = self.expression(exp.Dot(this=this, expression=field.this))
 7090                field.set("this", window_func)
 7091                this = field
 7092            else:
 7093                this = self.expression(exp.Dot(this=this, expression=field))
 7094
 7095            if field and field.comments:
 7096                t.cast(exp.Expr, this).add_comments(field.pop_comments())
 7097
 7098            this = self._parse_bracket(this)
 7099
 7100        return this
 7101
 7102    def _parse_paren(self) -> exp.Expr | None:
 7103        if not self._match(TokenType.L_PAREN):
 7104            return None
 7105
 7106        comments = self._prev_comments
 7107        query = self._parse_select()
 7108
 7109        if query:
 7110            expressions = [query]
 7111        else:
 7112            expressions = self._parse_expressions()
 7113
 7114        this = seq_get(expressions, 0)
 7115
 7116        if not this and self._match(TokenType.R_PAREN, advance=False):
 7117            this = self.expression(exp.Tuple())
 7118        elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA:
 7119            this = self.expression(exp.Tuple(expressions=expressions))
 7120        elif isinstance(this, exp.UNWRAPPED_QUERIES):
 7121            this = self._parse_subquery(this=this, parse_alias=False)
 7122        elif isinstance(this, (exp.Subquery, exp.Values)):
 7123            this = self._parse_subquery(
 7124                this=self._parse_query_modifiers(self._parse_set_operations(this)),
 7125                parse_alias=False,
 7126            )
 7127        else:
 7128            this = self.expression(exp.Paren(this=this))
 7129
 7130        if this:
 7131            this.add_comments(comments)
 7132
 7133        self._match_r_paren(expression=this)
 7134
 7135        if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc):
 7136            return self._parse_window(this)
 7137
 7138        return this
 7139
 7140    def _parse_primary(self) -> exp.Expr | None:
 7141        if self._match_set(self.PRIMARY_PARSERS):
 7142            token_type = self._prev.token_type
 7143            primary = self.PRIMARY_PARSERS[token_type](self, self._prev)
 7144
 7145            if token_type == TokenType.STRING:
 7146                expressions = [primary]
 7147                while self._match(TokenType.STRING, advance=False):
 7148                    if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED:
 7149                        self.raise_error(
 7150                            "Adjacent string literals need to be separated by whitespace or comments"
 7151                        )
 7152
 7153                    self._advance()
 7154                    expressions.append(exp.Literal.string(self._prev.text))
 7155
 7156                if len(expressions) > 1:
 7157                    return self.expression(
 7158                        exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE)
 7159                    )
 7160
 7161            return primary
 7162
 7163        if self._match_pair(TokenType.DOT, TokenType.NUMBER):
 7164            return exp.Literal.number(f"0.{self._prev.text}")
 7165
 7166        return self._parse_paren()
 7167
 7168    def _parse_field(
 7169        self,
 7170        any_token: bool = False,
 7171        tokens: t.Collection[TokenType] | None = None,
 7172        anonymous_func: bool = False,
 7173    ) -> exp.Expr | None:
 7174        after_dot = (
 7175            self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT
 7176        )
 7177
 7178        if anonymous_func:
 7179            field = (
 7180                self._parse_function(anonymous=anonymous_func, any_token=any_token)
 7181                or self._parse_primary()
 7182            )
 7183        else:
 7184            field = self._parse_primary() or self._parse_function(
 7185                anonymous=anonymous_func, any_token=any_token
 7186            )
 7187
 7188        field = field or self._parse_id_var(any_token=any_token, tokens=tokens)
 7189
 7190        if after_dot and isinstance(field, exp.Literal) and field.is_number:
 7191            name = field.name
 7192            if self._is_connected() and self._parse_var(any_token=True):
 7193                name += self._prev.text
 7194
 7195            field = exp.Identifier(this=name, quoted=True).update_positions(field)
 7196
 7197        return field
 7198
 7199    def _parse_function(
 7200        self,
 7201        functions: dict[str, t.Callable] | None = None,
 7202        anonymous: bool = False,
 7203        optional_parens: bool = True,
 7204        any_token: bool = False,
 7205    ) -> exp.Expr | None:
 7206        # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this)
 7207        # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences
 7208        fn_syntax = False
 7209        if (
 7210            self._match(TokenType.L_BRACE, advance=False)
 7211            and self._next
 7212            and self._next.text.upper() == "FN"
 7213        ):
 7214            self._advance(2)
 7215            fn_syntax = True
 7216
 7217        func = self._parse_function_call(
 7218            functions=functions,
 7219            anonymous=anonymous,
 7220            optional_parens=optional_parens,
 7221            any_token=any_token,
 7222        )
 7223
 7224        if fn_syntax:
 7225            self._match(TokenType.R_BRACE)
 7226
 7227        return func
 7228
 7229    def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]:
 7230        return self._parse_csv(lambda: self._parse_lambda(alias=alias))
 7231
 7232    def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren:
 7233        args = self._parse_function_args(alias=False)
 7234        if not args:
 7235            self.raise_error("Expected at least one argument")
 7236
 7237        # Wrapped so the connector keeps its precedence in the parent context
 7238        return exp.Paren(this=connector(*args, copy=False))
 7239
 7240    def _parse_function_call(
 7241        self,
 7242        functions: dict[str, t.Callable] | None = None,
 7243        anonymous: bool = False,
 7244        optional_parens: bool = True,
 7245        any_token: bool = False,
 7246    ) -> exp.Expr | None:
 7247        if not self._curr:
 7248            return None
 7249
 7250        comments = self._curr.comments
 7251        prev = self._prev
 7252        token = self._curr
 7253        token_type = self._curr.token_type
 7254        this: str | exp.Expr = self._curr.text
 7255        upper = self._curr.text.upper()
 7256
 7257        after_dot = prev.token_type == TokenType.DOT
 7258        parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper)
 7259        if (
 7260            optional_parens
 7261            and parser
 7262            and token_type not in self.INVALID_FUNC_NAME_TOKENS
 7263            and not after_dot
 7264        ):
 7265            self._advance()
 7266            return self._parse_window(parser(self))
 7267
 7268        if self._next.token_type != TokenType.L_PAREN:
 7269            if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot:
 7270                self._advance()
 7271                return self.expression(self.NO_PAREN_FUNCTIONS[token_type]())
 7272
 7273            return None
 7274
 7275        if any_token:
 7276            if token_type in self.RESERVED_TOKENS:
 7277                return None
 7278        elif token_type not in self.FUNC_TOKENS:
 7279            return None
 7280
 7281        self._advance(2)
 7282
 7283        parser = self.FUNCTION_PARSERS.get(upper)
 7284        if parser and not anonymous:
 7285            result = parser(self)
 7286        else:
 7287            subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type)
 7288
 7289            if subquery_predicate:
 7290                expr = None
 7291                if self._curr.token_type in self.SUBQUERY_TOKENS:
 7292                    expr = self._parse_select()
 7293                    self._match_r_paren()
 7294                elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE):
 7295                    # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like
 7296                    # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren
 7297                    self._advance(-1)
 7298                    expr = self._parse_bitwise()
 7299
 7300                if expr:
 7301                    return self.expression(subquery_predicate(this=expr), comments=comments)
 7302
 7303            if functions is None:
 7304                functions = self.FUNCTIONS
 7305
 7306            function = functions.get(upper)
 7307            known_function = function and not anonymous
 7308
 7309            alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS
 7310            args = self._parse_function_args(alias)
 7311
 7312            post_func_comments = self._curr.comments if self._curr else None
 7313            if known_function and post_func_comments:
 7314                # If the user-inputted comment "/* sqlglot.anonymous */" is following the function
 7315                # call we'll construct it as exp.Anonymous, even if it's "known"
 7316                if any(
 7317                    comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS)
 7318                    for comment in post_func_comments
 7319                ):
 7320                    known_function = False
 7321
 7322            if alias and known_function:
 7323                args = self._kv_to_prop_eq(args)
 7324
 7325            if known_function:
 7326                func_builder = t.cast(t.Callable, function)
 7327
 7328                # mypyc compiled functions don't have __code__, so we use
 7329                # try/except to check if func_builder accepts 'dialect'.
 7330                try:
 7331                    func = func_builder(args)
 7332                except TypeError:
 7333                    func = func_builder(args, dialect=self.dialect)
 7334
 7335                func = self.validate_expression(func, args)
 7336                if self.dialect.PRESERVE_ORIGINAL_NAMES:
 7337                    func.meta["name"] = this
 7338
 7339                result = func
 7340            else:
 7341                if token_type == TokenType.IDENTIFIER:
 7342                    this = exp.Identifier(this=this, quoted=True).update_positions(token)
 7343
 7344                result = self.expression(exp.Anonymous(this=this, expressions=args))
 7345
 7346            result = result.update_positions(token)
 7347
 7348        if isinstance(result, exp.Expr):
 7349            result.add_comments(comments)
 7350
 7351        if parser:
 7352            self._match(TokenType.R_PAREN, expression=result)
 7353        else:
 7354            self._match_r_paren(result)
 7355        return self._parse_window(result)
 7356
 7357    def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr:
 7358        return expression
 7359
 7360    def _kv_to_prop_eq(
 7361        self, expressions: list[exp.Expr], parse_map: bool = False
 7362    ) -> list[exp.Expr]:
 7363        transformed = []
 7364
 7365        for index, e in enumerate(expressions):
 7366            if isinstance(e, self.KEY_VALUE_DEFINITIONS):
 7367                if isinstance(e, exp.Alias):
 7368                    e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this))
 7369
 7370                if not isinstance(e, exp.PropertyEQ):
 7371                    e = self.expression(
 7372                        exp.PropertyEQ(
 7373                            this=e.this if parse_map else exp.to_identifier(e.this.name),
 7374                            expression=e.expression,
 7375                        )
 7376                    )
 7377
 7378                if isinstance(e.this, exp.Column):
 7379                    e.this.replace(e.this.this)
 7380            else:
 7381                e = self._to_prop_eq(e, index)
 7382
 7383            transformed.append(e)
 7384
 7385        return transformed
 7386
 7387    def _parse_function_properties(self) -> exp.Properties | None:
 7388        # Skip the generic `key = value` fallback in _parse_property since this
 7389        # runs post-AS where a function body like `name = expr` can be misread
 7390        # as a property.
 7391        properties = []
 7392        while True:
 7393            if self._match_texts(self.PROPERTY_PARSERS):
 7394                keyword = self._prev.text.upper()
 7395                prop = self.PROPERTY_PARSERS[keyword](self)
 7396            elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS):
 7397                keyword = self._prev.text.upper()
 7398                prop = self.PROPERTY_PARSERS[keyword](self, default=True)
 7399            else:
 7400                break
 7401            if not prop:
 7402                self.raise_error(f"Failed to parse property '{keyword}'")
 7403                break
 7404            for p in ensure_list(prop):
 7405                properties.append(p)
 7406
 7407        return self.expression(exp.Properties(expressions=properties)) if properties else None
 7408
 7409    def _parse_user_defined_function_expression(self) -> exp.Expr | None:
 7410        return self._parse_statement()
 7411
 7412    def _parse_function_parameter(self) -> exp.Expr | None:
 7413        return self._parse_column_def(this=self._parse_id_var(), computed_column=False)
 7414
 7415    def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None:
 7416        this = self._parse_table_parts(schema=True)
 7417
 7418        if not self._match(TokenType.L_PAREN):
 7419            return this
 7420
 7421        expressions = self._parse_csv(self._parse_function_parameter)
 7422        self._match_r_paren()
 7423        return self.expression(
 7424            exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True)
 7425        )
 7426
 7427    def _parse_macro_overloads(
 7428        self,
 7429        this: exp.UserDefinedFunction,
 7430        first_body: exp.Expr,
 7431        first_is_table: bool = False,
 7432    ) -> exp.MacroOverloads:
 7433        overloads = [
 7434            self.expression(
 7435                exp.MacroOverload(
 7436                    this=first_body,
 7437                    expressions=this.expressions or None,
 7438                    is_table=first_is_table,
 7439                )
 7440            )
 7441        ]
 7442        this.set("expressions", None)
 7443        this.set("wrapped", False)
 7444
 7445        while self._match(TokenType.COMMA):
 7446            if not self._match(TokenType.L_PAREN):
 7447                break
 7448
 7449            params = self._parse_csv(self._parse_function_parameter)
 7450            self._match_r_paren()
 7451
 7452            if not self._match(TokenType.ALIAS):
 7453                break
 7454
 7455            is_table = self._match(TokenType.TABLE)
 7456            body = self._parse_expression()
 7457            macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table)
 7458            overloads.append(self.expression(macro))
 7459
 7460        return self.expression(exp.MacroOverloads(expressions=overloads))
 7461
 7462    def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier:
 7463        literal = self._parse_primary()
 7464        if literal:
 7465            return self.expression(exp.Introducer(this=token.text, expression=literal), token)
 7466
 7467        return self._identifier_expression(token)
 7468
 7469    def _parse_session_parameter(self) -> exp.SessionParameter:
 7470        kind = None
 7471        this = self._parse_id_var() or self._parse_primary()
 7472
 7473        if this and self._match(TokenType.DOT):
 7474            kind = this.name
 7475            this = self._parse_var() or self._parse_primary()
 7476
 7477        return self.expression(exp.SessionParameter(this=this, kind=kind))
 7478
 7479    def _parse_lambda_arg(self) -> exp.Expr | None:
 7480        return self._parse_id_var()
 7481
 7482    def _parse_lambda(self, alias: bool = False) -> exp.Expr | None:
 7483        next_token_type = self._next.token_type
 7484
 7485        # Fast path: simple atom (column, literal, null, bool) followed by , or )
 7486        if (
 7487            next_token_type in self.LAMBDA_ARG_TERMINATORS
 7488            and (atom := self._parse_atom()) is not None
 7489        ):
 7490            return atom
 7491
 7492        index = self._index
 7493
 7494        if self._match(TokenType.L_PAREN):
 7495            expressions = t.cast(
 7496                list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg)
 7497            )
 7498
 7499            if not self._match(TokenType.R_PAREN):
 7500                self._retreat(index)
 7501            elif self._match_set(self.LAMBDAS):
 7502                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7503            else:
 7504                self._retreat(index)
 7505        elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS:
 7506            expressions = [self._parse_lambda_arg()]
 7507
 7508            if self._match_set(self.LAMBDAS):
 7509                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7510
 7511            self._retreat(index)
 7512
 7513        this: exp.Expr | None
 7514
 7515        if self._match(TokenType.DISTINCT):
 7516            this = self.expression(
 7517                exp.Distinct(expressions=self._parse_csv(self._parse_disjunction))
 7518            )
 7519        else:
 7520            self._match(TokenType.ALL)  # ALL is the default/no-op aggregate modifier (SQL-92)
 7521            this = self._parse_select_or_expression(alias=alias)
 7522
 7523        return self._parse_limit(
 7524            self._parse_respect_or_ignore_nulls(
 7525                self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this)))
 7526            )
 7527        )
 7528
 7529    def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7530        index = self._index
 7531        if not self._match(TokenType.L_PAREN):
 7532            return this
 7533
 7534        # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>),
 7535        # expr can be of both types
 7536        if self._match_set(self.SELECT_START_TOKENS):
 7537            self._retreat(index)
 7538            return this
 7539        args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def())
 7540        self._match_r_paren()
 7541        return self.expression(exp.Schema(this=this, expressions=args))
 7542
 7543    def _parse_field_def(self) -> exp.Expr | None:
 7544        return self._parse_column_def(self._parse_field(any_token=True))
 7545
 7546    def _parse_column_def(
 7547        self, this: exp.Expr | None, computed_column: bool = True
 7548    ) -> exp.Expr | None:
 7549        # column defs are not really columns, they're identifiers
 7550        if isinstance(this, exp.Column):
 7551            this = this.this
 7552
 7553        if not computed_column:
 7554            self._match(TokenType.ALIAS)
 7555
 7556        kind = self._parse_types(schema=True)
 7557
 7558        if self._match_text_seq("FOR", "ORDINALITY"):
 7559            return self.expression(exp.ColumnDef(this=this, ordinality=True))
 7560
 7561        constraints: list[exp.Expr] = []
 7562
 7563        if (not kind and self._match(TokenType.ALIAS)) or self._match_texts(
 7564            ("ALIAS", "MATERIALIZED")
 7565        ):
 7566            # Match storage before _parse_types so STORED is not treated as a data type
 7567            # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`).
 7568            persisted = self._prev.text.upper() == "MATERIALIZED"
 7569            expression = self._parse_disjunction()
 7570            if not persisted:
 7571                if self._match_text_seq("PERSISTED"):
 7572                    persisted = True
 7573                elif self._match_texts(("STORED", "VIRTUAL")):
 7574                    persisted = self._prev.text.upper() == "STORED"
 7575            constraint_kind = exp.ComputedColumnConstraint(
 7576                this=expression,
 7577                persisted=persisted,
 7578                data_type=exp.Var(this="AUTO")
 7579                if self._match_text_seq("AUTO")
 7580                else self._parse_types(),
 7581                not_null=self._match_pair(TokenType.NOT, TokenType.NULL),
 7582            )
 7583            constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind)))
 7584        elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False):
 7585            in_out_constraint = self.expression(
 7586                exp.InOutColumnConstraint(
 7587                    input_=self._match(TokenType.IN), output=self._match(TokenType.OUT)
 7588                )
 7589            )
 7590            constraints.append(in_out_constraint)
 7591            kind = self._parse_types()
 7592        elif (
 7593            kind
 7594            and self._match(TokenType.ALIAS, advance=False)
 7595            and (
 7596                not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
 7597                or self._next.token_type == TokenType.L_PAREN
 7598            )
 7599        ):
 7600            self._advance()
 7601            constraints.append(
 7602                self.expression(
 7603                    exp.ColumnConstraint(
 7604                        kind=exp.ComputedColumnConstraint(
 7605                            this=self._parse_disjunction(),
 7606                            persisted=self._match_texts(("STORED", "VIRTUAL"))
 7607                            and self._prev.text.upper() == "STORED",
 7608                        )
 7609                    )
 7610                )
 7611            )
 7612
 7613        while True:
 7614            constraint = self._parse_column_constraint()
 7615            if not constraint:
 7616                break
 7617            constraints.append(constraint)
 7618
 7619        if not kind and not constraints:
 7620            return this
 7621
 7622        position = None
 7623        if self._match_texts(("FIRST", "AFTER")):
 7624            pos = self._prev.text
 7625            position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos))
 7626
 7627        return self.expression(
 7628            exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position)
 7629        )
 7630
 7631    def _parse_auto_increment(
 7632        self,
 7633    ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint:
 7634        start = None
 7635        increment = None
 7636        order = None
 7637
 7638        if self._match(TokenType.L_PAREN, advance=False):
 7639            args = self._parse_wrapped_csv(self._parse_bitwise)
 7640            start = seq_get(args, 0)
 7641            increment = seq_get(args, 1)
 7642
 7643        # The remaining parts form an unordered bag and any of them can be omitted, in which
 7644        # case the engine falls back to its own default, so they're parsed independently.
 7645        while True:
 7646            if self._match_text_seq("START"):
 7647                start = self._parse_bitwise()
 7648            elif self._match_text_seq("INCREMENT"):
 7649                increment = self._parse_bitwise()
 7650            elif self._match_text_seq("ORDER"):
 7651                order = True
 7652            elif self._match_text_seq("NOORDER"):
 7653                order = False
 7654            else:
 7655                break
 7656
 7657        if start or increment or order is not None:
 7658            return exp.GeneratedAsIdentityColumnConstraint(
 7659                start=start, increment=increment, this=False, order=order
 7660            )
 7661
 7662        return exp.AutoIncrementColumnConstraint()
 7663
 7664    def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None:
 7665        if not self._match(TokenType.L_PAREN, advance=False):
 7666            return None
 7667
 7668        return self.expression(
 7669            exp.CheckColumnConstraint(
 7670                this=self._parse_wrapped(self._parse_assignment),
 7671                enforced=self._match_text_seq("ENFORCED"),
 7672            )
 7673        )
 7674
 7675    def _parse_auto_property(self) -> exp.AutoRefreshProperty | None:
 7676        if not self._match_text_seq("REFRESH"):
 7677            self._retreat(self._index - 1)
 7678            return None
 7679        return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True)))
 7680
 7681    def _parse_compress(self) -> exp.CompressColumnConstraint:
 7682        if self._match(TokenType.L_PAREN, advance=False):
 7683            return self.expression(
 7684                exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise))
 7685            )
 7686
 7687        return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise()))
 7688
 7689    def _parse_generated_as_identity(
 7690        self,
 7691    ) -> (
 7692        exp.GeneratedAsIdentityColumnConstraint
 7693        | exp.ComputedColumnConstraint
 7694        | exp.GeneratedAsRowColumnConstraint
 7695    ):
 7696        if self._match_text_seq("BY", "DEFAULT"):
 7697            on_null = self._match_pair(TokenType.ON, TokenType.NULL)
 7698            this = self.expression(
 7699                exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null)
 7700            )
 7701        else:
 7702            self._match_text_seq("ALWAYS")
 7703            this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True))
 7704
 7705        self._match(TokenType.ALIAS)
 7706
 7707        if self._match_text_seq("ROW"):
 7708            start = self._match_text_seq("START")
 7709            if not start:
 7710                self._match(TokenType.END)
 7711            hidden = self._match_text_seq("HIDDEN")
 7712            return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden))
 7713
 7714        identity = self._match_text_seq("IDENTITY")
 7715
 7716        if self._match(TokenType.L_PAREN):
 7717            if self._match_text_seq("START", "WITH"):
 7718                this.set("start", self._parse_bitwise())
 7719            if self._match_text_seq("INCREMENT", "BY"):
 7720                this.set("increment", self._parse_bitwise())
 7721            if self._match_text_seq("MINVALUE"):
 7722                this.set("minvalue", self._parse_bitwise())
 7723            if self._match_text_seq("MAXVALUE"):
 7724                this.set("maxvalue", self._parse_bitwise())
 7725
 7726            if self._match_text_seq("CYCLE"):
 7727                this.set("cycle", True)
 7728            elif self._match_text_seq("NO", "CYCLE"):
 7729                this.set("cycle", False)
 7730
 7731            if not identity:
 7732                this.set("expression", self._parse_range())
 7733            elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False):
 7734                args = self._parse_csv(self._parse_bitwise)
 7735                this.set("start", seq_get(args, 0))
 7736                this.set("increment", seq_get(args, 1))
 7737
 7738            self._match_r_paren()
 7739
 7740        return this
 7741
 7742    def _parse_inline(self) -> exp.InlineLengthColumnConstraint:
 7743        self._match_text_seq("LENGTH")
 7744        return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise()))
 7745
 7746    def _parse_not_constraint(self) -> exp.Expr | None:
 7747        if self._match_text_seq("NULL"):
 7748            return self.expression(exp.NotNullColumnConstraint())
 7749        if self._match_text_seq("CASESPECIFIC"):
 7750            return self.expression(exp.CaseSpecificColumnConstraint(not_=True))
 7751        if self._match_text_seq("FOR", "REPLICATION"):
 7752            return self.expression(exp.NotForReplicationColumnConstraint())
 7753
 7754        # Unconsume the `NOT` token
 7755        self._retreat(self._index - 1)
 7756        return None
 7757
 7758    def _parse_column_constraint(self) -> exp.Expr | None:
 7759        this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None
 7760
 7761        procedure_option_follows = (
 7762            self._match(TokenType.WITH, advance=False)
 7763            and self._next
 7764            and self._next.text.upper() in self.PROCEDURE_OPTIONS
 7765        )
 7766
 7767        if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS):
 7768            constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self)
 7769            if not constraint:
 7770                self._retreat(self._index - 1)
 7771                return None
 7772
 7773            return self.expression(exp.ColumnConstraint(this=this, kind=constraint))
 7774
 7775        if self._match_text_seq("CHARACTER", "SET"):
 7776            return self.expression(
 7777                exp.ColumnConstraint(
 7778                    this=this,
 7779                    kind=self.expression(
 7780                        exp.CharacterSetColumnConstraint(this=self._parse_var_or_string())
 7781                    ),
 7782                )
 7783            )
 7784
 7785        return this
 7786
 7787    def _parse_constraint(self) -> exp.Expr | None:
 7788        if not self._match(TokenType.CONSTRAINT):
 7789            return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS)
 7790
 7791        return self.expression(
 7792            exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints())
 7793        )
 7794
 7795    def _parse_unnamed_constraints(self) -> list[exp.Expr]:
 7796        constraints = []
 7797        while True:
 7798            constraint = self._parse_unnamed_constraint() or self._parse_function()
 7799            if not constraint:
 7800                break
 7801            constraints.append(constraint)
 7802
 7803        return constraints
 7804
 7805    def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None:
 7806        index = self._index
 7807
 7808        if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts(
 7809            constraints or self.CONSTRAINT_PARSERS
 7810        ):
 7811            return None
 7812
 7813        constraint_key = self._prev.text.upper()
 7814        if constraint_key not in self.CONSTRAINT_PARSERS:
 7815            self.raise_error(f"No parser found for schema constraint {constraint_key}.")
 7816
 7817        result = self.CONSTRAINT_PARSERS[constraint_key](self)
 7818        if not result:
 7819            self._retreat(index)
 7820
 7821        return result
 7822
 7823    def _parse_unique_key(self) -> exp.Expr | None:
 7824        if (
 7825            self._curr
 7826            and self._curr.token_type != TokenType.IDENTIFIER
 7827            and self._curr.text.upper() in self.CONSTRAINT_PARSERS
 7828        ):
 7829            return None
 7830        return self._parse_id_var(any_token=False)
 7831
 7832    def _parse_unique(self) -> exp.UniqueColumnConstraint:
 7833        self._match_texts(("KEY", "INDEX"))
 7834        return self.expression(
 7835            exp.UniqueColumnConstraint(
 7836                nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"),
 7837                this=self._parse_schema(self._parse_unique_key()),
 7838                index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text,
 7839                on_conflict=self._parse_on_conflict(),
 7840                options=self._parse_key_constraint_options(),
 7841            )
 7842        )
 7843
 7844    def _parse_key_constraint_options(self) -> list[str]:
 7845        options = []
 7846        while True:
 7847            if not self._curr:
 7848                break
 7849
 7850            if self._match(TokenType.ON):
 7851                action = None
 7852                on = self._advance_any() and self._prev.text
 7853
 7854                if self._match_text_seq("NO", "ACTION"):
 7855                    action = "NO ACTION"
 7856                elif self._match_text_seq("CASCADE"):
 7857                    action = "CASCADE"
 7858                elif self._match_text_seq("RESTRICT"):
 7859                    action = "RESTRICT"
 7860                elif self._match_pair(TokenType.SET, TokenType.NULL):
 7861                    action = "SET NULL"
 7862                elif self._match_pair(TokenType.SET, TokenType.DEFAULT):
 7863                    action = "SET DEFAULT"
 7864                else:
 7865                    self.raise_error("Invalid key constraint")
 7866
 7867                options.append(f"ON {on} {action}")
 7868            else:
 7869                var = self._parse_var_from_options(
 7870                    self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False
 7871                )
 7872                if not var:
 7873                    break
 7874                options.append(var.name)
 7875
 7876        return options
 7877
 7878    def _parse_references(self, match: bool = True) -> exp.Reference | None:
 7879        if match and not self._match(TokenType.REFERENCES):
 7880            return None
 7881
 7882        expressions: list | None = None
 7883        this = self._parse_table(schema=True)
 7884        options = self._parse_key_constraint_options()
 7885        return self.expression(exp.Reference(this=this, expressions=expressions, options=options))
 7886
 7887    def _parse_foreign_key(self) -> exp.ForeignKey:
 7888        expressions = (
 7889            self._parse_wrapped_id_vars()
 7890            if not self._match(TokenType.REFERENCES, advance=False)
 7891            else None
 7892        )
 7893        reference = self._parse_references()
 7894        on_options = {}
 7895
 7896        while self._match(TokenType.ON):
 7897            if not self._match_set((TokenType.DELETE, TokenType.UPDATE)):
 7898                self.raise_error("Expected DELETE or UPDATE")
 7899
 7900            kind = self._prev.text.lower()
 7901
 7902            if self._match_text_seq("NO", "ACTION"):
 7903                action = "NO ACTION"
 7904            elif self._match(TokenType.SET):
 7905                self._match_set((TokenType.NULL, TokenType.DEFAULT))
 7906                action = "SET " + self._prev.text.upper()
 7907            else:
 7908                self._advance()
 7909                action = self._prev.text.upper()
 7910
 7911            on_options[kind] = action
 7912
 7913        return self.expression(
 7914            exp.ForeignKey(
 7915                expressions=expressions,
 7916                reference=reference,
 7917                options=self._parse_key_constraint_options(),
 7918                **on_options,
 7919            )
 7920        )
 7921
 7922    def _parse_primary_key_part(self) -> exp.Expr | None:
 7923        return self._parse_field()
 7924
 7925    def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None:
 7926        if not self._match_text_seq("FOR", "SYSTEM_TIME"):
 7927            self._retreat(self._index - 1)
 7928            return None
 7929
 7930        id_vars = self._parse_wrapped_id_vars()
 7931        return self.expression(
 7932            exp.PeriodForSystemTimeConstraint(
 7933                this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1)
 7934            )
 7935        )
 7936
 7937    def _parse_primary_key(
 7938        self,
 7939        wrapped_optional: bool = False,
 7940        in_props: bool = False,
 7941        named_primary_key: bool = False,
 7942    ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:
 7943        desc = (
 7944            self._prev.token_type == TokenType.DESC
 7945            if self._match_set((TokenType.ASC, TokenType.DESC))
 7946            else None
 7947        )
 7948
 7949        this = None
 7950        if (
 7951            named_primary_key
 7952            and self._curr.text.upper() not in self.CONSTRAINT_PARSERS
 7953            and self._next
 7954            and self._next.token_type == TokenType.L_PAREN
 7955        ):
 7956            this = self._parse_id_var()
 7957
 7958        if not in_props and not self._match(TokenType.L_PAREN, advance=False):
 7959            return self.expression(
 7960                exp.PrimaryKeyColumnConstraint(
 7961                    desc=desc, options=self._parse_key_constraint_options()
 7962                )
 7963            )
 7964
 7965        expressions = self._parse_wrapped_csv(
 7966            self._parse_primary_key_part, optional=wrapped_optional
 7967        )
 7968
 7969        return self.expression(
 7970            exp.PrimaryKey(
 7971                this=this,
 7972                expressions=expressions,
 7973                include=self._parse_index_params(),
 7974                options=self._parse_key_constraint_options(),
 7975            )
 7976        )
 7977
 7978    def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None:
 7979        return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True))
 7980
 7981    def _parse_odbc_datetime_literal(self) -> exp.Expr:
 7982        """
 7983        Parses a datetime column in ODBC format. We parse the column into the corresponding
 7984        types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the
 7985        same as we did for `DATE('yyyy-mm-dd')`.
 7986
 7987        Reference:
 7988        https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals
 7989        """
 7990        self._match(TokenType.VAR)
 7991        exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()]
 7992        expression = self.expression(exp_class(this=self._parse_string()))
 7993        if not self._match(TokenType.R_BRACE):
 7994            self.raise_error("Expected }")
 7995        return expression
 7996
 7997    def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7998        if not self._match_set(self.BRACKETS):
 7999            return this
 8000
 8001        if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS:
 8002            map_token = seq_get(self._tokens, self._index - 2)
 8003            parse_map = map_token is not None and map_token.text.upper() == "MAP"
 8004        else:
 8005            parse_map = False
 8006
 8007        bracket_kind = self._prev.token_type
 8008        if (
 8009            bracket_kind == TokenType.L_BRACE
 8010            and self._curr
 8011            and self._curr.token_type == TokenType.VAR
 8012            and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS
 8013        ):
 8014            return self._parse_odbc_datetime_literal()
 8015
 8016        expressions = self._parse_csv(
 8017            lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE)
 8018        )
 8019
 8020        if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET):
 8021            self.raise_error("Expected ]")
 8022        elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE):
 8023            self.raise_error("Expected }")
 8024
 8025        # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs
 8026        if bracket_kind == TokenType.L_BRACE:
 8027            this = self.expression(
 8028                exp.Struct(
 8029                    expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map)
 8030                )
 8031            )
 8032        elif not this:
 8033            this = build_array_constructor(
 8034                exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect
 8035            )
 8036        else:
 8037            constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper())
 8038            if constructor_type:
 8039                return build_array_constructor(
 8040                    constructor_type,
 8041                    args=expressions,
 8042                    bracket_kind=bracket_kind,
 8043                    dialect=self.dialect,
 8044                )
 8045
 8046            expressions = apply_index_offset(
 8047                this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect
 8048            )
 8049            this = self.expression(
 8050                exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments()
 8051            )
 8052
 8053        self._add_comments(this)
 8054        return self._parse_bracket(this)
 8055
 8056    def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None:
 8057        if not self._match(TokenType.COLON):
 8058            return this
 8059
 8060        if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False):
 8061            self._advance()
 8062            end: exp.Expr | None = -exp.Literal.number("1")
 8063        else:
 8064            end = self._parse_assignment()
 8065        step = self._parse_unary() if self._match(TokenType.COLON) else None
 8066        return self.expression(exp.Slice(this=this, expression=end, step=step))
 8067
 8068    def _parse_case(self) -> exp.Expr | None:
 8069        if self._match(TokenType.DOT, advance=False):
 8070            # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake
 8071            self._retreat(self._index - 1)
 8072            return None
 8073
 8074        ifs = []
 8075        default = None
 8076
 8077        comments = self._prev_comments
 8078        expression = self._parse_disjunction()
 8079
 8080        while self._match(TokenType.WHEN):
 8081            this = self._parse_disjunction()
 8082            self._match(TokenType.THEN)
 8083            then = self._parse_disjunction()
 8084            ifs.append(self.expression(exp.If(this=this, true=then)))
 8085
 8086        if self._match(TokenType.ELSE):
 8087            default = self._parse_disjunction()
 8088
 8089        if not self._match(TokenType.END):
 8090            if isinstance(default, exp.Interval) and default.this.sql().upper() == "END":
 8091                default = exp.column("interval")
 8092            else:
 8093                self.raise_error("Expected END after CASE", self._prev)
 8094
 8095        return self.expression(
 8096            exp.Case(this=expression, ifs=ifs, default=default), comments=comments
 8097        )
 8098
 8099    def _parse_if(self) -> exp.Expr | None:
 8100        if self._match(TokenType.L_PAREN):
 8101            args = self._parse_csv(
 8102                lambda: self._parse_alias(self._parse_assignment(), explicit=True)
 8103            )
 8104            this = self.validate_expression(exp.If.from_arg_list(args), args)
 8105            self._match_r_paren()
 8106        else:
 8107            index = self._index - 1
 8108
 8109            if self.NO_PAREN_IF_COMMANDS and index == 0:
 8110                return self._parse_as_command(self._prev)
 8111
 8112            condition = self._parse_disjunction()
 8113
 8114            if not condition:
 8115                self._retreat(index)
 8116                return None
 8117
 8118            self._match(TokenType.THEN)
 8119            true = self._parse_disjunction()
 8120            false = self._parse_disjunction() if self._match(TokenType.ELSE) else None
 8121            self._match(TokenType.END)
 8122            this = self.expression(exp.If(this=condition, true=true, false=false))
 8123
 8124        return this
 8125
 8126    def _parse_next_value_for(self) -> exp.Expr | None:
 8127        if not self._match_text_seq("VALUE", "FOR"):
 8128            self._retreat(self._index - 1)
 8129            return None
 8130
 8131        return self.expression(
 8132            exp.NextValueFor(
 8133                this=self._parse_column(),
 8134                order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order),
 8135            )
 8136        )
 8137
 8138    def _parse_extract(self) -> exp.Extract | exp.Anonymous:
 8139        this = self._parse_function() or self._parse_var_or_string(upper=True)
 8140
 8141        if self._match(TokenType.FROM):
 8142            return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8143
 8144        if not self._match(TokenType.COMMA):
 8145            self.raise_error("Expected FROM or comma after EXTRACT", self._prev)
 8146
 8147        return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8148
 8149    def _parse_gap_fill(self) -> exp.GapFill:
 8150        self._match(TokenType.TABLE)
 8151        this = self._parse_table()
 8152
 8153        self._match(TokenType.COMMA)
 8154        args = [this, *self._parse_csv(self._parse_lambda)]
 8155
 8156        gap_fill = exp.GapFill.from_arg_list(args)
 8157        return self.validate_expression(gap_fill, args)
 8158
 8159    def _parse_char(self) -> exp.Chr:
 8160        return self.expression(
 8161            exp.Chr(
 8162                expressions=self._parse_csv(self._parse_assignment),
 8163                charset=self._match(TokenType.USING) and self._parse_charset_name(),
 8164            )
 8165        )
 8166
 8167    def _parse_charset_name(self) -> exp.Expr | None:
 8168        """
 8169        Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting
 8170        for specific name shapes override this.
 8171        """
 8172        return self._parse_var(
 8173            tokens={TokenType.BINARY, TokenType.IDENTIFIER},
 8174        )
 8175
 8176    def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr:
 8177        this = self._parse_assignment()
 8178
 8179        if not self._match(TokenType.ALIAS):
 8180            if self._match(TokenType.COMMA):
 8181                return self.expression(exp.CastToStrType(this=this, to=self._parse_string()))
 8182
 8183            self.raise_error("Expected AS after CAST")
 8184
 8185        fmt = None
 8186        to = self._parse_types(with_collation=True)
 8187
 8188        default = None
 8189        if self._match(TokenType.DEFAULT):
 8190            default = self._parse_bitwise()
 8191            self._match_text_seq("ON", "CONVERSION", "ERROR")
 8192
 8193        if self._match_set((TokenType.FORMAT, TokenType.COMMA)):
 8194            fmt_string = self._parse_wrapped(self._parse_string, optional=True)
 8195            fmt = self._parse_at_time_zone(fmt_string)
 8196
 8197            if not to:
 8198                to = exp.DType.UNKNOWN.into_expr()
 8199            if to.this in exp.DataType.TEMPORAL_TYPES:
 8200                this = self.expression(
 8201                    (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)(
 8202                        this=this,
 8203                        format=exp.Literal.string(
 8204                            format_time(
 8205                                fmt_string.this if fmt_string else "",
 8206                                self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING,
 8207                                self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE,
 8208                            )
 8209                        ),
 8210                        safe=safe,
 8211                    )
 8212                )
 8213
 8214                if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime):
 8215                    this.set("zone", fmt.args["zone"])
 8216                return this
 8217        elif not to:
 8218            self.raise_error("Expected TYPE after CAST")
 8219        elif isinstance(to, exp.Identifier):
 8220            to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True)
 8221        elif to.this == exp.DType.CHAR and (
 8222            self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")
 8223        ):
 8224            to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string())
 8225
 8226        return self.build_cast(
 8227            strict=strict,
 8228            this=this,
 8229            to=to,
 8230            format=fmt,
 8231            safe=safe,
 8232            action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False),
 8233            default=default,
 8234        )
 8235
 8236    def _parse_string_agg(self) -> exp.GroupConcat:
 8237        if self._match(TokenType.DISTINCT):
 8238            args: list[exp.Expr | None] = [
 8239                self.expression(exp.Distinct(expressions=[self._parse_disjunction()]))
 8240            ]
 8241            if self._match(TokenType.COMMA):
 8242                args.extend(self._parse_csv(self._parse_disjunction))
 8243        else:
 8244            args = self._parse_csv(self._parse_disjunction)  # type: ignore
 8245
 8246        if self._match_text_seq("ON", "OVERFLOW"):
 8247            # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior])
 8248            if self._match_text_seq("ERROR"):
 8249                on_overflow: exp.Expr | None = exp.var("ERROR")
 8250            else:
 8251                self._match_text_seq("TRUNCATE")
 8252                on_overflow = self.expression(
 8253                    exp.OverflowTruncateBehavior(
 8254                        this=self._parse_string(),
 8255                        with_count=(
 8256                            self._match_text_seq("WITH", "COUNT")
 8257                            or not self._match_text_seq("WITHOUT", "COUNT")
 8258                        ),
 8259                    )
 8260                )
 8261        else:
 8262            on_overflow = None
 8263
 8264        index = self._index
 8265        if not self._match(TokenType.R_PAREN) and args:
 8266            # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]])
 8267            # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n])
 8268            # The order is parsed through `this` as a canonicalization for WITHIN GROUPs
 8269            args[0] = self._parse_limit(this=self._parse_order(this=args[0]))
 8270            return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1)))
 8271
 8272        # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]).
 8273        # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that
 8274        # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them.
 8275        if not self._match_text_seq("WITHIN", "GROUP"):
 8276            self._retreat(index)
 8277            return self.validate_expression(exp.GroupConcat.from_arg_list(args), args)
 8278
 8279        # The corresponding match_r_paren will be called in parse_function (caller)
 8280        self._match_l_paren()
 8281
 8282        return self.expression(
 8283            exp.GroupConcat(
 8284                this=self._parse_order(this=seq_get(args, 0)),
 8285                separator=seq_get(args, 1),
 8286                on_overflow=on_overflow,
 8287            )
 8288        )
 8289
 8290    def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None:
 8291        this = self._parse_bitwise()
 8292
 8293        if self._match(TokenType.USING):
 8294            to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name())
 8295        elif self._match(TokenType.COMMA):
 8296            to = self._parse_types()
 8297        else:
 8298            to = None
 8299
 8300        return self.build_cast(strict=strict, this=this, to=to, safe=safe)
 8301
 8302    def _parse_xml_element(self) -> exp.XMLElement:
 8303        if self._match_text_seq("EVALNAME"):
 8304            evalname = True
 8305            this = self._parse_bitwise()
 8306        else:
 8307            evalname = None
 8308            self._match_text_seq("NAME")
 8309            this = self._parse_id_var()
 8310
 8311        return self.expression(
 8312            exp.XMLElement(
 8313                this=this,
 8314                expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise),
 8315                evalname=evalname,
 8316            )
 8317        )
 8318
 8319    def _parse_xml_table(self) -> exp.XMLTable:
 8320        namespaces = None
 8321        passing = None
 8322        columns = None
 8323
 8324        if self._match_text_seq("XMLNAMESPACES", "("):
 8325            namespaces = self._parse_xml_namespace()
 8326            self._match_text_seq(")", ",")
 8327
 8328        this = self._parse_string()
 8329
 8330        if self._match_text_seq("PASSING"):
 8331            # The BY VALUE keywords are optional and are provided for semantic clarity
 8332            self._match_text_seq("BY", "VALUE")
 8333            passing = self._parse_csv(self._parse_column)
 8334
 8335        by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF")
 8336
 8337        if self._match_text_seq("COLUMNS"):
 8338            columns = self._parse_csv(self._parse_field_def)
 8339
 8340        return self.expression(
 8341            exp.XMLTable(
 8342                this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref
 8343            )
 8344        )
 8345
 8346    def _parse_xml_namespace(self) -> list[exp.XMLNamespace]:
 8347        namespaces = []
 8348
 8349        while True:
 8350            if self._match(TokenType.DEFAULT):
 8351                uri = self._parse_string()
 8352            else:
 8353                uri = self._parse_alias(self._parse_string())
 8354            namespaces.append(self.expression(exp.XMLNamespace(this=uri)))
 8355            if not self._match(TokenType.COMMA):
 8356                break
 8357
 8358        return namespaces
 8359
 8360    def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None:
 8361        args = self._parse_csv(self._parse_disjunction)
 8362
 8363        if len(args) < 3:
 8364            return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1)))
 8365
 8366        return self.expression(exp.DecodeCase(expressions=args))
 8367
 8368    def _parse_json_key_value(self) -> exp.JSONKeyValue | None:
 8369        self._match_text_seq("KEY")
 8370        key = self._parse_column()
 8371        self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS)
 8372        self._match_text_seq("VALUE")
 8373        value = self._parse_bitwise()
 8374
 8375        if not key and not value:
 8376            return None
 8377        return self.expression(exp.JSONKeyValue(this=key, expression=value))
 8378
 8379    def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None:
 8380        if not this or not self._match_text_seq("FORMAT", "JSON"):
 8381            return this
 8382
 8383        return self.expression(exp.FormatJson(this=this))
 8384
 8385    def _parse_on_condition(self) -> exp.OnCondition | None:
 8386        # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS)
 8387        if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR:
 8388            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8389            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8390        else:
 8391            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8392            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8393
 8394        null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS)
 8395
 8396        if not empty and not error and not null:
 8397            return None
 8398
 8399        return self.expression(exp.OnCondition(empty=empty, error=error, null=null))
 8400
 8401    def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None:
 8402        # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL)
 8403        for value in values:
 8404            if self._match_text_seq(value, "ON", on):
 8405                return f"{value} ON {on}"
 8406
 8407        index = self._index
 8408        if self._match(TokenType.DEFAULT):
 8409            default_value = self._parse_bitwise()
 8410            if self._match_text_seq("ON", on):
 8411                return default_value
 8412
 8413            self._retreat(index)
 8414
 8415        return None
 8416
 8417    @t.overload
 8418    def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ...
 8419
 8420    @t.overload
 8421    def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ...
 8422
 8423    def _parse_json_object(self, agg=False):
 8424        star = self._parse_star()
 8425        expressions = (
 8426            [star]
 8427            if star
 8428            else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value()))
 8429        )
 8430        null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT")
 8431
 8432        unique_keys = None
 8433        if self._match_text_seq("WITH", "UNIQUE"):
 8434            unique_keys = True
 8435        elif self._match_text_seq("WITHOUT", "UNIQUE"):
 8436            unique_keys = False
 8437
 8438        self._match_text_seq("KEYS")
 8439
 8440        return_type = self._match_text_seq("RETURNING") and self._parse_format_json(
 8441            self._parse_type()
 8442        )
 8443        encoding = self._match_text_seq("ENCODING") and self._parse_var()
 8444
 8445        return self.expression(
 8446            (exp.JSONObjectAgg if agg else exp.JSONObject)(
 8447                expressions=expressions,
 8448                null_handling=null_handling,
 8449                unique_keys=unique_keys,
 8450                return_type=return_type,
 8451                encoding=encoding,
 8452            )
 8453        )
 8454
 8455    # Note: this is currently incomplete; it only implements the "JSON_value_column" part
 8456    def _parse_json_column_def(self) -> exp.JSONColumnDef:
 8457        if not self._match_text_seq("NESTED"):
 8458            this = self._parse_id_var()
 8459            ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY)
 8460            kind = self._parse_types(allow_identifiers=False)
 8461            nested = None
 8462        else:
 8463            this = None
 8464            ordinality = None
 8465            kind = None
 8466            nested = True
 8467
 8468        format_json = self._match_text_seq("FORMAT", "JSON")
 8469        path = self._match_text_seq("PATH") and self._parse_string()
 8470        nested_schema = nested and self._parse_json_schema()
 8471
 8472        return self.expression(
 8473            exp.JSONColumnDef(
 8474                this=this,
 8475                kind=kind,
 8476                path=path,
 8477                nested_schema=nested_schema,
 8478                ordinality=ordinality,
 8479                format_json=format_json,
 8480            )
 8481        )
 8482
 8483    def _parse_json_schema(self) -> exp.JSONSchema:
 8484        self._match_text_seq("COLUMNS")
 8485        return self.expression(
 8486            exp.JSONSchema(
 8487                expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True)
 8488            )
 8489        )
 8490
 8491    def _parse_json_table(self) -> exp.JSONTable:
 8492        this = self._parse_format_json(self._parse_bitwise())
 8493        path = self._match(TokenType.COMMA) and self._parse_string()
 8494        error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL")
 8495        empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL")
 8496        schema = self._parse_json_schema()
 8497
 8498        return exp.JSONTable(
 8499            this=this,
 8500            schema=schema,
 8501            path=path,
 8502            error_handling=error_handling,
 8503            empty_handling=empty_handling,
 8504        )
 8505
 8506    def _parse_match_against(self) -> exp.MatchAgainst:
 8507        if self._match_text_seq("TABLE"):
 8508            # parse SingleStore MATCH(TABLE ...) syntax
 8509            # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/
 8510            expressions = []
 8511            table = self._parse_table()
 8512            if table:
 8513                expressions = [table]
 8514        else:
 8515            expressions = self._parse_csv(self._parse_column)
 8516
 8517        self._match_text_seq(")", "AGAINST", "(")
 8518
 8519        this = self._parse_string()
 8520
 8521        if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"):
 8522            modifier = "IN NATURAL LANGUAGE MODE"
 8523            if self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8524                modifier = f"{modifier} WITH QUERY EXPANSION"
 8525        elif self._match_text_seq("IN", "BOOLEAN", "MODE"):
 8526            modifier = "IN BOOLEAN MODE"
 8527        elif self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8528            modifier = "WITH QUERY EXPANSION"
 8529        else:
 8530            modifier = None
 8531
 8532        return self.expression(
 8533            exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier)
 8534        )
 8535
 8536    # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16
 8537    def _parse_open_json(self) -> exp.OpenJSON:
 8538        this = self._parse_bitwise()
 8539        path = self._match(TokenType.COMMA) and self._parse_string()
 8540
 8541        def _parse_open_json_column_def() -> exp.OpenJSONColumnDef:
 8542            this = self._parse_field(any_token=True)
 8543            kind = self._parse_types()
 8544            path = self._parse_string()
 8545            as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON)
 8546
 8547            return self.expression(
 8548                exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json)
 8549            )
 8550
 8551        expressions = None
 8552        if self._match_pair(TokenType.R_PAREN, TokenType.WITH):
 8553            self._match_l_paren()
 8554            expressions = self._parse_csv(_parse_open_json_column_def)
 8555
 8556        return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions))
 8557
 8558    def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition:
 8559        args = self._parse_csv(self._parse_bitwise)
 8560
 8561        if self._match(TokenType.IN):
 8562            return self.expression(
 8563                exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0))
 8564            )
 8565
 8566        if haystack_first:
 8567            haystack = seq_get(args, 0)
 8568            needle = seq_get(args, 1)
 8569        else:
 8570            haystack = seq_get(args, 1)
 8571            needle = seq_get(args, 0)
 8572
 8573        return self.expression(
 8574            exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2))
 8575        )
 8576
 8577    def _parse_join_hint(self, func_name: str) -> exp.JoinHint:
 8578        args = self._parse_csv(self._parse_table)
 8579        return exp.JoinHint(this=func_name.upper(), expressions=args)
 8580
 8581    def _parse_substring(self) -> exp.Substring:
 8582        # Postgres supports the form: substring(string [from int] [for int])
 8583        # (despite being undocumented, the reverse order also works)
 8584        # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6
 8585
 8586        args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise))
 8587
 8588        start, length = None, None
 8589
 8590        while self._curr:
 8591            if self._match(TokenType.FROM):
 8592                start = self._parse_bitwise()
 8593            elif self._match(TokenType.FOR):
 8594                if not start:
 8595                    start = exp.Literal.number(1)
 8596                length = self._parse_bitwise()
 8597            else:
 8598                break
 8599
 8600        if start:
 8601            args.append(start)
 8602        if length:
 8603            args.append(length)
 8604
 8605        return self.validate_expression(exp.Substring.from_arg_list(args), args)
 8606
 8607    def _parse_trim(self) -> exp.Trim:
 8608        # https://www.w3resource.com/sql/character-functions/trim.php
 8609        # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html
 8610
 8611        position = None
 8612        collation = None
 8613        expression = None
 8614
 8615        if self._match_texts(self.TRIM_TYPES):
 8616            position = self._prev.text.upper()
 8617
 8618        this = self._parse_bitwise()
 8619        if self._match_set((TokenType.FROM, TokenType.COMMA)):
 8620            invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST
 8621            expression = self._parse_bitwise()
 8622
 8623            if invert_order:
 8624                this, expression = expression, this
 8625
 8626        if self._match(TokenType.COLLATE):
 8627            collation = self._parse_bitwise()
 8628
 8629        return self.expression(
 8630            exp.Trim(this=this, position=position, expression=expression, collation=collation)
 8631        )
 8632
 8633    def _parse_window_clause(self) -> list[exp.Expr] | None:
 8634        return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None
 8635
 8636    def _parse_named_window(self) -> exp.Expr | None:
 8637        return self._parse_window(self._parse_id_var(), alias=True)
 8638
 8639    def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None:
 8640        if self._curr.token_type == TokenType.VAR:
 8641            if self._match_text_seq("IGNORE", "NULLS"):
 8642                return self.expression(exp.IgnoreNulls(this=this))
 8643            if self._match_text_seq("RESPECT", "NULLS"):
 8644                return self.expression(exp.RespectNulls(this=this))
 8645        return this
 8646
 8647    def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None:
 8648        if self._match(TokenType.HAVING):
 8649            self._match_texts(("MAX", "MIN"))
 8650            max = self._prev.text.upper() != "MIN"
 8651            return self.expression(
 8652                exp.HavingMax(this=this, expression=self._parse_column(), max=max)
 8653            )
 8654
 8655        return this
 8656
 8657    def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None:
 8658        func = this
 8659        comments = func.comments if isinstance(func, exp.Expr) else None
 8660
 8661        # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html
 8662        if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue):
 8663            if self._match_text_seq("FROM", "FIRST"):
 8664                this.set("from_first", True)
 8665            elif self._match_text_seq("FROM", "LAST"):
 8666                this.set("from_first", False)
 8667
 8668        # T-SQL allows the OVER (...) syntax after WITHIN GROUP.
 8669        # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16
 8670        if self._match_text_seq("WITHIN", "GROUP"):
 8671            order = self._parse_wrapped(self._parse_order)
 8672            this = self.expression(exp.WithinGroup(this=this, expression=order))
 8673
 8674        if self._match_pair(TokenType.FILTER, TokenType.L_PAREN):
 8675            self._match(TokenType.WHERE)
 8676            this = self.expression(
 8677                exp.Filter(this=this, expression=self._parse_where(skip_where_token=True))
 8678            )
 8679            self._match_r_paren()
 8680
 8681        # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER
 8682        # Some dialects choose to implement and some do not.
 8683        # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html
 8684
 8685        # There is some code above in _parse_lambda that handles
 8686        #   SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ...
 8687
 8688        # The below changes handle
 8689        #   SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ...
 8690
 8691        # Oracle allows both formats
 8692        #   (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html)
 8693        #   and Snowflake chose to do the same for familiarity
 8694        #   https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes
 8695        if isinstance(this, exp.AggFunc):
 8696            ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls)
 8697
 8698            if ignore_respect and ignore_respect is not this:
 8699                ignore_respect.replace(ignore_respect.this)
 8700                this = self.expression(ignore_respect.__class__(this=this))
 8701
 8702        this = self._parse_respect_or_ignore_nulls(this)
 8703
 8704        # bigquery select from window x AS (partition by ...)
 8705        if alias:
 8706            over = None
 8707            self._match(TokenType.ALIAS)
 8708        elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS):
 8709            return this
 8710        else:
 8711            over = self._prev.text.upper()
 8712
 8713        if comments and isinstance(func, exp.Expr):
 8714            func.pop_comments()
 8715
 8716        if not self._match(TokenType.L_PAREN):
 8717            return self.expression(
 8718                exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments
 8719            )
 8720
 8721        window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS)
 8722
 8723        first: bool | None = True if self._match(TokenType.FIRST) else None
 8724        if self._match_text_seq("LAST"):
 8725            first = False
 8726
 8727        partition, order = self._parse_partition_and_order()
 8728        kind = (
 8729            self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS")
 8730        ) and self._prev.text
 8731
 8732        if kind:
 8733            self._match(TokenType.BETWEEN)
 8734            start = self._parse_window_spec()
 8735
 8736            end = self._parse_window_spec() if self._match(TokenType.AND) else {}
 8737            exclude = (
 8738                self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS)
 8739                if self._match_text_seq("EXCLUDE")
 8740                else None
 8741            )
 8742
 8743            spec = self.expression(
 8744                exp.WindowSpec(
 8745                    kind=kind,
 8746                    start=start["value"],
 8747                    start_side=start["side"],
 8748                    end=end.get("value"),
 8749                    end_side=end.get("side"),
 8750                    exclude=exclude,
 8751                )
 8752            )
 8753        else:
 8754            spec = None
 8755
 8756        self._match_r_paren()
 8757
 8758        window = self.expression(
 8759            exp.Window(
 8760                this=this,
 8761                partition_by=partition,
 8762                order=order,
 8763                spec=spec,
 8764                alias=window_alias,
 8765                over=over,
 8766                first=first,
 8767            ),
 8768            comments=comments,
 8769        )
 8770
 8771        # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...)
 8772        if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False):
 8773            return self._parse_window(window, alias=alias)
 8774
 8775        return window
 8776
 8777    def _parse_partition_and_order(
 8778        self,
 8779    ) -> tuple[list[exp.Expr], exp.Expr | None]:
 8780        return self._parse_partition_by(), self._parse_order()
 8781
 8782    def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]:
 8783        self._match(TokenType.BETWEEN)
 8784
 8785        return {
 8786            "value": (
 8787                (self._match_text_seq("UNBOUNDED") and "UNBOUNDED")
 8788                or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW")
 8789                or self._parse_bitwise()
 8790            ),
 8791            "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None,
 8792        }
 8793
 8794    def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None:
 8795        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 8796        # so this section tries to parse the clause version and if it fails, it treats the token
 8797        # as an identifier (alias)
 8798        if self._can_parse_limit_or_offset():
 8799            return this
 8800
 8801        # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the
 8802        # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it.
 8803        if self._can_parse_named_window():
 8804            return this
 8805
 8806        any_token = self._match(TokenType.ALIAS)
 8807        comments = self._prev_comments
 8808
 8809        if explicit and not any_token:
 8810            return this
 8811
 8812        if self._match(TokenType.L_PAREN):
 8813            aliases = self.expression(
 8814                exp.Aliases(
 8815                    this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token))
 8816                ),
 8817                comments=comments,
 8818            )
 8819            self._match_r_paren(aliases)
 8820            return aliases
 8821
 8822        alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or (
 8823            self.STRING_ALIASES and self._parse_string_as_identifier()
 8824        )
 8825
 8826        if alias:
 8827            comments.extend(alias.pop_comments())
 8828            this = self.expression(exp.Alias(this=this, alias=alias), comments=comments)
 8829            column = this.this
 8830
 8831            # Moves the comment next to the alias in `expr /* comment */ AS alias`
 8832            if not this.comments and column and column.comments:
 8833                this.comments = column.pop_comments()
 8834
 8835        return this
 8836
 8837    def _parse_id_var(
 8838        self,
 8839        any_token: bool = True,
 8840        tokens: t.Collection[TokenType] | None = None,
 8841    ) -> exp.Expr | None:
 8842        expression = self._parse_identifier()
 8843        if not expression and (
 8844            (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS)
 8845        ):
 8846            quoted = self._prev.token_type == TokenType.STRING
 8847            expression = self._identifier_expression(quoted=quoted)
 8848
 8849        return expression
 8850
 8851    def _parse_string(self) -> exp.Expr | None:
 8852        if self._match_set(self.STRING_PARSERS):
 8853            return self.STRING_PARSERS[self._prev.token_type](self, self._prev)
 8854        return self._parse_placeholder()
 8855
 8856    def _parse_string_as_identifier(self) -> exp.Identifier | None:
 8857        if not self._match(TokenType.STRING):
 8858            return None
 8859        output = exp.to_identifier(self._prev.text, quoted=True)
 8860        output.update_positions(self._prev)
 8861        return output
 8862
 8863    def _parse_number(self) -> exp.Expr | None:
 8864        if self._match_set(self.NUMERIC_PARSERS):
 8865            return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev)
 8866        return self._parse_placeholder()
 8867
 8868    def _parse_identifier(self) -> exp.Expr | None:
 8869        if self._match(TokenType.IDENTIFIER):
 8870            return self._identifier_expression(quoted=True)
 8871        return self._parse_placeholder()
 8872
 8873    def _parse_var(
 8874        self,
 8875        any_token: bool = False,
 8876        tokens: t.Collection[TokenType] | None = None,
 8877        upper: bool = False,
 8878    ) -> exp.Expr | None:
 8879        if (
 8880            (any_token and self._advance_any())
 8881            or self._match(TokenType.VAR)
 8882            or (self._match_set(tokens) if tokens else False)
 8883        ):
 8884            return self.expression(
 8885                exp.Var(this=self._prev.text.upper() if upper else self._prev.text)
 8886            )
 8887        return self._parse_placeholder()
 8888
 8889    def _advance_any(self, ignore_reserved: bool = False) -> Token | None:
 8890        if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS):
 8891            self._advance()
 8892            return self._prev
 8893        return None
 8894
 8895    def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None:
 8896        return self._parse_string() or self._parse_var(any_token=True, upper=upper)
 8897
 8898    def _parse_primary_or_var(self) -> exp.Expr | None:
 8899        return self._parse_primary() or self._parse_var(any_token=True)
 8900
 8901    def _parse_null(self) -> exp.Expr | None:
 8902        if self._match_set((TokenType.NULL, TokenType.UNKNOWN)):
 8903            return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev)
 8904        return self._parse_placeholder()
 8905
 8906    def _parse_boolean(self) -> exp.Expr | None:
 8907        if self._match(TokenType.TRUE):
 8908            return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev)
 8909        if self._match(TokenType.FALSE):
 8910            return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev)
 8911        return self._parse_placeholder()
 8912
 8913    def _parse_star(self) -> exp.Expr | None:
 8914        if self._match(TokenType.STAR):
 8915            return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev)
 8916        return self._parse_placeholder()
 8917
 8918    def _parse_parameter(self) -> exp.Parameter:
 8919        this = self._parse_identifier() or self._parse_primary_or_var()
 8920        return self.expression(exp.Parameter(this=this))
 8921
 8922    def _parse_placeholder(self) -> exp.Expr | None:
 8923        if self._match_set(self.PLACEHOLDER_PARSERS):
 8924            placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self)
 8925            if placeholder:
 8926                return placeholder
 8927            self._advance(-1)
 8928        return None
 8929
 8930    def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None:
 8931        if not self._match_texts(keywords):
 8932            return None
 8933        if self._match(TokenType.L_PAREN, advance=False):
 8934            return self._parse_wrapped_csv(self._parse_expression)
 8935
 8936        expression = self._parse_alias(self._parse_disjunction(), explicit=True)
 8937        return [expression] if expression else None
 8938
 8939    def _parse_csv(
 8940        self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA
 8941    ) -> list[T]:
 8942        parse_result = parse_method()
 8943        items = [parse_result] if parse_result is not None else []
 8944
 8945        while self._match(sep):
 8946            if isinstance(parse_result, exp.Expr):
 8947                self._add_comments(parse_result)
 8948            parse_result = parse_method()
 8949            if parse_result is not None:
 8950                items.append(parse_result)
 8951
 8952        return items
 8953
 8954    def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]:
 8955        return self._parse_wrapped_csv(self._parse_id_var, optional=optional)
 8956
 8957    def _parse_wrapped_csv(
 8958        self,
 8959        parse_method: t.Callable[[], T | None],
 8960        sep: TokenType = TokenType.COMMA,
 8961        optional: bool = False,
 8962    ) -> list[T]:
 8963        return self._parse_wrapped(
 8964            lambda: self._parse_csv(parse_method, sep=sep), optional=optional
 8965        )
 8966
 8967    def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T:
 8968        wrapped = self._match(TokenType.L_PAREN)
 8969        if not wrapped and not optional:
 8970            self.raise_error("Expecting (")
 8971        parse_result = parse_method()
 8972        if wrapped:
 8973            self._match_r_paren()
 8974        return parse_result
 8975
 8976    def _parse_expressions(self) -> list[exp.Expr]:
 8977        return self._parse_csv(self._parse_expression)
 8978
 8979    def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None:
 8980        return (
 8981            self._parse_set_operations(
 8982                self._parse_alias(self._parse_assignment(), explicit=True)
 8983                if alias
 8984                else self._parse_assignment()
 8985            )
 8986            or self._parse_select()
 8987        )
 8988
 8989    def _parse_ddl_select(self) -> exp.Expr | None:
 8990        return self._parse_query_modifiers(
 8991            self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False))
 8992        )
 8993
 8994    def _parse_transaction(self) -> exp.Transaction | exp.Command:
 8995        this = None
 8996        if self._match_texts(self.TRANSACTION_KIND):
 8997            this = self._prev.text
 8998
 8999        self._match_texts(("TRANSACTION", "WORK"))
 9000
 9001        modes = []
 9002        while True:
 9003            mode = []
 9004            while self._match(TokenType.VAR) or self._match(TokenType.NOT):
 9005                mode.append(self._prev.text)
 9006
 9007            if mode:
 9008                modes.append(" ".join(mode))
 9009            if not self._match(TokenType.COMMA):
 9010                break
 9011
 9012        return self.expression(exp.Transaction(this=this, modes=modes))
 9013
 9014    def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback:
 9015        chain = None
 9016        savepoint = None
 9017        is_rollback = self._prev.token_type == TokenType.ROLLBACK
 9018
 9019        self._match_texts(("TRANSACTION", "WORK"))
 9020
 9021        if self._match_text_seq("TO"):
 9022            self._match_text_seq("SAVEPOINT")
 9023            savepoint = self._parse_id_var()
 9024
 9025        if self._match(TokenType.AND):
 9026            chain = not self._match_text_seq("NO")
 9027            self._match_text_seq("CHAIN")
 9028
 9029        if is_rollback:
 9030            return self.expression(exp.Rollback(savepoint=savepoint))
 9031
 9032        return self.expression(exp.Commit(chain=chain))
 9033
 9034    def _parse_refresh(self) -> exp.Refresh | exp.Command:
 9035        if self._match_text_seq("EXTERNAL", "TABLE"):
 9036            kind = "EXTERNAL TABLE"
 9037        elif self._match(TokenType.TABLE):
 9038            kind = "TABLE"
 9039        elif self._match_text_seq("MATERIALIZED", "VIEW"):
 9040            kind = "MATERIALIZED VIEW"
 9041        else:
 9042            kind = ""
 9043
 9044        this = self._parse_string() or self._parse_table()
 9045        if not kind and not isinstance(this, exp.Literal):
 9046            return self._parse_as_command(self._prev)
 9047
 9048        return self.expression(exp.Refresh(this=this, kind=kind))
 9049
 9050    def _parse_column_def_with_exists(self):
 9051        start = self._index
 9052        self._match(TokenType.COLUMN)
 9053
 9054        exists_column = self._parse_exists(not_=True)
 9055        expression = self._parse_field_def()
 9056
 9057        if not isinstance(expression, exp.ColumnDef):
 9058            self._retreat(start)
 9059            return None
 9060
 9061        expression.set("exists", exists_column)
 9062
 9063        return expression
 9064
 9065    def _parse_add_column(self) -> exp.ColumnDef | None:
 9066        if not self._prev.text.upper() == "ADD":
 9067            return None
 9068
 9069        return self._parse_column_def_with_exists()
 9070
 9071    def _parse_drop_column(self) -> exp.Drop | exp.Command | None:
 9072        drop = self._parse_drop() if self._match(TokenType.DROP) else None
 9073        if drop and not isinstance(drop, exp.Command):
 9074            drop.set("kind", drop.args.get("kind", "COLUMN"))
 9075        return drop
 9076
 9077    def _parse_alter_drop_action(self) -> exp.Expr | None:
 9078        return self._parse_drop_column()
 9079
 9080    # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html
 9081    def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition:
 9082        return self.expression(
 9083            exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists)
 9084        )
 9085
 9086    def _parse_alter_table_add(self) -> list[exp.Expr]:
 9087        def _parse_add_alteration() -> exp.Expr | None:
 9088            self._match_text_seq("ADD")
 9089            if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False):
 9090                return self.expression(
 9091                    exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint))
 9092                )
 9093
 9094            column_def = self._parse_add_column()
 9095            if isinstance(column_def, exp.ColumnDef):
 9096                return column_def
 9097
 9098            exists = self._parse_exists(not_=True)
 9099            if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False):
 9100                return self.expression(
 9101                    exp.AddPartition(
 9102                        exists=exists,
 9103                        this=self._parse_field(any_token=True),
 9104                        location=self._match_text_seq("LOCATION", advance=False)
 9105                        and self._parse_property(),
 9106                    )
 9107                )
 9108
 9109            return None
 9110
 9111        if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and (
 9112            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
 9113            or self._match_text_seq("COLUMNS")
 9114        ):
 9115            schema = self._parse_schema()
 9116
 9117            return (
 9118                ensure_list(schema)
 9119                if schema
 9120                else self._parse_csv(self._parse_column_def_with_exists)
 9121            )
 9122
 9123        return self._parse_csv(_parse_add_alteration)
 9124
 9125    def _parse_alter_table_alter(self) -> exp.Expr | None:
 9126        if self._match_texts(self.ALTER_ALTER_PARSERS):
 9127            return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self)
 9128
 9129        # Many dialects support the ALTER [COLUMN] syntax, so if there is no
 9130        # keyword after ALTER we default to parsing this statement
 9131        self._match(TokenType.COLUMN)
 9132        exists = self._parse_exists()
 9133        column = self._parse_field(any_token=True)
 9134
 9135        if self._match_pair(TokenType.DROP, TokenType.DEFAULT):
 9136            return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None))
 9137        if self._match_pair(TokenType.SET, TokenType.DEFAULT):
 9138            return self.expression(
 9139                exp.AlterColumn(
 9140                    this=column, default=self._parse_disjunction(), exists=exists or None
 9141                )
 9142            )
 9143        if self._match(TokenType.COMMENT):
 9144            return self.expression(
 9145                exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None)
 9146            )
 9147        if self._match_text_seq("DROP", "NOT", "NULL"):
 9148            return self.expression(
 9149                exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None)
 9150            )
 9151        if self._match_text_seq("SET", "NOT", "NULL"):
 9152            return self.expression(
 9153                exp.AlterColumn(this=column, allow_null=False, exists=exists or None)
 9154            )
 9155
 9156        if self._match_text_seq("SET", "VISIBLE"):
 9157            return self.expression(
 9158                exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None)
 9159            )
 9160        if self._match_text_seq("SET", "INVISIBLE"):
 9161            return self.expression(
 9162                exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None)
 9163            )
 9164
 9165        self._match_text_seq("SET", "DATA")
 9166        self._match_text_seq("TYPE")
 9167        return self.expression(
 9168            exp.AlterColumn(
 9169                this=column,
 9170                dtype=self._parse_types(),
 9171                collate=self._match(TokenType.COLLATE) and self._parse_term(),
 9172                using=self._match(TokenType.USING) and self._parse_disjunction(),
 9173                exists=exists or None,
 9174            )
 9175        )
 9176
 9177    def _parse_alter_diststyle(self) -> exp.AlterDistStyle:
 9178        if self._match_texts(("ALL", "EVEN", "AUTO")):
 9179            return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper())))
 9180
 9181        self._match_text_seq("KEY", "DISTKEY")
 9182        return self.expression(exp.AlterDistStyle(this=self._parse_column()))
 9183
 9184    def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey:
 9185        if compound:
 9186            self._match_text_seq("SORTKEY")
 9187
 9188        if self._match(TokenType.L_PAREN, advance=False):
 9189            return self.expression(
 9190                exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound)
 9191            )
 9192
 9193        self._match_texts(("AUTO", "NONE"))
 9194        return self.expression(
 9195            exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound)
 9196        )
 9197
 9198    def _parse_alter_table_drop(self) -> list[exp.Expr]:
 9199        index = self._index - 1
 9200
 9201        partition_exists = self._parse_exists()
 9202        if self._match(TokenType.PARTITION, advance=False):
 9203            return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists))
 9204
 9205        self._retreat(index)
 9206        return self._parse_csv(self._parse_alter_drop_action)
 9207
 9208    def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None:
 9209        if self._match(TokenType.COLUMN) or (
 9210            not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False)
 9211        ):
 9212            exists = self._parse_exists()
 9213            old_column = self._parse_column()
 9214            to = self._match_text_seq("TO")
 9215            new_column = self._parse_column()
 9216
 9217            if old_column is None or not to or new_column is None:
 9218                return None
 9219
 9220            return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists))
 9221
 9222        self._match_text_seq("TO")
 9223        return self.expression(exp.AlterRename(this=self._parse_table(schema=True)))
 9224
 9225    def _parse_alter_table_set(self) -> exp.AlterSet:
 9226        alter_set = self.expression(exp.AlterSet())
 9227
 9228        if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq(
 9229            "TABLE", "PROPERTIES"
 9230        ):
 9231            alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment))
 9232        elif self._match_text_seq("FILESTREAM_ON", advance=False):
 9233            alter_set.set("expressions", [self._parse_assignment()])
 9234        elif self._match_texts(("LOGGED", "UNLOGGED")):
 9235            alter_set.set("option", exp.var(self._prev.text.upper()))
 9236        elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")):
 9237            alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}"))
 9238        elif self._match_text_seq("LOCATION"):
 9239            alter_set.set("location", self._parse_field())
 9240        elif self._match_text_seq("ACCESS", "METHOD"):
 9241            alter_set.set("access_method", self._parse_field())
 9242        elif self._match_text_seq("TABLESPACE"):
 9243            alter_set.set("tablespace", self._parse_field())
 9244        elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"):
 9245            alter_set.set("file_format", [self._parse_field()])
 9246        elif self._match_text_seq("STAGE_FILE_FORMAT"):
 9247            alter_set.set("file_format", self._parse_wrapped_options())
 9248        elif self._match_text_seq("STAGE_COPY_OPTIONS"):
 9249            alter_set.set("copy_options", self._parse_wrapped_options())
 9250        elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"):
 9251            alter_set.set("tag", self._parse_csv(self._parse_assignment))
 9252        else:
 9253            if self._match_text_seq("SERDE"):
 9254                alter_set.set("serde", self._parse_field())
 9255
 9256            properties = self._parse_wrapped(self._parse_properties, optional=True)
 9257            alter_set.set("expressions", [properties])
 9258
 9259        return alter_set
 9260
 9261    def _parse_alter_session(self) -> exp.AlterSession:
 9262        """Parse ALTER SESSION SET/UNSET statements."""
 9263        if self._match(TokenType.SET):
 9264            expressions = self._parse_csv(lambda: self._parse_set_item_assignment())
 9265            return self.expression(exp.AlterSession(expressions=expressions, unset=False))
 9266
 9267        self._match_text_seq("UNSET")
 9268        expressions = self._parse_csv(
 9269            lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True)))
 9270        )
 9271        return self.expression(exp.AlterSession(expressions=expressions, unset=True))
 9272
 9273    def _parse_alter(self) -> exp.Alter | exp.Command:
 9274        start = self._prev
 9275
 9276        iceberg = self._match_text_seq("ICEBERG")
 9277
 9278        alter_token = self._match_set(self.ALTERABLES) and self._prev
 9279        if not alter_token:
 9280            return self._parse_as_command(start)
 9281        if iceberg and alter_token.token_type != TokenType.TABLE:
 9282            return self._parse_as_command(start)
 9283
 9284        exists = self._parse_exists()
 9285        only = self._match_text_seq("ONLY")
 9286
 9287        if alter_token.token_type == TokenType.SESSION:
 9288            this = None
 9289            check = None
 9290            cluster = None
 9291        else:
 9292            this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS)
 9293            check = self._match_text_seq("WITH", "CHECK")
 9294            cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9295
 9296            if self._next:
 9297                self._advance()
 9298
 9299        parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None
 9300        if parser:
 9301            actions = ensure_list(parser(self))
 9302            not_valid = self._match_text_seq("NOT", "VALID")
 9303            options = self._parse_csv(self._parse_property)
 9304            cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE")
 9305
 9306            if not self._curr and actions:
 9307                return self.expression(
 9308                    exp.Alter(
 9309                        this=this,
 9310                        kind=alter_token.text.upper(),
 9311                        exists=exists,
 9312                        actions=actions,
 9313                        only=only,
 9314                        options=options,
 9315                        cluster=cluster,
 9316                        not_valid=not_valid,
 9317                        check=check,
 9318                        cascade=cascade,
 9319                        iceberg=iceberg,
 9320                    )
 9321                )
 9322
 9323        return self._parse_as_command(start)
 9324
 9325    def _parse_analyze(self) -> exp.Analyze | exp.Command:
 9326        start = self._prev
 9327        # https://duckdb.org/docs/sql/statements/analyze
 9328        if not self._curr:
 9329            return self.expression(exp.Analyze())
 9330
 9331        options = []
 9332        while self._match_texts(self.ANALYZE_STYLES):
 9333            if self._prev.text.upper() == "BUFFER_USAGE_LIMIT":
 9334                options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}")
 9335            else:
 9336                options.append(self._prev.text.upper())
 9337
 9338        tables: exp.Expr | list[exp.Expr] | None = None
 9339        inner_expression: exp.Expr | None = None
 9340
 9341        kind = self._curr.text.upper() if self._curr else None
 9342
 9343        if self._match(TokenType.TABLE):
 9344            tables = self._parse_csv(self._parse_table_parts)
 9345        elif self._match(TokenType.INDEX):
 9346            tables = self._parse_table_parts()
 9347        elif self._match_text_seq("TABLES"):
 9348            if self._match_set((TokenType.FROM, TokenType.IN)):
 9349                kind = f"{kind} {self._prev.text.upper()}"
 9350                tables = self._parse_table(schema=True, is_db_reference=True)
 9351        elif self._match_text_seq("DATABASE"):
 9352            tables = self._parse_table(schema=True, is_db_reference=True)
 9353        elif self._match_text_seq("CLUSTER"):
 9354            tables = self._parse_table()
 9355        # Try matching inner expr keywords before fallback to parse table.
 9356        elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9357            kind = None
 9358            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9359        else:
 9360            # Empty kind  https://prestodb.io/docs/current/sql/analyze.html
 9361            kind = None
 9362            tables = self._parse_csv(self._parse_table_parts)
 9363
 9364        partition = self._try_parse(self._parse_partition)
 9365        if not partition and self._match_texts(self.PARTITION_KEYWORDS):
 9366            return self._parse_as_command(start)
 9367
 9368        # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9369        if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq(
 9370            "WITH", "ASYNC", "MODE"
 9371        ):
 9372            mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE"
 9373        else:
 9374            mode = None
 9375
 9376        if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9377            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9378
 9379        properties = self._parse_properties()
 9380        return self.expression(
 9381            exp.Analyze(
 9382                kind=kind,
 9383                tables=ensure_list(tables),
 9384                mode=mode,
 9385                partition=partition,
 9386                properties=properties,
 9387                expression=inner_expression,
 9388                options=options,
 9389            )
 9390        )
 9391
 9392    # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html
 9393    def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics:
 9394        this = None
 9395        kind = self._prev.text.upper()
 9396        option = self._prev.text.upper() if self._match_text_seq("DELTA") else None
 9397        expressions = []
 9398
 9399        if not self._match_text_seq("STATISTICS"):
 9400            self.raise_error("Expecting token STATISTICS")
 9401
 9402        if self._match_text_seq("NOSCAN"):
 9403            this = "NOSCAN"
 9404        elif self._match(TokenType.FOR):
 9405            if self._match_text_seq("ALL", "COLUMNS"):
 9406                this = "FOR ALL COLUMNS"
 9407            if self._match_text_seq("COLUMNS"):
 9408                this = "FOR COLUMNS"
 9409                expressions = self._parse_csv(self._parse_column_reference)
 9410        elif self._match_text_seq("SAMPLE"):
 9411            sample = self._parse_number()
 9412            expressions = [
 9413                self.expression(
 9414                    exp.AnalyzeSample(
 9415                        sample=sample,
 9416                        kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None,
 9417                    )
 9418                )
 9419            ]
 9420
 9421        return self.expression(
 9422            exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions)
 9423        )
 9424
 9425    # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html
 9426    def _parse_analyze_validate(self) -> exp.AnalyzeValidate:
 9427        kind = None
 9428        this = None
 9429        expression: exp.Expr | None = None
 9430        if self._match_text_seq("REF", "UPDATE"):
 9431            kind = "REF"
 9432            this = "UPDATE"
 9433            if self._match_text_seq("SET", "DANGLING", "TO", "NULL"):
 9434                this = "UPDATE SET DANGLING TO NULL"
 9435        elif self._match_text_seq("STRUCTURE"):
 9436            kind = "STRUCTURE"
 9437            if self._match_text_seq("CASCADE", "FAST"):
 9438                this = "CASCADE FAST"
 9439            elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts(
 9440                ("ONLINE", "OFFLINE")
 9441            ):
 9442                this = f"CASCADE COMPLETE {self._prev.text.upper()}"
 9443                expression = self._parse_into()
 9444
 9445        return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression))
 9446
 9447    def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None:
 9448        this = self._prev.text.upper()
 9449        if self._match_text_seq("COLUMNS"):
 9450            return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}"))
 9451        return None
 9452
 9453    def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None:
 9454        kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None
 9455        if self._match_text_seq("STATISTICS"):
 9456            return self.expression(exp.AnalyzeDelete(kind=kind))
 9457        return None
 9458
 9459    def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None:
 9460        if self._match_text_seq("CHAINED", "ROWS"):
 9461            return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into()))
 9462        return None
 9463
 9464    # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html
 9465    def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram:
 9466        this = self._prev.text.upper()
 9467        expression: exp.Expr | None = None
 9468        expressions = []
 9469        update_options = None
 9470
 9471        if self._match_text_seq("HISTOGRAM", "ON"):
 9472            expressions = self._parse_csv(self._parse_column_reference)
 9473            with_expressions = []
 9474            while self._match(TokenType.WITH):
 9475                # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9476                if self._match_texts(("SYNC", "ASYNC")):
 9477                    if self._match_text_seq("MODE", advance=False):
 9478                        with_expressions.append(f"{self._prev.text.upper()} MODE")
 9479                        self._advance()
 9480                else:
 9481                    buckets = self._parse_number()
 9482                    if self._match_text_seq("BUCKETS"):
 9483                        with_expressions.append(f"{buckets} BUCKETS")
 9484            if with_expressions:
 9485                expression = self.expression(exp.AnalyzeWith(expressions=with_expressions))
 9486
 9487            if self._match_texts(("MANUAL", "AUTO")) and self._match(
 9488                TokenType.UPDATE, advance=False
 9489            ):
 9490                update_options = self._prev.text.upper()
 9491                self._advance()
 9492            elif self._match_text_seq("USING", "DATA"):
 9493                expression = self.expression(exp.UsingData(this=self._parse_string()))
 9494
 9495        return self.expression(
 9496            exp.AnalyzeHistogram(
 9497                this=this,
 9498                expressions=expressions,
 9499                expression=expression,
 9500                update_options=update_options,
 9501            )
 9502        )
 9503
 9504    def _parse_merge(self) -> exp.Merge:
 9505        self._match(TokenType.INTO)
 9506        target = self._parse_table()
 9507
 9508        if target and self._match(TokenType.ALIAS, advance=False):
 9509            target.set("alias", self._parse_table_alias())
 9510
 9511        self._match(TokenType.USING)
 9512        using = self._parse_table()
 9513
 9514        return self.expression(
 9515            exp.Merge(
 9516                this=target,
 9517                using=using,
 9518                on=self._match(TokenType.ON) and self._parse_disjunction(),
 9519                using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(),
 9520                whens=self._parse_when_matched(),
 9521                returning=self._parse_returning(),
 9522            )
 9523        )
 9524
 9525    def _parse_when_matched(self) -> exp.Whens:
 9526        whens = []
 9527
 9528        while self._match(TokenType.WHEN):
 9529            matched = not self._match(TokenType.NOT)
 9530            self._match_text_seq("MATCHED")
 9531            source = (
 9532                False
 9533                if self._match_text_seq("BY", "TARGET")
 9534                else self._match_text_seq("BY", "SOURCE")
 9535            )
 9536            condition = self._parse_disjunction() if self._match(TokenType.AND) else None
 9537
 9538            self._match(TokenType.THEN)
 9539
 9540            if self._match(TokenType.INSERT):
 9541                this = self._parse_star()
 9542                if this:
 9543                    then: exp.Expr | None = self.expression(exp.Insert(this=this))
 9544                else:
 9545                    then = self.expression(
 9546                        exp.Insert(
 9547                            this=exp.var("ROW")
 9548                            if self._match_text_seq("ROW")
 9549                            else self._parse_value(values=False),
 9550                            expression=self._match_text_seq("VALUES") and self._parse_value(),
 9551                            where=self._parse_where(),
 9552                        )
 9553                    )
 9554            elif self._match(TokenType.UPDATE):
 9555                expressions = self._parse_star()
 9556                if expressions:
 9557                    then = self.expression(exp.Update(expressions=expressions))
 9558                else:
 9559                    then = self.expression(
 9560                        exp.Update(
 9561                            expressions=self._match(TokenType.SET)
 9562                            and self._parse_csv(self._parse_equality),
 9563                            where=self._parse_where(),
 9564                        )
 9565                    )
 9566            elif self._match(TokenType.DELETE):
 9567                then = self.expression(exp.Var(this=self._prev.text))
 9568            else:
 9569                then = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 9570
 9571            whens.append(
 9572                self.expression(
 9573                    exp.When(matched=matched, source=source, condition=condition, then=then)
 9574                )
 9575            )
 9576        return self.expression(exp.Whens(expressions=whens))
 9577
 9578    def _parse_show(self) -> exp.Expr | None:
 9579        parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE)
 9580        if parser:
 9581            return parser(self)
 9582        return self._parse_as_command(self._prev)
 9583
 9584    def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None:
 9585        index = self._index
 9586
 9587        if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"):
 9588            return self._parse_set_transaction(global_=kind == "GLOBAL")
 9589
 9590        left = self._parse_primary() or self._parse_column()
 9591        assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS)
 9592
 9593        if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter):
 9594            self._retreat(index)
 9595            return None
 9596
 9597        right = self._parse_statement() or self._parse_id_var()
 9598        if isinstance(right, (exp.Column, exp.Identifier)):
 9599            right = exp.var(right.name)
 9600
 9601        this = self.expression(exp.EQ(this=left, expression=right))
 9602        return self.expression(exp.SetItem(this=this, kind=kind))
 9603
 9604    def _parse_set_transaction(self, global_: bool = False) -> exp.Expr:
 9605        self._match_text_seq("TRANSACTION")
 9606        characteristics = self._parse_csv(
 9607            lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS)
 9608        )
 9609        return self.expression(
 9610            exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_)
 9611        )
 9612
 9613    def _parse_set_item(self) -> exp.Expr | None:
 9614        parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE)
 9615        return parser(self) if parser else self._parse_set_item_assignment(kind=None)
 9616
 9617    def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command:
 9618        index = self._index
 9619        set_ = self.expression(
 9620            exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag)
 9621        )
 9622
 9623        if self._curr:
 9624            self._retreat(index)
 9625            return self._parse_as_command(self._prev)
 9626
 9627        return set_
 9628
 9629    def _parse_var_from_options(
 9630        self, options: OPTIONS_TYPE, raise_unmatched: bool = True
 9631    ) -> exp.Var | None:
 9632        start = self._curr
 9633        if not start:
 9634            return None
 9635
 9636        option = start.text.upper()
 9637        continuations = (
 9638            None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option)
 9639        )
 9640
 9641        index = self._index
 9642        self._advance()
 9643        for keywords in continuations or []:
 9644            if isinstance(keywords, str):
 9645                keywords = (keywords,)
 9646
 9647            if self._match_text_seq(*keywords):
 9648                option = f"{option} {' '.join(keywords)}"
 9649                break
 9650        else:
 9651            if continuations or continuations is None:
 9652                if raise_unmatched:
 9653                    self.raise_error(f"Unknown option {option}")
 9654
 9655                self._retreat(index)
 9656                return None
 9657
 9658        return exp.var(option)
 9659
 9660    def _parse_as_command(self, start: Token) -> exp.Command:
 9661        while self._curr:
 9662            self._advance()
 9663        text = self._find_sql(start, self._prev)
 9664        size = len(start.text)
 9665        self._warn_unsupported()
 9666        return exp.Command(this=text[:size], expression=text[size:])
 9667
 9668    def _parse_dict_property(self, this: str) -> exp.DictProperty:
 9669        settings = []
 9670
 9671        self._match_l_paren()
 9672        kind = self._parse_id_var()
 9673
 9674        if self._match(TokenType.L_PAREN):
 9675            while True:
 9676                key = self._parse_id_var()
 9677                value = self._parse_function() or self._parse_primary_or_var()
 9678                if not key and value is None:
 9679                    break
 9680                settings.append(self.expression(exp.DictSubProperty(this=key, value=value)))
 9681            self._match(TokenType.R_PAREN)
 9682
 9683        self._match_r_paren()
 9684
 9685        return self.expression(
 9686            exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings)
 9687        )
 9688
 9689    def _parse_dict_range(self, this: str) -> exp.DictRange:
 9690        self._match_l_paren()
 9691        has_min = self._match_text_seq("MIN")
 9692        if has_min:
 9693            min = self._parse_var() or self._parse_primary()
 9694            self._match_text_seq("MAX")
 9695            max = self._parse_var() or self._parse_primary()
 9696        else:
 9697            max = self._parse_var() or self._parse_primary()
 9698            min = exp.Literal.number(0)
 9699        self._match_r_paren()
 9700        return self.expression(exp.DictRange(this=this, min=min, max=max))
 9701
 9702    def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None:
 9703        index = self._index
 9704        expression = self._parse_column()
 9705        position = self._match(TokenType.COMMA) and self._parse_column()
 9706
 9707        if not self._match(TokenType.IN):
 9708            self._retreat(index - 1)
 9709            return None
 9710        iterator = self._parse_column()
 9711        condition = self._parse_disjunction() if self._match_text_seq("IF") else None
 9712        return self.expression(
 9713            exp.Comprehension(
 9714                this=this,
 9715                expression=expression,
 9716                position=position,
 9717                iterator=iterator,
 9718                condition=condition,
 9719            )
 9720        )
 9721
 9722    def _parse_heredoc(self) -> exp.Heredoc | None:
 9723        if self._match(TokenType.HEREDOC_STRING):
 9724            return self.expression(exp.Heredoc(this=self._prev.text))
 9725
 9726        if not self._match_text_seq("$"):
 9727            return None
 9728
 9729        tags = ["$"]
 9730        tag_text = None
 9731
 9732        if self._is_connected():
 9733            self._advance()
 9734            tags.append(self._prev.text.upper())
 9735        else:
 9736            self.raise_error("No closing $ found")
 9737
 9738        if tags[-1] != "$":
 9739            if self._is_connected() and self._match_text_seq("$"):
 9740                tag_text = tags[-1]
 9741                tags.append("$")
 9742            else:
 9743                self.raise_error("No closing $ found")
 9744
 9745        heredoc_start = self._curr
 9746
 9747        while self._curr:
 9748            if self._match_text_seq(*tags, advance=False):
 9749                this = self._find_sql(heredoc_start, self._prev)
 9750                self._advance(len(tags))
 9751                return self.expression(exp.Heredoc(this=this, tag=tag_text))
 9752
 9753            self._advance()
 9754
 9755        self.raise_error(f"No closing {''.join(tags)} found")
 9756        return None
 9757
 9758    def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None:
 9759        if not self._curr:
 9760            return None
 9761
 9762        index = self._index
 9763        this = []
 9764        while True:
 9765            # The current token might be multiple words
 9766            curr = self._curr.text.upper()
 9767            key = curr.split(" ")
 9768            this.append(curr)
 9769
 9770            self._advance()
 9771            result, trie = in_trie(trie, key)
 9772            if result == TrieResult.FAILED:
 9773                break
 9774
 9775            if result == TrieResult.EXISTS:
 9776                subparser = parsers[" ".join(this)]
 9777                return subparser
 9778
 9779        self._retreat(index)
 9780        return None
 9781
 9782    def _match_l_paren(self, expression: exp.Expr | None = None) -> None:
 9783        if not self._match(TokenType.L_PAREN, expression=expression):
 9784            self.raise_error("Expecting (")
 9785
 9786    def _match_r_paren(self, expression: exp.Expr | None = None) -> None:
 9787        if not self._match(TokenType.R_PAREN, expression=expression):
 9788            self.raise_error("Expecting )")
 9789
 9790    def _replace_lambda(
 9791        self, node: exp.Expr | None, expressions: list[exp.Expr]
 9792    ) -> exp.Expr | None:
 9793        if not node:
 9794            return node
 9795
 9796        lambda_types = {e.name: e.args.get("to") or False for e in expressions}
 9797
 9798        for column in node.find_all(exp.Column):
 9799            typ = lambda_types.get(column.parts[0].name)
 9800            if typ is not None:
 9801                dot_or_id = column.to_dot() if column.table else column.this
 9802
 9803                if typ:
 9804                    dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ))
 9805
 9806                parent = column.parent
 9807
 9808                while isinstance(parent, exp.Dot):
 9809                    if not isinstance(parent.parent, exp.Dot):
 9810                        parent.replace(dot_or_id)
 9811                        break
 9812                    parent = parent.parent
 9813                else:
 9814                    if column is node:
 9815                        node = dot_or_id
 9816                    else:
 9817                        column.replace(dot_or_id)
 9818        return node
 9819
 9820    def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr:
 9821        start = self._prev
 9822
 9823        # Not to be confused with TRUNCATE(number, decimals) function call
 9824        if self._match(TokenType.L_PAREN):
 9825            self._retreat(self._index - 2)
 9826            return self._parse_function()
 9827
 9828        # Clickhouse supports TRUNCATE DATABASE as well
 9829        is_database = self._match(TokenType.DATABASE)
 9830
 9831        self._match(TokenType.TABLE)
 9832
 9833        exists = self._parse_exists(not_=False)
 9834
 9835        expressions = self._parse_csv(
 9836            lambda: self._parse_table(schema=True, is_db_reference=is_database)
 9837        )
 9838
 9839        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9840
 9841        if self._match_text_seq("RESTART", "IDENTITY"):
 9842            identity = "RESTART"
 9843        elif self._match_text_seq("CONTINUE", "IDENTITY"):
 9844            identity = "CONTINUE"
 9845        else:
 9846            identity = None
 9847
 9848        if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"):
 9849            option = self._prev.text
 9850        else:
 9851            option = None
 9852
 9853        partition = self._parse_partition()
 9854
 9855        # Fallback case
 9856        if self._curr:
 9857            return self._parse_as_command(start)
 9858
 9859        return self.expression(
 9860            exp.TruncateTable(
 9861                expressions=expressions,
 9862                is_database=is_database,
 9863                exists=exists,
 9864                cluster=cluster,
 9865                identity=identity,
 9866                option=option,
 9867                partition=partition,
 9868            )
 9869        )
 9870
 9871    def _parse_indexed_column(self) -> exp.Expr | None:
 9872        return self._parse_ordered(self._parse_opclass)
 9873
 9874    def _parse_with_operator(self) -> exp.Expr | None:
 9875        this = self._parse_indexed_column()
 9876
 9877        if not self._match(TokenType.WITH):
 9878            return this
 9879
 9880        op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS)
 9881
 9882        return self.expression(exp.WithOperator(this=this, op=op))
 9883
 9884    def _parse_wrapped_options(self) -> list[exp.Expr]:
 9885        self._match(TokenType.EQ)
 9886        self._match(TokenType.L_PAREN)
 9887
 9888        opts: list[exp.Expr] = []
 9889        option: exp.Expr | list[exp.Expr] | None
 9890        while self._curr and not self._match(TokenType.R_PAREN):
 9891            if self._match_text_seq("FORMAT_NAME", "="):
 9892                # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL
 9893                option = self._parse_format_name()
 9894            else:
 9895                option = self._parse_property()
 9896
 9897            if option is None:
 9898                self.raise_error("Unable to parse option")
 9899                break
 9900
 9901            opts.extend(ensure_list(option))
 9902
 9903        return opts
 9904
 9905    def _parse_copy_parameters(self) -> list[exp.CopyParameter]:
 9906        sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None
 9907
 9908        options = []
 9909        while self._curr and not self._match(TokenType.R_PAREN, advance=False):
 9910            option = self._parse_var(any_token=True)
 9911            prev = self._prev.text.upper()
 9912
 9913            # Different dialects might separate options and values by white space, "=" and "AS"
 9914            self._match(TokenType.EQ)
 9915            self._match(TokenType.ALIAS)
 9916
 9917            param = self.expression(exp.CopyParameter(this=option))
 9918
 9919            if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match(
 9920                TokenType.L_PAREN, advance=False
 9921            ):
 9922                # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options
 9923                param.set("expressions", self._parse_wrapped_options())
 9924            elif prev == "FILE_FORMAT":
 9925                # T-SQL's external file format case
 9926                param.set("expression", self._parse_field())
 9927            elif (
 9928                prev == "FORMAT"
 9929                and self._prev.token_type == TokenType.ALIAS
 9930                and self._match_texts(("AVRO", "JSON"))
 9931            ):
 9932                param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}"))
 9933                param.set("expression", self._parse_field())
 9934            else:
 9935                param.set("expression", self._parse_unquoted_field() or self._parse_bracket())
 9936
 9937            options.append(param)
 9938
 9939            if sep:
 9940                self._match(sep)
 9941
 9942        return options
 9943
 9944    def _parse_credentials(self) -> exp.Credentials | None:
 9945        expr = self.expression(exp.Credentials())
 9946
 9947        if self._match_text_seq("STORAGE_INTEGRATION", "="):
 9948            expr.set("storage", self._parse_field())
 9949        if self._match_text_seq("CREDENTIALS"):
 9950            # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string>
 9951            creds = (
 9952                self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field()
 9953            )
 9954            expr.set("credentials", creds)
 9955        if self._match_text_seq("ENCRYPTION"):
 9956            expr.set("encryption", self._parse_wrapped_options())
 9957        if self._match_text_seq("IAM_ROLE"):
 9958            expr.set(
 9959                "iam_role",
 9960                exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(),
 9961            )
 9962        if self._match_text_seq("REGION"):
 9963            expr.set("region", self._parse_field())
 9964
 9965        return expr
 9966
 9967    def _parse_file_location(self) -> exp.Expr | None:
 9968        return self._parse_field()
 9969
 9970    def _parse_copy(self) -> exp.Copy | exp.Command:
 9971        start = self._prev
 9972
 9973        self._match(TokenType.INTO)
 9974
 9975        this = (
 9976            self._parse_select(nested=True, parse_subquery_alias=False)
 9977            if self._match(TokenType.L_PAREN, advance=False)
 9978            else self._parse_table(schema=True)
 9979        )
 9980
 9981        kind = self._match(TokenType.FROM) or not self._match_text_seq("TO")
 9982
 9983        files = self._parse_csv(self._parse_file_location)
 9984        if self._match(TokenType.EQ, advance=False):
 9985            # Backtrack one token since we've consumed the lhs of a parameter assignment here.
 9986            # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter
 9987            # list via `_parse_wrapped(..)` below.
 9988            self._advance(-1)
 9989            files = []
 9990
 9991        credentials = self._parse_credentials()
 9992
 9993        self._match_text_seq("WITH")
 9994
 9995        params = self._parse_wrapped(self._parse_copy_parameters, optional=True)
 9996
 9997        # Fallback case
 9998        if self._curr:
 9999            return self._parse_as_command(start)
10000
10001        return self.expression(
10002            exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params)
10003        )
10004
10005    def _parse_normalize(self) -> exp.Normalize:
10006        return self.expression(
10007            exp.Normalize(
10008                this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var()
10009            )
10010        )
10011
10012    def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor:
10013        args = self._parse_csv(lambda: self._parse_lambda())
10014
10015        this = seq_get(args, 0)
10016        decimals = seq_get(args, 1)
10017
10018        return expr_type(
10019            this=this,
10020            decimals=decimals,
10021            to=self._parse_var() if self._match_text_seq("TO") else None,
10022        )
10023
10024    def _parse_star_ops(self) -> exp.Expr | None:
10025        star_token = self._prev
10026
10027        if self._match_text_seq("COLUMNS", "(", advance=False):
10028            this = self._parse_function()
10029            if isinstance(this, exp.Columns):
10030                this.set("unpack", True)
10031            return this
10032
10033        index = self._index
10034        ilike = self._parse_string() if self._match(TokenType.ILIKE) else None
10035        if not ilike:
10036            # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)`
10037            self._retreat(index)
10038
10039        return self.expression(
10040            exp.Star(
10041                ilike=ilike,
10042                except_=self._parse_star_op("EXCEPT", "EXCLUDE"),
10043                replace=self._parse_star_op("REPLACE"),
10044                rename=self._parse_star_op("RENAME"),
10045            )
10046        ).update_positions(star_token)
10047
10048    def _parse_grant_privilege(self) -> exp.GrantPrivilege | None:
10049        privilege_parts = []
10050
10051        # Keep consuming consecutive keywords until comma (end of this privilege) or ON
10052        # (end of privilege list) or L_PAREN (start of column list) are met
10053        while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False):
10054            privilege_parts.append(self._curr.text.upper())
10055            self._advance()
10056
10057        if not privilege_parts:
10058            self.raise_error("Expected privilege")
10059            return None
10060
10061        this = exp.var(" ".join(privilege_parts))
10062        expressions = (
10063            self._parse_wrapped_csv(self._parse_column)
10064            if self._match(TokenType.L_PAREN, advance=False)
10065            else None
10066        )
10067
10068        return self.expression(exp.GrantPrivilege(this=this, expressions=expressions))
10069
10070    def _parse_grant_principal(self) -> exp.GrantPrincipal | None:
10071        kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper()
10072        principal = self._parse_id_var()
10073
10074        if not principal:
10075            return None
10076
10077        return self.expression(exp.GrantPrincipal(this=principal, kind=kind))
10078
10079    def _parse_grant_revoke_common(
10080        self,
10081    ) -> tuple[list | None, str | None, exp.Expr | None]:
10082        privileges = self._parse_csv(self._parse_grant_privilege)
10083
10084        self._match(TokenType.ON)
10085        kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None
10086
10087        # Attempt to parse the securable e.g. MySQL allows names
10088        # such as "foo.*", "*.*" which are not easily parseable yet
10089        securable = self._try_parse(self._parse_table_parts)
10090
10091        return privileges, kind, securable
10092
10093    def _parse_grant(self) -> exp.Grant | exp.Command:
10094        start = self._prev
10095
10096        privileges, kind, securable = self._parse_grant_revoke_common()
10097
10098        if not securable or not self._match_text_seq("TO"):
10099            return self._parse_as_command(start)
10100
10101        principals = self._parse_csv(self._parse_grant_principal)
10102
10103        grant_option = self._match_text_seq("WITH", "GRANT", "OPTION")
10104
10105        if self._curr:
10106            return self._parse_as_command(start)
10107
10108        return self.expression(
10109            exp.Grant(
10110                privileges=privileges,
10111                kind=kind,
10112                securable=securable,
10113                principals=principals,
10114                grant_option=grant_option,
10115            )
10116        )
10117
10118    def _parse_revoke(self) -> exp.Revoke | exp.Command:
10119        start = self._prev
10120
10121        grant_option = self._match_text_seq("GRANT", "OPTION", "FOR")
10122
10123        privileges, kind, securable = self._parse_grant_revoke_common()
10124
10125        if not securable or not self._match_text_seq("FROM"):
10126            return self._parse_as_command(start)
10127
10128        principals = self._parse_csv(self._parse_grant_principal)
10129
10130        cascade = None
10131        if self._match_texts(("CASCADE", "RESTRICT")):
10132            cascade = self._prev.text.upper()
10133
10134        if self._curr:
10135            return self._parse_as_command(start)
10136
10137        return self.expression(
10138            exp.Revoke(
10139                privileges=privileges,
10140                kind=kind,
10141                securable=securable,
10142                principals=principals,
10143                grant_option=grant_option,
10144                cascade=cascade,
10145            )
10146        )
10147
10148    def _parse_overlay(self) -> exp.Overlay:
10149        def _parse_overlay_arg(text: str) -> exp.Expr | None:
10150            return (
10151                self._parse_bitwise()
10152                if self._match(TokenType.COMMA) or self._match_text_seq(text)
10153                else None
10154            )
10155
10156        return self.expression(
10157            exp.Overlay(
10158                this=self._parse_bitwise(),
10159                expression=_parse_overlay_arg("PLACING"),
10160                from_=_parse_overlay_arg("FROM"),
10161                for_=_parse_overlay_arg("FOR"),
10162            )
10163        )
10164
10165    def _parse_format_name(self) -> exp.Property:
10166        # Note: Although not specified in the docs, Snowflake does accept a string/identifier
10167        # for FILE_FORMAT = <format_name>
10168        return self.expression(
10169            exp.Property(
10170                this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts()
10171            )
10172        )
10173
10174    def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F:
10175        is_distinct = self._match(TokenType.DISTINCT)
10176        if not is_distinct:
10177            self._match(TokenType.ALL)
10178
10179        args = [self._parse_lambda()]
10180        if self._match(TokenType.COMMA):
10181            args.extend(self._parse_function_args())
10182
10183        target = seq_get(args, distinct_index)
10184        if is_distinct and target:
10185            args[distinct_index] = self.expression(exp.Distinct(expressions=[target]))
10186
10187        return func.from_arg_list(args)
10188
10189    def _identifier_expression(
10190        self, token: Token | None = None, quoted: bool | None = None
10191    ) -> exp.Identifier:
10192        token = token or self._prev
10193        return self.expression(exp.Identifier(this=token.text, quoted=quoted), token)
10194
10195    def _build_pipe_cte(
10196        self,
10197        query: exp.Query,
10198        expressions: list[exp.Expr],
10199        alias_cte: exp.TableAlias | None = None,
10200    ) -> exp.Select:
10201        new_cte: str | exp.TableAlias | None
10202        if alias_cte:
10203            new_cte = alias_cte
10204        else:
10205            self._pipe_cte_counter += 1
10206            new_cte = f"__tmp{self._pipe_cte_counter}"
10207
10208        with_ = query.args.get("with_")
10209        ctes = with_.pop() if with_ else None
10210
10211        new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False)
10212        if ctes:
10213            new_select.set("with_", ctes)
10214
10215        return new_select.with_(new_cte, as_=query, copy=False)
10216
10217    def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select:
10218        select = self._parse_select(consume_pipe=False)
10219        if not select:
10220            return query
10221
10222        return self._build_pipe_cte(
10223            query=query.select(*select.expressions, append=False), expressions=[exp.Star()]
10224        )
10225
10226    def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select:
10227        limit = self._parse_limit()
10228        offset = self._parse_offset()
10229        if limit:
10230            curr_limit = query.args.get("limit", limit)
10231            if curr_limit.expression.to_py() >= limit.expression.to_py():
10232                query.limit(limit, copy=False)
10233        if offset:
10234            curr_offset = query.args.get("offset")
10235            curr_offset = curr_offset.expression.to_py() if curr_offset else 0
10236            query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False)
10237
10238        return query
10239
10240    def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None:
10241        this = self._parse_disjunction()
10242        if self._match_text_seq("GROUP", "AND", advance=False):
10243            return this
10244
10245        this = self._parse_alias(this)
10246
10247        if self._match_set((TokenType.ASC, TokenType.DESC), advance=False):
10248            return self._parse_ordered(lambda: this)
10249
10250        return this
10251
10252    def _parse_pipe_syntax_aggregate_group_order_by(
10253        self, query: exp.Select, group_by_exists: bool = True
10254    ) -> exp.Select:
10255        expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields)
10256        aggregates_or_groups, orders = [], []
10257        for element in expr:
10258            if isinstance(element, exp.Ordered):
10259                this = element.this
10260                if isinstance(this, exp.Alias):
10261                    element.set("this", this.args["alias"])
10262                orders.append(element)
10263            else:
10264                this = element
10265            aggregates_or_groups.append(this)
10266
10267        if group_by_exists:
10268            query.select(
10269                *aggregates_or_groups, *query.expressions, append=False, copy=False
10270            ).group_by(
10271                *[projection.args.get("alias", projection) for projection in aggregates_or_groups],
10272                copy=False,
10273            )
10274        else:
10275            query.select(*aggregates_or_groups, append=False, copy=False)
10276
10277        if orders:
10278            return query.order_by(*orders, append=False, copy=False)
10279
10280        return query
10281
10282    def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select:
10283        self._match_text_seq("AGGREGATE")
10284        query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False)
10285
10286        if self._match(TokenType.GROUP_BY) or (
10287            self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY)
10288        ):
10289            query = self._parse_pipe_syntax_aggregate_group_order_by(query)
10290
10291        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10292
10293    def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None:
10294        first_setop = self.parse_set_operation(this=query)
10295        if not first_setop:
10296            return None
10297
10298        def _parse_and_unwrap_query() -> exp.Expr | None:
10299            expr = self._parse_paren()
10300            return expr.assert_is(exp.Subquery).unnest() if expr else None
10301
10302        first_setop.this.pop()
10303
10304        setops = [
10305            first_setop.expression.pop().assert_is(exp.Subquery).unnest(),
10306            *self._parse_csv(_parse_and_unwrap_query),
10307        ]
10308
10309        query = self._build_pipe_cte(query=query, expressions=[exp.Star()])
10310        with_ = query.args.get("with_")
10311        ctes = with_.pop() if with_ else None
10312
10313        if isinstance(first_setop, exp.Union):
10314            query = query.union(*setops, copy=False, **first_setop.args)
10315        elif isinstance(first_setop, exp.Except):
10316            query = query.except_(*setops, copy=False, **first_setop.args)
10317        else:
10318            query = query.intersect(*setops, copy=False, **first_setop.args)
10319
10320        query.set("with_", ctes)
10321
10322        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10323
10324    def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None:
10325        join = self._parse_join()
10326        if not join:
10327            return None
10328
10329        if isinstance(query, exp.Select):
10330            return query.join(join, copy=False)
10331
10332        return query
10333
10334    def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select:
10335        pivots = self._parse_pivots()
10336        if not pivots:
10337            return query
10338
10339        from_ = query.args.get("from_")
10340        if from_:
10341            from_.this.set("pivots", pivots)
10342
10343        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10344
10345    def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select:
10346        self._match_text_seq("EXTEND")
10347        query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False)
10348        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10349
10350    def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select:
10351        sample = self._parse_table_sample()
10352
10353        with_ = query.args.get("with_")
10354        if with_:
10355            with_.expressions[-1].this.set("sample", sample)
10356        else:
10357            query.set("sample", sample)
10358
10359        return query
10360
10361    def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None:
10362        if isinstance(query, exp.Subquery):
10363            query = exp.select("*").from_(query, copy=False)
10364
10365        if not query.args.get("from_"):
10366            query = exp.select("*").from_(query.subquery(copy=False), copy=False)
10367
10368        while self._match(TokenType.PIPE_GT):
10369            start_index = self._index
10370            start_text = self._curr.text.upper()
10371            parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text)
10372            if not parser:
10373                # The set operators (UNION, etc) and the JOIN operator have a few common starting
10374                # keywords, making it tricky to disambiguate them without lookahead. The approach
10375                # here is to try and parse a set operation and if that fails, then try to parse a
10376                # join operator. If that fails as well, then the operator is not supported.
10377                parsed_query = self._parse_pipe_syntax_set_operator(query)
10378                parsed_query = parsed_query or self._parse_pipe_syntax_join(query)
10379                if not parsed_query:
10380                    self._retreat(start_index)
10381                    self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.")
10382                    break
10383                query = parsed_query
10384            else:
10385                query = parser(self, query)
10386
10387        return query
10388
10389    def _parse_declareitem(self) -> exp.DeclareItem | None:
10390        self._match_texts(("VAR", "VARIABLE"))
10391
10392        vars = self._parse_csv(self._parse_id_var)
10393        if not vars:
10394            return None
10395
10396        self._match(TokenType.ALIAS)
10397        kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types()
10398        default = (
10399            self._match(TokenType.DEFAULT) or self._match(TokenType.EQ)
10400        ) and self._parse_bitwise()
10401
10402        return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default))
10403
10404    def _parse_declare(self) -> exp.Declare | exp.Command:
10405        start = self._prev
10406        replace = self._match_text_seq("OR", "REPLACE")
10407        expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem))
10408
10409        if not expressions or self._curr:
10410            return self._parse_as_command(start)
10411
10412        return self.expression(exp.Declare(expressions=expressions, replace=replace))
10413
10414    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10415        exp_class = exp.Cast if strict else exp.TryCast
10416
10417        if exp_class == exp.TryCast:
10418            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10419
10420        return self.expression(exp_class(**kwargs))
10421
10422    def _parse_json_value(self) -> exp.JSONValue:
10423        this = self._parse_bitwise()
10424        self._match(TokenType.COMMA)
10425        path = self._parse_bitwise()
10426
10427        returning = self._match(TokenType.RETURNING) and self._parse_type()
10428
10429        return self.expression(
10430            exp.JSONValue(
10431                this=this,
10432                path=self.dialect.to_json_path(path),
10433                returning=returning,
10434                on_condition=self._parse_on_condition(),
10435            )
10436        )
10437
10438    def _parse_group_concat(self) -> exp.Expr | None:
10439        def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr:
10440            if isinstance(node, exp.Distinct) and len(node.expressions) > 1:
10441                concat_exprs = [
10442                    self.expression(
10443                        exp.Concat(
10444                            expressions=node.expressions,
10445                            safe=True,
10446                            coalesce=self.dialect.CONCAT_COALESCE,
10447                        )
10448                    )
10449                ]
10450                node.set("expressions", concat_exprs)
10451                return node
10452            if len(exprs) == 1:
10453                return exprs[0]
10454            return self.expression(
10455                exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE)
10456            )
10457
10458        args = self._parse_csv(self._parse_lambda)
10459
10460        if args:
10461            order = args[-1] if isinstance(args[-1], exp.Order) else None
10462
10463            if order:
10464                # Order By is the last (or only) expression in the list and has consumed the 'expr' before it,
10465                # remove 'expr' from exp.Order and add it back to args
10466                args[-1] = order.this
10467                order.set("this", concat_exprs(order.this, args))
10468
10469            this = order or concat_exprs(args[0], args)
10470        else:
10471            this = None
10472
10473        separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None
10474
10475        return self.expression(exp.GroupConcat(this=this, separator=separator))
10476
10477    def _parse_initcap(self) -> exp.Initcap:
10478        expr = exp.Initcap.from_arg_list(self._parse_function_args())
10479
10480        # attach dialect's default delimiters
10481        if expr.args.get("expression") is None:
10482            expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS))
10483
10484        return expr
10485
10486    def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None:
10487        while True:
10488            if not self._match(TokenType.L_PAREN):
10489                break
10490
10491            op = ""
10492            while self._curr and not self._match(TokenType.R_PAREN):
10493                op += self._curr.text
10494                self._advance()
10495
10496            comments = self._prev_comments
10497            this = self.expression(
10498                exp.Operator(this=this, operator=op, expression=self._parse_bitwise()),
10499                comments=comments,
10500            )
10501
10502            if not self._match(TokenType.OPERATOR):
10503                break
10504
10505        return this
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"}
 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        index = self._index
 2925
 2926        seq_props = self._parse_sequence_properties()
 2927        if seq_props:
 2928            return seq_props
 2929
 2930        self._retreat(index)
 2931        return self._parse_key_value_property()
 2932
 2933    def _parse_key_value_property(
 2934        self, parse_value: t.Callable[[], exp.Expr | None] | None = None
 2935    ) -> exp.Property | None:
 2936        index = self._index
 2937        key = self._parse_column()
 2938
 2939        if not self._match(TokenType.EQ):
 2940            self._retreat(index)
 2941            return None
 2942
 2943        # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise
 2944        if isinstance(key, exp.Column):
 2945            key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name)
 2946
 2947        value = (
 2948            parse_value()
 2949            if parse_value
 2950            else self._parse_bitwise() or self._parse_var(any_token=True)
 2951        )
 2952
 2953        # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier())
 2954        if isinstance(value, exp.Column):
 2955            value = exp.var(value.name)
 2956
 2957        return self.expression(exp.Property(this=key, value=value))
 2958
 2959    def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty:
 2960        if self._match_text_seq("BY"):
 2961            return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string()))
 2962
 2963        self._match(TokenType.ALIAS)
 2964        input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None
 2965        output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None
 2966
 2967        return self.expression(
 2968            exp.FileFormatProperty(
 2969                this=(
 2970                    self.expression(
 2971                        exp.InputOutputFormat(
 2972                            input_format=input_format, output_format=output_format
 2973                        )
 2974                    )
 2975                    if input_format or output_format
 2976                    else self._parse_var_or_string() or self._parse_number() or self._parse_id_var()
 2977                ),
 2978                hive_format=True,
 2979            )
 2980        )
 2981
 2982    def _parse_unquoted_field(self) -> exp.Expr | None:
 2983        field = self._parse_field()
 2984        if isinstance(field, exp.Identifier) and not field.quoted:
 2985            field = exp.var(field)
 2986
 2987        return field
 2988
 2989    def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E:
 2990        self._match(TokenType.EQ)
 2991        self._match(TokenType.ALIAS)
 2992
 2993        return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs))
 2994
 2995    def _parse_properties(self, before: bool | None = None) -> exp.Properties | None:
 2996        properties = []
 2997        while True:
 2998            if before:
 2999                prop = self._parse_property_before()
 3000            else:
 3001                prop = self._parse_property()
 3002            if not prop:
 3003                break
 3004            for p in ensure_list(prop):
 3005                properties.append(p)
 3006
 3007        if properties:
 3008            return self.expression(exp.Properties(expressions=properties))
 3009
 3010        return None
 3011
 3012    def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty:
 3013        return self.expression(
 3014            exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION"))
 3015        )
 3016
 3017    def _parse_sql_security(self) -> exp.SqlSecurityProperty:
 3018        return self.expression(
 3019            exp.SqlSecurityProperty(
 3020                this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper()
 3021            )
 3022        )
 3023
 3024    def _parse_settings_property(self) -> exp.SettingsProperty:
 3025        return self.expression(
 3026            exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment))
 3027        )
 3028
 3029    def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None:
 3030        if not self._match_text_seq("ON", "NULL", "INPUT"):
 3031            self._retreat(self._index - 1)
 3032            return None
 3033
 3034        return self.expression(exp.CalledOnNullInputProperty())
 3035
 3036    def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty:
 3037        if self._index >= 2:
 3038            pre_volatile_token = self._tokens[self._index - 2]
 3039        else:
 3040            pre_volatile_token = None
 3041
 3042        if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS:
 3043            return exp.VolatileProperty()
 3044
 3045        return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 3046
 3047    def _parse_retention_period(self) -> exp.Var:
 3048        # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...}
 3049        number = self._parse_number()
 3050        number_str = f"{number} " if number else ""
 3051        unit = self._parse_var(any_token=True)
 3052        return exp.var(f"{number_str}{unit}")
 3053
 3054    def _parse_system_versioning_property(
 3055        self, with_: bool = False
 3056    ) -> exp.WithSystemVersioningProperty:
 3057        self._match(TokenType.EQ)
 3058        prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_))
 3059
 3060        if self._match_text_seq("OFF"):
 3061            prop.set("on", False)
 3062            return prop
 3063
 3064        self._match(TokenType.ON)
 3065        if self._match(TokenType.L_PAREN):
 3066            while self._curr and not self._match(TokenType.R_PAREN):
 3067                if self._match_text_seq("HISTORY_TABLE", "="):
 3068                    prop.set("this", self._parse_table_parts())
 3069                elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="):
 3070                    prop.set("data_consistency", self._advance_any() and self._prev.text.upper())
 3071                elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="):
 3072                    prop.set("retention_period", self._parse_retention_period())
 3073
 3074                self._match(TokenType.COMMA)
 3075
 3076        return prop
 3077
 3078    def _parse_data_deletion_property(self) -> exp.DataDeletionProperty:
 3079        self._match(TokenType.EQ)
 3080        on = self._match_text_seq("ON") or not self._match_text_seq("OFF")
 3081        prop = self.expression(exp.DataDeletionProperty(on=on))
 3082
 3083        if self._match(TokenType.L_PAREN):
 3084            while self._curr and not self._match(TokenType.R_PAREN):
 3085                if self._match_text_seq("FILTER_COLUMN", "="):
 3086                    prop.set("filter_column", self._parse_column())
 3087                elif self._match_text_seq("RETENTION_PERIOD", "="):
 3088                    prop.set("retention_period", self._parse_retention_period())
 3089
 3090                self._match(TokenType.COMMA)
 3091
 3092        return prop
 3093
 3094    def _parse_distributed_property(self) -> exp.DistributedByProperty:
 3095        kind = "HASH"
 3096        expressions: list[exp.Expr] | None = None
 3097        if self._match_text_seq("BY", "HASH"):
 3098            expressions = self._parse_wrapped_csv(self._parse_id_var)
 3099        elif self._match_text_seq("BY", "RANDOM"):
 3100            kind = "RANDOM"
 3101
 3102        # If the BUCKETS keyword is not present, the number of buckets is AUTO
 3103        buckets: exp.Expr | None = None
 3104        if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"):
 3105            buckets = self._parse_number()
 3106
 3107        return self.expression(
 3108            exp.DistributedByProperty(
 3109                expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order()
 3110            )
 3111        )
 3112
 3113    def _parse_composite_key_property(self, expr_type: type[E]) -> E:
 3114        self._match_text_seq("KEY")
 3115        expressions = self._parse_wrapped_id_vars()
 3116        return self.expression(expr_type(expressions=expressions))
 3117
 3118    def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]:
 3119        if self._match_text_seq("(", "SYSTEM_VERSIONING"):
 3120            prop = self._parse_system_versioning_property(with_=True)
 3121            self._match_r_paren()
 3122            return prop
 3123
 3124        if self._match(TokenType.L_PAREN, advance=False):
 3125            result: list[exp.Expr] = []
 3126            for i in self._parse_wrapped_properties():
 3127                result.extend(i) if isinstance(i, list) else result.append(i)
 3128            return result
 3129
 3130        if self._match_text_seq("JOURNAL"):
 3131            return self._parse_withjournaltable()
 3132
 3133        if self._match_texts(self.VIEW_ATTRIBUTES):
 3134            return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper()))
 3135
 3136        if self._match_text_seq("DATA"):
 3137            return self._parse_withdata(no=False)
 3138        elif self._match_text_seq("NO", "DATA"):
 3139            return self._parse_withdata(no=True)
 3140
 3141        if self._match(TokenType.SERDE_PROPERTIES, advance=False):
 3142            return self._parse_serde_properties(with_=True)
 3143
 3144        if self._match(TokenType.SCHEMA):
 3145            return self.expression(
 3146                exp.WithSchemaBindingProperty(
 3147                    this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS)
 3148                )
 3149            )
 3150
 3151        if self._match_texts(self.PROCEDURE_OPTIONS, advance=False):
 3152            return self.expression(
 3153                exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option))
 3154            )
 3155
 3156        if not self._next:
 3157            return None
 3158
 3159        return self._parse_withisolatedloading()
 3160
 3161    def _parse_procedure_option(self) -> exp.Expr | None:
 3162        if self._match_text_seq("EXECUTE", "AS"):
 3163            return self.expression(
 3164                exp.ExecuteAsProperty(
 3165                    this=self._parse_var_from_options(
 3166                        self.EXECUTE_AS_OPTIONS, raise_unmatched=False
 3167                    )
 3168                    or self._parse_string()
 3169                )
 3170            )
 3171
 3172        return self._parse_var_from_options(self.PROCEDURE_OPTIONS)
 3173
 3174    # https://dev.mysql.com/doc/refman/8.0/en/create-view.html
 3175    def _parse_definer(self) -> exp.DefinerProperty | None:
 3176        self._match(TokenType.EQ)
 3177
 3178        user = self._parse_id_var()
 3179        self._match(TokenType.PARAMETER)
 3180        host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text)
 3181
 3182        if not user or not host:
 3183            return None
 3184
 3185        return exp.DefinerProperty(this=f"{user}@{host}")
 3186
 3187    def _parse_withjournaltable(self) -> exp.WithJournalTableProperty:
 3188        self._match(TokenType.TABLE)
 3189        self._match(TokenType.EQ)
 3190        return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts()))
 3191
 3192    def _parse_log(self, no: bool = False) -> exp.LogProperty:
 3193        return self.expression(exp.LogProperty(no=no))
 3194
 3195    def _parse_journal(self, **kwargs) -> exp.JournalProperty:
 3196        return self.expression(exp.JournalProperty(**kwargs))
 3197
 3198    def _parse_checksum(self) -> exp.ChecksumProperty:
 3199        self._match(TokenType.EQ)
 3200
 3201        on = None
 3202        if self._match(TokenType.ON):
 3203            on = True
 3204        elif self._match_text_seq("OFF"):
 3205            on = False
 3206
 3207        return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT)))
 3208
 3209    def _parse_cluster(self) -> exp.Cluster:
 3210        self._match(TokenType.CLUSTER_BY)
 3211        return self.expression(
 3212            exp.Cluster(
 3213                expressions=self._parse_csv(self._parse_column),
 3214            )
 3215        )
 3216
 3217    def _parse_cluster_property(self) -> exp.ClusterProperty:
 3218        return self.expression(
 3219            exp.ClusterProperty(
 3220                expressions=self._parse_wrapped_csv(self._parse_column),
 3221            )
 3222        )
 3223
 3224    def _parse_clustered_by(self) -> exp.ClusteredByProperty:
 3225        self._match_text_seq("BY")
 3226
 3227        self._match_l_paren()
 3228        expressions = self._parse_csv(self._parse_column)
 3229        self._match_r_paren()
 3230
 3231        if self._match_text_seq("SORTED", "BY"):
 3232            self._match_l_paren()
 3233            sorted_by = self._parse_csv(self._parse_ordered)
 3234            self._match_r_paren()
 3235        else:
 3236            sorted_by = None
 3237
 3238        self._match(TokenType.INTO)
 3239        buckets = self._parse_number()
 3240        self._match_text_seq("BUCKETS")
 3241
 3242        return self.expression(
 3243            exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets)
 3244        )
 3245
 3246    def _parse_copy_property(self) -> exp.CopyGrantsProperty | None:
 3247        if not self._match_text_seq("GRANTS"):
 3248            self._retreat(self._index - 1)
 3249            return None
 3250
 3251        return self.expression(exp.CopyGrantsProperty())
 3252
 3253    def _parse_freespace(self) -> exp.FreespaceProperty:
 3254        self._match(TokenType.EQ)
 3255        return self.expression(
 3256            exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT))
 3257        )
 3258
 3259    def _parse_mergeblockratio(
 3260        self, no: bool = False, default: bool = False
 3261    ) -> exp.MergeBlockRatioProperty:
 3262        if self._match(TokenType.EQ):
 3263            return self.expression(
 3264                exp.MergeBlockRatioProperty(
 3265                    this=self._parse_number(), percent=self._match(TokenType.PERCENT)
 3266                )
 3267            )
 3268
 3269        return self.expression(exp.MergeBlockRatioProperty(no=no, default=default))
 3270
 3271    def _parse_datablocksize(
 3272        self,
 3273        default: bool | None = None,
 3274        minimum: bool | None = None,
 3275        maximum: bool | None = None,
 3276    ) -> exp.DataBlocksizeProperty:
 3277        self._match(TokenType.EQ)
 3278        size = self._parse_number()
 3279
 3280        units = None
 3281        if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")):
 3282            units = self._prev.text
 3283
 3284        return self.expression(
 3285            exp.DataBlocksizeProperty(
 3286                size=size, units=units, default=default, minimum=minimum, maximum=maximum
 3287            )
 3288        )
 3289
 3290    def _parse_blockcompression(self) -> exp.BlockCompressionProperty:
 3291        self._match(TokenType.EQ)
 3292        always = self._match_text_seq("ALWAYS")
 3293        manual = self._match_text_seq("MANUAL")
 3294        never = self._match_text_seq("NEVER")
 3295        default = self._match_text_seq("DEFAULT")
 3296
 3297        autotemp = None
 3298        if self._match_text_seq("AUTOTEMP"):
 3299            autotemp = self._parse_schema()
 3300
 3301        return self.expression(
 3302            exp.BlockCompressionProperty(
 3303                always=always, manual=manual, never=never, default=default, autotemp=autotemp
 3304            )
 3305        )
 3306
 3307    def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None:
 3308        index = self._index
 3309        no = self._match_text_seq("NO")
 3310        concurrent = self._match_text_seq("CONCURRENT")
 3311
 3312        if not self._match_text_seq("ISOLATED", "LOADING"):
 3313            self._retreat(index)
 3314            return None
 3315
 3316        target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False)
 3317        return self.expression(
 3318            exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target)
 3319        )
 3320
 3321    def _parse_locking(self) -> exp.LockingProperty:
 3322        if self._match(TokenType.TABLE):
 3323            kind = "TABLE"
 3324        elif self._match(TokenType.VIEW):
 3325            kind = "VIEW"
 3326        elif self._match(TokenType.ROW):
 3327            kind = "ROW"
 3328        elif self._match_text_seq("DATABASE"):
 3329            kind = "DATABASE"
 3330        else:
 3331            kind = None
 3332
 3333        if kind in ("DATABASE", "TABLE", "VIEW"):
 3334            this = self._parse_table_parts()
 3335        else:
 3336            this = None
 3337
 3338        if self._match(TokenType.FOR):
 3339            for_or_in = "FOR"
 3340        elif self._match(TokenType.IN):
 3341            for_or_in = "IN"
 3342        else:
 3343            for_or_in = None
 3344
 3345        if self._match_text_seq("ACCESS"):
 3346            lock_type = "ACCESS"
 3347        elif self._match_texts(("EXCL", "EXCLUSIVE")):
 3348            lock_type = "EXCLUSIVE"
 3349        elif self._match_text_seq("SHARE"):
 3350            lock_type = "SHARE"
 3351        elif self._match_text_seq("READ"):
 3352            lock_type = "READ"
 3353        elif self._match_text_seq("WRITE"):
 3354            lock_type = "WRITE"
 3355        elif self._match_text_seq("CHECKSUM"):
 3356            lock_type = "CHECKSUM"
 3357        else:
 3358            lock_type = None
 3359
 3360        override = self._match_text_seq("OVERRIDE")
 3361
 3362        return self.expression(
 3363            exp.LockingProperty(
 3364                this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override
 3365            )
 3366        )
 3367
 3368    def _parse_partition_by(self) -> list[exp.Expr]:
 3369        if self._match(TokenType.PARTITION_BY):
 3370            return self._parse_csv(self._parse_disjunction)
 3371        return []
 3372
 3373    def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec:
 3374        def _parse_partition_bound_expr() -> exp.Expr | None:
 3375            if self._match_text_seq("MINVALUE"):
 3376                return exp.var("MINVALUE")
 3377            if self._match_text_seq("MAXVALUE"):
 3378                return exp.var("MAXVALUE")
 3379            return self._parse_bitwise()
 3380
 3381        this: exp.Expr | list[exp.Expr] | None = None
 3382        expression = None
 3383        from_expressions = None
 3384        to_expressions = None
 3385
 3386        if self._match(TokenType.IN):
 3387            this = self._parse_wrapped_csv(self._parse_bitwise)
 3388        elif self._match(TokenType.FROM):
 3389            from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3390            self._match_text_seq("TO")
 3391            to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3392        elif self._match_text_seq("WITH", "(", "MODULUS"):
 3393            this = self._parse_number()
 3394            self._match_text_seq(",", "REMAINDER")
 3395            expression = self._parse_number()
 3396            self._match_r_paren()
 3397        else:
 3398            self.raise_error("Failed to parse partition bound spec.")
 3399
 3400        return self.expression(
 3401            exp.PartitionBoundSpec(
 3402                this=this,
 3403                expression=expression,
 3404                from_expressions=from_expressions,
 3405                to_expressions=to_expressions,
 3406            )
 3407        )
 3408
 3409    # https://www.postgresql.org/docs/current/sql-createtable.html
 3410    def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None:
 3411        if not self._match_text_seq("OF"):
 3412            self._retreat(self._index - 1)
 3413            return None
 3414
 3415        this = self._parse_table(schema=True)
 3416
 3417        if self._match(TokenType.DEFAULT):
 3418            expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT")
 3419        elif self._match_text_seq("FOR", "VALUES"):
 3420            expression = self._parse_partition_bound_spec()
 3421        else:
 3422            self.raise_error("Expecting either DEFAULT or FOR VALUES clause.")
 3423
 3424        return self.expression(exp.PartitionedOfProperty(this=this, expression=expression))
 3425
 3426    def _parse_partitioned_by(self) -> exp.PartitionedByProperty:
 3427        self._match(TokenType.EQ)
 3428        return self.expression(
 3429            exp.PartitionedByProperty(
 3430                this=self._parse_schema() or self._parse_bracket(self._parse_field())
 3431            )
 3432        )
 3433
 3434    def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty:
 3435        if self._match_text_seq("AND", "STATISTICS"):
 3436            statistics = True
 3437        elif self._match_text_seq("AND", "NO", "STATISTICS"):
 3438            statistics = False
 3439        else:
 3440            statistics = None
 3441
 3442        return self.expression(exp.WithDataProperty(no=no, statistics=statistics))
 3443
 3444    def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None:
 3445        if self._match_text_seq("SQL"):
 3446            return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL"))
 3447        return None
 3448
 3449    def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None:
 3450        if self._match_text_seq("SQL", "DATA"):
 3451            return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA"))
 3452        return None
 3453
 3454    def _parse_no_property(self) -> exp.Expr | None:
 3455        if self._match_text_seq("PRIMARY", "INDEX"):
 3456            return exp.NoPrimaryIndexProperty()
 3457        if self._match_text_seq("SQL"):
 3458            return self.expression(exp.SqlReadWriteProperty(this="NO SQL"))
 3459        return None
 3460
 3461    def _parse_on_property(self) -> exp.Expr | None:
 3462        if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"):
 3463            return exp.OnCommitProperty()
 3464        if self._match_text_seq("COMMIT", "DELETE", "ROWS"):
 3465            return exp.OnCommitProperty(delete=True)
 3466        return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var())))
 3467
 3468    def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None:
 3469        if self._match_text_seq("SQL", "DATA"):
 3470            return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA"))
 3471        return None
 3472
 3473    def _parse_distkey(self) -> exp.DistKeyProperty:
 3474        return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var)))
 3475
 3476    def _parse_create_like(self) -> exp.LikeProperty | None:
 3477        table = self._parse_table(schema=True)
 3478
 3479        options = []
 3480        while self._match_texts(("INCLUDING", "EXCLUDING")):
 3481            this = self._prev.text.upper()
 3482
 3483            id_var = self._parse_id_var()
 3484            if not id_var:
 3485                return None
 3486
 3487            options.append(
 3488                self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper())))
 3489            )
 3490
 3491        return self.expression(exp.LikeProperty(this=table, expressions=options))
 3492
 3493    def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty:
 3494        return self.expression(
 3495            exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound)
 3496        )
 3497
 3498    def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty:
 3499        self._match(TokenType.EQ)
 3500        return self.expression(
 3501            exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default)
 3502        )
 3503
 3504    def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty:
 3505        self._match_text_seq("WITH", "CONNECTION")
 3506        return self.expression(
 3507            exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts())
 3508        )
 3509
 3510    def _parse_returns(self) -> exp.ReturnsProperty:
 3511        value: exp.Expr | None
 3512        null = None
 3513        is_table = self._match(TokenType.TABLE)
 3514
 3515        if is_table:
 3516            if self._match(TokenType.LT):
 3517                value = self.expression(
 3518                    exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types))
 3519                )
 3520                if not self._match(TokenType.GT):
 3521                    self.raise_error("Expecting >")
 3522            else:
 3523                value = self._parse_schema(exp.var("TABLE"))
 3524        elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"):
 3525            null = True
 3526            value = None
 3527        else:
 3528            value = self._parse_types()
 3529
 3530        return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null))
 3531
 3532    def _parse_describe(self) -> exp.Describe:
 3533        kind = self._prev.text if self._match_set(self.CREATABLES) else None
 3534        style: str | None = (
 3535            self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None
 3536        )
 3537        if self._match(TokenType.DOT):
 3538            style = None
 3539            self._retreat(self._index - 2)
 3540
 3541        format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None
 3542
 3543        if self._match_set(self.STATEMENT_PARSERS, advance=False):
 3544            this = self._parse_statement()
 3545        else:
 3546            this = self._parse_table(schema=True)
 3547
 3548        properties = self._parse_properties()
 3549        expressions = properties.expressions if properties else None
 3550        partition = self._parse_partition()
 3551        return self.expression(
 3552            exp.Describe(
 3553                this=this,
 3554                style=style,
 3555                kind=kind,
 3556                expressions=expressions,
 3557                partition=partition,
 3558                format=format,
 3559                as_json=self._match_text_seq("AS", "JSON"),
 3560            )
 3561        )
 3562
 3563    def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts:
 3564        kind = self._prev.text.upper()
 3565        expressions = []
 3566
 3567        def parse_conditional_insert() -> exp.ConditionalInsert | None:
 3568            if self._match(TokenType.WHEN):
 3569                expression = self._parse_disjunction()
 3570                self._match(TokenType.THEN)
 3571            else:
 3572                expression = None
 3573
 3574            else_ = self._match(TokenType.ELSE)
 3575
 3576            if not self._match(TokenType.INTO):
 3577                return None
 3578
 3579            return self.expression(
 3580                exp.ConditionalInsert(
 3581                    this=self.expression(
 3582                        exp.Insert(
 3583                            this=self._parse_table(schema=True),
 3584                            expression=self._parse_derived_table_values(),
 3585                        )
 3586                    ),
 3587                    expression=expression,
 3588                    else_=else_,
 3589                )
 3590            )
 3591
 3592        expression = parse_conditional_insert()
 3593        while expression is not None:
 3594            expressions.append(expression)
 3595            expression = parse_conditional_insert()
 3596
 3597        return self.expression(
 3598            exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()),
 3599            comments=comments,
 3600        )
 3601
 3602    def _parse_insert(self) -> exp.Insert | exp.MultitableInserts:
 3603        comments: list[str] = []
 3604        hint = self._parse_hint()
 3605        overwrite = self._match(TokenType.OVERWRITE)
 3606        ignore = self._match(TokenType.IGNORE)
 3607        local = self._match_text_seq("LOCAL")
 3608        alternative = None
 3609        is_function = None
 3610
 3611        if self._match_text_seq("DIRECTORY"):
 3612            this: exp.Expr | None = self.expression(
 3613                exp.Directory(
 3614                    this=self._parse_var_or_string(),
 3615                    local=local,
 3616                    row_format=self._parse_row_format(match_row=True),
 3617                )
 3618            )
 3619        else:
 3620            if self._match_set((TokenType.FIRST, TokenType.ALL)):
 3621                comments += ensure_list(self._prev_comments)
 3622                return self._parse_multitable_inserts(comments)
 3623
 3624            if self._match(TokenType.OR):
 3625                alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text
 3626
 3627            self._match(TokenType.INTO)
 3628            comments += ensure_list(self._prev_comments)
 3629            self._match(TokenType.TABLE)
 3630            is_function = self._match(TokenType.FUNCTION)
 3631
 3632            this = self._parse_function() if is_function else self._parse_insert_table()
 3633
 3634        # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant
 3635        set_values = None
 3636        if self._match(TokenType.SET):
 3637            columns = []
 3638            values = []
 3639
 3640            def _parse_set_assignment() -> exp.Expr | None:
 3641                target = self._parse_column()
 3642                if isinstance(target, exp.Column) and self._match(TokenType.EQ):
 3643                    if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3644                        value: exp.Expr | None = exp.var(self._prev.text.upper())
 3645                    else:
 3646                        value = self._parse_disjunction()
 3647
 3648                    if value:
 3649                        columns.append(target.this)
 3650                        values.append(value)
 3651                        return value
 3652
 3653                self.raise_error("Expected column assignment in INSERT ... SET")
 3654                return None
 3655
 3656            self._parse_csv(_parse_set_assignment)
 3657
 3658            this = self.expression(exp.Schema(this=this, expressions=columns))
 3659            set_values = self.expression(
 3660                exp.Values(
 3661                    expressions=[exp.Tuple(expressions=values)],
 3662                    alias=self._parse_table_alias(),
 3663                )
 3664            )
 3665
 3666        returning = self._parse_returning()  # TSQL allows RETURNING before source
 3667
 3668        stored = self._match_text_seq("STORED") and self._parse_stored()
 3669        by_name = self._match_text_seq("BY", "NAME")
 3670        exists = self._parse_exists()
 3671        replace_where = None
 3672        replace_using = None
 3673
 3674        if self._match(TokenType.REPLACE):
 3675            if self._match(TokenType.WHERE):
 3676                replace_where = self._parse_disjunction()
 3677            elif self._match(TokenType.USING):
 3678                replace_using = self._parse_using_identifiers()
 3679
 3680        return self.expression(
 3681            exp.Insert(
 3682                hint=hint,
 3683                is_function=is_function,
 3684                this=this,
 3685                stored=stored,
 3686                by_name=by_name,
 3687                exists=exists,
 3688                where=replace_where,
 3689                using=replace_using,
 3690                partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(),
 3691                settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(),
 3692                default=self._match_text_seq("DEFAULT", "VALUES"),
 3693                expression=set_values
 3694                or self._parse_derived_table_values()
 3695                or self._parse_ddl_select(),
 3696                conflict=self._parse_on_conflict(),
 3697                returning=returning or self._parse_returning(),
 3698                overwrite=overwrite,
 3699                alternative=alternative,
 3700                ignore=ignore,
 3701                source=self._match(TokenType.TABLE) and self._parse_table(),
 3702            ),
 3703            comments=comments,
 3704        )
 3705
 3706    def _parse_insert_table(self) -> exp.Expr | None:
 3707        this = self._parse_table(schema=True, parse_partition=True)
 3708        if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False):
 3709            this.set("alias", self._parse_table_alias())
 3710        return this
 3711
 3712    def _parse_kill(self) -> exp.Kill:
 3713        kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None
 3714
 3715        return self.expression(exp.Kill(this=self._parse_primary(), kind=kind))
 3716
 3717    def _parse_on_conflict(self) -> exp.OnConflict | None:
 3718        conflict = self._match_text_seq("ON", "CONFLICT")
 3719        duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY")
 3720
 3721        if not conflict and not duplicate:
 3722            return None
 3723
 3724        conflict_keys = None
 3725        constraint = None
 3726
 3727        if conflict:
 3728            if self._match_text_seq("ON", "CONSTRAINT"):
 3729                constraint = self._parse_id_var()
 3730            elif self._match(TokenType.L_PAREN):
 3731                conflict_keys = self._parse_csv(self._parse_indexed_column)
 3732                self._match_r_paren()
 3733
 3734        index_predicate = self._parse_where()
 3735
 3736        action = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 3737        if self._prev.token_type == TokenType.UPDATE:
 3738            self._match(TokenType.SET)
 3739            expressions = self._parse_csv(self._parse_equality)
 3740        else:
 3741            expressions = None
 3742
 3743        return self.expression(
 3744            exp.OnConflict(
 3745                duplicate=duplicate,
 3746                expressions=expressions,
 3747                action=action,
 3748                conflict_keys=conflict_keys,
 3749                index_predicate=index_predicate,
 3750                constraint=constraint,
 3751                where=self._parse_where(),
 3752            )
 3753        )
 3754
 3755    def _parse_returning(self) -> exp.Returning | None:
 3756        if not self._match(TokenType.RETURNING):
 3757            return None
 3758        return self.expression(
 3759            exp.Returning(
 3760                expressions=self._parse_csv(self._parse_expression),
 3761                into=self._match(TokenType.INTO) and self._parse_table_part(),
 3762            )
 3763        )
 3764
 3765    def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3766        if not self._match(TokenType.FORMAT):
 3767            return None
 3768        return self._parse_row_format()
 3769
 3770    def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None:
 3771        index = self._index
 3772        with_ = with_ or self._match_text_seq("WITH")
 3773
 3774        if not self._match(TokenType.SERDE_PROPERTIES):
 3775            self._retreat(index)
 3776            return None
 3777        return self.expression(
 3778            exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_)
 3779        )
 3780
 3781    def _parse_row_format(
 3782        self, match_row: bool = False
 3783    ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3784        if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT):
 3785            return None
 3786
 3787        if self._match_text_seq("SERDE"):
 3788            this = self._parse_string()
 3789
 3790            serde_properties = self._parse_serde_properties()
 3791
 3792            return self.expression(
 3793                exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties)
 3794            )
 3795
 3796        self._match_text_seq("DELIMITED")
 3797
 3798        kwargs = {}
 3799
 3800        if self._match_text_seq("FIELDS", "TERMINATED", "BY"):
 3801            kwargs["fields"] = self._parse_string()
 3802            if self._match_text_seq("ESCAPED", "BY"):
 3803                kwargs["escaped"] = self._parse_string()
 3804        if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"):
 3805            kwargs["collection_items"] = self._parse_string()
 3806        if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"):
 3807            kwargs["map_keys"] = self._parse_string()
 3808        if self._match_text_seq("LINES", "TERMINATED", "BY"):
 3809            kwargs["lines"] = self._parse_string()
 3810        if self._match_text_seq("NULL", "DEFINED", "AS"):
 3811            kwargs["null"] = self._parse_string()
 3812
 3813        return self.expression(exp.RowFormatDelimitedProperty(**kwargs))  # type: ignore
 3814
 3815    def _parse_load(self) -> exp.LoadData | exp.Command:
 3816        if self._match_text_seq("DATA"):
 3817            local = self._match_text_seq("LOCAL")
 3818            self._match_text_seq("INPATH")
 3819            inpath = self._parse_string()
 3820            overwrite = self._match(TokenType.OVERWRITE)
 3821            temp: bool | None = None
 3822            if self._match(TokenType.INTO):
 3823                temp = self._match(TokenType.TEMPORARY)
 3824                self._match(TokenType.TABLE)
 3825
 3826            return self.expression(
 3827                exp.LoadData(
 3828                    this=self._parse_table(schema=True),
 3829                    local=local,
 3830                    overwrite=overwrite,
 3831                    temp=temp,
 3832                    inpath=inpath,
 3833                    files=self._match_text_seq("FROM", "FILES")
 3834                    and exp.Properties(expressions=self._parse_wrapped_properties()),
 3835                    partition=self._parse_partition(),
 3836                    input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(),
 3837                    serde=self._match_text_seq("SERDE") and self._parse_string(),
 3838                )
 3839            )
 3840        return self._parse_as_command(self._prev)
 3841
 3842    def _parse_delete(self) -> exp.Delete:
 3843        hint = self._parse_hint()
 3844
 3845        # This handles MySQL's "Multiple-Table Syntax"
 3846        # https://dev.mysql.com/doc/refman/8.0/en/delete.html
 3847        tables = None
 3848        if not self._match(TokenType.FROM, advance=False):
 3849            tables = self._parse_csv(self._parse_table) or None
 3850
 3851        returning = self._parse_returning()
 3852
 3853        return self.expression(
 3854            exp.Delete(
 3855                hint=hint,
 3856                tables=tables,
 3857                this=self._match(TokenType.FROM) and self._parse_table(joins=True),
 3858                using=self._match(TokenType.USING)
 3859                and self._parse_csv(lambda: self._parse_table(joins=True)),
 3860                cluster=self._match(TokenType.ON) and self._parse_on_property(),
 3861                where=self._parse_where(),
 3862                returning=returning or self._parse_returning(),
 3863                order=self._parse_order(),
 3864                limit=self._parse_limit(),
 3865            )
 3866        )
 3867
 3868    def _parse_update(self) -> exp.Update:
 3869        hint = self._parse_hint()
 3870        kwargs: dict[str, object] = {
 3871            "hint": hint,
 3872            "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS),
 3873        }
 3874        while self._curr:
 3875            if self._match(TokenType.SET):
 3876                kwargs["expressions"] = self._parse_csv(self._parse_equality)
 3877            elif self._match(TokenType.RETURNING, advance=False):
 3878                kwargs["returning"] = self._parse_returning()
 3879            elif self._match(TokenType.FROM, advance=False):
 3880                from_ = self._parse_from(joins=True)
 3881                table = from_.this if from_ else None
 3882                if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False):
 3883                    table.set("joins", list(self._parse_joins()) or None)
 3884
 3885                kwargs["from_"] = from_
 3886            elif self._match(TokenType.WHERE, advance=False):
 3887                kwargs["where"] = self._parse_where()
 3888            elif self._match(TokenType.ORDER_BY, advance=False):
 3889                kwargs["order"] = self._parse_order()
 3890            elif self._match(TokenType.LIMIT, advance=False):
 3891                kwargs["limit"] = self._parse_limit()
 3892            else:
 3893                break
 3894
 3895        return self.expression(exp.Update(**kwargs))
 3896
 3897    def _parse_use(self) -> exp.Use:
 3898        return self.expression(
 3899            exp.Use(
 3900                kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False),
 3901                this=self._parse_table(schema=False),
 3902            )
 3903        )
 3904
 3905    def _parse_uncache(self) -> exp.Uncache:
 3906        if not self._match(TokenType.TABLE):
 3907            self.raise_error("Expecting TABLE after UNCACHE")
 3908
 3909        return self.expression(
 3910            exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True))
 3911        )
 3912
 3913    def _parse_cache(self) -> exp.Cache:
 3914        lazy = self._match_text_seq("LAZY")
 3915        self._match(TokenType.TABLE)
 3916        table = self._parse_table(schema=True)
 3917
 3918        options = []
 3919        if self._match_text_seq("OPTIONS"):
 3920            self._match_l_paren()
 3921            k = self._parse_string()
 3922            self._match(TokenType.EQ)
 3923            v = self._parse_string()
 3924            options = [k, v]
 3925            self._match_r_paren()
 3926
 3927        self._match(TokenType.ALIAS)
 3928        return self.expression(
 3929            exp.Cache(
 3930                this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True)
 3931            )
 3932        )
 3933
 3934    def _parse_partition(self) -> exp.Partition | None:
 3935        if not self._match_texts(self.PARTITION_KEYWORDS):
 3936            return None
 3937
 3938        return self.expression(
 3939            exp.Partition(
 3940                subpartition=self._prev.text.upper() == "SUBPARTITION",
 3941                expressions=self._parse_wrapped_csv(self._parse_disjunction),
 3942            )
 3943        )
 3944
 3945    def _parse_value(self, values: bool = True) -> exp.Tuple | None:
 3946        def _parse_value_expression() -> exp.Expr | None:
 3947            if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3948                return exp.var(self._prev.text.upper())
 3949            return self._parse_expression()
 3950
 3951        if self._match(TokenType.L_PAREN):
 3952            expressions = self._parse_csv(_parse_value_expression)
 3953            self._match_r_paren()
 3954            return self.expression(exp.Tuple(expressions=expressions))
 3955
 3956        # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows.
 3957        expression = self._parse_expression()
 3958        if expression:
 3959            return self.expression(exp.Tuple(expressions=[expression]))
 3960        return None
 3961
 3962    def _parse_projections(
 3963        self,
 3964    ) -> tuple[list[exp.Expr], list[exp.Expr] | None]:
 3965        return self._parse_expressions(), None
 3966
 3967    def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None:
 3968        if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)):
 3969            this: exp.Expr | None = self._parse_simplified_pivot(
 3970                is_unpivot=self._prev.token_type == TokenType.UNPIVOT
 3971            )
 3972        elif self._match(TokenType.FROM):
 3973            from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True)
 3974            # Support parentheses for duckdb FROM-first syntax
 3975            select = self._parse_select(from_=from_)
 3976            if select:
 3977                if not select.args.get("from_"):
 3978                    select.set("from_", from_)
 3979                this = select
 3980            else:
 3981                this = exp.select("*").from_(t.cast(exp.From, from_))
 3982                this = self._parse_query_modifiers(self._parse_set_operations(this))
 3983        else:
 3984            this = (
 3985                self._parse_table(consume_pipe=True)
 3986                if table
 3987                else self._parse_select(nested=True, parse_set_operation=False)
 3988            )
 3989
 3990            # Transform exp.Values into a exp.Table to pass through parse_query_modifiers
 3991            # in case a modifier (e.g. join) is following
 3992            if table and isinstance(this, exp.Values) and this.alias:
 3993                alias = this.args["alias"].pop()
 3994                this = exp.Table(this=this, alias=alias)
 3995
 3996            this = self._parse_query_modifiers(self._parse_set_operations(this))
 3997
 3998        return this
 3999
 4000    def _parse_select(
 4001        self,
 4002        nested: bool = False,
 4003        table: bool = False,
 4004        parse_subquery_alias: bool = True,
 4005        parse_set_operation: bool = True,
 4006        consume_pipe: bool = True,
 4007        from_: exp.From | None = None,
 4008    ) -> exp.Expr | None:
 4009        query = self._parse_select_query(
 4010            nested=nested,
 4011            table=table,
 4012            parse_subquery_alias=parse_subquery_alias,
 4013            parse_set_operation=parse_set_operation,
 4014        )
 4015
 4016        if consume_pipe and self._match(TokenType.PIPE_GT, advance=False):
 4017            if not query and from_:
 4018                query = exp.select("*").from_(from_)
 4019            if isinstance(query, exp.Query):
 4020                query = self._parse_pipe_syntax_query(query)
 4021                query = query.subquery(copy=False) if query and table else query
 4022
 4023        return query
 4024
 4025    def _parse_select_query(
 4026        self,
 4027        nested: bool = False,
 4028        table: bool = False,
 4029        parse_subquery_alias: bool = True,
 4030        parse_set_operation: bool = True,
 4031    ) -> exp.Expr | None:
 4032        cte = self._parse_with()
 4033
 4034        if cte:
 4035            this = self._parse_statement()
 4036
 4037            if not this:
 4038                self.raise_error("Failed to parse any statement following CTE")
 4039                return cte
 4040
 4041            while isinstance(this, exp.Subquery) and this.is_wrapper:
 4042                this = this.this
 4043
 4044            assert this is not None
 4045            if "with_" in this.arg_types:
 4046                if inner_cte := this.args.get("with_"):
 4047                    cte.set("expressions", cte.expressions + inner_cte.expressions)
 4048                    if inner_cte.args.get("recursive"):
 4049                        cte.set("recursive", True)
 4050                this.set("with_", cte)
 4051            else:
 4052                self.raise_error(f"{this.key} does not support CTE")
 4053                this = cte
 4054
 4055            return this
 4056
 4057        # duckdb supports leading with FROM x
 4058        from_ = (
 4059            self._parse_from(joins=True, consume_pipe=True)
 4060            if self._match(TokenType.FROM, advance=False)
 4061            else None
 4062        )
 4063
 4064        if self._match(TokenType.SELECT):
 4065            comments = self._prev_comments
 4066
 4067            hint = self._parse_hint()
 4068
 4069            if self._next and not self._next.token_type == TokenType.DOT:
 4070                all_ = self._match(TokenType.ALL)
 4071                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4072            else:
 4073                all_, matched_distinct = None, False
 4074
 4075            kind = (
 4076                self._prev.text.upper()
 4077                if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE"))
 4078                else None
 4079            )
 4080
 4081            distinct: exp.Expr | None = (
 4082                self.expression(
 4083                    exp.Distinct(
 4084                        on=self._parse_value(values=False) if self._match(TokenType.ON) else None
 4085                    )
 4086                )
 4087                if matched_distinct
 4088                else None
 4089            )
 4090
 4091            operation_modifiers = []
 4092            while self._curr and self._match_texts(self.OPERATION_MODIFIERS):
 4093                operation_modifiers.append(exp.var(self._prev.text.upper()))
 4094
 4095            limit = self._parse_limit(top=True)
 4096
 4097            # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ...
 4098            if limit and not matched_distinct and not all_:
 4099                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4100                if matched_distinct:
 4101                    distinct = self.expression(
 4102                        exp.Distinct(
 4103                            on=self._parse_value(values=False)
 4104                            if self._match(TokenType.ON)
 4105                            else None
 4106                        )
 4107                    )
 4108                else:
 4109                    all_ = self._match(TokenType.ALL)
 4110
 4111            if all_ and distinct:
 4112                self.raise_error("Cannot specify both ALL and DISTINCT after SELECT")
 4113
 4114            projections, exclude = self._parse_projections()
 4115
 4116            this = self.expression(
 4117                exp.Select(
 4118                    kind=kind,
 4119                    hint=hint,
 4120                    distinct=distinct,
 4121                    expressions=projections,
 4122                    limit=limit,
 4123                    exclude=exclude,
 4124                    operation_modifiers=operation_modifiers or None,
 4125                )
 4126            )
 4127            this.comments = comments
 4128
 4129            into = self._parse_into()
 4130            if into:
 4131                this.set("into", into)
 4132
 4133            if not from_:
 4134                from_ = self._parse_from()
 4135
 4136            if from_:
 4137                this.set("from_", from_)
 4138
 4139            this = self._parse_query_modifiers(this)
 4140        elif (table or nested) and self._match(TokenType.L_PAREN):
 4141            comments = self._prev_comments
 4142            this = self._parse_wrapped_select(table=table)
 4143
 4144            if this:
 4145                this.add_comments(comments, prepend=True)
 4146
 4147            # We return early here so that the UNION isn't attached to the subquery by the
 4148            # following call to _parse_set_operations, but instead becomes the parent node
 4149            self._match_r_paren()
 4150            return self._parse_subquery(this, parse_alias=parse_subquery_alias)
 4151        elif self._match(TokenType.VALUES, advance=False):
 4152            this = self._parse_derived_table_values()
 4153        elif from_:
 4154            this = exp.select("*").from_(from_.this, copy=False)
 4155            this = self._parse_query_modifiers(this)
 4156        elif self._match(TokenType.SUMMARIZE):
 4157            table = self._match(TokenType.TABLE)
 4158            this = self._parse_select() or self._parse_string() or self._parse_table()
 4159            return self.expression(exp.Summarize(this=this, table=table))
 4160        elif self._match(TokenType.DESCRIBE):
 4161            this = self._parse_describe()
 4162        else:
 4163            this = None
 4164
 4165        return self._parse_set_operations(this) if parse_set_operation else this
 4166
 4167    def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None:
 4168        self._match_text_seq("SEARCH")
 4169
 4170        kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper()
 4171
 4172        if not kind:
 4173            return None
 4174
 4175        self._match_text_seq("FIRST", "BY")
 4176
 4177        return self.expression(
 4178            exp.RecursiveWithSearch(
 4179                kind=kind,
 4180                this=self._parse_id_var(),
 4181                expression=self._match_text_seq("SET") and self._parse_id_var(),
 4182                using=self._match_text_seq("USING") and self._parse_id_var(),
 4183            )
 4184        )
 4185
 4186    def _parse_with(self, skip_with_token: bool = False) -> exp.With | None:
 4187        if not skip_with_token and not self._match(TokenType.WITH):
 4188            return None
 4189
 4190        comments = self._prev_comments
 4191        recursive = self._match(TokenType.RECURSIVE)
 4192
 4193        last_comments = None
 4194        expressions = []
 4195        udfs = []
 4196        while True:
 4197            cte = self._parse_cte()
 4198            if cte:
 4199                if isinstance(cte, exp.FunctionSpecification):
 4200                    udfs.append(cte)
 4201                else:
 4202                    expressions.append(cte)
 4203
 4204                if last_comments:
 4205                    cte.add_comments(last_comments)
 4206
 4207            if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH):
 4208                break
 4209            else:
 4210                self._match(TokenType.WITH)
 4211                recursive = self._match(TokenType.RECURSIVE) or recursive
 4212
 4213            last_comments = self._prev_comments
 4214
 4215        return self.expression(
 4216            exp.With(
 4217                expressions=expressions,
 4218                recursive=recursive or None,
 4219                search=self._parse_recursive_with_search(),
 4220                udfs=udfs or None,
 4221            ),
 4222            comments=comments,
 4223        )
 4224
 4225    def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None:
 4226        index = self._index
 4227
 4228        alias = self._parse_table_alias(self.ID_VAR_TOKENS)
 4229        if not alias or not alias.this:
 4230            self.raise_error("Expected CTE to have alias")
 4231
 4232        key_expressions = (
 4233            self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None
 4234        )
 4235
 4236        if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE:
 4237            self._retreat(index)
 4238            return None
 4239
 4240        comments = self._prev_comments
 4241
 4242        if self._match_text_seq("NOT", "MATERIALIZED"):
 4243            materialized = False
 4244        elif self._match_text_seq("MATERIALIZED"):
 4245            materialized = True
 4246        else:
 4247            materialized = None
 4248
 4249        cte = self.expression(
 4250            exp.CTE(
 4251                this=self._parse_wrapped(self._parse_statement),
 4252                alias=alias,
 4253                materialized=materialized,
 4254                key_expressions=key_expressions,
 4255            ),
 4256            comments=comments,
 4257        )
 4258
 4259        values = cte.this
 4260        if isinstance(values, exp.Values):
 4261            cte.set("this", self._values_to_select(values))
 4262
 4263        return cte
 4264
 4265    def _values_to_select(self, values: exp.Values) -> exp.Select:
 4266        if values.alias:
 4267            return exp.select("*").from_(values)
 4268        return exp.select("*").from_(exp.alias_(values, "_values", table=True))
 4269
 4270    def _parse_table_alias(
 4271        self, alias_tokens: t.Collection[TokenType] | None = None
 4272    ) -> exp.TableAlias | None:
 4273        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 4274        # so this section tries to parse the clause version and if it fails, it treats the token
 4275        # as an identifier (alias)
 4276        if self._can_parse_limit_or_offset():
 4277            return None
 4278
 4279        # START is never treated as an implicit alias when followed by WITH, since that
 4280        # would swallow the beginning of a START WITH ... CONNECT BY clause
 4281        if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH":
 4282            return None
 4283
 4284        any_token = self._match(TokenType.ALIAS)
 4285        alias = (
 4286            self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 4287            or self._parse_string_as_identifier()
 4288        )
 4289
 4290        index = self._index
 4291        if self._match(TokenType.L_PAREN):
 4292            columns = self._parse_csv(self._parse_function_parameter)
 4293            self._match_r_paren() if columns else self._retreat(index)
 4294        else:
 4295            columns = None
 4296
 4297        if not alias and not columns:
 4298            return None
 4299
 4300        table_alias = self.expression(exp.TableAlias(this=alias, columns=columns))
 4301
 4302        # We bubble up comments from the Identifier to the TableAlias
 4303        if isinstance(alias, exp.Identifier):
 4304            table_alias.add_comments(alias.pop_comments())
 4305
 4306        return table_alias
 4307
 4308    def _parse_subquery(
 4309        self, this: exp.Expr | None, parse_alias: bool = True
 4310    ) -> exp.Subquery | None:
 4311        if not this:
 4312            return None
 4313
 4314        return self.expression(
 4315            exp.Subquery(
 4316                this=this,
 4317                pivots=self._parse_pivots(),
 4318                alias=self._parse_table_alias() if parse_alias else None,
 4319                sample=self._parse_table_sample(),
 4320            )
 4321        )
 4322
 4323    def _implicit_unnests_to_explicit(self, this: E) -> E:
 4324        from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm
 4325
 4326        refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name}
 4327        for i, join in enumerate(this.args.get("joins") or []):
 4328            table = join.this
 4329            normalized_table = table.copy()
 4330            normalized_table.meta["maybe_column"] = True
 4331            normalized_table = _norm(normalized_table, dialect=self.dialect)
 4332
 4333            if isinstance(table, exp.Table) and not join.args.get("on"):
 4334                if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs:
 4335                    table_as_column = table.to_column()
 4336                    unnest = exp.Unnest(expressions=[table_as_column])
 4337
 4338                    # Table.to_column creates a parent Alias node that we want to convert to
 4339                    # a TableAlias and attach to the Unnest, so it matches the parser's output
 4340                    if isinstance(table.args.get("alias"), exp.TableAlias):
 4341                        table_as_column.replace(table_as_column.this)
 4342                        exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False)
 4343
 4344                    table.replace(unnest)
 4345
 4346            refs.add(normalized_table.alias_or_name)
 4347
 4348        return this
 4349
 4350    @t.overload
 4351    def _parse_query_modifiers(self, this: E) -> E: ...
 4352
 4353    @t.overload
 4354    def _parse_query_modifiers(self, this: None) -> None: ...
 4355
 4356    def _parse_query_modifiers(self, this):
 4357        if isinstance(this, self.MODIFIABLES):
 4358            for join in self._parse_joins():
 4359                this.append("joins", join)
 4360            for lateral in iter(self._parse_lateral, None):
 4361                this.append("laterals", lateral)
 4362
 4363            while True:
 4364                if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):
 4365                    modifier_token = self._curr
 4366                    parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type]
 4367                    key, expression = parser(self)
 4368
 4369                    if expression:
 4370                        if this.args.get(key):
 4371                            self.raise_error(
 4372                                f"Found multiple '{modifier_token.text.upper()}' clauses",
 4373                                token=modifier_token,
 4374                            )
 4375
 4376                        this.set(key, expression)
 4377                        if key == "limit":
 4378                            offset = expression.args.get("offset")
 4379                            expression.set("offset", None)
 4380
 4381                            if offset:
 4382                                offset = exp.Offset(expression=offset)
 4383                                this.set("offset", offset)
 4384
 4385                                limit_by_expressions = expression.expressions
 4386                                expression.set("expressions", None)
 4387                                offset.set("expressions", limit_by_expressions)
 4388                        continue
 4389
 4390                if self._curr.text.upper() == "START":
 4391                    modifier_token = self._curr
 4392                    connect = self._parse_connect()
 4393                    if connect:
 4394                        if this.args.get("connect"):
 4395                            self.raise_error(
 4396                                "Found multiple 'START WITH' clauses", token=modifier_token
 4397                            )
 4398
 4399                        this.set("connect", connect)
 4400                        continue
 4401                break
 4402
 4403        if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"):
 4404            this = self._implicit_unnests_to_explicit(this)
 4405
 4406        return this
 4407
 4408    def _parse_hint_fallback_to_string(self) -> exp.Hint | None:
 4409        start = self._curr
 4410        while self._curr:
 4411            self._advance()
 4412
 4413        end = self._tokens[self._index - 1]
 4414        return exp.Hint(expressions=[self._find_sql(start, end)])
 4415
 4416    def _parse_hint_function_call(self) -> exp.Expr | None:
 4417        return self._parse_function_call()
 4418
 4419    def _parse_hint_body(self) -> exp.Hint | None:
 4420        start_index = self._index
 4421        should_fallback_to_string = False
 4422
 4423        hints = []
 4424        try:
 4425            for hint in iter(
 4426                lambda: self._parse_csv(
 4427                    lambda: self._parse_hint_function_call() or self._parse_var(upper=True),
 4428                ),
 4429                [],
 4430            ):
 4431                hints.extend(hint)
 4432        except ParseError:
 4433            should_fallback_to_string = True
 4434
 4435        if should_fallback_to_string or self._curr:
 4436            self._retreat(start_index)
 4437            return self._parse_hint_fallback_to_string()
 4438
 4439        return self.expression(exp.Hint(expressions=hints))
 4440
 4441    def _parse_hint(self) -> exp.Hint | None:
 4442        if self._match(TokenType.HINT) and self._prev_comments:
 4443            return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect)
 4444
 4445        return None
 4446
 4447    def _parse_into(self) -> exp.Into | None:
 4448        if not self._match(TokenType.INTO):
 4449            return None
 4450
 4451        temp = self._match(TokenType.TEMPORARY)
 4452        unlogged = self._match_text_seq("UNLOGGED")
 4453        self._match(TokenType.TABLE)
 4454
 4455        return self.expression(
 4456            exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged)
 4457        )
 4458
 4459    def _parse_from(
 4460        self,
 4461        joins: bool = False,
 4462        skip_from_token: bool = False,
 4463        consume_pipe: bool = False,
 4464    ) -> exp.From | None:
 4465        if not skip_from_token and not self._match(TokenType.FROM):
 4466            return None
 4467
 4468        comments = self._prev_comments
 4469        return self.expression(
 4470            exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)),
 4471            comments=comments,
 4472        )
 4473
 4474    def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure:
 4475        return self.expression(
 4476            exp.MatchRecognizeMeasure(
 4477                window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(),
 4478                this=self._parse_expression(),
 4479            )
 4480        )
 4481
 4482    def _parse_match_recognize(self) -> exp.MatchRecognize | None:
 4483        if not self._match(TokenType.MATCH_RECOGNIZE):
 4484            return None
 4485
 4486        self._match_l_paren()
 4487
 4488        partition = self._parse_partition_by()
 4489        order = self._parse_order()
 4490
 4491        measures = (
 4492            self._parse_csv(self._parse_match_recognize_measure)
 4493            if self._match_text_seq("MEASURES")
 4494            else None
 4495        )
 4496
 4497        if self._match_text_seq("ONE", "ROW", "PER", "MATCH"):
 4498            rows = exp.var("ONE ROW PER MATCH")
 4499        elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"):
 4500            text = "ALL ROWS PER MATCH"
 4501            if self._match_text_seq("SHOW", "EMPTY", "MATCHES"):
 4502                text += " SHOW EMPTY MATCHES"
 4503            elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"):
 4504                text += " OMIT EMPTY MATCHES"
 4505            elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"):
 4506                text += " WITH UNMATCHED ROWS"
 4507            rows = exp.var(text)
 4508        else:
 4509            rows = None
 4510
 4511        if self._match_text_seq("AFTER", "MATCH", "SKIP"):
 4512            text = "AFTER MATCH SKIP"
 4513            if self._match_text_seq("PAST", "LAST", "ROW"):
 4514                text += " PAST LAST ROW"
 4515            elif self._match_text_seq("TO", "NEXT", "ROW"):
 4516                text += " TO NEXT ROW"
 4517            elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"):
 4518                direction = self._prev.text.upper()
 4519                pattern_var = self._advance_any()
 4520                if not pattern_var:
 4521                    self.raise_error(
 4522                        f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}"
 4523                    )
 4524                text += f" TO {direction} {pattern_var.text if pattern_var else ''}"
 4525            after = exp.var(text)
 4526        else:
 4527            after = None
 4528
 4529        if self._match_text_seq("PATTERN"):
 4530            self._match_l_paren()
 4531
 4532            if not self._curr:
 4533                self.raise_error("Expecting )", self._curr)
 4534
 4535            paren = 1
 4536            start = self._curr
 4537
 4538            while self._curr and paren > 0:
 4539                if self._curr.token_type == TokenType.L_PAREN:
 4540                    paren += 1
 4541                if self._curr.token_type == TokenType.R_PAREN:
 4542                    paren -= 1
 4543
 4544                end = self._prev
 4545                self._advance()
 4546
 4547            if paren > 0:
 4548                self.raise_error("Expecting )", self._curr)
 4549
 4550            pattern = exp.var(self._find_sql(start, end))
 4551        else:
 4552            pattern = None
 4553
 4554        define = (
 4555            self._parse_csv(self._parse_name_as_expression)
 4556            if self._match_text_seq("DEFINE")
 4557            else None
 4558        )
 4559
 4560        self._match_r_paren()
 4561
 4562        return self.expression(
 4563            exp.MatchRecognize(
 4564                partition_by=partition,
 4565                order=order,
 4566                measures=measures,
 4567                rows=rows,
 4568                after=after,
 4569                pattern=pattern,
 4570                define=define,
 4571                alias=self._parse_table_alias(),
 4572            )
 4573        )
 4574
 4575    def _parse_lateral(self) -> exp.Lateral | None:
 4576        cross_apply: bool | None = None
 4577        if self._match_pair(TokenType.CROSS, TokenType.APPLY):
 4578            cross_apply = True
 4579        elif self._match_pair(TokenType.OUTER, TokenType.APPLY):
 4580            cross_apply = False
 4581
 4582        if cross_apply is not None:
 4583            this = self._parse_select(table=True)
 4584            view = None
 4585            outer = None
 4586        elif self._match(TokenType.LATERAL):
 4587            this = self._parse_select(table=True)
 4588            view = self._match(TokenType.VIEW)
 4589            outer = self._match(TokenType.OUTER)
 4590        else:
 4591            return None
 4592
 4593        if not this:
 4594            this = (
 4595                self._parse_unnest()
 4596                or self._parse_function()
 4597                or self._parse_id_var(any_token=False)
 4598            )
 4599
 4600            while self._match(TokenType.DOT):
 4601                this = exp.Dot(
 4602                    this=this,
 4603                    expression=self._parse_function() or self._parse_id_var(any_token=False),
 4604                )
 4605
 4606        ordinality: bool | None = None
 4607
 4608        if view:
 4609            table = self._parse_id_var(any_token=False)
 4610            columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else []
 4611            table_alias: exp.TableAlias | None = self.expression(
 4612                exp.TableAlias(this=table, columns=columns)
 4613            )
 4614        elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias:
 4615            # We move the alias from the lateral's child node to the lateral itself
 4616            table_alias = this.args["alias"].pop()
 4617        else:
 4618            ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 4619            table_alias = self._parse_table_alias()
 4620
 4621        return self.expression(
 4622            exp.Lateral(
 4623                this=this,
 4624                view=view,
 4625                outer=outer,
 4626                alias=table_alias,
 4627                cross_apply=cross_apply,
 4628                ordinality=ordinality,
 4629            )
 4630        )
 4631
 4632    def _parse_stream(self) -> exp.Stream | None:
 4633        index = self._index
 4634        if self._match(TokenType.STREAM):
 4635            if this := self._try_parse(self._parse_table):
 4636                return self.expression(exp.Stream(this=this))
 4637            self._retreat(index)
 4638        return None
 4639
 4640    def _parse_join_parts(
 4641        self,
 4642    ) -> tuple[Token | None, Token | None, Token | None]:
 4643        return (
 4644            self._prev if self._match_set(self.JOIN_METHODS) else None,
 4645            self._prev if self._match_set(self.JOIN_SIDES) else None,
 4646            self._prev if self._match_set(self.JOIN_KINDS) else None,
 4647        )
 4648
 4649    def _parse_using_identifiers(self) -> list[exp.Expr]:
 4650        def _parse_column_as_identifier() -> exp.Expr | None:
 4651            this = self._parse_column()
 4652            if isinstance(this, exp.Column):
 4653                return this.this
 4654            return this
 4655
 4656        return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True)
 4657
 4658    def _parse_join(
 4659        self,
 4660        skip_join_token: bool = False,
 4661        parse_bracket: bool = False,
 4662        alias_tokens: t.Collection[TokenType] | None = None,
 4663    ) -> exp.Join | None:
 4664        if self._match(TokenType.COMMA):
 4665            table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens))
 4666            cross_join = self.expression(exp.Join(this=table)) if table else None
 4667
 4668            if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE:
 4669                cross_join.set("kind", "CROSS")
 4670
 4671            return cross_join
 4672
 4673        index = self._index
 4674        method, side, kind = self._parse_join_parts()
 4675        directed = self._match_text_seq("DIRECTED")
 4676        hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None
 4677        join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN)
 4678        join_comments = self._prev_comments
 4679
 4680        if not skip_join_token and not join:
 4681            self._retreat(index)
 4682            kind = None
 4683            method = None
 4684            side = None
 4685
 4686        outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False)
 4687        cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False)
 4688
 4689        if not skip_join_token and not join and not outer_apply and not cross_apply:
 4690            return None
 4691
 4692        kwargs: dict[str, t.Any] = {
 4693            "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4694        }
 4695        if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA):
 4696            kwargs["expressions"] = self._parse_csv(
 4697                lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4698            )
 4699
 4700        if method:
 4701            kwargs["method"] = method.text.upper()
 4702        if side:
 4703            kwargs["side"] = side.text.upper()
 4704        if kind:
 4705            kwargs["kind"] = kind.text.upper()
 4706        if hint:
 4707            kwargs["hint"] = hint
 4708
 4709        if self._match(TokenType.MATCH_CONDITION):
 4710            kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison)
 4711
 4712        if self._match(TokenType.ON):
 4713            kwargs["on"] = self._parse_disjunction()
 4714        elif self._match(TokenType.USING):
 4715            kwargs["using"] = self._parse_using_identifiers()
 4716        elif (
 4717            not method
 4718            and not (outer_apply or cross_apply)
 4719            and not isinstance(kwargs["this"], exp.Unnest)
 4720            and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY))
 4721        ):
 4722            index = self._index
 4723            joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens))
 4724
 4725            if joins and self._match(TokenType.ON):
 4726                kwargs["on"] = self._parse_disjunction()
 4727            elif joins and self._match(TokenType.USING):
 4728                kwargs["using"] = self._parse_using_identifiers()
 4729            else:
 4730                joins = None
 4731                self._retreat(index)
 4732
 4733            kwargs["this"].set("joins", joins if joins else None)
 4734
 4735        kwargs["pivots"] = self._parse_pivots()
 4736
 4737        comments = [c for token in (method, side, kind) if token for c in token.comments]
 4738        comments = (join_comments or []) + comments
 4739
 4740        if (
 4741            self.ADD_JOIN_ON_TRUE
 4742            and not kwargs.get("on")
 4743            and not kwargs.get("using")
 4744            and not kwargs.get("method")
 4745            and kwargs.get("kind") in (None, "INNER", "OUTER")
 4746        ):
 4747            kwargs["on"] = exp.true()
 4748
 4749        if directed:
 4750            kwargs["directed"] = directed
 4751
 4752        return self.expression(exp.Join(**kwargs), comments=comments)
 4753
 4754    def _parse_opclass(self) -> exp.Expr | None:
 4755        this = self._parse_disjunction()
 4756
 4757        if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False):
 4758            return this
 4759
 4760        if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False):
 4761            return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts()))
 4762
 4763        return this
 4764
 4765    def _parse_index_params(self) -> exp.IndexParameters:
 4766        using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None
 4767
 4768        if self._match(TokenType.L_PAREN, advance=False):
 4769            columns = self._parse_wrapped_csv(self._parse_with_operator)
 4770        else:
 4771            columns = None
 4772
 4773        include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None
 4774        partition_by = self._parse_partition_by()
 4775        with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties()
 4776        tablespace = (
 4777            self._parse_var(any_token=True)
 4778            if self._match_text_seq("USING", "INDEX", "TABLESPACE")
 4779            else None
 4780        )
 4781        where = self._parse_where()
 4782
 4783        on = self._parse_field() if self._match(TokenType.ON) else None
 4784
 4785        return self.expression(
 4786            exp.IndexParameters(
 4787                using=using,
 4788                columns=columns,
 4789                include=include,
 4790                partition_by=partition_by,
 4791                where=where,
 4792                with_storage=with_storage,
 4793                tablespace=tablespace,
 4794                on=on,
 4795            )
 4796        )
 4797
 4798    def _parse_index(
 4799        self, index: exp.Expr | None = None, anonymous: bool = False
 4800    ) -> exp.Index | None:
 4801        if index or anonymous:
 4802            unique = None
 4803            primary = None
 4804            amp = None
 4805
 4806            self._match(TokenType.ON)
 4807            self._match(TokenType.TABLE)  # hive
 4808            table = self._parse_table_parts(schema=True)
 4809        else:
 4810            unique = self._match(TokenType.UNIQUE)
 4811            primary = self._match_text_seq("PRIMARY")
 4812            amp = self._match_text_seq("AMP")
 4813
 4814            if not self._match(TokenType.INDEX):
 4815                return None
 4816
 4817            index = self._parse_id_var()
 4818            table = None
 4819
 4820        params = self._parse_index_params()
 4821
 4822        return self.expression(
 4823            exp.Index(
 4824                this=index, table=table, unique=unique, primary=primary, amp=amp, params=params
 4825            )
 4826        )
 4827
 4828    def _parse_table_hints(self) -> list[exp.Expr] | None:
 4829        hints: list[exp.Expr] = []
 4830        if self._match_pair(TokenType.WITH, TokenType.L_PAREN):
 4831            # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16
 4832            hints.append(
 4833                self.expression(
 4834                    exp.WithTableHint(
 4835                        expressions=self._parse_csv(
 4836                            lambda: self._parse_function() or self._parse_var(any_token=True)
 4837                        )
 4838                    )
 4839                )
 4840            )
 4841            self._match_r_paren()
 4842        else:
 4843            # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
 4844            while self._match_set(self.TABLE_INDEX_HINT_TOKENS):
 4845                hint = exp.IndexTableHint(this=self._prev.text.upper())
 4846
 4847                self._match_set((TokenType.INDEX, TokenType.KEY))
 4848                if self._match(TokenType.FOR):
 4849                    hint.set("target", self._advance_any() and self._prev.text.upper())
 4850
 4851                hint.set("expressions", self._parse_wrapped_id_vars())
 4852                hints.append(hint)
 4853
 4854        return hints or None
 4855
 4856    def _parse_table_part(self, schema: bool = False) -> exp.Expr | None:
 4857        return (
 4858            (not schema and self._parse_function(optional_parens=False))
 4859            or self._parse_id_var(any_token=False)
 4860            or self._parse_string_as_identifier()
 4861            or self._parse_placeholder()
 4862        )
 4863
 4864    def _parse_table_parts_fast(self) -> exp.Table | None:
 4865        index = self._index
 4866        parts: list[exp.Identifier] | None = None
 4867        all_comments: list[str] | None = None
 4868
 4869        while self._match_set(self.IDENTIFIER_TOKENS):
 4870            token = self._prev
 4871            comments = self._prev_comments
 4872
 4873            has_dot = self._match(TokenType.DOT)
 4874            curr_tt = self._curr.token_type
 4875
 4876            if not has_dot:
 4877                if curr_tt in self.TABLE_POSTFIX_TOKENS:
 4878                    self._retreat(index)
 4879                    return None
 4880            elif curr_tt not in self.IDENTIFIER_TOKENS:
 4881                self._retreat(index)
 4882                return None
 4883
 4884            if parts is None:
 4885                parts = []
 4886
 4887            if comments:
 4888                if all_comments is None:
 4889                    all_comments = []
 4890                all_comments.extend(comments)
 4891                self._prev_comments = []
 4892
 4893            parts.append(
 4894                self.expression(
 4895                    exp.Identifier(
 4896                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 4897                    ),
 4898                    token,
 4899                )
 4900            )
 4901
 4902            if not has_dot:
 4903                break
 4904
 4905        if parts is None:
 4906            return None
 4907
 4908        n = len(parts)
 4909
 4910        if n == 1:
 4911            table: exp.Table = exp.Table(this=parts[0])
 4912        elif n == 2:
 4913            table = exp.Table(this=parts[1], db=parts[0])
 4914        elif n >= 3:
 4915            this: exp.Identifier | exp.Dot = parts[2]
 4916            for i in range(3, n):
 4917                this = exp.Dot(this=this, expression=parts[i])
 4918
 4919            table = exp.Table(this=this, db=parts[1], catalog=parts[0])
 4920
 4921        if table is None:
 4922            self._retreat(index)
 4923        elif all_comments:
 4924            table.add_comments(all_comments)
 4925        return table
 4926
 4927    def _parse_table_parts(
 4928        self,
 4929        schema: bool = False,
 4930        is_db_reference: bool = False,
 4931        wildcard: bool = False,
 4932        fast: bool = False,
 4933    ) -> exp.Table | exp.Dot | None:
 4934        if fast:
 4935            return self._parse_table_parts_fast()
 4936
 4937        catalog: exp.Expr | str | None = None
 4938        db: exp.Expr | str | None = None
 4939        table: exp.Expr | str | None = self._parse_table_part(schema=schema)
 4940
 4941        while self._match(TokenType.DOT):
 4942            if catalog:
 4943                # This allows nesting the table in arbitrarily many dot expressions if needed
 4944                table = self.expression(
 4945                    exp.Dot(this=table, expression=self._parse_table_part(schema=schema))
 4946                )
 4947            else:
 4948                catalog = db
 4949                db = table
 4950                # "" used for tsql FROM a..b case
 4951                table = self._parse_table_part(schema=schema) or ""
 4952
 4953        if (
 4954            wildcard
 4955            and self._is_connected()
 4956            and (isinstance(table, exp.Identifier) or not table)
 4957            and self._match(TokenType.STAR)
 4958        ):
 4959            if isinstance(table, exp.Identifier):
 4960                table.args["this"] += "*"
 4961            else:
 4962                table = exp.Identifier(this="*")
 4963
 4964        if is_db_reference:
 4965            catalog = db
 4966            db = table
 4967            table = None
 4968
 4969        if not table and not is_db_reference:
 4970            self.raise_error(f"Expected table name but got {self._curr}")
 4971        if not db and is_db_reference:
 4972            self.raise_error(f"Expected database name but got {self._curr}")
 4973
 4974        table = self.expression(exp.Table(this=table, db=db, catalog=catalog))
 4975
 4976        # Bubble up comments from identifier parts to the Table
 4977        comments = []
 4978        for part in table.parts:
 4979            if part_comments := part.pop_comments():
 4980                comments.extend(part_comments)
 4981        if comments:
 4982            table.add_comments(comments)
 4983
 4984        changes = self._parse_changes()
 4985        if changes:
 4986            table.set("changes", changes)
 4987
 4988        at_before = self._parse_historical_data()
 4989        if at_before:
 4990            table.set("when", at_before)
 4991
 4992        pivots = self._parse_pivots()
 4993        if pivots:
 4994            table.set("pivots", pivots)
 4995
 4996        return table
 4997
 4998    def _parse_table(
 4999        self,
 5000        schema: bool = False,
 5001        joins: bool = False,
 5002        alias_tokens: t.Collection[TokenType] | None = None,
 5003        parse_bracket: bool = False,
 5004        is_db_reference: bool = False,
 5005        parse_partition: bool = False,
 5006        consume_pipe: bool = False,
 5007    ) -> exp.Expr | None:
 5008        if not schema and not is_db_reference and not consume_pipe and not joins:
 5009            index = self._index
 5010            table = self._parse_table_parts(fast=True)
 5011
 5012            if table is not None:
 5013                curr_tt = self._curr.token_type
 5014                next_tt = self._next.token_type
 5015
 5016                fast_terminators = self.TABLE_TERMINATORS
 5017
 5018                # only return the table if we're sure there are no other operators
 5019                # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT
 5020                if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION:
 5021                    return table
 5022
 5023                postfix_tokens = self.TABLE_POSTFIX_TOKENS
 5024
 5025                if curr_tt not in postfix_tokens and next_tt not in postfix_tokens:
 5026                    if alias := self._parse_table_alias(
 5027                        alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS
 5028                    ):
 5029                        table.set("alias", alias)
 5030
 5031                    if self._curr.token_type in fast_terminators:
 5032                        return table
 5033
 5034                self._retreat(index)
 5035
 5036        if stream := self._parse_stream():
 5037            return stream
 5038
 5039        if lateral := self._parse_lateral():
 5040            return lateral
 5041
 5042        if unnest := self._parse_unnest():
 5043            return unnest
 5044
 5045        if values := self._parse_derived_table_values():
 5046            return values
 5047
 5048        if subquery := self._parse_select(table=True, consume_pipe=consume_pipe):
 5049            if not subquery.args.get("pivots"):
 5050                subquery.set("pivots", self._parse_pivots())
 5051            if joins:
 5052                for join in self._parse_joins():
 5053                    subquery.append("joins", join)
 5054            return subquery
 5055
 5056        bracket = parse_bracket and self._parse_bracket(None)
 5057        bracket = self.expression(exp.Table(this=bracket)) if bracket else None
 5058
 5059        rows_from_tables = (
 5060            self._parse_wrapped_csv(self._parse_table)
 5061            if self._match_text_seq("ROWS", "FROM")
 5062            else None
 5063        )
 5064        rows_from = (
 5065            self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None
 5066        )
 5067
 5068        only = self._match(TokenType.ONLY)
 5069
 5070        this = t.cast(
 5071            exp.Expr,
 5072            bracket
 5073            or rows_from
 5074            or self._parse_bracket(
 5075                self._parse_table_parts(schema=schema, is_db_reference=is_db_reference)
 5076            ),
 5077        )
 5078
 5079        if only:
 5080            this.set("only", only)
 5081
 5082        # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context
 5083        self._match(TokenType.STAR)
 5084
 5085        parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION
 5086        if parse_partition and self._match(TokenType.PARTITION, advance=False):
 5087            this.set("partition", self._parse_partition())
 5088
 5089        if schema:
 5090            return self._parse_schema(this=this)
 5091
 5092        if self.dialect.ALIAS_POST_VERSION:
 5093            this.set("version", self._parse_version())
 5094
 5095        if self.dialect.ALIAS_POST_TABLESAMPLE:
 5096            this.set("sample", self._parse_table_sample())
 5097
 5098        alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 5099        if alias:
 5100            this.set("alias", alias)
 5101
 5102            # DuckDB requires the time-travel clause to come after the alias, e.g.
 5103            # SELECT * FROM t AS a AT (VERSION => 1)
 5104            if isinstance(this, exp.Table) and not this.args.get("when"):
 5105                this.set("when", self._parse_historical_data())
 5106
 5107        if self._match(TokenType.INDEXED_BY):
 5108            this.set("indexed", self._parse_table_parts())
 5109        elif self._match_text_seq("NOT", "INDEXED"):
 5110            this.set("indexed", False)
 5111
 5112        if isinstance(this, exp.Table) and self._match_text_seq("AT"):
 5113            return self.expression(
 5114                exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var())
 5115            )
 5116
 5117        this.set("hints", self._parse_table_hints())
 5118
 5119        if not this.args.get("pivots"):
 5120            this.set("pivots", self._parse_pivots())
 5121
 5122        if not self.dialect.ALIAS_POST_TABLESAMPLE:
 5123            this.set("sample", self._parse_table_sample())
 5124
 5125        if not self.dialect.ALIAS_POST_VERSION:
 5126            this.set("version", self._parse_version())
 5127
 5128        if joins:
 5129            for join in self._parse_joins(alias_tokens=alias_tokens):
 5130                this.append("joins", join)
 5131
 5132        if self._match_pair(TokenType.WITH, TokenType.ORDINALITY):
 5133            this.set("ordinality", True)
 5134            this.set("alias", self._parse_table_alias())
 5135
 5136        # TABLE(<tvf>) is parsed into a Table wrapping exp.TableFromRows, so we
 5137        # hoist the table args onto the latter and return it instead
 5138        if isinstance(this, exp.Table) and isinstance(this.this, exp.TableFromRows):
 5139            table_from_rows = this.this
 5140            for arg in exp.TableFromRows.arg_types:
 5141                if arg != "this":
 5142                    table_from_rows.set(arg, this.args.get(arg))
 5143
 5144            this = table_from_rows
 5145
 5146        return this
 5147
 5148    def _parse_version(self) -> exp.Version | None:
 5149        for phrase, this in self.VERSION_PHRASES.items():
 5150            if self._match_text_seq(*phrase):
 5151                break
 5152        else:
 5153            return None
 5154
 5155        if self._match_set((TokenType.FROM, TokenType.BETWEEN)):
 5156            kind = self._prev.text.upper()
 5157            start = self._parse_bitwise()
 5158            self._match_texts(("TO", "AND"))
 5159            end = self._parse_bitwise()
 5160            expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end]))
 5161        elif self._match_text_seq("CONTAINED", "IN"):
 5162            kind = "CONTAINED IN"
 5163            expression = self.expression(
 5164                exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise))
 5165            )
 5166        elif self._match(TokenType.ALL):
 5167            kind = "ALL"
 5168            expression = None
 5169        else:
 5170            self._match_text_seq("AS", "OF")
 5171            kind = "AS OF"
 5172            expression = self._parse_type()
 5173
 5174        return self.expression(exp.Version(this=this, expression=expression, kind=kind))
 5175
 5176    def _parse_historical_data(self) -> exp.HistoricalData | None:
 5177        # https://docs.snowflake.com/en/sql-reference/constructs/at-before
 5178        index = self._index
 5179        historical_data = None
 5180        if self._match_texts(self.HISTORICAL_DATA_PREFIX):
 5181            this = self._prev.text.upper()
 5182            kind = (
 5183                self._match(TokenType.L_PAREN)
 5184                and self._match_texts(self.HISTORICAL_DATA_KIND)
 5185                and self._prev.text.upper()
 5186            )
 5187            expression = self._match(TokenType.FARROW) and self._parse_bitwise()
 5188
 5189            if expression:
 5190                self._match_r_paren()
 5191                historical_data = self.expression(
 5192                    exp.HistoricalData(this=this, kind=kind, expression=expression)
 5193                )
 5194            else:
 5195                self._retreat(index)
 5196
 5197        return historical_data
 5198
 5199    def _parse_changes(self) -> exp.Changes | None:
 5200        if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"):
 5201            return None
 5202
 5203        information = self._parse_var(any_token=True)
 5204        self._match_r_paren()
 5205
 5206        return self.expression(
 5207            exp.Changes(
 5208                information=information,
 5209                at_before=self._parse_historical_data(),
 5210                end=self._parse_historical_data(),
 5211            )
 5212        )
 5213
 5214    def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None:
 5215        if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False):
 5216            return None
 5217
 5218        self._advance()
 5219
 5220        expressions = self._parse_wrapped_csv(self._parse_equality)
 5221        offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 5222
 5223        alias = self._parse_table_alias() if with_alias else None
 5224
 5225        if alias:
 5226            if self.dialect.UNNEST_COLUMN_ONLY:
 5227                if alias.args.get("columns"):
 5228                    self.raise_error("Unexpected extra column alias in unnest.")
 5229
 5230                alias.set("columns", [alias.this])
 5231                alias.set("this", None)
 5232
 5233            columns = alias.args.get("columns") or []
 5234            if offset and len(expressions) < len(columns):
 5235                offset = columns.pop()
 5236
 5237        if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET):
 5238            self._match(TokenType.ALIAS)
 5239            offset = self._parse_id_var(
 5240                any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS
 5241            ) or exp.to_identifier("offset")
 5242
 5243        return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset))
 5244
 5245    def _parse_derived_table_values(self) -> exp.Values | None:
 5246        is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES)
 5247        if not is_derived and not (
 5248            # ClickHouse's `FORMAT Values` is equivalent to `VALUES`
 5249            self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES")
 5250        ):
 5251            return None
 5252
 5253        expressions = self._parse_csv(self._parse_value)
 5254        alias = self._parse_table_alias()
 5255
 5256        if is_derived:
 5257            self._match_r_paren()
 5258
 5259        return self.expression(
 5260            exp.Values(expressions=expressions, alias=alias or self._parse_table_alias())
 5261        )
 5262
 5263    def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None:
 5264        if not self._match(TokenType.TABLE_SAMPLE) and not (
 5265            as_modifier and self._match_text_seq("USING", "SAMPLE")
 5266        ):
 5267            return None
 5268
 5269        bucket_numerator = None
 5270        bucket_denominator = None
 5271        bucket_field = None
 5272        percent = None
 5273        size = None
 5274        seed = None
 5275
 5276        method = self._parse_var(tokens=(TokenType.ROW,), upper=True)
 5277        matched_l_paren = self._match(TokenType.L_PAREN)
 5278
 5279        if self.TABLESAMPLE_CSV:
 5280            num = None
 5281            expressions = self._parse_csv(self._parse_primary)
 5282        else:
 5283            expressions = None
 5284            num = (
 5285                self._parse_factor(parse_mod=False)
 5286                if self._match(TokenType.NUMBER, advance=False)
 5287                else self._parse_primary() or self._parse_placeholder()
 5288            )
 5289
 5290        if self._match_text_seq("BUCKET"):
 5291            bucket_numerator = self._parse_number()
 5292            self._match_text_seq("OUT", "OF")
 5293            bucket_denominator = bucket_denominator = self._parse_number()
 5294            self._match(TokenType.ON)
 5295            bucket_field = self._parse_field()
 5296        elif self._match_set((TokenType.PERCENT, TokenType.MOD)):
 5297            percent = num
 5298        elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
 5299            size = num
 5300        else:
 5301            percent = num
 5302
 5303        if matched_l_paren:
 5304            self._match_r_paren()
 5305
 5306        if self._match(TokenType.L_PAREN):
 5307            method = self._parse_var(upper=True)
 5308            seed = self._match(TokenType.COMMA) and self._parse_number()
 5309            self._match_r_paren()
 5310        elif self._match_texts(("SEED", "REPEATABLE")):
 5311            seed = self._parse_wrapped(self._parse_number)
 5312
 5313        if not method and self.DEFAULT_SAMPLING_METHOD:
 5314            method = exp.var(self.DEFAULT_SAMPLING_METHOD)
 5315
 5316        return self.expression(
 5317            exp.TableSample(
 5318                expressions=expressions,
 5319                method=method,
 5320                bucket_numerator=bucket_numerator,
 5321                bucket_denominator=bucket_denominator,
 5322                bucket_field=bucket_field,
 5323                percent=percent,
 5324                size=size,
 5325                seed=seed,
 5326            )
 5327        )
 5328
 5329    def _parse_pivots(self) -> list[exp.Pivot] | None:
 5330        if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT):
 5331            return None
 5332        return list(iter(self._parse_pivot, None)) or None
 5333
 5334    def _parse_joins(
 5335        self, alias_tokens: t.Collection[TokenType] | None = None
 5336    ) -> t.Iterator[exp.Join]:
 5337        return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None)
 5338
 5339    def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None:
 5340        if not self._match(TokenType.INTO):
 5341            return None
 5342
 5343        return self.expression(
 5344            exp.UnpivotColumns(
 5345                this=self._match_text_seq("NAME") and self._parse_column(),
 5346                expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column),
 5347            )
 5348        )
 5349
 5350    # https://duckdb.org/docs/sql/statements/pivot
 5351    def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot:
 5352        def _parse_on() -> exp.Expr | None:
 5353            this = self._parse_bitwise()
 5354
 5355            if self._match(TokenType.IN):
 5356                # PIVOT ... ON col IN (row_val1, row_val2)
 5357                return self._parse_in(this)
 5358            if self._match(TokenType.ALIAS, advance=False):
 5359                # UNPIVOT ... ON (col1, col2, col3) AS row_val
 5360                return self._parse_alias(this)
 5361
 5362            return this
 5363
 5364        this = self._parse_table()
 5365        expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on)
 5366        into = self._parse_unpivot_columns()
 5367        using = self._match(TokenType.USING) and self._parse_csv(
 5368            lambda: self._parse_alias(self._parse_column())
 5369        )
 5370        group = self._parse_group()
 5371
 5372        return self.expression(
 5373            exp.Pivot(
 5374                this=this,
 5375                expressions=expressions,
 5376                using=using,
 5377                group=group,
 5378                unpivot=is_unpivot,
 5379                into=into,
 5380            )
 5381        )
 5382
 5383    def _parse_pivot_in(self) -> exp.In:
 5384        def _parse_aliased_expression() -> exp.Expr | None:
 5385            this = self._parse_select_or_expression()
 5386
 5387            self._match(TokenType.ALIAS)
 5388            alias = self._parse_bitwise()
 5389            if alias:
 5390                if isinstance(alias, exp.Column) and not alias.db:
 5391                    alias = alias.this
 5392                return self.expression(exp.PivotAlias(this=this, alias=alias))
 5393
 5394            return this
 5395
 5396        value = self._parse_column()
 5397
 5398        if not self._match(TokenType.IN):
 5399            self.raise_error("Expecting IN")
 5400
 5401        if self._match(TokenType.L_PAREN):
 5402            if self._match(TokenType.ANY):
 5403                exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order()))
 5404            else:
 5405                exprs = self._parse_csv(_parse_aliased_expression)
 5406            self._match_r_paren()
 5407            return self.expression(exp.In(this=value, expressions=exprs))
 5408
 5409        return self.expression(exp.In(this=value, field=self._parse_id_var()))
 5410
 5411    def _parse_pivot_aggregation(self) -> exp.Expr | None:
 5412        func = self._parse_function()
 5413        if not func:
 5414            if self._prev.token_type == TokenType.COMMA:
 5415                return None
 5416            self.raise_error("Expecting an aggregation function in PIVOT")
 5417
 5418        return self._parse_alias(func)
 5419
 5420    def _parse_pivot(self) -> exp.Pivot | None:
 5421        index = self._index
 5422        include_nulls = None
 5423
 5424        if self._match(TokenType.PIVOT):
 5425            unpivot = False
 5426        elif self._match(TokenType.UNPIVOT):
 5427            unpivot = True
 5428
 5429            # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax
 5430            if self._match_text_seq("INCLUDE", "NULLS"):
 5431                include_nulls = True
 5432            elif self._match_text_seq("EXCLUDE", "NULLS"):
 5433                include_nulls = False
 5434        else:
 5435            return None
 5436
 5437        expressions = []
 5438
 5439        if not self._match(TokenType.L_PAREN):
 5440            self._retreat(index)
 5441            return None
 5442
 5443        if unpivot:
 5444            expressions = self._parse_csv(self._parse_column)
 5445        else:
 5446            expressions = self._parse_csv(self._parse_pivot_aggregation)
 5447
 5448        if not expressions:
 5449            self.raise_error("Failed to parse PIVOT's aggregation list")
 5450
 5451        if not self._match(TokenType.FOR):
 5452            self.raise_error("Expecting FOR")
 5453
 5454        fields = []
 5455        while True:
 5456            field = self._try_parse(self._parse_pivot_in)
 5457            if not field:
 5458                break
 5459            fields.append(field)
 5460
 5461        default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped(
 5462            self._parse_bitwise
 5463        )
 5464
 5465        group = self._parse_group()
 5466
 5467        self._match_r_paren()
 5468
 5469        pivot = self.expression(
 5470            exp.Pivot(
 5471                expressions=expressions,
 5472                fields=fields,
 5473                unpivot=unpivot,
 5474                include_nulls=include_nulls,
 5475                default_on_null=default_on_null,
 5476                group=group,
 5477            )
 5478        )
 5479
 5480        if unpivot:
 5481            pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions])
 5482            for pivot_field in pivot.fields:
 5483                if isinstance(pivot_field, exp.In):
 5484                    pivot_field.set("this", _unpivot_target(pivot_field.this))
 5485
 5486            pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST)
 5487
 5488        if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False):
 5489            pivot.set("alias", self._parse_table_alias())
 5490
 5491        if not unpivot:
 5492            names = self._pivot_column_names(t.cast(list[exp.Expr], expressions))
 5493
 5494            columns: list[exp.Expr] = []
 5495            all_fields = []
 5496            for pivot_field in pivot.fields:
 5497                pivot_field_expressions = pivot_field.expressions
 5498
 5499                # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case.
 5500                if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny):
 5501                    continue
 5502
 5503                all_fields.append(
 5504                    [
 5505                        # An explicit `<field> AS <alias>` names the output column directly,
 5506                        # so it wins over the dialect's string-identifying convention
 5507                        fld.sql()
 5508                        if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias)
 5509                        else fld.alias_or_name
 5510                        for fld in pivot_field_expressions
 5511                    ]
 5512                )
 5513
 5514            if all_fields:
 5515                if names:
 5516                    all_fields.append(names)
 5517
 5518                # Generate all possible combinations of the pivot columns
 5519                # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US'))
 5520                # generates the product between [[2000, 2010], ['NL', 'US'], ['total']]
 5521                for fld_parts_tuple in itertools.product(*all_fields):
 5522                    fld_parts = list(fld_parts_tuple)
 5523
 5524                    if names and self.PREFIXED_PIVOT_COLUMNS:
 5525                        # Move the "name" to the front of the list
 5526                        fld_parts.insert(0, fld_parts.pop(-1))
 5527
 5528                    columns.append(exp.to_identifier("_".join(fld_parts)))
 5529
 5530            pivot.set("columns", columns)
 5531            pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS)
 5532            pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS)
 5533            pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING)
 5534
 5535        return pivot
 5536
 5537    def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]:
 5538        return [agg.alias for agg in aggregations if agg.alias]
 5539
 5540    def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None:
 5541        if not skip_where_token and not self._match(TokenType.PREWHERE):
 5542            return None
 5543
 5544        comments = self._prev_comments
 5545        return self.expression(
 5546            exp.PreWhere(this=self._parse_disjunction()),
 5547            comments=comments,
 5548        )
 5549
 5550    def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None:
 5551        if not skip_where_token and not self._match(TokenType.WHERE):
 5552            return None
 5553
 5554        comments = self._prev_comments
 5555        return self.expression(
 5556            exp.Where(this=self._parse_disjunction()),
 5557            comments=comments,
 5558        )
 5559
 5560    def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None:
 5561        if not skip_group_by_token and not self._match(TokenType.GROUP_BY):
 5562            return None
 5563        comments = self._prev_comments
 5564
 5565        elements: dict[str, t.Any] = defaultdict(list)
 5566
 5567        if self._match(TokenType.ALL):
 5568            elements["all"] = True
 5569        elif self._match(TokenType.DISTINCT):
 5570            elements["all"] = False
 5571
 5572        while True:
 5573            index = self._index
 5574
 5575            # Stop before consuming modifier tokens like LIMIT, OFFSET and WINDOW,
 5576            # which are also valid identifiers
 5577            if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False):
 5578                break
 5579
 5580            elements["expressions"].extend(
 5581                self._parse_csv(
 5582                    lambda: (
 5583                        None
 5584                        if self._match_set((TokenType.CUBE, TokenType.ROLLUP), advance=False)
 5585                        else self._parse_disjunction()
 5586                    )
 5587                )
 5588            )
 5589            grouping_sets_as_group_by_element = (
 5590                not elements["expressions"] or self._prev.token_type == TokenType.COMMA
 5591            )
 5592
 5593            before_with_index = self._index
 5594            with_prefix = self._match(TokenType.WITH)
 5595
 5596            if cube_or_rollup := self._parse_cube_or_rollup(with_prefix=with_prefix):
 5597                key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube"
 5598                elements[key].append(cube_or_rollup)
 5599            elif grouping_sets := self._parse_grouping_sets():
 5600                elements["grouping_sets"].append(grouping_sets)
 5601                elements["grouping_sets_as_group_by_element"] = grouping_sets_as_group_by_element
 5602                if not grouping_sets_as_group_by_element:
 5603                    break
 5604            elif self._match_text_seq("TOTALS"):
 5605                elements["totals"] = True  # type: ignore
 5606
 5607            if before_with_index <= self._index <= before_with_index + 1:
 5608                self._retreat(before_with_index)
 5609                break
 5610
 5611            if index == self._index:
 5612                break
 5613
 5614        return self.expression(exp.Group(**elements), comments=comments)  # type: ignore
 5615
 5616    def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None:
 5617        if self._match(TokenType.CUBE):
 5618            kind: type[exp.Cube | exp.Rollup] = exp.Cube
 5619        elif self._match(TokenType.ROLLUP):
 5620            kind = exp.Rollup
 5621        else:
 5622            return None
 5623
 5624        return self.expression(
 5625            kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise))
 5626        )
 5627
 5628    def _parse_grouping_sets(self) -> exp.GroupingSets | None:
 5629        if self._match(TokenType.GROUPING_SETS):
 5630            return self.expression(
 5631                exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set))
 5632            )
 5633        return None
 5634
 5635    def _parse_grouping_set(self) -> exp.Expr | None:
 5636        return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise()
 5637
 5638    def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None:
 5639        if not skip_having_token and not self._match(TokenType.HAVING):
 5640            return None
 5641        comments = self._prev_comments
 5642        return self.expression(
 5643            exp.Having(this=self._parse_disjunction()),
 5644            comments=comments,
 5645        )
 5646
 5647    def _parse_qualify(self) -> exp.Qualify | None:
 5648        if not self._match(TokenType.QUALIFY):
 5649            return None
 5650        return self.expression(exp.Qualify(this=self._parse_disjunction()))
 5651
 5652    def _parse_connect_with_prior(self) -> exp.Expr | None:
 5653        self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression(
 5654            exp.Prior(this=self._parse_bitwise())
 5655        )
 5656        connect = self._parse_disjunction()
 5657        self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR")
 5658        return connect
 5659
 5660    def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None:
 5661        if skip_start_token:
 5662            start = None
 5663        elif self._match_text_seq("START", "WITH"):
 5664            start = self._parse_disjunction()
 5665        else:
 5666            return None
 5667
 5668        self._match(TokenType.CONNECT_BY)
 5669        nocycle = self._match_text_seq("NOCYCLE")
 5670        connect = self._parse_connect_with_prior()
 5671
 5672        if not start and self._match_text_seq("START", "WITH"):
 5673            start = self._parse_disjunction()
 5674
 5675        return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle))
 5676
 5677    def _parse_name_as_expression(self) -> exp.Expr | None:
 5678        this = self._parse_id_var(any_token=True)
 5679        if self._match(TokenType.ALIAS):
 5680            this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction()))
 5681        return this
 5682
 5683    def _parse_interpolate(self) -> list[exp.Expr] | None:
 5684        if self._match_text_seq("INTERPOLATE"):
 5685            return self._parse_wrapped_csv(self._parse_name_as_expression)
 5686        return None
 5687
 5688    def _parse_order(
 5689        self, this: exp.Expr | None = None, skip_order_token: bool = False
 5690    ) -> exp.Expr | None:
 5691        siblings = None
 5692        if not skip_order_token and not self._match(TokenType.ORDER_BY):
 5693            if not self._match(TokenType.ORDER_SIBLINGS_BY):
 5694                return this
 5695
 5696            siblings = True
 5697
 5698        comments = self._prev_comments
 5699        return self.expression(
 5700            exp.Order(
 5701                this=this,
 5702                expressions=self._parse_csv(self._parse_ordered),
 5703                siblings=siblings,
 5704            ),
 5705            comments=comments,
 5706        )
 5707
 5708    def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None:
 5709        if not self._match(token):
 5710            return None
 5711        return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered)))
 5712
 5713    def _parse_ordered(
 5714        self, parse_method: t.Callable[[], exp.Expr | None] | None = None
 5715    ) -> exp.Ordered | None:
 5716        this = parse_method() if parse_method else self._parse_disjunction()
 5717        if not this:
 5718            return None
 5719
 5720        if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL:
 5721            this = exp.var("ALL")
 5722
 5723        asc = self._match(TokenType.ASC)
 5724        desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None)
 5725
 5726        is_nulls_first = self._match_text_seq("NULLS", "FIRST")
 5727        is_nulls_last = self._match_text_seq("NULLS", "LAST")
 5728
 5729        nulls_first = is_nulls_first or False
 5730        explicitly_null_ordered = is_nulls_first or is_nulls_last
 5731
 5732        if (
 5733            not explicitly_null_ordered
 5734            and (
 5735                (not desc and self.dialect.NULL_ORDERING == "nulls_are_small")
 5736                or (desc and self.dialect.NULL_ORDERING != "nulls_are_small")
 5737            )
 5738            and self.dialect.NULL_ORDERING != "nulls_are_last"
 5739        ):
 5740            nulls_first = True
 5741
 5742        if self._match_text_seq("WITH", "FILL"):
 5743            with_fill = self.expression(
 5744                exp.WithFill(
 5745                    from_=self._match(TokenType.FROM) and self._parse_bitwise(),
 5746                    to=self._match_text_seq("TO") and self._parse_bitwise(),
 5747                    step=self._match_text_seq("STEP") and self._parse_bitwise(),
 5748                    interpolate=self._parse_interpolate(),
 5749                )
 5750            )
 5751        else:
 5752            with_fill = None
 5753
 5754        return self.expression(
 5755            exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill)
 5756        )
 5757
 5758    def _parse_limit_options(self) -> exp.LimitOptions | None:
 5759        percent = self._match_set((TokenType.PERCENT, TokenType.MOD))
 5760        rows = self._match_set((TokenType.ROW, TokenType.ROWS))
 5761        self._match_text_seq("ONLY")
 5762        with_ties = self._match_text_seq("WITH", "TIES")
 5763
 5764        if not (percent or rows or with_ties):
 5765            return None
 5766
 5767        return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties))
 5768
 5769    def _parse_limit(
 5770        self,
 5771        this: exp.Expr | None = None,
 5772        top: bool = False,
 5773        skip_limit_token: bool = False,
 5774    ) -> exp.Expr | None:
 5775        if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT):
 5776            comments = self._prev_comments
 5777            if top:
 5778                limit_paren = self._match(TokenType.L_PAREN)
 5779                expression = (
 5780                    self._parse_term() or self._parse_select()
 5781                    if limit_paren
 5782                    else self._parse_number()
 5783                )
 5784
 5785                if limit_paren:
 5786                    self._match_r_paren()
 5787
 5788            else:
 5789                if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL):
 5790                    return this
 5791
 5792                expression = self._parse_term(parse_mod=False)
 5793            limit_options = self._parse_limit_options()
 5794
 5795            if self._match(TokenType.COMMA):
 5796                offset = expression
 5797                expression = self._parse_term()
 5798            else:
 5799                offset = None
 5800
 5801            limit_exp = self.expression(
 5802                exp.Limit(
 5803                    this=this,
 5804                    expression=expression,
 5805                    offset=offset,
 5806                    limit_options=limit_options,
 5807                    expressions=self._parse_limit_by(),
 5808                ),
 5809                comments=comments,
 5810            )
 5811
 5812            return limit_exp
 5813
 5814        if self._match(TokenType.FETCH):
 5815            direction = (
 5816                self._prev.text.upper()
 5817                if self._match_set((TokenType.FIRST, TokenType.NEXT))
 5818                else "FIRST"
 5819            )
 5820
 5821            count = self._parse_field(tokens=self.FETCH_TOKENS)
 5822
 5823            return self.expression(
 5824                exp.Fetch(
 5825                    direction=direction, count=count, limit_options=self._parse_limit_options()
 5826                )
 5827            )
 5828
 5829        return this
 5830
 5831    def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None:
 5832        if not self._match(TokenType.OFFSET):
 5833            return this
 5834
 5835        count = self._parse_term()
 5836        self._match_set((TokenType.ROW, TokenType.ROWS))
 5837
 5838        return self.expression(
 5839            exp.Offset(this=this, expression=count, expressions=self._parse_limit_by())
 5840        )
 5841
 5842    def _can_parse_limit_or_offset(self) -> bool:
 5843        if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False):
 5844            return False
 5845
 5846        index = self._index
 5847        result = bool(
 5848            self._try_parse(self._parse_limit, retreat=True)
 5849            or self._try_parse(self._parse_offset, retreat=True)
 5850        )
 5851        self._retreat(index)
 5852
 5853        # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset
 5854        if self._next.token_type == TokenType.MATCH_CONDITION:
 5855            result = False
 5856
 5857        return result
 5858
 5859    def _can_parse_named_window(self) -> bool:
 5860        # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias.
 5861        # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`.
 5862        if not self._match(TokenType.WINDOW, advance=False):
 5863            return False
 5864
 5865        name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None
 5866        if name is None or name.token_type not in self.ID_VAR_TOKENS:
 5867            return False
 5868
 5869        alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None
 5870        if alias_tok is None or alias_tok.token_type != TokenType.ALIAS:
 5871            return False
 5872
 5873        body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None
 5874        return body is not None and body.token_type == TokenType.L_PAREN
 5875
 5876    def _parse_limit_by(self) -> list[exp.Expr] | None:
 5877        return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None
 5878
 5879    def _parse_locks(self) -> list[exp.Lock]:
 5880        locks = []
 5881        while True:
 5882            update, key = None, None
 5883            if self._match_text_seq("FOR", "UPDATE"):
 5884                update = True
 5885            elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq(
 5886                "LOCK", "IN", "SHARE", "MODE"
 5887            ):
 5888                update = False
 5889            elif self._match_text_seq("FOR", "KEY", "SHARE"):
 5890                update, key = False, True
 5891            elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"):
 5892                update, key = True, True
 5893            else:
 5894                break
 5895
 5896            expressions = None
 5897            if self._match_text_seq("OF"):
 5898                expressions = self._parse_csv(lambda: self._parse_table(schema=True))
 5899
 5900            wait: bool | exp.Expr | None = None
 5901            if self._match_text_seq("NOWAIT"):
 5902                wait = True
 5903            elif self._match_text_seq("WAIT"):
 5904                wait = self._parse_primary()
 5905            elif self._match_text_seq("SKIP", "LOCKED"):
 5906                wait = False
 5907
 5908            locks.append(
 5909                self.expression(
 5910                    exp.Lock(update=update, expressions=expressions, wait=wait, key=key)
 5911                )
 5912            )
 5913
 5914        return locks
 5915
 5916    def parse_set_operation(
 5917        self, this: exp.Expr | None, consume_pipe: bool = False
 5918    ) -> exp.Expr | None:
 5919        start = self._index
 5920        _, side_token, kind_token = self._parse_join_parts()
 5921
 5922        side = side_token.text if side_token else None
 5923        kind = kind_token.text if kind_token else None
 5924
 5925        if not self._match_set(self.SET_OPERATIONS):
 5926            self._retreat(start)
 5927            return None
 5928
 5929        token_type = self._prev.token_type
 5930
 5931        if token_type == TokenType.UNION:
 5932            operation: type[exp.SetOperation] = exp.Union
 5933        elif token_type == TokenType.EXCEPT:
 5934            operation = exp.Except
 5935        else:
 5936            operation = exp.Intersect
 5937
 5938        comments = self._prev.comments
 5939
 5940        if self._match(TokenType.DISTINCT):
 5941            distinct: bool | None = True
 5942        elif self._match(TokenType.ALL):
 5943            distinct = False
 5944        else:
 5945            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
 5946            if distinct is None:
 5947                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
 5948
 5949        by_name = (
 5950            self._match_text_seq("BY", "NAME")
 5951            or self._match_text_seq("STRICT", "CORRESPONDING")
 5952            or None
 5953        )
 5954        if self._match_text_seq("CORRESPONDING"):
 5955            by_name = True
 5956            if not side and not kind:
 5957                kind = "INNER"
 5958
 5959        on_column_list = None
 5960        if by_name and self._match_texts(("ON", "BY")):
 5961            on_column_list = self._parse_wrapped_csv(self._parse_column)
 5962
 5963        expression = self._parse_select(
 5964            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
 5965        )
 5966
 5967        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
 5968        # in _parse_cte and so that alias pushdown can reach into set operation branches
 5969        if isinstance(this, exp.Values):
 5970            this = self._values_to_select(this)
 5971        if isinstance(expression, exp.Values):
 5972            expression = self._values_to_select(expression)
 5973
 5974        if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery):
 5975            subquery = this.this
 5976            subquery.set("alias", exp.TableAlias(this=this.args["alias"]))
 5977            subquery.add_comments(this.pop_comments())
 5978            this = subquery
 5979
 5980        return self.expression(
 5981            operation(
 5982                this=this,
 5983                distinct=distinct,
 5984                by_name=by_name,
 5985                expression=expression,
 5986                side=side,
 5987                kind=kind,
 5988                on=on_column_list,
 5989            ),
 5990            comments=comments,
 5991        )
 5992
 5993    def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None:
 5994        while this:
 5995            setop = self.parse_set_operation(this)
 5996            if not setop:
 5997                break
 5998            this = setop
 5999
 6000        if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP:
 6001            expression = this.expression
 6002
 6003            if expression:
 6004                for arg in self.SET_OP_MODIFIERS:
 6005                    expr = expression.args.get(arg)
 6006                    if expr:
 6007                        this.set(arg, expr.pop())
 6008
 6009        return this
 6010
 6011    def _parse_expression(self) -> exp.Expr | None:
 6012        return self._parse_alias(self._parse_assignment())
 6013
 6014    def _parse_assignment(self) -> exp.Expr | None:
 6015        this = self._parse_disjunction()
 6016        if not this and self._next.token_type in self.ASSIGNMENT:
 6017            # This allows us to parse <non-identifier token> := <expr>
 6018            this = exp.column(
 6019                t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text)
 6020            )
 6021
 6022        while self._match_set(self.ASSIGNMENT):
 6023            if isinstance(this, exp.Column) and len(this.parts) == 1:
 6024                this = this.this
 6025
 6026            comments = self._prev_comments
 6027            this = self.expression(
 6028                self.ASSIGNMENT[self._prev.token_type](
 6029                    this=this, expression=self._parse_assignment()
 6030                ),
 6031                comments=comments,
 6032            )
 6033
 6034        return this
 6035
 6036    def _parse_disjunction(self) -> exp.Expr | None:
 6037        this = self._parse_conjunction()
 6038        while self._match_set(self.DISJUNCTION):
 6039            comments = self._prev_comments
 6040            this = self.expression(
 6041                self.DISJUNCTION[self._prev.token_type](
 6042                    this=this, expression=self._parse_conjunction()
 6043                ),
 6044                comments=comments,
 6045            )
 6046        return this
 6047
 6048    def _parse_conjunction(self) -> exp.Expr | None:
 6049        this = self._parse_equality()
 6050        while self._match_set(self.CONJUNCTION):
 6051            comments = self._prev_comments
 6052            this = self.expression(
 6053                self.CONJUNCTION[self._prev.token_type](
 6054                    this=this, expression=self._parse_equality()
 6055                ),
 6056                comments=comments,
 6057            )
 6058        return this
 6059
 6060    def _parse_equality(self) -> exp.Expr | None:
 6061        this = self._parse_comparison()
 6062        while self._match_set(self.EQUALITY):
 6063            comments = self._prev_comments
 6064            this = self.expression(
 6065                self.EQUALITY[self._prev.token_type](
 6066                    this=this, expression=self._parse_comparison()
 6067                ),
 6068                comments=comments,
 6069            )
 6070        return this
 6071
 6072    def _parse_comparison(self) -> exp.Expr | None:
 6073        this = self._parse_range()
 6074        while self._match_set(self.COMPARISON):
 6075            comments = self._prev_comments
 6076            this = self.expression(
 6077                self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()),
 6078                comments=comments,
 6079            )
 6080        return this
 6081
 6082    def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6083        this = this or self._parse_bitwise()
 6084
 6085        while True:
 6086            negate = self._match(TokenType.NOT)
 6087            if self._match_set(self.RANGE_PARSERS):
 6088                expression = self.RANGE_PARSERS[self._prev.token_type](self, this)
 6089                if not expression:
 6090                    return this
 6091
 6092                this = expression
 6093            elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)):
 6094                this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6095            elif self._match(TokenType.NOTNULL):
 6096                # Postgres supports ISNULL and NOTNULL for conditions.
 6097                # https://blog.andreiavram.ro/postgresql-null-composite-type/
 6098                if self.dialect.NORMALIZE_NOT_NULL:
 6099                    this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6100                    this = self.expression(exp.Not(this=this))
 6101                else:
 6102                    this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True))
 6103            else:
 6104                if negate:
 6105                    self._retreat(self._index - 1)
 6106                break
 6107
 6108            if negate:
 6109                this = self._negate_range(this)
 6110                if self._curr and (
 6111                    self._curr.token_type == TokenType.NOT
 6112                    or self._curr.token_type in self.RANGE_PARSERS
 6113                ):
 6114                    this = self.expression(exp.Paren(this=this))
 6115
 6116        return this
 6117
 6118    def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6119        if not this:
 6120            return this
 6121
 6122        expression = this.this if isinstance(this, exp.Escape) else this
 6123        if isinstance(expression, (exp.Like, exp.ILike)):
 6124            expression.set("negate", True)
 6125            return this
 6126
 6127        return self.expression(exp.Not(this=this))
 6128
 6129    def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None:
 6130        index = self._index - 1
 6131        negate = self._match(TokenType.NOT)
 6132
 6133        if self._match_text_seq("DISTINCT", "FROM"):
 6134            klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ
 6135            return self.expression(klass(this=this, expression=self._parse_bitwise()))
 6136
 6137        if self._match(TokenType.JSON):
 6138            kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper()
 6139
 6140            if self._match_text_seq("WITH"):
 6141                _with = True
 6142            elif self._match_text_seq("WITHOUT"):
 6143                _with = False
 6144            else:
 6145                _with = None
 6146
 6147            unique = self._match(TokenType.UNIQUE)
 6148            self._match_text_seq("KEYS")
 6149            expression: exp.Expr | None = self.expression(
 6150                exp.JSON(this=kind, with_=_with, unique=unique)
 6151            )
 6152        else:
 6153            expression = self._parse_null() or self._parse_bitwise()
 6154            if not expression:
 6155                self._retreat(index)
 6156                return None
 6157
 6158        if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL:
 6159            this = self.expression(exp.Is(this=this, expression=expression, negate=True))
 6160        else:
 6161            this = self.expression(exp.Is(this=this, expression=expression))
 6162            this = self.expression(exp.Not(this=this)) if negate else this
 6163
 6164        return self._parse_column_ops(this)
 6165
 6166    def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In:
 6167        unnest = self._parse_unnest(with_alias=False)
 6168        if unnest:
 6169            this = self.expression(exp.In(this=this, unnest=unnest))
 6170        elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)):
 6171            matched_l_paren = self._prev.token_type == TokenType.L_PAREN
 6172            expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias))
 6173
 6174            if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query):
 6175                this = self.expression(
 6176                    exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False))
 6177                )
 6178            else:
 6179                this = self.expression(exp.In(this=this, expressions=expressions))
 6180
 6181            if matched_l_paren:
 6182                self._match_r_paren(this)
 6183            elif not self._match(TokenType.R_BRACKET, expression=this):
 6184                self.raise_error("Expecting ]")
 6185        else:
 6186            this = self.expression(exp.In(this=this, field=self._parse_column()))
 6187
 6188        return this
 6189
 6190    def _parse_between(self, this: exp.Expr | None) -> exp.Between:
 6191        symmetric = None
 6192        if self._match_text_seq("SYMMETRIC"):
 6193            symmetric = True
 6194        elif self._match_text_seq("ASYMMETRIC"):
 6195            symmetric = False
 6196
 6197        low = self._parse_bitwise()
 6198        self._match(TokenType.AND)
 6199        high = self._parse_bitwise()
 6200
 6201        return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric))
 6202
 6203    def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None:
 6204        if not self._match(TokenType.ESCAPE):
 6205            return this
 6206        return self.expression(
 6207            exp.Escape(this=this, expression=self._parse_string() or self._parse_null())
 6208        )
 6209
 6210    def _parse_interval_span(
 6211        self, this: exp.Expr, parse_function_unit: bool = True
 6212    ) -> exp.Interval:
 6213        # handle day-time format interval span with omitted units:
 6214        #   INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`>
 6215        interval_span_units_omitted = None
 6216        if (
 6217            this
 6218            and this.is_string
 6219            and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
 6220            and exp.INTERVAL_DAY_TIME_RE.match(this.name)
 6221        ):
 6222            index = self._index
 6223
 6224            # Var "TO" Var
 6225            first_unit = self._parse_var(any_token=True, upper=True)
 6226            second_unit = None
 6227            if first_unit and self._match_text_seq("TO"):
 6228                second_unit = self._parse_var(any_token=True, upper=True)
 6229
 6230            interval_span_units_omitted = not (first_unit and second_unit)
 6231
 6232            self._retreat(index)
 6233
 6234        unit_index = self._index
 6235        if interval_span_units_omitted:
 6236            unit = None
 6237        else:
 6238            # Only attempt to parse a unit if the current token can actually be one, so that a
 6239            # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x)
 6240            is_unit = self._curr is not None and (
 6241                self._curr.token_type == TokenType.VAR
 6242                or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS
 6243            )
 6244            unit = self._parse_function() if parse_function_unit and is_unit else None
 6245            if not unit and is_unit:
 6246                unit = self._parse_var(any_token=True, upper=True)
 6247
 6248        # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse
 6249        # each INTERVAL expression into this canonical form so it's easy to transpile
 6250        if this and this.is_number:
 6251            try:
 6252                this = exp.Literal.string(this.to_py())
 6253            except ValueError:
 6254                self.raise_error(f"Invalid numeric interval literal: {this.name!r}")
 6255        elif this and this.is_string:
 6256            parts = exp.INTERVAL_STRING_RE.findall(this.name)
 6257            if parts and unit:
 6258                # Unconsume the eagerly-parsed unit, since the real unit was part of the string
 6259                unit = None
 6260                self._retreat(unit_index)
 6261
 6262            if len(parts) == 1:
 6263                this = exp.Literal.string(parts[0][0])
 6264                unit = self.expression(exp.Var(this=parts[0][1].upper()))
 6265
 6266        if self.INTERVAL_SPANS and self._match_text_seq("TO"):
 6267            unit = self.expression(
 6268                exp.IntervalSpan(
 6269                    this=unit,
 6270                    expression=self._parse_function()
 6271                    or self._parse_var(any_token=True, upper=True),
 6272                )
 6273            )
 6274
 6275        return self.expression(exp.Interval(this=this, unit=unit))
 6276
 6277    def _parse_interval(
 6278        self, require_interval: bool = True, parse_function_unit: bool = True
 6279    ) -> exp.Add | exp.Interval | None:
 6280        index = self._index
 6281
 6282        if not self._match(TokenType.INTERVAL) and require_interval:
 6283            return None
 6284
 6285        if self._match(TokenType.STRING, advance=False):
 6286            this = self._parse_primary()
 6287        else:
 6288            this = self._parse_term()
 6289
 6290        if not this or (
 6291            isinstance(this, exp.Column)
 6292            and not this.table
 6293            and not this.this.quoted
 6294            and self._curr
 6295            and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS
 6296        ):
 6297            self._retreat(index)
 6298            return None
 6299
 6300        interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit)
 6301
 6302        index = self._index
 6303        self._match(TokenType.PLUS)
 6304
 6305        # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals
 6306        if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False):
 6307            return self.expression(
 6308                exp.Add(
 6309                    this=interval,
 6310                    expression=self._parse_interval(False, parse_function_unit=parse_function_unit),
 6311                )
 6312            )
 6313
 6314        self._retreat(index)
 6315        return interval
 6316
 6317    def _parse_bitwise(self) -> exp.Expr | None:
 6318        this = self._parse_term()
 6319
 6320        while True:
 6321            if self._match_set(self.BITWISE):
 6322                this = self.expression(
 6323                    self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term())
 6324                )
 6325            elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE):
 6326                this = self.expression(
 6327                    exp.DPipe(
 6328                        this=this,
 6329                        expression=self._parse_term(),
 6330                        safe=not self.dialect.STRICT_STRING_CONCAT,
 6331                    )
 6332                )
 6333            elif self._match(TokenType.DQMARK):
 6334                this = self.expression(
 6335                    exp.Coalesce(this=this, expressions=ensure_list(self._parse_term()))
 6336                )
 6337            elif self._match_pair(TokenType.LT, TokenType.LT):
 6338                this = self.expression(
 6339                    exp.BitwiseLeftShift(this=this, expression=self._parse_term())
 6340                )
 6341            elif self._match_pair(TokenType.GT, TokenType.GT):
 6342                this = self.expression(
 6343                    exp.BitwiseRightShift(this=this, expression=self._parse_term())
 6344                )
 6345            elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS):
 6346                this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term())
 6347            else:
 6348                break
 6349
 6350        return this
 6351
 6352    def _parse_term(self, parse_mod: bool = True) -> exp.Expr | None:
 6353        this = self._parse_factor(parse_mod=parse_mod)
 6354
 6355        while self._match_set(self.TERM):
 6356            klass = self.TERM[self._prev.token_type]
 6357            comments = self._prev_comments
 6358            expression = self._parse_factor(parse_mod=parse_mod)
 6359
 6360            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6361
 6362            if isinstance(this, exp.Collate):
 6363                self._normalize_collate(this)
 6364
 6365        return this
 6366
 6367    def _normalize_collate(self, collate: exp.Collate) -> None:
 6368        expr = collate.expression
 6369
 6370        # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise
 6371        # fallback to Identifier / Var
 6372        if isinstance(expr, exp.Column) and len(expr.parts) == 1:
 6373            ident = expr.this
 6374            if isinstance(ident, exp.Identifier):
 6375                collate.set("expression", ident if ident.quoted else exp.var(ident.name))
 6376
 6377    def _parse_factor(self, parse_mod: bool = True) -> exp.Expr | None:
 6378        parse_method = self._parse_factor_operand
 6379        this = self._parse_at_time_zone(parse_method())
 6380
 6381        while self._match_set(self.FACTOR, advance=False):
 6382            if not parse_mod and self._curr.token_type == TokenType.MOD:
 6383                break
 6384
 6385            self._advance()
 6386            klass = self.FACTOR[self._prev.token_type]
 6387            comments = self._prev_comments
 6388            expression = parse_method()
 6389
 6390            if not expression and klass is exp.IntDiv and self._prev.text.isalpha():
 6391                self._retreat(self._index - 1)
 6392                return this
 6393
 6394            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6395
 6396            if isinstance(this, exp.Div):
 6397                this.set("typed", self.dialect.TYPED_DIVISION)
 6398                this.set("safe", self.dialect.SAFE_DIVISION)
 6399
 6400        return this
 6401
 6402    def _parse_factor_operand(self) -> exp.Expr | None:
 6403        return self._parse_exponent() if self.EXPONENT else self._parse_unary()
 6404
 6405    def _parse_exponent(self) -> exp.Expr | None:
 6406        this = self._parse_unary()
 6407        while self._match_set(self.EXPONENT):
 6408            comments = self._prev_comments
 6409            this = self.expression(
 6410                self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()),
 6411                comments=comments,
 6412            )
 6413        return this
 6414
 6415    def _parse_unary(self) -> exp.Expr | None:
 6416        if self._match_set(self.UNARY_PARSERS):
 6417            return self.UNARY_PARSERS[self._prev.token_type](self)
 6418        return self._parse_type()
 6419
 6420    def _parse_type(
 6421        self, parse_interval: bool = True, fallback_to_identifier: bool = False
 6422    ) -> exp.Expr | None:
 6423        if not fallback_to_identifier and (atom := self._parse_atom()) is not None:
 6424            return atom
 6425
 6426        if interval := parse_interval and self._parse_interval():
 6427            return self._parse_column_ops(interval)
 6428
 6429        index = self._index
 6430        data_type = self._parse_types(check_func=True, allow_identifiers=False)
 6431
 6432        # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g.
 6433        # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>)
 6434        if isinstance(data_type, exp.Cast):
 6435            # This constructor can contain ops directly after it, for instance struct unnesting:
 6436            # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).*
 6437            return self._parse_column_ops(data_type)
 6438
 6439        if data_type:
 6440            index2 = self._index
 6441            this = self._parse_primary()
 6442
 6443            if isinstance(this, exp.Literal):
 6444                literal = this.name
 6445                this = self._parse_column_ops(this)
 6446
 6447                parser = self.TYPE_LITERAL_PARSERS.get(data_type.this)
 6448                if parser:
 6449                    return parser(self, this, data_type)
 6450
 6451                if self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR and TIME_ZONE_RE.search(literal):
 6452                    if data_type.is_type(exp.DType.TIMESTAMP):
 6453                        data_type = exp.DType.TIMESTAMPTZ.into_expr()
 6454                    elif data_type.is_type(exp.DType.TIME):
 6455                        data_type = exp.DType.TIMETZ.into_expr()
 6456
 6457                return self.expression(exp.Cast(this=this, to=data_type))
 6458
 6459            # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0)
 6460            # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 )
 6461            #
 6462            # If the index difference here is greater than 1, that means the parser itself must have
 6463            # consumed additional tokens such as the DECIMAL scale and precision in the above example.
 6464            #
 6465            # If it's not greater than 1, then it must be 1, because we've consumed at least the type
 6466            # keyword, meaning that the expressions arg of the DataType must have gotten set by a
 6467            # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to
 6468            # DECIMAL(38, 0)) in order to facilitate the data type's transpilation.
 6469            #
 6470            # In these cases, we don't really want to return the converted type, but instead retreat
 6471            # and try to parse a Column or Identifier in the section below.
 6472            if data_type.expressions and index2 - index > 1:
 6473                self._retreat(index2)
 6474                return self._parse_column_ops(data_type)
 6475
 6476            self._retreat(index)
 6477
 6478        if fallback_to_identifier:
 6479            return self._parse_id_var()
 6480
 6481        return self._parse_column()
 6482
 6483    def _parse_type_size(self) -> exp.DataTypeParam | None:
 6484        this = self._parse_type()
 6485        if not this:
 6486            return None
 6487
 6488        if isinstance(this, exp.Column) and not this.table:
 6489            this = exp.var(this.name.upper())
 6490
 6491        return self.expression(
 6492            exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True))
 6493        )
 6494
 6495    def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None:
 6496        type_name = identifier.name
 6497
 6498        while self._match(TokenType.DOT):
 6499            type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
 6500
 6501        return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True)
 6502
 6503    def _parse_types(
 6504        self,
 6505        check_func: bool = False,
 6506        schema: bool = False,
 6507        allow_identifiers: bool = True,
 6508        with_collation: bool = False,
 6509    ) -> exp.Expr | None:
 6510        index = self._index
 6511        this: exp.Expr | None = None
 6512
 6513        if self._match_set(self.TYPE_TOKENS):
 6514            type_token = self._prev.token_type
 6515        else:
 6516            type_token = None
 6517            identifier = allow_identifiers and self._parse_id_var(
 6518                any_token=False, tokens=(TokenType.VAR,)
 6519            )
 6520            if isinstance(identifier, exp.Identifier):
 6521                if identifier.quoted and identifier.name in self.QUOTED_TYPES_TO_PRESERVE:
 6522                    this = exp.DataType.build(identifier, udt=True)
 6523                else:
 6524                    try:
 6525                        tokens = self.dialect.tokenize(identifier.name)
 6526                    except TokenError:
 6527                        tokens = None
 6528
 6529                    if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS:
 6530                        if len(tokens) > 1:
 6531                            return exp.DataType.from_str(identifier.name, dialect=self.dialect)
 6532                    elif self.dialect.SUPPORTS_USER_DEFINED_TYPES:
 6533                        this = self._parse_user_defined_type(identifier)
 6534                    else:
 6535                        self._retreat(self._index - 1)
 6536                        return None
 6537            else:
 6538                return None
 6539
 6540        if type_token == TokenType.PSEUDO_TYPE:
 6541            return self.expression(exp.PseudoType(this=self._prev.text.upper()))
 6542
 6543        if type_token == TokenType.OBJECT_IDENTIFIER:
 6544            return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper()))
 6545
 6546        # https://materialize.com/docs/sql/types/map/
 6547        if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET):
 6548            key_type = self._parse_types(
 6549                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6550            )
 6551            if not self._match(TokenType.FARROW):
 6552                self._retreat(index)
 6553                return None
 6554
 6555            value_type = self._parse_types(
 6556                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6557            )
 6558            if not self._match(TokenType.R_BRACKET):
 6559                self._retreat(index)
 6560                return None
 6561
 6562            return exp.DataType(
 6563                this=exp.DType.MAP,
 6564                expressions=[key_type, value_type],
 6565                nested=True,
 6566            )
 6567
 6568        nested = type_token in self.NESTED_TYPE_TOKENS
 6569        is_struct = type_token in self.STRUCT_TYPE_TOKENS
 6570        is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS
 6571        expressions = None
 6572        maybe_func = False
 6573
 6574        if self._match(TokenType.L_PAREN):
 6575            if is_struct:
 6576                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6577            elif nested:
 6578                expressions = self._parse_csv(
 6579                    lambda: self._parse_types(
 6580                        check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6581                    )
 6582                )
 6583                if type_token == TokenType.NULLABLE and len(expressions) == 1:
 6584                    this = expressions[0]
 6585                    this.set("nullable", True)
 6586                    self._match_r_paren()
 6587                    return this
 6588            elif type_token in self.ENUM_TYPE_TOKENS:
 6589                expressions = self._parse_csv(self._parse_equality)
 6590            elif type_token == TokenType.JSON:
 6591                # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value)
 6592                # https://clickhouse.com/docs/sql-reference/data-types/newjson
 6593                expressions = self._parse_csv(self._parse_json_type_arg)
 6594            elif is_aggregate:
 6595                func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var(
 6596                    any_token=False, tokens=(TokenType.VAR, TokenType.ANY)
 6597                )
 6598                if not func_or_ident:
 6599                    return None
 6600                expressions = [func_or_ident]
 6601                if self._match(TokenType.COMMA):
 6602                    expressions.extend(
 6603                        self._parse_csv(
 6604                            lambda: self._parse_types(
 6605                                check_func=check_func,
 6606                                schema=schema,
 6607                                allow_identifiers=allow_identifiers,
 6608                            )
 6609                        )
 6610                    )
 6611            else:
 6612                expressions = self._parse_csv(self._parse_type_size)
 6613
 6614                # https://docs.snowflake.com/en/sql-reference/data-types-vector
 6615                if type_token == TokenType.VECTOR and len(expressions) == 2:
 6616                    expressions = self._parse_vector_expressions(expressions)
 6617
 6618            if not self._match(TokenType.R_PAREN):
 6619                self._retreat(index)
 6620                return None
 6621
 6622            maybe_func = True
 6623
 6624        values: list[exp.Expr] | None = None
 6625
 6626        if nested and self._match(TokenType.LT):
 6627            if is_struct:
 6628                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6629            else:
 6630                expressions = self._parse_csv(
 6631                    lambda: self._parse_types(
 6632                        check_func=check_func,
 6633                        schema=schema,
 6634                        allow_identifiers=allow_identifiers,
 6635                        with_collation=True,
 6636                    )
 6637                )
 6638
 6639            if not self._match(TokenType.GT):
 6640                self.raise_error("Expecting >")
 6641
 6642            if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)):
 6643                values = self._parse_csv(self._parse_disjunction)
 6644                if not values and is_struct:
 6645                    values = None
 6646                    self._retreat(self._index - 1)
 6647                else:
 6648                    self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN))
 6649
 6650        if type_token in self.TIMESTAMPS:
 6651            if self._match_text_seq("WITH", "TIME", "ZONE"):
 6652                maybe_func = False
 6653                tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ
 6654                this = exp.DataType(this=tz_type, expressions=expressions)
 6655            elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"):
 6656                maybe_func = False
 6657                this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions)
 6658            elif self._match_text_seq("WITHOUT", "TIME", "ZONE"):
 6659                maybe_func = False
 6660        elif type_token == TokenType.INTERVAL:
 6661            if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS:
 6662                unit = self._parse_var(upper=True)
 6663                if self._match_text_seq("TO"):
 6664                    unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True))
 6665
 6666                this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit))))
 6667            else:
 6668                this = self.expression(exp.DataType(this=exp.DType.INTERVAL))
 6669        elif type_token == TokenType.VOID:
 6670            this = exp.DataType(this=exp.DType.NULL)
 6671
 6672        if maybe_func and check_func:
 6673            index2 = self._index
 6674            peek = self._parse_string()
 6675
 6676            if not peek:
 6677                self._retreat(index)
 6678                return None
 6679
 6680            self._retreat(index2)
 6681
 6682        if not this:
 6683            assert type_token is not None
 6684            if self._match_text_seq("UNSIGNED"):
 6685                unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token)
 6686                if not unsigned_type_token:
 6687                    self.raise_error(f"Cannot convert {type_token.name} to unsigned.")
 6688
 6689                type_token = unsigned_type_token or type_token
 6690
 6691            # NULLABLE without parentheses can be a column (Presto/Trino)
 6692            if type_token == TokenType.NULLABLE and not expressions:
 6693                self._retreat(index)
 6694                return None
 6695
 6696            this = exp.DataType(
 6697                this=exp.DType[type_token.name],
 6698                expressions=expressions,
 6699                nested=nested,
 6700            )
 6701
 6702            # Empty arrays/structs are allowed
 6703            if values is not None:
 6704                cls = exp.Struct if is_struct else exp.Array
 6705                this = exp.cast(cls(expressions=values), this, copy=False)
 6706
 6707        elif expressions:
 6708            this.set("expressions", expressions)
 6709
 6710        # https://materialize.com/docs/sql/types/list/#type-name
 6711        while self._match(TokenType.LIST):
 6712            this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True)
 6713
 6714        index = self._index
 6715
 6716        # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3]
 6717        matched_array = self._match(TokenType.ARRAY)
 6718
 6719        while self._curr:
 6720            datatype_token = self._prev.token_type
 6721            matched_l_bracket = self._match(TokenType.L_BRACKET)
 6722
 6723            if (not matched_l_bracket and not matched_array) or (
 6724                datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET)
 6725            ):
 6726                # Postgres allows casting empty arrays such as ARRAY[]::INT[],
 6727                # not to be confused with the fixed size array parsing
 6728                break
 6729
 6730            matched_array = False
 6731            values = self._parse_csv(self._parse_disjunction) or None
 6732            if (
 6733                values
 6734                and not schema
 6735                and (
 6736                    not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS
 6737                    or datatype_token == TokenType.ARRAY
 6738                    or not self._match(TokenType.R_BRACKET, advance=False)
 6739                )
 6740            ):
 6741                # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB
 6742                # 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
 6743                self._retreat(index)
 6744                break
 6745
 6746            this = exp.DataType(
 6747                this=exp.DType.ARRAY, expressions=[this], values=values, nested=True
 6748            )
 6749            self._match(TokenType.R_BRACKET)
 6750
 6751        if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType):
 6752            converter = self.TYPE_CONVERTERS.get(this.this)
 6753            if converter:
 6754                this = converter(t.cast(exp.DataType, this))
 6755
 6756        if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE):
 6757            this.set("collate", self._parse_identifier() or self._parse_column())
 6758
 6759        return this
 6760
 6761    def _parse_json_type_arg(self) -> exp.Expr | None:
 6762        """Parse a single argument to ClickHouse's JSON type."""
 6763
 6764        # SKIP col or SKIP REGEXP 'pattern'
 6765        if self._match_text_seq("SKIP"):
 6766            regexp = self._match(TokenType.RLIKE)
 6767            arg = self._parse_column()
 6768            if isinstance(arg, exp.Column):
 6769                arg = arg.to_dot()
 6770            return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg))
 6771
 6772        param_or_col = self._parse_column()
 6773        if not isinstance(param_or_col, exp.Column):
 6774            return None
 6775
 6776        # Parameter: name=value (e.g., max_dynamic_paths=2)
 6777        if len(param_or_col.parts) == 1 and self._match(TokenType.EQ):
 6778            param = param_or_col.name
 6779            value = self._parse_primary()
 6780            return self.expression(exp.EQ(this=exp.var(param), expression=value))
 6781
 6782        # Column type hint: col_name Type
 6783        col = param_or_col.to_dot()
 6784        kind = self._parse_types(check_func=False, allow_identifiers=False)
 6785        return self.expression(exp.ColumnDef(this=col, kind=kind))
 6786
 6787    def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]:
 6788        return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]]
 6789
 6790    def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None:
 6791        index = self._index
 6792
 6793        if (
 6794            self._curr
 6795            and self._next
 6796            and self._curr.token_type in self.TYPE_TOKENS
 6797            and self._next.token_type in self.TYPE_TOKENS
 6798        ):
 6799            # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a
 6800            # type token. Without this, the list will be parsed as a type and we'll eventually crash
 6801            this = self._parse_id_var()
 6802        else:
 6803            this = (
 6804                self._parse_type(parse_interval=False, fallback_to_identifier=True)
 6805                or self._parse_id_var()
 6806            )
 6807
 6808        self._match(TokenType.COLON)
 6809
 6810        if (
 6811            type_required
 6812            and not isinstance(this, exp.DataType)
 6813            and not self._match_set(self.TYPE_TOKENS, advance=False)
 6814        ):
 6815            self._retreat(index)
 6816            return self._parse_types()
 6817
 6818        return self._parse_column_def(this)
 6819
 6820    def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None:
 6821        if not self._match_text_seq("AT", "TIME", "ZONE"):
 6822            return this
 6823        return self._parse_at_time_zone(
 6824            self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary()))
 6825        )
 6826
 6827    def _parse_atom(self) -> exp.Expr | None:
 6828        if (
 6829            self._curr.token_type in self.IDENTIFIER_TOKENS
 6830            and (column := self._parse_column()) is not None
 6831        ):
 6832            return column
 6833
 6834        token = self._curr
 6835        token_type = token.token_type
 6836
 6837        if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)):
 6838            return None
 6839
 6840        next_type = self._next.token_type
 6841
 6842        if (
 6843            next_type in self.COLUMN_OPERATORS
 6844            or next_type in self.COLUMN_POSTFIX_TOKENS
 6845            or (token_type == TokenType.STRING and next_type == TokenType.STRING)
 6846        ):
 6847            return None
 6848
 6849        self._advance()
 6850        return primary_parser(self, token)
 6851
 6852    def _parse_column(self) -> exp.Expr | None:
 6853        column: exp.Expr | None = self._parse_column_parts_fast()
 6854        if column is None:
 6855            this = self._parse_column_reference()
 6856            if not this:
 6857                this = self._parse_bracket(this)
 6858            column = self._parse_column_ops(this) if this else this
 6859
 6860        if column:
 6861            if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 6862                column.set("join_mark", self._match(TokenType.JOIN_MARKER))
 6863            if self.COLON_IS_VARIANT_EXTRACT:
 6864                column = self._parse_colon_as_variant_extract(column)
 6865
 6866        return column
 6867
 6868    def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None:
 6869        """Fast path for simple column and dot references (a, a.b, ...).
 6870
 6871        Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks
 6872        that nothing complex follows. If it does, retreats and returns None so
 6873        the slow path can handle it. For >4 parts, wraps in exp.Dot nodes.
 6874        """
 6875        index = self._index
 6876        parts: list[exp.Identifier] | None = None
 6877        all_comments: list[str] | None = None
 6878
 6879        while self._match_set(self.IDENTIFIER_TOKENS):
 6880            token = self._prev
 6881            comments = self._prev_comments
 6882
 6883            if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS:
 6884                self._retreat(index)
 6885                return None
 6886
 6887            has_dot = self._match(TokenType.DOT)
 6888            curr_tt = self._curr.token_type
 6889
 6890            if not has_dot:
 6891                if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS:
 6892                    self._retreat(index)
 6893                    return None
 6894            elif curr_tt not in self.IDENTIFIER_TOKENS:
 6895                self._retreat(index)
 6896                return None
 6897
 6898            if parts is None:
 6899                parts = []
 6900
 6901            if comments:
 6902                if all_comments is None:
 6903                    all_comments = []
 6904                all_comments.extend(comments)
 6905                self._prev_comments = []
 6906
 6907            parts.append(
 6908                self.expression(
 6909                    exp.Identifier(
 6910                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 6911                    ),
 6912                    token,
 6913                )
 6914            )
 6915
 6916            if not has_dot:
 6917                break
 6918
 6919        if parts is None:
 6920            return None
 6921
 6922        n = len(parts)
 6923
 6924        if n == 1:
 6925            column: exp.Column | exp.Dot = exp.Column(this=parts[0])
 6926        elif n == 2:
 6927            column = exp.Column(this=parts[1], table=parts[0])
 6928        elif n == 3:
 6929            column = exp.Column(this=parts[2], table=parts[1], db=parts[0])
 6930        else:
 6931            column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0])
 6932
 6933            for i in range(4, n):
 6934                column = exp.Dot(this=column, expression=parts[i])
 6935
 6936        if all_comments:
 6937            column.add_comments(all_comments)
 6938
 6939        return column
 6940
 6941    def _parse_column_reference(self) -> exp.Expr | None:
 6942        this = self._parse_field()
 6943        if (
 6944            not this
 6945            and self._match(TokenType.VALUES, advance=False)
 6946            and self.VALUES_FOLLOWED_BY_PAREN
 6947            and (not self._next or self._next.token_type != TokenType.L_PAREN)
 6948        ):
 6949            this = self._parse_id_var()
 6950
 6951        if isinstance(this, exp.Identifier):
 6952            # We bubble up comments from the Identifier to the Column
 6953            this = self.expression(exp.Column(this=this), comments=this.pop_comments())
 6954
 6955        return this
 6956
 6957    def _build_json_extract(
 6958        self,
 6959        this: exp.Expr | None,
 6960        path_parts: list[exp.JSONPathPart],
 6961    ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]:
 6962        if len(path_parts) > 1:
 6963            this = self.expression(
 6964                exp.JSONExtract(
 6965                    this=this,
 6966                    expression=exp.JSONPath(expressions=path_parts),
 6967                    variant_extract=True,
 6968                    requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION,
 6969                )
 6970            )
 6971            path_parts = [exp.JSONPathRoot()]
 6972
 6973        return this, path_parts
 6974
 6975    def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None:
 6976        path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()]
 6977
 6978        while self._match(TokenType.COLON):
 6979            if not self.COLON_CHAIN_IS_SINGLE_EXTRACT:
 6980                this, path_parts = self._build_json_extract(this, path_parts)
 6981
 6982            key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 6983
 6984            if key:
 6985                quoted = isinstance(key, exp.Identifier) and key.quoted
 6986                path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted))
 6987
 6988            while True:
 6989                if self._match(TokenType.DOT):
 6990                    next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 6991
 6992                    if next_key:
 6993                        quoted = isinstance(next_key, exp.Identifier) and next_key.quoted
 6994                        path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted))
 6995                elif self._match(TokenType.L_BRACKET):
 6996                    bracket_expr = self._parse_bracket_key_value()
 6997
 6998                    if not self._match(TokenType.R_BRACKET):
 6999                        self.raise_error("Expected ]")
 7000
 7001                    if bracket_expr:
 7002                        if bracket_expr.is_string:
 7003                            path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True))
 7004                        elif bracket_expr.is_star:
 7005                            path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard()))
 7006                        elif bracket_expr.is_number:
 7007                            path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py()))
 7008                        else:
 7009                            this, path_parts = self._build_json_extract(this, path_parts)
 7010
 7011                            this = self.expression(
 7012                                exp.Bracket(
 7013                                    this=this, expressions=[bracket_expr], json_access=True
 7014                                ),
 7015                            )
 7016
 7017                elif self._match(TokenType.DCOLON):
 7018                    this, path_parts = self._build_json_extract(this, path_parts)
 7019
 7020                    cast_type = self._parse_types()
 7021                    if cast_type:
 7022                        this = self.expression(exp.Cast(this=this, to=cast_type))
 7023                    else:
 7024                        self.raise_error("Expected type after '::'")
 7025                else:
 7026                    break
 7027
 7028        this, _ = self._build_json_extract(this, path_parts)
 7029
 7030        return this
 7031
 7032    def _parse_dcolon(self) -> exp.Expr | None:
 7033        return self._parse_types()
 7034
 7035    def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None:
 7036        while self._curr.token_type in self.BRACKETS:
 7037            this = self._parse_bracket(this)
 7038
 7039        column_operators = self.COLUMN_OPERATORS
 7040        cast_column_operators = self.CAST_COLUMN_OPERATORS
 7041        while self._curr:
 7042            op_token = self._curr.token_type
 7043
 7044            if op_token not in column_operators:
 7045                break
 7046            op = column_operators[op_token]
 7047            self._advance()
 7048
 7049            if op_token in cast_column_operators:
 7050                field = self._parse_dcolon()
 7051                if not field:
 7052                    self.raise_error("Expected type")
 7053            elif op and self._curr:
 7054                field = self._parse_column_reference() or self._parse_bitwise()
 7055                if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False):
 7056                    field = self._parse_column_ops(field)
 7057            else:
 7058                dot = self._is_connected() and self._prev.token_type == TokenType.DOT
 7059                field = self._parse_field(any_token=True, anonymous_func=True)
 7060
 7061                # In t.true, t.null we should produce an Identifier node
 7062                if dot and isinstance(field, (exp.Null, exp.Boolean)):
 7063                    field = self.expression(
 7064                        exp.Identifier(this=self._prev.text),
 7065                        comments=field.comments,
 7066                    )
 7067
 7068            # Function calls can be qualified, e.g., x.y.FOO()
 7069            # This converts the final AST to a series of Dots leading to the function call
 7070            # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules
 7071            if isinstance(field, (exp.Func, exp.Window)) and this:
 7072                this = this.transform(
 7073                    lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n
 7074                )
 7075
 7076            if op:
 7077                this = op(self, this, field)
 7078            elif isinstance(this, exp.Column) and not this.args.get("catalog"):
 7079                this = self.expression(
 7080                    exp.Column(
 7081                        this=field,
 7082                        table=this.this,
 7083                        db=this.args.get("table"),
 7084                        catalog=this.args.get("db"),
 7085                    ),
 7086                    comments=this.comments,
 7087                )
 7088            elif isinstance(field, exp.Window):
 7089                # Move the exp.Dot's to the window's function
 7090                window_func = self.expression(exp.Dot(this=this, expression=field.this))
 7091                field.set("this", window_func)
 7092                this = field
 7093            else:
 7094                this = self.expression(exp.Dot(this=this, expression=field))
 7095
 7096            if field and field.comments:
 7097                t.cast(exp.Expr, this).add_comments(field.pop_comments())
 7098
 7099            this = self._parse_bracket(this)
 7100
 7101        return this
 7102
 7103    def _parse_paren(self) -> exp.Expr | None:
 7104        if not self._match(TokenType.L_PAREN):
 7105            return None
 7106
 7107        comments = self._prev_comments
 7108        query = self._parse_select()
 7109
 7110        if query:
 7111            expressions = [query]
 7112        else:
 7113            expressions = self._parse_expressions()
 7114
 7115        this = seq_get(expressions, 0)
 7116
 7117        if not this and self._match(TokenType.R_PAREN, advance=False):
 7118            this = self.expression(exp.Tuple())
 7119        elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA:
 7120            this = self.expression(exp.Tuple(expressions=expressions))
 7121        elif isinstance(this, exp.UNWRAPPED_QUERIES):
 7122            this = self._parse_subquery(this=this, parse_alias=False)
 7123        elif isinstance(this, (exp.Subquery, exp.Values)):
 7124            this = self._parse_subquery(
 7125                this=self._parse_query_modifiers(self._parse_set_operations(this)),
 7126                parse_alias=False,
 7127            )
 7128        else:
 7129            this = self.expression(exp.Paren(this=this))
 7130
 7131        if this:
 7132            this.add_comments(comments)
 7133
 7134        self._match_r_paren(expression=this)
 7135
 7136        if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc):
 7137            return self._parse_window(this)
 7138
 7139        return this
 7140
 7141    def _parse_primary(self) -> exp.Expr | None:
 7142        if self._match_set(self.PRIMARY_PARSERS):
 7143            token_type = self._prev.token_type
 7144            primary = self.PRIMARY_PARSERS[token_type](self, self._prev)
 7145
 7146            if token_type == TokenType.STRING:
 7147                expressions = [primary]
 7148                while self._match(TokenType.STRING, advance=False):
 7149                    if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED:
 7150                        self.raise_error(
 7151                            "Adjacent string literals need to be separated by whitespace or comments"
 7152                        )
 7153
 7154                    self._advance()
 7155                    expressions.append(exp.Literal.string(self._prev.text))
 7156
 7157                if len(expressions) > 1:
 7158                    return self.expression(
 7159                        exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE)
 7160                    )
 7161
 7162            return primary
 7163
 7164        if self._match_pair(TokenType.DOT, TokenType.NUMBER):
 7165            return exp.Literal.number(f"0.{self._prev.text}")
 7166
 7167        return self._parse_paren()
 7168
 7169    def _parse_field(
 7170        self,
 7171        any_token: bool = False,
 7172        tokens: t.Collection[TokenType] | None = None,
 7173        anonymous_func: bool = False,
 7174    ) -> exp.Expr | None:
 7175        after_dot = (
 7176            self.SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES and self._prev.token_type == TokenType.DOT
 7177        )
 7178
 7179        if anonymous_func:
 7180            field = (
 7181                self._parse_function(anonymous=anonymous_func, any_token=any_token)
 7182                or self._parse_primary()
 7183            )
 7184        else:
 7185            field = self._parse_primary() or self._parse_function(
 7186                anonymous=anonymous_func, any_token=any_token
 7187            )
 7188
 7189        field = field or self._parse_id_var(any_token=any_token, tokens=tokens)
 7190
 7191        if after_dot and isinstance(field, exp.Literal) and field.is_number:
 7192            name = field.name
 7193            if self._is_connected() and self._parse_var(any_token=True):
 7194                name += self._prev.text
 7195
 7196            field = exp.Identifier(this=name, quoted=True).update_positions(field)
 7197
 7198        return field
 7199
 7200    def _parse_function(
 7201        self,
 7202        functions: dict[str, t.Callable] | None = None,
 7203        anonymous: bool = False,
 7204        optional_parens: bool = True,
 7205        any_token: bool = False,
 7206    ) -> exp.Expr | None:
 7207        # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this)
 7208        # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences
 7209        fn_syntax = False
 7210        if (
 7211            self._match(TokenType.L_BRACE, advance=False)
 7212            and self._next
 7213            and self._next.text.upper() == "FN"
 7214        ):
 7215            self._advance(2)
 7216            fn_syntax = True
 7217
 7218        func = self._parse_function_call(
 7219            functions=functions,
 7220            anonymous=anonymous,
 7221            optional_parens=optional_parens,
 7222            any_token=any_token,
 7223        )
 7224
 7225        if fn_syntax:
 7226            self._match(TokenType.R_BRACE)
 7227
 7228        return func
 7229
 7230    def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]:
 7231        return self._parse_csv(lambda: self._parse_lambda(alias=alias))
 7232
 7233    def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren:
 7234        args = self._parse_function_args(alias=False)
 7235        if not args:
 7236            self.raise_error("Expected at least one argument")
 7237
 7238        # Wrapped so the connector keeps its precedence in the parent context
 7239        return exp.Paren(this=connector(*args, copy=False))
 7240
 7241    def _parse_function_call(
 7242        self,
 7243        functions: dict[str, t.Callable] | None = None,
 7244        anonymous: bool = False,
 7245        optional_parens: bool = True,
 7246        any_token: bool = False,
 7247    ) -> exp.Expr | None:
 7248        if not self._curr:
 7249            return None
 7250
 7251        comments = self._curr.comments
 7252        prev = self._prev
 7253        token = self._curr
 7254        token_type = self._curr.token_type
 7255        this: str | exp.Expr = self._curr.text
 7256        upper = self._curr.text.upper()
 7257
 7258        after_dot = prev.token_type == TokenType.DOT
 7259        parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper)
 7260        if (
 7261            optional_parens
 7262            and parser
 7263            and token_type not in self.INVALID_FUNC_NAME_TOKENS
 7264            and not after_dot
 7265        ):
 7266            self._advance()
 7267            return self._parse_window(parser(self))
 7268
 7269        if self._next.token_type != TokenType.L_PAREN:
 7270            if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot:
 7271                self._advance()
 7272                return self.expression(self.NO_PAREN_FUNCTIONS[token_type]())
 7273
 7274            return None
 7275
 7276        if any_token:
 7277            if token_type in self.RESERVED_TOKENS:
 7278                return None
 7279        elif token_type not in self.FUNC_TOKENS:
 7280            return None
 7281
 7282        self._advance(2)
 7283
 7284        parser = self.FUNCTION_PARSERS.get(upper)
 7285        if parser and not anonymous:
 7286            result = parser(self)
 7287        else:
 7288            subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type)
 7289
 7290            if subquery_predicate:
 7291                expr = None
 7292                if self._curr.token_type in self.SUBQUERY_TOKENS:
 7293                    expr = self._parse_select()
 7294                    self._match_r_paren()
 7295                elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE):
 7296                    # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like
 7297                    # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren
 7298                    self._advance(-1)
 7299                    expr = self._parse_bitwise()
 7300
 7301                if expr:
 7302                    return self.expression(subquery_predicate(this=expr), comments=comments)
 7303
 7304            if functions is None:
 7305                functions = self.FUNCTIONS
 7306
 7307            function = functions.get(upper)
 7308            known_function = function and not anonymous
 7309
 7310            alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS
 7311            args = self._parse_function_args(alias)
 7312
 7313            post_func_comments = self._curr.comments if self._curr else None
 7314            if known_function and post_func_comments:
 7315                # If the user-inputted comment "/* sqlglot.anonymous */" is following the function
 7316                # call we'll construct it as exp.Anonymous, even if it's "known"
 7317                if any(
 7318                    comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS)
 7319                    for comment in post_func_comments
 7320                ):
 7321                    known_function = False
 7322
 7323            if alias and known_function:
 7324                args = self._kv_to_prop_eq(args)
 7325
 7326            if known_function:
 7327                func_builder = t.cast(t.Callable, function)
 7328
 7329                # mypyc compiled functions don't have __code__, so we use
 7330                # try/except to check if func_builder accepts 'dialect'.
 7331                try:
 7332                    func = func_builder(args)
 7333                except TypeError:
 7334                    func = func_builder(args, dialect=self.dialect)
 7335
 7336                func = self.validate_expression(func, args)
 7337                if self.dialect.PRESERVE_ORIGINAL_NAMES:
 7338                    func.meta["name"] = this
 7339
 7340                result = func
 7341            else:
 7342                if token_type == TokenType.IDENTIFIER:
 7343                    this = exp.Identifier(this=this, quoted=True).update_positions(token)
 7344
 7345                result = self.expression(exp.Anonymous(this=this, expressions=args))
 7346
 7347            result = result.update_positions(token)
 7348
 7349        if isinstance(result, exp.Expr):
 7350            result.add_comments(comments)
 7351
 7352        if parser:
 7353            self._match(TokenType.R_PAREN, expression=result)
 7354        else:
 7355            self._match_r_paren(result)
 7356        return self._parse_window(result)
 7357
 7358    def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr:
 7359        return expression
 7360
 7361    def _kv_to_prop_eq(
 7362        self, expressions: list[exp.Expr], parse_map: bool = False
 7363    ) -> list[exp.Expr]:
 7364        transformed = []
 7365
 7366        for index, e in enumerate(expressions):
 7367            if isinstance(e, self.KEY_VALUE_DEFINITIONS):
 7368                if isinstance(e, exp.Alias):
 7369                    e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this))
 7370
 7371                if not isinstance(e, exp.PropertyEQ):
 7372                    e = self.expression(
 7373                        exp.PropertyEQ(
 7374                            this=e.this if parse_map else exp.to_identifier(e.this.name),
 7375                            expression=e.expression,
 7376                        )
 7377                    )
 7378
 7379                if isinstance(e.this, exp.Column):
 7380                    e.this.replace(e.this.this)
 7381            else:
 7382                e = self._to_prop_eq(e, index)
 7383
 7384            transformed.append(e)
 7385
 7386        return transformed
 7387
 7388    def _parse_function_properties(self) -> exp.Properties | None:
 7389        # Skip the generic `key = value` fallback in _parse_property since this
 7390        # runs post-AS where a function body like `name = expr` can be misread
 7391        # as a property.
 7392        properties = []
 7393        while True:
 7394            if self._match_texts(self.PROPERTY_PARSERS):
 7395                keyword = self._prev.text.upper()
 7396                prop = self.PROPERTY_PARSERS[keyword](self)
 7397            elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS):
 7398                keyword = self._prev.text.upper()
 7399                prop = self.PROPERTY_PARSERS[keyword](self, default=True)
 7400            else:
 7401                break
 7402            if not prop:
 7403                self.raise_error(f"Failed to parse property '{keyword}'")
 7404                break
 7405            for p in ensure_list(prop):
 7406                properties.append(p)
 7407
 7408        return self.expression(exp.Properties(expressions=properties)) if properties else None
 7409
 7410    def _parse_user_defined_function_expression(self) -> exp.Expr | None:
 7411        return self._parse_statement()
 7412
 7413    def _parse_function_parameter(self) -> exp.Expr | None:
 7414        return self._parse_column_def(this=self._parse_id_var(), computed_column=False)
 7415
 7416    def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None:
 7417        this = self._parse_table_parts(schema=True)
 7418
 7419        if not self._match(TokenType.L_PAREN):
 7420            return this
 7421
 7422        expressions = self._parse_csv(self._parse_function_parameter)
 7423        self._match_r_paren()
 7424        return self.expression(
 7425            exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True)
 7426        )
 7427
 7428    def _parse_macro_overloads(
 7429        self,
 7430        this: exp.UserDefinedFunction,
 7431        first_body: exp.Expr,
 7432        first_is_table: bool = False,
 7433    ) -> exp.MacroOverloads:
 7434        overloads = [
 7435            self.expression(
 7436                exp.MacroOverload(
 7437                    this=first_body,
 7438                    expressions=this.expressions or None,
 7439                    is_table=first_is_table,
 7440                )
 7441            )
 7442        ]
 7443        this.set("expressions", None)
 7444        this.set("wrapped", False)
 7445
 7446        while self._match(TokenType.COMMA):
 7447            if not self._match(TokenType.L_PAREN):
 7448                break
 7449
 7450            params = self._parse_csv(self._parse_function_parameter)
 7451            self._match_r_paren()
 7452
 7453            if not self._match(TokenType.ALIAS):
 7454                break
 7455
 7456            is_table = self._match(TokenType.TABLE)
 7457            body = self._parse_expression()
 7458            macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table)
 7459            overloads.append(self.expression(macro))
 7460
 7461        return self.expression(exp.MacroOverloads(expressions=overloads))
 7462
 7463    def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier:
 7464        literal = self._parse_primary()
 7465        if literal:
 7466            return self.expression(exp.Introducer(this=token.text, expression=literal), token)
 7467
 7468        return self._identifier_expression(token)
 7469
 7470    def _parse_session_parameter(self) -> exp.SessionParameter:
 7471        kind = None
 7472        this = self._parse_id_var() or self._parse_primary()
 7473
 7474        if this and self._match(TokenType.DOT):
 7475            kind = this.name
 7476            this = self._parse_var() or self._parse_primary()
 7477
 7478        return self.expression(exp.SessionParameter(this=this, kind=kind))
 7479
 7480    def _parse_lambda_arg(self) -> exp.Expr | None:
 7481        return self._parse_id_var()
 7482
 7483    def _parse_lambda(self, alias: bool = False) -> exp.Expr | None:
 7484        next_token_type = self._next.token_type
 7485
 7486        # Fast path: simple atom (column, literal, null, bool) followed by , or )
 7487        if (
 7488            next_token_type in self.LAMBDA_ARG_TERMINATORS
 7489            and (atom := self._parse_atom()) is not None
 7490        ):
 7491            return atom
 7492
 7493        index = self._index
 7494
 7495        if self._match(TokenType.L_PAREN):
 7496            expressions = t.cast(
 7497                list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg)
 7498            )
 7499
 7500            if not self._match(TokenType.R_PAREN):
 7501                self._retreat(index)
 7502            elif self._match_set(self.LAMBDAS):
 7503                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7504            else:
 7505                self._retreat(index)
 7506        elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS:
 7507            expressions = [self._parse_lambda_arg()]
 7508
 7509            if self._match_set(self.LAMBDAS):
 7510                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7511
 7512            self._retreat(index)
 7513
 7514        this: exp.Expr | None
 7515
 7516        if self._match(TokenType.DISTINCT):
 7517            this = self.expression(
 7518                exp.Distinct(expressions=self._parse_csv(self._parse_disjunction))
 7519            )
 7520        else:
 7521            self._match(TokenType.ALL)  # ALL is the default/no-op aggregate modifier (SQL-92)
 7522            this = self._parse_select_or_expression(alias=alias)
 7523
 7524        return self._parse_limit(
 7525            self._parse_respect_or_ignore_nulls(
 7526                self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this)))
 7527            )
 7528        )
 7529
 7530    def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7531        index = self._index
 7532        if not self._match(TokenType.L_PAREN):
 7533            return this
 7534
 7535        # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>),
 7536        # expr can be of both types
 7537        if self._match_set(self.SELECT_START_TOKENS):
 7538            self._retreat(index)
 7539            return this
 7540        args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def())
 7541        self._match_r_paren()
 7542        return self.expression(exp.Schema(this=this, expressions=args))
 7543
 7544    def _parse_field_def(self) -> exp.Expr | None:
 7545        return self._parse_column_def(self._parse_field(any_token=True))
 7546
 7547    def _parse_column_def(
 7548        self, this: exp.Expr | None, computed_column: bool = True
 7549    ) -> exp.Expr | None:
 7550        # column defs are not really columns, they're identifiers
 7551        if isinstance(this, exp.Column):
 7552            this = this.this
 7553
 7554        if not computed_column:
 7555            self._match(TokenType.ALIAS)
 7556
 7557        kind = self._parse_types(schema=True)
 7558
 7559        if self._match_text_seq("FOR", "ORDINALITY"):
 7560            return self.expression(exp.ColumnDef(this=this, ordinality=True))
 7561
 7562        constraints: list[exp.Expr] = []
 7563
 7564        if (not kind and self._match(TokenType.ALIAS)) or self._match_texts(
 7565            ("ALIAS", "MATERIALIZED")
 7566        ):
 7567            # Match storage before _parse_types so STORED is not treated as a data type
 7568            # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`).
 7569            persisted = self._prev.text.upper() == "MATERIALIZED"
 7570            expression = self._parse_disjunction()
 7571            if not persisted:
 7572                if self._match_text_seq("PERSISTED"):
 7573                    persisted = True
 7574                elif self._match_texts(("STORED", "VIRTUAL")):
 7575                    persisted = self._prev.text.upper() == "STORED"
 7576            constraint_kind = exp.ComputedColumnConstraint(
 7577                this=expression,
 7578                persisted=persisted,
 7579                data_type=exp.Var(this="AUTO")
 7580                if self._match_text_seq("AUTO")
 7581                else self._parse_types(),
 7582                not_null=self._match_pair(TokenType.NOT, TokenType.NULL),
 7583            )
 7584            constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind)))
 7585        elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False):
 7586            in_out_constraint = self.expression(
 7587                exp.InOutColumnConstraint(
 7588                    input_=self._match(TokenType.IN), output=self._match(TokenType.OUT)
 7589                )
 7590            )
 7591            constraints.append(in_out_constraint)
 7592            kind = self._parse_types()
 7593        elif (
 7594            kind
 7595            and self._match(TokenType.ALIAS, advance=False)
 7596            and (
 7597                not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
 7598                or self._next.token_type == TokenType.L_PAREN
 7599            )
 7600        ):
 7601            self._advance()
 7602            constraints.append(
 7603                self.expression(
 7604                    exp.ColumnConstraint(
 7605                        kind=exp.ComputedColumnConstraint(
 7606                            this=self._parse_disjunction(),
 7607                            persisted=self._match_texts(("STORED", "VIRTUAL"))
 7608                            and self._prev.text.upper() == "STORED",
 7609                        )
 7610                    )
 7611                )
 7612            )
 7613
 7614        while True:
 7615            constraint = self._parse_column_constraint()
 7616            if not constraint:
 7617                break
 7618            constraints.append(constraint)
 7619
 7620        if not kind and not constraints:
 7621            return this
 7622
 7623        position = None
 7624        if self._match_texts(("FIRST", "AFTER")):
 7625            pos = self._prev.text
 7626            position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos))
 7627
 7628        return self.expression(
 7629            exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position)
 7630        )
 7631
 7632    def _parse_auto_increment(
 7633        self,
 7634    ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint:
 7635        start = None
 7636        increment = None
 7637        order = None
 7638
 7639        if self._match(TokenType.L_PAREN, advance=False):
 7640            args = self._parse_wrapped_csv(self._parse_bitwise)
 7641            start = seq_get(args, 0)
 7642            increment = seq_get(args, 1)
 7643
 7644        # The remaining parts form an unordered bag and any of them can be omitted, in which
 7645        # case the engine falls back to its own default, so they're parsed independently.
 7646        while True:
 7647            if self._match_text_seq("START"):
 7648                start = self._parse_bitwise()
 7649            elif self._match_text_seq("INCREMENT"):
 7650                increment = self._parse_bitwise()
 7651            elif self._match_text_seq("ORDER"):
 7652                order = True
 7653            elif self._match_text_seq("NOORDER"):
 7654                order = False
 7655            else:
 7656                break
 7657
 7658        if start or increment or order is not None:
 7659            return exp.GeneratedAsIdentityColumnConstraint(
 7660                start=start, increment=increment, this=False, order=order
 7661            )
 7662
 7663        return exp.AutoIncrementColumnConstraint()
 7664
 7665    def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None:
 7666        if not self._match(TokenType.L_PAREN, advance=False):
 7667            return None
 7668
 7669        return self.expression(
 7670            exp.CheckColumnConstraint(
 7671                this=self._parse_wrapped(self._parse_assignment),
 7672                enforced=self._match_text_seq("ENFORCED"),
 7673            )
 7674        )
 7675
 7676    def _parse_auto_property(self) -> exp.AutoRefreshProperty | None:
 7677        if not self._match_text_seq("REFRESH"):
 7678            self._retreat(self._index - 1)
 7679            return None
 7680        return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True)))
 7681
 7682    def _parse_compress(self) -> exp.CompressColumnConstraint:
 7683        if self._match(TokenType.L_PAREN, advance=False):
 7684            return self.expression(
 7685                exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise))
 7686            )
 7687
 7688        return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise()))
 7689
 7690    def _parse_generated_as_identity(
 7691        self,
 7692    ) -> (
 7693        exp.GeneratedAsIdentityColumnConstraint
 7694        | exp.ComputedColumnConstraint
 7695        | exp.GeneratedAsRowColumnConstraint
 7696    ):
 7697        if self._match_text_seq("BY", "DEFAULT"):
 7698            on_null = self._match_pair(TokenType.ON, TokenType.NULL)
 7699            this = self.expression(
 7700                exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null)
 7701            )
 7702        else:
 7703            self._match_text_seq("ALWAYS")
 7704            this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True))
 7705
 7706        self._match(TokenType.ALIAS)
 7707
 7708        if self._match_text_seq("ROW"):
 7709            start = self._match_text_seq("START")
 7710            if not start:
 7711                self._match(TokenType.END)
 7712            hidden = self._match_text_seq("HIDDEN")
 7713            return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden))
 7714
 7715        identity = self._match_text_seq("IDENTITY")
 7716
 7717        if self._match(TokenType.L_PAREN):
 7718            if self._match_text_seq("START", "WITH"):
 7719                this.set("start", self._parse_bitwise())
 7720            if self._match_text_seq("INCREMENT", "BY"):
 7721                this.set("increment", self._parse_bitwise())
 7722            if self._match_text_seq("MINVALUE"):
 7723                this.set("minvalue", self._parse_bitwise())
 7724            if self._match_text_seq("MAXVALUE"):
 7725                this.set("maxvalue", self._parse_bitwise())
 7726
 7727            if self._match_text_seq("CYCLE"):
 7728                this.set("cycle", True)
 7729            elif self._match_text_seq("NO", "CYCLE"):
 7730                this.set("cycle", False)
 7731
 7732            if not identity:
 7733                this.set("expression", self._parse_range())
 7734            elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False):
 7735                args = self._parse_csv(self._parse_bitwise)
 7736                this.set("start", seq_get(args, 0))
 7737                this.set("increment", seq_get(args, 1))
 7738
 7739            self._match_r_paren()
 7740
 7741        return this
 7742
 7743    def _parse_inline(self) -> exp.InlineLengthColumnConstraint:
 7744        self._match_text_seq("LENGTH")
 7745        return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise()))
 7746
 7747    def _parse_not_constraint(self) -> exp.Expr | None:
 7748        if self._match_text_seq("NULL"):
 7749            return self.expression(exp.NotNullColumnConstraint())
 7750        if self._match_text_seq("CASESPECIFIC"):
 7751            return self.expression(exp.CaseSpecificColumnConstraint(not_=True))
 7752        if self._match_text_seq("FOR", "REPLICATION"):
 7753            return self.expression(exp.NotForReplicationColumnConstraint())
 7754
 7755        # Unconsume the `NOT` token
 7756        self._retreat(self._index - 1)
 7757        return None
 7758
 7759    def _parse_column_constraint(self) -> exp.Expr | None:
 7760        this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None
 7761
 7762        procedure_option_follows = (
 7763            self._match(TokenType.WITH, advance=False)
 7764            and self._next
 7765            and self._next.text.upper() in self.PROCEDURE_OPTIONS
 7766        )
 7767
 7768        if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS):
 7769            constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self)
 7770            if not constraint:
 7771                self._retreat(self._index - 1)
 7772                return None
 7773
 7774            return self.expression(exp.ColumnConstraint(this=this, kind=constraint))
 7775
 7776        if self._match_text_seq("CHARACTER", "SET"):
 7777            return self.expression(
 7778                exp.ColumnConstraint(
 7779                    this=this,
 7780                    kind=self.expression(
 7781                        exp.CharacterSetColumnConstraint(this=self._parse_var_or_string())
 7782                    ),
 7783                )
 7784            )
 7785
 7786        return this
 7787
 7788    def _parse_constraint(self) -> exp.Expr | None:
 7789        if not self._match(TokenType.CONSTRAINT):
 7790            return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS)
 7791
 7792        return self.expression(
 7793            exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints())
 7794        )
 7795
 7796    def _parse_unnamed_constraints(self) -> list[exp.Expr]:
 7797        constraints = []
 7798        while True:
 7799            constraint = self._parse_unnamed_constraint() or self._parse_function()
 7800            if not constraint:
 7801                break
 7802            constraints.append(constraint)
 7803
 7804        return constraints
 7805
 7806    def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None:
 7807        index = self._index
 7808
 7809        if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts(
 7810            constraints or self.CONSTRAINT_PARSERS
 7811        ):
 7812            return None
 7813
 7814        constraint_key = self._prev.text.upper()
 7815        if constraint_key not in self.CONSTRAINT_PARSERS:
 7816            self.raise_error(f"No parser found for schema constraint {constraint_key}.")
 7817
 7818        result = self.CONSTRAINT_PARSERS[constraint_key](self)
 7819        if not result:
 7820            self._retreat(index)
 7821
 7822        return result
 7823
 7824    def _parse_unique_key(self) -> exp.Expr | None:
 7825        if (
 7826            self._curr
 7827            and self._curr.token_type != TokenType.IDENTIFIER
 7828            and self._curr.text.upper() in self.CONSTRAINT_PARSERS
 7829        ):
 7830            return None
 7831        return self._parse_id_var(any_token=False)
 7832
 7833    def _parse_unique(self) -> exp.UniqueColumnConstraint:
 7834        self._match_texts(("KEY", "INDEX"))
 7835        return self.expression(
 7836            exp.UniqueColumnConstraint(
 7837                nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"),
 7838                this=self._parse_schema(self._parse_unique_key()),
 7839                index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text,
 7840                on_conflict=self._parse_on_conflict(),
 7841                options=self._parse_key_constraint_options(),
 7842            )
 7843        )
 7844
 7845    def _parse_key_constraint_options(self) -> list[str]:
 7846        options = []
 7847        while True:
 7848            if not self._curr:
 7849                break
 7850
 7851            if self._match(TokenType.ON):
 7852                action = None
 7853                on = self._advance_any() and self._prev.text
 7854
 7855                if self._match_text_seq("NO", "ACTION"):
 7856                    action = "NO ACTION"
 7857                elif self._match_text_seq("CASCADE"):
 7858                    action = "CASCADE"
 7859                elif self._match_text_seq("RESTRICT"):
 7860                    action = "RESTRICT"
 7861                elif self._match_pair(TokenType.SET, TokenType.NULL):
 7862                    action = "SET NULL"
 7863                elif self._match_pair(TokenType.SET, TokenType.DEFAULT):
 7864                    action = "SET DEFAULT"
 7865                else:
 7866                    self.raise_error("Invalid key constraint")
 7867
 7868                options.append(f"ON {on} {action}")
 7869            else:
 7870                var = self._parse_var_from_options(
 7871                    self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False
 7872                )
 7873                if not var:
 7874                    break
 7875                options.append(var.name)
 7876
 7877        return options
 7878
 7879    def _parse_references(self, match: bool = True) -> exp.Reference | None:
 7880        if match and not self._match(TokenType.REFERENCES):
 7881            return None
 7882
 7883        expressions: list | None = None
 7884        this = self._parse_table(schema=True)
 7885        options = self._parse_key_constraint_options()
 7886        return self.expression(exp.Reference(this=this, expressions=expressions, options=options))
 7887
 7888    def _parse_foreign_key(self) -> exp.ForeignKey:
 7889        expressions = (
 7890            self._parse_wrapped_id_vars()
 7891            if not self._match(TokenType.REFERENCES, advance=False)
 7892            else None
 7893        )
 7894        reference = self._parse_references()
 7895        on_options = {}
 7896
 7897        while self._match(TokenType.ON):
 7898            if not self._match_set((TokenType.DELETE, TokenType.UPDATE)):
 7899                self.raise_error("Expected DELETE or UPDATE")
 7900
 7901            kind = self._prev.text.lower()
 7902
 7903            if self._match_text_seq("NO", "ACTION"):
 7904                action = "NO ACTION"
 7905            elif self._match(TokenType.SET):
 7906                self._match_set((TokenType.NULL, TokenType.DEFAULT))
 7907                action = "SET " + self._prev.text.upper()
 7908            else:
 7909                self._advance()
 7910                action = self._prev.text.upper()
 7911
 7912            on_options[kind] = action
 7913
 7914        return self.expression(
 7915            exp.ForeignKey(
 7916                expressions=expressions,
 7917                reference=reference,
 7918                options=self._parse_key_constraint_options(),
 7919                **on_options,
 7920            )
 7921        )
 7922
 7923    def _parse_primary_key_part(self) -> exp.Expr | None:
 7924        return self._parse_field()
 7925
 7926    def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None:
 7927        if not self._match_text_seq("FOR", "SYSTEM_TIME"):
 7928            self._retreat(self._index - 1)
 7929            return None
 7930
 7931        id_vars = self._parse_wrapped_id_vars()
 7932        return self.expression(
 7933            exp.PeriodForSystemTimeConstraint(
 7934                this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1)
 7935            )
 7936        )
 7937
 7938    def _parse_primary_key(
 7939        self,
 7940        wrapped_optional: bool = False,
 7941        in_props: bool = False,
 7942        named_primary_key: bool = False,
 7943    ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:
 7944        desc = (
 7945            self._prev.token_type == TokenType.DESC
 7946            if self._match_set((TokenType.ASC, TokenType.DESC))
 7947            else None
 7948        )
 7949
 7950        this = None
 7951        if (
 7952            named_primary_key
 7953            and self._curr.text.upper() not in self.CONSTRAINT_PARSERS
 7954            and self._next
 7955            and self._next.token_type == TokenType.L_PAREN
 7956        ):
 7957            this = self._parse_id_var()
 7958
 7959        if not in_props and not self._match(TokenType.L_PAREN, advance=False):
 7960            return self.expression(
 7961                exp.PrimaryKeyColumnConstraint(
 7962                    desc=desc, options=self._parse_key_constraint_options()
 7963                )
 7964            )
 7965
 7966        expressions = self._parse_wrapped_csv(
 7967            self._parse_primary_key_part, optional=wrapped_optional
 7968        )
 7969
 7970        return self.expression(
 7971            exp.PrimaryKey(
 7972                this=this,
 7973                expressions=expressions,
 7974                include=self._parse_index_params(),
 7975                options=self._parse_key_constraint_options(),
 7976            )
 7977        )
 7978
 7979    def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None:
 7980        return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True))
 7981
 7982    def _parse_odbc_datetime_literal(self) -> exp.Expr:
 7983        """
 7984        Parses a datetime column in ODBC format. We parse the column into the corresponding
 7985        types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the
 7986        same as we did for `DATE('yyyy-mm-dd')`.
 7987
 7988        Reference:
 7989        https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals
 7990        """
 7991        self._match(TokenType.VAR)
 7992        exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()]
 7993        expression = self.expression(exp_class(this=self._parse_string()))
 7994        if not self._match(TokenType.R_BRACE):
 7995            self.raise_error("Expected }")
 7996        return expression
 7997
 7998    def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7999        if not self._match_set(self.BRACKETS):
 8000            return this
 8001
 8002        if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS:
 8003            map_token = seq_get(self._tokens, self._index - 2)
 8004            parse_map = map_token is not None and map_token.text.upper() == "MAP"
 8005        else:
 8006            parse_map = False
 8007
 8008        bracket_kind = self._prev.token_type
 8009        if (
 8010            bracket_kind == TokenType.L_BRACE
 8011            and self._curr
 8012            and self._curr.token_type == TokenType.VAR
 8013            and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS
 8014        ):
 8015            return self._parse_odbc_datetime_literal()
 8016
 8017        expressions = self._parse_csv(
 8018            lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE)
 8019        )
 8020
 8021        if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET):
 8022            self.raise_error("Expected ]")
 8023        elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE):
 8024            self.raise_error("Expected }")
 8025
 8026        # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs
 8027        if bracket_kind == TokenType.L_BRACE:
 8028            this = self.expression(
 8029                exp.Struct(
 8030                    expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map)
 8031                )
 8032            )
 8033        elif not this:
 8034            this = build_array_constructor(
 8035                exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect
 8036            )
 8037        else:
 8038            constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper())
 8039            if constructor_type:
 8040                return build_array_constructor(
 8041                    constructor_type,
 8042                    args=expressions,
 8043                    bracket_kind=bracket_kind,
 8044                    dialect=self.dialect,
 8045                )
 8046
 8047            expressions = apply_index_offset(
 8048                this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect
 8049            )
 8050            this = self.expression(
 8051                exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments()
 8052            )
 8053
 8054        self._add_comments(this)
 8055        return self._parse_bracket(this)
 8056
 8057    def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None:
 8058        if not self._match(TokenType.COLON):
 8059            return this
 8060
 8061        if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False):
 8062            self._advance()
 8063            end: exp.Expr | None = -exp.Literal.number("1")
 8064        else:
 8065            end = self._parse_assignment()
 8066        step = self._parse_unary() if self._match(TokenType.COLON) else None
 8067        return self.expression(exp.Slice(this=this, expression=end, step=step))
 8068
 8069    def _parse_case(self) -> exp.Expr | None:
 8070        if self._match(TokenType.DOT, advance=False):
 8071            # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake
 8072            self._retreat(self._index - 1)
 8073            return None
 8074
 8075        ifs = []
 8076        default = None
 8077
 8078        comments = self._prev_comments
 8079        expression = self._parse_disjunction()
 8080
 8081        while self._match(TokenType.WHEN):
 8082            this = self._parse_disjunction()
 8083            self._match(TokenType.THEN)
 8084            then = self._parse_disjunction()
 8085            ifs.append(self.expression(exp.If(this=this, true=then)))
 8086
 8087        if self._match(TokenType.ELSE):
 8088            default = self._parse_disjunction()
 8089
 8090        if not self._match(TokenType.END):
 8091            if isinstance(default, exp.Interval) and default.this.sql().upper() == "END":
 8092                default = exp.column("interval")
 8093            else:
 8094                self.raise_error("Expected END after CASE", self._prev)
 8095
 8096        return self.expression(
 8097            exp.Case(this=expression, ifs=ifs, default=default), comments=comments
 8098        )
 8099
 8100    def _parse_if(self) -> exp.Expr | None:
 8101        if self._match(TokenType.L_PAREN):
 8102            args = self._parse_csv(
 8103                lambda: self._parse_alias(self._parse_assignment(), explicit=True)
 8104            )
 8105            this = self.validate_expression(exp.If.from_arg_list(args), args)
 8106            self._match_r_paren()
 8107        else:
 8108            index = self._index - 1
 8109
 8110            if self.NO_PAREN_IF_COMMANDS and index == 0:
 8111                return self._parse_as_command(self._prev)
 8112
 8113            condition = self._parse_disjunction()
 8114
 8115            if not condition:
 8116                self._retreat(index)
 8117                return None
 8118
 8119            self._match(TokenType.THEN)
 8120            true = self._parse_disjunction()
 8121            false = self._parse_disjunction() if self._match(TokenType.ELSE) else None
 8122            self._match(TokenType.END)
 8123            this = self.expression(exp.If(this=condition, true=true, false=false))
 8124
 8125        return this
 8126
 8127    def _parse_next_value_for(self) -> exp.Expr | None:
 8128        if not self._match_text_seq("VALUE", "FOR"):
 8129            self._retreat(self._index - 1)
 8130            return None
 8131
 8132        return self.expression(
 8133            exp.NextValueFor(
 8134                this=self._parse_column(),
 8135                order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order),
 8136            )
 8137        )
 8138
 8139    def _parse_extract(self) -> exp.Extract | exp.Anonymous:
 8140        this = self._parse_function() or self._parse_var_or_string(upper=True)
 8141
 8142        if self._match(TokenType.FROM):
 8143            return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8144
 8145        if not self._match(TokenType.COMMA):
 8146            self.raise_error("Expected FROM or comma after EXTRACT", self._prev)
 8147
 8148        return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8149
 8150    def _parse_gap_fill(self) -> exp.GapFill:
 8151        self._match(TokenType.TABLE)
 8152        this = self._parse_table()
 8153
 8154        self._match(TokenType.COMMA)
 8155        args = [this, *self._parse_csv(self._parse_lambda)]
 8156
 8157        gap_fill = exp.GapFill.from_arg_list(args)
 8158        return self.validate_expression(gap_fill, args)
 8159
 8160    def _parse_char(self) -> exp.Chr:
 8161        return self.expression(
 8162            exp.Chr(
 8163                expressions=self._parse_csv(self._parse_assignment),
 8164                charset=self._match(TokenType.USING) and self._parse_charset_name(),
 8165            )
 8166        )
 8167
 8168    def _parse_charset_name(self) -> exp.Expr | None:
 8169        """
 8170        Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting
 8171        for specific name shapes override this.
 8172        """
 8173        return self._parse_var(
 8174            tokens={TokenType.BINARY, TokenType.IDENTIFIER},
 8175        )
 8176
 8177    def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr:
 8178        this = self._parse_assignment()
 8179
 8180        if not self._match(TokenType.ALIAS):
 8181            if self._match(TokenType.COMMA):
 8182                return self.expression(exp.CastToStrType(this=this, to=self._parse_string()))
 8183
 8184            self.raise_error("Expected AS after CAST")
 8185
 8186        fmt = None
 8187        to = self._parse_types(with_collation=True)
 8188
 8189        default = None
 8190        if self._match(TokenType.DEFAULT):
 8191            default = self._parse_bitwise()
 8192            self._match_text_seq("ON", "CONVERSION", "ERROR")
 8193
 8194        if self._match_set((TokenType.FORMAT, TokenType.COMMA)):
 8195            fmt_string = self._parse_wrapped(self._parse_string, optional=True)
 8196            fmt = self._parse_at_time_zone(fmt_string)
 8197
 8198            if not to:
 8199                to = exp.DType.UNKNOWN.into_expr()
 8200            if to.this in exp.DataType.TEMPORAL_TYPES:
 8201                this = self.expression(
 8202                    (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)(
 8203                        this=this,
 8204                        format=exp.Literal.string(
 8205                            format_time(
 8206                                fmt_string.this if fmt_string else "",
 8207                                self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING,
 8208                                self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE,
 8209                            )
 8210                        ),
 8211                        safe=safe,
 8212                    )
 8213                )
 8214
 8215                if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime):
 8216                    this.set("zone", fmt.args["zone"])
 8217                return this
 8218        elif not to:
 8219            self.raise_error("Expected TYPE after CAST")
 8220        elif isinstance(to, exp.Identifier):
 8221            to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True)
 8222        elif to.this == exp.DType.CHAR and (
 8223            self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")
 8224        ):
 8225            to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string())
 8226
 8227        return self.build_cast(
 8228            strict=strict,
 8229            this=this,
 8230            to=to,
 8231            format=fmt,
 8232            safe=safe,
 8233            action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False),
 8234            default=default,
 8235        )
 8236
 8237    def _parse_string_agg(self) -> exp.GroupConcat:
 8238        if self._match(TokenType.DISTINCT):
 8239            args: list[exp.Expr | None] = [
 8240                self.expression(exp.Distinct(expressions=[self._parse_disjunction()]))
 8241            ]
 8242            if self._match(TokenType.COMMA):
 8243                args.extend(self._parse_csv(self._parse_disjunction))
 8244        else:
 8245            args = self._parse_csv(self._parse_disjunction)  # type: ignore
 8246
 8247        if self._match_text_seq("ON", "OVERFLOW"):
 8248            # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior])
 8249            if self._match_text_seq("ERROR"):
 8250                on_overflow: exp.Expr | None = exp.var("ERROR")
 8251            else:
 8252                self._match_text_seq("TRUNCATE")
 8253                on_overflow = self.expression(
 8254                    exp.OverflowTruncateBehavior(
 8255                        this=self._parse_string(),
 8256                        with_count=(
 8257                            self._match_text_seq("WITH", "COUNT")
 8258                            or not self._match_text_seq("WITHOUT", "COUNT")
 8259                        ),
 8260                    )
 8261                )
 8262        else:
 8263            on_overflow = None
 8264
 8265        index = self._index
 8266        if not self._match(TokenType.R_PAREN) and args:
 8267            # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]])
 8268            # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n])
 8269            # The order is parsed through `this` as a canonicalization for WITHIN GROUPs
 8270            args[0] = self._parse_limit(this=self._parse_order(this=args[0]))
 8271            return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1)))
 8272
 8273        # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]).
 8274        # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that
 8275        # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them.
 8276        if not self._match_text_seq("WITHIN", "GROUP"):
 8277            self._retreat(index)
 8278            return self.validate_expression(exp.GroupConcat.from_arg_list(args), args)
 8279
 8280        # The corresponding match_r_paren will be called in parse_function (caller)
 8281        self._match_l_paren()
 8282
 8283        return self.expression(
 8284            exp.GroupConcat(
 8285                this=self._parse_order(this=seq_get(args, 0)),
 8286                separator=seq_get(args, 1),
 8287                on_overflow=on_overflow,
 8288            )
 8289        )
 8290
 8291    def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None:
 8292        this = self._parse_bitwise()
 8293
 8294        if self._match(TokenType.USING):
 8295            to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name())
 8296        elif self._match(TokenType.COMMA):
 8297            to = self._parse_types()
 8298        else:
 8299            to = None
 8300
 8301        return self.build_cast(strict=strict, this=this, to=to, safe=safe)
 8302
 8303    def _parse_xml_element(self) -> exp.XMLElement:
 8304        if self._match_text_seq("EVALNAME"):
 8305            evalname = True
 8306            this = self._parse_bitwise()
 8307        else:
 8308            evalname = None
 8309            self._match_text_seq("NAME")
 8310            this = self._parse_id_var()
 8311
 8312        return self.expression(
 8313            exp.XMLElement(
 8314                this=this,
 8315                expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise),
 8316                evalname=evalname,
 8317            )
 8318        )
 8319
 8320    def _parse_xml_table(self) -> exp.XMLTable:
 8321        namespaces = None
 8322        passing = None
 8323        columns = None
 8324
 8325        if self._match_text_seq("XMLNAMESPACES", "("):
 8326            namespaces = self._parse_xml_namespace()
 8327            self._match_text_seq(")", ",")
 8328
 8329        this = self._parse_string()
 8330
 8331        if self._match_text_seq("PASSING"):
 8332            # The BY VALUE keywords are optional and are provided for semantic clarity
 8333            self._match_text_seq("BY", "VALUE")
 8334            passing = self._parse_csv(self._parse_column)
 8335
 8336        by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF")
 8337
 8338        if self._match_text_seq("COLUMNS"):
 8339            columns = self._parse_csv(self._parse_field_def)
 8340
 8341        return self.expression(
 8342            exp.XMLTable(
 8343                this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref
 8344            )
 8345        )
 8346
 8347    def _parse_xml_namespace(self) -> list[exp.XMLNamespace]:
 8348        namespaces = []
 8349
 8350        while True:
 8351            if self._match(TokenType.DEFAULT):
 8352                uri = self._parse_string()
 8353            else:
 8354                uri = self._parse_alias(self._parse_string())
 8355            namespaces.append(self.expression(exp.XMLNamespace(this=uri)))
 8356            if not self._match(TokenType.COMMA):
 8357                break
 8358
 8359        return namespaces
 8360
 8361    def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None:
 8362        args = self._parse_csv(self._parse_disjunction)
 8363
 8364        if len(args) < 3:
 8365            return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1)))
 8366
 8367        return self.expression(exp.DecodeCase(expressions=args))
 8368
 8369    def _parse_json_key_value(self) -> exp.JSONKeyValue | None:
 8370        self._match_text_seq("KEY")
 8371        key = self._parse_column()
 8372        self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS)
 8373        self._match_text_seq("VALUE")
 8374        value = self._parse_bitwise()
 8375
 8376        if not key and not value:
 8377            return None
 8378        return self.expression(exp.JSONKeyValue(this=key, expression=value))
 8379
 8380    def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None:
 8381        if not this or not self._match_text_seq("FORMAT", "JSON"):
 8382            return this
 8383
 8384        return self.expression(exp.FormatJson(this=this))
 8385
 8386    def _parse_on_condition(self) -> exp.OnCondition | None:
 8387        # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS)
 8388        if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR:
 8389            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8390            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8391        else:
 8392            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8393            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8394
 8395        null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS)
 8396
 8397        if not empty and not error and not null:
 8398            return None
 8399
 8400        return self.expression(exp.OnCondition(empty=empty, error=error, null=null))
 8401
 8402    def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None:
 8403        # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL)
 8404        for value in values:
 8405            if self._match_text_seq(value, "ON", on):
 8406                return f"{value} ON {on}"
 8407
 8408        index = self._index
 8409        if self._match(TokenType.DEFAULT):
 8410            default_value = self._parse_bitwise()
 8411            if self._match_text_seq("ON", on):
 8412                return default_value
 8413
 8414            self._retreat(index)
 8415
 8416        return None
 8417
 8418    @t.overload
 8419    def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ...
 8420
 8421    @t.overload
 8422    def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ...
 8423
 8424    def _parse_json_object(self, agg=False):
 8425        star = self._parse_star()
 8426        expressions = (
 8427            [star]
 8428            if star
 8429            else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value()))
 8430        )
 8431        null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT")
 8432
 8433        unique_keys = None
 8434        if self._match_text_seq("WITH", "UNIQUE"):
 8435            unique_keys = True
 8436        elif self._match_text_seq("WITHOUT", "UNIQUE"):
 8437            unique_keys = False
 8438
 8439        self._match_text_seq("KEYS")
 8440
 8441        return_type = self._match_text_seq("RETURNING") and self._parse_format_json(
 8442            self._parse_type()
 8443        )
 8444        encoding = self._match_text_seq("ENCODING") and self._parse_var()
 8445
 8446        return self.expression(
 8447            (exp.JSONObjectAgg if agg else exp.JSONObject)(
 8448                expressions=expressions,
 8449                null_handling=null_handling,
 8450                unique_keys=unique_keys,
 8451                return_type=return_type,
 8452                encoding=encoding,
 8453            )
 8454        )
 8455
 8456    # Note: this is currently incomplete; it only implements the "JSON_value_column" part
 8457    def _parse_json_column_def(self) -> exp.JSONColumnDef:
 8458        if not self._match_text_seq("NESTED"):
 8459            this = self._parse_id_var()
 8460            ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY)
 8461            kind = self._parse_types(allow_identifiers=False)
 8462            nested = None
 8463        else:
 8464            this = None
 8465            ordinality = None
 8466            kind = None
 8467            nested = True
 8468
 8469        format_json = self._match_text_seq("FORMAT", "JSON")
 8470        path = self._match_text_seq("PATH") and self._parse_string()
 8471        nested_schema = nested and self._parse_json_schema()
 8472
 8473        return self.expression(
 8474            exp.JSONColumnDef(
 8475                this=this,
 8476                kind=kind,
 8477                path=path,
 8478                nested_schema=nested_schema,
 8479                ordinality=ordinality,
 8480                format_json=format_json,
 8481            )
 8482        )
 8483
 8484    def _parse_json_schema(self) -> exp.JSONSchema:
 8485        self._match_text_seq("COLUMNS")
 8486        return self.expression(
 8487            exp.JSONSchema(
 8488                expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True)
 8489            )
 8490        )
 8491
 8492    def _parse_json_table(self) -> exp.JSONTable:
 8493        this = self._parse_format_json(self._parse_bitwise())
 8494        path = self._match(TokenType.COMMA) and self._parse_string()
 8495        error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL")
 8496        empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL")
 8497        schema = self._parse_json_schema()
 8498
 8499        return exp.JSONTable(
 8500            this=this,
 8501            schema=schema,
 8502            path=path,
 8503            error_handling=error_handling,
 8504            empty_handling=empty_handling,
 8505        )
 8506
 8507    def _parse_match_against(self) -> exp.MatchAgainst:
 8508        if self._match_text_seq("TABLE"):
 8509            # parse SingleStore MATCH(TABLE ...) syntax
 8510            # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/
 8511            expressions = []
 8512            table = self._parse_table()
 8513            if table:
 8514                expressions = [table]
 8515        else:
 8516            expressions = self._parse_csv(self._parse_column)
 8517
 8518        self._match_text_seq(")", "AGAINST", "(")
 8519
 8520        this = self._parse_string()
 8521
 8522        if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"):
 8523            modifier = "IN NATURAL LANGUAGE MODE"
 8524            if self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8525                modifier = f"{modifier} WITH QUERY EXPANSION"
 8526        elif self._match_text_seq("IN", "BOOLEAN", "MODE"):
 8527            modifier = "IN BOOLEAN MODE"
 8528        elif self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8529            modifier = "WITH QUERY EXPANSION"
 8530        else:
 8531            modifier = None
 8532
 8533        return self.expression(
 8534            exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier)
 8535        )
 8536
 8537    # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16
 8538    def _parse_open_json(self) -> exp.OpenJSON:
 8539        this = self._parse_bitwise()
 8540        path = self._match(TokenType.COMMA) and self._parse_string()
 8541
 8542        def _parse_open_json_column_def() -> exp.OpenJSONColumnDef:
 8543            this = self._parse_field(any_token=True)
 8544            kind = self._parse_types()
 8545            path = self._parse_string()
 8546            as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON)
 8547
 8548            return self.expression(
 8549                exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json)
 8550            )
 8551
 8552        expressions = None
 8553        if self._match_pair(TokenType.R_PAREN, TokenType.WITH):
 8554            self._match_l_paren()
 8555            expressions = self._parse_csv(_parse_open_json_column_def)
 8556
 8557        return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions))
 8558
 8559    def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition:
 8560        args = self._parse_csv(self._parse_bitwise)
 8561
 8562        if self._match(TokenType.IN):
 8563            return self.expression(
 8564                exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0))
 8565            )
 8566
 8567        if haystack_first:
 8568            haystack = seq_get(args, 0)
 8569            needle = seq_get(args, 1)
 8570        else:
 8571            haystack = seq_get(args, 1)
 8572            needle = seq_get(args, 0)
 8573
 8574        return self.expression(
 8575            exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2))
 8576        )
 8577
 8578    def _parse_join_hint(self, func_name: str) -> exp.JoinHint:
 8579        args = self._parse_csv(self._parse_table)
 8580        return exp.JoinHint(this=func_name.upper(), expressions=args)
 8581
 8582    def _parse_substring(self) -> exp.Substring:
 8583        # Postgres supports the form: substring(string [from int] [for int])
 8584        # (despite being undocumented, the reverse order also works)
 8585        # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6
 8586
 8587        args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise))
 8588
 8589        start, length = None, None
 8590
 8591        while self._curr:
 8592            if self._match(TokenType.FROM):
 8593                start = self._parse_bitwise()
 8594            elif self._match(TokenType.FOR):
 8595                if not start:
 8596                    start = exp.Literal.number(1)
 8597                length = self._parse_bitwise()
 8598            else:
 8599                break
 8600
 8601        if start:
 8602            args.append(start)
 8603        if length:
 8604            args.append(length)
 8605
 8606        return self.validate_expression(exp.Substring.from_arg_list(args), args)
 8607
 8608    def _parse_trim(self) -> exp.Trim:
 8609        # https://www.w3resource.com/sql/character-functions/trim.php
 8610        # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html
 8611
 8612        position = None
 8613        collation = None
 8614        expression = None
 8615
 8616        if self._match_texts(self.TRIM_TYPES):
 8617            position = self._prev.text.upper()
 8618
 8619        this = self._parse_bitwise()
 8620        if self._match_set((TokenType.FROM, TokenType.COMMA)):
 8621            invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST
 8622            expression = self._parse_bitwise()
 8623
 8624            if invert_order:
 8625                this, expression = expression, this
 8626
 8627        if self._match(TokenType.COLLATE):
 8628            collation = self._parse_bitwise()
 8629
 8630        return self.expression(
 8631            exp.Trim(this=this, position=position, expression=expression, collation=collation)
 8632        )
 8633
 8634    def _parse_window_clause(self) -> list[exp.Expr] | None:
 8635        return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None
 8636
 8637    def _parse_named_window(self) -> exp.Expr | None:
 8638        return self._parse_window(self._parse_id_var(), alias=True)
 8639
 8640    def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None:
 8641        if self._curr.token_type == TokenType.VAR:
 8642            if self._match_text_seq("IGNORE", "NULLS"):
 8643                return self.expression(exp.IgnoreNulls(this=this))
 8644            if self._match_text_seq("RESPECT", "NULLS"):
 8645                return self.expression(exp.RespectNulls(this=this))
 8646        return this
 8647
 8648    def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None:
 8649        if self._match(TokenType.HAVING):
 8650            self._match_texts(("MAX", "MIN"))
 8651            max = self._prev.text.upper() != "MIN"
 8652            return self.expression(
 8653                exp.HavingMax(this=this, expression=self._parse_column(), max=max)
 8654            )
 8655
 8656        return this
 8657
 8658    def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None:
 8659        func = this
 8660        comments = func.comments if isinstance(func, exp.Expr) else None
 8661
 8662        # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/nth_value.html
 8663        if self.SUPPORTS_NTH_VALUE_FROM_MODIFIER and isinstance(this, exp.NthValue):
 8664            if self._match_text_seq("FROM", "FIRST"):
 8665                this.set("from_first", True)
 8666            elif self._match_text_seq("FROM", "LAST"):
 8667                this.set("from_first", False)
 8668
 8669        # T-SQL allows the OVER (...) syntax after WITHIN GROUP.
 8670        # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16
 8671        if self._match_text_seq("WITHIN", "GROUP"):
 8672            order = self._parse_wrapped(self._parse_order)
 8673            this = self.expression(exp.WithinGroup(this=this, expression=order))
 8674
 8675        if self._match_pair(TokenType.FILTER, TokenType.L_PAREN):
 8676            self._match(TokenType.WHERE)
 8677            this = self.expression(
 8678                exp.Filter(this=this, expression=self._parse_where(skip_where_token=True))
 8679            )
 8680            self._match_r_paren()
 8681
 8682        # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER
 8683        # Some dialects choose to implement and some do not.
 8684        # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html
 8685
 8686        # There is some code above in _parse_lambda that handles
 8687        #   SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ...
 8688
 8689        # The below changes handle
 8690        #   SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ...
 8691
 8692        # Oracle allows both formats
 8693        #   (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html)
 8694        #   and Snowflake chose to do the same for familiarity
 8695        #   https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes
 8696        if isinstance(this, exp.AggFunc):
 8697            ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls)
 8698
 8699            if ignore_respect and ignore_respect is not this:
 8700                ignore_respect.replace(ignore_respect.this)
 8701                this = self.expression(ignore_respect.__class__(this=this))
 8702
 8703        this = self._parse_respect_or_ignore_nulls(this)
 8704
 8705        # bigquery select from window x AS (partition by ...)
 8706        if alias:
 8707            over = None
 8708            self._match(TokenType.ALIAS)
 8709        elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS):
 8710            return this
 8711        else:
 8712            over = self._prev.text.upper()
 8713
 8714        if comments and isinstance(func, exp.Expr):
 8715            func.pop_comments()
 8716
 8717        if not self._match(TokenType.L_PAREN):
 8718            return self.expression(
 8719                exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments
 8720            )
 8721
 8722        window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS)
 8723
 8724        first: bool | None = True if self._match(TokenType.FIRST) else None
 8725        if self._match_text_seq("LAST"):
 8726            first = False
 8727
 8728        partition, order = self._parse_partition_and_order()
 8729        kind = (
 8730            self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS")
 8731        ) and self._prev.text
 8732
 8733        if kind:
 8734            self._match(TokenType.BETWEEN)
 8735            start = self._parse_window_spec()
 8736
 8737            end = self._parse_window_spec() if self._match(TokenType.AND) else {}
 8738            exclude = (
 8739                self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS)
 8740                if self._match_text_seq("EXCLUDE")
 8741                else None
 8742            )
 8743
 8744            spec = self.expression(
 8745                exp.WindowSpec(
 8746                    kind=kind,
 8747                    start=start["value"],
 8748                    start_side=start["side"],
 8749                    end=end.get("value"),
 8750                    end_side=end.get("side"),
 8751                    exclude=exclude,
 8752                )
 8753            )
 8754        else:
 8755            spec = None
 8756
 8757        self._match_r_paren()
 8758
 8759        window = self.expression(
 8760            exp.Window(
 8761                this=this,
 8762                partition_by=partition,
 8763                order=order,
 8764                spec=spec,
 8765                alias=window_alias,
 8766                over=over,
 8767                first=first,
 8768            ),
 8769            comments=comments,
 8770        )
 8771
 8772        # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...)
 8773        if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False):
 8774            return self._parse_window(window, alias=alias)
 8775
 8776        return window
 8777
 8778    def _parse_partition_and_order(
 8779        self,
 8780    ) -> tuple[list[exp.Expr], exp.Expr | None]:
 8781        return self._parse_partition_by(), self._parse_order()
 8782
 8783    def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]:
 8784        self._match(TokenType.BETWEEN)
 8785
 8786        return {
 8787            "value": (
 8788                (self._match_text_seq("UNBOUNDED") and "UNBOUNDED")
 8789                or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW")
 8790                or self._parse_bitwise()
 8791            ),
 8792            "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None,
 8793        }
 8794
 8795    def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None:
 8796        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 8797        # so this section tries to parse the clause version and if it fails, it treats the token
 8798        # as an identifier (alias)
 8799        if self._can_parse_limit_or_offset():
 8800            return this
 8801
 8802        # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the
 8803        # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it.
 8804        if self._can_parse_named_window():
 8805            return this
 8806
 8807        any_token = self._match(TokenType.ALIAS)
 8808        comments = self._prev_comments
 8809
 8810        if explicit and not any_token:
 8811            return this
 8812
 8813        if self._match(TokenType.L_PAREN):
 8814            aliases = self.expression(
 8815                exp.Aliases(
 8816                    this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token))
 8817                ),
 8818                comments=comments,
 8819            )
 8820            self._match_r_paren(aliases)
 8821            return aliases
 8822
 8823        alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or (
 8824            self.STRING_ALIASES and self._parse_string_as_identifier()
 8825        )
 8826
 8827        if alias:
 8828            comments.extend(alias.pop_comments())
 8829            this = self.expression(exp.Alias(this=this, alias=alias), comments=comments)
 8830            column = this.this
 8831
 8832            # Moves the comment next to the alias in `expr /* comment */ AS alias`
 8833            if not this.comments and column and column.comments:
 8834                this.comments = column.pop_comments()
 8835
 8836        return this
 8837
 8838    def _parse_id_var(
 8839        self,
 8840        any_token: bool = True,
 8841        tokens: t.Collection[TokenType] | None = None,
 8842    ) -> exp.Expr | None:
 8843        expression = self._parse_identifier()
 8844        if not expression and (
 8845            (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS)
 8846        ):
 8847            quoted = self._prev.token_type == TokenType.STRING
 8848            expression = self._identifier_expression(quoted=quoted)
 8849
 8850        return expression
 8851
 8852    def _parse_string(self) -> exp.Expr | None:
 8853        if self._match_set(self.STRING_PARSERS):
 8854            return self.STRING_PARSERS[self._prev.token_type](self, self._prev)
 8855        return self._parse_placeholder()
 8856
 8857    def _parse_string_as_identifier(self) -> exp.Identifier | None:
 8858        if not self._match(TokenType.STRING):
 8859            return None
 8860        output = exp.to_identifier(self._prev.text, quoted=True)
 8861        output.update_positions(self._prev)
 8862        return output
 8863
 8864    def _parse_number(self) -> exp.Expr | None:
 8865        if self._match_set(self.NUMERIC_PARSERS):
 8866            return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev)
 8867        return self._parse_placeholder()
 8868
 8869    def _parse_identifier(self) -> exp.Expr | None:
 8870        if self._match(TokenType.IDENTIFIER):
 8871            return self._identifier_expression(quoted=True)
 8872        return self._parse_placeholder()
 8873
 8874    def _parse_var(
 8875        self,
 8876        any_token: bool = False,
 8877        tokens: t.Collection[TokenType] | None = None,
 8878        upper: bool = False,
 8879    ) -> exp.Expr | None:
 8880        if (
 8881            (any_token and self._advance_any())
 8882            or self._match(TokenType.VAR)
 8883            or (self._match_set(tokens) if tokens else False)
 8884        ):
 8885            return self.expression(
 8886                exp.Var(this=self._prev.text.upper() if upper else self._prev.text)
 8887            )
 8888        return self._parse_placeholder()
 8889
 8890    def _advance_any(self, ignore_reserved: bool = False) -> Token | None:
 8891        if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS):
 8892            self._advance()
 8893            return self._prev
 8894        return None
 8895
 8896    def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None:
 8897        return self._parse_string() or self._parse_var(any_token=True, upper=upper)
 8898
 8899    def _parse_primary_or_var(self) -> exp.Expr | None:
 8900        return self._parse_primary() or self._parse_var(any_token=True)
 8901
 8902    def _parse_null(self) -> exp.Expr | None:
 8903        if self._match_set((TokenType.NULL, TokenType.UNKNOWN)):
 8904            return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev)
 8905        return self._parse_placeholder()
 8906
 8907    def _parse_boolean(self) -> exp.Expr | None:
 8908        if self._match(TokenType.TRUE):
 8909            return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev)
 8910        if self._match(TokenType.FALSE):
 8911            return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev)
 8912        return self._parse_placeholder()
 8913
 8914    def _parse_star(self) -> exp.Expr | None:
 8915        if self._match(TokenType.STAR):
 8916            return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev)
 8917        return self._parse_placeholder()
 8918
 8919    def _parse_parameter(self) -> exp.Parameter:
 8920        this = self._parse_identifier() or self._parse_primary_or_var()
 8921        return self.expression(exp.Parameter(this=this))
 8922
 8923    def _parse_placeholder(self) -> exp.Expr | None:
 8924        if self._match_set(self.PLACEHOLDER_PARSERS):
 8925            placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self)
 8926            if placeholder:
 8927                return placeholder
 8928            self._advance(-1)
 8929        return None
 8930
 8931    def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None:
 8932        if not self._match_texts(keywords):
 8933            return None
 8934        if self._match(TokenType.L_PAREN, advance=False):
 8935            return self._parse_wrapped_csv(self._parse_expression)
 8936
 8937        expression = self._parse_alias(self._parse_disjunction(), explicit=True)
 8938        return [expression] if expression else None
 8939
 8940    def _parse_csv(
 8941        self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA
 8942    ) -> list[T]:
 8943        parse_result = parse_method()
 8944        items = [parse_result] if parse_result is not None else []
 8945
 8946        while self._match(sep):
 8947            if isinstance(parse_result, exp.Expr):
 8948                self._add_comments(parse_result)
 8949            parse_result = parse_method()
 8950            if parse_result is not None:
 8951                items.append(parse_result)
 8952
 8953        return items
 8954
 8955    def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]:
 8956        return self._parse_wrapped_csv(self._parse_id_var, optional=optional)
 8957
 8958    def _parse_wrapped_csv(
 8959        self,
 8960        parse_method: t.Callable[[], T | None],
 8961        sep: TokenType = TokenType.COMMA,
 8962        optional: bool = False,
 8963    ) -> list[T]:
 8964        return self._parse_wrapped(
 8965            lambda: self._parse_csv(parse_method, sep=sep), optional=optional
 8966        )
 8967
 8968    def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T:
 8969        wrapped = self._match(TokenType.L_PAREN)
 8970        if not wrapped and not optional:
 8971            self.raise_error("Expecting (")
 8972        parse_result = parse_method()
 8973        if wrapped:
 8974            self._match_r_paren()
 8975        return parse_result
 8976
 8977    def _parse_expressions(self) -> list[exp.Expr]:
 8978        return self._parse_csv(self._parse_expression)
 8979
 8980    def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None:
 8981        return (
 8982            self._parse_set_operations(
 8983                self._parse_alias(self._parse_assignment(), explicit=True)
 8984                if alias
 8985                else self._parse_assignment()
 8986            )
 8987            or self._parse_select()
 8988        )
 8989
 8990    def _parse_ddl_select(self) -> exp.Expr | None:
 8991        return self._parse_query_modifiers(
 8992            self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False))
 8993        )
 8994
 8995    def _parse_transaction(self) -> exp.Transaction | exp.Command:
 8996        this = None
 8997        if self._match_texts(self.TRANSACTION_KIND):
 8998            this = self._prev.text
 8999
 9000        self._match_texts(("TRANSACTION", "WORK"))
 9001
 9002        modes = []
 9003        while True:
 9004            mode = []
 9005            while self._match(TokenType.VAR) or self._match(TokenType.NOT):
 9006                mode.append(self._prev.text)
 9007
 9008            if mode:
 9009                modes.append(" ".join(mode))
 9010            if not self._match(TokenType.COMMA):
 9011                break
 9012
 9013        return self.expression(exp.Transaction(this=this, modes=modes))
 9014
 9015    def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback:
 9016        chain = None
 9017        savepoint = None
 9018        is_rollback = self._prev.token_type == TokenType.ROLLBACK
 9019
 9020        self._match_texts(("TRANSACTION", "WORK"))
 9021
 9022        if self._match_text_seq("TO"):
 9023            self._match_text_seq("SAVEPOINT")
 9024            savepoint = self._parse_id_var()
 9025
 9026        if self._match(TokenType.AND):
 9027            chain = not self._match_text_seq("NO")
 9028            self._match_text_seq("CHAIN")
 9029
 9030        if is_rollback:
 9031            return self.expression(exp.Rollback(savepoint=savepoint))
 9032
 9033        return self.expression(exp.Commit(chain=chain))
 9034
 9035    def _parse_refresh(self) -> exp.Refresh | exp.Command:
 9036        if self._match_text_seq("EXTERNAL", "TABLE"):
 9037            kind = "EXTERNAL TABLE"
 9038        elif self._match(TokenType.TABLE):
 9039            kind = "TABLE"
 9040        elif self._match_text_seq("MATERIALIZED", "VIEW"):
 9041            kind = "MATERIALIZED VIEW"
 9042        else:
 9043            kind = ""
 9044
 9045        this = self._parse_string() or self._parse_table()
 9046        if not kind and not isinstance(this, exp.Literal):
 9047            return self._parse_as_command(self._prev)
 9048
 9049        return self.expression(exp.Refresh(this=this, kind=kind))
 9050
 9051    def _parse_column_def_with_exists(self):
 9052        start = self._index
 9053        self._match(TokenType.COLUMN)
 9054
 9055        exists_column = self._parse_exists(not_=True)
 9056        expression = self._parse_field_def()
 9057
 9058        if not isinstance(expression, exp.ColumnDef):
 9059            self._retreat(start)
 9060            return None
 9061
 9062        expression.set("exists", exists_column)
 9063
 9064        return expression
 9065
 9066    def _parse_add_column(self) -> exp.ColumnDef | None:
 9067        if not self._prev.text.upper() == "ADD":
 9068            return None
 9069
 9070        return self._parse_column_def_with_exists()
 9071
 9072    def _parse_drop_column(self) -> exp.Drop | exp.Command | None:
 9073        drop = self._parse_drop() if self._match(TokenType.DROP) else None
 9074        if drop and not isinstance(drop, exp.Command):
 9075            drop.set("kind", drop.args.get("kind", "COLUMN"))
 9076        return drop
 9077
 9078    def _parse_alter_drop_action(self) -> exp.Expr | None:
 9079        return self._parse_drop_column()
 9080
 9081    # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html
 9082    def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition:
 9083        return self.expression(
 9084            exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists)
 9085        )
 9086
 9087    def _parse_alter_table_add(self) -> list[exp.Expr]:
 9088        def _parse_add_alteration() -> exp.Expr | None:
 9089            self._match_text_seq("ADD")
 9090            if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False):
 9091                return self.expression(
 9092                    exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint))
 9093                )
 9094
 9095            column_def = self._parse_add_column()
 9096            if isinstance(column_def, exp.ColumnDef):
 9097                return column_def
 9098
 9099            exists = self._parse_exists(not_=True)
 9100            if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False):
 9101                return self.expression(
 9102                    exp.AddPartition(
 9103                        exists=exists,
 9104                        this=self._parse_field(any_token=True),
 9105                        location=self._match_text_seq("LOCATION", advance=False)
 9106                        and self._parse_property(),
 9107                    )
 9108                )
 9109
 9110            return None
 9111
 9112        if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and (
 9113            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
 9114            or self._match_text_seq("COLUMNS")
 9115        ):
 9116            schema = self._parse_schema()
 9117
 9118            return (
 9119                ensure_list(schema)
 9120                if schema
 9121                else self._parse_csv(self._parse_column_def_with_exists)
 9122            )
 9123
 9124        return self._parse_csv(_parse_add_alteration)
 9125
 9126    def _parse_alter_table_alter(self) -> exp.Expr | None:
 9127        if self._match_texts(self.ALTER_ALTER_PARSERS):
 9128            return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self)
 9129
 9130        # Many dialects support the ALTER [COLUMN] syntax, so if there is no
 9131        # keyword after ALTER we default to parsing this statement
 9132        self._match(TokenType.COLUMN)
 9133        exists = self._parse_exists()
 9134        column = self._parse_field(any_token=True)
 9135
 9136        if self._match_pair(TokenType.DROP, TokenType.DEFAULT):
 9137            return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None))
 9138        if self._match_pair(TokenType.SET, TokenType.DEFAULT):
 9139            return self.expression(
 9140                exp.AlterColumn(
 9141                    this=column, default=self._parse_disjunction(), exists=exists or None
 9142                )
 9143            )
 9144        if self._match(TokenType.COMMENT):
 9145            return self.expression(
 9146                exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None)
 9147            )
 9148        if self._match_text_seq("DROP", "NOT", "NULL"):
 9149            return self.expression(
 9150                exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None)
 9151            )
 9152        if self._match_text_seq("SET", "NOT", "NULL"):
 9153            return self.expression(
 9154                exp.AlterColumn(this=column, allow_null=False, exists=exists or None)
 9155            )
 9156
 9157        if self._match_text_seq("SET", "VISIBLE"):
 9158            return self.expression(
 9159                exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None)
 9160            )
 9161        if self._match_text_seq("SET", "INVISIBLE"):
 9162            return self.expression(
 9163                exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None)
 9164            )
 9165
 9166        self._match_text_seq("SET", "DATA")
 9167        self._match_text_seq("TYPE")
 9168        return self.expression(
 9169            exp.AlterColumn(
 9170                this=column,
 9171                dtype=self._parse_types(),
 9172                collate=self._match(TokenType.COLLATE) and self._parse_term(),
 9173                using=self._match(TokenType.USING) and self._parse_disjunction(),
 9174                exists=exists or None,
 9175            )
 9176        )
 9177
 9178    def _parse_alter_diststyle(self) -> exp.AlterDistStyle:
 9179        if self._match_texts(("ALL", "EVEN", "AUTO")):
 9180            return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper())))
 9181
 9182        self._match_text_seq("KEY", "DISTKEY")
 9183        return self.expression(exp.AlterDistStyle(this=self._parse_column()))
 9184
 9185    def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey:
 9186        if compound:
 9187            self._match_text_seq("SORTKEY")
 9188
 9189        if self._match(TokenType.L_PAREN, advance=False):
 9190            return self.expression(
 9191                exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound)
 9192            )
 9193
 9194        self._match_texts(("AUTO", "NONE"))
 9195        return self.expression(
 9196            exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound)
 9197        )
 9198
 9199    def _parse_alter_table_drop(self) -> list[exp.Expr]:
 9200        index = self._index - 1
 9201
 9202        partition_exists = self._parse_exists()
 9203        if self._match(TokenType.PARTITION, advance=False):
 9204            return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists))
 9205
 9206        self._retreat(index)
 9207        return self._parse_csv(self._parse_alter_drop_action)
 9208
 9209    def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None:
 9210        if self._match(TokenType.COLUMN) or (
 9211            not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False)
 9212        ):
 9213            exists = self._parse_exists()
 9214            old_column = self._parse_column()
 9215            to = self._match_text_seq("TO")
 9216            new_column = self._parse_column()
 9217
 9218            if old_column is None or not to or new_column is None:
 9219                return None
 9220
 9221            return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists))
 9222
 9223        self._match_text_seq("TO")
 9224        return self.expression(exp.AlterRename(this=self._parse_table(schema=True)))
 9225
 9226    def _parse_alter_table_set(self) -> exp.AlterSet:
 9227        alter_set = self.expression(exp.AlterSet())
 9228
 9229        if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq(
 9230            "TABLE", "PROPERTIES"
 9231        ):
 9232            alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment))
 9233        elif self._match_text_seq("FILESTREAM_ON", advance=False):
 9234            alter_set.set("expressions", [self._parse_assignment()])
 9235        elif self._match_texts(("LOGGED", "UNLOGGED")):
 9236            alter_set.set("option", exp.var(self._prev.text.upper()))
 9237        elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")):
 9238            alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}"))
 9239        elif self._match_text_seq("LOCATION"):
 9240            alter_set.set("location", self._parse_field())
 9241        elif self._match_text_seq("ACCESS", "METHOD"):
 9242            alter_set.set("access_method", self._parse_field())
 9243        elif self._match_text_seq("TABLESPACE"):
 9244            alter_set.set("tablespace", self._parse_field())
 9245        elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"):
 9246            alter_set.set("file_format", [self._parse_field()])
 9247        elif self._match_text_seq("STAGE_FILE_FORMAT"):
 9248            alter_set.set("file_format", self._parse_wrapped_options())
 9249        elif self._match_text_seq("STAGE_COPY_OPTIONS"):
 9250            alter_set.set("copy_options", self._parse_wrapped_options())
 9251        elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"):
 9252            alter_set.set("tag", self._parse_csv(self._parse_assignment))
 9253        else:
 9254            if self._match_text_seq("SERDE"):
 9255                alter_set.set("serde", self._parse_field())
 9256
 9257            properties = self._parse_wrapped(self._parse_properties, optional=True)
 9258            alter_set.set("expressions", [properties])
 9259
 9260        return alter_set
 9261
 9262    def _parse_alter_session(self) -> exp.AlterSession:
 9263        """Parse ALTER SESSION SET/UNSET statements."""
 9264        if self._match(TokenType.SET):
 9265            expressions = self._parse_csv(lambda: self._parse_set_item_assignment())
 9266            return self.expression(exp.AlterSession(expressions=expressions, unset=False))
 9267
 9268        self._match_text_seq("UNSET")
 9269        expressions = self._parse_csv(
 9270            lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True)))
 9271        )
 9272        return self.expression(exp.AlterSession(expressions=expressions, unset=True))
 9273
 9274    def _parse_alter(self) -> exp.Alter | exp.Command:
 9275        start = self._prev
 9276
 9277        iceberg = self._match_text_seq("ICEBERG")
 9278
 9279        alter_token = self._match_set(self.ALTERABLES) and self._prev
 9280        if not alter_token:
 9281            return self._parse_as_command(start)
 9282        if iceberg and alter_token.token_type != TokenType.TABLE:
 9283            return self._parse_as_command(start)
 9284
 9285        exists = self._parse_exists()
 9286        only = self._match_text_seq("ONLY")
 9287
 9288        if alter_token.token_type == TokenType.SESSION:
 9289            this = None
 9290            check = None
 9291            cluster = None
 9292        else:
 9293            this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS)
 9294            check = self._match_text_seq("WITH", "CHECK")
 9295            cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9296
 9297            if self._next:
 9298                self._advance()
 9299
 9300        parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None
 9301        if parser:
 9302            actions = ensure_list(parser(self))
 9303            not_valid = self._match_text_seq("NOT", "VALID")
 9304            options = self._parse_csv(self._parse_property)
 9305            cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE")
 9306
 9307            if not self._curr and actions:
 9308                return self.expression(
 9309                    exp.Alter(
 9310                        this=this,
 9311                        kind=alter_token.text.upper(),
 9312                        exists=exists,
 9313                        actions=actions,
 9314                        only=only,
 9315                        options=options,
 9316                        cluster=cluster,
 9317                        not_valid=not_valid,
 9318                        check=check,
 9319                        cascade=cascade,
 9320                        iceberg=iceberg,
 9321                    )
 9322                )
 9323
 9324        return self._parse_as_command(start)
 9325
 9326    def _parse_analyze(self) -> exp.Analyze | exp.Command:
 9327        start = self._prev
 9328        # https://duckdb.org/docs/sql/statements/analyze
 9329        if not self._curr:
 9330            return self.expression(exp.Analyze())
 9331
 9332        options = []
 9333        while self._match_texts(self.ANALYZE_STYLES):
 9334            if self._prev.text.upper() == "BUFFER_USAGE_LIMIT":
 9335                options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}")
 9336            else:
 9337                options.append(self._prev.text.upper())
 9338
 9339        tables: exp.Expr | list[exp.Expr] | None = None
 9340        inner_expression: exp.Expr | None = None
 9341
 9342        kind = self._curr.text.upper() if self._curr else None
 9343
 9344        if self._match(TokenType.TABLE):
 9345            tables = self._parse_csv(self._parse_table_parts)
 9346        elif self._match(TokenType.INDEX):
 9347            tables = self._parse_table_parts()
 9348        elif self._match_text_seq("TABLES"):
 9349            if self._match_set((TokenType.FROM, TokenType.IN)):
 9350                kind = f"{kind} {self._prev.text.upper()}"
 9351                tables = self._parse_table(schema=True, is_db_reference=True)
 9352        elif self._match_text_seq("DATABASE"):
 9353            tables = self._parse_table(schema=True, is_db_reference=True)
 9354        elif self._match_text_seq("CLUSTER"):
 9355            tables = self._parse_table()
 9356        # Try matching inner expr keywords before fallback to parse table.
 9357        elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9358            kind = None
 9359            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9360        else:
 9361            # Empty kind  https://prestodb.io/docs/current/sql/analyze.html
 9362            kind = None
 9363            tables = self._parse_csv(self._parse_table_parts)
 9364
 9365        partition = self._try_parse(self._parse_partition)
 9366        if not partition and self._match_texts(self.PARTITION_KEYWORDS):
 9367            return self._parse_as_command(start)
 9368
 9369        # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9370        if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq(
 9371            "WITH", "ASYNC", "MODE"
 9372        ):
 9373            mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE"
 9374        else:
 9375            mode = None
 9376
 9377        if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9378            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9379
 9380        properties = self._parse_properties()
 9381        return self.expression(
 9382            exp.Analyze(
 9383                kind=kind,
 9384                tables=ensure_list(tables),
 9385                mode=mode,
 9386                partition=partition,
 9387                properties=properties,
 9388                expression=inner_expression,
 9389                options=options,
 9390            )
 9391        )
 9392
 9393    # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html
 9394    def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics:
 9395        this = None
 9396        kind = self._prev.text.upper()
 9397        option = self._prev.text.upper() if self._match_text_seq("DELTA") else None
 9398        expressions = []
 9399
 9400        if not self._match_text_seq("STATISTICS"):
 9401            self.raise_error("Expecting token STATISTICS")
 9402
 9403        if self._match_text_seq("NOSCAN"):
 9404            this = "NOSCAN"
 9405        elif self._match(TokenType.FOR):
 9406            if self._match_text_seq("ALL", "COLUMNS"):
 9407                this = "FOR ALL COLUMNS"
 9408            if self._match_text_seq("COLUMNS"):
 9409                this = "FOR COLUMNS"
 9410                expressions = self._parse_csv(self._parse_column_reference)
 9411        elif self._match_text_seq("SAMPLE"):
 9412            sample = self._parse_number()
 9413            expressions = [
 9414                self.expression(
 9415                    exp.AnalyzeSample(
 9416                        sample=sample,
 9417                        kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None,
 9418                    )
 9419                )
 9420            ]
 9421
 9422        return self.expression(
 9423            exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions)
 9424        )
 9425
 9426    # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html
 9427    def _parse_analyze_validate(self) -> exp.AnalyzeValidate:
 9428        kind = None
 9429        this = None
 9430        expression: exp.Expr | None = None
 9431        if self._match_text_seq("REF", "UPDATE"):
 9432            kind = "REF"
 9433            this = "UPDATE"
 9434            if self._match_text_seq("SET", "DANGLING", "TO", "NULL"):
 9435                this = "UPDATE SET DANGLING TO NULL"
 9436        elif self._match_text_seq("STRUCTURE"):
 9437            kind = "STRUCTURE"
 9438            if self._match_text_seq("CASCADE", "FAST"):
 9439                this = "CASCADE FAST"
 9440            elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts(
 9441                ("ONLINE", "OFFLINE")
 9442            ):
 9443                this = f"CASCADE COMPLETE {self._prev.text.upper()}"
 9444                expression = self._parse_into()
 9445
 9446        return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression))
 9447
 9448    def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None:
 9449        this = self._prev.text.upper()
 9450        if self._match_text_seq("COLUMNS"):
 9451            return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}"))
 9452        return None
 9453
 9454    def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None:
 9455        kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None
 9456        if self._match_text_seq("STATISTICS"):
 9457            return self.expression(exp.AnalyzeDelete(kind=kind))
 9458        return None
 9459
 9460    def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None:
 9461        if self._match_text_seq("CHAINED", "ROWS"):
 9462            return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into()))
 9463        return None
 9464
 9465    # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html
 9466    def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram:
 9467        this = self._prev.text.upper()
 9468        expression: exp.Expr | None = None
 9469        expressions = []
 9470        update_options = None
 9471
 9472        if self._match_text_seq("HISTOGRAM", "ON"):
 9473            expressions = self._parse_csv(self._parse_column_reference)
 9474            with_expressions = []
 9475            while self._match(TokenType.WITH):
 9476                # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9477                if self._match_texts(("SYNC", "ASYNC")):
 9478                    if self._match_text_seq("MODE", advance=False):
 9479                        with_expressions.append(f"{self._prev.text.upper()} MODE")
 9480                        self._advance()
 9481                else:
 9482                    buckets = self._parse_number()
 9483                    if self._match_text_seq("BUCKETS"):
 9484                        with_expressions.append(f"{buckets} BUCKETS")
 9485            if with_expressions:
 9486                expression = self.expression(exp.AnalyzeWith(expressions=with_expressions))
 9487
 9488            if self._match_texts(("MANUAL", "AUTO")) and self._match(
 9489                TokenType.UPDATE, advance=False
 9490            ):
 9491                update_options = self._prev.text.upper()
 9492                self._advance()
 9493            elif self._match_text_seq("USING", "DATA"):
 9494                expression = self.expression(exp.UsingData(this=self._parse_string()))
 9495
 9496        return self.expression(
 9497            exp.AnalyzeHistogram(
 9498                this=this,
 9499                expressions=expressions,
 9500                expression=expression,
 9501                update_options=update_options,
 9502            )
 9503        )
 9504
 9505    def _parse_merge(self) -> exp.Merge:
 9506        self._match(TokenType.INTO)
 9507        target = self._parse_table()
 9508
 9509        if target and self._match(TokenType.ALIAS, advance=False):
 9510            target.set("alias", self._parse_table_alias())
 9511
 9512        self._match(TokenType.USING)
 9513        using = self._parse_table()
 9514
 9515        return self.expression(
 9516            exp.Merge(
 9517                this=target,
 9518                using=using,
 9519                on=self._match(TokenType.ON) and self._parse_disjunction(),
 9520                using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(),
 9521                whens=self._parse_when_matched(),
 9522                returning=self._parse_returning(),
 9523            )
 9524        )
 9525
 9526    def _parse_when_matched(self) -> exp.Whens:
 9527        whens = []
 9528
 9529        while self._match(TokenType.WHEN):
 9530            matched = not self._match(TokenType.NOT)
 9531            self._match_text_seq("MATCHED")
 9532            source = (
 9533                False
 9534                if self._match_text_seq("BY", "TARGET")
 9535                else self._match_text_seq("BY", "SOURCE")
 9536            )
 9537            condition = self._parse_disjunction() if self._match(TokenType.AND) else None
 9538
 9539            self._match(TokenType.THEN)
 9540
 9541            if self._match(TokenType.INSERT):
 9542                this = self._parse_star()
 9543                if this:
 9544                    then: exp.Expr | None = self.expression(exp.Insert(this=this))
 9545                else:
 9546                    then = self.expression(
 9547                        exp.Insert(
 9548                            this=exp.var("ROW")
 9549                            if self._match_text_seq("ROW")
 9550                            else self._parse_value(values=False),
 9551                            expression=self._match_text_seq("VALUES") and self._parse_value(),
 9552                            where=self._parse_where(),
 9553                        )
 9554                    )
 9555            elif self._match(TokenType.UPDATE):
 9556                expressions = self._parse_star()
 9557                if expressions:
 9558                    then = self.expression(exp.Update(expressions=expressions))
 9559                else:
 9560                    then = self.expression(
 9561                        exp.Update(
 9562                            expressions=self._match(TokenType.SET)
 9563                            and self._parse_csv(self._parse_equality),
 9564                            where=self._parse_where(),
 9565                        )
 9566                    )
 9567            elif self._match(TokenType.DELETE):
 9568                then = self.expression(exp.Var(this=self._prev.text))
 9569            else:
 9570                then = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 9571
 9572            whens.append(
 9573                self.expression(
 9574                    exp.When(matched=matched, source=source, condition=condition, then=then)
 9575                )
 9576            )
 9577        return self.expression(exp.Whens(expressions=whens))
 9578
 9579    def _parse_show(self) -> exp.Expr | None:
 9580        parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE)
 9581        if parser:
 9582            return parser(self)
 9583        return self._parse_as_command(self._prev)
 9584
 9585    def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None:
 9586        index = self._index
 9587
 9588        if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"):
 9589            return self._parse_set_transaction(global_=kind == "GLOBAL")
 9590
 9591        left = self._parse_primary() or self._parse_column()
 9592        assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS)
 9593
 9594        if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter):
 9595            self._retreat(index)
 9596            return None
 9597
 9598        right = self._parse_statement() or self._parse_id_var()
 9599        if isinstance(right, (exp.Column, exp.Identifier)):
 9600            right = exp.var(right.name)
 9601
 9602        this = self.expression(exp.EQ(this=left, expression=right))
 9603        return self.expression(exp.SetItem(this=this, kind=kind))
 9604
 9605    def _parse_set_transaction(self, global_: bool = False) -> exp.Expr:
 9606        self._match_text_seq("TRANSACTION")
 9607        characteristics = self._parse_csv(
 9608            lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS)
 9609        )
 9610        return self.expression(
 9611            exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_)
 9612        )
 9613
 9614    def _parse_set_item(self) -> exp.Expr | None:
 9615        parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE)
 9616        return parser(self) if parser else self._parse_set_item_assignment(kind=None)
 9617
 9618    def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command:
 9619        index = self._index
 9620        set_ = self.expression(
 9621            exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag)
 9622        )
 9623
 9624        if self._curr:
 9625            self._retreat(index)
 9626            return self._parse_as_command(self._prev)
 9627
 9628        return set_
 9629
 9630    def _parse_var_from_options(
 9631        self, options: OPTIONS_TYPE, raise_unmatched: bool = True
 9632    ) -> exp.Var | None:
 9633        start = self._curr
 9634        if not start:
 9635            return None
 9636
 9637        option = start.text.upper()
 9638        continuations = (
 9639            None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option)
 9640        )
 9641
 9642        index = self._index
 9643        self._advance()
 9644        for keywords in continuations or []:
 9645            if isinstance(keywords, str):
 9646                keywords = (keywords,)
 9647
 9648            if self._match_text_seq(*keywords):
 9649                option = f"{option} {' '.join(keywords)}"
 9650                break
 9651        else:
 9652            if continuations or continuations is None:
 9653                if raise_unmatched:
 9654                    self.raise_error(f"Unknown option {option}")
 9655
 9656                self._retreat(index)
 9657                return None
 9658
 9659        return exp.var(option)
 9660
 9661    def _parse_as_command(self, start: Token) -> exp.Command:
 9662        while self._curr:
 9663            self._advance()
 9664        text = self._find_sql(start, self._prev)
 9665        size = len(start.text)
 9666        self._warn_unsupported()
 9667        return exp.Command(this=text[:size], expression=text[size:])
 9668
 9669    def _parse_dict_property(self, this: str) -> exp.DictProperty:
 9670        settings = []
 9671
 9672        self._match_l_paren()
 9673        kind = self._parse_id_var()
 9674
 9675        if self._match(TokenType.L_PAREN):
 9676            while True:
 9677                key = self._parse_id_var()
 9678                value = self._parse_function() or self._parse_primary_or_var()
 9679                if not key and value is None:
 9680                    break
 9681                settings.append(self.expression(exp.DictSubProperty(this=key, value=value)))
 9682            self._match(TokenType.R_PAREN)
 9683
 9684        self._match_r_paren()
 9685
 9686        return self.expression(
 9687            exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings)
 9688        )
 9689
 9690    def _parse_dict_range(self, this: str) -> exp.DictRange:
 9691        self._match_l_paren()
 9692        has_min = self._match_text_seq("MIN")
 9693        if has_min:
 9694            min = self._parse_var() or self._parse_primary()
 9695            self._match_text_seq("MAX")
 9696            max = self._parse_var() or self._parse_primary()
 9697        else:
 9698            max = self._parse_var() or self._parse_primary()
 9699            min = exp.Literal.number(0)
 9700        self._match_r_paren()
 9701        return self.expression(exp.DictRange(this=this, min=min, max=max))
 9702
 9703    def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None:
 9704        index = self._index
 9705        expression = self._parse_column()
 9706        position = self._match(TokenType.COMMA) and self._parse_column()
 9707
 9708        if not self._match(TokenType.IN):
 9709            self._retreat(index - 1)
 9710            return None
 9711        iterator = self._parse_column()
 9712        condition = self._parse_disjunction() if self._match_text_seq("IF") else None
 9713        return self.expression(
 9714            exp.Comprehension(
 9715                this=this,
 9716                expression=expression,
 9717                position=position,
 9718                iterator=iterator,
 9719                condition=condition,
 9720            )
 9721        )
 9722
 9723    def _parse_heredoc(self) -> exp.Heredoc | None:
 9724        if self._match(TokenType.HEREDOC_STRING):
 9725            return self.expression(exp.Heredoc(this=self._prev.text))
 9726
 9727        if not self._match_text_seq("$"):
 9728            return None
 9729
 9730        tags = ["$"]
 9731        tag_text = None
 9732
 9733        if self._is_connected():
 9734            self._advance()
 9735            tags.append(self._prev.text.upper())
 9736        else:
 9737            self.raise_error("No closing $ found")
 9738
 9739        if tags[-1] != "$":
 9740            if self._is_connected() and self._match_text_seq("$"):
 9741                tag_text = tags[-1]
 9742                tags.append("$")
 9743            else:
 9744                self.raise_error("No closing $ found")
 9745
 9746        heredoc_start = self._curr
 9747
 9748        while self._curr:
 9749            if self._match_text_seq(*tags, advance=False):
 9750                this = self._find_sql(heredoc_start, self._prev)
 9751                self._advance(len(tags))
 9752                return self.expression(exp.Heredoc(this=this, tag=tag_text))
 9753
 9754            self._advance()
 9755
 9756        self.raise_error(f"No closing {''.join(tags)} found")
 9757        return None
 9758
 9759    def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None:
 9760        if not self._curr:
 9761            return None
 9762
 9763        index = self._index
 9764        this = []
 9765        while True:
 9766            # The current token might be multiple words
 9767            curr = self._curr.text.upper()
 9768            key = curr.split(" ")
 9769            this.append(curr)
 9770
 9771            self._advance()
 9772            result, trie = in_trie(trie, key)
 9773            if result == TrieResult.FAILED:
 9774                break
 9775
 9776            if result == TrieResult.EXISTS:
 9777                subparser = parsers[" ".join(this)]
 9778                return subparser
 9779
 9780        self._retreat(index)
 9781        return None
 9782
 9783    def _match_l_paren(self, expression: exp.Expr | None = None) -> None:
 9784        if not self._match(TokenType.L_PAREN, expression=expression):
 9785            self.raise_error("Expecting (")
 9786
 9787    def _match_r_paren(self, expression: exp.Expr | None = None) -> None:
 9788        if not self._match(TokenType.R_PAREN, expression=expression):
 9789            self.raise_error("Expecting )")
 9790
 9791    def _replace_lambda(
 9792        self, node: exp.Expr | None, expressions: list[exp.Expr]
 9793    ) -> exp.Expr | None:
 9794        if not node:
 9795            return node
 9796
 9797        lambda_types = {e.name: e.args.get("to") or False for e in expressions}
 9798
 9799        for column in node.find_all(exp.Column):
 9800            typ = lambda_types.get(column.parts[0].name)
 9801            if typ is not None:
 9802                dot_or_id = column.to_dot() if column.table else column.this
 9803
 9804                if typ:
 9805                    dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ))
 9806
 9807                parent = column.parent
 9808
 9809                while isinstance(parent, exp.Dot):
 9810                    if not isinstance(parent.parent, exp.Dot):
 9811                        parent.replace(dot_or_id)
 9812                        break
 9813                    parent = parent.parent
 9814                else:
 9815                    if column is node:
 9816                        node = dot_or_id
 9817                    else:
 9818                        column.replace(dot_or_id)
 9819        return node
 9820
 9821    def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr:
 9822        start = self._prev
 9823
 9824        # Not to be confused with TRUNCATE(number, decimals) function call
 9825        if self._match(TokenType.L_PAREN):
 9826            self._retreat(self._index - 2)
 9827            return self._parse_function()
 9828
 9829        # Clickhouse supports TRUNCATE DATABASE as well
 9830        is_database = self._match(TokenType.DATABASE)
 9831
 9832        self._match(TokenType.TABLE)
 9833
 9834        exists = self._parse_exists(not_=False)
 9835
 9836        expressions = self._parse_csv(
 9837            lambda: self._parse_table(schema=True, is_db_reference=is_database)
 9838        )
 9839
 9840        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9841
 9842        if self._match_text_seq("RESTART", "IDENTITY"):
 9843            identity = "RESTART"
 9844        elif self._match_text_seq("CONTINUE", "IDENTITY"):
 9845            identity = "CONTINUE"
 9846        else:
 9847            identity = None
 9848
 9849        if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"):
 9850            option = self._prev.text
 9851        else:
 9852            option = None
 9853
 9854        partition = self._parse_partition()
 9855
 9856        # Fallback case
 9857        if self._curr:
 9858            return self._parse_as_command(start)
 9859
 9860        return self.expression(
 9861            exp.TruncateTable(
 9862                expressions=expressions,
 9863                is_database=is_database,
 9864                exists=exists,
 9865                cluster=cluster,
 9866                identity=identity,
 9867                option=option,
 9868                partition=partition,
 9869            )
 9870        )
 9871
 9872    def _parse_indexed_column(self) -> exp.Expr | None:
 9873        return self._parse_ordered(self._parse_opclass)
 9874
 9875    def _parse_with_operator(self) -> exp.Expr | None:
 9876        this = self._parse_indexed_column()
 9877
 9878        if not self._match(TokenType.WITH):
 9879            return this
 9880
 9881        op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS)
 9882
 9883        return self.expression(exp.WithOperator(this=this, op=op))
 9884
 9885    def _parse_wrapped_options(self) -> list[exp.Expr]:
 9886        self._match(TokenType.EQ)
 9887        self._match(TokenType.L_PAREN)
 9888
 9889        opts: list[exp.Expr] = []
 9890        option: exp.Expr | list[exp.Expr] | None
 9891        while self._curr and not self._match(TokenType.R_PAREN):
 9892            if self._match_text_seq("FORMAT_NAME", "="):
 9893                # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL
 9894                option = self._parse_format_name()
 9895            else:
 9896                option = self._parse_property()
 9897
 9898            if option is None:
 9899                self.raise_error("Unable to parse option")
 9900                break
 9901
 9902            opts.extend(ensure_list(option))
 9903
 9904        return opts
 9905
 9906    def _parse_copy_parameters(self) -> list[exp.CopyParameter]:
 9907        sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None
 9908
 9909        options = []
 9910        while self._curr and not self._match(TokenType.R_PAREN, advance=False):
 9911            option = self._parse_var(any_token=True)
 9912            prev = self._prev.text.upper()
 9913
 9914            # Different dialects might separate options and values by white space, "=" and "AS"
 9915            self._match(TokenType.EQ)
 9916            self._match(TokenType.ALIAS)
 9917
 9918            param = self.expression(exp.CopyParameter(this=option))
 9919
 9920            if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match(
 9921                TokenType.L_PAREN, advance=False
 9922            ):
 9923                # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options
 9924                param.set("expressions", self._parse_wrapped_options())
 9925            elif prev == "FILE_FORMAT":
 9926                # T-SQL's external file format case
 9927                param.set("expression", self._parse_field())
 9928            elif (
 9929                prev == "FORMAT"
 9930                and self._prev.token_type == TokenType.ALIAS
 9931                and self._match_texts(("AVRO", "JSON"))
 9932            ):
 9933                param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}"))
 9934                param.set("expression", self._parse_field())
 9935            else:
 9936                param.set("expression", self._parse_unquoted_field() or self._parse_bracket())
 9937
 9938            options.append(param)
 9939
 9940            if sep:
 9941                self._match(sep)
 9942
 9943        return options
 9944
 9945    def _parse_credentials(self) -> exp.Credentials | None:
 9946        expr = self.expression(exp.Credentials())
 9947
 9948        if self._match_text_seq("STORAGE_INTEGRATION", "="):
 9949            expr.set("storage", self._parse_field())
 9950        if self._match_text_seq("CREDENTIALS"):
 9951            # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string>
 9952            creds = (
 9953                self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field()
 9954            )
 9955            expr.set("credentials", creds)
 9956        if self._match_text_seq("ENCRYPTION"):
 9957            expr.set("encryption", self._parse_wrapped_options())
 9958        if self._match_text_seq("IAM_ROLE"):
 9959            expr.set(
 9960                "iam_role",
 9961                exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(),
 9962            )
 9963        if self._match_text_seq("REGION"):
 9964            expr.set("region", self._parse_field())
 9965
 9966        return expr
 9967
 9968    def _parse_file_location(self) -> exp.Expr | None:
 9969        return self._parse_field()
 9970
 9971    def _parse_copy(self) -> exp.Copy | exp.Command:
 9972        start = self._prev
 9973
 9974        self._match(TokenType.INTO)
 9975
 9976        this = (
 9977            self._parse_select(nested=True, parse_subquery_alias=False)
 9978            if self._match(TokenType.L_PAREN, advance=False)
 9979            else self._parse_table(schema=True)
 9980        )
 9981
 9982        kind = self._match(TokenType.FROM) or not self._match_text_seq("TO")
 9983
 9984        files = self._parse_csv(self._parse_file_location)
 9985        if self._match(TokenType.EQ, advance=False):
 9986            # Backtrack one token since we've consumed the lhs of a parameter assignment here.
 9987            # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter
 9988            # list via `_parse_wrapped(..)` below.
 9989            self._advance(-1)
 9990            files = []
 9991
 9992        credentials = self._parse_credentials()
 9993
 9994        self._match_text_seq("WITH")
 9995
 9996        params = self._parse_wrapped(self._parse_copy_parameters, optional=True)
 9997
 9998        # Fallback case
 9999        if self._curr:
10000            return self._parse_as_command(start)
10001
10002        return self.expression(
10003            exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params)
10004        )
10005
10006    def _parse_normalize(self) -> exp.Normalize:
10007        return self.expression(
10008            exp.Normalize(
10009                this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var()
10010            )
10011        )
10012
10013    def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor:
10014        args = self._parse_csv(lambda: self._parse_lambda())
10015
10016        this = seq_get(args, 0)
10017        decimals = seq_get(args, 1)
10018
10019        return expr_type(
10020            this=this,
10021            decimals=decimals,
10022            to=self._parse_var() if self._match_text_seq("TO") else None,
10023        )
10024
10025    def _parse_star_ops(self) -> exp.Expr | None:
10026        star_token = self._prev
10027
10028        if self._match_text_seq("COLUMNS", "(", advance=False):
10029            this = self._parse_function()
10030            if isinstance(this, exp.Columns):
10031                this.set("unpack", True)
10032            return this
10033
10034        index = self._index
10035        ilike = self._parse_string() if self._match(TokenType.ILIKE) else None
10036        if not ilike:
10037            # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)`
10038            self._retreat(index)
10039
10040        return self.expression(
10041            exp.Star(
10042                ilike=ilike,
10043                except_=self._parse_star_op("EXCEPT", "EXCLUDE"),
10044                replace=self._parse_star_op("REPLACE"),
10045                rename=self._parse_star_op("RENAME"),
10046            )
10047        ).update_positions(star_token)
10048
10049    def _parse_grant_privilege(self) -> exp.GrantPrivilege | None:
10050        privilege_parts = []
10051
10052        # Keep consuming consecutive keywords until comma (end of this privilege) or ON
10053        # (end of privilege list) or L_PAREN (start of column list) are met
10054        while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False):
10055            privilege_parts.append(self._curr.text.upper())
10056            self._advance()
10057
10058        if not privilege_parts:
10059            self.raise_error("Expected privilege")
10060            return None
10061
10062        this = exp.var(" ".join(privilege_parts))
10063        expressions = (
10064            self._parse_wrapped_csv(self._parse_column)
10065            if self._match(TokenType.L_PAREN, advance=False)
10066            else None
10067        )
10068
10069        return self.expression(exp.GrantPrivilege(this=this, expressions=expressions))
10070
10071    def _parse_grant_principal(self) -> exp.GrantPrincipal | None:
10072        kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper()
10073        principal = self._parse_id_var()
10074
10075        if not principal:
10076            return None
10077
10078        return self.expression(exp.GrantPrincipal(this=principal, kind=kind))
10079
10080    def _parse_grant_revoke_common(
10081        self,
10082    ) -> tuple[list | None, str | None, exp.Expr | None]:
10083        privileges = self._parse_csv(self._parse_grant_privilege)
10084
10085        self._match(TokenType.ON)
10086        kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None
10087
10088        # Attempt to parse the securable e.g. MySQL allows names
10089        # such as "foo.*", "*.*" which are not easily parseable yet
10090        securable = self._try_parse(self._parse_table_parts)
10091
10092        return privileges, kind, securable
10093
10094    def _parse_grant(self) -> exp.Grant | exp.Command:
10095        start = self._prev
10096
10097        privileges, kind, securable = self._parse_grant_revoke_common()
10098
10099        if not securable or not self._match_text_seq("TO"):
10100            return self._parse_as_command(start)
10101
10102        principals = self._parse_csv(self._parse_grant_principal)
10103
10104        grant_option = self._match_text_seq("WITH", "GRANT", "OPTION")
10105
10106        if self._curr:
10107            return self._parse_as_command(start)
10108
10109        return self.expression(
10110            exp.Grant(
10111                privileges=privileges,
10112                kind=kind,
10113                securable=securable,
10114                principals=principals,
10115                grant_option=grant_option,
10116            )
10117        )
10118
10119    def _parse_revoke(self) -> exp.Revoke | exp.Command:
10120        start = self._prev
10121
10122        grant_option = self._match_text_seq("GRANT", "OPTION", "FOR")
10123
10124        privileges, kind, securable = self._parse_grant_revoke_common()
10125
10126        if not securable or not self._match_text_seq("FROM"):
10127            return self._parse_as_command(start)
10128
10129        principals = self._parse_csv(self._parse_grant_principal)
10130
10131        cascade = None
10132        if self._match_texts(("CASCADE", "RESTRICT")):
10133            cascade = self._prev.text.upper()
10134
10135        if self._curr:
10136            return self._parse_as_command(start)
10137
10138        return self.expression(
10139            exp.Revoke(
10140                privileges=privileges,
10141                kind=kind,
10142                securable=securable,
10143                principals=principals,
10144                grant_option=grant_option,
10145                cascade=cascade,
10146            )
10147        )
10148
10149    def _parse_overlay(self) -> exp.Overlay:
10150        def _parse_overlay_arg(text: str) -> exp.Expr | None:
10151            return (
10152                self._parse_bitwise()
10153                if self._match(TokenType.COMMA) or self._match_text_seq(text)
10154                else None
10155            )
10156
10157        return self.expression(
10158            exp.Overlay(
10159                this=self._parse_bitwise(),
10160                expression=_parse_overlay_arg("PLACING"),
10161                from_=_parse_overlay_arg("FROM"),
10162                for_=_parse_overlay_arg("FOR"),
10163            )
10164        )
10165
10166    def _parse_format_name(self) -> exp.Property:
10167        # Note: Although not specified in the docs, Snowflake does accept a string/identifier
10168        # for FILE_FORMAT = <format_name>
10169        return self.expression(
10170            exp.Property(
10171                this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts()
10172            )
10173        )
10174
10175    def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F:
10176        is_distinct = self._match(TokenType.DISTINCT)
10177        if not is_distinct:
10178            self._match(TokenType.ALL)
10179
10180        args = [self._parse_lambda()]
10181        if self._match(TokenType.COMMA):
10182            args.extend(self._parse_function_args())
10183
10184        target = seq_get(args, distinct_index)
10185        if is_distinct and target:
10186            args[distinct_index] = self.expression(exp.Distinct(expressions=[target]))
10187
10188        return func.from_arg_list(args)
10189
10190    def _identifier_expression(
10191        self, token: Token | None = None, quoted: bool | None = None
10192    ) -> exp.Identifier:
10193        token = token or self._prev
10194        return self.expression(exp.Identifier(this=token.text, quoted=quoted), token)
10195
10196    def _build_pipe_cte(
10197        self,
10198        query: exp.Query,
10199        expressions: list[exp.Expr],
10200        alias_cte: exp.TableAlias | None = None,
10201    ) -> exp.Select:
10202        new_cte: str | exp.TableAlias | None
10203        if alias_cte:
10204            new_cte = alias_cte
10205        else:
10206            self._pipe_cte_counter += 1
10207            new_cte = f"__tmp{self._pipe_cte_counter}"
10208
10209        with_ = query.args.get("with_")
10210        ctes = with_.pop() if with_ else None
10211
10212        new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False)
10213        if ctes:
10214            new_select.set("with_", ctes)
10215
10216        return new_select.with_(new_cte, as_=query, copy=False)
10217
10218    def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select:
10219        select = self._parse_select(consume_pipe=False)
10220        if not select:
10221            return query
10222
10223        return self._build_pipe_cte(
10224            query=query.select(*select.expressions, append=False), expressions=[exp.Star()]
10225        )
10226
10227    def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select:
10228        limit = self._parse_limit()
10229        offset = self._parse_offset()
10230        if limit:
10231            curr_limit = query.args.get("limit", limit)
10232            if curr_limit.expression.to_py() >= limit.expression.to_py():
10233                query.limit(limit, copy=False)
10234        if offset:
10235            curr_offset = query.args.get("offset")
10236            curr_offset = curr_offset.expression.to_py() if curr_offset else 0
10237            query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False)
10238
10239        return query
10240
10241    def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None:
10242        this = self._parse_disjunction()
10243        if self._match_text_seq("GROUP", "AND", advance=False):
10244            return this
10245
10246        this = self._parse_alias(this)
10247
10248        if self._match_set((TokenType.ASC, TokenType.DESC), advance=False):
10249            return self._parse_ordered(lambda: this)
10250
10251        return this
10252
10253    def _parse_pipe_syntax_aggregate_group_order_by(
10254        self, query: exp.Select, group_by_exists: bool = True
10255    ) -> exp.Select:
10256        expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields)
10257        aggregates_or_groups, orders = [], []
10258        for element in expr:
10259            if isinstance(element, exp.Ordered):
10260                this = element.this
10261                if isinstance(this, exp.Alias):
10262                    element.set("this", this.args["alias"])
10263                orders.append(element)
10264            else:
10265                this = element
10266            aggregates_or_groups.append(this)
10267
10268        if group_by_exists:
10269            query.select(
10270                *aggregates_or_groups, *query.expressions, append=False, copy=False
10271            ).group_by(
10272                *[projection.args.get("alias", projection) for projection in aggregates_or_groups],
10273                copy=False,
10274            )
10275        else:
10276            query.select(*aggregates_or_groups, append=False, copy=False)
10277
10278        if orders:
10279            return query.order_by(*orders, append=False, copy=False)
10280
10281        return query
10282
10283    def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select:
10284        self._match_text_seq("AGGREGATE")
10285        query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False)
10286
10287        if self._match(TokenType.GROUP_BY) or (
10288            self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY)
10289        ):
10290            query = self._parse_pipe_syntax_aggregate_group_order_by(query)
10291
10292        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10293
10294    def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None:
10295        first_setop = self.parse_set_operation(this=query)
10296        if not first_setop:
10297            return None
10298
10299        def _parse_and_unwrap_query() -> exp.Expr | None:
10300            expr = self._parse_paren()
10301            return expr.assert_is(exp.Subquery).unnest() if expr else None
10302
10303        first_setop.this.pop()
10304
10305        setops = [
10306            first_setop.expression.pop().assert_is(exp.Subquery).unnest(),
10307            *self._parse_csv(_parse_and_unwrap_query),
10308        ]
10309
10310        query = self._build_pipe_cte(query=query, expressions=[exp.Star()])
10311        with_ = query.args.get("with_")
10312        ctes = with_.pop() if with_ else None
10313
10314        if isinstance(first_setop, exp.Union):
10315            query = query.union(*setops, copy=False, **first_setop.args)
10316        elif isinstance(first_setop, exp.Except):
10317            query = query.except_(*setops, copy=False, **first_setop.args)
10318        else:
10319            query = query.intersect(*setops, copy=False, **first_setop.args)
10320
10321        query.set("with_", ctes)
10322
10323        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10324
10325    def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None:
10326        join = self._parse_join()
10327        if not join:
10328            return None
10329
10330        if isinstance(query, exp.Select):
10331            return query.join(join, copy=False)
10332
10333        return query
10334
10335    def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select:
10336        pivots = self._parse_pivots()
10337        if not pivots:
10338            return query
10339
10340        from_ = query.args.get("from_")
10341        if from_:
10342            from_.this.set("pivots", pivots)
10343
10344        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10345
10346    def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select:
10347        self._match_text_seq("EXTEND")
10348        query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False)
10349        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10350
10351    def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select:
10352        sample = self._parse_table_sample()
10353
10354        with_ = query.args.get("with_")
10355        if with_:
10356            with_.expressions[-1].this.set("sample", sample)
10357        else:
10358            query.set("sample", sample)
10359
10360        return query
10361
10362    def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None:
10363        if isinstance(query, exp.Subquery):
10364            query = exp.select("*").from_(query, copy=False)
10365
10366        if not query.args.get("from_"):
10367            query = exp.select("*").from_(query.subquery(copy=False), copy=False)
10368
10369        while self._match(TokenType.PIPE_GT):
10370            start_index = self._index
10371            start_text = self._curr.text.upper()
10372            parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text)
10373            if not parser:
10374                # The set operators (UNION, etc) and the JOIN operator have a few common starting
10375                # keywords, making it tricky to disambiguate them without lookahead. The approach
10376                # here is to try and parse a set operation and if that fails, then try to parse a
10377                # join operator. If that fails as well, then the operator is not supported.
10378                parsed_query = self._parse_pipe_syntax_set_operator(query)
10379                parsed_query = parsed_query or self._parse_pipe_syntax_join(query)
10380                if not parsed_query:
10381                    self._retreat(start_index)
10382                    self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.")
10383                    break
10384                query = parsed_query
10385            else:
10386                query = parser(self, query)
10387
10388        return query
10389
10390    def _parse_declareitem(self) -> exp.DeclareItem | None:
10391        self._match_texts(("VAR", "VARIABLE"))
10392
10393        vars = self._parse_csv(self._parse_id_var)
10394        if not vars:
10395            return None
10396
10397        self._match(TokenType.ALIAS)
10398        kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types()
10399        default = (
10400            self._match(TokenType.DEFAULT) or self._match(TokenType.EQ)
10401        ) and self._parse_bitwise()
10402
10403        return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default))
10404
10405    def _parse_declare(self) -> exp.Declare | exp.Command:
10406        start = self._prev
10407        replace = self._match_text_seq("OR", "REPLACE")
10408        expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem))
10409
10410        if not expressions or self._curr:
10411            return self._parse_as_command(start)
10412
10413        return self.expression(exp.Declare(expressions=expressions, replace=replace))
10414
10415    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10416        exp_class = exp.Cast if strict else exp.TryCast
10417
10418        if exp_class == exp.TryCast:
10419            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10420
10421        return self.expression(exp_class(**kwargs))
10422
10423    def _parse_json_value(self) -> exp.JSONValue:
10424        this = self._parse_bitwise()
10425        self._match(TokenType.COMMA)
10426        path = self._parse_bitwise()
10427
10428        returning = self._match(TokenType.RETURNING) and self._parse_type()
10429
10430        return self.expression(
10431            exp.JSONValue(
10432                this=this,
10433                path=self.dialect.to_json_path(path),
10434                returning=returning,
10435                on_condition=self._parse_on_condition(),
10436            )
10437        )
10438
10439    def _parse_group_concat(self) -> exp.Expr | None:
10440        def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr:
10441            if isinstance(node, exp.Distinct) and len(node.expressions) > 1:
10442                concat_exprs = [
10443                    self.expression(
10444                        exp.Concat(
10445                            expressions=node.expressions,
10446                            safe=True,
10447                            coalesce=self.dialect.CONCAT_COALESCE,
10448                        )
10449                    )
10450                ]
10451                node.set("expressions", concat_exprs)
10452                return node
10453            if len(exprs) == 1:
10454                return exprs[0]
10455            return self.expression(
10456                exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE)
10457            )
10458
10459        args = self._parse_csv(self._parse_lambda)
10460
10461        if args:
10462            order = args[-1] if isinstance(args[-1], exp.Order) else None
10463
10464            if order:
10465                # Order By is the last (or only) expression in the list and has consumed the 'expr' before it,
10466                # remove 'expr' from exp.Order and add it back to args
10467                args[-1] = order.this
10468                order.set("this", concat_exprs(order.this, args))
10469
10470            this = order or concat_exprs(args[0], args)
10471        else:
10472            this = None
10473
10474        separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None
10475
10476        return self.expression(exp.GroupConcat(this=this, separator=separator))
10477
10478    def _parse_initcap(self) -> exp.Initcap:
10479        expr = exp.Initcap.from_arg_list(self._parse_function_args())
10480
10481        # attach dialect's default delimiters
10482        if expr.args.get("expression") is None:
10483            expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS))
10484
10485        return expr
10486
10487    def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None:
10488        while True:
10489            if not self._match(TokenType.L_PAREN):
10490                break
10491
10492            op = ""
10493            while self._curr and not self._match(TokenType.R_PAREN):
10494                op += self._curr.text
10495                self._advance()
10496
10497            comments = self._prev_comments
10498            this = self.expression(
10499                exp.Operator(this=this, operator=op, expression=self._parse_bitwise()),
10500                comments=comments,
10501            )
10502
10503            if not self._match(TokenType.OPERATOR):
10504                break
10505
10506        return this

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'>>, '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'>>, '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'>>, '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', 'TRAILING', 'LEADING'}
IDENTIFIER_TOKENS: ClassVar[frozenset] = frozenset({<TokenType.VAR: 89>, <TokenType.IDENTIFIER: 79>})
BRACKETS: ClassVar[frozenset] = frozenset({<TokenType.L_BRACKET: 3>, <TokenType.L_BRACE: 5>})
COLUMN_POSTFIX_TOKENS: ClassVar[frozenset] = frozenset({<TokenType.L_PAREN: 1>, <TokenType.L_BRACKET: 3>, <TokenType.L_BRACE: 5>, <TokenType.JOIN_MARKER: 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 = {'CHECK', 'PERIOD', 'FOREIGN KEY', 'TRUNCATE', 'BUCKET', 'LIKE', 'PRIMARY KEY', 'UNIQUE', 'EXCLUDE'}
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 = {'DEFERRED', 'EXCLUSIVE', 'IMMEDIATE'}
TRANSACTION_CHARACTERISTICS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'ISOLATION': (('LEVEL', 'REPEATABLE', 'READ'), ('LEVEL', 'READ', 'COMMITTED'), ('LEVEL', 'READ', 'UNCOMITTED'), ('LEVEL', 'SERIALIZABLE')), 'READ': ('WRITE', 'ONLY')}
CONFLICT_ACTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'ABORT': (), 'FAIL': (), 'IGNORE': (), 'REPLACE': (), 'ROLLBACK': (), 'UPDATE': (), 'DO': ('NOTHING', 'UPDATE')}
TRIGGER_TIMING: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'INSTEAD': (('OF',),), 'BEFORE': (), 'AFTER': ()}
TRIGGER_DEFERRABLE: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'NOT': (('DEFERRABLE',),), 'DEFERRABLE': ()}
CREATE_SEQUENCE: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'SCALE': ('EXTEND', 'NOEXTEND'), 'SHARD': ('EXTEND', 'NOEXTEND'), 'NO': ('CYCLE', 'CACHE', 'MAXVALUE', 'MINVALUE'), 'SESSION': (), 'GLOBAL': (), 'KEEP': (), 'NOKEEP': (), 'ORDER': (), 'NOORDER': (), 'NOCACHE': (), 'CYCLE': (), 'NOCYCLE': (), 'NOMINVALUE': (), 'NOMAXVALUE': (), 'NOSCALE': (), 'NOSHARD': ()}
ISOLATED_LOADING_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'FOR': ('ALL', 'INSERT', 'NONE')}
USABLES: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'ROLE': (), 'WAREHOUSE': (), 'DATABASE': (), 'SCHEMA': (), 'CATALOG': ()}
CAST_ACTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'RENAME': ('FIELDS',), 'ADD': ('FIELDS',)}
SCHEMA_BINDING_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'TYPE': ('EVOLUTION',), 'BINDING': (), 'COMPENSATION': (), 'EVOLUTION': ()}
PROCEDURE_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {}
EXECUTE_AS_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'CALLER': (), 'SELF': (), 'OWNER': ()}
KEY_CONSTRAINT_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'NOT': ('ENFORCED',), 'MATCH': ('FULL', 'PARTIAL', 'SIMPLE'), 'INITIALLY': ('DEFERRED', 'IMMEDIATE'), 'USING': ('BTREE', 'HASH'), 'DEFERRABLE': (), 'NORELY': (), 'RELY': ()}
WINDOW_EXCLUDE_OPTIONS: ClassVar[dict[str, Sequence[Union[Sequence[str], str]]]] = {'NO': ('OTHERS',), 'CURRENT': ('ROW',), 'GROUP': (), 'TIES': ()}
INSERT_ALTERNATIVES: ClassVar = {'FAIL', 'IGNORE', 'REPLACE', 'ROLLBACK', 'ABORT'}
CLONE_KEYWORDS: ClassVar = {'COPY', 'CLONE'}
VERSION_PHRASES: ClassVar[dict[tuple[str, ...], str]] = {('FOR', 'SYSTEM_TIME'): 'TIMESTAMP', ('FOR', 'SYSTEM', 'TIME'): 'TIMESTAMP', ('FOR', 'TIMESTAMP'): 'TIMESTAMP', ('FOR', 'VERSION'): 'VERSION', ('TIMESTAMP', 'AS', 'OF'): 'TIMESTAMP', ('VERSION', 'AS', 'OF'): 'VERSION'}
HISTORICAL_DATA_PREFIX: ClassVar = {'BEFORE', 'END', 'AT'}
HISTORICAL_DATA_KIND: ClassVar = {'VERSION', 'TIMESTAMP', 'STREAM', 'OFFSET', 'STATEMENT'}
OPCLASS_FOLLOW_KEYWORDS: ClassVar = {'NULLS', 'WITH', 'ASC', 'DESC'}
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 = {'VIEW_METADATA', 'ENCRYPTION', 'SCHEMABINDING'}
WINDOW_ALIAS_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 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 = {'PRECEDING', 'FOLLOWING'}
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 = {'FORMAT_OPTIONS', 'CREDENTIAL', 'FILE_FORMAT', 'COPY_OPTIONS'}
IS_JSON_PREDICATE_KIND: ClassVar = {'VALUE', 'SCALAR', 'OBJECT', 'ARRAY'}
ODBC_DATETIME_LITERALS: ClassVar[dict[str, type[sqlglot.expressions.core.Expr]]] = {}
ON_CONDITION_TOKENS: ClassVar = {'EMPTY', 'TRUE', 'NULL', 'ERROR', 'FALSE'}
PRIVILEGE_FOLLOW_TOKENS: ClassVar = {<TokenType.L_PAREN: 1>, <TokenType.ON: 338>, <TokenType.COMMA: 7>}
DESCRIBE_STYLES: ClassVar = {'FORMATTED', 'EXTENDED', 'HISTORY', 'ANALYZE'}
SET_ASSIGNMENT_DELIMITERS: ClassVar = {'TO', '=', ':='}
ANALYZE_STYLES: ClassVar = {'VERBOSE', 'LOCAL', 'SKIP_LOCKED', 'NO_WRITE_TO_BINLOG', 'FULL', 'SAMPLE', 'BUFFER_USAGE_LIMIT'}
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', 'BREADTH', 'CYCLE'}
SECURITY_PROPERTY_KEYWORDS: ClassVar = {'DEFINER', 'NONE', 'INVOKER'}
STRICT_CAST: ClassVar = True
PREFIXED_PIVOT_COLUMNS: ClassVar = False
IDENTIFY_PIVOT_STRINGS: ClassVar = False
UNPIVOT_VALUE_COLUMNS_FIRST: ClassVar = False
PIVOT_COLUMN_NAMING: ClassVar[str] = 'agg_name_if_aliased'
LOG_DEFAULTS_TO_LN: ClassVar = False
TABLESAMPLE_CSV: ClassVar = False
DEFAULT_SAMPLING_METHOD: ClassVar[str | None] = None
SET_REQUIRES_ASSIGNMENT_DELIMITER: ClassVar = True
TRIM_PATTERN_FIRST: ClassVar = False
STRING_ALIASES: ClassVar = False
MODIFIERS_ATTACHED_TO_SET_OP: ClassVar = True
SET_OP_MODIFIERS: ClassVar = {'offset', 'order', 'limit'}
NO_PAREN_IF_COMMANDS: ClassVar = True
JSON_ARROWS_REQUIRE_JSON_TYPE: ClassVar = False
COLON_IS_VARIANT_EXTRACT: ClassVar = False
COLON_CHAIN_IS_SINGLE_EXTRACT: ClassVar = True
VALUES_FOLLOWED_BY_PAREN: ClassVar = True
SUPPORTS_IMPLICIT_UNNEST: ClassVar = False
SUPPORTS_DIGIT_PREFIXED_FIELD_NAMES: ClassVar = False
INTERVAL_SPANS: ClassVar = True
SUPPORTS_PARTITION_SELECTION: ClassVar = False
WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: ClassVar = True
OPTIONAL_ALIAS_TOKEN_CTE: ClassVar = True
ALTER_RENAME_REQUIRES_COLUMN: ClassVar = True
ALTER_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:
5916    def parse_set_operation(
5917        self, this: exp.Expr | None, consume_pipe: bool = False
5918    ) -> exp.Expr | None:
5919        start = self._index
5920        _, side_token, kind_token = self._parse_join_parts()
5921
5922        side = side_token.text if side_token else None
5923        kind = kind_token.text if kind_token else None
5924
5925        if not self._match_set(self.SET_OPERATIONS):
5926            self._retreat(start)
5927            return None
5928
5929        token_type = self._prev.token_type
5930
5931        if token_type == TokenType.UNION:
5932            operation: type[exp.SetOperation] = exp.Union
5933        elif token_type == TokenType.EXCEPT:
5934            operation = exp.Except
5935        else:
5936            operation = exp.Intersect
5937
5938        comments = self._prev.comments
5939
5940        if self._match(TokenType.DISTINCT):
5941            distinct: bool | None = True
5942        elif self._match(TokenType.ALL):
5943            distinct = False
5944        else:
5945            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
5946            if distinct is None:
5947                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
5948
5949        by_name = (
5950            self._match_text_seq("BY", "NAME")
5951            or self._match_text_seq("STRICT", "CORRESPONDING")
5952            or None
5953        )
5954        if self._match_text_seq("CORRESPONDING"):
5955            by_name = True
5956            if not side and not kind:
5957                kind = "INNER"
5958
5959        on_column_list = None
5960        if by_name and self._match_texts(("ON", "BY")):
5961            on_column_list = self._parse_wrapped_csv(self._parse_column)
5962
5963        expression = self._parse_select(
5964            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
5965        )
5966
5967        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
5968        # in _parse_cte and so that alias pushdown can reach into set operation branches
5969        if isinstance(this, exp.Values):
5970            this = self._values_to_select(this)
5971        if isinstance(expression, exp.Values):
5972            expression = self._values_to_select(expression)
5973
5974        if isinstance(this, exp.Alias) and isinstance(this.this, exp.Subquery):
5975            subquery = this.this
5976            subquery.set("alias", exp.TableAlias(this=this.args["alias"]))
5977            subquery.add_comments(this.pop_comments())
5978            this = subquery
5979
5980        return self.expression(
5981            operation(
5982                this=this,
5983                distinct=distinct,
5984                by_name=by_name,
5985                expression=expression,
5986                side=side,
5987                kind=kind,
5988                on=on_column_list,
5989            ),
5990            comments=comments,
5991        )
def build_cast(self, strict: bool, **kwargs) -> sqlglot.expressions.core.Expr:
10415    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10416        exp_class = exp.Cast if strict else exp.TryCast
10417
10418        if exp_class == exp.TryCast:
10419            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10420
10421        return self.expression(exp_class(**kwargs))