close
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(self: Parser, this: exp.Expr, key: exp.Expr) -> exp.JSONBContains:
  329    return self.expression(exp.JSONBContains(this=this, expression=key))
  330
  331
  332SENTINEL_NONE: Token = Token(TokenType.SENTINEL, "SENTINEL")
  333
  334
  335class Parser:
  336    """
  337    Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.
  338
  339    Args:
  340        error_level: The desired error level.
  341            Default: ErrorLevel.IMMEDIATE
  342        error_message_context: The amount of context to capture from a query string when displaying
  343            the error message (in number of characters).
  344            Default: 100
  345        max_errors: Maximum number of error messages to include in a raised ParseError.
  346            This is only relevant if error_level is ErrorLevel.RAISE.
  347            Default: 3
  348        max_nodes: Maximum number of AST nodes to prevent memory exhaustion.
  349            Set to -1 (default) to disable the check.
  350    """
  351
  352    __slots__ = (
  353        "error_level",
  354        "error_message_context",
  355        "max_errors",
  356        "max_nodes",
  357        "dialect",
  358        "sql",
  359        "errors",
  360        "_tokens",
  361        "_index",
  362        "_curr",
  363        "_next",
  364        "_prev",
  365        "_prev_comments",
  366        "_pipe_cte_counter",
  367        "_chunks",
  368        "_chunk_index",
  369        "_tokens_size",
  370        "_node_count",
  371    )
  372
  373    FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = {
  374        **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()},
  375        **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce),
  376        "ARRAY": lambda args, dialect: exp.Array(expressions=args),
  377        "ARRAYAGG": lambda args, dialect: exp.ArrayAgg(
  378            this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None
  379        ),
  380        "ARRAY_AGG": 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_APPEND": build_array_append,
  384        "ARRAY_CAT": build_array_concat,
  385        "ARRAY_CONCAT": build_array_concat,
  386        "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args),
  387        "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args),
  388        "ARRAY_PREPEND": build_array_prepend,
  389        "ARRAY_REMOVE": build_array_remove,
  390        "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True),
  391        "CONCAT": lambda args, dialect: exp.Concat(
  392            expressions=args,
  393            safe=not dialect.STRICT_STRING_CONCAT,
  394            coalesce=dialect.CONCAT_COALESCE,
  395        ),
  396        "CONCAT_WS": lambda args, dialect: exp.ConcatWs(
  397            expressions=args,
  398            safe=not dialect.STRICT_STRING_CONCAT,
  399            coalesce=dialect.CONCAT_WS_COALESCE,
  400        ),
  401        "CONVERT_TIMEZONE": build_convert_timezone,
  402        "DATE_TO_DATE_STR": lambda args: exp.Cast(
  403            this=seq_get(args, 0),
  404            to=exp.DataType(this=exp.DType.TEXT),
  405        ),
  406        "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray(
  407            start=seq_get(args, 0),
  408            end=seq_get(args, 1),
  409            step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")),
  410        ),
  411        "GENERATE_UUID": lambda args, dialect: exp.Uuid(
  412            is_string=dialect.UUID_IS_STRING_TYPE or None
  413        ),
  414        "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)),
  415        "GREATEST": lambda args, dialect: exp.Greatest(
  416            this=seq_get(args, 0),
  417            expressions=args[1:],
  418            ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS,
  419        ),
  420        "LEAST": lambda args, dialect: exp.Least(
  421            this=seq_get(args, 0),
  422            expressions=args[1:],
  423            ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS,
  424        ),
  425        "HEX": build_hex,
  426        "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract),
  427        "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar),
  428        "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar),
  429        "JSON_KEYS": lambda args, dialect: exp.JSONKeys(
  430            this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1))
  431        ),
  432        "LIKE": build_like,
  433        "LOG": build_logarithm,
  434        "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)),
  435        "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)),
  436        "LOWER": build_lower,
  437        "LPAD": lambda args: build_pad(args),
  438        "LEFTPAD": lambda args: build_pad(args),
  439        "LTRIM": lambda args: build_trim(args),
  440        "MOD": build_mod,
  441        "RIGHTPAD": lambda args: build_pad(args, is_left=False),
  442        "RPAD": lambda args: build_pad(args, is_left=False),
  443        "RTRIM": lambda args: build_trim(args, is_left=False),
  444        "SCOPE_RESOLUTION": lambda args: (
  445            exp.ScopeResolution(expression=seq_get(args, 0))
  446            if len(args) != 2
  447            else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1))
  448        ),
  449        "STRPOS": exp.StrPosition.from_arg_list,
  450        "CHARINDEX": lambda args: build_locate_strposition(args),
  451        "INSTR": exp.StrPosition.from_arg_list,
  452        "LOCATE": lambda args: build_locate_strposition(args),
  453        "TIME_TO_TIME_STR": lambda args: exp.Cast(
  454            this=seq_get(args, 0),
  455            to=exp.DataType(this=exp.DType.TEXT),
  456        ),
  457        "TO_HEX": build_hex,
  458        "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring(
  459            this=exp.Cast(
  460                this=seq_get(args, 0),
  461                to=exp.DataType(this=exp.DType.TEXT),
  462            ),
  463            start=exp.Literal.number(1),
  464            length=exp.Literal.number(10),
  465        ),
  466        "UNNEST": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))),
  467        "UPPER": build_upper,
  468        "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None),
  469        "UUID_STRING": lambda args, dialect: exp.Uuid(
  470            this=seq_get(args, 0),
  471            name=seq_get(args, 1),
  472            is_string=dialect.UUID_IS_STRING_TYPE or None,
  473        ),
  474        "VAR_MAP": build_var_map,
  475    }
  476
  477    NO_PAREN_FUNCTIONS: t.ClassVar[dict] = {
  478        TokenType.CURRENT_DATE: exp.CurrentDate,
  479        TokenType.CURRENT_DATETIME: exp.CurrentDate,
  480        TokenType.CURRENT_TIME: exp.CurrentTime,
  481        TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp,
  482        TokenType.CURRENT_USER: exp.CurrentUser,
  483        TokenType.CURRENT_ROLE: exp.CurrentRole,
  484    }
  485
  486    STRUCT_TYPE_TOKENS: t.ClassVar = {
  487        TokenType.NESTED,
  488        TokenType.OBJECT,
  489        TokenType.STRUCT,
  490        TokenType.UNION,
  491    }
  492
  493    NESTED_TYPE_TOKENS: t.ClassVar = {
  494        TokenType.ARRAY,
  495        TokenType.LIST,
  496        TokenType.LOWCARDINALITY,
  497        TokenType.MAP,
  498        TokenType.NULLABLE,
  499        TokenType.RANGE,
  500        *STRUCT_TYPE_TOKENS,
  501    }
  502
  503    ENUM_TYPE_TOKENS: t.ClassVar = {
  504        TokenType.DYNAMIC,
  505        TokenType.ENUM,
  506        TokenType.ENUM8,
  507        TokenType.ENUM16,
  508    }
  509
  510    AGGREGATE_TYPE_TOKENS: t.ClassVar = {
  511        TokenType.AGGREGATEFUNCTION,
  512        TokenType.SIMPLEAGGREGATEFUNCTION,
  513    }
  514
  515    TYPE_TOKENS: t.ClassVar = {
  516        TokenType.BIT,
  517        TokenType.BOOLEAN,
  518        TokenType.TINYINT,
  519        TokenType.UTINYINT,
  520        TokenType.SMALLINT,
  521        TokenType.USMALLINT,
  522        TokenType.INT,
  523        TokenType.UINT,
  524        TokenType.BIGINT,
  525        TokenType.UBIGINT,
  526        TokenType.BIGNUM,
  527        TokenType.INT128,
  528        TokenType.UINT128,
  529        TokenType.INT256,
  530        TokenType.UINT256,
  531        TokenType.MEDIUMINT,
  532        TokenType.UMEDIUMINT,
  533        TokenType.FIXEDSTRING,
  534        TokenType.FLOAT,
  535        TokenType.DOUBLE,
  536        TokenType.UDOUBLE,
  537        TokenType.CHAR,
  538        TokenType.NCHAR,
  539        TokenType.VARCHAR,
  540        TokenType.NVARCHAR,
  541        TokenType.BPCHAR,
  542        TokenType.TEXT,
  543        TokenType.MEDIUMTEXT,
  544        TokenType.LONGTEXT,
  545        TokenType.BLOB,
  546        TokenType.MEDIUMBLOB,
  547        TokenType.LONGBLOB,
  548        TokenType.BINARY,
  549        TokenType.VARBINARY,
  550        TokenType.JSON,
  551        TokenType.JSONB,
  552        TokenType.INTERVAL,
  553        TokenType.TINYBLOB,
  554        TokenType.TINYTEXT,
  555        TokenType.TIME,
  556        TokenType.TIMETZ,
  557        TokenType.TIME_NS,
  558        TokenType.TIMESTAMP,
  559        TokenType.TIMESTAMP_S,
  560        TokenType.TIMESTAMP_MS,
  561        TokenType.TIMESTAMP_NS,
  562        TokenType.TIMESTAMPTZ,
  563        TokenType.TIMESTAMPLTZ,
  564        TokenType.TIMESTAMPNTZ,
  565        TokenType.DATETIME,
  566        TokenType.DATETIME2,
  567        TokenType.DATETIME64,
  568        TokenType.SMALLDATETIME,
  569        TokenType.DATE,
  570        TokenType.DATE32,
  571        TokenType.INT4RANGE,
  572        TokenType.INT4MULTIRANGE,
  573        TokenType.INT8RANGE,
  574        TokenType.INT8MULTIRANGE,
  575        TokenType.NUMRANGE,
  576        TokenType.NUMMULTIRANGE,
  577        TokenType.TSRANGE,
  578        TokenType.TSMULTIRANGE,
  579        TokenType.TSTZRANGE,
  580        TokenType.TSTZMULTIRANGE,
  581        TokenType.DATERANGE,
  582        TokenType.DATEMULTIRANGE,
  583        TokenType.DECIMAL,
  584        TokenType.DECIMAL32,
  585        TokenType.DECIMAL64,
  586        TokenType.DECIMAL128,
  587        TokenType.DECIMAL256,
  588        TokenType.DECFLOAT,
  589        TokenType.UDECIMAL,
  590        TokenType.BIGDECIMAL,
  591        TokenType.UUID,
  592        TokenType.GEOGRAPHY,
  593        TokenType.GEOGRAPHYPOINT,
  594        TokenType.GEOMETRY,
  595        TokenType.POINT,
  596        TokenType.RING,
  597        TokenType.LINESTRING,
  598        TokenType.MULTILINESTRING,
  599        TokenType.POLYGON,
  600        TokenType.MULTIPOLYGON,
  601        TokenType.HLLSKETCH,
  602        TokenType.HSTORE,
  603        TokenType.PSEUDO_TYPE,
  604        TokenType.SUPER,
  605        TokenType.SERIAL,
  606        TokenType.SMALLSERIAL,
  607        TokenType.BIGSERIAL,
  608        TokenType.XML,
  609        TokenType.YEAR,
  610        TokenType.USERDEFINED,
  611        TokenType.MONEY,
  612        TokenType.SMALLMONEY,
  613        TokenType.ROWVERSION,
  614        TokenType.IMAGE,
  615        TokenType.VARIANT,
  616        TokenType.VECTOR,
  617        TokenType.VOID,
  618        TokenType.OBJECT,
  619        TokenType.OBJECT_IDENTIFIER,
  620        TokenType.INET,
  621        TokenType.IPADDRESS,
  622        TokenType.IPPREFIX,
  623        TokenType.IPV4,
  624        TokenType.IPV6,
  625        TokenType.UNKNOWN,
  626        TokenType.NOTHING,
  627        TokenType.NULL,
  628        TokenType.NAME,
  629        TokenType.TDIGEST,
  630        TokenType.DYNAMIC,
  631        *ENUM_TYPE_TOKENS,
  632        *NESTED_TYPE_TOKENS,
  633        *AGGREGATE_TYPE_TOKENS,
  634    }
  635
  636    SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = {
  637        TokenType.BIGINT: TokenType.UBIGINT,
  638        TokenType.INT: TokenType.UINT,
  639        TokenType.MEDIUMINT: TokenType.UMEDIUMINT,
  640        TokenType.SMALLINT: TokenType.USMALLINT,
  641        TokenType.TINYINT: TokenType.UTINYINT,
  642        TokenType.DECIMAL: TokenType.UDECIMAL,
  643        TokenType.DOUBLE: TokenType.UDOUBLE,
  644    }
  645
  646    SUBQUERY_PREDICATES: t.ClassVar = {
  647        TokenType.ANY: exp.Any,
  648        TokenType.ALL: exp.All,
  649        TokenType.EXISTS: exp.Exists,
  650        TokenType.SOME: exp.Any,
  651    }
  652
  653    SUBQUERY_TOKENS: t.ClassVar = {
  654        TokenType.SELECT,
  655        TokenType.WITH,
  656        TokenType.FROM,
  657    }
  658
  659    RESERVED_TOKENS: t.ClassVar = {
  660        *Tokenizer.SINGLE_TOKENS.values(),
  661        TokenType.SELECT,
  662    } - {TokenType.IDENTIFIER}
  663
  664    # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers,
  665    # string literals), so they must never be treated as keywords when matching by text
  666    TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset(
  667        {
  668            TokenType.BIT_STRING,
  669            TokenType.BYTE_STRING,
  670            TokenType.HEREDOC_STRING,
  671            TokenType.HEX_STRING,
  672            TokenType.IDENTIFIER,
  673            TokenType.NATIONAL_STRING,
  674            TokenType.RAW_STRING,
  675            TokenType.STRING,
  676            TokenType.UNICODE_STRING,
  677        }
  678    )
  679
  680    DB_CREATABLES: t.ClassVar = {
  681        TokenType.DATABASE,
  682        TokenType.DICTIONARY,
  683        TokenType.FILE_FORMAT,
  684        TokenType.MODEL,
  685        TokenType.NAMESPACE,
  686        TokenType.SCHEMA,
  687        TokenType.SEMANTIC_VIEW,
  688        TokenType.SEQUENCE,
  689        TokenType.SINK,
  690        TokenType.SOURCE,
  691        TokenType.STAGE,
  692        TokenType.STORAGE_INTEGRATION,
  693        TokenType.STREAMLIT,
  694        TokenType.TABLE,
  695        TokenType.TAG,
  696        TokenType.VIEW,
  697        TokenType.WAREHOUSE,
  698    }
  699
  700    CREATABLES: t.ClassVar = {
  701        TokenType.COLUMN,
  702        TokenType.CONSTRAINT,
  703        TokenType.FOREIGN_KEY,
  704        TokenType.FUNCTION,
  705        TokenType.INDEX,
  706        TokenType.PROCEDURE,
  707        TokenType.TRIGGER,
  708        TokenType.TYPE,
  709        *DB_CREATABLES,
  710    }
  711
  712    TRIGGER_EVENTS: t.ClassVar = {
  713        TokenType.INSERT,
  714        TokenType.UPDATE,
  715        TokenType.DELETE,
  716        TokenType.TRUNCATE,
  717    }
  718
  719    ALTERABLES: t.ClassVar = {
  720        TokenType.INDEX,
  721        TokenType.TABLE,
  722        TokenType.VIEW,
  723        TokenType.SESSION,
  724    }
  725
  726    # Tokens that can represent identifiers
  727    ID_VAR_TOKENS: t.ClassVar[set] = {
  728        TokenType.ALL,
  729        TokenType.ANALYZE,
  730        TokenType.ATTACH,
  731        TokenType.VAR,
  732        TokenType.ANTI,
  733        TokenType.APPLY,
  734        TokenType.ASC,
  735        TokenType.ASOF,
  736        TokenType.AUTO_INCREMENT,
  737        TokenType.BEGIN,
  738        TokenType.BPCHAR,
  739        TokenType.CACHE,
  740        TokenType.CASE,
  741        TokenType.COLLATE,
  742        TokenType.COMMAND,
  743        TokenType.COMMENT,
  744        TokenType.COMMIT,
  745        TokenType.CONSTRAINT,
  746        TokenType.COPY,
  747        TokenType.CUBE,
  748        TokenType.CURRENT_SCHEMA,
  749        TokenType.DECLARE,
  750        TokenType.DEFAULT,
  751        TokenType.DELETE,
  752        TokenType.DESC,
  753        TokenType.DESCRIBE,
  754        TokenType.DETACH,
  755        TokenType.DICTIONARY,
  756        TokenType.DIV,
  757        TokenType.END,
  758        TokenType.EXECUTE,
  759        TokenType.EXPORT,
  760        TokenType.ESCAPE,
  761        TokenType.FALSE,
  762        TokenType.FIRST,
  763        TokenType.FILE,
  764        TokenType.FILTER,
  765        TokenType.FINAL,
  766        TokenType.FORMAT,
  767        TokenType.FULL,
  768        TokenType.GET,
  769        TokenType.IDENTIFIER,
  770        TokenType.INOUT,
  771        TokenType.IS,
  772        TokenType.ISNULL,
  773        TokenType.INTERVAL,
  774        TokenType.KEEP,
  775        TokenType.KILL,
  776        TokenType.LEFT,
  777        TokenType.LIMIT,
  778        TokenType.LOAD,
  779        TokenType.LOCK,
  780        TokenType.MATCH,
  781        TokenType.MERGE,
  782        TokenType.NATURAL,
  783        TokenType.NEXT,
  784        TokenType.OFFSET,
  785        TokenType.OPERATOR,
  786        TokenType.ORDINALITY,
  787        TokenType.OUT,
  788        TokenType.OVER,
  789        TokenType.OVERLAPS,
  790        TokenType.OVERWRITE,
  791        TokenType.PARTITION,
  792        TokenType.PERCENT,
  793        TokenType.PIVOT,
  794        TokenType.PROJECTION,
  795        TokenType.PRAGMA,
  796        TokenType.PUT,
  797        TokenType.RANGE,
  798        TokenType.RECURSIVE,
  799        TokenType.REFERENCES,
  800        TokenType.REFRESH,
  801        TokenType.RENAME,
  802        TokenType.REPLACE,
  803        TokenType.RIGHT,
  804        TokenType.ROLLUP,
  805        TokenType.ROW,
  806        TokenType.ROWS,
  807        TokenType.SEMI,
  808        TokenType.SET,
  809        TokenType.SETTINGS,
  810        TokenType.SHOW,
  811        TokenType.STREAM,
  812        TokenType.STREAMLIT,
  813        TokenType.TEMPORARY,
  814        TokenType.TOP,
  815        TokenType.TRUE,
  816        TokenType.TRUNCATE,
  817        TokenType.UNIQUE,
  818        TokenType.UNNEST,
  819        TokenType.UNPIVOT,
  820        TokenType.UPDATE,
  821        TokenType.USE,
  822        TokenType.VOLATILE,
  823        TokenType.WINDOW,
  824        TokenType.CURRENT_CATALOG,
  825        TokenType.LOCALTIME,
  826        TokenType.LOCALTIMESTAMP,
  827        TokenType.SESSION_USER,
  828        TokenType.STRAIGHT_JOIN,
  829        *ALTERABLES,
  830        *CREATABLES,
  831        *SUBQUERY_PREDICATES,
  832        *TYPE_TOKENS,
  833        *NO_PAREN_FUNCTIONS,
  834    } - {TokenType.UNION}
  835
  836    TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - {
  837        TokenType.ANTI,
  838        TokenType.ASOF,
  839        TokenType.FULL,
  840        TokenType.LEFT,
  841        TokenType.LOCK,
  842        TokenType.NATURAL,
  843        TokenType.RIGHT,
  844        TokenType.SEMI,
  845        TokenType.WINDOW,
  846    }
  847
  848    ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS
  849
  850    COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS
  851
  852    ARRAY_CONSTRUCTORS: t.ClassVar = {
  853        "ARRAY": exp.Array,
  854        "LIST": exp.List,
  855    }
  856
  857    COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS}
  858
  859    UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET}
  860
  861    TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"}
  862
  863    # Tokens that indicate a simple column reference
  864    IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER})
  865
  866    BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE})
  867
  868    # Postfix tokens that prevent the bare column fast path
  869    COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  870        {
  871            TokenType.L_PAREN,
  872            TokenType.L_BRACKET,
  873            TokenType.L_BRACE,
  874            TokenType.COLON,
  875            TokenType.JOIN_MARKER,
  876        }
  877    )
  878
  879    TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  880        {
  881            TokenType.L_PAREN,
  882            TokenType.L_BRACKET,
  883            TokenType.L_BRACE,
  884            TokenType.PIVOT,
  885            TokenType.UNPIVOT,
  886            TokenType.TABLE_SAMPLE,
  887        }
  888    )
  889
  890    FUNC_TOKENS: t.ClassVar = {
  891        TokenType.COLLATE,
  892        TokenType.COMMAND,
  893        TokenType.CURRENT_DATE,
  894        TokenType.CURRENT_DATETIME,
  895        TokenType.CURRENT_SCHEMA,
  896        TokenType.CURRENT_TIMESTAMP,
  897        TokenType.CURRENT_TIME,
  898        TokenType.CURRENT_USER,
  899        TokenType.CURRENT_CATALOG,
  900        TokenType.DECLARE,
  901        TokenType.FILTER,
  902        TokenType.FIRST,
  903        TokenType.FORMAT,
  904        TokenType.GET,
  905        TokenType.GLOB,
  906        TokenType.IDENTIFIER,
  907        TokenType.INDEX,
  908        TokenType.ISNULL,
  909        TokenType.ILIKE,
  910        TokenType.INSERT,
  911        TokenType.LIKE,
  912        TokenType.LOCALTIME,
  913        TokenType.LOCALTIMESTAMP,
  914        TokenType.MERGE,
  915        TokenType.NEXT,
  916        TokenType.OFFSET,
  917        TokenType.PRIMARY_KEY,
  918        TokenType.RANGE,
  919        TokenType.REPLACE,
  920        TokenType.RLIKE,
  921        TokenType.ROW,
  922        TokenType.SESSION_USER,
  923        TokenType.UNNEST,
  924        TokenType.VAR,
  925        TokenType.LEFT,
  926        TokenType.RIGHT,
  927        TokenType.SEQUENCE,
  928        TokenType.DATE,
  929        TokenType.DATETIME,
  930        TokenType.TABLE,
  931        TokenType.TIMESTAMP,
  932        TokenType.TIMESTAMPTZ,
  933        TokenType.TRUNCATE,
  934        TokenType.UTC_DATE,
  935        TokenType.UTC_TIME,
  936        TokenType.UTC_TIMESTAMP,
  937        TokenType.WINDOW,
  938        TokenType.XOR,
  939        *TYPE_TOKENS,
  940        *SUBQUERY_PREDICATES,
  941    }
  942
  943    CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  944        TokenType.AND: exp.And,
  945    }
  946
  947    ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  948        TokenType.COLON_EQ: exp.PropertyEQ,
  949    }
  950
  951    DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  952        TokenType.OR: exp.Or,
  953    }
  954
  955    EQUALITY: t.ClassVar = {
  956        TokenType.EQ: exp.EQ,
  957        TokenType.NEQ: exp.NEQ,
  958        TokenType.NULLSAFE_EQ: exp.NullSafeEQ,
  959    }
  960
  961    COMPARISON: t.ClassVar = {
  962        TokenType.GT: exp.GT,
  963        TokenType.GTE: exp.GTE,
  964        TokenType.LT: exp.LT,
  965        TokenType.LTE: exp.LTE,
  966    }
  967
  968    BITWISE: t.ClassVar = {
  969        TokenType.AMP: exp.BitwiseAnd,
  970        TokenType.CARET: exp.BitwiseXor,
  971        TokenType.PIPE: exp.BitwiseOr,
  972    }
  973
  974    TERM: t.ClassVar = {
  975        TokenType.DASH: exp.Sub,
  976        TokenType.PLUS: exp.Add,
  977        TokenType.MOD: exp.Mod,
  978        TokenType.COLLATE: exp.Collate,
  979    }
  980
  981    FACTOR: t.ClassVar = {
  982        TokenType.DIV: exp.IntDiv,
  983        TokenType.LR_ARROW: exp.Distance,
  984        TokenType.LLRR_ARROW: exp.DistanceNd,
  985        TokenType.SLASH: exp.Div,
  986        TokenType.STAR: exp.Mul,
  987    }
  988
  989    EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {}
  990
  991    TIMES: t.ClassVar = {
  992        TokenType.TIME,
  993        TokenType.TIMETZ,
  994    }
  995
  996    TIMESTAMPS: t.ClassVar = {
  997        TokenType.TIMESTAMP,
  998        TokenType.TIMESTAMPNTZ,
  999        TokenType.TIMESTAMPTZ,
 1000        TokenType.TIMESTAMPLTZ,
 1001        *TIMES,
 1002    }
 1003
 1004    SET_OPERATIONS: t.ClassVar = {
 1005        TokenType.UNION,
 1006        TokenType.INTERSECT,
 1007        TokenType.EXCEPT,
 1008    }
 1009
 1010    JOIN_METHODS: t.ClassVar = {
 1011        TokenType.ASOF,
 1012        TokenType.NATURAL,
 1013        TokenType.POSITIONAL,
 1014    }
 1015
 1016    JOIN_SIDES: t.ClassVar = {
 1017        TokenType.LEFT,
 1018        TokenType.RIGHT,
 1019        TokenType.FULL,
 1020    }
 1021
 1022    JOIN_KINDS: t.ClassVar = {
 1023        TokenType.ANTI,
 1024        TokenType.CROSS,
 1025        TokenType.INNER,
 1026        TokenType.OUTER,
 1027        TokenType.SEMI,
 1028        TokenType.STRAIGHT_JOIN,
 1029    }
 1030
 1031    JOIN_HINTS: t.ClassVar[set[str]] = set()
 1032
 1033    # Tokens that unambiguously end a table reference on the fast path
 1034    TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset(
 1035        {
 1036            TokenType.COMMA,
 1037            TokenType.GROUP_BY,
 1038            TokenType.HAVING,
 1039            TokenType.JOIN,
 1040            TokenType.LIMIT,
 1041            TokenType.ON,
 1042            TokenType.ORDER_BY,
 1043            TokenType.R_PAREN,
 1044            TokenType.SEMICOLON,
 1045            TokenType.SENTINEL,
 1046            TokenType.WHERE,
 1047            *SET_OPERATIONS,
 1048            *JOIN_KINDS,
 1049            *JOIN_METHODS,
 1050            *JOIN_SIDES,
 1051        }
 1052    )
 1053
 1054    LAMBDAS: t.ClassVar = {
 1055        TokenType.ARROW: lambda self, expressions: self.expression(
 1056            exp.Lambda(
 1057                this=self._replace_lambda(
 1058                    self._parse_disjunction(),
 1059                    expressions,
 1060                ),
 1061                expressions=expressions,
 1062            )
 1063        ),
 1064        TokenType.FARROW: lambda self, expressions: self.expression(
 1065            exp.Kwarg(
 1066                this=exp.var(expressions[0].name),
 1067                expression=self._parse_disjunction() or self._parse_select(),
 1068            )
 1069        ),
 1070    }
 1071
 1072    # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake
 1073    TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False
 1074
 1075    LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN})
 1076
 1077    COLUMN_OPERATORS: t.ClassVar = {
 1078        TokenType.DOT: None,
 1079        TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)),
 1080        TokenType.DCOLON: lambda self, this, to: self.build_cast(
 1081            strict=self.STRICT_CAST, this=this, to=to
 1082        ),
 1083        TokenType.ARROW: lambda self, this, path: self.expression(
 1084            exp.JSONExtract(
 1085                this=this,
 1086                expression=self.dialect.to_json_path(path),
 1087                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1088            )
 1089        ),
 1090        TokenType.DARROW: lambda self, this, path: self.expression(
 1091            exp.JSONExtractScalar(
 1092                this=this,
 1093                expression=self.dialect.to_json_path(path),
 1094                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1095                scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY,
 1096            )
 1097        ),
 1098        TokenType.HASH_ARROW: lambda self, this, path: self.expression(
 1099            exp.JSONBExtract(this=this, expression=path)
 1100        ),
 1101        TokenType.DHASH_ARROW: lambda self, this, path: self.expression(
 1102            exp.JSONBExtractScalar(this=this, expression=path)
 1103        ),
 1104        TokenType.PLACEHOLDER: lambda self, this, key: self.expression(
 1105            exp.JSONBContains(this=this, expression=key)
 1106        ),
 1107    }
 1108
 1109    # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator"
 1110    # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs).
 1111    JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {}
 1112
 1113    CAST_COLUMN_OPERATORS: t.ClassVar = {
 1114        TokenType.DOTCOLON,
 1115        TokenType.DCOLON,
 1116    }
 1117
 1118    EXPRESSION_PARSERS: t.ClassVar = {
 1119        exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY),
 1120        exp.Column: lambda self: self._parse_column(),
 1121        exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()),
 1122        exp.Condition: lambda self: self._parse_disjunction(),
 1123        exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True),
 1124        exp.Expr: lambda self: self._parse_expression(),
 1125        exp.From: lambda self: self._parse_from(joins=True),
 1126        exp.GrantPrincipal: lambda self: self._parse_grant_principal(),
 1127        exp.GrantPrivilege: lambda self: self._parse_grant_privilege(),
 1128        exp.Group: lambda self: self._parse_group(),
 1129        exp.Having: lambda self: self._parse_having(),
 1130        exp.Hint: lambda self: self._parse_hint_body(),
 1131        exp.Identifier: lambda self: self._parse_id_var(),
 1132        exp.Join: lambda self: self._parse_join(),
 1133        exp.Lambda: lambda self: self._parse_lambda(),
 1134        exp.Lateral: lambda self: self._parse_lateral(),
 1135        exp.Limit: lambda self: self._parse_limit(),
 1136        exp.Offset: lambda self: self._parse_offset(),
 1137        exp.Order: lambda self: self._parse_order(),
 1138        exp.Ordered: lambda self: self._parse_ordered(),
 1139        exp.Properties: lambda self: self._parse_properties(),
 1140        exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(),
 1141        exp.Qualify: lambda self: self._parse_qualify(),
 1142        exp.Returning: lambda self: self._parse_returning(),
 1143        exp.Select: lambda self: self._parse_select(),
 1144        exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY),
 1145        exp.Table: lambda self: self._parse_table_parts(),
 1146        exp.TableAlias: lambda self: self._parse_table_alias(),
 1147        exp.Tuple: lambda self: self._parse_value(values=False),
 1148        exp.Whens: lambda self: self._parse_when_matched(),
 1149        exp.Where: lambda self: self._parse_where(),
 1150        exp.Window: lambda self: self._parse_named_window(),
 1151        exp.With: lambda self: self._parse_with(),
 1152    }
 1153
 1154    STATEMENT_PARSERS: t.ClassVar = {
 1155        TokenType.ALTER: lambda self: self._parse_alter(),
 1156        TokenType.ANALYZE: lambda self: self._parse_analyze(),
 1157        TokenType.BEGIN: lambda self: self._parse_transaction(),
 1158        TokenType.CACHE: lambda self: self._parse_cache(),
 1159        TokenType.COMMENT: lambda self: self._parse_comment(),
 1160        TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(),
 1161        TokenType.COPY: lambda self: self._parse_copy(),
 1162        TokenType.CREATE: lambda self: self._parse_create(),
 1163        TokenType.DECLARE: lambda self: self._parse_declare(),
 1164        TokenType.DELETE: lambda self: self._parse_delete(),
 1165        TokenType.DESC: lambda self: self._parse_describe(),
 1166        TokenType.DESCRIBE: lambda self: self._parse_describe(),
 1167        TokenType.DROP: lambda self: self._parse_drop(),
 1168        TokenType.GRANT: lambda self: self._parse_grant(),
 1169        TokenType.REVOKE: lambda self: self._parse_revoke(),
 1170        TokenType.INSERT: lambda self: self._parse_insert(),
 1171        TokenType.KILL: lambda self: self._parse_kill(),
 1172        TokenType.LOAD: lambda self: self._parse_load(),
 1173        TokenType.MERGE: lambda self: self._parse_merge(),
 1174        TokenType.PIVOT: lambda self: self._parse_simplified_pivot(),
 1175        TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())),
 1176        TokenType.REFRESH: lambda self: self._parse_refresh(),
 1177        TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(),
 1178        TokenType.SET: lambda self: self._parse_set(),
 1179        TokenType.TRUNCATE: lambda self: self._parse_truncate_table(),
 1180        TokenType.UNCACHE: lambda self: self._parse_uncache(),
 1181        TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True),
 1182        TokenType.UPDATE: lambda self: self._parse_update(),
 1183        TokenType.USE: lambda self: self._parse_use(),
 1184        TokenType.SEMICOLON: lambda self: exp.Semicolon(),
 1185    }
 1186
 1187    UNARY_PARSERS: t.ClassVar = {
 1188        TokenType.PLUS: lambda self: self._parse_unary(),  # Unary + is handled as a no-op
 1189        TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())),
 1190        TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())),
 1191        TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())),
 1192        TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())),
 1193        TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())),
 1194    }
 1195
 1196    STRING_PARSERS: t.ClassVar = {
 1197        TokenType.HEREDOC_STRING: lambda self, token: self.expression(
 1198            exp.RawString(this=token.text), token
 1199        ),
 1200        TokenType.NATIONAL_STRING: lambda self, token: self.expression(
 1201            exp.National(this=token.text), token
 1202        ),
 1203        TokenType.RAW_STRING: lambda self, token: self.expression(
 1204            exp.RawString(this=token.text), token
 1205        ),
 1206        TokenType.STRING: lambda self, token: self.expression(
 1207            exp.Literal(this=token.text, is_string=True), token
 1208        ),
 1209        TokenType.UNICODE_STRING: lambda self, token: self.expression(
 1210            exp.UnicodeString(
 1211                this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string()
 1212            ),
 1213            token,
 1214        ),
 1215    }
 1216
 1217    NUMERIC_PARSERS: t.ClassVar = {
 1218        TokenType.BIT_STRING: lambda self, token: self.expression(
 1219            exp.BitString(this=token.text), token
 1220        ),
 1221        TokenType.BYTE_STRING: lambda self, token: self.expression(
 1222            exp.ByteString(
 1223                this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None
 1224            ),
 1225            token,
 1226        ),
 1227        TokenType.HEX_STRING: lambda self, token: self.expression(
 1228            exp.HexString(
 1229                this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None
 1230            ),
 1231            token,
 1232        ),
 1233        TokenType.NUMBER: lambda self, token: self.expression(
 1234            exp.Literal(this=token.text, is_string=False), token
 1235        ),
 1236    }
 1237
 1238    PRIMARY_PARSERS: t.ClassVar = {
 1239        **STRING_PARSERS,
 1240        **NUMERIC_PARSERS,
 1241        TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token),
 1242        TokenType.NULL: lambda self, _: self.expression(exp.Null()),
 1243        TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)),
 1244        TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)),
 1245        TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(),
 1246        TokenType.STAR: lambda self, _: self._parse_star_ops(),
 1247    }
 1248
 1249    PLACEHOLDER_PARSERS: t.ClassVar = {
 1250        TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()),
 1251        TokenType.PARAMETER: lambda self: self._parse_parameter(),
 1252        TokenType.COLON: lambda self: (
 1253            self.expression(exp.Placeholder(this=self._prev.text))
 1254            if self._match_set(self.COLON_PLACEHOLDER_TOKENS)
 1255            else None
 1256        ),
 1257    }
 1258
 1259    RANGE_PARSERS: t.ClassVar = {
 1260        TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll),
 1261        TokenType.BETWEEN: lambda self, this: self._parse_between(this),
 1262        TokenType.GLOB: binary_range_parser(exp.Glob),
 1263        TokenType.ILIKE: binary_range_parser(exp.ILike),
 1264        TokenType.IN: lambda self, this: self._parse_in(this),
 1265        TokenType.IRLIKE: binary_range_parser(exp.RegexpILike),
 1266        TokenType.IS: lambda self, this: self._parse_is(this),
 1267        TokenType.LIKE: binary_range_parser(exp.Like),
 1268        TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy),
 1269        TokenType.OVERLAPS: binary_range_parser(exp.Overlaps),
 1270        TokenType.RLIKE: binary_range_parser(exp.RegexpLike),
 1271        TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo),
 1272        TokenType.FOR: lambda self, this: self._parse_comprehension(this),
 1273        TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys),
 1274        TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys),
 1275        TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath),
 1276        TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists),
 1277        TokenType.ADJACENT: binary_range_parser(exp.Adjacent),
 1278        TokenType.OPERATOR: lambda self, this: self._parse_operator(this),
 1279        TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft),
 1280        TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight),
 1281    }
 1282
 1283    PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = {
 1284        "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query),
 1285        "AS": lambda self, query: self._build_pipe_cte(
 1286            query, [exp.Star()], self._parse_table_alias()
 1287        ),
 1288        "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False),
 1289        "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query),
 1290        "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query),
 1291        "ORDER BY": lambda self, query: query.order_by(
 1292            self._parse_order(), append=False, copy=False
 1293        ),
 1294        "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1295        "SELECT": lambda self, query: self._parse_pipe_syntax_select(query),
 1296        "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query),
 1297        "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1298        "WHERE": lambda self, query: query.where(self._parse_where(), copy=False),
 1299    }
 1300
 1301    PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1302        "ALLOWED_VALUES": lambda self: self.expression(
 1303            exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary))
 1304        ),
 1305        "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty),
 1306        "AUTO": lambda self: self._parse_auto_property(),
 1307        "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty),
 1308        "BACKUP": lambda self: self.expression(
 1309            exp.BackupProperty(this=self._parse_var(any_token=True))
 1310        ),
 1311        "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(),
 1312        "CALLED": lambda self: self._parse_called_on_null_input_property(),
 1313        "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs),
 1314        "CHECKSUM": lambda self: self._parse_checksum(),
 1315        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1316        "CLUSTERED": lambda self: self._parse_clustered_by(),
 1317        "COLLATE": lambda self, **kwargs: self._parse_property_assignment(
 1318            exp.CollateProperty, **kwargs
 1319        ),
 1320        "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty),
 1321        "CONTAINS": lambda self: self._parse_contains_property(),
 1322        "COPY": lambda self: self._parse_copy_property(),
 1323        "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs),
 1324        "DATA_DELETION": lambda self: self._parse_data_deletion_property(),
 1325        "DEFINER": lambda self: self._parse_definer(),
 1326        "DETERMINISTIC": lambda self: self.expression(
 1327            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1328        ),
 1329        "DISTRIBUTED": lambda self: self._parse_distributed_property(),
 1330        "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty),
 1331        "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()),
 1332        "DISTKEY": lambda self: self._parse_distkey(),
 1333        "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty),
 1334        "EMPTY": lambda self: self.expression(exp.EmptyProperty()),
 1335        "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty),
 1336        "ENVIRONMENT": lambda self: self.expression(
 1337            exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment))
 1338        ),
 1339        "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty),
 1340        "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty),
 1341        "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()),
 1342        "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs),
 1343        "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1344        "FREESPACE": lambda self: self._parse_freespace(),
 1345        "GLOBAL": lambda self: self.expression(exp.GlobalProperty()),
 1346        "HEAP": lambda self: self.expression(exp.HeapProperty()),
 1347        "ICEBERG": lambda self: self.expression(exp.IcebergProperty()),
 1348        "IMMUTABLE": lambda self: self.expression(
 1349            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1350        ),
 1351        "INHERITS": lambda self: self.expression(
 1352            exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table))
 1353        ),
 1354        "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())),
 1355        "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs),
 1356        "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty),
 1357        "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"),
 1358        "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"),
 1359        "LIKE": lambda self: self._parse_create_like(),
 1360        "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty),
 1361        "LOCK": lambda self: self._parse_locking(),
 1362        "LOCKING": lambda self: self._parse_locking(),
 1363        "LOG": lambda self, **kwargs: self._parse_log(**kwargs),
 1364        "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()),
 1365        "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs),
 1366        "MODIFIES": lambda self: self._parse_modifies_property(),
 1367        "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)),
 1368        "NO": lambda self: self._parse_no_property(),
 1369        "ON": lambda self: self._parse_on_property(),
 1370        "ORDER BY": lambda self: self._parse_order(skip_order_token=True),
 1371        "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())),
 1372        "PARTITION": lambda self: self._parse_partitioned_of(),
 1373        "PARTITION BY": lambda self: self._parse_partitioned_by(),
 1374        "PARTITIONED BY": lambda self: self._parse_partitioned_by(),
 1375        "PARTITIONED_BY": lambda self: self._parse_partitioned_by(),
 1376        "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True),
 1377        "RANGE": lambda self: self._parse_dict_range(this="RANGE"),
 1378        "READS": lambda self: self._parse_reads_property(),
 1379        "REMOTE": lambda self: self._parse_remote_with_connection(),
 1380        "RETURNS": lambda self: self._parse_returns(),
 1381        "STRICT": lambda self: self.expression(exp.StrictProperty()),
 1382        "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()),
 1383        "ROW": lambda self: self._parse_row(),
 1384        "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty),
 1385        "SAMPLE": lambda self: self.expression(
 1386            exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise())
 1387        ),
 1388        "SECURE": lambda self: self.expression(exp.SecureProperty()),
 1389        "SECURITY": lambda self: self._parse_sql_security(),
 1390        "SQL SECURITY": lambda self: self._parse_sql_security(),
 1391        "SET": lambda self: self.expression(exp.SetProperty(multi=False)),
 1392        "SETTINGS": lambda self: self._parse_settings_property(),
 1393        "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty),
 1394        "SORTKEY": lambda self: self._parse_sortkey(),
 1395        "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"),
 1396        "STABLE": lambda self: self.expression(
 1397            exp.StabilityProperty(this=exp.Literal.string("STABLE"))
 1398        ),
 1399        "STORED": lambda self: self._parse_stored(),
 1400        "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(),
 1401        "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(),
 1402        "TEMP": lambda self: self.expression(exp.TemporaryProperty()),
 1403        "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()),
 1404        "TO": lambda self: self._parse_to_table(),
 1405        "TRANSIENT": lambda self: self.expression(exp.TransientProperty()),
 1406        "TRANSFORM": lambda self: self.expression(
 1407            exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression))
 1408        ),
 1409        "TTL": lambda self: self._parse_ttl(),
 1410        "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1411        "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()),
 1412        "VOLATILE": lambda self: self._parse_volatile_property(),
 1413        "WITH": lambda self: self._parse_with_property(),
 1414    }
 1415
 1416    CONSTRAINT_PARSERS: t.ClassVar = {
 1417        "AUTOINCREMENT": lambda self: self._parse_auto_increment(),
 1418        "AUTO_INCREMENT": lambda self: self._parse_auto_increment(),
 1419        "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)),
 1420        "CHECK": lambda self: self._parse_check_constraint(),
 1421        "COLLATE": lambda self: self.expression(
 1422            exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column())
 1423        ),
 1424        "COMMENT": lambda self: self.expression(
 1425            exp.CommentColumnConstraint(this=self._parse_string())
 1426        ),
 1427        "COMPRESS": lambda self: self._parse_compress(),
 1428        "CLUSTERED": lambda self: self.expression(
 1429            exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1430        ),
 1431        "NONCLUSTERED": lambda self: self.expression(
 1432            exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1433        ),
 1434        "DEFAULT": lambda self: self.expression(
 1435            exp.DefaultColumnConstraint(this=self._parse_bitwise())
 1436        ),
 1437        "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())),
 1438        "EPHEMERAL": lambda self: self.expression(
 1439            exp.EphemeralColumnConstraint(this=self._parse_bitwise())
 1440        ),
 1441        "EXCLUDE": lambda self: self.expression(
 1442            exp.ExcludeColumnConstraint(this=self._parse_index_params())
 1443        ),
 1444        "FOREIGN KEY": lambda self: self._parse_foreign_key(),
 1445        "FORMAT": lambda self: self.expression(
 1446            exp.DateFormatColumnConstraint(this=self._parse_var_or_string())
 1447        ),
 1448        "GENERATED": lambda self: self._parse_generated_as_identity(),
 1449        "IDENTITY": lambda self: self._parse_auto_increment(),
 1450        "INLINE": lambda self: self._parse_inline(),
 1451        "LIKE": lambda self: self._parse_create_like(),
 1452        "NOT": lambda self: self._parse_not_constraint(),
 1453        "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)),
 1454        "ON": lambda self: (
 1455            (
 1456                self._match(TokenType.UPDATE)
 1457                and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function()))
 1458            )
 1459            or self.expression(exp.OnProperty(this=self._parse_id_var()))
 1460        ),
 1461        "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())),
 1462        "PERIOD": lambda self: self._parse_period_for_system_time(),
 1463        "PRIMARY KEY": lambda self: self._parse_primary_key(),
 1464        "REFERENCES": lambda self: self._parse_references(match=False),
 1465        "TITLE": lambda self: self.expression(
 1466            exp.TitleColumnConstraint(this=self._parse_var_or_string())
 1467        ),
 1468        "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])),
 1469        "UNIQUE": lambda self: self._parse_unique(),
 1470        "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()),
 1471        "WITH": lambda self: self.expression(
 1472            exp.Properties(expressions=self._parse_wrapped_properties())
 1473        ),
 1474        "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1475        "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1476    }
 1477
 1478    def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None:
 1479        if not self._match(TokenType.L_PAREN, advance=False):
 1480            # Partitioning by bucket or truncate follows the syntax:
 1481            # PARTITION BY (BUCKET(..) | TRUNCATE(..))
 1482            # If we don't have parenthesis after each keyword, we should instead parse this as an identifier
 1483            self._retreat(self._index - 1)
 1484            return None
 1485
 1486        klass = (
 1487            exp.PartitionedByBucket
 1488            if self._prev.text.upper() == "BUCKET"
 1489            else exp.PartitionByTruncate
 1490        )
 1491
 1492        args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column())
 1493        this, expression = seq_get(args, 0), seq_get(args, 1)
 1494
 1495        if isinstance(this, exp.Literal):
 1496            # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order
 1497            #  - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)`
 1498            #  - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)`
 1499            # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)`
 1500            #
 1501            # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning
 1502            # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties
 1503            this, expression = expression, this
 1504
 1505        return self.expression(klass(this=this, expression=expression))
 1506
 1507    ALTER_PARSERS: t.ClassVar = {
 1508        "ADD": lambda self: self._parse_alter_table_add(),
 1509        "AS": lambda self: self._parse_select(),
 1510        "ALTER": lambda self: self._parse_alter_table_alter(),
 1511        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1512        "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())),
 1513        "DROP": lambda self: self._parse_alter_table_drop(),
 1514        "RENAME": lambda self: self._parse_alter_table_rename(),
 1515        "SET": lambda self: self._parse_alter_table_set(),
 1516        "SWAP": lambda self: self.expression(
 1517            exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True))
 1518        ),
 1519    }
 1520
 1521    ALTER_ALTER_PARSERS: t.ClassVar = {
 1522        "DISTKEY": lambda self: self._parse_alter_diststyle(),
 1523        "DISTSTYLE": lambda self: self._parse_alter_diststyle(),
 1524        "SORTKEY": lambda self: self._parse_alter_sortkey(),
 1525        "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True),
 1526    }
 1527
 1528    SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = {
 1529        "CHECK",
 1530        "EXCLUDE",
 1531        "FOREIGN KEY",
 1532        "LIKE",
 1533        "PERIOD",
 1534        "PRIMARY KEY",
 1535        "UNIQUE",
 1536        "BUCKET",
 1537        "TRUNCATE",
 1538    }
 1539
 1540    NO_PAREN_FUNCTION_PARSERS: t.ClassVar = {
 1541        "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())),
 1542        "CASE": lambda self: self._parse_case(),
 1543        "CONNECT_BY_ROOT": lambda self: self.expression(
 1544            exp.ConnectByRoot(this=self._parse_column())
 1545        ),
 1546        "IF": lambda self: self._parse_if(),
 1547    }
 1548
 1549    INVALID_FUNC_NAME_TOKENS: t.ClassVar = {
 1550        TokenType.IDENTIFIER,
 1551        TokenType.STRING,
 1552    }
 1553
 1554    FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"}
 1555
 1556    KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice)
 1557
 1558    FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1559        **{
 1560            name: lambda self: self._parse_distinct_arg_function(exp.ArgMax)
 1561            for name in exp.ArgMax.sql_names()
 1562        },
 1563        **{
 1564            name: lambda self: self._parse_distinct_arg_function(exp.ArgMin)
 1565            for name in exp.ArgMin.sql_names()
 1566        },
 1567        "CAST": lambda self: self._parse_cast(self.STRICT_CAST),
 1568        "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil),
 1569        "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST),
 1570        "CHAR": lambda self: self._parse_char(),
 1571        "CHR": lambda self: self._parse_char(),
 1572        "DECODE": lambda self: self._parse_decode(),
 1573        "EXTRACT": lambda self: self._parse_extract(),
 1574        "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor),
 1575        "GAP_FILL": lambda self: self._parse_gap_fill(),
 1576        "INITCAP": lambda self: self._parse_initcap(),
 1577        "JSON_OBJECT": lambda self: self._parse_json_object(),
 1578        "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True),
 1579        "JSON_TABLE": lambda self: self._parse_json_table(),
 1580        "MATCH": lambda self: self._parse_match_against(),
 1581        "NORMALIZE": lambda self: self._parse_normalize(),
 1582        "OPENJSON": lambda self: self._parse_open_json(),
 1583        "OVERLAY": lambda self: self._parse_overlay(),
 1584        "POSITION": lambda self: self._parse_position(),
 1585        "SAFE_CAST": lambda self: self._parse_cast(False, safe=True),
 1586        "STRING_AGG": lambda self: self._parse_string_agg(),
 1587        "SUBSTRING": lambda self: self._parse_substring(),
 1588        "TRIM": lambda self: self._parse_trim(),
 1589        "TRY_CAST": lambda self: self._parse_cast(False, safe=True),
 1590        "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True),
 1591        "XMLELEMENT": lambda self: self._parse_xml_element(),
 1592        "XMLTABLE": lambda self: self._parse_xml_table(),
 1593    }
 1594
 1595    QUERY_MODIFIER_PARSERS: t.ClassVar = {
 1596        TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()),
 1597        TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()),
 1598        TokenType.WHERE: lambda self: ("where", self._parse_where()),
 1599        TokenType.GROUP_BY: lambda self: ("group", self._parse_group()),
 1600        TokenType.HAVING: lambda self: ("having", self._parse_having()),
 1601        TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()),
 1602        TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()),
 1603        TokenType.ORDER_BY: lambda self: ("order", self._parse_order()),
 1604        TokenType.LIMIT: lambda self: ("limit", self._parse_limit()),
 1605        TokenType.FETCH: lambda self: ("limit", self._parse_limit()),
 1606        TokenType.OFFSET: lambda self: ("offset", self._parse_offset()),
 1607        TokenType.FOR: lambda self: ("locks", self._parse_locks()),
 1608        TokenType.LOCK: lambda self: ("locks", self._parse_locks()),
 1609        TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1610        TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1611        TokenType.CLUSTER_BY: lambda self: (
 1612            "cluster",
 1613            self._parse_cluster(),
 1614        ),
 1615        TokenType.DISTRIBUTE_BY: lambda self: (
 1616            "distribute",
 1617            self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY),
 1618        ),
 1619        TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)),
 1620        TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)),
 1621    }
 1622    QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS)
 1623
 1624    SET_PARSERS: t.ClassVar = {
 1625        "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"),
 1626        "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"),
 1627        "SESSION": lambda self: self._parse_set_item_assignment("SESSION"),
 1628        "TRANSACTION": lambda self: self._parse_set_transaction(),
 1629    }
 1630
 1631    SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {}
 1632
 1633    TYPE_LITERAL_PARSERS: t.ClassVar = {
 1634        exp.DType.JSON: lambda self, this, _: self.expression(exp.ParseJSON(this=this)),
 1635    }
 1636
 1637    TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {}
 1638
 1639    DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN}
 1640
 1641    PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE}
 1642
 1643    TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"}
 1644    TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = {
 1645        "ISOLATION": (
 1646            ("LEVEL", "REPEATABLE", "READ"),
 1647            ("LEVEL", "READ", "COMMITTED"),
 1648            ("LEVEL", "READ", "UNCOMITTED"),
 1649            ("LEVEL", "SERIALIZABLE"),
 1650        ),
 1651        "READ": ("WRITE", "ONLY"),
 1652    }
 1653
 1654    CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1655        **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()),
 1656        "DO": ("NOTHING", "UPDATE"),
 1657    }
 1658
 1659    TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = {
 1660        "INSTEAD": (("OF",),),
 1661        "BEFORE": tuple(),
 1662        "AFTER": tuple(),
 1663    }
 1664
 1665    TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = {
 1666        "NOT": (("DEFERRABLE",),),
 1667        "DEFERRABLE": tuple(),
 1668    }
 1669
 1670    CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = {
 1671        "SCALE": ("EXTEND", "NOEXTEND"),
 1672        "SHARD": ("EXTEND", "NOEXTEND"),
 1673        "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"),
 1674        **dict.fromkeys(
 1675            (
 1676                "SESSION",
 1677                "GLOBAL",
 1678                "KEEP",
 1679                "NOKEEP",
 1680                "ORDER",
 1681                "NOORDER",
 1682                "NOCACHE",
 1683                "CYCLE",
 1684                "NOCYCLE",
 1685                "NOMINVALUE",
 1686                "NOMAXVALUE",
 1687                "NOSCALE",
 1688                "NOSHARD",
 1689            ),
 1690            tuple(),
 1691        ),
 1692    }
 1693
 1694    ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")}
 1695
 1696    USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1697        ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple()
 1698    )
 1699
 1700    CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",))
 1701
 1702    SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1703        "TYPE": ("EVOLUTION",),
 1704        **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()),
 1705    }
 1706
 1707    PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {}
 1708
 1709    EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1710        ("CALLER", "SELF", "OWNER"), tuple()
 1711    )
 1712
 1713    KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1714        "NOT": ("ENFORCED",),
 1715        "MATCH": (
 1716            "FULL",
 1717            "PARTIAL",
 1718            "SIMPLE",
 1719        ),
 1720        "INITIALLY": ("DEFERRED", "IMMEDIATE"),
 1721        "USING": (
 1722            "BTREE",
 1723            "HASH",
 1724        ),
 1725        **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()),
 1726    }
 1727
 1728    WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1729        "NO": ("OTHERS",),
 1730        "CURRENT": ("ROW",),
 1731        **dict.fromkeys(("GROUP", "TIES"), tuple()),
 1732    }
 1733
 1734    INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"}
 1735
 1736    CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"}
 1737    # Time travel clause prefixes, mapped to whether they pin a timestamp or a version
 1738    VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = {
 1739        ("FOR", "SYSTEM_TIME"): "TIMESTAMP",
 1740        ("FOR", "SYSTEM", "TIME"): "TIMESTAMP",
 1741        ("FOR", "TIMESTAMP"): "TIMESTAMP",
 1742        ("FOR", "VERSION"): "VERSION",
 1743        ("TIMESTAMP", "AS", "OF"): "TIMESTAMP",
 1744        ("VERSION", "AS", "OF"): "VERSION",
 1745    }
 1746
 1747    HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"}
 1748    HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"}
 1749
 1750    OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"}
 1751
 1752    OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN}
 1753
 1754    TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE}
 1755
 1756    VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"}
 1757
 1758    WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS}
 1759    WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER}
 1760    WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"}
 1761
 1762    JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS}
 1763
 1764    FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT}
 1765
 1766    ADD_CONSTRAINT_TOKENS: t.ClassVar = {
 1767        TokenType.CONSTRAINT,
 1768        TokenType.FOREIGN_KEY,
 1769        TokenType.INDEX,
 1770        TokenType.KEY,
 1771        TokenType.PRIMARY_KEY,
 1772        TokenType.UNIQUE,
 1773    }
 1774
 1775    DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT}
 1776
 1777    UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS
 1778
 1779    SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT}
 1780
 1781    COPY_INTO_VARLEN_OPTIONS: t.ClassVar = {
 1782        "FILE_FORMAT",
 1783        "COPY_OPTIONS",
 1784        "FORMAT_OPTIONS",
 1785        "CREDENTIAL",
 1786    }
 1787
 1788    IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"}
 1789
 1790    ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {}
 1791
 1792    ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"}
 1793
 1794    PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN}
 1795
 1796    # The style options for the DESCRIBE statement
 1797    DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"}
 1798
 1799    SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"}
 1800
 1801    # The style options for the ANALYZE statement
 1802    ANALYZE_STYLES: t.ClassVar = {
 1803        "BUFFER_USAGE_LIMIT",
 1804        "FULL",
 1805        "LOCAL",
 1806        "NO_WRITE_TO_BINLOG",
 1807        "SAMPLE",
 1808        "SKIP_LOCKED",
 1809        "VERBOSE",
 1810    }
 1811
 1812    ANALYZE_EXPRESSION_PARSERS: t.ClassVar = {
 1813        "ALL": lambda self: self._parse_analyze_columns(),
 1814        "COMPUTE": lambda self: self._parse_analyze_statistics(),
 1815        "DELETE": lambda self: self._parse_analyze_delete(),
 1816        "DROP": lambda self: self._parse_analyze_histogram(),
 1817        "ESTIMATE": lambda self: self._parse_analyze_statistics(),
 1818        "LIST": lambda self: self._parse_analyze_list(),
 1819        "PREDICATE": lambda self: self._parse_analyze_columns(),
 1820        "UPDATE": lambda self: self._parse_analyze_histogram(),
 1821        "VALIDATE": lambda self: self._parse_analyze_validate(),
 1822    }
 1823
 1824    PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"}
 1825
 1826    AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET)
 1827
 1828    OPERATION_MODIFIERS: t.ClassVar[set[str]] = set()
 1829
 1830    RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"}
 1831
 1832    SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"}
 1833
 1834    MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values)
 1835
 1836    STRICT_CAST: t.ClassVar = True
 1837
 1838    PREFIXED_PIVOT_COLUMNS: t.ClassVar = False
 1839    IDENTIFY_PIVOT_STRINGS: t.ClassVar = False
 1840    # Whether an UNPIVOT outputs its value column(s) before the name column
 1841    UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False
 1842    # Controls when an aggregation's name is included in a pivoted column's name:
 1843    # "agg_name_if_aliased" - only for aggregations that carry an explicit alias
 1844    # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations
 1845    # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only)
 1846    PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased"
 1847
 1848    LOG_DEFAULTS_TO_LN: t.ClassVar = False
 1849
 1850    # Whether the table sample clause expects CSV syntax
 1851    TABLESAMPLE_CSV: t.ClassVar = False
 1852
 1853    # The default method used for table sampling
 1854    DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None
 1855
 1856    # Whether the SET command needs a delimiter (e.g. "=") for assignments
 1857    SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True
 1858
 1859    # Whether the TRIM function expects the characters to trim as its first argument
 1860    TRIM_PATTERN_FIRST: t.ClassVar = False
 1861
 1862    # Whether string aliases are supported `SELECT COUNT(*) 'count'`
 1863    STRING_ALIASES: t.ClassVar = False
 1864
 1865    # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand)
 1866    MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True
 1867    SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset"}
 1868
 1869    # Whether to parse IF statements that aren't followed by a left parenthesis as commands
 1870    NO_PAREN_IF_COMMANDS: t.ClassVar = True
 1871
 1872    # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres)
 1873    JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False
 1874
 1875    # Whether the `:` operator is used to extract a value from a VARIANT column
 1876    COLON_IS_VARIANT_EXTRACT: t.ClassVar = False
 1877
 1878    # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged
 1879    # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks)
 1880    COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True
 1881
 1882    # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause.
 1883    # If this is True and '(' is not found, the keyword will be treated as an identifier
 1884    VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True
 1885
 1886    # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift)
 1887    SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False
 1888
 1889    # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS
 1890    INTERVAL_SPANS: t.ClassVar = True
 1891
 1892    # Whether a PARTITION clause can follow a table reference
 1893    SUPPORTS_PARTITION_SELECTION: t.ClassVar = False
 1894
 1895    # Whether the `name AS expr` schema/column constraint requires parentheses around `expr`
 1896    WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True
 1897
 1898    # Whether the 'AS' keyword is optional in the CTE definition syntax
 1899    OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True
 1900
 1901    # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword
 1902    ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True
 1903
 1904    # Whether Alter statements are allowed to contain Partition specifications
 1905    ALTER_TABLE_PARTITIONS: t.ClassVar = False
 1906
 1907    # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree.
 1908    # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is
 1909    # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such
 1910    # as BigQuery, where all joins have the same precedence.
 1911    JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False
 1912
 1913    # Whether TIMESTAMP <literal> can produce a zone-aware timestamp
 1914    ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False
 1915
 1916    # Whether map literals support arbitrary expressions as keys.
 1917    # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB).
 1918    # When False, keys are typically restricted to identifiers.
 1919    MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False
 1920
 1921    # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this
 1922    # is true for Snowflake but not for BigQuery which can also process strings
 1923    JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False
 1924
 1925    # Dialects like Databricks support JOINS without join criteria
 1926    # Adding an ON TRUE, makes transpilation semantically correct for other dialects
 1927    ADD_JOIN_ON_TRUE: t.ClassVar = False
 1928
 1929    # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]'
 1930    # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND`
 1931    SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False
 1932
 1933    # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them
 1934    # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation.
 1935    ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False
 1936
 1937    SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS)
 1938    SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS)
 1939
 1940    def __init__(
 1941        self,
 1942        error_level: ErrorLevel | None = None,
 1943        error_message_context: int = 100,
 1944        max_errors: int = 3,
 1945        max_nodes: int = -1,
 1946        dialect: DialectType = None,
 1947    ):
 1948        self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE
 1949        self.error_message_context: int = error_message_context
 1950        self.max_errors: int = max_errors
 1951        self.max_nodes: int = max_nodes
 1952        self.dialect: t.Any = _resolve_dialect(dialect)
 1953        self.sql: str = ""
 1954        self.errors: list[ParseError] = []
 1955        self._tokens: list[Token] = []
 1956        self._tokens_size: i64 = 0
 1957        self._index: i64 = 0
 1958        self._curr: Token = SENTINEL_NONE
 1959        self._next: Token = SENTINEL_NONE
 1960        self._prev: Token = SENTINEL_NONE
 1961        self._prev_comments: list[str] = []
 1962        self._pipe_cte_counter: int = 0
 1963        self._chunks: list[list[Token]] = []
 1964        self._chunk_index: i64 = 0
 1965        self._node_count: int = 0
 1966
 1967    def reset(self) -> None:
 1968        self.sql = ""
 1969        self.errors = []
 1970        self._tokens = []
 1971        self._tokens_size = 0
 1972        self._index = 0
 1973        self._curr = SENTINEL_NONE
 1974        self._next = SENTINEL_NONE
 1975        self._prev = SENTINEL_NONE
 1976        self._prev_comments = []
 1977        self._pipe_cte_counter = 0
 1978        self._chunks = []
 1979        self._chunk_index = 0
 1980        self._node_count = 0
 1981
 1982    def _advance(self, times: i64 = 1) -> None:
 1983        index = self._index + times
 1984        self._index = index
 1985        tokens = self._tokens
 1986        size = self._tokens_size
 1987        self._curr = tokens[index] if index < size else SENTINEL_NONE
 1988        self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE
 1989
 1990        if index > 0:
 1991            prev = tokens[index - 1]
 1992            self._prev = prev
 1993            self._prev_comments = prev.comments
 1994        else:
 1995            self._prev = SENTINEL_NONE
 1996            self._prev_comments = []
 1997
 1998    def _advance_chunk(self) -> None:
 1999        self._index = -1
 2000        self._tokens = self._chunks[self._chunk_index]
 2001        self._tokens_size = i64(len(self._tokens))
 2002        self._chunk_index += 1
 2003        self._advance()
 2004
 2005    def _retreat(self, index: i64) -> None:
 2006        if index != self._index:
 2007            self._advance(index - self._index)
 2008
 2009    def _add_comments(self, expression: exp.Expr | None) -> None:
 2010        if expression and self._prev_comments:
 2011            expression.add_comments(self._prev_comments)
 2012            self._prev_comments = []
 2013
 2014    def _match(
 2015        self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None
 2016    ) -> bool:
 2017        if self._curr.token_type == token_type:
 2018            if advance:
 2019                self._advance()
 2020            self._add_comments(expression)
 2021            return True
 2022        return False
 2023
 2024    def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool:
 2025        if self._curr.token_type in types:
 2026            if advance:
 2027                self._advance()
 2028            return True
 2029        return False
 2030
 2031    def _match_pair(
 2032        self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True
 2033    ) -> bool:
 2034        if self._curr.token_type == token_type_a and self._next.token_type == token_type_b:
 2035            if advance:
 2036                self._advance(2)
 2037            return True
 2038        return False
 2039
 2040    def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool:
 2041        if (
 2042            self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS
 2043            and self._curr.text.upper() in texts
 2044        ):
 2045            if advance:
 2046                self._advance()
 2047            return True
 2048        return False
 2049
 2050    def _match_text_seq(self, *texts: str, advance: bool = True) -> bool:
 2051        index = self._index
 2052        excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS
 2053        for text in texts:
 2054            if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text:
 2055                self._advance()
 2056            else:
 2057                self._retreat(index)
 2058                return False
 2059
 2060        if not advance:
 2061            self._retreat(index)
 2062
 2063        return True
 2064
 2065    def _is_connected(self) -> bool:
 2066        prev = self._prev
 2067        curr = self._curr
 2068        return bool(prev and curr and prev.end + 1 == curr.start)
 2069
 2070    def _find_sql(self, start: Token, end: Token) -> str:
 2071        return self.sql[start.start : end.end + 1]
 2072
 2073    def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None:
 2074        token = token or self._curr or self._prev or Token.string("")
 2075        formatted_sql, start_context, highlight, end_context = highlight_sql(
 2076            sql=self.sql,
 2077            positions=[(token.start, token.end)],
 2078            context_length=self.error_message_context,
 2079        )
 2080        formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n  {formatted_sql}"
 2081
 2082        error = ParseError.new(
 2083            formatted_message,
 2084            description=message,
 2085            line=token.line,
 2086            col=token.col,
 2087            start_context=start_context,
 2088            highlight=highlight,
 2089            end_context=end_context,
 2090        )
 2091
 2092        if self.error_level == ErrorLevel.IMMEDIATE:
 2093            raise error
 2094
 2095        self.errors.append(error)
 2096
 2097    def validate_expression(self, expression: E, args: list | None = None) -> E:
 2098        if self.max_nodes > -1:
 2099            self._node_count += 1
 2100            if self._node_count > self.max_nodes:
 2101                self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded")
 2102        if self.error_level != ErrorLevel.IGNORE:
 2103            for error_message in expression.error_messages(args):
 2104                self.raise_error(error_message)
 2105        return expression
 2106
 2107    def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None:
 2108        index = self._index
 2109        error_level = self.error_level
 2110        this: T | None = None
 2111
 2112        self.error_level = ErrorLevel.IMMEDIATE
 2113        try:
 2114            this = parse_method()
 2115        except ParseError:
 2116            this = None
 2117        finally:
 2118            if not this or retreat:
 2119                self._retreat(index)
 2120            self.error_level = error_level
 2121
 2122        return this
 2123
 2124    def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]:
 2125        """
 2126        Parses a list of tokens and returns a list of syntax trees, one tree
 2127        per parsed SQL statement.
 2128
 2129        Args:
 2130            raw_tokens: The list of tokens.
 2131            sql: The original SQL string.
 2132
 2133        Returns:
 2134            The list of the produced syntax trees.
 2135        """
 2136        return self._parse(
 2137            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
 2138        )
 2139
 2140    def parse_into(
 2141        self,
 2142        expression_types: exp.IntoType,
 2143        raw_tokens: list[Token],
 2144        sql: str | None = None,
 2145    ) -> list[exp.Expr | None]:
 2146        """
 2147        Parses a list of tokens into a given Expr type. If a collection of Expr
 2148        types is given instead, this method will try to parse the token list into each one
 2149        of them, stopping at the first for which the parsing succeeds.
 2150
 2151        Args:
 2152            expression_types: The expression type(s) to try and parse the token list into.
 2153            raw_tokens: The list of tokens.
 2154            sql: The original SQL string, used to produce helpful debug messages.
 2155
 2156        Returns:
 2157            The target Expr.
 2158        """
 2159        errors = []
 2160        for expression_type in ensure_list(expression_types):
 2161            parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type))
 2162            if not parser:
 2163                raise TypeError(f"No parser registered for {expression_type}")
 2164
 2165            try:
 2166                return self._parse(parser, raw_tokens, sql)
 2167            except ParseError as e:
 2168                e.errors[0]["into_expression"] = expression_type
 2169                errors.append(e)
 2170
 2171        raise ParseError(
 2172            f"Failed to parse '{sql or raw_tokens}' into {expression_types}",
 2173            errors=merge_errors(errors),
 2174        ) from errors[-1]
 2175
 2176    def check_errors(self) -> None:
 2177        """Logs or raises any found errors, depending on the chosen error level setting."""
 2178        if self.error_level == ErrorLevel.WARN:
 2179            for error in self.errors:
 2180                logger.error(str(error))
 2181        elif self.error_level == ErrorLevel.RAISE and self.errors:
 2182            raise ParseError(
 2183                concat_messages(self.errors, self.max_errors),
 2184                errors=merge_errors(self.errors),
 2185            )
 2186
 2187    def expression(
 2188        self,
 2189        instance: E,
 2190        token: Token | None = None,
 2191        comments: list[str] | None = None,
 2192    ) -> E:
 2193        if token:
 2194            instance.update_positions(token)
 2195        instance.add_comments(comments) if comments else self._add_comments(instance)
 2196        if not instance.is_primitive:
 2197            instance = self.validate_expression(instance)
 2198        return instance
 2199
 2200    def _parse_batch_statements(
 2201        self,
 2202        parse_method: t.Callable[[Parser], exp.Expr | None],
 2203        sep_first_statement: bool = True,
 2204    ) -> list[exp.Expr | None]:
 2205        expressions = []
 2206
 2207        # Chunkification binds if/while statements with the first statement of the body
 2208        if sep_first_statement:
 2209            self._match(TokenType.BEGIN)
 2210            expressions.append(parse_method(self))
 2211
 2212        chunks_length = len(self._chunks)
 2213        while self._chunk_index < chunks_length:
 2214            self._advance_chunk()
 2215
 2216            if self._match(TokenType.ELSE, advance=False):
 2217                return expressions
 2218
 2219            if expressions and not self._next and self._match(TokenType.END):
 2220                expressions.append(exp.EndStatement())
 2221                continue
 2222
 2223            expressions.append(parse_method(self))
 2224
 2225            if self._index < self._tokens_size:
 2226                self.raise_error("Invalid expression / Unexpected token")
 2227
 2228            self.check_errors()
 2229
 2230        return expressions
 2231
 2232    def _parse(
 2233        self,
 2234        parse_method: t.Callable[[Parser], exp.Expr | None],
 2235        raw_tokens: list[Token],
 2236        sql: str | None = None,
 2237    ) -> list[exp.Expr | None]:
 2238        self.reset()
 2239        self.sql = sql or ""
 2240
 2241        total = len(raw_tokens)
 2242        chunks: list[list[Token]] = [[]]
 2243
 2244        for i, token in enumerate(raw_tokens):
 2245            if token.token_type == TokenType.SEMICOLON:
 2246                if token.comments:
 2247                    chunks.append([token])
 2248
 2249                if i < total - 1:
 2250                    chunks.append([])
 2251            else:
 2252                chunks[-1].append(token)
 2253
 2254        self._chunks = chunks
 2255
 2256        return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False)
 2257
 2258    def _warn_unsupported(self) -> None:
 2259        if self._tokens_size <= 1:
 2260            return
 2261
 2262        # We use _find_sql because self.sql may comprise multiple chunks, and we're only
 2263        # interested in emitting a warning for the one being currently processed.
 2264        sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context]
 2265
 2266        logger.warning(
 2267            f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'."
 2268        )
 2269
 2270    def _parse_command(self) -> exp.Command:
 2271        self._warn_unsupported()
 2272        comments = self._prev_comments
 2273        return self.expression(
 2274            exp.Command(this=self._prev.text.upper(), expression=self._parse_string()),
 2275            comments=comments,
 2276        )
 2277
 2278    def _parse_comment(self, allow_exists: bool = True) -> exp.Expr:
 2279        start = self._prev
 2280        exists = self._parse_exists() if allow_exists else None
 2281
 2282        self._match(TokenType.ON)
 2283
 2284        materialized = self._match_text_seq("MATERIALIZED")
 2285        kind = self._match_set(self.CREATABLES) and self._prev
 2286        if not kind:
 2287            return self._parse_as_command(start)
 2288
 2289        if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2290            this = self._parse_user_defined_function(kind=kind.token_type)
 2291        elif kind.token_type == TokenType.TABLE:
 2292            this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS)
 2293        elif kind.token_type == TokenType.COLUMN:
 2294            this = self._parse_column()
 2295        else:
 2296            this = self._parse_table_parts(schema=True)
 2297
 2298        self._match(TokenType.IS)
 2299
 2300        return self.expression(
 2301            exp.Comment(
 2302                this=this,
 2303                kind=kind.text,
 2304                expression=self._parse_string(),
 2305                exists=exists,
 2306                materialized=materialized,
 2307            )
 2308        )
 2309
 2310    def _parse_to_table(
 2311        self,
 2312    ) -> exp.ToTableProperty:
 2313        table = self._parse_table_parts(schema=True)
 2314        return self.expression(exp.ToTableProperty(this=table))
 2315
 2316    # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl
 2317    def _parse_ttl(self) -> exp.Expr:
 2318        def _parse_ttl_action() -> exp.Expr | None:
 2319            this = self._parse_bitwise()
 2320
 2321            if self._match_text_seq("DELETE"):
 2322                return self.expression(exp.MergeTreeTTLAction(this=this, delete=True))
 2323            if self._match_text_seq("RECOMPRESS"):
 2324                return self.expression(
 2325                    exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise())
 2326                )
 2327            if self._match_text_seq("TO", "DISK"):
 2328                return self.expression(
 2329                    exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string())
 2330                )
 2331            if self._match_text_seq("TO", "VOLUME"):
 2332                return self.expression(
 2333                    exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string())
 2334                )
 2335
 2336            return this
 2337
 2338        expressions = self._parse_csv(_parse_ttl_action)
 2339        where = self._parse_where()
 2340        group = self._parse_group()
 2341
 2342        aggregates = None
 2343        if group and self._match(TokenType.SET):
 2344            aggregates = self._parse_csv(self._parse_set_item)
 2345
 2346        return self.expression(
 2347            exp.MergeTreeTTL(
 2348                expressions=expressions, where=where, group=group, aggregates=aggregates
 2349            )
 2350        )
 2351
 2352    def _parse_condition(self) -> exp.Expr | None:
 2353        return self._parse_wrapped(parse_method=self._parse_expression, optional=True)
 2354
 2355    def _parse_block(self) -> exp.Block:
 2356        return self.expression(
 2357            exp.Block(
 2358                expressions=self._parse_batch_statements(
 2359                    parse_method=lambda self: self._parse_statement()
 2360                )
 2361            )
 2362        )
 2363
 2364    def _parse_whileblock(self) -> exp.WhileBlock:
 2365        return self.expression(
 2366            exp.WhileBlock(this=self._parse_condition(), body=self._parse_block())
 2367        )
 2368
 2369    def _parse_statement(self) -> exp.Expr | None:
 2370        if not self._curr:
 2371            return None
 2372
 2373        if self._match_set(self.STATEMENT_PARSERS):
 2374            comments = self._prev_comments
 2375            stmt = self.STATEMENT_PARSERS[self._prev.token_type](self)
 2376            stmt.add_comments(comments, prepend=True)
 2377            return stmt
 2378
 2379        if self._match_set(self.dialect.tokenizer_class.COMMANDS):
 2380            return self._parse_command()
 2381
 2382        if self._match_text_seq("WHILE"):
 2383            return self._parse_whileblock()
 2384
 2385        expression = self._parse_expression()
 2386        expression = self._parse_set_operations(expression) if expression else self._parse_select()
 2387
 2388        if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False):
 2389            expression = self._parse_pipe_syntax_query(expression)
 2390
 2391        return self._parse_query_modifiers(expression)
 2392
 2393    def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command:
 2394        start = self._prev
 2395        temporary = self._match(TokenType.TEMPORARY)
 2396        materialized = self._match_text_seq("MATERIALIZED")
 2397        iceberg = self._match_text_seq("ICEBERG")
 2398
 2399        kind = self._match_set(self.CREATABLES) and self._prev.text.upper()
 2400        if not kind or (iceberg and kind and kind != "TABLE"):
 2401            return self._parse_as_command(start)
 2402
 2403        concurrently = self._match_text_seq("CONCURRENTLY")
 2404        if_exists = exists or self._parse_exists()
 2405
 2406        if kind == "COLUMN":
 2407            this = self._parse_column()
 2408        else:
 2409            this = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA")
 2410
 2411        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 2412
 2413        if self._match(TokenType.L_PAREN, advance=False):
 2414            expressions = self._parse_wrapped_csv(self._parse_types)
 2415        else:
 2416            expressions = None
 2417
 2418        cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper()
 2419
 2420        return self.expression(
 2421            exp.Drop(
 2422                exists=if_exists,
 2423                this=this,
 2424                expressions=expressions,
 2425                kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind,
 2426                temporary=temporary,
 2427                materialized=materialized,
 2428                cascade=cascade_or_restrict == "CASCADE",
 2429                restrict=cascade_or_restrict == "RESTRICT",
 2430                constraints=self._match_text_seq("CONSTRAINTS"),
 2431                purge=self._match_text_seq("PURGE"),
 2432                cluster=cluster,
 2433                concurrently=concurrently,
 2434                sync=self._match_text_seq("SYNC"),
 2435                iceberg=iceberg,
 2436                force=self._match_text_seq("FORCE"),
 2437            )
 2438        )
 2439
 2440    def _parse_exists(self, not_: bool = False) -> bool | None:
 2441        return (
 2442            self._match_text_seq("IF")
 2443            and (not not_ or self._match(TokenType.NOT))
 2444            and self._match(TokenType.EXISTS)
 2445        )
 2446
 2447    def _parse_create(self) -> exp.Create | exp.Command:
 2448        # Note: this can't be None because we've matched a statement parser
 2449        start = self._prev
 2450
 2451        replace = (
 2452            start.token_type == TokenType.REPLACE
 2453            or self._match_pair(TokenType.OR, TokenType.REPLACE)
 2454            or self._match_pair(TokenType.OR, TokenType.ALTER)
 2455        )
 2456        refresh = self._match_pair(TokenType.OR, TokenType.REFRESH)
 2457
 2458        unique = self._match(TokenType.UNIQUE)
 2459
 2460        if self._match_text_seq("CLUSTERED", "COLUMNSTORE"):
 2461            clustered = True
 2462        elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq(
 2463            "COLUMNSTORE"
 2464        ):
 2465            clustered = False
 2466        else:
 2467            clustered = None
 2468
 2469        if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False):
 2470            self._advance()
 2471
 2472        properties = None
 2473        create_token = self._match_set(self.CREATABLES) and self._prev
 2474
 2475        if not create_token:
 2476            # exp.Properties.Location.POST_CREATE
 2477            properties = self._parse_properties()
 2478            create_token = self._match_set(self.CREATABLES) and self._prev
 2479
 2480            if not properties or not create_token:
 2481                return self._parse_as_command(start)
 2482
 2483        create_token_type = t.cast(Token, create_token).token_type
 2484
 2485        concurrently = self._match_text_seq("CONCURRENTLY")
 2486        exists = self._parse_exists(not_=True)
 2487        this = None
 2488        expression: exp.Expr | None = None
 2489        indexes = None
 2490        no_schema_binding = None
 2491        begin = None
 2492        clone = None
 2493
 2494        def extend_props(temp_props: exp.Properties | None) -> None:
 2495            nonlocal properties
 2496            if properties and temp_props:
 2497                properties.expressions.extend(temp_props.expressions)
 2498            elif temp_props:
 2499                properties = temp_props
 2500
 2501        if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2502            this = self._parse_user_defined_function(kind=create_token_type)
 2503
 2504            # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature)
 2505            extend_props(self._parse_properties())
 2506
 2507            expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None
 2508
 2509            if (
 2510                not expression
 2511                and create_token_type == TokenType.FUNCTION
 2512                and isinstance(this, exp.UserDefinedFunction)
 2513                and this.args.get("wrapped")
 2514            ):
 2515                pre_table_index = self._index
 2516                is_table = self._match(TokenType.TABLE)
 2517
 2518                expression = self._parse_expression()
 2519                overload_mode = bool(
 2520                    expression
 2521                    and self._curr.token_type == TokenType.COMMA
 2522                    and self._next.token_type == TokenType.L_PAREN
 2523                )
 2524                if not overload_mode:
 2525                    self._retreat(pre_table_index)
 2526                    is_table = False
 2527                    expression = None
 2528            else:
 2529                is_table = False
 2530                overload_mode = False
 2531
 2532            extend_props(self._parse_function_properties())
 2533
 2534            if not expression:
 2535                if self._match(TokenType.COMMAND):
 2536                    expression = self._parse_as_command(self._prev)
 2537                else:
 2538                    begin = self._match(TokenType.BEGIN)
 2539                    return_ = self._match_text_seq("RETURN")
 2540
 2541                    if self._match(TokenType.STRING, advance=False):
 2542                        # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property
 2543                        # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement
 2544                        expression = self._parse_string()
 2545                        extend_props(self._parse_properties())
 2546                    else:
 2547                        expression = (
 2548                            self._parse_user_defined_function_expression()
 2549                            if create_token_type == TokenType.FUNCTION
 2550                            else self._parse_block()
 2551                        )
 2552
 2553                    if return_:
 2554                        expression = self.expression(exp.Return(this=expression))
 2555
 2556            if overload_mode and expression:
 2557                expression = self._parse_macro_overloads(
 2558                    t.cast(exp.UserDefinedFunction, this), expression, is_table
 2559                )
 2560        elif create_token_type == TokenType.INDEX:
 2561            # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c)
 2562            if not self._match(TokenType.ON):
 2563                index = self._parse_id_var()
 2564                anonymous = False
 2565            else:
 2566                index = None
 2567                anonymous = True
 2568
 2569            this = self._parse_index(index=index, anonymous=anonymous)
 2570        elif (
 2571            create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER)
 2572        ) or create_token_type == TokenType.TRIGGER:
 2573            if is_constraint := (create_token_type == TokenType.CONSTRAINT):
 2574                create_token = self._prev
 2575
 2576            trigger_name = self._parse_id_var()
 2577            if not trigger_name:
 2578                return self._parse_as_command(start)
 2579
 2580            timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False)
 2581            timing = timing_var.this if timing_var else None
 2582            if not timing:
 2583                return self._parse_as_command(start)
 2584
 2585            events = self._parse_trigger_events()
 2586            if not self._match(TokenType.ON):
 2587                self.raise_error("Expected ON in trigger definition")
 2588
 2589            table = self._parse_table_parts()
 2590            referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None
 2591            deferrable, initially = self._parse_trigger_deferrable()
 2592            referencing = self._parse_trigger_referencing()
 2593            for_each = self._parse_trigger_for_each()
 2594            when = self._match_text_seq("WHEN") and self._parse_wrapped(
 2595                self._parse_disjunction, optional=True
 2596            )
 2597            execute = self._parse_trigger_execute()
 2598
 2599            if execute is None:
 2600                return self._parse_as_command(start)
 2601
 2602            trigger_props = self.expression(
 2603                exp.TriggerProperties(
 2604                    table=table,
 2605                    timing=timing,
 2606                    events=events,
 2607                    execute=execute,
 2608                    constraint=is_constraint,
 2609                    referenced_table=referenced_table,
 2610                    deferrable=deferrable,
 2611                    initially=initially,
 2612                    referencing=referencing,
 2613                    for_each=for_each,
 2614                    when=when,
 2615                )
 2616            )
 2617
 2618            this = trigger_name
 2619            extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else []))
 2620        elif create_token_type == TokenType.TYPE:
 2621            this = self._parse_table_parts(schema=True)
 2622            if not this or not self._match(TokenType.ALIAS):
 2623                return self._parse_as_command(start)
 2624
 2625            if self._match(TokenType.ENUM):
 2626                expression = exp.DataType(
 2627                    this=exp.DType.ENUM,
 2628                    expressions=self._parse_wrapped_csv(self._parse_string),
 2629                )
 2630            elif self._match(TokenType.L_PAREN, advance=False):
 2631                expression = self._parse_schema()
 2632            else:
 2633                return self._parse_as_command(start)
 2634        elif create_token_type in self.DB_CREATABLES:
 2635            table_parts = self._parse_table_parts(
 2636                schema=True, is_db_reference=create_token_type == TokenType.SCHEMA
 2637            )
 2638
 2639            # exp.Properties.Location.POST_NAME
 2640            self._match(TokenType.COMMA)
 2641            extend_props(self._parse_properties(before=True))
 2642
 2643            this = self._parse_schema(this=table_parts)
 2644
 2645            # exp.Properties.Location.POST_SCHEMA and POST_WITH
 2646            extend_props(self._parse_properties())
 2647
 2648            has_alias = self._match(TokenType.ALIAS)
 2649            if not self._match_set(self.DDL_SELECT_TOKENS, advance=False):
 2650                # exp.Properties.Location.POST_ALIAS
 2651                extend_props(self._parse_properties())
 2652
 2653            if create_token_type == TokenType.SEQUENCE:
 2654                expression = self._parse_types()
 2655                props = self._parse_properties()
 2656                if props:
 2657                    sequence_props = exp.SequenceProperties()
 2658                    options = []
 2659                    for prop in props:
 2660                        if isinstance(prop, exp.SequenceProperties):
 2661                            for arg, value in prop.args.items():
 2662                                if arg == "options":
 2663                                    options.extend(value)
 2664                                else:
 2665                                    sequence_props.set(arg, value)
 2666                            prop.pop()
 2667
 2668                    if options:
 2669                        sequence_props.set("options", options)
 2670
 2671                    props.append("expressions", sequence_props)
 2672                    extend_props(props)
 2673            else:
 2674                expression = self._parse_ddl_select()
 2675
 2676                # Some dialects also support using a table as an alias instead of a SELECT.
 2677                # Here we fallback to this as an alternative.
 2678                if not expression and has_alias:
 2679                    expression = self._try_parse(self._parse_table_parts)
 2680
 2681            if create_token_type == TokenType.TABLE:
 2682                # exp.Properties.Location.POST_EXPRESSION
 2683                extend_props(self._parse_properties())
 2684
 2685                indexes = []
 2686                while True:
 2687                    index = self._parse_index()
 2688
 2689                    # exp.Properties.Location.POST_INDEX
 2690                    extend_props(self._parse_properties())
 2691                    if not index:
 2692                        break
 2693                    else:
 2694                        self._match(TokenType.COMMA)
 2695                        indexes.append(index)
 2696            elif create_token_type == TokenType.VIEW:
 2697                if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"):
 2698                    no_schema_binding = True
 2699            elif create_token_type in (TokenType.SINK, TokenType.SOURCE):
 2700                extend_props(self._parse_properties())
 2701
 2702            shallow = self._match_text_seq("SHALLOW")
 2703
 2704            if self._match_texts(self.CLONE_KEYWORDS):
 2705                copy = self._prev.text.lower() == "copy"
 2706                clone = self.expression(
 2707                    exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy)
 2708                )
 2709
 2710        if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False):
 2711            return self._parse_as_command(start)
 2712
 2713        create_kind_text = create_token.text.upper()
 2714        return self.expression(
 2715            exp.Create(
 2716                this=this,
 2717                kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text,
 2718                replace=replace,
 2719                refresh=refresh,
 2720                unique=unique,
 2721                expression=expression,
 2722                exists=exists,
 2723                properties=properties,
 2724                indexes=indexes,
 2725                no_schema_binding=no_schema_binding,
 2726                begin=begin,
 2727                clone=clone,
 2728                concurrently=concurrently,
 2729                clustered=clustered,
 2730            )
 2731        )
 2732
 2733    def _parse_sequence_properties(self) -> exp.SequenceProperties | None:
 2734        seq = exp.SequenceProperties()
 2735
 2736        options = []
 2737        index = self._index
 2738
 2739        while self._curr:
 2740            self._match(TokenType.COMMA)
 2741            if self._match_text_seq("INCREMENT"):
 2742                self._match_text_seq("BY")
 2743                self._match_text_seq("=")
 2744                seq.set("increment", self._parse_term())
 2745            elif self._match_text_seq("MINVALUE"):
 2746                seq.set("minvalue", self._parse_term())
 2747            elif self._match_text_seq("MAXVALUE"):
 2748                seq.set("maxvalue", self._parse_term())
 2749            elif self._match_text_seq("START"):
 2750                self._match_text_seq("WITH")
 2751                self._match_text_seq("=")
 2752                seq.set("start", self._parse_term())
 2753            elif self._match_text_seq("CACHE"):
 2754                # T-SQL allows empty CACHE which is initialized dynamically
 2755                seq.set("cache", self._parse_number() or True)
 2756            elif self._match_text_seq("OWNED", "BY"):
 2757                # "OWNED BY NONE" is the default
 2758                seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column())
 2759            else:
 2760                opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False)
 2761                if opt:
 2762                    options.append(opt)
 2763                else:
 2764                    break
 2765
 2766        seq.set("options", options if options else None)
 2767        return None if self._index == index else seq
 2768
 2769    def _parse_trigger_events(self) -> list[exp.TriggerEvent]:
 2770        events = []
 2771
 2772        while True:
 2773            event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper()
 2774
 2775            if not event_type:
 2776                self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)")
 2777
 2778            columns = (
 2779                self._parse_csv(self._parse_column)
 2780                if event_type == "UPDATE" and self._match_text_seq("OF")
 2781                else None
 2782            )
 2783
 2784            events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns)))
 2785
 2786            if not self._match(TokenType.OR):
 2787                break
 2788
 2789        return events
 2790
 2791    def _parse_trigger_deferrable(
 2792        self,
 2793    ) -> tuple[str | None, str | None]:
 2794        deferrable_var = self._parse_var_from_options(
 2795            self.TRIGGER_DEFERRABLE, raise_unmatched=False
 2796        )
 2797        deferrable = deferrable_var.this if deferrable_var else None
 2798
 2799        initially = None
 2800        if deferrable and self._match_text_seq("INITIALLY"):
 2801            initially = (
 2802                self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None
 2803            )
 2804
 2805        return deferrable, initially
 2806
 2807    def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None:
 2808        if not self._match_text_seq(keyword):
 2809            return None
 2810        if not self._match_text_seq("TABLE"):
 2811            self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause")
 2812        self._match_text_seq("AS")
 2813        return self._parse_id_var()
 2814
 2815    def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None:
 2816        if not self._match_text_seq("REFERENCING"):
 2817            return None
 2818
 2819        old_alias = None
 2820        new_alias = None
 2821
 2822        while True:
 2823            if alias := self._parse_trigger_referencing_clause("OLD"):
 2824                if old_alias is not None:
 2825                    self.raise_error("Duplicate OLD clause in REFERENCING")
 2826                old_alias = alias
 2827            elif alias := self._parse_trigger_referencing_clause("NEW"):
 2828                if new_alias is not None:
 2829                    self.raise_error("Duplicate NEW clause in REFERENCING")
 2830                new_alias = alias
 2831            else:
 2832                break
 2833
 2834        if old_alias is None and new_alias is None:
 2835            self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE")
 2836
 2837        return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias))
 2838
 2839    def _parse_trigger_for_each(self) -> str | None:
 2840        if not self._match_text_seq("FOR", "EACH"):
 2841            return None
 2842
 2843        return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None
 2844
 2845    def _parse_trigger_execute(self) -> exp.TriggerExecute | None:
 2846        if not self._match(TokenType.EXECUTE):
 2847            return None
 2848
 2849        if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)):
 2850            self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE")
 2851
 2852        func_call = self._parse_column()
 2853        return self.expression(exp.TriggerExecute(this=func_call))
 2854
 2855    def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None:
 2856        # only used for teradata currently
 2857        self._match(TokenType.COMMA)
 2858
 2859        kwargs = {
 2860            "no": self._match_text_seq("NO"),
 2861            "dual": self._match_text_seq("DUAL"),
 2862            "before": self._match_text_seq("BEFORE"),
 2863            "default": self._match_text_seq("DEFAULT"),
 2864            "local": (self._match_text_seq("LOCAL") and "LOCAL")
 2865            or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"),
 2866            "after": self._match_text_seq("AFTER"),
 2867            "minimum": self._match_texts(("MIN", "MINIMUM")),
 2868            "maximum": self._match_texts(("MAX", "MAXIMUM")),
 2869        }
 2870
 2871        if self._match_texts(self.PROPERTY_PARSERS):
 2872            parser = self.PROPERTY_PARSERS[self._prev.text.upper()]
 2873            try:
 2874                return parser(self, **{k: v for k, v in kwargs.items() if v})
 2875            except TypeError:
 2876                self.raise_error(f"Cannot parse property '{self._prev.text}'")
 2877
 2878        if self._match_text_seq("CHARACTER", "SET"):
 2879            return self._parse_character_set(default=bool(kwargs["default"]))
 2880
 2881        return None
 2882
 2883    def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]:
 2884        return self._parse_wrapped_csv(self._parse_property)
 2885
 2886    def _parse_property(self) -> exp.Expr | list[exp.Expr] | None:
 2887        if self._match_texts(self.PROPERTY_PARSERS):
 2888            return self.PROPERTY_PARSERS[self._prev.text.upper()](self)
 2889
 2890        if self._match_text_seq("CHARACTER", "SET"):
 2891            return self._parse_character_set()
 2892
 2893        if self._match(TokenType.DEFAULT):
 2894            if self._match_texts(self.PROPERTY_PARSERS):
 2895                return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True)
 2896
 2897            if self._match_text_seq("CHARACTER", "SET"):
 2898                return self._parse_character_set(default=True)
 2899
 2900        if self._match_text_seq("COMPOUND", "SORTKEY"):
 2901            return self._parse_sortkey(compound=True)
 2902
 2903        if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"):
 2904            return self.expression(exp.ParameterStyleProperty(this="PANDAS"))
 2905
 2906        index = self._index
 2907
 2908        seq_props = self._parse_sequence_properties()
 2909        if seq_props:
 2910            return seq_props
 2911
 2912        self._retreat(index)
 2913        return self._parse_key_value_property()
 2914
 2915    def _parse_key_value_property(
 2916        self, parse_value: t.Callable[[], exp.Expr | None] | None = None
 2917    ) -> exp.Property | None:
 2918        index = self._index
 2919        key = self._parse_column()
 2920
 2921        if not self._match(TokenType.EQ):
 2922            self._retreat(index)
 2923            return None
 2924
 2925        # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise
 2926        if isinstance(key, exp.Column):
 2927            key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name)
 2928
 2929        value = (
 2930            parse_value()
 2931            if parse_value
 2932            else self._parse_bitwise() or self._parse_var(any_token=True)
 2933        )
 2934
 2935        # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier())
 2936        if isinstance(value, exp.Column):
 2937            value = exp.var(value.name)
 2938
 2939        return self.expression(exp.Property(this=key, value=value))
 2940
 2941    def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty:
 2942        if self._match_text_seq("BY"):
 2943            return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string()))
 2944
 2945        self._match(TokenType.ALIAS)
 2946        input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None
 2947        output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None
 2948
 2949        return self.expression(
 2950            exp.FileFormatProperty(
 2951                this=(
 2952                    self.expression(
 2953                        exp.InputOutputFormat(
 2954                            input_format=input_format, output_format=output_format
 2955                        )
 2956                    )
 2957                    if input_format or output_format
 2958                    else self._parse_var_or_string() or self._parse_number() or self._parse_id_var()
 2959                ),
 2960                hive_format=True,
 2961            )
 2962        )
 2963
 2964    def _parse_unquoted_field(self) -> exp.Expr | None:
 2965        field = self._parse_field()
 2966        if isinstance(field, exp.Identifier) and not field.quoted:
 2967            field = exp.var(field)
 2968
 2969        return field
 2970
 2971    def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E:
 2972        self._match(TokenType.EQ)
 2973        self._match(TokenType.ALIAS)
 2974
 2975        return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs))
 2976
 2977    def _parse_properties(self, before: bool | None = None) -> exp.Properties | None:
 2978        properties = []
 2979        while True:
 2980            if before:
 2981                prop = self._parse_property_before()
 2982            else:
 2983                prop = self._parse_property()
 2984            if not prop:
 2985                break
 2986            for p in ensure_list(prop):
 2987                properties.append(p)
 2988
 2989        if properties:
 2990            return self.expression(exp.Properties(expressions=properties))
 2991
 2992        return None
 2993
 2994    def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty:
 2995        return self.expression(
 2996            exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION"))
 2997        )
 2998
 2999    def _parse_sql_security(self) -> exp.SqlSecurityProperty:
 3000        return self.expression(
 3001            exp.SqlSecurityProperty(
 3002                this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper()
 3003            )
 3004        )
 3005
 3006    def _parse_settings_property(self) -> exp.SettingsProperty:
 3007        return self.expression(
 3008            exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment))
 3009        )
 3010
 3011    def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None:
 3012        if not self._match_text_seq("ON", "NULL", "INPUT"):
 3013            self._retreat(self._index - 1)
 3014            return None
 3015
 3016        return self.expression(exp.CalledOnNullInputProperty())
 3017
 3018    def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty:
 3019        if self._index >= 2:
 3020            pre_volatile_token = self._tokens[self._index - 2]
 3021        else:
 3022            pre_volatile_token = None
 3023
 3024        if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS:
 3025            return exp.VolatileProperty()
 3026
 3027        return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 3028
 3029    def _parse_retention_period(self) -> exp.Var:
 3030        # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...}
 3031        number = self._parse_number()
 3032        number_str = f"{number} " if number else ""
 3033        unit = self._parse_var(any_token=True)
 3034        return exp.var(f"{number_str}{unit}")
 3035
 3036    def _parse_system_versioning_property(
 3037        self, with_: bool = False
 3038    ) -> exp.WithSystemVersioningProperty:
 3039        self._match(TokenType.EQ)
 3040        prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_))
 3041
 3042        if self._match_text_seq("OFF"):
 3043            prop.set("on", False)
 3044            return prop
 3045
 3046        self._match(TokenType.ON)
 3047        if self._match(TokenType.L_PAREN):
 3048            while self._curr and not self._match(TokenType.R_PAREN):
 3049                if self._match_text_seq("HISTORY_TABLE", "="):
 3050                    prop.set("this", self._parse_table_parts())
 3051                elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="):
 3052                    prop.set("data_consistency", self._advance_any() and self._prev.text.upper())
 3053                elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="):
 3054                    prop.set("retention_period", self._parse_retention_period())
 3055
 3056                self._match(TokenType.COMMA)
 3057
 3058        return prop
 3059
 3060    def _parse_data_deletion_property(self) -> exp.DataDeletionProperty:
 3061        self._match(TokenType.EQ)
 3062        on = self._match_text_seq("ON") or not self._match_text_seq("OFF")
 3063        prop = self.expression(exp.DataDeletionProperty(on=on))
 3064
 3065        if self._match(TokenType.L_PAREN):
 3066            while self._curr and not self._match(TokenType.R_PAREN):
 3067                if self._match_text_seq("FILTER_COLUMN", "="):
 3068                    prop.set("filter_column", self._parse_column())
 3069                elif self._match_text_seq("RETENTION_PERIOD", "="):
 3070                    prop.set("retention_period", self._parse_retention_period())
 3071
 3072                self._match(TokenType.COMMA)
 3073
 3074        return prop
 3075
 3076    def _parse_distributed_property(self) -> exp.DistributedByProperty:
 3077        kind = "HASH"
 3078        expressions: list[exp.Expr] | None = None
 3079        if self._match_text_seq("BY", "HASH"):
 3080            expressions = self._parse_wrapped_csv(self._parse_id_var)
 3081        elif self._match_text_seq("BY", "RANDOM"):
 3082            kind = "RANDOM"
 3083
 3084        # If the BUCKETS keyword is not present, the number of buckets is AUTO
 3085        buckets: exp.Expr | None = None
 3086        if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"):
 3087            buckets = self._parse_number()
 3088
 3089        return self.expression(
 3090            exp.DistributedByProperty(
 3091                expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order()
 3092            )
 3093        )
 3094
 3095    def _parse_composite_key_property(self, expr_type: type[E]) -> E:
 3096        self._match_text_seq("KEY")
 3097        expressions = self._parse_wrapped_id_vars()
 3098        return self.expression(expr_type(expressions=expressions))
 3099
 3100    def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]:
 3101        if self._match_text_seq("(", "SYSTEM_VERSIONING"):
 3102            prop = self._parse_system_versioning_property(with_=True)
 3103            self._match_r_paren()
 3104            return prop
 3105
 3106        if self._match(TokenType.L_PAREN, advance=False):
 3107            result: list[exp.Expr] = []
 3108            for i in self._parse_wrapped_properties():
 3109                result.extend(i) if isinstance(i, list) else result.append(i)
 3110            return result
 3111
 3112        if self._match_text_seq("JOURNAL"):
 3113            return self._parse_withjournaltable()
 3114
 3115        if self._match_texts(self.VIEW_ATTRIBUTES):
 3116            return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper()))
 3117
 3118        if self._match_text_seq("DATA"):
 3119            return self._parse_withdata(no=False)
 3120        elif self._match_text_seq("NO", "DATA"):
 3121            return self._parse_withdata(no=True)
 3122
 3123        if self._match(TokenType.SERDE_PROPERTIES, advance=False):
 3124            return self._parse_serde_properties(with_=True)
 3125
 3126        if self._match(TokenType.SCHEMA):
 3127            return self.expression(
 3128                exp.WithSchemaBindingProperty(
 3129                    this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS)
 3130                )
 3131            )
 3132
 3133        if self._match_texts(self.PROCEDURE_OPTIONS, advance=False):
 3134            return self.expression(
 3135                exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option))
 3136            )
 3137
 3138        if not self._next:
 3139            return None
 3140
 3141        return self._parse_withisolatedloading()
 3142
 3143    def _parse_procedure_option(self) -> exp.Expr | None:
 3144        if self._match_text_seq("EXECUTE", "AS"):
 3145            return self.expression(
 3146                exp.ExecuteAsProperty(
 3147                    this=self._parse_var_from_options(
 3148                        self.EXECUTE_AS_OPTIONS, raise_unmatched=False
 3149                    )
 3150                    or self._parse_string()
 3151                )
 3152            )
 3153
 3154        return self._parse_var_from_options(self.PROCEDURE_OPTIONS)
 3155
 3156    # https://dev.mysql.com/doc/refman/8.0/en/create-view.html
 3157    def _parse_definer(self) -> exp.DefinerProperty | None:
 3158        self._match(TokenType.EQ)
 3159
 3160        user = self._parse_id_var()
 3161        self._match(TokenType.PARAMETER)
 3162        host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text)
 3163
 3164        if not user or not host:
 3165            return None
 3166
 3167        return exp.DefinerProperty(this=f"{user}@{host}")
 3168
 3169    def _parse_withjournaltable(self) -> exp.WithJournalTableProperty:
 3170        self._match(TokenType.TABLE)
 3171        self._match(TokenType.EQ)
 3172        return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts()))
 3173
 3174    def _parse_log(self, no: bool = False) -> exp.LogProperty:
 3175        return self.expression(exp.LogProperty(no=no))
 3176
 3177    def _parse_journal(self, **kwargs) -> exp.JournalProperty:
 3178        return self.expression(exp.JournalProperty(**kwargs))
 3179
 3180    def _parse_checksum(self) -> exp.ChecksumProperty:
 3181        self._match(TokenType.EQ)
 3182
 3183        on = None
 3184        if self._match(TokenType.ON):
 3185            on = True
 3186        elif self._match_text_seq("OFF"):
 3187            on = False
 3188
 3189        return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT)))
 3190
 3191    def _parse_cluster(self) -> exp.Cluster:
 3192        self._match(TokenType.CLUSTER_BY)
 3193        return self.expression(
 3194            exp.Cluster(
 3195                expressions=self._parse_csv(self._parse_column),
 3196            )
 3197        )
 3198
 3199    def _parse_cluster_property(self) -> exp.ClusterProperty:
 3200        return self.expression(
 3201            exp.ClusterProperty(
 3202                expressions=self._parse_wrapped_csv(self._parse_column),
 3203            )
 3204        )
 3205
 3206    def _parse_clustered_by(self) -> exp.ClusteredByProperty:
 3207        self._match_text_seq("BY")
 3208
 3209        self._match_l_paren()
 3210        expressions = self._parse_csv(self._parse_column)
 3211        self._match_r_paren()
 3212
 3213        if self._match_text_seq("SORTED", "BY"):
 3214            self._match_l_paren()
 3215            sorted_by = self._parse_csv(self._parse_ordered)
 3216            self._match_r_paren()
 3217        else:
 3218            sorted_by = None
 3219
 3220        self._match(TokenType.INTO)
 3221        buckets = self._parse_number()
 3222        self._match_text_seq("BUCKETS")
 3223
 3224        return self.expression(
 3225            exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets)
 3226        )
 3227
 3228    def _parse_copy_property(self) -> exp.CopyGrantsProperty | None:
 3229        if not self._match_text_seq("GRANTS"):
 3230            self._retreat(self._index - 1)
 3231            return None
 3232
 3233        return self.expression(exp.CopyGrantsProperty())
 3234
 3235    def _parse_freespace(self) -> exp.FreespaceProperty:
 3236        self._match(TokenType.EQ)
 3237        return self.expression(
 3238            exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT))
 3239        )
 3240
 3241    def _parse_mergeblockratio(
 3242        self, no: bool = False, default: bool = False
 3243    ) -> exp.MergeBlockRatioProperty:
 3244        if self._match(TokenType.EQ):
 3245            return self.expression(
 3246                exp.MergeBlockRatioProperty(
 3247                    this=self._parse_number(), percent=self._match(TokenType.PERCENT)
 3248                )
 3249            )
 3250
 3251        return self.expression(exp.MergeBlockRatioProperty(no=no, default=default))
 3252
 3253    def _parse_datablocksize(
 3254        self,
 3255        default: bool | None = None,
 3256        minimum: bool | None = None,
 3257        maximum: bool | None = None,
 3258    ) -> exp.DataBlocksizeProperty:
 3259        self._match(TokenType.EQ)
 3260        size = self._parse_number()
 3261
 3262        units = None
 3263        if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")):
 3264            units = self._prev.text
 3265
 3266        return self.expression(
 3267            exp.DataBlocksizeProperty(
 3268                size=size, units=units, default=default, minimum=minimum, maximum=maximum
 3269            )
 3270        )
 3271
 3272    def _parse_blockcompression(self) -> exp.BlockCompressionProperty:
 3273        self._match(TokenType.EQ)
 3274        always = self._match_text_seq("ALWAYS")
 3275        manual = self._match_text_seq("MANUAL")
 3276        never = self._match_text_seq("NEVER")
 3277        default = self._match_text_seq("DEFAULT")
 3278
 3279        autotemp = None
 3280        if self._match_text_seq("AUTOTEMP"):
 3281            autotemp = self._parse_schema()
 3282
 3283        return self.expression(
 3284            exp.BlockCompressionProperty(
 3285                always=always, manual=manual, never=never, default=default, autotemp=autotemp
 3286            )
 3287        )
 3288
 3289    def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None:
 3290        index = self._index
 3291        no = self._match_text_seq("NO")
 3292        concurrent = self._match_text_seq("CONCURRENT")
 3293
 3294        if not self._match_text_seq("ISOLATED", "LOADING"):
 3295            self._retreat(index)
 3296            return None
 3297
 3298        target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False)
 3299        return self.expression(
 3300            exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target)
 3301        )
 3302
 3303    def _parse_locking(self) -> exp.LockingProperty:
 3304        if self._match(TokenType.TABLE):
 3305            kind = "TABLE"
 3306        elif self._match(TokenType.VIEW):
 3307            kind = "VIEW"
 3308        elif self._match(TokenType.ROW):
 3309            kind = "ROW"
 3310        elif self._match_text_seq("DATABASE"):
 3311            kind = "DATABASE"
 3312        else:
 3313            kind = None
 3314
 3315        if kind in ("DATABASE", "TABLE", "VIEW"):
 3316            this = self._parse_table_parts()
 3317        else:
 3318            this = None
 3319
 3320        if self._match(TokenType.FOR):
 3321            for_or_in = "FOR"
 3322        elif self._match(TokenType.IN):
 3323            for_or_in = "IN"
 3324        else:
 3325            for_or_in = None
 3326
 3327        if self._match_text_seq("ACCESS"):
 3328            lock_type = "ACCESS"
 3329        elif self._match_texts(("EXCL", "EXCLUSIVE")):
 3330            lock_type = "EXCLUSIVE"
 3331        elif self._match_text_seq("SHARE"):
 3332            lock_type = "SHARE"
 3333        elif self._match_text_seq("READ"):
 3334            lock_type = "READ"
 3335        elif self._match_text_seq("WRITE"):
 3336            lock_type = "WRITE"
 3337        elif self._match_text_seq("CHECKSUM"):
 3338            lock_type = "CHECKSUM"
 3339        else:
 3340            lock_type = None
 3341
 3342        override = self._match_text_seq("OVERRIDE")
 3343
 3344        return self.expression(
 3345            exp.LockingProperty(
 3346                this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override
 3347            )
 3348        )
 3349
 3350    def _parse_partition_by(self) -> list[exp.Expr]:
 3351        if self._match(TokenType.PARTITION_BY):
 3352            return self._parse_csv(self._parse_disjunction)
 3353        return []
 3354
 3355    def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec:
 3356        def _parse_partition_bound_expr() -> exp.Expr | None:
 3357            if self._match_text_seq("MINVALUE"):
 3358                return exp.var("MINVALUE")
 3359            if self._match_text_seq("MAXVALUE"):
 3360                return exp.var("MAXVALUE")
 3361            return self._parse_bitwise()
 3362
 3363        this: exp.Expr | list[exp.Expr] | None = None
 3364        expression = None
 3365        from_expressions = None
 3366        to_expressions = None
 3367
 3368        if self._match(TokenType.IN):
 3369            this = self._parse_wrapped_csv(self._parse_bitwise)
 3370        elif self._match(TokenType.FROM):
 3371            from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3372            self._match_text_seq("TO")
 3373            to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3374        elif self._match_text_seq("WITH", "(", "MODULUS"):
 3375            this = self._parse_number()
 3376            self._match_text_seq(",", "REMAINDER")
 3377            expression = self._parse_number()
 3378            self._match_r_paren()
 3379        else:
 3380            self.raise_error("Failed to parse partition bound spec.")
 3381
 3382        return self.expression(
 3383            exp.PartitionBoundSpec(
 3384                this=this,
 3385                expression=expression,
 3386                from_expressions=from_expressions,
 3387                to_expressions=to_expressions,
 3388            )
 3389        )
 3390
 3391    # https://www.postgresql.org/docs/current/sql-createtable.html
 3392    def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None:
 3393        if not self._match_text_seq("OF"):
 3394            self._retreat(self._index - 1)
 3395            return None
 3396
 3397        this = self._parse_table(schema=True)
 3398
 3399        if self._match(TokenType.DEFAULT):
 3400            expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT")
 3401        elif self._match_text_seq("FOR", "VALUES"):
 3402            expression = self._parse_partition_bound_spec()
 3403        else:
 3404            self.raise_error("Expecting either DEFAULT or FOR VALUES clause.")
 3405
 3406        return self.expression(exp.PartitionedOfProperty(this=this, expression=expression))
 3407
 3408    def _parse_partitioned_by(self) -> exp.PartitionedByProperty:
 3409        self._match(TokenType.EQ)
 3410        return self.expression(
 3411            exp.PartitionedByProperty(
 3412                this=self._parse_schema() or self._parse_bracket(self._parse_field())
 3413            )
 3414        )
 3415
 3416    def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty:
 3417        if self._match_text_seq("AND", "STATISTICS"):
 3418            statistics = True
 3419        elif self._match_text_seq("AND", "NO", "STATISTICS"):
 3420            statistics = False
 3421        else:
 3422            statistics = None
 3423
 3424        return self.expression(exp.WithDataProperty(no=no, statistics=statistics))
 3425
 3426    def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None:
 3427        if self._match_text_seq("SQL"):
 3428            return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL"))
 3429        return None
 3430
 3431    def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None:
 3432        if self._match_text_seq("SQL", "DATA"):
 3433            return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA"))
 3434        return None
 3435
 3436    def _parse_no_property(self) -> exp.Expr | None:
 3437        if self._match_text_seq("PRIMARY", "INDEX"):
 3438            return exp.NoPrimaryIndexProperty()
 3439        if self._match_text_seq("SQL"):
 3440            return self.expression(exp.SqlReadWriteProperty(this="NO SQL"))
 3441        return None
 3442
 3443    def _parse_on_property(self) -> exp.Expr | None:
 3444        if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"):
 3445            return exp.OnCommitProperty()
 3446        if self._match_text_seq("COMMIT", "DELETE", "ROWS"):
 3447            return exp.OnCommitProperty(delete=True)
 3448        return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var())))
 3449
 3450    def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None:
 3451        if self._match_text_seq("SQL", "DATA"):
 3452            return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA"))
 3453        return None
 3454
 3455    def _parse_distkey(self) -> exp.DistKeyProperty:
 3456        return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var)))
 3457
 3458    def _parse_create_like(self) -> exp.LikeProperty | None:
 3459        table = self._parse_table(schema=True)
 3460
 3461        options = []
 3462        while self._match_texts(("INCLUDING", "EXCLUDING")):
 3463            this = self._prev.text.upper()
 3464
 3465            id_var = self._parse_id_var()
 3466            if not id_var:
 3467                return None
 3468
 3469            options.append(
 3470                self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper())))
 3471            )
 3472
 3473        return self.expression(exp.LikeProperty(this=table, expressions=options))
 3474
 3475    def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty:
 3476        return self.expression(
 3477            exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound)
 3478        )
 3479
 3480    def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty:
 3481        self._match(TokenType.EQ)
 3482        return self.expression(
 3483            exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default)
 3484        )
 3485
 3486    def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty:
 3487        self._match_text_seq("WITH", "CONNECTION")
 3488        return self.expression(
 3489            exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts())
 3490        )
 3491
 3492    def _parse_returns(self) -> exp.ReturnsProperty:
 3493        value: exp.Expr | None
 3494        null = None
 3495        is_table = self._match(TokenType.TABLE)
 3496
 3497        if is_table:
 3498            if self._match(TokenType.LT):
 3499                value = self.expression(
 3500                    exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types))
 3501                )
 3502                if not self._match(TokenType.GT):
 3503                    self.raise_error("Expecting >")
 3504            else:
 3505                value = self._parse_schema(exp.var("TABLE"))
 3506        elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"):
 3507            null = True
 3508            value = None
 3509        else:
 3510            value = self._parse_types()
 3511
 3512        return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null))
 3513
 3514    def _parse_describe(self) -> exp.Describe:
 3515        kind = self._prev.text if self._match_set(self.CREATABLES) else None
 3516        style: str | None = (
 3517            self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None
 3518        )
 3519        if self._match(TokenType.DOT):
 3520            style = None
 3521            self._retreat(self._index - 2)
 3522
 3523        format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None
 3524
 3525        if self._match_set(self.STATEMENT_PARSERS, advance=False):
 3526            this = self._parse_statement()
 3527        else:
 3528            this = self._parse_table(schema=True)
 3529
 3530        properties = self._parse_properties()
 3531        expressions = properties.expressions if properties else None
 3532        partition = self._parse_partition()
 3533        return self.expression(
 3534            exp.Describe(
 3535                this=this,
 3536                style=style,
 3537                kind=kind,
 3538                expressions=expressions,
 3539                partition=partition,
 3540                format=format,
 3541                as_json=self._match_text_seq("AS", "JSON"),
 3542            )
 3543        )
 3544
 3545    def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts:
 3546        kind = self._prev.text.upper()
 3547        expressions = []
 3548
 3549        def parse_conditional_insert() -> exp.ConditionalInsert | None:
 3550            if self._match(TokenType.WHEN):
 3551                expression = self._parse_disjunction()
 3552                self._match(TokenType.THEN)
 3553            else:
 3554                expression = None
 3555
 3556            else_ = self._match(TokenType.ELSE)
 3557
 3558            if not self._match(TokenType.INTO):
 3559                return None
 3560
 3561            return self.expression(
 3562                exp.ConditionalInsert(
 3563                    this=self.expression(
 3564                        exp.Insert(
 3565                            this=self._parse_table(schema=True),
 3566                            expression=self._parse_derived_table_values(),
 3567                        )
 3568                    ),
 3569                    expression=expression,
 3570                    else_=else_,
 3571                )
 3572            )
 3573
 3574        expression = parse_conditional_insert()
 3575        while expression is not None:
 3576            expressions.append(expression)
 3577            expression = parse_conditional_insert()
 3578
 3579        return self.expression(
 3580            exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()),
 3581            comments=comments,
 3582        )
 3583
 3584    def _parse_insert(self) -> exp.Insert | exp.MultitableInserts:
 3585        comments: list[str] = []
 3586        hint = self._parse_hint()
 3587        overwrite = self._match(TokenType.OVERWRITE)
 3588        ignore = self._match(TokenType.IGNORE)
 3589        local = self._match_text_seq("LOCAL")
 3590        alternative = None
 3591        is_function = None
 3592
 3593        if self._match_text_seq("DIRECTORY"):
 3594            this: exp.Expr | None = self.expression(
 3595                exp.Directory(
 3596                    this=self._parse_var_or_string(),
 3597                    local=local,
 3598                    row_format=self._parse_row_format(match_row=True),
 3599                )
 3600            )
 3601        else:
 3602            if self._match_set((TokenType.FIRST, TokenType.ALL)):
 3603                comments += ensure_list(self._prev_comments)
 3604                return self._parse_multitable_inserts(comments)
 3605
 3606            if self._match(TokenType.OR):
 3607                alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text
 3608
 3609            self._match(TokenType.INTO)
 3610            comments += ensure_list(self._prev_comments)
 3611            self._match(TokenType.TABLE)
 3612            is_function = self._match(TokenType.FUNCTION)
 3613
 3614            this = self._parse_function() if is_function else self._parse_insert_table()
 3615
 3616        # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant
 3617        set_values = None
 3618        if self._match(TokenType.SET):
 3619            columns = []
 3620            values = []
 3621
 3622            def _parse_set_assignment() -> exp.Expr | None:
 3623                target = self._parse_column()
 3624                if isinstance(target, exp.Column) and self._match(TokenType.EQ):
 3625                    if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3626                        value: exp.Expr | None = exp.var(self._prev.text.upper())
 3627                    else:
 3628                        value = self._parse_disjunction()
 3629
 3630                    if value:
 3631                        columns.append(target.this)
 3632                        values.append(value)
 3633                        return value
 3634
 3635                self.raise_error("Expected column assignment in INSERT ... SET")
 3636                return None
 3637
 3638            self._parse_csv(_parse_set_assignment)
 3639
 3640            this = self.expression(exp.Schema(this=this, expressions=columns))
 3641            set_values = self.expression(
 3642                exp.Values(
 3643                    expressions=[exp.Tuple(expressions=values)],
 3644                    alias=self._parse_table_alias(),
 3645                )
 3646            )
 3647
 3648        returning = self._parse_returning()  # TSQL allows RETURNING before source
 3649
 3650        stored = self._match_text_seq("STORED") and self._parse_stored()
 3651        by_name = self._match_text_seq("BY", "NAME")
 3652        exists = self._parse_exists()
 3653        replace_where = None
 3654        replace_using = None
 3655
 3656        if self._match(TokenType.REPLACE):
 3657            if self._match(TokenType.WHERE):
 3658                replace_where = self._parse_disjunction()
 3659            elif self._match(TokenType.USING):
 3660                replace_using = self._parse_using_identifiers()
 3661
 3662        return self.expression(
 3663            exp.Insert(
 3664                hint=hint,
 3665                is_function=is_function,
 3666                this=this,
 3667                stored=stored,
 3668                by_name=by_name,
 3669                exists=exists,
 3670                where=replace_where,
 3671                using=replace_using,
 3672                partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(),
 3673                settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(),
 3674                default=self._match_text_seq("DEFAULT", "VALUES"),
 3675                expression=set_values
 3676                or self._parse_derived_table_values()
 3677                or self._parse_ddl_select(),
 3678                conflict=self._parse_on_conflict(),
 3679                returning=returning or self._parse_returning(),
 3680                overwrite=overwrite,
 3681                alternative=alternative,
 3682                ignore=ignore,
 3683                source=self._match(TokenType.TABLE) and self._parse_table(),
 3684            ),
 3685            comments=comments,
 3686        )
 3687
 3688    def _parse_insert_table(self) -> exp.Expr | None:
 3689        this = self._parse_table(schema=True, parse_partition=True)
 3690        if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False):
 3691            this.set("alias", self._parse_table_alias())
 3692        return this
 3693
 3694    def _parse_kill(self) -> exp.Kill:
 3695        kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None
 3696
 3697        return self.expression(exp.Kill(this=self._parse_primary(), kind=kind))
 3698
 3699    def _parse_on_conflict(self) -> exp.OnConflict | None:
 3700        conflict = self._match_text_seq("ON", "CONFLICT")
 3701        duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY")
 3702
 3703        if not conflict and not duplicate:
 3704            return None
 3705
 3706        conflict_keys = None
 3707        constraint = None
 3708
 3709        if conflict:
 3710            if self._match_text_seq("ON", "CONSTRAINT"):
 3711                constraint = self._parse_id_var()
 3712            elif self._match(TokenType.L_PAREN):
 3713                conflict_keys = self._parse_csv(self._parse_indexed_column)
 3714                self._match_r_paren()
 3715
 3716        index_predicate = self._parse_where()
 3717
 3718        action = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 3719        if self._prev.token_type == TokenType.UPDATE:
 3720            self._match(TokenType.SET)
 3721            expressions = self._parse_csv(self._parse_equality)
 3722        else:
 3723            expressions = None
 3724
 3725        return self.expression(
 3726            exp.OnConflict(
 3727                duplicate=duplicate,
 3728                expressions=expressions,
 3729                action=action,
 3730                conflict_keys=conflict_keys,
 3731                index_predicate=index_predicate,
 3732                constraint=constraint,
 3733                where=self._parse_where(),
 3734            )
 3735        )
 3736
 3737    def _parse_returning(self) -> exp.Returning | None:
 3738        if not self._match(TokenType.RETURNING):
 3739            return None
 3740        return self.expression(
 3741            exp.Returning(
 3742                expressions=self._parse_csv(self._parse_expression),
 3743                into=self._match(TokenType.INTO) and self._parse_table_part(),
 3744            )
 3745        )
 3746
 3747    def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3748        if not self._match(TokenType.FORMAT):
 3749            return None
 3750        return self._parse_row_format()
 3751
 3752    def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None:
 3753        index = self._index
 3754        with_ = with_ or self._match_text_seq("WITH")
 3755
 3756        if not self._match(TokenType.SERDE_PROPERTIES):
 3757            self._retreat(index)
 3758            return None
 3759        return self.expression(
 3760            exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_)
 3761        )
 3762
 3763    def _parse_row_format(
 3764        self, match_row: bool = False
 3765    ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3766        if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT):
 3767            return None
 3768
 3769        if self._match_text_seq("SERDE"):
 3770            this = self._parse_string()
 3771
 3772            serde_properties = self._parse_serde_properties()
 3773
 3774            return self.expression(
 3775                exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties)
 3776            )
 3777
 3778        self._match_text_seq("DELIMITED")
 3779
 3780        kwargs = {}
 3781
 3782        if self._match_text_seq("FIELDS", "TERMINATED", "BY"):
 3783            kwargs["fields"] = self._parse_string()
 3784            if self._match_text_seq("ESCAPED", "BY"):
 3785                kwargs["escaped"] = self._parse_string()
 3786        if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"):
 3787            kwargs["collection_items"] = self._parse_string()
 3788        if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"):
 3789            kwargs["map_keys"] = self._parse_string()
 3790        if self._match_text_seq("LINES", "TERMINATED", "BY"):
 3791            kwargs["lines"] = self._parse_string()
 3792        if self._match_text_seq("NULL", "DEFINED", "AS"):
 3793            kwargs["null"] = self._parse_string()
 3794
 3795        return self.expression(exp.RowFormatDelimitedProperty(**kwargs))  # type: ignore
 3796
 3797    def _parse_load(self) -> exp.LoadData | exp.Command:
 3798        if self._match_text_seq("DATA"):
 3799            local = self._match_text_seq("LOCAL")
 3800            self._match_text_seq("INPATH")
 3801            inpath = self._parse_string()
 3802            overwrite = self._match(TokenType.OVERWRITE)
 3803            temp: bool | None = None
 3804            if self._match(TokenType.INTO):
 3805                temp = self._match(TokenType.TEMPORARY)
 3806                self._match(TokenType.TABLE)
 3807
 3808            return self.expression(
 3809                exp.LoadData(
 3810                    this=self._parse_table(schema=True),
 3811                    local=local,
 3812                    overwrite=overwrite,
 3813                    temp=temp,
 3814                    inpath=inpath,
 3815                    files=self._match_text_seq("FROM", "FILES")
 3816                    and exp.Properties(expressions=self._parse_wrapped_properties()),
 3817                    partition=self._parse_partition(),
 3818                    input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(),
 3819                    serde=self._match_text_seq("SERDE") and self._parse_string(),
 3820                )
 3821            )
 3822        return self._parse_as_command(self._prev)
 3823
 3824    def _parse_delete(self) -> exp.Delete:
 3825        hint = self._parse_hint()
 3826
 3827        # This handles MySQL's "Multiple-Table Syntax"
 3828        # https://dev.mysql.com/doc/refman/8.0/en/delete.html
 3829        tables = None
 3830        if not self._match(TokenType.FROM, advance=False):
 3831            tables = self._parse_csv(self._parse_table) or None
 3832
 3833        returning = self._parse_returning()
 3834
 3835        return self.expression(
 3836            exp.Delete(
 3837                hint=hint,
 3838                tables=tables,
 3839                this=self._match(TokenType.FROM) and self._parse_table(joins=True),
 3840                using=self._match(TokenType.USING)
 3841                and self._parse_csv(lambda: self._parse_table(joins=True)),
 3842                cluster=self._match(TokenType.ON) and self._parse_on_property(),
 3843                where=self._parse_where(),
 3844                returning=returning or self._parse_returning(),
 3845                order=self._parse_order(),
 3846                limit=self._parse_limit(),
 3847            )
 3848        )
 3849
 3850    def _parse_update(self) -> exp.Update:
 3851        hint = self._parse_hint()
 3852        kwargs: dict[str, object] = {
 3853            "hint": hint,
 3854            "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS),
 3855        }
 3856        while self._curr:
 3857            if self._match(TokenType.SET):
 3858                kwargs["expressions"] = self._parse_csv(self._parse_equality)
 3859            elif self._match(TokenType.RETURNING, advance=False):
 3860                kwargs["returning"] = self._parse_returning()
 3861            elif self._match(TokenType.FROM, advance=False):
 3862                from_ = self._parse_from(joins=True)
 3863                table = from_.this if from_ else None
 3864                if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False):
 3865                    table.set("joins", list(self._parse_joins()) or None)
 3866
 3867                kwargs["from_"] = from_
 3868            elif self._match(TokenType.WHERE, advance=False):
 3869                kwargs["where"] = self._parse_where()
 3870            elif self._match(TokenType.ORDER_BY, advance=False):
 3871                kwargs["order"] = self._parse_order()
 3872            elif self._match(TokenType.LIMIT, advance=False):
 3873                kwargs["limit"] = self._parse_limit()
 3874            else:
 3875                break
 3876
 3877        return self.expression(exp.Update(**kwargs))
 3878
 3879    def _parse_use(self) -> exp.Use:
 3880        return self.expression(
 3881            exp.Use(
 3882                kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False),
 3883                this=self._parse_table(schema=False),
 3884            )
 3885        )
 3886
 3887    def _parse_uncache(self) -> exp.Uncache:
 3888        if not self._match(TokenType.TABLE):
 3889            self.raise_error("Expecting TABLE after UNCACHE")
 3890
 3891        return self.expression(
 3892            exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True))
 3893        )
 3894
 3895    def _parse_cache(self) -> exp.Cache:
 3896        lazy = self._match_text_seq("LAZY")
 3897        self._match(TokenType.TABLE)
 3898        table = self._parse_table(schema=True)
 3899
 3900        options = []
 3901        if self._match_text_seq("OPTIONS"):
 3902            self._match_l_paren()
 3903            k = self._parse_string()
 3904            self._match(TokenType.EQ)
 3905            v = self._parse_string()
 3906            options = [k, v]
 3907            self._match_r_paren()
 3908
 3909        self._match(TokenType.ALIAS)
 3910        return self.expression(
 3911            exp.Cache(
 3912                this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True)
 3913            )
 3914        )
 3915
 3916    def _parse_partition(self) -> exp.Partition | None:
 3917        if not self._match_texts(self.PARTITION_KEYWORDS):
 3918            return None
 3919
 3920        return self.expression(
 3921            exp.Partition(
 3922                subpartition=self._prev.text.upper() == "SUBPARTITION",
 3923                expressions=self._parse_wrapped_csv(self._parse_disjunction),
 3924            )
 3925        )
 3926
 3927    def _parse_value(self, values: bool = True) -> exp.Tuple | None:
 3928        def _parse_value_expression() -> exp.Expr | None:
 3929            if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3930                return exp.var(self._prev.text.upper())
 3931            return self._parse_expression()
 3932
 3933        if self._match(TokenType.L_PAREN):
 3934            expressions = self._parse_csv(_parse_value_expression)
 3935            self._match_r_paren()
 3936            return self.expression(exp.Tuple(expressions=expressions))
 3937
 3938        # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows.
 3939        expression = self._parse_expression()
 3940        if expression:
 3941            return self.expression(exp.Tuple(expressions=[expression]))
 3942        return None
 3943
 3944    def _parse_projections(
 3945        self,
 3946    ) -> tuple[list[exp.Expr], list[exp.Expr] | None]:
 3947        return self._parse_expressions(), None
 3948
 3949    def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None:
 3950        if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)):
 3951            this: exp.Expr | None = self._parse_simplified_pivot(
 3952                is_unpivot=self._prev.token_type == TokenType.UNPIVOT
 3953            )
 3954        elif self._match(TokenType.FROM):
 3955            from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True)
 3956            # Support parentheses for duckdb FROM-first syntax
 3957            select = self._parse_select(from_=from_)
 3958            if select:
 3959                if not select.args.get("from_"):
 3960                    select.set("from_", from_)
 3961                this = select
 3962            else:
 3963                this = exp.select("*").from_(t.cast(exp.From, from_))
 3964                this = self._parse_query_modifiers(self._parse_set_operations(this))
 3965        else:
 3966            this = (
 3967                self._parse_table(consume_pipe=True)
 3968                if table
 3969                else self._parse_select(nested=True, parse_set_operation=False)
 3970            )
 3971
 3972            # Transform exp.Values into a exp.Table to pass through parse_query_modifiers
 3973            # in case a modifier (e.g. join) is following
 3974            if table and isinstance(this, exp.Values) and this.alias:
 3975                alias = this.args["alias"].pop()
 3976                this = exp.Table(this=this, alias=alias)
 3977
 3978            this = self._parse_query_modifiers(self._parse_set_operations(this))
 3979
 3980        return this
 3981
 3982    def _parse_select(
 3983        self,
 3984        nested: bool = False,
 3985        table: bool = False,
 3986        parse_subquery_alias: bool = True,
 3987        parse_set_operation: bool = True,
 3988        consume_pipe: bool = True,
 3989        from_: exp.From | None = None,
 3990    ) -> exp.Expr | None:
 3991        query = self._parse_select_query(
 3992            nested=nested,
 3993            table=table,
 3994            parse_subquery_alias=parse_subquery_alias,
 3995            parse_set_operation=parse_set_operation,
 3996        )
 3997
 3998        if consume_pipe and self._match(TokenType.PIPE_GT, advance=False):
 3999            if not query and from_:
 4000                query = exp.select("*").from_(from_)
 4001            if isinstance(query, exp.Query):
 4002                query = self._parse_pipe_syntax_query(query)
 4003                query = query.subquery(copy=False) if query and table else query
 4004
 4005        return query
 4006
 4007    def _parse_select_query(
 4008        self,
 4009        nested: bool = False,
 4010        table: bool = False,
 4011        parse_subquery_alias: bool = True,
 4012        parse_set_operation: bool = True,
 4013    ) -> exp.Expr | None:
 4014        cte = self._parse_with()
 4015
 4016        if cte:
 4017            this = self._parse_statement()
 4018
 4019            if not this:
 4020                self.raise_error("Failed to parse any statement following CTE")
 4021                return cte
 4022
 4023            while isinstance(this, exp.Subquery) and this.is_wrapper:
 4024                this = this.this
 4025
 4026            assert this is not None
 4027            if "with_" in this.arg_types:
 4028                if inner_cte := this.args.get("with_"):
 4029                    cte.set("expressions", cte.expressions + inner_cte.expressions)
 4030                    if inner_cte.args.get("recursive"):
 4031                        cte.set("recursive", True)
 4032                this.set("with_", cte)
 4033            else:
 4034                self.raise_error(f"{this.key} does not support CTE")
 4035                this = cte
 4036
 4037            return this
 4038
 4039        # duckdb supports leading with FROM x
 4040        from_ = (
 4041            self._parse_from(joins=True, consume_pipe=True)
 4042            if self._match(TokenType.FROM, advance=False)
 4043            else None
 4044        )
 4045
 4046        if self._match(TokenType.SELECT):
 4047            comments = self._prev_comments
 4048
 4049            hint = self._parse_hint()
 4050
 4051            if self._next and not self._next.token_type == TokenType.DOT:
 4052                all_ = self._match(TokenType.ALL)
 4053                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4054            else:
 4055                all_, matched_distinct = None, False
 4056
 4057            kind = (
 4058                self._prev.text.upper()
 4059                if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE"))
 4060                else None
 4061            )
 4062
 4063            distinct: exp.Expr | None = (
 4064                self.expression(
 4065                    exp.Distinct(
 4066                        on=self._parse_value(values=False) if self._match(TokenType.ON) else None
 4067                    )
 4068                )
 4069                if matched_distinct
 4070                else None
 4071            )
 4072
 4073            operation_modifiers = []
 4074            while self._curr and self._match_texts(self.OPERATION_MODIFIERS):
 4075                operation_modifiers.append(exp.var(self._prev.text.upper()))
 4076
 4077            limit = self._parse_limit(top=True)
 4078
 4079            # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ...
 4080            if limit and not matched_distinct and not all_:
 4081                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4082                if matched_distinct:
 4083                    distinct = self.expression(
 4084                        exp.Distinct(
 4085                            on=self._parse_value(values=False)
 4086                            if self._match(TokenType.ON)
 4087                            else None
 4088                        )
 4089                    )
 4090                else:
 4091                    all_ = self._match(TokenType.ALL)
 4092
 4093            if all_ and distinct:
 4094                self.raise_error("Cannot specify both ALL and DISTINCT after SELECT")
 4095
 4096            projections, exclude = self._parse_projections()
 4097
 4098            this = self.expression(
 4099                exp.Select(
 4100                    kind=kind,
 4101                    hint=hint,
 4102                    distinct=distinct,
 4103                    expressions=projections,
 4104                    limit=limit,
 4105                    exclude=exclude,
 4106                    operation_modifiers=operation_modifiers or None,
 4107                )
 4108            )
 4109            this.comments = comments
 4110
 4111            into = self._parse_into()
 4112            if into:
 4113                this.set("into", into)
 4114
 4115            if not from_:
 4116                from_ = self._parse_from()
 4117
 4118            if from_:
 4119                this.set("from_", from_)
 4120
 4121            this = self._parse_query_modifiers(this)
 4122        elif (table or nested) and self._match(TokenType.L_PAREN):
 4123            comments = self._prev_comments
 4124            this = self._parse_wrapped_select(table=table)
 4125
 4126            if this:
 4127                this.add_comments(comments, prepend=True)
 4128
 4129            # We return early here so that the UNION isn't attached to the subquery by the
 4130            # following call to _parse_set_operations, but instead becomes the parent node
 4131            self._match_r_paren()
 4132            return self._parse_subquery(this, parse_alias=parse_subquery_alias)
 4133        elif self._match(TokenType.VALUES, advance=False):
 4134            this = self._parse_derived_table_values()
 4135        elif from_:
 4136            this = exp.select("*").from_(from_.this, copy=False)
 4137            this = self._parse_query_modifiers(this)
 4138        elif self._match(TokenType.SUMMARIZE):
 4139            table = self._match(TokenType.TABLE)
 4140            this = self._parse_select() or self._parse_string() or self._parse_table()
 4141            return self.expression(exp.Summarize(this=this, table=table))
 4142        elif self._match(TokenType.DESCRIBE):
 4143            this = self._parse_describe()
 4144        else:
 4145            this = None
 4146
 4147        return self._parse_set_operations(this) if parse_set_operation else this
 4148
 4149    def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None:
 4150        self._match_text_seq("SEARCH")
 4151
 4152        kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper()
 4153
 4154        if not kind:
 4155            return None
 4156
 4157        self._match_text_seq("FIRST", "BY")
 4158
 4159        return self.expression(
 4160            exp.RecursiveWithSearch(
 4161                kind=kind,
 4162                this=self._parse_id_var(),
 4163                expression=self._match_text_seq("SET") and self._parse_id_var(),
 4164                using=self._match_text_seq("USING") and self._parse_id_var(),
 4165            )
 4166        )
 4167
 4168    def _parse_with(self, skip_with_token: bool = False) -> exp.With | None:
 4169        if not skip_with_token and not self._match(TokenType.WITH):
 4170            return None
 4171
 4172        comments = self._prev_comments
 4173        recursive = self._match(TokenType.RECURSIVE)
 4174
 4175        last_comments = None
 4176        expressions = []
 4177        udfs = []
 4178        while True:
 4179            cte = self._parse_cte()
 4180            if cte:
 4181                if isinstance(cte, exp.FunctionSpecification):
 4182                    udfs.append(cte)
 4183                else:
 4184                    expressions.append(cte)
 4185
 4186                if last_comments:
 4187                    cte.add_comments(last_comments)
 4188
 4189            if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH):
 4190                break
 4191            else:
 4192                self._match(TokenType.WITH)
 4193                recursive = self._match(TokenType.RECURSIVE) or recursive
 4194
 4195            last_comments = self._prev_comments
 4196
 4197        return self.expression(
 4198            exp.With(
 4199                expressions=expressions,
 4200                recursive=recursive or None,
 4201                search=self._parse_recursive_with_search(),
 4202                udfs=udfs or None,
 4203            ),
 4204            comments=comments,
 4205        )
 4206
 4207    def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None:
 4208        index = self._index
 4209
 4210        alias = self._parse_table_alias(self.ID_VAR_TOKENS)
 4211        if not alias or not alias.this:
 4212            self.raise_error("Expected CTE to have alias")
 4213
 4214        key_expressions = (
 4215            self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None
 4216        )
 4217
 4218        if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE:
 4219            self._retreat(index)
 4220            return None
 4221
 4222        comments = self._prev_comments
 4223
 4224        if self._match_text_seq("NOT", "MATERIALIZED"):
 4225            materialized = False
 4226        elif self._match_text_seq("MATERIALIZED"):
 4227            materialized = True
 4228        else:
 4229            materialized = None
 4230
 4231        cte = self.expression(
 4232            exp.CTE(
 4233                this=self._parse_wrapped(self._parse_statement),
 4234                alias=alias,
 4235                materialized=materialized,
 4236                key_expressions=key_expressions,
 4237            ),
 4238            comments=comments,
 4239        )
 4240
 4241        values = cte.this
 4242        if isinstance(values, exp.Values):
 4243            cte.set("this", self._values_to_select(values))
 4244
 4245        return cte
 4246
 4247    def _values_to_select(self, values: exp.Values) -> exp.Select:
 4248        if values.alias:
 4249            return exp.select("*").from_(values)
 4250        return exp.select("*").from_(exp.alias_(values, "_values", table=True))
 4251
 4252    def _parse_table_alias(
 4253        self, alias_tokens: t.Collection[TokenType] | None = None
 4254    ) -> exp.TableAlias | None:
 4255        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 4256        # so this section tries to parse the clause version and if it fails, it treats the token
 4257        # as an identifier (alias)
 4258        if self._can_parse_limit_or_offset():
 4259            return None
 4260
 4261        # START is never treated as an implicit alias when followed by WITH, since that
 4262        # would swallow the beginning of a START WITH ... CONNECT BY clause
 4263        if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH":
 4264            return None
 4265
 4266        any_token = self._match(TokenType.ALIAS)
 4267        alias = (
 4268            self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 4269            or self._parse_string_as_identifier()
 4270        )
 4271
 4272        index = self._index
 4273        if self._match(TokenType.L_PAREN):
 4274            columns = self._parse_csv(self._parse_function_parameter)
 4275            self._match_r_paren() if columns else self._retreat(index)
 4276        else:
 4277            columns = None
 4278
 4279        if not alias and not columns:
 4280            return None
 4281
 4282        table_alias = self.expression(exp.TableAlias(this=alias, columns=columns))
 4283
 4284        # We bubble up comments from the Identifier to the TableAlias
 4285        if isinstance(alias, exp.Identifier):
 4286            table_alias.add_comments(alias.pop_comments())
 4287
 4288        return table_alias
 4289
 4290    def _parse_subquery(
 4291        self, this: exp.Expr | None, parse_alias: bool = True
 4292    ) -> exp.Subquery | None:
 4293        if not this:
 4294            return None
 4295
 4296        return self.expression(
 4297            exp.Subquery(
 4298                this=this,
 4299                pivots=self._parse_pivots(),
 4300                alias=self._parse_table_alias() if parse_alias else None,
 4301                sample=self._parse_table_sample(),
 4302            )
 4303        )
 4304
 4305    def _implicit_unnests_to_explicit(self, this: E) -> E:
 4306        from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm
 4307
 4308        refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name}
 4309        for i, join in enumerate(this.args.get("joins") or []):
 4310            table = join.this
 4311            normalized_table = table.copy()
 4312            normalized_table.meta["maybe_column"] = True
 4313            normalized_table = _norm(normalized_table, dialect=self.dialect)
 4314
 4315            if isinstance(table, exp.Table) and not join.args.get("on"):
 4316                if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs:
 4317                    table_as_column = table.to_column()
 4318                    unnest = exp.Unnest(expressions=[table_as_column])
 4319
 4320                    # Table.to_column creates a parent Alias node that we want to convert to
 4321                    # a TableAlias and attach to the Unnest, so it matches the parser's output
 4322                    if isinstance(table.args.get("alias"), exp.TableAlias):
 4323                        table_as_column.replace(table_as_column.this)
 4324                        exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False)
 4325
 4326                    table.replace(unnest)
 4327
 4328            refs.add(normalized_table.alias_or_name)
 4329
 4330        return this
 4331
 4332    @t.overload
 4333    def _parse_query_modifiers(self, this: E) -> E: ...
 4334
 4335    @t.overload
 4336    def _parse_query_modifiers(self, this: None) -> None: ...
 4337
 4338    def _parse_query_modifiers(self, this):
 4339        if isinstance(this, self.MODIFIABLES):
 4340            for join in self._parse_joins():
 4341                this.append("joins", join)
 4342            for lateral in iter(self._parse_lateral, None):
 4343                this.append("laterals", lateral)
 4344
 4345            while True:
 4346                if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):
 4347                    modifier_token = self._curr
 4348                    parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type]
 4349                    key, expression = parser(self)
 4350
 4351                    if expression:
 4352                        if this.args.get(key):
 4353                            self.raise_error(
 4354                                f"Found multiple '{modifier_token.text.upper()}' clauses",
 4355                                token=modifier_token,
 4356                            )
 4357
 4358                        this.set(key, expression)
 4359                        if key == "limit":
 4360                            offset = expression.args.get("offset")
 4361                            expression.set("offset", None)
 4362
 4363                            if offset:
 4364                                offset = exp.Offset(expression=offset)
 4365                                this.set("offset", offset)
 4366
 4367                                limit_by_expressions = expression.expressions
 4368                                expression.set("expressions", None)
 4369                                offset.set("expressions", limit_by_expressions)
 4370                        continue
 4371
 4372                if self._curr.text.upper() == "START":
 4373                    modifier_token = self._curr
 4374                    connect = self._parse_connect()
 4375                    if connect:
 4376                        if this.args.get("connect"):
 4377                            self.raise_error(
 4378                                "Found multiple 'START WITH' clauses", token=modifier_token
 4379                            )
 4380
 4381                        this.set("connect", connect)
 4382                        continue
 4383                break
 4384
 4385        if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"):
 4386            this = self._implicit_unnests_to_explicit(this)
 4387
 4388        return this
 4389
 4390    def _parse_hint_fallback_to_string(self) -> exp.Hint | None:
 4391        start = self._curr
 4392        while self._curr:
 4393            self._advance()
 4394
 4395        end = self._tokens[self._index - 1]
 4396        return exp.Hint(expressions=[self._find_sql(start, end)])
 4397
 4398    def _parse_hint_function_call(self) -> exp.Expr | None:
 4399        return self._parse_function_call()
 4400
 4401    def _parse_hint_body(self) -> exp.Hint | None:
 4402        start_index = self._index
 4403        should_fallback_to_string = False
 4404
 4405        hints = []
 4406        try:
 4407            for hint in iter(
 4408                lambda: self._parse_csv(
 4409                    lambda: self._parse_hint_function_call() or self._parse_var(upper=True),
 4410                ),
 4411                [],
 4412            ):
 4413                hints.extend(hint)
 4414        except ParseError:
 4415            should_fallback_to_string = True
 4416
 4417        if should_fallback_to_string or self._curr:
 4418            self._retreat(start_index)
 4419            return self._parse_hint_fallback_to_string()
 4420
 4421        return self.expression(exp.Hint(expressions=hints))
 4422
 4423    def _parse_hint(self) -> exp.Hint | None:
 4424        if self._match(TokenType.HINT) and self._prev_comments:
 4425            return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect)
 4426
 4427        return None
 4428
 4429    def _parse_into(self) -> exp.Into | None:
 4430        if not self._match(TokenType.INTO):
 4431            return None
 4432
 4433        temp = self._match(TokenType.TEMPORARY)
 4434        unlogged = self._match_text_seq("UNLOGGED")
 4435        self._match(TokenType.TABLE)
 4436
 4437        return self.expression(
 4438            exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged)
 4439        )
 4440
 4441    def _parse_from(
 4442        self,
 4443        joins: bool = False,
 4444        skip_from_token: bool = False,
 4445        consume_pipe: bool = False,
 4446    ) -> exp.From | None:
 4447        if not skip_from_token and not self._match(TokenType.FROM):
 4448            return None
 4449
 4450        comments = self._prev_comments
 4451        return self.expression(
 4452            exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)),
 4453            comments=comments,
 4454        )
 4455
 4456    def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure:
 4457        return self.expression(
 4458            exp.MatchRecognizeMeasure(
 4459                window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(),
 4460                this=self._parse_expression(),
 4461            )
 4462        )
 4463
 4464    def _parse_match_recognize(self) -> exp.MatchRecognize | None:
 4465        if not self._match(TokenType.MATCH_RECOGNIZE):
 4466            return None
 4467
 4468        self._match_l_paren()
 4469
 4470        partition = self._parse_partition_by()
 4471        order = self._parse_order()
 4472
 4473        measures = (
 4474            self._parse_csv(self._parse_match_recognize_measure)
 4475            if self._match_text_seq("MEASURES")
 4476            else None
 4477        )
 4478
 4479        if self._match_text_seq("ONE", "ROW", "PER", "MATCH"):
 4480            rows = exp.var("ONE ROW PER MATCH")
 4481        elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"):
 4482            text = "ALL ROWS PER MATCH"
 4483            if self._match_text_seq("SHOW", "EMPTY", "MATCHES"):
 4484                text += " SHOW EMPTY MATCHES"
 4485            elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"):
 4486                text += " OMIT EMPTY MATCHES"
 4487            elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"):
 4488                text += " WITH UNMATCHED ROWS"
 4489            rows = exp.var(text)
 4490        else:
 4491            rows = None
 4492
 4493        if self._match_text_seq("AFTER", "MATCH", "SKIP"):
 4494            text = "AFTER MATCH SKIP"
 4495            if self._match_text_seq("PAST", "LAST", "ROW"):
 4496                text += " PAST LAST ROW"
 4497            elif self._match_text_seq("TO", "NEXT", "ROW"):
 4498                text += " TO NEXT ROW"
 4499            elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"):
 4500                direction = self._prev.text.upper()
 4501                pattern_var = self._advance_any()
 4502                if not pattern_var:
 4503                    self.raise_error(
 4504                        f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}"
 4505                    )
 4506                text += f" TO {direction} {pattern_var.text if pattern_var else ''}"
 4507            after = exp.var(text)
 4508        else:
 4509            after = None
 4510
 4511        if self._match_text_seq("PATTERN"):
 4512            self._match_l_paren()
 4513
 4514            if not self._curr:
 4515                self.raise_error("Expecting )", self._curr)
 4516
 4517            paren = 1
 4518            start = self._curr
 4519
 4520            while self._curr and paren > 0:
 4521                if self._curr.token_type == TokenType.L_PAREN:
 4522                    paren += 1
 4523                if self._curr.token_type == TokenType.R_PAREN:
 4524                    paren -= 1
 4525
 4526                end = self._prev
 4527                self._advance()
 4528
 4529            if paren > 0:
 4530                self.raise_error("Expecting )", self._curr)
 4531
 4532            pattern = exp.var(self._find_sql(start, end))
 4533        else:
 4534            pattern = None
 4535
 4536        define = (
 4537            self._parse_csv(self._parse_name_as_expression)
 4538            if self._match_text_seq("DEFINE")
 4539            else None
 4540        )
 4541
 4542        self._match_r_paren()
 4543
 4544        return self.expression(
 4545            exp.MatchRecognize(
 4546                partition_by=partition,
 4547                order=order,
 4548                measures=measures,
 4549                rows=rows,
 4550                after=after,
 4551                pattern=pattern,
 4552                define=define,
 4553                alias=self._parse_table_alias(),
 4554            )
 4555        )
 4556
 4557    def _parse_lateral(self) -> exp.Lateral | None:
 4558        cross_apply: bool | None = None
 4559        if self._match_pair(TokenType.CROSS, TokenType.APPLY):
 4560            cross_apply = True
 4561        elif self._match_pair(TokenType.OUTER, TokenType.APPLY):
 4562            cross_apply = False
 4563
 4564        if cross_apply is not None:
 4565            this = self._parse_select(table=True)
 4566            view = None
 4567            outer = None
 4568        elif self._match(TokenType.LATERAL):
 4569            this = self._parse_select(table=True)
 4570            view = self._match(TokenType.VIEW)
 4571            outer = self._match(TokenType.OUTER)
 4572        else:
 4573            return None
 4574
 4575        if not this:
 4576            this = (
 4577                self._parse_unnest()
 4578                or self._parse_function()
 4579                or self._parse_id_var(any_token=False)
 4580            )
 4581
 4582            while self._match(TokenType.DOT):
 4583                this = exp.Dot(
 4584                    this=this,
 4585                    expression=self._parse_function() or self._parse_id_var(any_token=False),
 4586                )
 4587
 4588        ordinality: bool | None = None
 4589
 4590        if view:
 4591            table = self._parse_id_var(any_token=False)
 4592            columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else []
 4593            table_alias: exp.TableAlias | None = self.expression(
 4594                exp.TableAlias(this=table, columns=columns)
 4595            )
 4596        elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias:
 4597            # We move the alias from the lateral's child node to the lateral itself
 4598            table_alias = this.args["alias"].pop()
 4599        else:
 4600            ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 4601            table_alias = self._parse_table_alias()
 4602
 4603        return self.expression(
 4604            exp.Lateral(
 4605                this=this,
 4606                view=view,
 4607                outer=outer,
 4608                alias=table_alias,
 4609                cross_apply=cross_apply,
 4610                ordinality=ordinality,
 4611            )
 4612        )
 4613
 4614    def _parse_stream(self) -> exp.Stream | None:
 4615        index = self._index
 4616        if self._match(TokenType.STREAM):
 4617            if this := self._try_parse(self._parse_table):
 4618                return self.expression(exp.Stream(this=this))
 4619            self._retreat(index)
 4620        return None
 4621
 4622    def _parse_join_parts(
 4623        self,
 4624    ) -> tuple[Token | None, Token | None, Token | None]:
 4625        return (
 4626            self._prev if self._match_set(self.JOIN_METHODS) else None,
 4627            self._prev if self._match_set(self.JOIN_SIDES) else None,
 4628            self._prev if self._match_set(self.JOIN_KINDS) else None,
 4629        )
 4630
 4631    def _parse_using_identifiers(self) -> list[exp.Expr]:
 4632        def _parse_column_as_identifier() -> exp.Expr | None:
 4633            this = self._parse_column()
 4634            if isinstance(this, exp.Column):
 4635                return this.this
 4636            return this
 4637
 4638        return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True)
 4639
 4640    def _parse_join(
 4641        self,
 4642        skip_join_token: bool = False,
 4643        parse_bracket: bool = False,
 4644        alias_tokens: t.Collection[TokenType] | None = None,
 4645    ) -> exp.Join | None:
 4646        if self._match(TokenType.COMMA):
 4647            table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens))
 4648            cross_join = self.expression(exp.Join(this=table)) if table else None
 4649
 4650            if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE:
 4651                cross_join.set("kind", "CROSS")
 4652
 4653            return cross_join
 4654
 4655        index = self._index
 4656        method, side, kind = self._parse_join_parts()
 4657        directed = self._match_text_seq("DIRECTED")
 4658        hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None
 4659        join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN)
 4660        join_comments = self._prev_comments
 4661
 4662        if not skip_join_token and not join:
 4663            self._retreat(index)
 4664            kind = None
 4665            method = None
 4666            side = None
 4667
 4668        outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False)
 4669        cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False)
 4670
 4671        if not skip_join_token and not join and not outer_apply and not cross_apply:
 4672            return None
 4673
 4674        kwargs: dict[str, t.Any] = {
 4675            "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4676        }
 4677        if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA):
 4678            kwargs["expressions"] = self._parse_csv(
 4679                lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4680            )
 4681
 4682        if method:
 4683            kwargs["method"] = method.text.upper()
 4684        if side:
 4685            kwargs["side"] = side.text.upper()
 4686        if kind:
 4687            kwargs["kind"] = kind.text.upper()
 4688        if hint:
 4689            kwargs["hint"] = hint
 4690
 4691        if self._match(TokenType.MATCH_CONDITION):
 4692            kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison)
 4693
 4694        if self._match(TokenType.ON):
 4695            kwargs["on"] = self._parse_disjunction()
 4696        elif self._match(TokenType.USING):
 4697            kwargs["using"] = self._parse_using_identifiers()
 4698        elif (
 4699            not method
 4700            and not (outer_apply or cross_apply)
 4701            and not isinstance(kwargs["this"], exp.Unnest)
 4702            and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY))
 4703        ):
 4704            index = self._index
 4705            joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens))
 4706
 4707            if joins and self._match(TokenType.ON):
 4708                kwargs["on"] = self._parse_disjunction()
 4709            elif joins and self._match(TokenType.USING):
 4710                kwargs["using"] = self._parse_using_identifiers()
 4711            else:
 4712                joins = None
 4713                self._retreat(index)
 4714
 4715            kwargs["this"].set("joins", joins if joins else None)
 4716
 4717        kwargs["pivots"] = self._parse_pivots()
 4718
 4719        comments = [c for token in (method, side, kind) if token for c in token.comments]
 4720        comments = (join_comments or []) + comments
 4721
 4722        if (
 4723            self.ADD_JOIN_ON_TRUE
 4724            and not kwargs.get("on")
 4725            and not kwargs.get("using")
 4726            and not kwargs.get("method")
 4727            and kwargs.get("kind") in (None, "INNER", "OUTER")
 4728        ):
 4729            kwargs["on"] = exp.true()
 4730
 4731        if directed:
 4732            kwargs["directed"] = directed
 4733
 4734        return self.expression(exp.Join(**kwargs), comments=comments)
 4735
 4736    def _parse_opclass(self) -> exp.Expr | None:
 4737        this = self._parse_disjunction()
 4738
 4739        if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False):
 4740            return this
 4741
 4742        if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False):
 4743            return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts()))
 4744
 4745        return this
 4746
 4747    def _parse_index_params(self) -> exp.IndexParameters:
 4748        using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None
 4749
 4750        if self._match(TokenType.L_PAREN, advance=False):
 4751            columns = self._parse_wrapped_csv(self._parse_with_operator)
 4752        else:
 4753            columns = None
 4754
 4755        include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None
 4756        partition_by = self._parse_partition_by()
 4757        with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties()
 4758        tablespace = (
 4759            self._parse_var(any_token=True)
 4760            if self._match_text_seq("USING", "INDEX", "TABLESPACE")
 4761            else None
 4762        )
 4763        where = self._parse_where()
 4764
 4765        on = self._parse_field() if self._match(TokenType.ON) else None
 4766
 4767        return self.expression(
 4768            exp.IndexParameters(
 4769                using=using,
 4770                columns=columns,
 4771                include=include,
 4772                partition_by=partition_by,
 4773                where=where,
 4774                with_storage=with_storage,
 4775                tablespace=tablespace,
 4776                on=on,
 4777            )
 4778        )
 4779
 4780    def _parse_index(
 4781        self, index: exp.Expr | None = None, anonymous: bool = False
 4782    ) -> exp.Index | None:
 4783        if index or anonymous:
 4784            unique = None
 4785            primary = None
 4786            amp = None
 4787
 4788            self._match(TokenType.ON)
 4789            self._match(TokenType.TABLE)  # hive
 4790            table = self._parse_table_parts(schema=True)
 4791        else:
 4792            unique = self._match(TokenType.UNIQUE)
 4793            primary = self._match_text_seq("PRIMARY")
 4794            amp = self._match_text_seq("AMP")
 4795
 4796            if not self._match(TokenType.INDEX):
 4797                return None
 4798
 4799            index = self._parse_id_var()
 4800            table = None
 4801
 4802        params = self._parse_index_params()
 4803
 4804        return self.expression(
 4805            exp.Index(
 4806                this=index, table=table, unique=unique, primary=primary, amp=amp, params=params
 4807            )
 4808        )
 4809
 4810    def _parse_table_hints(self) -> list[exp.Expr] | None:
 4811        hints: list[exp.Expr] = []
 4812        if self._match_pair(TokenType.WITH, TokenType.L_PAREN):
 4813            # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16
 4814            hints.append(
 4815                self.expression(
 4816                    exp.WithTableHint(
 4817                        expressions=self._parse_csv(
 4818                            lambda: self._parse_function() or self._parse_var(any_token=True)
 4819                        )
 4820                    )
 4821                )
 4822            )
 4823            self._match_r_paren()
 4824        else:
 4825            # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
 4826            while self._match_set(self.TABLE_INDEX_HINT_TOKENS):
 4827                hint = exp.IndexTableHint(this=self._prev.text.upper())
 4828
 4829                self._match_set((TokenType.INDEX, TokenType.KEY))
 4830                if self._match(TokenType.FOR):
 4831                    hint.set("target", self._advance_any() and self._prev.text.upper())
 4832
 4833                hint.set("expressions", self._parse_wrapped_id_vars())
 4834                hints.append(hint)
 4835
 4836        return hints or None
 4837
 4838    def _parse_table_part(self, schema: bool = False) -> exp.Expr | None:
 4839        return (
 4840            (not schema and self._parse_function(optional_parens=False))
 4841            or self._parse_id_var(any_token=False)
 4842            or self._parse_string_as_identifier()
 4843            or self._parse_placeholder()
 4844        )
 4845
 4846    def _parse_table_parts_fast(self) -> exp.Table | None:
 4847        index = self._index
 4848        parts: list[exp.Identifier] | None = None
 4849        all_comments: list[str] | None = None
 4850
 4851        while self._match_set(self.IDENTIFIER_TOKENS):
 4852            token = self._prev
 4853            comments = self._prev_comments
 4854
 4855            has_dot = self._match(TokenType.DOT)
 4856            curr_tt = self._curr.token_type
 4857
 4858            if not has_dot:
 4859                if curr_tt in self.TABLE_POSTFIX_TOKENS:
 4860                    self._retreat(index)
 4861                    return None
 4862            elif curr_tt not in self.IDENTIFIER_TOKENS:
 4863                self._retreat(index)
 4864                return None
 4865
 4866            if parts is None:
 4867                parts = []
 4868
 4869            if comments:
 4870                if all_comments is None:
 4871                    all_comments = []
 4872                all_comments.extend(comments)
 4873                self._prev_comments = []
 4874
 4875            parts.append(
 4876                self.expression(
 4877                    exp.Identifier(
 4878                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 4879                    ),
 4880                    token,
 4881                )
 4882            )
 4883
 4884            if not has_dot:
 4885                break
 4886
 4887        if parts is None:
 4888            return None
 4889
 4890        n = len(parts)
 4891
 4892        if n == 1:
 4893            table: exp.Table = exp.Table(this=parts[0])
 4894        elif n == 2:
 4895            table = exp.Table(this=parts[1], db=parts[0])
 4896        elif n >= 3:
 4897            this: exp.Identifier | exp.Dot = parts[2]
 4898            for i in range(3, n):
 4899                this = exp.Dot(this=this, expression=parts[i])
 4900
 4901            table = exp.Table(this=this, db=parts[1], catalog=parts[0])
 4902
 4903        if table is None:
 4904            self._retreat(index)
 4905        elif all_comments:
 4906            table.add_comments(all_comments)
 4907        return table
 4908
 4909    def _parse_table_parts(
 4910        self,
 4911        schema: bool = False,
 4912        is_db_reference: bool = False,
 4913        wildcard: bool = False,
 4914        fast: bool = False,
 4915    ) -> exp.Table | exp.Dot | None:
 4916        if fast:
 4917            return self._parse_table_parts_fast()
 4918
 4919        catalog: exp.Expr | str | None = None
 4920        db: exp.Expr | str | None = None
 4921        table: exp.Expr | str | None = self._parse_table_part(schema=schema)
 4922
 4923        while self._match(TokenType.DOT):
 4924            if catalog:
 4925                # This allows nesting the table in arbitrarily many dot expressions if needed
 4926                table = self.expression(
 4927                    exp.Dot(this=table, expression=self._parse_table_part(schema=schema))
 4928                )
 4929            else:
 4930                catalog = db
 4931                db = table
 4932                # "" used for tsql FROM a..b case
 4933                table = self._parse_table_part(schema=schema) or ""
 4934
 4935        if (
 4936            wildcard
 4937            and self._is_connected()
 4938            and (isinstance(table, exp.Identifier) or not table)
 4939            and self._match(TokenType.STAR)
 4940        ):
 4941            if isinstance(table, exp.Identifier):
 4942                table.args["this"] += "*"
 4943            else:
 4944                table = exp.Identifier(this="*")
 4945
 4946        if is_db_reference:
 4947            catalog = db
 4948            db = table
 4949            table = None
 4950
 4951        if not table and not is_db_reference:
 4952            self.raise_error(f"Expected table name but got {self._curr}")
 4953        if not db and is_db_reference:
 4954            self.raise_error(f"Expected database name but got {self._curr}")
 4955
 4956        table = self.expression(exp.Table(this=table, db=db, catalog=catalog))
 4957
 4958        # Bubble up comments from identifier parts to the Table
 4959        comments = []
 4960        for part in table.parts:
 4961            if part_comments := part.pop_comments():
 4962                comments.extend(part_comments)
 4963        if comments:
 4964            table.add_comments(comments)
 4965
 4966        changes = self._parse_changes()
 4967        if changes:
 4968            table.set("changes", changes)
 4969
 4970        at_before = self._parse_historical_data()
 4971        if at_before:
 4972            table.set("when", at_before)
 4973
 4974        pivots = self._parse_pivots()
 4975        if pivots:
 4976            table.set("pivots", pivots)
 4977
 4978        return table
 4979
 4980    def _parse_table(
 4981        self,
 4982        schema: bool = False,
 4983        joins: bool = False,
 4984        alias_tokens: t.Collection[TokenType] | None = None,
 4985        parse_bracket: bool = False,
 4986        is_db_reference: bool = False,
 4987        parse_partition: bool = False,
 4988        consume_pipe: bool = False,
 4989    ) -> exp.Expr | None:
 4990        if not schema and not is_db_reference and not consume_pipe and not joins:
 4991            index = self._index
 4992            table = self._parse_table_parts(fast=True)
 4993
 4994            if table is not None:
 4995                curr_tt = self._curr.token_type
 4996                next_tt = self._next.token_type
 4997
 4998                fast_terminators = self.TABLE_TERMINATORS
 4999
 5000                # only return the table if we're sure there are no other operators
 5001                # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT
 5002                if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION:
 5003                    return table
 5004
 5005                postfix_tokens = self.TABLE_POSTFIX_TOKENS
 5006
 5007                if curr_tt not in postfix_tokens and next_tt not in postfix_tokens:
 5008                    if alias := self._parse_table_alias(
 5009                        alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS
 5010                    ):
 5011                        table.set("alias", alias)
 5012
 5013                    if self._curr.token_type in fast_terminators:
 5014                        return table
 5015
 5016                self._retreat(index)
 5017
 5018        if stream := self._parse_stream():
 5019            return stream
 5020
 5021        if lateral := self._parse_lateral():
 5022            return lateral
 5023
 5024        if unnest := self._parse_unnest():
 5025            return unnest
 5026
 5027        if values := self._parse_derived_table_values():
 5028            return values
 5029
 5030        if subquery := self._parse_select(table=True, consume_pipe=consume_pipe):
 5031            if not subquery.args.get("pivots"):
 5032                subquery.set("pivots", self._parse_pivots())
 5033            if joins:
 5034                for join in self._parse_joins():
 5035                    subquery.append("joins", join)
 5036            return subquery
 5037
 5038        bracket = parse_bracket and self._parse_bracket(None)
 5039        bracket = self.expression(exp.Table(this=bracket)) if bracket else None
 5040
 5041        rows_from_tables = (
 5042            self._parse_wrapped_csv(self._parse_table)
 5043            if self._match_text_seq("ROWS", "FROM")
 5044            else None
 5045        )
 5046        rows_from = (
 5047            self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None
 5048        )
 5049
 5050        only = self._match(TokenType.ONLY)
 5051
 5052        this = t.cast(
 5053            exp.Expr,
 5054            bracket
 5055            or rows_from
 5056            or self._parse_bracket(
 5057                self._parse_table_parts(schema=schema, is_db_reference=is_db_reference)
 5058            ),
 5059        )
 5060
 5061        if only:
 5062            this.set("only", only)
 5063
 5064        # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context
 5065        self._match(TokenType.STAR)
 5066
 5067        parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION
 5068        if parse_partition and self._match(TokenType.PARTITION, advance=False):
 5069            this.set("partition", self._parse_partition())
 5070
 5071        if schema:
 5072            return self._parse_schema(this=this)
 5073
 5074        if self.dialect.ALIAS_POST_VERSION:
 5075            this.set("version", self._parse_version())
 5076
 5077        if self.dialect.ALIAS_POST_TABLESAMPLE:
 5078            this.set("sample", self._parse_table_sample())
 5079
 5080        alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 5081        if alias:
 5082            this.set("alias", alias)
 5083
 5084            # DuckDB requires the time-travel clause to come after the alias, e.g.
 5085            # SELECT * FROM t AS a AT (VERSION => 1)
 5086            if isinstance(this, exp.Table) and not this.args.get("when"):
 5087                this.set("when", self._parse_historical_data())
 5088
 5089        if self._match(TokenType.INDEXED_BY):
 5090            this.set("indexed", self._parse_table_parts())
 5091        elif self._match_text_seq("NOT", "INDEXED"):
 5092            this.set("indexed", False)
 5093
 5094        if isinstance(this, exp.Table) and self._match_text_seq("AT"):
 5095            return self.expression(
 5096                exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var())
 5097            )
 5098
 5099        this.set("hints", self._parse_table_hints())
 5100
 5101        if not this.args.get("pivots"):
 5102            this.set("pivots", self._parse_pivots())
 5103
 5104        if not self.dialect.ALIAS_POST_TABLESAMPLE:
 5105            this.set("sample", self._parse_table_sample())
 5106
 5107        if not self.dialect.ALIAS_POST_VERSION:
 5108            this.set("version", self._parse_version())
 5109
 5110        if joins:
 5111            for join in self._parse_joins(alias_tokens=alias_tokens):
 5112                this.append("joins", join)
 5113
 5114        if self._match_pair(TokenType.WITH, TokenType.ORDINALITY):
 5115            this.set("ordinality", True)
 5116            this.set("alias", self._parse_table_alias())
 5117
 5118        return this
 5119
 5120    def _parse_version(self) -> exp.Version | None:
 5121        for phrase, this in self.VERSION_PHRASES.items():
 5122            if self._match_text_seq(*phrase):
 5123                break
 5124        else:
 5125            return None
 5126
 5127        if self._match_set((TokenType.FROM, TokenType.BETWEEN)):
 5128            kind = self._prev.text.upper()
 5129            start = self._parse_bitwise()
 5130            self._match_texts(("TO", "AND"))
 5131            end = self._parse_bitwise()
 5132            expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end]))
 5133        elif self._match_text_seq("CONTAINED", "IN"):
 5134            kind = "CONTAINED IN"
 5135            expression = self.expression(
 5136                exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise))
 5137            )
 5138        elif self._match(TokenType.ALL):
 5139            kind = "ALL"
 5140            expression = None
 5141        else:
 5142            self._match_text_seq("AS", "OF")
 5143            kind = "AS OF"
 5144            expression = self._parse_type()
 5145
 5146        return self.expression(exp.Version(this=this, expression=expression, kind=kind))
 5147
 5148    def _parse_historical_data(self) -> exp.HistoricalData | None:
 5149        # https://docs.snowflake.com/en/sql-reference/constructs/at-before
 5150        index = self._index
 5151        historical_data = None
 5152        if self._match_texts(self.HISTORICAL_DATA_PREFIX):
 5153            this = self._prev.text.upper()
 5154            kind = (
 5155                self._match(TokenType.L_PAREN)
 5156                and self._match_texts(self.HISTORICAL_DATA_KIND)
 5157                and self._prev.text.upper()
 5158            )
 5159            expression = self._match(TokenType.FARROW) and self._parse_bitwise()
 5160
 5161            if expression:
 5162                self._match_r_paren()
 5163                historical_data = self.expression(
 5164                    exp.HistoricalData(this=this, kind=kind, expression=expression)
 5165                )
 5166            else:
 5167                self._retreat(index)
 5168
 5169        return historical_data
 5170
 5171    def _parse_changes(self) -> exp.Changes | None:
 5172        if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"):
 5173            return None
 5174
 5175        information = self._parse_var(any_token=True)
 5176        self._match_r_paren()
 5177
 5178        return self.expression(
 5179            exp.Changes(
 5180                information=information,
 5181                at_before=self._parse_historical_data(),
 5182                end=self._parse_historical_data(),
 5183            )
 5184        )
 5185
 5186    def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None:
 5187        if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False):
 5188            return None
 5189
 5190        self._advance()
 5191
 5192        expressions = self._parse_wrapped_csv(self._parse_equality)
 5193        offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 5194
 5195        alias = self._parse_table_alias() if with_alias else None
 5196
 5197        if alias:
 5198            if self.dialect.UNNEST_COLUMN_ONLY:
 5199                if alias.args.get("columns"):
 5200                    self.raise_error("Unexpected extra column alias in unnest.")
 5201
 5202                alias.set("columns", [alias.this])
 5203                alias.set("this", None)
 5204
 5205            columns = alias.args.get("columns") or []
 5206            if offset and len(expressions) < len(columns):
 5207                offset = columns.pop()
 5208
 5209        if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET):
 5210            self._match(TokenType.ALIAS)
 5211            offset = self._parse_id_var(
 5212                any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS
 5213            ) or exp.to_identifier("offset")
 5214
 5215        return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset))
 5216
 5217    def _parse_derived_table_values(self) -> exp.Values | None:
 5218        is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES)
 5219        if not is_derived and not (
 5220            # ClickHouse's `FORMAT Values` is equivalent to `VALUES`
 5221            self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES")
 5222        ):
 5223            return None
 5224
 5225        expressions = self._parse_csv(self._parse_value)
 5226        alias = self._parse_table_alias()
 5227
 5228        if is_derived:
 5229            self._match_r_paren()
 5230
 5231        return self.expression(
 5232            exp.Values(expressions=expressions, alias=alias or self._parse_table_alias())
 5233        )
 5234
 5235    def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None:
 5236        if not self._match(TokenType.TABLE_SAMPLE) and not (
 5237            as_modifier and self._match_text_seq("USING", "SAMPLE")
 5238        ):
 5239            return None
 5240
 5241        bucket_numerator = None
 5242        bucket_denominator = None
 5243        bucket_field = None
 5244        percent = None
 5245        size = None
 5246        seed = None
 5247
 5248        method = self._parse_var(tokens=(TokenType.ROW,), upper=True)
 5249        matched_l_paren = self._match(TokenType.L_PAREN)
 5250
 5251        if self.TABLESAMPLE_CSV:
 5252            num = None
 5253            expressions = self._parse_csv(self._parse_primary)
 5254        else:
 5255            expressions = None
 5256            num = (
 5257                self._parse_factor()
 5258                if self._match(TokenType.NUMBER, advance=False)
 5259                else self._parse_primary() or self._parse_placeholder()
 5260            )
 5261
 5262        if self._match_text_seq("BUCKET"):
 5263            bucket_numerator = self._parse_number()
 5264            self._match_text_seq("OUT", "OF")
 5265            bucket_denominator = bucket_denominator = self._parse_number()
 5266            self._match(TokenType.ON)
 5267            bucket_field = self._parse_field()
 5268        elif self._match_set((TokenType.PERCENT, TokenType.MOD)):
 5269            percent = num
 5270        elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
 5271            size = num
 5272        else:
 5273            percent = num
 5274
 5275        if matched_l_paren:
 5276            self._match_r_paren()
 5277
 5278        if self._match(TokenType.L_PAREN):
 5279            method = self._parse_var(upper=True)
 5280            seed = self._match(TokenType.COMMA) and self._parse_number()
 5281            self._match_r_paren()
 5282        elif self._match_texts(("SEED", "REPEATABLE")):
 5283            seed = self._parse_wrapped(self._parse_number)
 5284
 5285        if not method and self.DEFAULT_SAMPLING_METHOD:
 5286            method = exp.var(self.DEFAULT_SAMPLING_METHOD)
 5287
 5288        return self.expression(
 5289            exp.TableSample(
 5290                expressions=expressions,
 5291                method=method,
 5292                bucket_numerator=bucket_numerator,
 5293                bucket_denominator=bucket_denominator,
 5294                bucket_field=bucket_field,
 5295                percent=percent,
 5296                size=size,
 5297                seed=seed,
 5298            )
 5299        )
 5300
 5301    def _parse_pivots(self) -> list[exp.Pivot] | None:
 5302        if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT):
 5303            return None
 5304        return list(iter(self._parse_pivot, None)) or None
 5305
 5306    def _parse_joins(
 5307        self, alias_tokens: t.Collection[TokenType] | None = None
 5308    ) -> t.Iterator[exp.Join]:
 5309        return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None)
 5310
 5311    def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None:
 5312        if not self._match(TokenType.INTO):
 5313            return None
 5314
 5315        return self.expression(
 5316            exp.UnpivotColumns(
 5317                this=self._match_text_seq("NAME") and self._parse_column(),
 5318                expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column),
 5319            )
 5320        )
 5321
 5322    # https://duckdb.org/docs/sql/statements/pivot
 5323    def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot:
 5324        def _parse_on() -> exp.Expr | None:
 5325            this = self._parse_bitwise()
 5326
 5327            if self._match(TokenType.IN):
 5328                # PIVOT ... ON col IN (row_val1, row_val2)
 5329                return self._parse_in(this)
 5330            if self._match(TokenType.ALIAS, advance=False):
 5331                # UNPIVOT ... ON (col1, col2, col3) AS row_val
 5332                return self._parse_alias(this)
 5333
 5334            return this
 5335
 5336        this = self._parse_table()
 5337        expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on)
 5338        into = self._parse_unpivot_columns()
 5339        using = self._match(TokenType.USING) and self._parse_csv(
 5340            lambda: self._parse_alias(self._parse_column())
 5341        )
 5342        group = self._parse_group()
 5343
 5344        return self.expression(
 5345            exp.Pivot(
 5346                this=this,
 5347                expressions=expressions,
 5348                using=using,
 5349                group=group,
 5350                unpivot=is_unpivot,
 5351                into=into,
 5352            )
 5353        )
 5354
 5355    def _parse_pivot_in(self) -> exp.In:
 5356        def _parse_aliased_expression() -> exp.Expr | None:
 5357            this = self._parse_select_or_expression()
 5358
 5359            self._match(TokenType.ALIAS)
 5360            alias = self._parse_bitwise()
 5361            if alias:
 5362                if isinstance(alias, exp.Column) and not alias.db:
 5363                    alias = alias.this
 5364                return self.expression(exp.PivotAlias(this=this, alias=alias))
 5365
 5366            return this
 5367
 5368        value = self._parse_column()
 5369
 5370        if not self._match(TokenType.IN):
 5371            self.raise_error("Expecting IN")
 5372
 5373        if self._match(TokenType.L_PAREN):
 5374            if self._match(TokenType.ANY):
 5375                exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order()))
 5376            else:
 5377                exprs = self._parse_csv(_parse_aliased_expression)
 5378            self._match_r_paren()
 5379            return self.expression(exp.In(this=value, expressions=exprs))
 5380
 5381        return self.expression(exp.In(this=value, field=self._parse_id_var()))
 5382
 5383    def _parse_pivot_aggregation(self) -> exp.Expr | None:
 5384        func = self._parse_function()
 5385        if not func:
 5386            if self._prev.token_type == TokenType.COMMA:
 5387                return None
 5388            self.raise_error("Expecting an aggregation function in PIVOT")
 5389
 5390        return self._parse_alias(func)
 5391
 5392    def _parse_pivot(self) -> exp.Pivot | None:
 5393        index = self._index
 5394        include_nulls = None
 5395
 5396        if self._match(TokenType.PIVOT):
 5397            unpivot = False
 5398        elif self._match(TokenType.UNPIVOT):
 5399            unpivot = True
 5400
 5401            # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax
 5402            if self._match_text_seq("INCLUDE", "NULLS"):
 5403                include_nulls = True
 5404            elif self._match_text_seq("EXCLUDE", "NULLS"):
 5405                include_nulls = False
 5406        else:
 5407            return None
 5408
 5409        expressions = []
 5410
 5411        if not self._match(TokenType.L_PAREN):
 5412            self._retreat(index)
 5413            return None
 5414
 5415        if unpivot:
 5416            expressions = self._parse_csv(self._parse_column)
 5417        else:
 5418            expressions = self._parse_csv(self._parse_pivot_aggregation)
 5419
 5420        if not expressions:
 5421            self.raise_error("Failed to parse PIVOT's aggregation list")
 5422
 5423        if not self._match(TokenType.FOR):
 5424            self.raise_error("Expecting FOR")
 5425
 5426        fields = []
 5427        while True:
 5428            field = self._try_parse(self._parse_pivot_in)
 5429            if not field:
 5430                break
 5431            fields.append(field)
 5432
 5433        default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped(
 5434            self._parse_bitwise
 5435        )
 5436
 5437        group = self._parse_group()
 5438
 5439        self._match_r_paren()
 5440
 5441        pivot = self.expression(
 5442            exp.Pivot(
 5443                expressions=expressions,
 5444                fields=fields,
 5445                unpivot=unpivot,
 5446                include_nulls=include_nulls,
 5447                default_on_null=default_on_null,
 5448                group=group,
 5449            )
 5450        )
 5451
 5452        if unpivot:
 5453            pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions])
 5454            for pivot_field in pivot.fields:
 5455                if isinstance(pivot_field, exp.In):
 5456                    pivot_field.set("this", _unpivot_target(pivot_field.this))
 5457
 5458            pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST)
 5459
 5460        if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False):
 5461            pivot.set("alias", self._parse_table_alias())
 5462
 5463        if not unpivot:
 5464            names = self._pivot_column_names(t.cast(list[exp.Expr], expressions))
 5465
 5466            columns: list[exp.Expr] = []
 5467            all_fields = []
 5468            for pivot_field in pivot.fields:
 5469                pivot_field_expressions = pivot_field.expressions
 5470
 5471                # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case.
 5472                if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny):
 5473                    continue
 5474
 5475                all_fields.append(
 5476                    [
 5477                        # An explicit `<field> AS <alias>` names the output column directly,
 5478                        # so it wins over the dialect's string-identifying convention
 5479                        fld.sql()
 5480                        if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias)
 5481                        else fld.alias_or_name
 5482                        for fld in pivot_field_expressions
 5483                    ]
 5484                )
 5485
 5486            if all_fields:
 5487                if names:
 5488                    all_fields.append(names)
 5489
 5490                # Generate all possible combinations of the pivot columns
 5491                # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US'))
 5492                # generates the product between [[2000, 2010], ['NL', 'US'], ['total']]
 5493                for fld_parts_tuple in itertools.product(*all_fields):
 5494                    fld_parts = list(fld_parts_tuple)
 5495
 5496                    if names and self.PREFIXED_PIVOT_COLUMNS:
 5497                        # Move the "name" to the front of the list
 5498                        fld_parts.insert(0, fld_parts.pop(-1))
 5499
 5500                    columns.append(exp.to_identifier("_".join(fld_parts)))
 5501
 5502            pivot.set("columns", columns)
 5503            pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS)
 5504            pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS)
 5505            pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING)
 5506
 5507        return pivot
 5508
 5509    def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]:
 5510        return [agg.alias for agg in aggregations if agg.alias]
 5511
 5512    def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None:
 5513        if not skip_where_token and not self._match(TokenType.PREWHERE):
 5514            return None
 5515
 5516        comments = self._prev_comments
 5517        return self.expression(
 5518            exp.PreWhere(this=self._parse_disjunction()),
 5519            comments=comments,
 5520        )
 5521
 5522    def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None:
 5523        if not skip_where_token and not self._match(TokenType.WHERE):
 5524            return None
 5525
 5526        comments = self._prev_comments
 5527        return self.expression(
 5528            exp.Where(this=self._parse_disjunction()),
 5529            comments=comments,
 5530        )
 5531
 5532    def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None:
 5533        if not skip_group_by_token and not self._match(TokenType.GROUP_BY):
 5534            return None
 5535        comments = self._prev_comments
 5536
 5537        elements: dict[str, t.Any] = defaultdict(list)
 5538
 5539        if self._match(TokenType.ALL):
 5540            elements["all"] = True
 5541        elif self._match(TokenType.DISTINCT):
 5542            elements["all"] = False
 5543
 5544        if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False):
 5545            return self.expression(exp.Group(**elements), comments=comments)  # type: ignore
 5546
 5547        while True:
 5548            index = self._index
 5549
 5550            elements["expressions"].extend(
 5551                self._parse_csv(
 5552                    lambda: (
 5553                        None
 5554                        if self._match_set((TokenType.CUBE, TokenType.ROLLUP), advance=False)
 5555                        else self._parse_disjunction()
 5556                    )
 5557                )
 5558            )
 5559
 5560            before_with_index = self._index
 5561            with_prefix = self._match(TokenType.WITH)
 5562
 5563            if cube_or_rollup := self._parse_cube_or_rollup(with_prefix=with_prefix):
 5564                key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube"
 5565                elements[key].append(cube_or_rollup)
 5566            elif grouping_sets := self._parse_grouping_sets():
 5567                elements["grouping_sets"].append(grouping_sets)
 5568            elif self._match_text_seq("TOTALS"):
 5569                elements["totals"] = True  # type: ignore
 5570
 5571            if before_with_index <= self._index <= before_with_index + 1:
 5572                self._retreat(before_with_index)
 5573                break
 5574
 5575            if index == self._index:
 5576                break
 5577
 5578        return self.expression(exp.Group(**elements), comments=comments)  # type: ignore
 5579
 5580    def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None:
 5581        if self._match(TokenType.CUBE):
 5582            kind: type[exp.Cube | exp.Rollup] = exp.Cube
 5583        elif self._match(TokenType.ROLLUP):
 5584            kind = exp.Rollup
 5585        else:
 5586            return None
 5587
 5588        return self.expression(
 5589            kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise))
 5590        )
 5591
 5592    def _parse_grouping_sets(self) -> exp.GroupingSets | None:
 5593        if self._match(TokenType.GROUPING_SETS):
 5594            return self.expression(
 5595                exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set))
 5596            )
 5597        return None
 5598
 5599    def _parse_grouping_set(self) -> exp.Expr | None:
 5600        return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise()
 5601
 5602    def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None:
 5603        if not skip_having_token and not self._match(TokenType.HAVING):
 5604            return None
 5605        comments = self._prev_comments
 5606        return self.expression(
 5607            exp.Having(this=self._parse_disjunction()),
 5608            comments=comments,
 5609        )
 5610
 5611    def _parse_qualify(self) -> exp.Qualify | None:
 5612        if not self._match(TokenType.QUALIFY):
 5613            return None
 5614        return self.expression(exp.Qualify(this=self._parse_disjunction()))
 5615
 5616    def _parse_connect_with_prior(self) -> exp.Expr | None:
 5617        self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression(
 5618            exp.Prior(this=self._parse_bitwise())
 5619        )
 5620        connect = self._parse_disjunction()
 5621        self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR")
 5622        return connect
 5623
 5624    def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None:
 5625        if skip_start_token:
 5626            start = None
 5627        elif self._match_text_seq("START", "WITH"):
 5628            start = self._parse_disjunction()
 5629        else:
 5630            return None
 5631
 5632        self._match(TokenType.CONNECT_BY)
 5633        nocycle = self._match_text_seq("NOCYCLE")
 5634        connect = self._parse_connect_with_prior()
 5635
 5636        if not start and self._match_text_seq("START", "WITH"):
 5637            start = self._parse_disjunction()
 5638
 5639        return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle))
 5640
 5641    def _parse_name_as_expression(self) -> exp.Expr | None:
 5642        this = self._parse_id_var(any_token=True)
 5643        if self._match(TokenType.ALIAS):
 5644            this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction()))
 5645        return this
 5646
 5647    def _parse_interpolate(self) -> list[exp.Expr] | None:
 5648        if self._match_text_seq("INTERPOLATE"):
 5649            return self._parse_wrapped_csv(self._parse_name_as_expression)
 5650        return None
 5651
 5652    def _parse_order(
 5653        self, this: exp.Expr | None = None, skip_order_token: bool = False
 5654    ) -> exp.Expr | None:
 5655        siblings = None
 5656        if not skip_order_token and not self._match(TokenType.ORDER_BY):
 5657            if not self._match(TokenType.ORDER_SIBLINGS_BY):
 5658                return this
 5659
 5660            siblings = True
 5661
 5662        comments = self._prev_comments
 5663        return self.expression(
 5664            exp.Order(
 5665                this=this,
 5666                expressions=self._parse_csv(self._parse_ordered),
 5667                siblings=siblings,
 5668            ),
 5669            comments=comments,
 5670        )
 5671
 5672    def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None:
 5673        if not self._match(token):
 5674            return None
 5675        return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered)))
 5676
 5677    def _parse_ordered(
 5678        self, parse_method: t.Callable[[], exp.Expr | None] | None = None
 5679    ) -> exp.Ordered | None:
 5680        this = parse_method() if parse_method else self._parse_disjunction()
 5681        if not this:
 5682            return None
 5683
 5684        if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL:
 5685            this = exp.var("ALL")
 5686
 5687        asc = self._match(TokenType.ASC)
 5688        desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None)
 5689
 5690        is_nulls_first = self._match_text_seq("NULLS", "FIRST")
 5691        is_nulls_last = self._match_text_seq("NULLS", "LAST")
 5692
 5693        nulls_first = is_nulls_first or False
 5694        explicitly_null_ordered = is_nulls_first or is_nulls_last
 5695
 5696        if (
 5697            not explicitly_null_ordered
 5698            and (
 5699                (not desc and self.dialect.NULL_ORDERING == "nulls_are_small")
 5700                or (desc and self.dialect.NULL_ORDERING != "nulls_are_small")
 5701            )
 5702            and self.dialect.NULL_ORDERING != "nulls_are_last"
 5703        ):
 5704            nulls_first = True
 5705
 5706        if self._match_text_seq("WITH", "FILL"):
 5707            with_fill = self.expression(
 5708                exp.WithFill(
 5709                    from_=self._match(TokenType.FROM) and self._parse_bitwise(),
 5710                    to=self._match_text_seq("TO") and self._parse_bitwise(),
 5711                    step=self._match_text_seq("STEP") and self._parse_bitwise(),
 5712                    interpolate=self._parse_interpolate(),
 5713                )
 5714            )
 5715        else:
 5716            with_fill = None
 5717
 5718        return self.expression(
 5719            exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill)
 5720        )
 5721
 5722    def _parse_limit_options(self) -> exp.LimitOptions | None:
 5723        percent = self._match_set((TokenType.PERCENT, TokenType.MOD))
 5724        rows = self._match_set((TokenType.ROW, TokenType.ROWS))
 5725        self._match_text_seq("ONLY")
 5726        with_ties = self._match_text_seq("WITH", "TIES")
 5727
 5728        if not (percent or rows or with_ties):
 5729            return None
 5730
 5731        return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties))
 5732
 5733    def _parse_limit(
 5734        self,
 5735        this: exp.Expr | None = None,
 5736        top: bool = False,
 5737        skip_limit_token: bool = False,
 5738    ) -> exp.Expr | None:
 5739        if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT):
 5740            comments = self._prev_comments
 5741            if top:
 5742                limit_paren = self._match(TokenType.L_PAREN)
 5743                expression = (
 5744                    self._parse_term() or self._parse_select()
 5745                    if limit_paren
 5746                    else self._parse_number()
 5747                )
 5748
 5749                if limit_paren:
 5750                    self._match_r_paren()
 5751
 5752            else:
 5753                if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL):
 5754                    return this
 5755
 5756                # Parsing LIMIT x% (i.e x PERCENT) as a term leads to an error, since
 5757                # we try to build an exp.Mod expr. For that matter, we backtrack and instead
 5758                # consume the factor plus parse the percentage separately
 5759                index = self._index
 5760                expression = self._try_parse(self._parse_term)
 5761                if isinstance(expression, exp.Mod):
 5762                    self._retreat(index)
 5763                    expression = self._parse_factor()
 5764                elif not expression:
 5765                    expression = self._parse_factor()
 5766            limit_options = self._parse_limit_options()
 5767
 5768            if self._match(TokenType.COMMA):
 5769                offset = expression
 5770                expression = self._parse_term()
 5771            else:
 5772                offset = None
 5773
 5774            limit_exp = self.expression(
 5775                exp.Limit(
 5776                    this=this,
 5777                    expression=expression,
 5778                    offset=offset,
 5779                    limit_options=limit_options,
 5780                    expressions=self._parse_limit_by(),
 5781                ),
 5782                comments=comments,
 5783            )
 5784
 5785            return limit_exp
 5786
 5787        if self._match(TokenType.FETCH):
 5788            direction = (
 5789                self._prev.text.upper()
 5790                if self._match_set((TokenType.FIRST, TokenType.NEXT))
 5791                else "FIRST"
 5792            )
 5793
 5794            count = self._parse_field(tokens=self.FETCH_TOKENS)
 5795
 5796            return self.expression(
 5797                exp.Fetch(
 5798                    direction=direction, count=count, limit_options=self._parse_limit_options()
 5799                )
 5800            )
 5801
 5802        return this
 5803
 5804    def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None:
 5805        if not self._match(TokenType.OFFSET):
 5806            return this
 5807
 5808        count = self._parse_term()
 5809        self._match_set((TokenType.ROW, TokenType.ROWS))
 5810
 5811        return self.expression(
 5812            exp.Offset(this=this, expression=count, expressions=self._parse_limit_by())
 5813        )
 5814
 5815    def _can_parse_limit_or_offset(self) -> bool:
 5816        if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False):
 5817            return False
 5818
 5819        index = self._index
 5820        result = bool(
 5821            self._try_parse(self._parse_limit, retreat=True)
 5822            or self._try_parse(self._parse_offset, retreat=True)
 5823        )
 5824        self._retreat(index)
 5825
 5826        # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset
 5827        if self._next.token_type == TokenType.MATCH_CONDITION:
 5828            result = False
 5829
 5830        return result
 5831
 5832    def _can_parse_named_window(self) -> bool:
 5833        # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias.
 5834        # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`.
 5835        if not self._match(TokenType.WINDOW, advance=False):
 5836            return False
 5837
 5838        name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None
 5839        if name is None or name.token_type not in self.ID_VAR_TOKENS:
 5840            return False
 5841
 5842        alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None
 5843        if alias_tok is None or alias_tok.token_type != TokenType.ALIAS:
 5844            return False
 5845
 5846        body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None
 5847        return body is not None and body.token_type == TokenType.L_PAREN
 5848
 5849    def _parse_limit_by(self) -> list[exp.Expr] | None:
 5850        return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None
 5851
 5852    def _parse_locks(self) -> list[exp.Lock]:
 5853        locks = []
 5854        while True:
 5855            update, key = None, None
 5856            if self._match_text_seq("FOR", "UPDATE"):
 5857                update = True
 5858            elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq(
 5859                "LOCK", "IN", "SHARE", "MODE"
 5860            ):
 5861                update = False
 5862            elif self._match_text_seq("FOR", "KEY", "SHARE"):
 5863                update, key = False, True
 5864            elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"):
 5865                update, key = True, True
 5866            else:
 5867                break
 5868
 5869            expressions = None
 5870            if self._match_text_seq("OF"):
 5871                expressions = self._parse_csv(lambda: self._parse_table(schema=True))
 5872
 5873            wait: bool | exp.Expr | None = None
 5874            if self._match_text_seq("NOWAIT"):
 5875                wait = True
 5876            elif self._match_text_seq("WAIT"):
 5877                wait = self._parse_primary()
 5878            elif self._match_text_seq("SKIP", "LOCKED"):
 5879                wait = False
 5880
 5881            locks.append(
 5882                self.expression(
 5883                    exp.Lock(update=update, expressions=expressions, wait=wait, key=key)
 5884                )
 5885            )
 5886
 5887        return locks
 5888
 5889    def parse_set_operation(
 5890        self, this: exp.Expr | None, consume_pipe: bool = False
 5891    ) -> exp.Expr | None:
 5892        start = self._index
 5893        _, side_token, kind_token = self._parse_join_parts()
 5894
 5895        side = side_token.text if side_token else None
 5896        kind = kind_token.text if kind_token else None
 5897
 5898        if not self._match_set(self.SET_OPERATIONS):
 5899            self._retreat(start)
 5900            return None
 5901
 5902        token_type = self._prev.token_type
 5903
 5904        if token_type == TokenType.UNION:
 5905            operation: type[exp.SetOperation] = exp.Union
 5906        elif token_type == TokenType.EXCEPT:
 5907            operation = exp.Except
 5908        else:
 5909            operation = exp.Intersect
 5910
 5911        comments = self._prev.comments
 5912
 5913        if self._match(TokenType.DISTINCT):
 5914            distinct: bool | None = True
 5915        elif self._match(TokenType.ALL):
 5916            distinct = False
 5917        else:
 5918            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
 5919            if distinct is None:
 5920                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
 5921
 5922        by_name = (
 5923            self._match_text_seq("BY", "NAME")
 5924            or self._match_text_seq("STRICT", "CORRESPONDING")
 5925            or None
 5926        )
 5927        if self._match_text_seq("CORRESPONDING"):
 5928            by_name = True
 5929            if not side and not kind:
 5930                kind = "INNER"
 5931
 5932        on_column_list = None
 5933        if by_name and self._match_texts(("ON", "BY")):
 5934            on_column_list = self._parse_wrapped_csv(self._parse_column)
 5935
 5936        expression = self._parse_select(
 5937            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
 5938        )
 5939
 5940        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
 5941        # in _parse_cte and so that alias pushdown can reach into set operation branches
 5942        if isinstance(this, exp.Values):
 5943            this = self._values_to_select(this)
 5944        if isinstance(expression, exp.Values):
 5945            expression = self._values_to_select(expression)
 5946
 5947        return self.expression(
 5948            operation(
 5949                this=this,
 5950                distinct=distinct,
 5951                by_name=by_name,
 5952                expression=expression,
 5953                side=side,
 5954                kind=kind,
 5955                on=on_column_list,
 5956            ),
 5957            comments=comments,
 5958        )
 5959
 5960    def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None:
 5961        while this:
 5962            setop = self.parse_set_operation(this)
 5963            if not setop:
 5964                break
 5965            this = setop
 5966
 5967        if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP:
 5968            expression = this.expression
 5969
 5970            if expression:
 5971                for arg in self.SET_OP_MODIFIERS:
 5972                    expr = expression.args.get(arg)
 5973                    if expr:
 5974                        this.set(arg, expr.pop())
 5975
 5976        return this
 5977
 5978    def _parse_expression(self) -> exp.Expr | None:
 5979        return self._parse_alias(self._parse_assignment())
 5980
 5981    def _parse_assignment(self) -> exp.Expr | None:
 5982        this = self._parse_disjunction()
 5983        if not this and self._next.token_type in self.ASSIGNMENT:
 5984            # This allows us to parse <non-identifier token> := <expr>
 5985            this = exp.column(
 5986                t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text)
 5987            )
 5988
 5989        while self._match_set(self.ASSIGNMENT):
 5990            if isinstance(this, exp.Column) and len(this.parts) == 1:
 5991                this = this.this
 5992
 5993            comments = self._prev_comments
 5994            this = self.expression(
 5995                self.ASSIGNMENT[self._prev.token_type](
 5996                    this=this, expression=self._parse_assignment()
 5997                ),
 5998                comments=comments,
 5999            )
 6000
 6001        return this
 6002
 6003    def _parse_disjunction(self) -> exp.Expr | None:
 6004        this = self._parse_conjunction()
 6005        while self._match_set(self.DISJUNCTION):
 6006            comments = self._prev_comments
 6007            this = self.expression(
 6008                self.DISJUNCTION[self._prev.token_type](
 6009                    this=this, expression=self._parse_conjunction()
 6010                ),
 6011                comments=comments,
 6012            )
 6013        return this
 6014
 6015    def _parse_conjunction(self) -> exp.Expr | None:
 6016        this = self._parse_equality()
 6017        while self._match_set(self.CONJUNCTION):
 6018            comments = self._prev_comments
 6019            this = self.expression(
 6020                self.CONJUNCTION[self._prev.token_type](
 6021                    this=this, expression=self._parse_equality()
 6022                ),
 6023                comments=comments,
 6024            )
 6025        return this
 6026
 6027    def _parse_equality(self) -> exp.Expr | None:
 6028        this = self._parse_comparison()
 6029        while self._match_set(self.EQUALITY):
 6030            comments = self._prev_comments
 6031            this = self.expression(
 6032                self.EQUALITY[self._prev.token_type](
 6033                    this=this, expression=self._parse_comparison()
 6034                ),
 6035                comments=comments,
 6036            )
 6037        return this
 6038
 6039    def _parse_comparison(self) -> exp.Expr | None:
 6040        this = self._parse_range()
 6041        while self._match_set(self.COMPARISON):
 6042            comments = self._prev_comments
 6043            this = self.expression(
 6044                self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()),
 6045                comments=comments,
 6046            )
 6047        return this
 6048
 6049    def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6050        this = this or self._parse_bitwise()
 6051
 6052        while True:
 6053            negate = self._match(TokenType.NOT)
 6054            if self._match_set(self.RANGE_PARSERS):
 6055                expression = self.RANGE_PARSERS[self._prev.token_type](self, this)
 6056                if not expression:
 6057                    return this
 6058
 6059                this = expression
 6060            elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)):
 6061                this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6062            elif self._match(TokenType.NOTNULL):
 6063                # Postgres supports ISNULL and NOTNULL for conditions.
 6064                # https://blog.andreiavram.ro/postgresql-null-composite-type/
 6065                if self.dialect.NORMALIZE_NOT_NULL:
 6066                    this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6067                    this = self.expression(exp.Not(this=this))
 6068                else:
 6069                    this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True))
 6070            else:
 6071                if negate:
 6072                    self._retreat(self._index - 1)
 6073                break
 6074
 6075            if negate:
 6076                this = self._negate_range(this)
 6077                if self._curr and (
 6078                    self._curr.token_type == TokenType.NOT
 6079                    or self._curr.token_type in self.RANGE_PARSERS
 6080                ):
 6081                    this = self.expression(exp.Paren(this=this))
 6082
 6083        return this
 6084
 6085    def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6086        if not this:
 6087            return this
 6088
 6089        expression = this.this if isinstance(this, exp.Escape) else this
 6090        if isinstance(expression, (exp.Like, exp.ILike)):
 6091            expression.set("negate", True)
 6092            return this
 6093
 6094        return self.expression(exp.Not(this=this))
 6095
 6096    def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None:
 6097        index = self._index - 1
 6098        negate = self._match(TokenType.NOT)
 6099
 6100        if self._match_text_seq("DISTINCT", "FROM"):
 6101            klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ
 6102            return self.expression(klass(this=this, expression=self._parse_bitwise()))
 6103
 6104        if self._match(TokenType.JSON):
 6105            kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper()
 6106
 6107            if self._match_text_seq("WITH"):
 6108                _with = True
 6109            elif self._match_text_seq("WITHOUT"):
 6110                _with = False
 6111            else:
 6112                _with = None
 6113
 6114            unique = self._match(TokenType.UNIQUE)
 6115            self._match_text_seq("KEYS")
 6116            expression: exp.Expr | None = self.expression(
 6117                exp.JSON(this=kind, with_=_with, unique=unique)
 6118            )
 6119        else:
 6120            expression = self._parse_null() or self._parse_bitwise()
 6121            if not expression:
 6122                self._retreat(index)
 6123                return None
 6124
 6125        if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL:
 6126            this = self.expression(exp.Is(this=this, expression=expression, negate=True))
 6127        else:
 6128            this = self.expression(exp.Is(this=this, expression=expression))
 6129            this = self.expression(exp.Not(this=this)) if negate else this
 6130
 6131        return self._parse_column_ops(this)
 6132
 6133    def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In:
 6134        unnest = self._parse_unnest(with_alias=False)
 6135        if unnest:
 6136            this = self.expression(exp.In(this=this, unnest=unnest))
 6137        elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)):
 6138            matched_l_paren = self._prev.token_type == TokenType.L_PAREN
 6139            expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias))
 6140
 6141            if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query):
 6142                this = self.expression(
 6143                    exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False))
 6144                )
 6145            else:
 6146                this = self.expression(exp.In(this=this, expressions=expressions))
 6147
 6148            if matched_l_paren:
 6149                self._match_r_paren(this)
 6150            elif not self._match(TokenType.R_BRACKET, expression=this):
 6151                self.raise_error("Expecting ]")
 6152        else:
 6153            this = self.expression(exp.In(this=this, field=self._parse_column()))
 6154
 6155        return this
 6156
 6157    def _parse_between(self, this: exp.Expr | None) -> exp.Between:
 6158        symmetric = None
 6159        if self._match_text_seq("SYMMETRIC"):
 6160            symmetric = True
 6161        elif self._match_text_seq("ASYMMETRIC"):
 6162            symmetric = False
 6163
 6164        low = self._parse_bitwise()
 6165        self._match(TokenType.AND)
 6166        high = self._parse_bitwise()
 6167
 6168        return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric))
 6169
 6170    def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None:
 6171        if not self._match(TokenType.ESCAPE):
 6172            return this
 6173        return self.expression(
 6174            exp.Escape(this=this, expression=self._parse_string() or self._parse_null())
 6175        )
 6176
 6177    def _parse_interval_span(
 6178        self, this: exp.Expr, parse_function_unit: bool = True
 6179    ) -> exp.Interval:
 6180        # handle day-time format interval span with omitted units:
 6181        #   INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`>
 6182        interval_span_units_omitted = None
 6183        if (
 6184            this
 6185            and this.is_string
 6186            and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
 6187            and exp.INTERVAL_DAY_TIME_RE.match(this.name)
 6188        ):
 6189            index = self._index
 6190
 6191            # Var "TO" Var
 6192            first_unit = self._parse_var(any_token=True, upper=True)
 6193            second_unit = None
 6194            if first_unit and self._match_text_seq("TO"):
 6195                second_unit = self._parse_var(any_token=True, upper=True)
 6196
 6197            interval_span_units_omitted = not (first_unit and second_unit)
 6198
 6199            self._retreat(index)
 6200
 6201        unit_index = self._index
 6202        if interval_span_units_omitted:
 6203            unit = None
 6204        else:
 6205            # Only attempt to parse a unit if the current token can actually be one, so that a
 6206            # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x)
 6207            is_unit = self._curr is not None and (
 6208                self._curr.token_type == TokenType.VAR
 6209                or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS
 6210            )
 6211            unit = self._parse_function() if parse_function_unit and is_unit else None
 6212            if not unit and is_unit:
 6213                unit = self._parse_var(any_token=True, upper=True)
 6214
 6215        # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse
 6216        # each INTERVAL expression into this canonical form so it's easy to transpile
 6217        if this and this.is_number:
 6218            try:
 6219                this = exp.Literal.string(this.to_py())
 6220            except ValueError:
 6221                self.raise_error(f"Invalid numeric interval literal: {this.name!r}")
 6222        elif this and this.is_string:
 6223            parts = exp.INTERVAL_STRING_RE.findall(this.name)
 6224            if parts and unit:
 6225                # Unconsume the eagerly-parsed unit, since the real unit was part of the string
 6226                unit = None
 6227                self._retreat(unit_index)
 6228
 6229            if len(parts) == 1:
 6230                this = exp.Literal.string(parts[0][0])
 6231                unit = self.expression(exp.Var(this=parts[0][1].upper()))
 6232
 6233        if self.INTERVAL_SPANS and self._match_text_seq("TO"):
 6234            unit = self.expression(
 6235                exp.IntervalSpan(
 6236                    this=unit,
 6237                    expression=self._parse_function()
 6238                    or self._parse_var(any_token=True, upper=True),
 6239                )
 6240            )
 6241
 6242        return self.expression(exp.Interval(this=this, unit=unit))
 6243
 6244    def _parse_interval(
 6245        self, require_interval: bool = True, parse_function_unit: bool = True
 6246    ) -> exp.Add | exp.Interval | None:
 6247        index = self._index
 6248
 6249        if not self._match(TokenType.INTERVAL) and require_interval:
 6250            return None
 6251
 6252        if self._match(TokenType.STRING, advance=False):
 6253            this = self._parse_primary()
 6254        else:
 6255            this = self._parse_term()
 6256
 6257        if not this or (
 6258            isinstance(this, exp.Column)
 6259            and not this.table
 6260            and not this.this.quoted
 6261            and self._curr
 6262            and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS
 6263        ):
 6264            self._retreat(index)
 6265            return None
 6266
 6267        interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit)
 6268
 6269        index = self._index
 6270        self._match(TokenType.PLUS)
 6271
 6272        # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals
 6273        if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False):
 6274            return self.expression(
 6275                exp.Add(
 6276                    this=interval,
 6277                    expression=self._parse_interval(False, parse_function_unit=parse_function_unit),
 6278                )
 6279            )
 6280
 6281        self._retreat(index)
 6282        return interval
 6283
 6284    def _parse_bitwise(self) -> exp.Expr | None:
 6285        this = self._parse_term()
 6286
 6287        while True:
 6288            if self._match_set(self.BITWISE):
 6289                this = self.expression(
 6290                    self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term())
 6291                )
 6292            elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE):
 6293                this = self.expression(
 6294                    exp.DPipe(
 6295                        this=this,
 6296                        expression=self._parse_term(),
 6297                        safe=not self.dialect.STRICT_STRING_CONCAT,
 6298                    )
 6299                )
 6300            elif self._match(TokenType.DQMARK):
 6301                this = self.expression(
 6302                    exp.Coalesce(this=this, expressions=ensure_list(self._parse_term()))
 6303                )
 6304            elif self._match_pair(TokenType.LT, TokenType.LT):
 6305                this = self.expression(
 6306                    exp.BitwiseLeftShift(this=this, expression=self._parse_term())
 6307                )
 6308            elif self._match_pair(TokenType.GT, TokenType.GT):
 6309                this = self.expression(
 6310                    exp.BitwiseRightShift(this=this, expression=self._parse_term())
 6311                )
 6312            elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS):
 6313                this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term())
 6314            else:
 6315                break
 6316
 6317        return this
 6318
 6319    def _parse_term(self) -> exp.Expr | None:
 6320        this = self._parse_factor()
 6321
 6322        while self._match_set(self.TERM):
 6323            klass = self.TERM[self._prev.token_type]
 6324            comments = self._prev_comments
 6325            expression = self._parse_factor()
 6326
 6327            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6328
 6329            if isinstance(this, exp.Collate):
 6330                self._normalize_collate(this)
 6331
 6332        return this
 6333
 6334    def _normalize_collate(self, collate: exp.Collate) -> None:
 6335        expr = collate.expression
 6336
 6337        # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise
 6338        # fallback to Identifier / Var
 6339        if isinstance(expr, exp.Column) and len(expr.parts) == 1:
 6340            ident = expr.this
 6341            if isinstance(ident, exp.Identifier):
 6342                collate.set("expression", ident if ident.quoted else exp.var(ident.name))
 6343
 6344    def _parse_factor(self) -> exp.Expr | None:
 6345        parse_method = self._parse_factor_operand
 6346        this = self._parse_at_time_zone(parse_method())
 6347
 6348        while self._match_set(self.FACTOR):
 6349            klass = self.FACTOR[self._prev.token_type]
 6350            comments = self._prev_comments
 6351            expression = parse_method()
 6352
 6353            if not expression and klass is exp.IntDiv and self._prev.text.isalpha():
 6354                self._retreat(self._index - 1)
 6355                return this
 6356
 6357            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6358
 6359            if isinstance(this, exp.Div):
 6360                this.set("typed", self.dialect.TYPED_DIVISION)
 6361                this.set("safe", self.dialect.SAFE_DIVISION)
 6362
 6363        return this
 6364
 6365    def _parse_factor_operand(self) -> exp.Expr | None:
 6366        return self._parse_exponent() if self.EXPONENT else self._parse_unary()
 6367
 6368    def _parse_exponent(self) -> exp.Expr | None:
 6369        this = self._parse_unary()
 6370        while self._match_set(self.EXPONENT):
 6371            comments = self._prev_comments
 6372            this = self.expression(
 6373                self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()),
 6374                comments=comments,
 6375            )
 6376        return this
 6377
 6378    def _parse_unary(self) -> exp.Expr | None:
 6379        if self._match_set(self.UNARY_PARSERS):
 6380            return self.UNARY_PARSERS[self._prev.token_type](self)
 6381        return self._parse_type()
 6382
 6383    def _parse_type(
 6384        self, parse_interval: bool = True, fallback_to_identifier: bool = False
 6385    ) -> exp.Expr | None:
 6386        if not fallback_to_identifier and (atom := self._parse_atom()) is not None:
 6387            return atom
 6388
 6389        if interval := parse_interval and self._parse_interval():
 6390            return self._parse_column_ops(interval)
 6391
 6392        index = self._index
 6393        data_type = self._parse_types(check_func=True, allow_identifiers=False)
 6394
 6395        # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g.
 6396        # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>)
 6397        if isinstance(data_type, exp.Cast):
 6398            # This constructor can contain ops directly after it, for instance struct unnesting:
 6399            # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).*
 6400            return self._parse_column_ops(data_type)
 6401
 6402        if data_type:
 6403            index2 = self._index
 6404            this = self._parse_primary()
 6405
 6406            if isinstance(this, exp.Literal):
 6407                literal = this.name
 6408                this = self._parse_column_ops(this)
 6409
 6410                parser = self.TYPE_LITERAL_PARSERS.get(data_type.this)
 6411                if parser:
 6412                    return parser(self, this, data_type)
 6413
 6414                if (
 6415                    self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR
 6416                    and data_type.is_type(exp.DType.TIMESTAMP)
 6417                    and TIME_ZONE_RE.search(literal)
 6418                ):
 6419                    data_type = exp.DType.TIMESTAMPTZ.into_expr()
 6420
 6421                return self.expression(exp.Cast(this=this, to=data_type))
 6422
 6423            # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0)
 6424            # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 )
 6425            #
 6426            # If the index difference here is greater than 1, that means the parser itself must have
 6427            # consumed additional tokens such as the DECIMAL scale and precision in the above example.
 6428            #
 6429            # If it's not greater than 1, then it must be 1, because we've consumed at least the type
 6430            # keyword, meaning that the expressions arg of the DataType must have gotten set by a
 6431            # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to
 6432            # DECIMAL(38, 0)) in order to facilitate the data type's transpilation.
 6433            #
 6434            # In these cases, we don't really want to return the converted type, but instead retreat
 6435            # and try to parse a Column or Identifier in the section below.
 6436            if data_type.expressions and index2 - index > 1:
 6437                self._retreat(index2)
 6438                return self._parse_column_ops(data_type)
 6439
 6440            self._retreat(index)
 6441
 6442        if fallback_to_identifier:
 6443            return self._parse_id_var()
 6444
 6445        return self._parse_column()
 6446
 6447    def _parse_type_size(self) -> exp.DataTypeParam | None:
 6448        this = self._parse_type()
 6449        if not this:
 6450            return None
 6451
 6452        if isinstance(this, exp.Column) and not this.table:
 6453            this = exp.var(this.name.upper())
 6454
 6455        return self.expression(
 6456            exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True))
 6457        )
 6458
 6459    def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None:
 6460        type_name = identifier.name
 6461
 6462        while self._match(TokenType.DOT):
 6463            type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
 6464
 6465        return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True)
 6466
 6467    def _parse_types(
 6468        self,
 6469        check_func: bool = False,
 6470        schema: bool = False,
 6471        allow_identifiers: bool = True,
 6472        with_collation: bool = False,
 6473    ) -> exp.Expr | None:
 6474        index = self._index
 6475        this: exp.Expr | None = None
 6476
 6477        if self._match_set(self.TYPE_TOKENS):
 6478            type_token = self._prev.token_type
 6479        else:
 6480            type_token = None
 6481            identifier = allow_identifiers and self._parse_id_var(
 6482                any_token=False, tokens=(TokenType.VAR,)
 6483            )
 6484            if isinstance(identifier, exp.Identifier):
 6485                try:
 6486                    tokens = self.dialect.tokenize(identifier.name)
 6487                except TokenError:
 6488                    tokens = None
 6489
 6490                if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS:
 6491                    if len(tokens) > 1:
 6492                        return exp.DataType.from_str(identifier.name, dialect=self.dialect)
 6493                elif self.dialect.SUPPORTS_USER_DEFINED_TYPES:
 6494                    this = self._parse_user_defined_type(identifier)
 6495                else:
 6496                    self._retreat(self._index - 1)
 6497                    return None
 6498            else:
 6499                return None
 6500
 6501        if type_token == TokenType.PSEUDO_TYPE:
 6502            return self.expression(exp.PseudoType(this=self._prev.text.upper()))
 6503
 6504        if type_token == TokenType.OBJECT_IDENTIFIER:
 6505            return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper()))
 6506
 6507        # https://materialize.com/docs/sql/types/map/
 6508        if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET):
 6509            key_type = self._parse_types(
 6510                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6511            )
 6512            if not self._match(TokenType.FARROW):
 6513                self._retreat(index)
 6514                return None
 6515
 6516            value_type = self._parse_types(
 6517                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6518            )
 6519            if not self._match(TokenType.R_BRACKET):
 6520                self._retreat(index)
 6521                return None
 6522
 6523            return exp.DataType(
 6524                this=exp.DType.MAP,
 6525                expressions=[key_type, value_type],
 6526                nested=True,
 6527            )
 6528
 6529        nested = type_token in self.NESTED_TYPE_TOKENS
 6530        is_struct = type_token in self.STRUCT_TYPE_TOKENS
 6531        is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS
 6532        expressions = None
 6533        maybe_func = False
 6534
 6535        if self._match(TokenType.L_PAREN):
 6536            if is_struct:
 6537                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6538            elif nested:
 6539                expressions = self._parse_csv(
 6540                    lambda: self._parse_types(
 6541                        check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6542                    )
 6543                )
 6544                if type_token == TokenType.NULLABLE and len(expressions) == 1:
 6545                    this = expressions[0]
 6546                    this.set("nullable", True)
 6547                    self._match_r_paren()
 6548                    return this
 6549            elif type_token in self.ENUM_TYPE_TOKENS:
 6550                expressions = self._parse_csv(self._parse_equality)
 6551            elif type_token == TokenType.JSON:
 6552                # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value)
 6553                # https://clickhouse.com/docs/sql-reference/data-types/newjson
 6554                expressions = self._parse_csv(self._parse_json_type_arg)
 6555            elif is_aggregate:
 6556                func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var(
 6557                    any_token=False, tokens=(TokenType.VAR, TokenType.ANY)
 6558                )
 6559                if not func_or_ident:
 6560                    return None
 6561                expressions = [func_or_ident]
 6562                if self._match(TokenType.COMMA):
 6563                    expressions.extend(
 6564                        self._parse_csv(
 6565                            lambda: self._parse_types(
 6566                                check_func=check_func,
 6567                                schema=schema,
 6568                                allow_identifiers=allow_identifiers,
 6569                            )
 6570                        )
 6571                    )
 6572            else:
 6573                expressions = self._parse_csv(self._parse_type_size)
 6574
 6575                # https://docs.snowflake.com/en/sql-reference/data-types-vector
 6576                if type_token == TokenType.VECTOR and len(expressions) == 2:
 6577                    expressions = self._parse_vector_expressions(expressions)
 6578
 6579            if not self._match(TokenType.R_PAREN):
 6580                self._retreat(index)
 6581                return None
 6582
 6583            maybe_func = True
 6584
 6585        values: list[exp.Expr] | None = None
 6586
 6587        if nested and self._match(TokenType.LT):
 6588            if is_struct:
 6589                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6590            else:
 6591                expressions = self._parse_csv(
 6592                    lambda: self._parse_types(
 6593                        check_func=check_func,
 6594                        schema=schema,
 6595                        allow_identifiers=allow_identifiers,
 6596                        with_collation=True,
 6597                    )
 6598                )
 6599
 6600            if not self._match(TokenType.GT):
 6601                self.raise_error("Expecting >")
 6602
 6603            if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)):
 6604                values = self._parse_csv(self._parse_disjunction)
 6605                if not values and is_struct:
 6606                    values = None
 6607                    self._retreat(self._index - 1)
 6608                else:
 6609                    self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN))
 6610
 6611        if type_token in self.TIMESTAMPS:
 6612            if self._match_text_seq("WITH", "TIME", "ZONE"):
 6613                maybe_func = False
 6614                tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ
 6615                this = exp.DataType(this=tz_type, expressions=expressions)
 6616            elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"):
 6617                maybe_func = False
 6618                this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions)
 6619            elif self._match_text_seq("WITHOUT", "TIME", "ZONE"):
 6620                maybe_func = False
 6621        elif type_token == TokenType.INTERVAL:
 6622            if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS:
 6623                unit = self._parse_var(upper=True)
 6624                if self._match_text_seq("TO"):
 6625                    unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True))
 6626
 6627                this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit))))
 6628            else:
 6629                this = self.expression(exp.DataType(this=exp.DType.INTERVAL))
 6630        elif type_token == TokenType.VOID:
 6631            this = exp.DataType(this=exp.DType.NULL)
 6632
 6633        if maybe_func and check_func:
 6634            index2 = self._index
 6635            peek = self._parse_string()
 6636
 6637            if not peek:
 6638                self._retreat(index)
 6639                return None
 6640
 6641            self._retreat(index2)
 6642
 6643        if not this:
 6644            assert type_token is not None
 6645            if self._match_text_seq("UNSIGNED"):
 6646                unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token)
 6647                if not unsigned_type_token:
 6648                    self.raise_error(f"Cannot convert {type_token.name} to unsigned.")
 6649
 6650                type_token = unsigned_type_token or type_token
 6651
 6652            # NULLABLE without parentheses can be a column (Presto/Trino)
 6653            if type_token == TokenType.NULLABLE and not expressions:
 6654                self._retreat(index)
 6655                return None
 6656
 6657            this = exp.DataType(
 6658                this=exp.DType[type_token.name],
 6659                expressions=expressions,
 6660                nested=nested,
 6661            )
 6662
 6663            # Empty arrays/structs are allowed
 6664            if values is not None:
 6665                cls = exp.Struct if is_struct else exp.Array
 6666                this = exp.cast(cls(expressions=values), this, copy=False)
 6667
 6668        elif expressions:
 6669            this.set("expressions", expressions)
 6670
 6671        # https://materialize.com/docs/sql/types/list/#type-name
 6672        while self._match(TokenType.LIST):
 6673            this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True)
 6674
 6675        index = self._index
 6676
 6677        # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3]
 6678        matched_array = self._match(TokenType.ARRAY)
 6679
 6680        while self._curr:
 6681            datatype_token = self._prev.token_type
 6682            matched_l_bracket = self._match(TokenType.L_BRACKET)
 6683
 6684            if (not matched_l_bracket and not matched_array) or (
 6685                datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET)
 6686            ):
 6687                # Postgres allows casting empty arrays such as ARRAY[]::INT[],
 6688                # not to be confused with the fixed size array parsing
 6689                break
 6690
 6691            matched_array = False
 6692            values = self._parse_csv(self._parse_disjunction) or None
 6693            if (
 6694                values
 6695                and not schema
 6696                and (
 6697                    not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS
 6698                    or datatype_token == TokenType.ARRAY
 6699                    or not self._match(TokenType.R_BRACKET, advance=False)
 6700                )
 6701            ):
 6702                # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB
 6703                # 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
 6704                self._retreat(index)
 6705                break
 6706
 6707            this = exp.DataType(
 6708                this=exp.DType.ARRAY, expressions=[this], values=values, nested=True
 6709            )
 6710            self._match(TokenType.R_BRACKET)
 6711
 6712        if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType):
 6713            converter = self.TYPE_CONVERTERS.get(this.this)
 6714            if converter:
 6715                this = converter(t.cast(exp.DataType, this))
 6716
 6717        if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE):
 6718            this.set("collate", self._parse_identifier() or self._parse_column())
 6719
 6720        return this
 6721
 6722    def _parse_json_type_arg(self) -> exp.Expr | None:
 6723        """Parse a single argument to ClickHouse's JSON type."""
 6724
 6725        # SKIP col or SKIP REGEXP 'pattern'
 6726        if self._match_text_seq("SKIP"):
 6727            regexp = self._match(TokenType.RLIKE)
 6728            arg = self._parse_column()
 6729            if isinstance(arg, exp.Column):
 6730                arg = arg.to_dot()
 6731            return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg))
 6732
 6733        param_or_col = self._parse_column()
 6734        if not isinstance(param_or_col, exp.Column):
 6735            return None
 6736
 6737        # Parameter: name=value (e.g., max_dynamic_paths=2)
 6738        if len(param_or_col.parts) == 1 and self._match(TokenType.EQ):
 6739            param = param_or_col.name
 6740            value = self._parse_primary()
 6741            return self.expression(exp.EQ(this=exp.var(param), expression=value))
 6742
 6743        # Column type hint: col_name Type
 6744        col = param_or_col.to_dot()
 6745        kind = self._parse_types(check_func=False, allow_identifiers=False)
 6746        return self.expression(exp.ColumnDef(this=col, kind=kind))
 6747
 6748    def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]:
 6749        return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]]
 6750
 6751    def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None:
 6752        index = self._index
 6753
 6754        if (
 6755            self._curr
 6756            and self._next
 6757            and self._curr.token_type in self.TYPE_TOKENS
 6758            and self._next.token_type in self.TYPE_TOKENS
 6759        ):
 6760            # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a
 6761            # type token. Without this, the list will be parsed as a type and we'll eventually crash
 6762            this = self._parse_id_var()
 6763        else:
 6764            this = (
 6765                self._parse_type(parse_interval=False, fallback_to_identifier=True)
 6766                or self._parse_id_var()
 6767            )
 6768
 6769        self._match(TokenType.COLON)
 6770
 6771        if (
 6772            type_required
 6773            and not isinstance(this, exp.DataType)
 6774            and not self._match_set(self.TYPE_TOKENS, advance=False)
 6775        ):
 6776            self._retreat(index)
 6777            return self._parse_types()
 6778
 6779        return self._parse_column_def(this)
 6780
 6781    def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None:
 6782        if not self._match_text_seq("AT", "TIME", "ZONE"):
 6783            return this
 6784        return self._parse_at_time_zone(
 6785            self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary()))
 6786        )
 6787
 6788    def _parse_atom(self) -> exp.Expr | None:
 6789        if (
 6790            self._curr.token_type in self.IDENTIFIER_TOKENS
 6791            and (column := self._parse_column()) is not None
 6792        ):
 6793            return column
 6794
 6795        token = self._curr
 6796        token_type = token.token_type
 6797
 6798        if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)):
 6799            return None
 6800
 6801        next_type = self._next.token_type
 6802
 6803        if (
 6804            next_type in self.COLUMN_OPERATORS
 6805            or next_type in self.COLUMN_POSTFIX_TOKENS
 6806            or (token_type == TokenType.STRING and next_type == TokenType.STRING)
 6807        ):
 6808            return None
 6809
 6810        self._advance()
 6811        return primary_parser(self, token)
 6812
 6813    def _parse_column(self) -> exp.Expr | None:
 6814        column: exp.Expr | None = self._parse_column_parts_fast()
 6815        if column is None:
 6816            this = self._parse_column_reference()
 6817            if not this:
 6818                this = self._parse_bracket(this)
 6819            column = self._parse_column_ops(this) if this else this
 6820
 6821        if column:
 6822            if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 6823                column.set("join_mark", self._match(TokenType.JOIN_MARKER))
 6824            if self.COLON_IS_VARIANT_EXTRACT:
 6825                column = self._parse_colon_as_variant_extract(column)
 6826
 6827        return column
 6828
 6829    def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None:
 6830        """Fast path for simple column and dot references (a, a.b, ...).
 6831
 6832        Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks
 6833        that nothing complex follows. If it does, retreats and returns None so
 6834        the slow path can handle it. For >4 parts, wraps in exp.Dot nodes.
 6835        """
 6836        index = self._index
 6837        parts: list[exp.Identifier] | None = None
 6838        all_comments: list[str] | None = None
 6839
 6840        while self._match_set(self.IDENTIFIER_TOKENS):
 6841            token = self._prev
 6842            comments = self._prev_comments
 6843
 6844            if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS:
 6845                self._retreat(index)
 6846                return None
 6847
 6848            has_dot = self._match(TokenType.DOT)
 6849            curr_tt = self._curr.token_type
 6850
 6851            if not has_dot:
 6852                if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS:
 6853                    self._retreat(index)
 6854                    return None
 6855            elif curr_tt not in self.IDENTIFIER_TOKENS:
 6856                self._retreat(index)
 6857                return None
 6858
 6859            if parts is None:
 6860                parts = []
 6861
 6862            if comments:
 6863                if all_comments is None:
 6864                    all_comments = []
 6865                all_comments.extend(comments)
 6866                self._prev_comments = []
 6867
 6868            parts.append(
 6869                self.expression(
 6870                    exp.Identifier(
 6871                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 6872                    ),
 6873                    token,
 6874                )
 6875            )
 6876
 6877            if not has_dot:
 6878                break
 6879
 6880        if parts is None:
 6881            return None
 6882
 6883        n = len(parts)
 6884
 6885        if n == 1:
 6886            column: exp.Column | exp.Dot = exp.Column(this=parts[0])
 6887        elif n == 2:
 6888            column = exp.Column(this=parts[1], table=parts[0])
 6889        elif n == 3:
 6890            column = exp.Column(this=parts[2], table=parts[1], db=parts[0])
 6891        else:
 6892            column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0])
 6893
 6894            for i in range(4, n):
 6895                column = exp.Dot(this=column, expression=parts[i])
 6896
 6897        if all_comments:
 6898            column.add_comments(all_comments)
 6899
 6900        return column
 6901
 6902    def _parse_column_reference(self) -> exp.Expr | None:
 6903        this = self._parse_field()
 6904        if (
 6905            not this
 6906            and self._match(TokenType.VALUES, advance=False)
 6907            and self.VALUES_FOLLOWED_BY_PAREN
 6908            and (not self._next or self._next.token_type != TokenType.L_PAREN)
 6909        ):
 6910            this = self._parse_id_var()
 6911
 6912        if isinstance(this, exp.Identifier):
 6913            # We bubble up comments from the Identifier to the Column
 6914            this = self.expression(exp.Column(this=this), comments=this.pop_comments())
 6915
 6916        return this
 6917
 6918    def _build_json_extract(
 6919        self,
 6920        this: exp.Expr | None,
 6921        path_parts: list[exp.JSONPathPart],
 6922    ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]:
 6923        if len(path_parts) > 1:
 6924            this = self.expression(
 6925                exp.JSONExtract(
 6926                    this=this,
 6927                    expression=exp.JSONPath(expressions=path_parts),
 6928                    variant_extract=True,
 6929                    requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION,
 6930                )
 6931            )
 6932            path_parts = [exp.JSONPathRoot()]
 6933
 6934        return this, path_parts
 6935
 6936    def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None:
 6937        path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()]
 6938
 6939        while self._match(TokenType.COLON):
 6940            if not self.COLON_CHAIN_IS_SINGLE_EXTRACT:
 6941                this, path_parts = self._build_json_extract(this, path_parts)
 6942
 6943            key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 6944
 6945            if key:
 6946                quoted = isinstance(key, exp.Identifier) and key.quoted
 6947                path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted))
 6948
 6949            while True:
 6950                if self._match(TokenType.DOT):
 6951                    next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 6952
 6953                    if next_key:
 6954                        quoted = isinstance(next_key, exp.Identifier) and next_key.quoted
 6955                        path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted))
 6956                elif self._match(TokenType.L_BRACKET):
 6957                    bracket_expr = self._parse_bracket_key_value()
 6958
 6959                    if not self._match(TokenType.R_BRACKET):
 6960                        self.raise_error("Expected ]")
 6961
 6962                    if bracket_expr:
 6963                        if bracket_expr.is_string:
 6964                            path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True))
 6965                        elif bracket_expr.is_star:
 6966                            path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard()))
 6967                        elif bracket_expr.is_number:
 6968                            path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py()))
 6969                        else:
 6970                            this, path_parts = self._build_json_extract(this, path_parts)
 6971
 6972                            this = self.expression(
 6973                                exp.Bracket(
 6974                                    this=this, expressions=[bracket_expr], json_access=True
 6975                                ),
 6976                            )
 6977
 6978                elif self._match(TokenType.DCOLON):
 6979                    this, path_parts = self._build_json_extract(this, path_parts)
 6980
 6981                    cast_type = self._parse_types()
 6982                    if cast_type:
 6983                        this = self.expression(exp.Cast(this=this, to=cast_type))
 6984                    else:
 6985                        self.raise_error("Expected type after '::'")
 6986                else:
 6987                    break
 6988
 6989        this, _ = self._build_json_extract(this, path_parts)
 6990
 6991        return this
 6992
 6993    def _parse_dcolon(self) -> exp.Expr | None:
 6994        return self._parse_types()
 6995
 6996    def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None:
 6997        while self._curr.token_type in self.BRACKETS:
 6998            this = self._parse_bracket(this)
 6999
 7000        column_operators = self.COLUMN_OPERATORS
 7001        cast_column_operators = self.CAST_COLUMN_OPERATORS
 7002        while self._curr:
 7003            op_token = self._curr.token_type
 7004
 7005            if op_token not in column_operators:
 7006                break
 7007            op = column_operators[op_token]
 7008            self._advance()
 7009
 7010            if op_token in cast_column_operators:
 7011                field = self._parse_dcolon()
 7012                if not field:
 7013                    self.raise_error("Expected type")
 7014            elif op and self._curr:
 7015                field = self._parse_column_reference() or self._parse_bitwise()
 7016                if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False):
 7017                    field = self._parse_column_ops(field)
 7018            else:
 7019                dot = self._is_connected() and self._prev.token_type == TokenType.DOT
 7020                field = self._parse_field(any_token=True, anonymous_func=True)
 7021
 7022                # In t.true, t.null we should produce an Identifier node
 7023                if dot and isinstance(field, (exp.Null, exp.Boolean)):
 7024                    field = self.expression(
 7025                        exp.Identifier(this=self._prev.text),
 7026                        comments=field.comments,
 7027                    )
 7028
 7029            # Function calls can be qualified, e.g., x.y.FOO()
 7030            # This converts the final AST to a series of Dots leading to the function call
 7031            # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules
 7032            if isinstance(field, (exp.Func, exp.Window)) and this:
 7033                this = this.transform(
 7034                    lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n
 7035                )
 7036
 7037            if op:
 7038                this = op(self, this, field)
 7039            elif isinstance(this, exp.Column) and not this.args.get("catalog"):
 7040                this = self.expression(
 7041                    exp.Column(
 7042                        this=field,
 7043                        table=this.this,
 7044                        db=this.args.get("table"),
 7045                        catalog=this.args.get("db"),
 7046                    ),
 7047                    comments=this.comments,
 7048                )
 7049            elif isinstance(field, exp.Window):
 7050                # Move the exp.Dot's to the window's function
 7051                window_func = self.expression(exp.Dot(this=this, expression=field.this))
 7052                field.set("this", window_func)
 7053                this = field
 7054            else:
 7055                this = self.expression(exp.Dot(this=this, expression=field))
 7056
 7057            if field and field.comments:
 7058                t.cast(exp.Expr, this).add_comments(field.pop_comments())
 7059
 7060            this = self._parse_bracket(this)
 7061
 7062        return this
 7063
 7064    def _parse_paren(self) -> exp.Expr | None:
 7065        if not self._match(TokenType.L_PAREN):
 7066            return None
 7067
 7068        comments = self._prev_comments
 7069        query = self._parse_select()
 7070
 7071        if query:
 7072            expressions = [query]
 7073        else:
 7074            expressions = self._parse_expressions()
 7075
 7076        this = seq_get(expressions, 0)
 7077
 7078        if not this and self._match(TokenType.R_PAREN, advance=False):
 7079            this = self.expression(exp.Tuple())
 7080        elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA:
 7081            this = self.expression(exp.Tuple(expressions=expressions))
 7082        elif isinstance(this, exp.UNWRAPPED_QUERIES):
 7083            this = self._parse_subquery(this=this, parse_alias=False)
 7084        elif isinstance(this, (exp.Subquery, exp.Values)):
 7085            this = self._parse_subquery(
 7086                this=self._parse_query_modifiers(self._parse_set_operations(this)),
 7087                parse_alias=False,
 7088            )
 7089        else:
 7090            this = self.expression(exp.Paren(this=this))
 7091
 7092        if this:
 7093            this.add_comments(comments)
 7094
 7095        self._match_r_paren(expression=this)
 7096
 7097        if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc):
 7098            return self._parse_window(this)
 7099
 7100        return this
 7101
 7102    def _parse_primary(self) -> exp.Expr | None:
 7103        if self._match_set(self.PRIMARY_PARSERS):
 7104            token_type = self._prev.token_type
 7105            primary = self.PRIMARY_PARSERS[token_type](self, self._prev)
 7106
 7107            if token_type == TokenType.STRING:
 7108                expressions = [primary]
 7109                while self._match(TokenType.STRING, advance=False):
 7110                    if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED:
 7111                        self.raise_error(
 7112                            "Adjacent string literals need to be separated by whitespace or comments"
 7113                        )
 7114
 7115                    self._advance()
 7116                    expressions.append(exp.Literal.string(self._prev.text))
 7117
 7118                if len(expressions) > 1:
 7119                    return self.expression(
 7120                        exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE)
 7121                    )
 7122
 7123            return primary
 7124
 7125        if self._match_pair(TokenType.DOT, TokenType.NUMBER):
 7126            return exp.Literal.number(f"0.{self._prev.text}")
 7127
 7128        return self._parse_paren()
 7129
 7130    def _parse_field(
 7131        self,
 7132        any_token: bool = False,
 7133        tokens: t.Collection[TokenType] | None = None,
 7134        anonymous_func: bool = False,
 7135    ) -> exp.Expr | None:
 7136        if anonymous_func:
 7137            field = (
 7138                self._parse_function(anonymous=anonymous_func, any_token=any_token)
 7139                or self._parse_primary()
 7140            )
 7141        else:
 7142            field = self._parse_primary() or self._parse_function(
 7143                anonymous=anonymous_func, any_token=any_token
 7144            )
 7145        return field or self._parse_id_var(any_token=any_token, tokens=tokens)
 7146
 7147    def _parse_function(
 7148        self,
 7149        functions: dict[str, t.Callable] | None = None,
 7150        anonymous: bool = False,
 7151        optional_parens: bool = True,
 7152        any_token: bool = False,
 7153    ) -> exp.Expr | None:
 7154        # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this)
 7155        # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences
 7156        fn_syntax = False
 7157        if (
 7158            self._match(TokenType.L_BRACE, advance=False)
 7159            and self._next
 7160            and self._next.text.upper() == "FN"
 7161        ):
 7162            self._advance(2)
 7163            fn_syntax = True
 7164
 7165        func = self._parse_function_call(
 7166            functions=functions,
 7167            anonymous=anonymous,
 7168            optional_parens=optional_parens,
 7169            any_token=any_token,
 7170        )
 7171
 7172        if fn_syntax:
 7173            self._match(TokenType.R_BRACE)
 7174
 7175        return func
 7176
 7177    def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]:
 7178        return self._parse_csv(lambda: self._parse_lambda(alias=alias))
 7179
 7180    def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren:
 7181        args = self._parse_function_args(alias=False)
 7182        if not args:
 7183            self.raise_error("Expected at least one argument")
 7184
 7185        # Wrapped so the connector keeps its precedence in the parent context
 7186        return exp.Paren(this=connector(*args, copy=False))
 7187
 7188    def _parse_function_call(
 7189        self,
 7190        functions: dict[str, t.Callable] | None = None,
 7191        anonymous: bool = False,
 7192        optional_parens: bool = True,
 7193        any_token: bool = False,
 7194    ) -> exp.Expr | None:
 7195        if not self._curr:
 7196            return None
 7197
 7198        comments = self._curr.comments
 7199        prev = self._prev
 7200        token = self._curr
 7201        token_type = self._curr.token_type
 7202        this: str | exp.Expr = self._curr.text
 7203        upper = self._curr.text.upper()
 7204
 7205        after_dot = prev.token_type == TokenType.DOT
 7206        parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper)
 7207        if (
 7208            optional_parens
 7209            and parser
 7210            and token_type not in self.INVALID_FUNC_NAME_TOKENS
 7211            and not after_dot
 7212        ):
 7213            self._advance()
 7214            return self._parse_window(parser(self))
 7215
 7216        if self._next.token_type != TokenType.L_PAREN:
 7217            if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot:
 7218                self._advance()
 7219                return self.expression(self.NO_PAREN_FUNCTIONS[token_type]())
 7220
 7221            return None
 7222
 7223        if any_token:
 7224            if token_type in self.RESERVED_TOKENS:
 7225                return None
 7226        elif token_type not in self.FUNC_TOKENS:
 7227            return None
 7228
 7229        self._advance(2)
 7230
 7231        parser = self.FUNCTION_PARSERS.get(upper)
 7232        if parser and not anonymous:
 7233            result = parser(self)
 7234        else:
 7235            subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type)
 7236
 7237            if subquery_predicate:
 7238                expr = None
 7239                if self._curr.token_type in self.SUBQUERY_TOKENS:
 7240                    expr = self._parse_select()
 7241                    self._match_r_paren()
 7242                elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE):
 7243                    # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like
 7244                    # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren
 7245                    self._advance(-1)
 7246                    expr = self._parse_bitwise()
 7247
 7248                if expr:
 7249                    return self.expression(subquery_predicate(this=expr), comments=comments)
 7250
 7251            if functions is None:
 7252                functions = self.FUNCTIONS
 7253
 7254            function = functions.get(upper)
 7255            known_function = function and not anonymous
 7256
 7257            alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS
 7258            args = self._parse_function_args(alias)
 7259
 7260            post_func_comments = self._curr.comments if self._curr else None
 7261            if known_function and post_func_comments:
 7262                # If the user-inputted comment "/* sqlglot.anonymous */" is following the function
 7263                # call we'll construct it as exp.Anonymous, even if it's "known"
 7264                if any(
 7265                    comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS)
 7266                    for comment in post_func_comments
 7267                ):
 7268                    known_function = False
 7269
 7270            if alias and known_function:
 7271                args = self._kv_to_prop_eq(args)
 7272
 7273            if known_function:
 7274                func_builder = t.cast(t.Callable, function)
 7275
 7276                # mypyc compiled functions don't have __code__, so we use
 7277                # try/except to check if func_builder accepts 'dialect'.
 7278                try:
 7279                    func = func_builder(args)
 7280                except TypeError:
 7281                    func = func_builder(args, dialect=self.dialect)
 7282
 7283                func = self.validate_expression(func, args)
 7284                if self.dialect.PRESERVE_ORIGINAL_NAMES:
 7285                    func.meta["name"] = this
 7286
 7287                result = func
 7288            else:
 7289                if token_type == TokenType.IDENTIFIER:
 7290                    this = exp.Identifier(this=this, quoted=True).update_positions(token)
 7291
 7292                result = self.expression(exp.Anonymous(this=this, expressions=args))
 7293
 7294            result = result.update_positions(token)
 7295
 7296        if isinstance(result, exp.Expr):
 7297            result.add_comments(comments)
 7298
 7299        if parser:
 7300            self._match(TokenType.R_PAREN, expression=result)
 7301        else:
 7302            self._match_r_paren(result)
 7303        return self._parse_window(result)
 7304
 7305    def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr:
 7306        return expression
 7307
 7308    def _kv_to_prop_eq(
 7309        self, expressions: list[exp.Expr], parse_map: bool = False
 7310    ) -> list[exp.Expr]:
 7311        transformed = []
 7312
 7313        for index, e in enumerate(expressions):
 7314            if isinstance(e, self.KEY_VALUE_DEFINITIONS):
 7315                if isinstance(e, exp.Alias):
 7316                    e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this))
 7317
 7318                if not isinstance(e, exp.PropertyEQ):
 7319                    e = self.expression(
 7320                        exp.PropertyEQ(
 7321                            this=e.this if parse_map else exp.to_identifier(e.this.name),
 7322                            expression=e.expression,
 7323                        )
 7324                    )
 7325
 7326                if isinstance(e.this, exp.Column):
 7327                    e.this.replace(e.this.this)
 7328            else:
 7329                e = self._to_prop_eq(e, index)
 7330
 7331            transformed.append(e)
 7332
 7333        return transformed
 7334
 7335    def _parse_function_properties(self) -> exp.Properties | None:
 7336        # Skip the generic `key = value` fallback in _parse_property since this
 7337        # runs post-AS where a function body like `name = expr` can be misread
 7338        # as a property.
 7339        properties = []
 7340        while True:
 7341            if self._match_texts(self.PROPERTY_PARSERS):
 7342                keyword = self._prev.text.upper()
 7343                prop = self.PROPERTY_PARSERS[keyword](self)
 7344            elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS):
 7345                keyword = self._prev.text.upper()
 7346                prop = self.PROPERTY_PARSERS[keyword](self, default=True)
 7347            else:
 7348                break
 7349            if not prop:
 7350                self.raise_error(f"Failed to parse property '{keyword}'")
 7351                break
 7352            for p in ensure_list(prop):
 7353                properties.append(p)
 7354
 7355        return self.expression(exp.Properties(expressions=properties)) if properties else None
 7356
 7357    def _parse_user_defined_function_expression(self) -> exp.Expr | None:
 7358        return self._parse_statement()
 7359
 7360    def _parse_function_parameter(self) -> exp.Expr | None:
 7361        return self._parse_column_def(this=self._parse_id_var(), computed_column=False)
 7362
 7363    def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None:
 7364        this = self._parse_table_parts(schema=True)
 7365
 7366        if not self._match(TokenType.L_PAREN):
 7367            return this
 7368
 7369        expressions = self._parse_csv(self._parse_function_parameter)
 7370        self._match_r_paren()
 7371        return self.expression(
 7372            exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True)
 7373        )
 7374
 7375    def _parse_macro_overloads(
 7376        self,
 7377        this: exp.UserDefinedFunction,
 7378        first_body: exp.Expr,
 7379        first_is_table: bool = False,
 7380    ) -> exp.MacroOverloads:
 7381        overloads = [
 7382            self.expression(
 7383                exp.MacroOverload(
 7384                    this=first_body,
 7385                    expressions=this.expressions or None,
 7386                    is_table=first_is_table,
 7387                )
 7388            )
 7389        ]
 7390        this.set("expressions", None)
 7391        this.set("wrapped", False)
 7392
 7393        while self._match(TokenType.COMMA):
 7394            if not self._match(TokenType.L_PAREN):
 7395                break
 7396
 7397            params = self._parse_csv(self._parse_function_parameter)
 7398            self._match_r_paren()
 7399
 7400            if not self._match(TokenType.ALIAS):
 7401                break
 7402
 7403            is_table = self._match(TokenType.TABLE)
 7404            body = self._parse_expression()
 7405            macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table)
 7406            overloads.append(self.expression(macro))
 7407
 7408        return self.expression(exp.MacroOverloads(expressions=overloads))
 7409
 7410    def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier:
 7411        literal = self._parse_primary()
 7412        if literal:
 7413            return self.expression(exp.Introducer(this=token.text, expression=literal), token)
 7414
 7415        return self._identifier_expression(token)
 7416
 7417    def _parse_session_parameter(self) -> exp.SessionParameter:
 7418        kind = None
 7419        this = self._parse_id_var() or self._parse_primary()
 7420
 7421        if this and self._match(TokenType.DOT):
 7422            kind = this.name
 7423            this = self._parse_var() or self._parse_primary()
 7424
 7425        return self.expression(exp.SessionParameter(this=this, kind=kind))
 7426
 7427    def _parse_lambda_arg(self) -> exp.Expr | None:
 7428        return self._parse_id_var()
 7429
 7430    def _parse_lambda(self, alias: bool = False) -> exp.Expr | None:
 7431        next_token_type = self._next.token_type
 7432
 7433        # Fast path: simple atom (column, literal, null, bool) followed by , or )
 7434        if (
 7435            next_token_type in self.LAMBDA_ARG_TERMINATORS
 7436            and (atom := self._parse_atom()) is not None
 7437        ):
 7438            return atom
 7439
 7440        index = self._index
 7441
 7442        if self._match(TokenType.L_PAREN):
 7443            expressions = t.cast(
 7444                list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg)
 7445            )
 7446
 7447            if not self._match(TokenType.R_PAREN):
 7448                self._retreat(index)
 7449            elif self._match_set(self.LAMBDAS):
 7450                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7451            else:
 7452                self._retreat(index)
 7453        elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS:
 7454            expressions = [self._parse_lambda_arg()]
 7455
 7456            if self._match_set(self.LAMBDAS):
 7457                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7458
 7459            self._retreat(index)
 7460
 7461        this: exp.Expr | None
 7462
 7463        if self._match(TokenType.DISTINCT):
 7464            this = self.expression(
 7465                exp.Distinct(expressions=self._parse_csv(self._parse_disjunction))
 7466            )
 7467        else:
 7468            self._match(TokenType.ALL)  # ALL is the default/no-op aggregate modifier (SQL-92)
 7469            this = self._parse_select_or_expression(alias=alias)
 7470
 7471        return self._parse_limit(
 7472            self._parse_respect_or_ignore_nulls(
 7473                self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this)))
 7474            )
 7475        )
 7476
 7477    def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7478        index = self._index
 7479        if not self._match(TokenType.L_PAREN):
 7480            return this
 7481
 7482        # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>),
 7483        # expr can be of both types
 7484        if self._match_set(self.SELECT_START_TOKENS):
 7485            self._retreat(index)
 7486            return this
 7487        args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def())
 7488        self._match_r_paren()
 7489        return self.expression(exp.Schema(this=this, expressions=args))
 7490
 7491    def _parse_field_def(self) -> exp.Expr | None:
 7492        return self._parse_column_def(self._parse_field(any_token=True))
 7493
 7494    def _parse_column_def(
 7495        self, this: exp.Expr | None, computed_column: bool = True
 7496    ) -> exp.Expr | None:
 7497        # column defs are not really columns, they're identifiers
 7498        if isinstance(this, exp.Column):
 7499            this = this.this
 7500
 7501        if not computed_column:
 7502            self._match(TokenType.ALIAS)
 7503
 7504        kind = self._parse_types(schema=True)
 7505
 7506        if self._match_text_seq("FOR", "ORDINALITY"):
 7507            return self.expression(exp.ColumnDef(this=this, ordinality=True))
 7508
 7509        constraints: list[exp.Expr] = []
 7510
 7511        if (not kind and self._match(TokenType.ALIAS)) or self._match_texts(
 7512            ("ALIAS", "MATERIALIZED")
 7513        ):
 7514            # Match storage before _parse_types so STORED is not treated as a data type
 7515            # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`).
 7516            persisted = self._prev.text.upper() == "MATERIALIZED"
 7517            expression = self._parse_disjunction()
 7518            if not persisted:
 7519                if self._match_text_seq("PERSISTED"):
 7520                    persisted = True
 7521                elif self._match_texts(("STORED", "VIRTUAL")):
 7522                    persisted = self._prev.text.upper() == "STORED"
 7523            constraint_kind = exp.ComputedColumnConstraint(
 7524                this=expression,
 7525                persisted=persisted,
 7526                data_type=exp.Var(this="AUTO")
 7527                if self._match_text_seq("AUTO")
 7528                else self._parse_types(),
 7529                not_null=self._match_pair(TokenType.NOT, TokenType.NULL),
 7530            )
 7531            constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind)))
 7532        elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False):
 7533            in_out_constraint = self.expression(
 7534                exp.InOutColumnConstraint(
 7535                    input_=self._match(TokenType.IN), output=self._match(TokenType.OUT)
 7536                )
 7537            )
 7538            constraints.append(in_out_constraint)
 7539            kind = self._parse_types()
 7540        elif (
 7541            kind
 7542            and self._match(TokenType.ALIAS, advance=False)
 7543            and (
 7544                not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
 7545                or self._next.token_type == TokenType.L_PAREN
 7546            )
 7547        ):
 7548            self._advance()
 7549            constraints.append(
 7550                self.expression(
 7551                    exp.ColumnConstraint(
 7552                        kind=exp.ComputedColumnConstraint(
 7553                            this=self._parse_disjunction(),
 7554                            persisted=self._match_texts(("STORED", "VIRTUAL"))
 7555                            and self._prev.text.upper() == "STORED",
 7556                        )
 7557                    )
 7558                )
 7559            )
 7560
 7561        while True:
 7562            constraint = self._parse_column_constraint()
 7563            if not constraint:
 7564                break
 7565            constraints.append(constraint)
 7566
 7567        if not kind and not constraints:
 7568            return this
 7569
 7570        position = None
 7571        if self._match_texts(("FIRST", "AFTER")):
 7572            pos = self._prev.text
 7573            position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos))
 7574
 7575        return self.expression(
 7576            exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position)
 7577        )
 7578
 7579    def _parse_auto_increment(
 7580        self,
 7581    ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint:
 7582        start = None
 7583        increment = None
 7584        order = None
 7585
 7586        if self._match(TokenType.L_PAREN, advance=False):
 7587            args = self._parse_wrapped_csv(self._parse_bitwise)
 7588            start = seq_get(args, 0)
 7589            increment = seq_get(args, 1)
 7590
 7591        # The remaining parts form an unordered bag and any of them can be omitted, in which
 7592        # case the engine falls back to its own default, so they're parsed independently.
 7593        while True:
 7594            if self._match_text_seq("START"):
 7595                start = self._parse_bitwise()
 7596            elif self._match_text_seq("INCREMENT"):
 7597                increment = self._parse_bitwise()
 7598            elif self._match_text_seq("ORDER"):
 7599                order = True
 7600            elif self._match_text_seq("NOORDER"):
 7601                order = False
 7602            else:
 7603                break
 7604
 7605        if start or increment or order is not None:
 7606            return exp.GeneratedAsIdentityColumnConstraint(
 7607                start=start, increment=increment, this=False, order=order
 7608            )
 7609
 7610        return exp.AutoIncrementColumnConstraint()
 7611
 7612    def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None:
 7613        if not self._match(TokenType.L_PAREN, advance=False):
 7614            return None
 7615
 7616        return self.expression(
 7617            exp.CheckColumnConstraint(
 7618                this=self._parse_wrapped(self._parse_assignment),
 7619                enforced=self._match_text_seq("ENFORCED"),
 7620            )
 7621        )
 7622
 7623    def _parse_auto_property(self) -> exp.AutoRefreshProperty | None:
 7624        if not self._match_text_seq("REFRESH"):
 7625            self._retreat(self._index - 1)
 7626            return None
 7627        return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True)))
 7628
 7629    def _parse_compress(self) -> exp.CompressColumnConstraint:
 7630        if self._match(TokenType.L_PAREN, advance=False):
 7631            return self.expression(
 7632                exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise))
 7633            )
 7634
 7635        return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise()))
 7636
 7637    def _parse_generated_as_identity(
 7638        self,
 7639    ) -> (
 7640        exp.GeneratedAsIdentityColumnConstraint
 7641        | exp.ComputedColumnConstraint
 7642        | exp.GeneratedAsRowColumnConstraint
 7643    ):
 7644        if self._match_text_seq("BY", "DEFAULT"):
 7645            on_null = self._match_pair(TokenType.ON, TokenType.NULL)
 7646            this = self.expression(
 7647                exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null)
 7648            )
 7649        else:
 7650            self._match_text_seq("ALWAYS")
 7651            this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True))
 7652
 7653        self._match(TokenType.ALIAS)
 7654
 7655        if self._match_text_seq("ROW"):
 7656            start = self._match_text_seq("START")
 7657            if not start:
 7658                self._match(TokenType.END)
 7659            hidden = self._match_text_seq("HIDDEN")
 7660            return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden))
 7661
 7662        identity = self._match_text_seq("IDENTITY")
 7663
 7664        if self._match(TokenType.L_PAREN):
 7665            if self._match_text_seq("START", "WITH"):
 7666                this.set("start", self._parse_bitwise())
 7667            if self._match_text_seq("INCREMENT", "BY"):
 7668                this.set("increment", self._parse_bitwise())
 7669            if self._match_text_seq("MINVALUE"):
 7670                this.set("minvalue", self._parse_bitwise())
 7671            if self._match_text_seq("MAXVALUE"):
 7672                this.set("maxvalue", self._parse_bitwise())
 7673
 7674            if self._match_text_seq("CYCLE"):
 7675                this.set("cycle", True)
 7676            elif self._match_text_seq("NO", "CYCLE"):
 7677                this.set("cycle", False)
 7678
 7679            if not identity:
 7680                this.set("expression", self._parse_range())
 7681            elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False):
 7682                args = self._parse_csv(self._parse_bitwise)
 7683                this.set("start", seq_get(args, 0))
 7684                this.set("increment", seq_get(args, 1))
 7685
 7686            self._match_r_paren()
 7687
 7688        return this
 7689
 7690    def _parse_inline(self) -> exp.InlineLengthColumnConstraint:
 7691        self._match_text_seq("LENGTH")
 7692        return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise()))
 7693
 7694    def _parse_not_constraint(self) -> exp.Expr | None:
 7695        if self._match_text_seq("NULL"):
 7696            return self.expression(exp.NotNullColumnConstraint())
 7697        if self._match_text_seq("CASESPECIFIC"):
 7698            return self.expression(exp.CaseSpecificColumnConstraint(not_=True))
 7699        if self._match_text_seq("FOR", "REPLICATION"):
 7700            return self.expression(exp.NotForReplicationColumnConstraint())
 7701
 7702        # Unconsume the `NOT` token
 7703        self._retreat(self._index - 1)
 7704        return None
 7705
 7706    def _parse_column_constraint(self) -> exp.Expr | None:
 7707        this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None
 7708
 7709        procedure_option_follows = (
 7710            self._match(TokenType.WITH, advance=False)
 7711            and self._next
 7712            and self._next.text.upper() in self.PROCEDURE_OPTIONS
 7713        )
 7714
 7715        if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS):
 7716            constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self)
 7717            if not constraint:
 7718                self._retreat(self._index - 1)
 7719                return None
 7720
 7721            return self.expression(exp.ColumnConstraint(this=this, kind=constraint))
 7722
 7723        if self._match_text_seq("CHARACTER", "SET"):
 7724            return self.expression(
 7725                exp.ColumnConstraint(
 7726                    this=this,
 7727                    kind=self.expression(
 7728                        exp.CharacterSetColumnConstraint(this=self._parse_var_or_string())
 7729                    ),
 7730                )
 7731            )
 7732
 7733        return this
 7734
 7735    def _parse_constraint(self) -> exp.Expr | None:
 7736        if not self._match(TokenType.CONSTRAINT):
 7737            return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS)
 7738
 7739        return self.expression(
 7740            exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints())
 7741        )
 7742
 7743    def _parse_unnamed_constraints(self) -> list[exp.Expr]:
 7744        constraints = []
 7745        while True:
 7746            constraint = self._parse_unnamed_constraint() or self._parse_function()
 7747            if not constraint:
 7748                break
 7749            constraints.append(constraint)
 7750
 7751        return constraints
 7752
 7753    def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None:
 7754        index = self._index
 7755
 7756        if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts(
 7757            constraints or self.CONSTRAINT_PARSERS
 7758        ):
 7759            return None
 7760
 7761        constraint_key = self._prev.text.upper()
 7762        if constraint_key not in self.CONSTRAINT_PARSERS:
 7763            self.raise_error(f"No parser found for schema constraint {constraint_key}.")
 7764
 7765        result = self.CONSTRAINT_PARSERS[constraint_key](self)
 7766        if not result:
 7767            self._retreat(index)
 7768
 7769        return result
 7770
 7771    def _parse_unique_key(self) -> exp.Expr | None:
 7772        if (
 7773            self._curr
 7774            and self._curr.token_type != TokenType.IDENTIFIER
 7775            and self._curr.text.upper() in self.CONSTRAINT_PARSERS
 7776        ):
 7777            return None
 7778        return self._parse_id_var(any_token=False)
 7779
 7780    def _parse_unique(self) -> exp.UniqueColumnConstraint:
 7781        self._match_texts(("KEY", "INDEX"))
 7782        return self.expression(
 7783            exp.UniqueColumnConstraint(
 7784                nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"),
 7785                this=self._parse_schema(self._parse_unique_key()),
 7786                index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text,
 7787                on_conflict=self._parse_on_conflict(),
 7788                options=self._parse_key_constraint_options(),
 7789            )
 7790        )
 7791
 7792    def _parse_key_constraint_options(self) -> list[str]:
 7793        options = []
 7794        while True:
 7795            if not self._curr:
 7796                break
 7797
 7798            if self._match(TokenType.ON):
 7799                action = None
 7800                on = self._advance_any() and self._prev.text
 7801
 7802                if self._match_text_seq("NO", "ACTION"):
 7803                    action = "NO ACTION"
 7804                elif self._match_text_seq("CASCADE"):
 7805                    action = "CASCADE"
 7806                elif self._match_text_seq("RESTRICT"):
 7807                    action = "RESTRICT"
 7808                elif self._match_pair(TokenType.SET, TokenType.NULL):
 7809                    action = "SET NULL"
 7810                elif self._match_pair(TokenType.SET, TokenType.DEFAULT):
 7811                    action = "SET DEFAULT"
 7812                else:
 7813                    self.raise_error("Invalid key constraint")
 7814
 7815                options.append(f"ON {on} {action}")
 7816            else:
 7817                var = self._parse_var_from_options(
 7818                    self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False
 7819                )
 7820                if not var:
 7821                    break
 7822                options.append(var.name)
 7823
 7824        return options
 7825
 7826    def _parse_references(self, match: bool = True) -> exp.Reference | None:
 7827        if match and not self._match(TokenType.REFERENCES):
 7828            return None
 7829
 7830        expressions: list | None = None
 7831        this = self._parse_table(schema=True)
 7832        options = self._parse_key_constraint_options()
 7833        return self.expression(exp.Reference(this=this, expressions=expressions, options=options))
 7834
 7835    def _parse_foreign_key(self) -> exp.ForeignKey:
 7836        expressions = (
 7837            self._parse_wrapped_id_vars()
 7838            if not self._match(TokenType.REFERENCES, advance=False)
 7839            else None
 7840        )
 7841        reference = self._parse_references()
 7842        on_options = {}
 7843
 7844        while self._match(TokenType.ON):
 7845            if not self._match_set((TokenType.DELETE, TokenType.UPDATE)):
 7846                self.raise_error("Expected DELETE or UPDATE")
 7847
 7848            kind = self._prev.text.lower()
 7849
 7850            if self._match_text_seq("NO", "ACTION"):
 7851                action = "NO ACTION"
 7852            elif self._match(TokenType.SET):
 7853                self._match_set((TokenType.NULL, TokenType.DEFAULT))
 7854                action = "SET " + self._prev.text.upper()
 7855            else:
 7856                self._advance()
 7857                action = self._prev.text.upper()
 7858
 7859            on_options[kind] = action
 7860
 7861        return self.expression(
 7862            exp.ForeignKey(
 7863                expressions=expressions,
 7864                reference=reference,
 7865                options=self._parse_key_constraint_options(),
 7866                **on_options,
 7867            )
 7868        )
 7869
 7870    def _parse_primary_key_part(self) -> exp.Expr | None:
 7871        return self._parse_field()
 7872
 7873    def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None:
 7874        if not self._match_text_seq("FOR", "SYSTEM_TIME"):
 7875            self._retreat(self._index - 1)
 7876            return None
 7877
 7878        id_vars = self._parse_wrapped_id_vars()
 7879        return self.expression(
 7880            exp.PeriodForSystemTimeConstraint(
 7881                this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1)
 7882            )
 7883        )
 7884
 7885    def _parse_primary_key(
 7886        self,
 7887        wrapped_optional: bool = False,
 7888        in_props: bool = False,
 7889        named_primary_key: bool = False,
 7890    ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:
 7891        desc = (
 7892            self._prev.token_type == TokenType.DESC
 7893            if self._match_set((TokenType.ASC, TokenType.DESC))
 7894            else None
 7895        )
 7896
 7897        this = None
 7898        if (
 7899            named_primary_key
 7900            and self._curr.text.upper() not in self.CONSTRAINT_PARSERS
 7901            and self._next
 7902            and self._next.token_type == TokenType.L_PAREN
 7903        ):
 7904            this = self._parse_id_var()
 7905
 7906        if not in_props and not self._match(TokenType.L_PAREN, advance=False):
 7907            return self.expression(
 7908                exp.PrimaryKeyColumnConstraint(
 7909                    desc=desc, options=self._parse_key_constraint_options()
 7910                )
 7911            )
 7912
 7913        expressions = self._parse_wrapped_csv(
 7914            self._parse_primary_key_part, optional=wrapped_optional
 7915        )
 7916
 7917        return self.expression(
 7918            exp.PrimaryKey(
 7919                this=this,
 7920                expressions=expressions,
 7921                include=self._parse_index_params(),
 7922                options=self._parse_key_constraint_options(),
 7923            )
 7924        )
 7925
 7926    def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None:
 7927        return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True))
 7928
 7929    def _parse_odbc_datetime_literal(self) -> exp.Expr:
 7930        """
 7931        Parses a datetime column in ODBC format. We parse the column into the corresponding
 7932        types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the
 7933        same as we did for `DATE('yyyy-mm-dd')`.
 7934
 7935        Reference:
 7936        https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals
 7937        """
 7938        self._match(TokenType.VAR)
 7939        exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()]
 7940        expression = self.expression(exp_class(this=self._parse_string()))
 7941        if not self._match(TokenType.R_BRACE):
 7942            self.raise_error("Expected }")
 7943        return expression
 7944
 7945    def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7946        if not self._match_set(self.BRACKETS):
 7947            return this
 7948
 7949        if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS:
 7950            map_token = seq_get(self._tokens, self._index - 2)
 7951            parse_map = map_token is not None and map_token.text.upper() == "MAP"
 7952        else:
 7953            parse_map = False
 7954
 7955        bracket_kind = self._prev.token_type
 7956        if (
 7957            bracket_kind == TokenType.L_BRACE
 7958            and self._curr
 7959            and self._curr.token_type == TokenType.VAR
 7960            and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS
 7961        ):
 7962            return self._parse_odbc_datetime_literal()
 7963
 7964        expressions = self._parse_csv(
 7965            lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE)
 7966        )
 7967
 7968        if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET):
 7969            self.raise_error("Expected ]")
 7970        elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE):
 7971            self.raise_error("Expected }")
 7972
 7973        # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs
 7974        if bracket_kind == TokenType.L_BRACE:
 7975            this = self.expression(
 7976                exp.Struct(
 7977                    expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map)
 7978                )
 7979            )
 7980        elif not this:
 7981            this = build_array_constructor(
 7982                exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect
 7983            )
 7984        else:
 7985            constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper())
 7986            if constructor_type:
 7987                return build_array_constructor(
 7988                    constructor_type,
 7989                    args=expressions,
 7990                    bracket_kind=bracket_kind,
 7991                    dialect=self.dialect,
 7992                )
 7993
 7994            expressions = apply_index_offset(
 7995                this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect
 7996            )
 7997            this = self.expression(
 7998                exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments()
 7999            )
 8000
 8001        self._add_comments(this)
 8002        return self._parse_bracket(this)
 8003
 8004    def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None:
 8005        if not self._match(TokenType.COLON):
 8006            return this
 8007
 8008        if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False):
 8009            self._advance()
 8010            end: exp.Expr | None = -exp.Literal.number("1")
 8011        else:
 8012            end = self._parse_assignment()
 8013        step = self._parse_unary() if self._match(TokenType.COLON) else None
 8014        return self.expression(exp.Slice(this=this, expression=end, step=step))
 8015
 8016    def _parse_case(self) -> exp.Expr | None:
 8017        if self._match(TokenType.DOT, advance=False):
 8018            # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake
 8019            self._retreat(self._index - 1)
 8020            return None
 8021
 8022        ifs = []
 8023        default = None
 8024
 8025        comments = self._prev_comments
 8026        expression = self._parse_disjunction()
 8027
 8028        while self._match(TokenType.WHEN):
 8029            this = self._parse_disjunction()
 8030            self._match(TokenType.THEN)
 8031            then = self._parse_disjunction()
 8032            ifs.append(self.expression(exp.If(this=this, true=then)))
 8033
 8034        if self._match(TokenType.ELSE):
 8035            default = self._parse_disjunction()
 8036
 8037        if not self._match(TokenType.END):
 8038            if isinstance(default, exp.Interval) and default.this.sql().upper() == "END":
 8039                default = exp.column("interval")
 8040            else:
 8041                self.raise_error("Expected END after CASE", self._prev)
 8042
 8043        return self.expression(
 8044            exp.Case(this=expression, ifs=ifs, default=default), comments=comments
 8045        )
 8046
 8047    def _parse_if(self) -> exp.Expr | None:
 8048        if self._match(TokenType.L_PAREN):
 8049            args = self._parse_csv(
 8050                lambda: self._parse_alias(self._parse_assignment(), explicit=True)
 8051            )
 8052            this = self.validate_expression(exp.If.from_arg_list(args), args)
 8053            self._match_r_paren()
 8054        else:
 8055            index = self._index - 1
 8056
 8057            if self.NO_PAREN_IF_COMMANDS and index == 0:
 8058                return self._parse_as_command(self._prev)
 8059
 8060            condition = self._parse_disjunction()
 8061
 8062            if not condition:
 8063                self._retreat(index)
 8064                return None
 8065
 8066            self._match(TokenType.THEN)
 8067            true = self._parse_disjunction()
 8068            false = self._parse_disjunction() if self._match(TokenType.ELSE) else None
 8069            self._match(TokenType.END)
 8070            this = self.expression(exp.If(this=condition, true=true, false=false))
 8071
 8072        return this
 8073
 8074    def _parse_next_value_for(self) -> exp.Expr | None:
 8075        if not self._match_text_seq("VALUE", "FOR"):
 8076            self._retreat(self._index - 1)
 8077            return None
 8078
 8079        return self.expression(
 8080            exp.NextValueFor(
 8081                this=self._parse_column(),
 8082                order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order),
 8083            )
 8084        )
 8085
 8086    def _parse_extract(self) -> exp.Extract | exp.Anonymous:
 8087        this = self._parse_function() or self._parse_var_or_string(upper=True)
 8088
 8089        if self._match(TokenType.FROM):
 8090            return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8091
 8092        if not self._match(TokenType.COMMA):
 8093            self.raise_error("Expected FROM or comma after EXTRACT", self._prev)
 8094
 8095        return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8096
 8097    def _parse_gap_fill(self) -> exp.GapFill:
 8098        self._match(TokenType.TABLE)
 8099        this = self._parse_table()
 8100
 8101        self._match(TokenType.COMMA)
 8102        args = [this, *self._parse_csv(self._parse_lambda)]
 8103
 8104        gap_fill = exp.GapFill.from_arg_list(args)
 8105        return self.validate_expression(gap_fill, args)
 8106
 8107    def _parse_char(self) -> exp.Chr:
 8108        return self.expression(
 8109            exp.Chr(
 8110                expressions=self._parse_csv(self._parse_assignment),
 8111                charset=self._match(TokenType.USING) and self._parse_charset_name(),
 8112            )
 8113        )
 8114
 8115    def _parse_charset_name(self) -> exp.Expr | None:
 8116        """
 8117        Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting
 8118        for specific name shapes override this.
 8119        """
 8120        return self._parse_var(
 8121            tokens={TokenType.BINARY, TokenType.IDENTIFIER},
 8122        )
 8123
 8124    def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr:
 8125        this = self._parse_assignment()
 8126
 8127        if not self._match(TokenType.ALIAS):
 8128            if self._match(TokenType.COMMA):
 8129                return self.expression(exp.CastToStrType(this=this, to=self._parse_string()))
 8130
 8131            self.raise_error("Expected AS after CAST")
 8132
 8133        fmt = None
 8134        to = self._parse_types(with_collation=True)
 8135
 8136        default = None
 8137        if self._match(TokenType.DEFAULT):
 8138            default = self._parse_bitwise()
 8139            self._match_text_seq("ON", "CONVERSION", "ERROR")
 8140
 8141        if self._match_set((TokenType.FORMAT, TokenType.COMMA)):
 8142            fmt_string = self._parse_wrapped(self._parse_string, optional=True)
 8143            fmt = self._parse_at_time_zone(fmt_string)
 8144
 8145            if not to:
 8146                to = exp.DType.UNKNOWN.into_expr()
 8147            if to.this in exp.DataType.TEMPORAL_TYPES:
 8148                this = self.expression(
 8149                    (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)(
 8150                        this=this,
 8151                        format=exp.Literal.string(
 8152                            format_time(
 8153                                fmt_string.this if fmt_string else "",
 8154                                self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING,
 8155                                self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE,
 8156                            )
 8157                        ),
 8158                        safe=safe,
 8159                    )
 8160                )
 8161
 8162                if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime):
 8163                    this.set("zone", fmt.args["zone"])
 8164                return this
 8165        elif not to:
 8166            self.raise_error("Expected TYPE after CAST")
 8167        elif isinstance(to, exp.Identifier):
 8168            to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True)
 8169        elif to.this == exp.DType.CHAR and (
 8170            self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")
 8171        ):
 8172            to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string())
 8173
 8174        return self.build_cast(
 8175            strict=strict,
 8176            this=this,
 8177            to=to,
 8178            format=fmt,
 8179            safe=safe,
 8180            action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False),
 8181            default=default,
 8182        )
 8183
 8184    def _parse_string_agg(self) -> exp.GroupConcat:
 8185        if self._match(TokenType.DISTINCT):
 8186            args: list[exp.Expr | None] = [
 8187                self.expression(exp.Distinct(expressions=[self._parse_disjunction()]))
 8188            ]
 8189            if self._match(TokenType.COMMA):
 8190                args.extend(self._parse_csv(self._parse_disjunction))
 8191        else:
 8192            args = self._parse_csv(self._parse_disjunction)  # type: ignore
 8193
 8194        if self._match_text_seq("ON", "OVERFLOW"):
 8195            # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior])
 8196            if self._match_text_seq("ERROR"):
 8197                on_overflow: exp.Expr | None = exp.var("ERROR")
 8198            else:
 8199                self._match_text_seq("TRUNCATE")
 8200                on_overflow = self.expression(
 8201                    exp.OverflowTruncateBehavior(
 8202                        this=self._parse_string(),
 8203                        with_count=(
 8204                            self._match_text_seq("WITH", "COUNT")
 8205                            or not self._match_text_seq("WITHOUT", "COUNT")
 8206                        ),
 8207                    )
 8208                )
 8209        else:
 8210            on_overflow = None
 8211
 8212        index = self._index
 8213        if not self._match(TokenType.R_PAREN) and args:
 8214            # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]])
 8215            # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n])
 8216            # The order is parsed through `this` as a canonicalization for WITHIN GROUPs
 8217            args[0] = self._parse_limit(this=self._parse_order(this=args[0]))
 8218            return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1)))
 8219
 8220        # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]).
 8221        # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that
 8222        # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them.
 8223        if not self._match_text_seq("WITHIN", "GROUP"):
 8224            self._retreat(index)
 8225            return self.validate_expression(exp.GroupConcat.from_arg_list(args), args)
 8226
 8227        # The corresponding match_r_paren will be called in parse_function (caller)
 8228        self._match_l_paren()
 8229
 8230        return self.expression(
 8231            exp.GroupConcat(
 8232                this=self._parse_order(this=seq_get(args, 0)),
 8233                separator=seq_get(args, 1),
 8234                on_overflow=on_overflow,
 8235            )
 8236        )
 8237
 8238    def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None:
 8239        this = self._parse_bitwise()
 8240
 8241        if self._match(TokenType.USING):
 8242            to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name())
 8243        elif self._match(TokenType.COMMA):
 8244            to = self._parse_types()
 8245        else:
 8246            to = None
 8247
 8248        return self.build_cast(strict=strict, this=this, to=to, safe=safe)
 8249
 8250    def _parse_xml_element(self) -> exp.XMLElement:
 8251        if self._match_text_seq("EVALNAME"):
 8252            evalname = True
 8253            this = self._parse_bitwise()
 8254        else:
 8255            evalname = None
 8256            self._match_text_seq("NAME")
 8257            this = self._parse_id_var()
 8258
 8259        return self.expression(
 8260            exp.XMLElement(
 8261                this=this,
 8262                expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise),
 8263                evalname=evalname,
 8264            )
 8265        )
 8266
 8267    def _parse_xml_table(self) -> exp.XMLTable:
 8268        namespaces = None
 8269        passing = None
 8270        columns = None
 8271
 8272        if self._match_text_seq("XMLNAMESPACES", "("):
 8273            namespaces = self._parse_xml_namespace()
 8274            self._match_text_seq(")", ",")
 8275
 8276        this = self._parse_string()
 8277
 8278        if self._match_text_seq("PASSING"):
 8279            # The BY VALUE keywords are optional and are provided for semantic clarity
 8280            self._match_text_seq("BY", "VALUE")
 8281            passing = self._parse_csv(self._parse_column)
 8282
 8283        by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF")
 8284
 8285        if self._match_text_seq("COLUMNS"):
 8286            columns = self._parse_csv(self._parse_field_def)
 8287
 8288        return self.expression(
 8289            exp.XMLTable(
 8290                this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref
 8291            )
 8292        )
 8293
 8294    def _parse_xml_namespace(self) -> list[exp.XMLNamespace]:
 8295        namespaces = []
 8296
 8297        while True:
 8298            if self._match(TokenType.DEFAULT):
 8299                uri = self._parse_string()
 8300            else:
 8301                uri = self._parse_alias(self._parse_string())
 8302            namespaces.append(self.expression(exp.XMLNamespace(this=uri)))
 8303            if not self._match(TokenType.COMMA):
 8304                break
 8305
 8306        return namespaces
 8307
 8308    def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None:
 8309        args = self._parse_csv(self._parse_disjunction)
 8310
 8311        if len(args) < 3:
 8312            return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1)))
 8313
 8314        return self.expression(exp.DecodeCase(expressions=args))
 8315
 8316    def _parse_json_key_value(self) -> exp.JSONKeyValue | None:
 8317        self._match_text_seq("KEY")
 8318        key = self._parse_column()
 8319        self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS)
 8320        self._match_text_seq("VALUE")
 8321        value = self._parse_bitwise()
 8322
 8323        if not key and not value:
 8324            return None
 8325        return self.expression(exp.JSONKeyValue(this=key, expression=value))
 8326
 8327    def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None:
 8328        if not this or not self._match_text_seq("FORMAT", "JSON"):
 8329            return this
 8330
 8331        return self.expression(exp.FormatJson(this=this))
 8332
 8333    def _parse_on_condition(self) -> exp.OnCondition | None:
 8334        # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS)
 8335        if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR:
 8336            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8337            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8338        else:
 8339            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8340            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8341
 8342        null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS)
 8343
 8344        if not empty and not error and not null:
 8345            return None
 8346
 8347        return self.expression(exp.OnCondition(empty=empty, error=error, null=null))
 8348
 8349    def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None:
 8350        # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL)
 8351        for value in values:
 8352            if self._match_text_seq(value, "ON", on):
 8353                return f"{value} ON {on}"
 8354
 8355        index = self._index
 8356        if self._match(TokenType.DEFAULT):
 8357            default_value = self._parse_bitwise()
 8358            if self._match_text_seq("ON", on):
 8359                return default_value
 8360
 8361            self._retreat(index)
 8362
 8363        return None
 8364
 8365    @t.overload
 8366    def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ...
 8367
 8368    @t.overload
 8369    def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ...
 8370
 8371    def _parse_json_object(self, agg=False):
 8372        star = self._parse_star()
 8373        expressions = (
 8374            [star]
 8375            if star
 8376            else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value()))
 8377        )
 8378        null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT")
 8379
 8380        unique_keys = None
 8381        if self._match_text_seq("WITH", "UNIQUE"):
 8382            unique_keys = True
 8383        elif self._match_text_seq("WITHOUT", "UNIQUE"):
 8384            unique_keys = False
 8385
 8386        self._match_text_seq("KEYS")
 8387
 8388        return_type = self._match_text_seq("RETURNING") and self._parse_format_json(
 8389            self._parse_type()
 8390        )
 8391        encoding = self._match_text_seq("ENCODING") and self._parse_var()
 8392
 8393        return self.expression(
 8394            (exp.JSONObjectAgg if agg else exp.JSONObject)(
 8395                expressions=expressions,
 8396                null_handling=null_handling,
 8397                unique_keys=unique_keys,
 8398                return_type=return_type,
 8399                encoding=encoding,
 8400            )
 8401        )
 8402
 8403    # Note: this is currently incomplete; it only implements the "JSON_value_column" part
 8404    def _parse_json_column_def(self) -> exp.JSONColumnDef:
 8405        if not self._match_text_seq("NESTED"):
 8406            this = self._parse_id_var()
 8407            ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY)
 8408            kind = self._parse_types(allow_identifiers=False)
 8409            nested = None
 8410        else:
 8411            this = None
 8412            ordinality = None
 8413            kind = None
 8414            nested = True
 8415
 8416        format_json = self._match_text_seq("FORMAT", "JSON")
 8417        path = self._match_text_seq("PATH") and self._parse_string()
 8418        nested_schema = nested and self._parse_json_schema()
 8419
 8420        return self.expression(
 8421            exp.JSONColumnDef(
 8422                this=this,
 8423                kind=kind,
 8424                path=path,
 8425                nested_schema=nested_schema,
 8426                ordinality=ordinality,
 8427                format_json=format_json,
 8428            )
 8429        )
 8430
 8431    def _parse_json_schema(self) -> exp.JSONSchema:
 8432        self._match_text_seq("COLUMNS")
 8433        return self.expression(
 8434            exp.JSONSchema(
 8435                expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True)
 8436            )
 8437        )
 8438
 8439    def _parse_json_table(self) -> exp.JSONTable:
 8440        this = self._parse_format_json(self._parse_bitwise())
 8441        path = self._match(TokenType.COMMA) and self._parse_string()
 8442        error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL")
 8443        empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL")
 8444        schema = self._parse_json_schema()
 8445
 8446        return exp.JSONTable(
 8447            this=this,
 8448            schema=schema,
 8449            path=path,
 8450            error_handling=error_handling,
 8451            empty_handling=empty_handling,
 8452        )
 8453
 8454    def _parse_match_against(self) -> exp.MatchAgainst:
 8455        if self._match_text_seq("TABLE"):
 8456            # parse SingleStore MATCH(TABLE ...) syntax
 8457            # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/
 8458            expressions = []
 8459            table = self._parse_table()
 8460            if table:
 8461                expressions = [table]
 8462        else:
 8463            expressions = self._parse_csv(self._parse_column)
 8464
 8465        self._match_text_seq(")", "AGAINST", "(")
 8466
 8467        this = self._parse_string()
 8468
 8469        if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"):
 8470            modifier = "IN NATURAL LANGUAGE MODE"
 8471            if self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8472                modifier = f"{modifier} WITH QUERY EXPANSION"
 8473        elif self._match_text_seq("IN", "BOOLEAN", "MODE"):
 8474            modifier = "IN BOOLEAN MODE"
 8475        elif self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8476            modifier = "WITH QUERY EXPANSION"
 8477        else:
 8478            modifier = None
 8479
 8480        return self.expression(
 8481            exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier)
 8482        )
 8483
 8484    # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16
 8485    def _parse_open_json(self) -> exp.OpenJSON:
 8486        this = self._parse_bitwise()
 8487        path = self._match(TokenType.COMMA) and self._parse_string()
 8488
 8489        def _parse_open_json_column_def() -> exp.OpenJSONColumnDef:
 8490            this = self._parse_field(any_token=True)
 8491            kind = self._parse_types()
 8492            path = self._parse_string()
 8493            as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON)
 8494
 8495            return self.expression(
 8496                exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json)
 8497            )
 8498
 8499        expressions = None
 8500        if self._match_pair(TokenType.R_PAREN, TokenType.WITH):
 8501            self._match_l_paren()
 8502            expressions = self._parse_csv(_parse_open_json_column_def)
 8503
 8504        return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions))
 8505
 8506    def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition:
 8507        args = self._parse_csv(self._parse_bitwise)
 8508
 8509        if self._match(TokenType.IN):
 8510            return self.expression(
 8511                exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0))
 8512            )
 8513
 8514        if haystack_first:
 8515            haystack = seq_get(args, 0)
 8516            needle = seq_get(args, 1)
 8517        else:
 8518            haystack = seq_get(args, 1)
 8519            needle = seq_get(args, 0)
 8520
 8521        return self.expression(
 8522            exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2))
 8523        )
 8524
 8525    def _parse_join_hint(self, func_name: str) -> exp.JoinHint:
 8526        args = self._parse_csv(self._parse_table)
 8527        return exp.JoinHint(this=func_name.upper(), expressions=args)
 8528
 8529    def _parse_substring(self) -> exp.Substring:
 8530        # Postgres supports the form: substring(string [from int] [for int])
 8531        # (despite being undocumented, the reverse order also works)
 8532        # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6
 8533
 8534        args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise))
 8535
 8536        start, length = None, None
 8537
 8538        while self._curr:
 8539            if self._match(TokenType.FROM):
 8540                start = self._parse_bitwise()
 8541            elif self._match(TokenType.FOR):
 8542                if not start:
 8543                    start = exp.Literal.number(1)
 8544                length = self._parse_bitwise()
 8545            else:
 8546                break
 8547
 8548        if start:
 8549            args.append(start)
 8550        if length:
 8551            args.append(length)
 8552
 8553        return self.validate_expression(exp.Substring.from_arg_list(args), args)
 8554
 8555    def _parse_trim(self) -> exp.Trim:
 8556        # https://www.w3resource.com/sql/character-functions/trim.php
 8557        # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html
 8558
 8559        position = None
 8560        collation = None
 8561        expression = None
 8562
 8563        if self._match_texts(self.TRIM_TYPES):
 8564            position = self._prev.text.upper()
 8565
 8566        this = self._parse_bitwise()
 8567        if self._match_set((TokenType.FROM, TokenType.COMMA)):
 8568            invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST
 8569            expression = self._parse_bitwise()
 8570
 8571            if invert_order:
 8572                this, expression = expression, this
 8573
 8574        if self._match(TokenType.COLLATE):
 8575            collation = self._parse_bitwise()
 8576
 8577        return self.expression(
 8578            exp.Trim(this=this, position=position, expression=expression, collation=collation)
 8579        )
 8580
 8581    def _parse_window_clause(self) -> list[exp.Expr] | None:
 8582        return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None
 8583
 8584    def _parse_named_window(self) -> exp.Expr | None:
 8585        return self._parse_window(self._parse_id_var(), alias=True)
 8586
 8587    def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None:
 8588        if self._curr.token_type == TokenType.VAR:
 8589            if self._match_text_seq("IGNORE", "NULLS"):
 8590                return self.expression(exp.IgnoreNulls(this=this))
 8591            if self._match_text_seq("RESPECT", "NULLS"):
 8592                return self.expression(exp.RespectNulls(this=this))
 8593        return this
 8594
 8595    def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None:
 8596        if self._match(TokenType.HAVING):
 8597            self._match_texts(("MAX", "MIN"))
 8598            max = self._prev.text.upper() != "MIN"
 8599            return self.expression(
 8600                exp.HavingMax(this=this, expression=self._parse_column(), max=max)
 8601            )
 8602
 8603        return this
 8604
 8605    def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None:
 8606        func = this
 8607        comments = func.comments if isinstance(func, exp.Expr) else None
 8608
 8609        # T-SQL allows the OVER (...) syntax after WITHIN GROUP.
 8610        # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16
 8611        if self._match_text_seq("WITHIN", "GROUP"):
 8612            order = self._parse_wrapped(self._parse_order)
 8613            this = self.expression(exp.WithinGroup(this=this, expression=order))
 8614
 8615        if self._match_pair(TokenType.FILTER, TokenType.L_PAREN):
 8616            self._match(TokenType.WHERE)
 8617            this = self.expression(
 8618                exp.Filter(this=this, expression=self._parse_where(skip_where_token=True))
 8619            )
 8620            self._match_r_paren()
 8621
 8622        # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER
 8623        # Some dialects choose to implement and some do not.
 8624        # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html
 8625
 8626        # There is some code above in _parse_lambda that handles
 8627        #   SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ...
 8628
 8629        # The below changes handle
 8630        #   SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ...
 8631
 8632        # Oracle allows both formats
 8633        #   (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html)
 8634        #   and Snowflake chose to do the same for familiarity
 8635        #   https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes
 8636        if isinstance(this, exp.AggFunc):
 8637            ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls)
 8638
 8639            if ignore_respect and ignore_respect is not this:
 8640                ignore_respect.replace(ignore_respect.this)
 8641                this = self.expression(ignore_respect.__class__(this=this))
 8642
 8643        this = self._parse_respect_or_ignore_nulls(this)
 8644
 8645        # bigquery select from window x AS (partition by ...)
 8646        if alias:
 8647            over = None
 8648            self._match(TokenType.ALIAS)
 8649        elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS):
 8650            return this
 8651        else:
 8652            over = self._prev.text.upper()
 8653
 8654        if comments and isinstance(func, exp.Expr):
 8655            func.pop_comments()
 8656
 8657        if not self._match(TokenType.L_PAREN):
 8658            return self.expression(
 8659                exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments
 8660            )
 8661
 8662        window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS)
 8663
 8664        first: bool | None = True if self._match(TokenType.FIRST) else None
 8665        if self._match_text_seq("LAST"):
 8666            first = False
 8667
 8668        partition, order = self._parse_partition_and_order()
 8669        kind = (
 8670            self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS")
 8671        ) and self._prev.text
 8672
 8673        if kind:
 8674            self._match(TokenType.BETWEEN)
 8675            start = self._parse_window_spec()
 8676
 8677            end = self._parse_window_spec() if self._match(TokenType.AND) else {}
 8678            exclude = (
 8679                self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS)
 8680                if self._match_text_seq("EXCLUDE")
 8681                else None
 8682            )
 8683
 8684            spec = self.expression(
 8685                exp.WindowSpec(
 8686                    kind=kind,
 8687                    start=start["value"],
 8688                    start_side=start["side"],
 8689                    end=end.get("value"),
 8690                    end_side=end.get("side"),
 8691                    exclude=exclude,
 8692                )
 8693            )
 8694        else:
 8695            spec = None
 8696
 8697        self._match_r_paren()
 8698
 8699        window = self.expression(
 8700            exp.Window(
 8701                this=this,
 8702                partition_by=partition,
 8703                order=order,
 8704                spec=spec,
 8705                alias=window_alias,
 8706                over=over,
 8707                first=first,
 8708            ),
 8709            comments=comments,
 8710        )
 8711
 8712        # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...)
 8713        if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False):
 8714            return self._parse_window(window, alias=alias)
 8715
 8716        return window
 8717
 8718    def _parse_partition_and_order(
 8719        self,
 8720    ) -> tuple[list[exp.Expr], exp.Expr | None]:
 8721        return self._parse_partition_by(), self._parse_order()
 8722
 8723    def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]:
 8724        self._match(TokenType.BETWEEN)
 8725
 8726        return {
 8727            "value": (
 8728                (self._match_text_seq("UNBOUNDED") and "UNBOUNDED")
 8729                or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW")
 8730                or self._parse_bitwise()
 8731            ),
 8732            "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None,
 8733        }
 8734
 8735    def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None:
 8736        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 8737        # so this section tries to parse the clause version and if it fails, it treats the token
 8738        # as an identifier (alias)
 8739        if self._can_parse_limit_or_offset():
 8740            return this
 8741
 8742        # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the
 8743        # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it.
 8744        if self._can_parse_named_window():
 8745            return this
 8746
 8747        any_token = self._match(TokenType.ALIAS)
 8748        comments = self._prev_comments
 8749
 8750        if explicit and not any_token:
 8751            return this
 8752
 8753        if self._match(TokenType.L_PAREN):
 8754            aliases = self.expression(
 8755                exp.Aliases(
 8756                    this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token))
 8757                ),
 8758                comments=comments,
 8759            )
 8760            self._match_r_paren(aliases)
 8761            return aliases
 8762
 8763        alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or (
 8764            self.STRING_ALIASES and self._parse_string_as_identifier()
 8765        )
 8766
 8767        if alias:
 8768            comments.extend(alias.pop_comments())
 8769            this = self.expression(exp.Alias(this=this, alias=alias), comments=comments)
 8770            column = this.this
 8771
 8772            # Moves the comment next to the alias in `expr /* comment */ AS alias`
 8773            if not this.comments and column and column.comments:
 8774                this.comments = column.pop_comments()
 8775
 8776        return this
 8777
 8778    def _parse_id_var(
 8779        self,
 8780        any_token: bool = True,
 8781        tokens: t.Collection[TokenType] | None = None,
 8782    ) -> exp.Expr | None:
 8783        expression = self._parse_identifier()
 8784        if not expression and (
 8785            (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS)
 8786        ):
 8787            quoted = self._prev.token_type == TokenType.STRING
 8788            expression = self._identifier_expression(quoted=quoted)
 8789
 8790        return expression
 8791
 8792    def _parse_string(self) -> exp.Expr | None:
 8793        if self._match_set(self.STRING_PARSERS):
 8794            return self.STRING_PARSERS[self._prev.token_type](self, self._prev)
 8795        return self._parse_placeholder()
 8796
 8797    def _parse_string_as_identifier(self) -> exp.Identifier | None:
 8798        if not self._match(TokenType.STRING):
 8799            return None
 8800        output = exp.to_identifier(self._prev.text, quoted=True)
 8801        output.update_positions(self._prev)
 8802        return output
 8803
 8804    def _parse_number(self) -> exp.Expr | None:
 8805        if self._match_set(self.NUMERIC_PARSERS):
 8806            return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev)
 8807        return self._parse_placeholder()
 8808
 8809    def _parse_identifier(self) -> exp.Expr | None:
 8810        if self._match(TokenType.IDENTIFIER):
 8811            return self._identifier_expression(quoted=True)
 8812        return self._parse_placeholder()
 8813
 8814    def _parse_var(
 8815        self,
 8816        any_token: bool = False,
 8817        tokens: t.Collection[TokenType] | None = None,
 8818        upper: bool = False,
 8819    ) -> exp.Expr | None:
 8820        if (
 8821            (any_token and self._advance_any())
 8822            or self._match(TokenType.VAR)
 8823            or (self._match_set(tokens) if tokens else False)
 8824        ):
 8825            return self.expression(
 8826                exp.Var(this=self._prev.text.upper() if upper else self._prev.text)
 8827            )
 8828        return self._parse_placeholder()
 8829
 8830    def _advance_any(self, ignore_reserved: bool = False) -> Token | None:
 8831        if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS):
 8832            self._advance()
 8833            return self._prev
 8834        return None
 8835
 8836    def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None:
 8837        return self._parse_string() or self._parse_var(any_token=True, upper=upper)
 8838
 8839    def _parse_primary_or_var(self) -> exp.Expr | None:
 8840        return self._parse_primary() or self._parse_var(any_token=True)
 8841
 8842    def _parse_null(self) -> exp.Expr | None:
 8843        if self._match_set((TokenType.NULL, TokenType.UNKNOWN)):
 8844            return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev)
 8845        return self._parse_placeholder()
 8846
 8847    def _parse_boolean(self) -> exp.Expr | None:
 8848        if self._match(TokenType.TRUE):
 8849            return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev)
 8850        if self._match(TokenType.FALSE):
 8851            return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev)
 8852        return self._parse_placeholder()
 8853
 8854    def _parse_star(self) -> exp.Expr | None:
 8855        if self._match(TokenType.STAR):
 8856            return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev)
 8857        return self._parse_placeholder()
 8858
 8859    def _parse_parameter(self) -> exp.Parameter:
 8860        this = self._parse_identifier() or self._parse_primary_or_var()
 8861        return self.expression(exp.Parameter(this=this))
 8862
 8863    def _parse_placeholder(self) -> exp.Expr | None:
 8864        if self._match_set(self.PLACEHOLDER_PARSERS):
 8865            placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self)
 8866            if placeholder:
 8867                return placeholder
 8868            self._advance(-1)
 8869        return None
 8870
 8871    def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None:
 8872        if not self._match_texts(keywords):
 8873            return None
 8874        if self._match(TokenType.L_PAREN, advance=False):
 8875            return self._parse_wrapped_csv(self._parse_expression)
 8876
 8877        expression = self._parse_alias(self._parse_disjunction(), explicit=True)
 8878        return [expression] if expression else None
 8879
 8880    def _parse_csv(
 8881        self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA
 8882    ) -> list[T]:
 8883        parse_result = parse_method()
 8884        items = [parse_result] if parse_result is not None else []
 8885
 8886        while self._match(sep):
 8887            if isinstance(parse_result, exp.Expr):
 8888                self._add_comments(parse_result)
 8889            parse_result = parse_method()
 8890            if parse_result is not None:
 8891                items.append(parse_result)
 8892
 8893        return items
 8894
 8895    def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]:
 8896        return self._parse_wrapped_csv(self._parse_id_var, optional=optional)
 8897
 8898    def _parse_wrapped_csv(
 8899        self,
 8900        parse_method: t.Callable[[], T | None],
 8901        sep: TokenType = TokenType.COMMA,
 8902        optional: bool = False,
 8903    ) -> list[T]:
 8904        return self._parse_wrapped(
 8905            lambda: self._parse_csv(parse_method, sep=sep), optional=optional
 8906        )
 8907
 8908    def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T:
 8909        wrapped = self._match(TokenType.L_PAREN)
 8910        if not wrapped and not optional:
 8911            self.raise_error("Expecting (")
 8912        parse_result = parse_method()
 8913        if wrapped:
 8914            self._match_r_paren()
 8915        return parse_result
 8916
 8917    def _parse_expressions(self) -> list[exp.Expr]:
 8918        return self._parse_csv(self._parse_expression)
 8919
 8920    def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None:
 8921        return (
 8922            self._parse_set_operations(
 8923                self._parse_alias(self._parse_assignment(), explicit=True)
 8924                if alias
 8925                else self._parse_assignment()
 8926            )
 8927            or self._parse_select()
 8928        )
 8929
 8930    def _parse_ddl_select(self) -> exp.Expr | None:
 8931        return self._parse_query_modifiers(
 8932            self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False))
 8933        )
 8934
 8935    def _parse_transaction(self) -> exp.Transaction | exp.Command:
 8936        this = None
 8937        if self._match_texts(self.TRANSACTION_KIND):
 8938            this = self._prev.text
 8939
 8940        self._match_texts(("TRANSACTION", "WORK"))
 8941
 8942        modes = []
 8943        while True:
 8944            mode = []
 8945            while self._match(TokenType.VAR) or self._match(TokenType.NOT):
 8946                mode.append(self._prev.text)
 8947
 8948            if mode:
 8949                modes.append(" ".join(mode))
 8950            if not self._match(TokenType.COMMA):
 8951                break
 8952
 8953        return self.expression(exp.Transaction(this=this, modes=modes))
 8954
 8955    def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback:
 8956        chain = None
 8957        savepoint = None
 8958        is_rollback = self._prev.token_type == TokenType.ROLLBACK
 8959
 8960        self._match_texts(("TRANSACTION", "WORK"))
 8961
 8962        if self._match_text_seq("TO"):
 8963            self._match_text_seq("SAVEPOINT")
 8964            savepoint = self._parse_id_var()
 8965
 8966        if self._match(TokenType.AND):
 8967            chain = not self._match_text_seq("NO")
 8968            self._match_text_seq("CHAIN")
 8969
 8970        if is_rollback:
 8971            return self.expression(exp.Rollback(savepoint=savepoint))
 8972
 8973        return self.expression(exp.Commit(chain=chain))
 8974
 8975    def _parse_refresh(self) -> exp.Refresh | exp.Command:
 8976        if self._match_text_seq("EXTERNAL", "TABLE"):
 8977            kind = "EXTERNAL TABLE"
 8978        elif self._match(TokenType.TABLE):
 8979            kind = "TABLE"
 8980        elif self._match_text_seq("MATERIALIZED", "VIEW"):
 8981            kind = "MATERIALIZED VIEW"
 8982        else:
 8983            kind = ""
 8984
 8985        this = self._parse_string() or self._parse_table()
 8986        if not kind and not isinstance(this, exp.Literal):
 8987            return self._parse_as_command(self._prev)
 8988
 8989        return self.expression(exp.Refresh(this=this, kind=kind))
 8990
 8991    def _parse_column_def_with_exists(self):
 8992        start = self._index
 8993        self._match(TokenType.COLUMN)
 8994
 8995        exists_column = self._parse_exists(not_=True)
 8996        expression = self._parse_field_def()
 8997
 8998        if not isinstance(expression, exp.ColumnDef):
 8999            self._retreat(start)
 9000            return None
 9001
 9002        expression.set("exists", exists_column)
 9003
 9004        return expression
 9005
 9006    def _parse_add_column(self) -> exp.ColumnDef | None:
 9007        if not self._prev.text.upper() == "ADD":
 9008            return None
 9009
 9010        return self._parse_column_def_with_exists()
 9011
 9012    def _parse_drop_column(self) -> exp.Drop | exp.Command | None:
 9013        drop = self._parse_drop() if self._match(TokenType.DROP) else None
 9014        if drop and not isinstance(drop, exp.Command):
 9015            drop.set("kind", drop.args.get("kind", "COLUMN"))
 9016        return drop
 9017
 9018    def _parse_alter_drop_action(self) -> exp.Expr | None:
 9019        return self._parse_drop_column()
 9020
 9021    # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html
 9022    def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition:
 9023        return self.expression(
 9024            exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists)
 9025        )
 9026
 9027    def _parse_alter_table_add(self) -> list[exp.Expr]:
 9028        def _parse_add_alteration() -> exp.Expr | None:
 9029            self._match_text_seq("ADD")
 9030            if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False):
 9031                return self.expression(
 9032                    exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint))
 9033                )
 9034
 9035            column_def = self._parse_add_column()
 9036            if isinstance(column_def, exp.ColumnDef):
 9037                return column_def
 9038
 9039            exists = self._parse_exists(not_=True)
 9040            if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False):
 9041                return self.expression(
 9042                    exp.AddPartition(
 9043                        exists=exists,
 9044                        this=self._parse_field(any_token=True),
 9045                        location=self._match_text_seq("LOCATION", advance=False)
 9046                        and self._parse_property(),
 9047                    )
 9048                )
 9049
 9050            return None
 9051
 9052        if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and (
 9053            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
 9054            or self._match_text_seq("COLUMNS")
 9055        ):
 9056            schema = self._parse_schema()
 9057
 9058            return (
 9059                ensure_list(schema)
 9060                if schema
 9061                else self._parse_csv(self._parse_column_def_with_exists)
 9062            )
 9063
 9064        return self._parse_csv(_parse_add_alteration)
 9065
 9066    def _parse_alter_table_alter(self) -> exp.Expr | None:
 9067        if self._match_texts(self.ALTER_ALTER_PARSERS):
 9068            return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self)
 9069
 9070        # Many dialects support the ALTER [COLUMN] syntax, so if there is no
 9071        # keyword after ALTER we default to parsing this statement
 9072        self._match(TokenType.COLUMN)
 9073        exists = self._parse_exists()
 9074        column = self._parse_field(any_token=True)
 9075
 9076        if self._match_pair(TokenType.DROP, TokenType.DEFAULT):
 9077            return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None))
 9078        if self._match_pair(TokenType.SET, TokenType.DEFAULT):
 9079            return self.expression(
 9080                exp.AlterColumn(
 9081                    this=column, default=self._parse_disjunction(), exists=exists or None
 9082                )
 9083            )
 9084        if self._match(TokenType.COMMENT):
 9085            return self.expression(
 9086                exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None)
 9087            )
 9088        if self._match_text_seq("DROP", "NOT", "NULL"):
 9089            return self.expression(
 9090                exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None)
 9091            )
 9092        if self._match_text_seq("SET", "NOT", "NULL"):
 9093            return self.expression(
 9094                exp.AlterColumn(this=column, allow_null=False, exists=exists or None)
 9095            )
 9096
 9097        if self._match_text_seq("SET", "VISIBLE"):
 9098            return self.expression(
 9099                exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None)
 9100            )
 9101        if self._match_text_seq("SET", "INVISIBLE"):
 9102            return self.expression(
 9103                exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None)
 9104            )
 9105
 9106        self._match_text_seq("SET", "DATA")
 9107        self._match_text_seq("TYPE")
 9108        return self.expression(
 9109            exp.AlterColumn(
 9110                this=column,
 9111                dtype=self._parse_types(),
 9112                collate=self._match(TokenType.COLLATE) and self._parse_term(),
 9113                using=self._match(TokenType.USING) and self._parse_disjunction(),
 9114                exists=exists or None,
 9115            )
 9116        )
 9117
 9118    def _parse_alter_diststyle(self) -> exp.AlterDistStyle:
 9119        if self._match_texts(("ALL", "EVEN", "AUTO")):
 9120            return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper())))
 9121
 9122        self._match_text_seq("KEY", "DISTKEY")
 9123        return self.expression(exp.AlterDistStyle(this=self._parse_column()))
 9124
 9125    def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey:
 9126        if compound:
 9127            self._match_text_seq("SORTKEY")
 9128
 9129        if self._match(TokenType.L_PAREN, advance=False):
 9130            return self.expression(
 9131                exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound)
 9132            )
 9133
 9134        self._match_texts(("AUTO", "NONE"))
 9135        return self.expression(
 9136            exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound)
 9137        )
 9138
 9139    def _parse_alter_table_drop(self) -> list[exp.Expr]:
 9140        index = self._index - 1
 9141
 9142        partition_exists = self._parse_exists()
 9143        if self._match(TokenType.PARTITION, advance=False):
 9144            return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists))
 9145
 9146        self._retreat(index)
 9147        return self._parse_csv(self._parse_alter_drop_action)
 9148
 9149    def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None:
 9150        if self._match(TokenType.COLUMN) or (
 9151            not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False)
 9152        ):
 9153            exists = self._parse_exists()
 9154            old_column = self._parse_column()
 9155            to = self._match_text_seq("TO")
 9156            new_column = self._parse_column()
 9157
 9158            if old_column is None or not to or new_column is None:
 9159                return None
 9160
 9161            return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists))
 9162
 9163        self._match_text_seq("TO")
 9164        return self.expression(exp.AlterRename(this=self._parse_table(schema=True)))
 9165
 9166    def _parse_alter_table_set(self) -> exp.AlterSet:
 9167        alter_set = self.expression(exp.AlterSet())
 9168
 9169        if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq(
 9170            "TABLE", "PROPERTIES"
 9171        ):
 9172            alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment))
 9173        elif self._match_text_seq("FILESTREAM_ON", advance=False):
 9174            alter_set.set("expressions", [self._parse_assignment()])
 9175        elif self._match_texts(("LOGGED", "UNLOGGED")):
 9176            alter_set.set("option", exp.var(self._prev.text.upper()))
 9177        elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")):
 9178            alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}"))
 9179        elif self._match_text_seq("LOCATION"):
 9180            alter_set.set("location", self._parse_field())
 9181        elif self._match_text_seq("ACCESS", "METHOD"):
 9182            alter_set.set("access_method", self._parse_field())
 9183        elif self._match_text_seq("TABLESPACE"):
 9184            alter_set.set("tablespace", self._parse_field())
 9185        elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"):
 9186            alter_set.set("file_format", [self._parse_field()])
 9187        elif self._match_text_seq("STAGE_FILE_FORMAT"):
 9188            alter_set.set("file_format", self._parse_wrapped_options())
 9189        elif self._match_text_seq("STAGE_COPY_OPTIONS"):
 9190            alter_set.set("copy_options", self._parse_wrapped_options())
 9191        elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"):
 9192            alter_set.set("tag", self._parse_csv(self._parse_assignment))
 9193        else:
 9194            if self._match_text_seq("SERDE"):
 9195                alter_set.set("serde", self._parse_field())
 9196
 9197            properties = self._parse_wrapped(self._parse_properties, optional=True)
 9198            alter_set.set("expressions", [properties])
 9199
 9200        return alter_set
 9201
 9202    def _parse_alter_session(self) -> exp.AlterSession:
 9203        """Parse ALTER SESSION SET/UNSET statements."""
 9204        if self._match(TokenType.SET):
 9205            expressions = self._parse_csv(lambda: self._parse_set_item_assignment())
 9206            return self.expression(exp.AlterSession(expressions=expressions, unset=False))
 9207
 9208        self._match_text_seq("UNSET")
 9209        expressions = self._parse_csv(
 9210            lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True)))
 9211        )
 9212        return self.expression(exp.AlterSession(expressions=expressions, unset=True))
 9213
 9214    def _parse_alter(self) -> exp.Alter | exp.Command:
 9215        start = self._prev
 9216
 9217        iceberg = self._match_text_seq("ICEBERG")
 9218
 9219        alter_token = self._match_set(self.ALTERABLES) and self._prev
 9220        if not alter_token:
 9221            return self._parse_as_command(start)
 9222        if iceberg and alter_token.token_type != TokenType.TABLE:
 9223            return self._parse_as_command(start)
 9224
 9225        exists = self._parse_exists()
 9226        only = self._match_text_seq("ONLY")
 9227
 9228        if alter_token.token_type == TokenType.SESSION:
 9229            this = None
 9230            check = None
 9231            cluster = None
 9232        else:
 9233            this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS)
 9234            check = self._match_text_seq("WITH", "CHECK")
 9235            cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9236
 9237            if self._next:
 9238                self._advance()
 9239
 9240        parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None
 9241        if parser:
 9242            actions = ensure_list(parser(self))
 9243            not_valid = self._match_text_seq("NOT", "VALID")
 9244            options = self._parse_csv(self._parse_property)
 9245            cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE")
 9246
 9247            if not self._curr and actions:
 9248                return self.expression(
 9249                    exp.Alter(
 9250                        this=this,
 9251                        kind=alter_token.text.upper(),
 9252                        exists=exists,
 9253                        actions=actions,
 9254                        only=only,
 9255                        options=options,
 9256                        cluster=cluster,
 9257                        not_valid=not_valid,
 9258                        check=check,
 9259                        cascade=cascade,
 9260                        iceberg=iceberg,
 9261                    )
 9262                )
 9263
 9264        return self._parse_as_command(start)
 9265
 9266    def _parse_analyze(self) -> exp.Analyze | exp.Command:
 9267        start = self._prev
 9268        # https://duckdb.org/docs/sql/statements/analyze
 9269        if not self._curr:
 9270            return self.expression(exp.Analyze())
 9271
 9272        options = []
 9273        while self._match_texts(self.ANALYZE_STYLES):
 9274            if self._prev.text.upper() == "BUFFER_USAGE_LIMIT":
 9275                options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}")
 9276            else:
 9277                options.append(self._prev.text.upper())
 9278
 9279        this: exp.Expr | None = None
 9280        inner_expression: exp.Expr | None = None
 9281
 9282        kind = self._curr.text.upper() if self._curr else None
 9283
 9284        if self._match(TokenType.TABLE) or self._match(TokenType.INDEX):
 9285            this = self._parse_table_parts()
 9286        elif self._match_text_seq("TABLES"):
 9287            if self._match_set((TokenType.FROM, TokenType.IN)):
 9288                kind = f"{kind} {self._prev.text.upper()}"
 9289                this = self._parse_table(schema=True, is_db_reference=True)
 9290        elif self._match_text_seq("DATABASE"):
 9291            this = self._parse_table(schema=True, is_db_reference=True)
 9292        elif self._match_text_seq("CLUSTER"):
 9293            this = self._parse_table()
 9294        # Try matching inner expr keywords before fallback to parse table.
 9295        elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9296            kind = None
 9297            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9298        else:
 9299            # Empty kind  https://prestodb.io/docs/current/sql/analyze.html
 9300            kind = None
 9301            this = self._parse_table_parts()
 9302
 9303        partition = self._try_parse(self._parse_partition)
 9304        if not partition and self._match_texts(self.PARTITION_KEYWORDS):
 9305            return self._parse_as_command(start)
 9306
 9307        # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9308        if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq(
 9309            "WITH", "ASYNC", "MODE"
 9310        ):
 9311            mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE"
 9312        else:
 9313            mode = None
 9314
 9315        if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9316            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9317
 9318        properties = self._parse_properties()
 9319        return self.expression(
 9320            exp.Analyze(
 9321                kind=kind,
 9322                this=this,
 9323                mode=mode,
 9324                partition=partition,
 9325                properties=properties,
 9326                expression=inner_expression,
 9327                options=options,
 9328            )
 9329        )
 9330
 9331    # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html
 9332    def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics:
 9333        this = None
 9334        kind = self._prev.text.upper()
 9335        option = self._prev.text.upper() if self._match_text_seq("DELTA") else None
 9336        expressions = []
 9337
 9338        if not self._match_text_seq("STATISTICS"):
 9339            self.raise_error("Expecting token STATISTICS")
 9340
 9341        if self._match_text_seq("NOSCAN"):
 9342            this = "NOSCAN"
 9343        elif self._match(TokenType.FOR):
 9344            if self._match_text_seq("ALL", "COLUMNS"):
 9345                this = "FOR ALL COLUMNS"
 9346            if self._match_text_seq("COLUMNS"):
 9347                this = "FOR COLUMNS"
 9348                expressions = self._parse_csv(self._parse_column_reference)
 9349        elif self._match_text_seq("SAMPLE"):
 9350            sample = self._parse_number()
 9351            expressions = [
 9352                self.expression(
 9353                    exp.AnalyzeSample(
 9354                        sample=sample,
 9355                        kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None,
 9356                    )
 9357                )
 9358            ]
 9359
 9360        return self.expression(
 9361            exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions)
 9362        )
 9363
 9364    # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html
 9365    def _parse_analyze_validate(self) -> exp.AnalyzeValidate:
 9366        kind = None
 9367        this = None
 9368        expression: exp.Expr | None = None
 9369        if self._match_text_seq("REF", "UPDATE"):
 9370            kind = "REF"
 9371            this = "UPDATE"
 9372            if self._match_text_seq("SET", "DANGLING", "TO", "NULL"):
 9373                this = "UPDATE SET DANGLING TO NULL"
 9374        elif self._match_text_seq("STRUCTURE"):
 9375            kind = "STRUCTURE"
 9376            if self._match_text_seq("CASCADE", "FAST"):
 9377                this = "CASCADE FAST"
 9378            elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts(
 9379                ("ONLINE", "OFFLINE")
 9380            ):
 9381                this = f"CASCADE COMPLETE {self._prev.text.upper()}"
 9382                expression = self._parse_into()
 9383
 9384        return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression))
 9385
 9386    def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None:
 9387        this = self._prev.text.upper()
 9388        if self._match_text_seq("COLUMNS"):
 9389            return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}"))
 9390        return None
 9391
 9392    def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None:
 9393        kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None
 9394        if self._match_text_seq("STATISTICS"):
 9395            return self.expression(exp.AnalyzeDelete(kind=kind))
 9396        return None
 9397
 9398    def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None:
 9399        if self._match_text_seq("CHAINED", "ROWS"):
 9400            return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into()))
 9401        return None
 9402
 9403    # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html
 9404    def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram:
 9405        this = self._prev.text.upper()
 9406        expression: exp.Expr | None = None
 9407        expressions = []
 9408        update_options = None
 9409
 9410        if self._match_text_seq("HISTOGRAM", "ON"):
 9411            expressions = self._parse_csv(self._parse_column_reference)
 9412            with_expressions = []
 9413            while self._match(TokenType.WITH):
 9414                # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9415                if self._match_texts(("SYNC", "ASYNC")):
 9416                    if self._match_text_seq("MODE", advance=False):
 9417                        with_expressions.append(f"{self._prev.text.upper()} MODE")
 9418                        self._advance()
 9419                else:
 9420                    buckets = self._parse_number()
 9421                    if self._match_text_seq("BUCKETS"):
 9422                        with_expressions.append(f"{buckets} BUCKETS")
 9423            if with_expressions:
 9424                expression = self.expression(exp.AnalyzeWith(expressions=with_expressions))
 9425
 9426            if self._match_texts(("MANUAL", "AUTO")) and self._match(
 9427                TokenType.UPDATE, advance=False
 9428            ):
 9429                update_options = self._prev.text.upper()
 9430                self._advance()
 9431            elif self._match_text_seq("USING", "DATA"):
 9432                expression = self.expression(exp.UsingData(this=self._parse_string()))
 9433
 9434        return self.expression(
 9435            exp.AnalyzeHistogram(
 9436                this=this,
 9437                expressions=expressions,
 9438                expression=expression,
 9439                update_options=update_options,
 9440            )
 9441        )
 9442
 9443    def _parse_merge(self) -> exp.Merge:
 9444        self._match(TokenType.INTO)
 9445        target = self._parse_table()
 9446
 9447        if target and self._match(TokenType.ALIAS, advance=False):
 9448            target.set("alias", self._parse_table_alias())
 9449
 9450        self._match(TokenType.USING)
 9451        using = self._parse_table()
 9452
 9453        return self.expression(
 9454            exp.Merge(
 9455                this=target,
 9456                using=using,
 9457                on=self._match(TokenType.ON) and self._parse_disjunction(),
 9458                using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(),
 9459                whens=self._parse_when_matched(),
 9460                returning=self._parse_returning(),
 9461            )
 9462        )
 9463
 9464    def _parse_when_matched(self) -> exp.Whens:
 9465        whens = []
 9466
 9467        while self._match(TokenType.WHEN):
 9468            matched = not self._match(TokenType.NOT)
 9469            self._match_text_seq("MATCHED")
 9470            source = (
 9471                False
 9472                if self._match_text_seq("BY", "TARGET")
 9473                else self._match_text_seq("BY", "SOURCE")
 9474            )
 9475            condition = self._parse_disjunction() if self._match(TokenType.AND) else None
 9476
 9477            self._match(TokenType.THEN)
 9478
 9479            if self._match(TokenType.INSERT):
 9480                this = self._parse_star()
 9481                if this:
 9482                    then: exp.Expr | None = self.expression(exp.Insert(this=this))
 9483                else:
 9484                    then = self.expression(
 9485                        exp.Insert(
 9486                            this=exp.var("ROW")
 9487                            if self._match_text_seq("ROW")
 9488                            else self._parse_value(values=False),
 9489                            expression=self._match_text_seq("VALUES") and self._parse_value(),
 9490                            where=self._parse_where(),
 9491                        )
 9492                    )
 9493            elif self._match(TokenType.UPDATE):
 9494                expressions = self._parse_star()
 9495                if expressions:
 9496                    then = self.expression(exp.Update(expressions=expressions))
 9497                else:
 9498                    then = self.expression(
 9499                        exp.Update(
 9500                            expressions=self._match(TokenType.SET)
 9501                            and self._parse_csv(self._parse_equality),
 9502                            where=self._parse_where(),
 9503                        )
 9504                    )
 9505            elif self._match(TokenType.DELETE):
 9506                then = self.expression(exp.Var(this=self._prev.text))
 9507            else:
 9508                then = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 9509
 9510            whens.append(
 9511                self.expression(
 9512                    exp.When(matched=matched, source=source, condition=condition, then=then)
 9513                )
 9514            )
 9515        return self.expression(exp.Whens(expressions=whens))
 9516
 9517    def _parse_show(self) -> exp.Expr | None:
 9518        parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE)
 9519        if parser:
 9520            return parser(self)
 9521        return self._parse_as_command(self._prev)
 9522
 9523    def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None:
 9524        index = self._index
 9525
 9526        if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"):
 9527            return self._parse_set_transaction(global_=kind == "GLOBAL")
 9528
 9529        left = self._parse_primary() or self._parse_column()
 9530        assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS)
 9531
 9532        if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter):
 9533            self._retreat(index)
 9534            return None
 9535
 9536        right = self._parse_statement() or self._parse_id_var()
 9537        if isinstance(right, (exp.Column, exp.Identifier)):
 9538            right = exp.var(right.name)
 9539
 9540        this = self.expression(exp.EQ(this=left, expression=right))
 9541        return self.expression(exp.SetItem(this=this, kind=kind))
 9542
 9543    def _parse_set_transaction(self, global_: bool = False) -> exp.Expr:
 9544        self._match_text_seq("TRANSACTION")
 9545        characteristics = self._parse_csv(
 9546            lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS)
 9547        )
 9548        return self.expression(
 9549            exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_)
 9550        )
 9551
 9552    def _parse_set_item(self) -> exp.Expr | None:
 9553        parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE)
 9554        return parser(self) if parser else self._parse_set_item_assignment(kind=None)
 9555
 9556    def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command:
 9557        index = self._index
 9558        set_ = self.expression(
 9559            exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag)
 9560        )
 9561
 9562        if self._curr:
 9563            self._retreat(index)
 9564            return self._parse_as_command(self._prev)
 9565
 9566        return set_
 9567
 9568    def _parse_var_from_options(
 9569        self, options: OPTIONS_TYPE, raise_unmatched: bool = True
 9570    ) -> exp.Var | None:
 9571        start = self._curr
 9572        if not start:
 9573            return None
 9574
 9575        option = start.text.upper()
 9576        continuations = (
 9577            None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option)
 9578        )
 9579
 9580        index = self._index
 9581        self._advance()
 9582        for keywords in continuations or []:
 9583            if isinstance(keywords, str):
 9584                keywords = (keywords,)
 9585
 9586            if self._match_text_seq(*keywords):
 9587                option = f"{option} {' '.join(keywords)}"
 9588                break
 9589        else:
 9590            if continuations or continuations is None:
 9591                if raise_unmatched:
 9592                    self.raise_error(f"Unknown option {option}")
 9593
 9594                self._retreat(index)
 9595                return None
 9596
 9597        return exp.var(option)
 9598
 9599    def _parse_as_command(self, start: Token) -> exp.Command:
 9600        while self._curr:
 9601            self._advance()
 9602        text = self._find_sql(start, self._prev)
 9603        size = len(start.text)
 9604        self._warn_unsupported()
 9605        return exp.Command(this=text[:size], expression=text[size:])
 9606
 9607    def _parse_dict_property(self, this: str) -> exp.DictProperty:
 9608        settings = []
 9609
 9610        self._match_l_paren()
 9611        kind = self._parse_id_var()
 9612
 9613        if self._match(TokenType.L_PAREN):
 9614            while True:
 9615                key = self._parse_id_var()
 9616                value = self._parse_function() or self._parse_primary_or_var()
 9617                if not key and value is None:
 9618                    break
 9619                settings.append(self.expression(exp.DictSubProperty(this=key, value=value)))
 9620            self._match(TokenType.R_PAREN)
 9621
 9622        self._match_r_paren()
 9623
 9624        return self.expression(
 9625            exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings)
 9626        )
 9627
 9628    def _parse_dict_range(self, this: str) -> exp.DictRange:
 9629        self._match_l_paren()
 9630        has_min = self._match_text_seq("MIN")
 9631        if has_min:
 9632            min = self._parse_var() or self._parse_primary()
 9633            self._match_text_seq("MAX")
 9634            max = self._parse_var() or self._parse_primary()
 9635        else:
 9636            max = self._parse_var() or self._parse_primary()
 9637            min = exp.Literal.number(0)
 9638        self._match_r_paren()
 9639        return self.expression(exp.DictRange(this=this, min=min, max=max))
 9640
 9641    def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None:
 9642        index = self._index
 9643        expression = self._parse_column()
 9644        position = self._match(TokenType.COMMA) and self._parse_column()
 9645
 9646        if not self._match(TokenType.IN):
 9647            self._retreat(index - 1)
 9648            return None
 9649        iterator = self._parse_column()
 9650        condition = self._parse_disjunction() if self._match_text_seq("IF") else None
 9651        return self.expression(
 9652            exp.Comprehension(
 9653                this=this,
 9654                expression=expression,
 9655                position=position,
 9656                iterator=iterator,
 9657                condition=condition,
 9658            )
 9659        )
 9660
 9661    def _parse_heredoc(self) -> exp.Heredoc | None:
 9662        if self._match(TokenType.HEREDOC_STRING):
 9663            return self.expression(exp.Heredoc(this=self._prev.text))
 9664
 9665        if not self._match_text_seq("$"):
 9666            return None
 9667
 9668        tags = ["$"]
 9669        tag_text = None
 9670
 9671        if self._is_connected():
 9672            self._advance()
 9673            tags.append(self._prev.text.upper())
 9674        else:
 9675            self.raise_error("No closing $ found")
 9676
 9677        if tags[-1] != "$":
 9678            if self._is_connected() and self._match_text_seq("$"):
 9679                tag_text = tags[-1]
 9680                tags.append("$")
 9681            else:
 9682                self.raise_error("No closing $ found")
 9683
 9684        heredoc_start = self._curr
 9685
 9686        while self._curr:
 9687            if self._match_text_seq(*tags, advance=False):
 9688                this = self._find_sql(heredoc_start, self._prev)
 9689                self._advance(len(tags))
 9690                return self.expression(exp.Heredoc(this=this, tag=tag_text))
 9691
 9692            self._advance()
 9693
 9694        self.raise_error(f"No closing {''.join(tags)} found")
 9695        return None
 9696
 9697    def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None:
 9698        if not self._curr:
 9699            return None
 9700
 9701        index = self._index
 9702        this = []
 9703        while True:
 9704            # The current token might be multiple words
 9705            curr = self._curr.text.upper()
 9706            key = curr.split(" ")
 9707            this.append(curr)
 9708
 9709            self._advance()
 9710            result, trie = in_trie(trie, key)
 9711            if result == TrieResult.FAILED:
 9712                break
 9713
 9714            if result == TrieResult.EXISTS:
 9715                subparser = parsers[" ".join(this)]
 9716                return subparser
 9717
 9718        self._retreat(index)
 9719        return None
 9720
 9721    def _match_l_paren(self, expression: exp.Expr | None = None) -> None:
 9722        if not self._match(TokenType.L_PAREN, expression=expression):
 9723            self.raise_error("Expecting (")
 9724
 9725    def _match_r_paren(self, expression: exp.Expr | None = None) -> None:
 9726        if not self._match(TokenType.R_PAREN, expression=expression):
 9727            self.raise_error("Expecting )")
 9728
 9729    def _replace_lambda(
 9730        self, node: exp.Expr | None, expressions: list[exp.Expr]
 9731    ) -> exp.Expr | None:
 9732        if not node:
 9733            return node
 9734
 9735        lambda_types = {e.name: e.args.get("to") or False for e in expressions}
 9736
 9737        for column in node.find_all(exp.Column):
 9738            typ = lambda_types.get(column.parts[0].name)
 9739            if typ is not None:
 9740                dot_or_id = column.to_dot() if column.table else column.this
 9741
 9742                if typ:
 9743                    dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ))
 9744
 9745                parent = column.parent
 9746
 9747                while isinstance(parent, exp.Dot):
 9748                    if not isinstance(parent.parent, exp.Dot):
 9749                        parent.replace(dot_or_id)
 9750                        break
 9751                    parent = parent.parent
 9752                else:
 9753                    if column is node:
 9754                        node = dot_or_id
 9755                    else:
 9756                        column.replace(dot_or_id)
 9757        return node
 9758
 9759    def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr:
 9760        start = self._prev
 9761
 9762        # Not to be confused with TRUNCATE(number, decimals) function call
 9763        if self._match(TokenType.L_PAREN):
 9764            self._retreat(self._index - 2)
 9765            return self._parse_function()
 9766
 9767        # Clickhouse supports TRUNCATE DATABASE as well
 9768        is_database = self._match(TokenType.DATABASE)
 9769
 9770        self._match(TokenType.TABLE)
 9771
 9772        exists = self._parse_exists(not_=False)
 9773
 9774        expressions = self._parse_csv(
 9775            lambda: self._parse_table(schema=True, is_db_reference=is_database)
 9776        )
 9777
 9778        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9779
 9780        if self._match_text_seq("RESTART", "IDENTITY"):
 9781            identity = "RESTART"
 9782        elif self._match_text_seq("CONTINUE", "IDENTITY"):
 9783            identity = "CONTINUE"
 9784        else:
 9785            identity = None
 9786
 9787        if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"):
 9788            option = self._prev.text
 9789        else:
 9790            option = None
 9791
 9792        partition = self._parse_partition()
 9793
 9794        # Fallback case
 9795        if self._curr:
 9796            return self._parse_as_command(start)
 9797
 9798        return self.expression(
 9799            exp.TruncateTable(
 9800                expressions=expressions,
 9801                is_database=is_database,
 9802                exists=exists,
 9803                cluster=cluster,
 9804                identity=identity,
 9805                option=option,
 9806                partition=partition,
 9807            )
 9808        )
 9809
 9810    def _parse_indexed_column(self) -> exp.Expr | None:
 9811        return self._parse_ordered(self._parse_opclass)
 9812
 9813    def _parse_with_operator(self) -> exp.Expr | None:
 9814        this = self._parse_indexed_column()
 9815
 9816        if not self._match(TokenType.WITH):
 9817            return this
 9818
 9819        op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS)
 9820
 9821        return self.expression(exp.WithOperator(this=this, op=op))
 9822
 9823    def _parse_wrapped_options(self) -> list[exp.Expr]:
 9824        self._match(TokenType.EQ)
 9825        self._match(TokenType.L_PAREN)
 9826
 9827        opts: list[exp.Expr] = []
 9828        option: exp.Expr | list[exp.Expr] | None
 9829        while self._curr and not self._match(TokenType.R_PAREN):
 9830            if self._match_text_seq("FORMAT_NAME", "="):
 9831                # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL
 9832                option = self._parse_format_name()
 9833            else:
 9834                option = self._parse_property()
 9835
 9836            if option is None:
 9837                self.raise_error("Unable to parse option")
 9838                break
 9839
 9840            opts.extend(ensure_list(option))
 9841
 9842        return opts
 9843
 9844    def _parse_copy_parameters(self) -> list[exp.CopyParameter]:
 9845        sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None
 9846
 9847        options = []
 9848        while self._curr and not self._match(TokenType.R_PAREN, advance=False):
 9849            option = self._parse_var(any_token=True)
 9850            prev = self._prev.text.upper()
 9851
 9852            # Different dialects might separate options and values by white space, "=" and "AS"
 9853            self._match(TokenType.EQ)
 9854            self._match(TokenType.ALIAS)
 9855
 9856            param = self.expression(exp.CopyParameter(this=option))
 9857
 9858            if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match(
 9859                TokenType.L_PAREN, advance=False
 9860            ):
 9861                # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options
 9862                param.set("expressions", self._parse_wrapped_options())
 9863            elif prev == "FILE_FORMAT":
 9864                # T-SQL's external file format case
 9865                param.set("expression", self._parse_field())
 9866            elif (
 9867                prev == "FORMAT"
 9868                and self._prev.token_type == TokenType.ALIAS
 9869                and self._match_texts(("AVRO", "JSON"))
 9870            ):
 9871                param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}"))
 9872                param.set("expression", self._parse_field())
 9873            else:
 9874                param.set("expression", self._parse_unquoted_field() or self._parse_bracket())
 9875
 9876            options.append(param)
 9877
 9878            if sep:
 9879                self._match(sep)
 9880
 9881        return options
 9882
 9883    def _parse_credentials(self) -> exp.Credentials | None:
 9884        expr = self.expression(exp.Credentials())
 9885
 9886        if self._match_text_seq("STORAGE_INTEGRATION", "="):
 9887            expr.set("storage", self._parse_field())
 9888        if self._match_text_seq("CREDENTIALS"):
 9889            # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string>
 9890            creds = (
 9891                self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field()
 9892            )
 9893            expr.set("credentials", creds)
 9894        if self._match_text_seq("ENCRYPTION"):
 9895            expr.set("encryption", self._parse_wrapped_options())
 9896        if self._match_text_seq("IAM_ROLE"):
 9897            expr.set(
 9898                "iam_role",
 9899                exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(),
 9900            )
 9901        if self._match_text_seq("REGION"):
 9902            expr.set("region", self._parse_field())
 9903
 9904        return expr
 9905
 9906    def _parse_file_location(self) -> exp.Expr | None:
 9907        return self._parse_field()
 9908
 9909    def _parse_copy(self) -> exp.Copy | exp.Command:
 9910        start = self._prev
 9911
 9912        self._match(TokenType.INTO)
 9913
 9914        this = (
 9915            self._parse_select(nested=True, parse_subquery_alias=False)
 9916            if self._match(TokenType.L_PAREN, advance=False)
 9917            else self._parse_table(schema=True)
 9918        )
 9919
 9920        kind = self._match(TokenType.FROM) or not self._match_text_seq("TO")
 9921
 9922        files = self._parse_csv(self._parse_file_location)
 9923        if self._match(TokenType.EQ, advance=False):
 9924            # Backtrack one token since we've consumed the lhs of a parameter assignment here.
 9925            # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter
 9926            # list via `_parse_wrapped(..)` below.
 9927            self._advance(-1)
 9928            files = []
 9929
 9930        credentials = self._parse_credentials()
 9931
 9932        self._match_text_seq("WITH")
 9933
 9934        params = self._parse_wrapped(self._parse_copy_parameters, optional=True)
 9935
 9936        # Fallback case
 9937        if self._curr:
 9938            return self._parse_as_command(start)
 9939
 9940        return self.expression(
 9941            exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params)
 9942        )
 9943
 9944    def _parse_normalize(self) -> exp.Normalize:
 9945        return self.expression(
 9946            exp.Normalize(
 9947                this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var()
 9948            )
 9949        )
 9950
 9951    def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor:
 9952        args = self._parse_csv(lambda: self._parse_lambda())
 9953
 9954        this = seq_get(args, 0)
 9955        decimals = seq_get(args, 1)
 9956
 9957        return expr_type(
 9958            this=this,
 9959            decimals=decimals,
 9960            to=self._parse_var() if self._match_text_seq("TO") else None,
 9961        )
 9962
 9963    def _parse_star_ops(self) -> exp.Expr | None:
 9964        star_token = self._prev
 9965
 9966        if self._match_text_seq("COLUMNS", "(", advance=False):
 9967            this = self._parse_function()
 9968            if isinstance(this, exp.Columns):
 9969                this.set("unpack", True)
 9970            return this
 9971
 9972        index = self._index
 9973        ilike = self._parse_string() if self._match(TokenType.ILIKE) else None
 9974        if not ilike:
 9975            # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)`
 9976            self._retreat(index)
 9977
 9978        return self.expression(
 9979            exp.Star(
 9980                ilike=ilike,
 9981                except_=self._parse_star_op("EXCEPT", "EXCLUDE"),
 9982                replace=self._parse_star_op("REPLACE"),
 9983                rename=self._parse_star_op("RENAME"),
 9984            )
 9985        ).update_positions(star_token)
 9986
 9987    def _parse_grant_privilege(self) -> exp.GrantPrivilege | None:
 9988        privilege_parts = []
 9989
 9990        # Keep consuming consecutive keywords until comma (end of this privilege) or ON
 9991        # (end of privilege list) or L_PAREN (start of column list) are met
 9992        while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False):
 9993            privilege_parts.append(self._curr.text.upper())
 9994            self._advance()
 9995
 9996        this = exp.var(" ".join(privilege_parts))
 9997        expressions = (
 9998            self._parse_wrapped_csv(self._parse_column)
 9999            if self._match(TokenType.L_PAREN, advance=False)
10000            else None
10001        )
10002
10003        return self.expression(exp.GrantPrivilege(this=this, expressions=expressions))
10004
10005    def _parse_grant_principal(self) -> exp.GrantPrincipal | None:
10006        kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper()
10007        principal = self._parse_id_var()
10008
10009        if not principal:
10010            return None
10011
10012        return self.expression(exp.GrantPrincipal(this=principal, kind=kind))
10013
10014    def _parse_grant_revoke_common(
10015        self,
10016    ) -> tuple[list | None, str | None, exp.Expr | None]:
10017        privileges = self._parse_csv(self._parse_grant_privilege)
10018
10019        self._match(TokenType.ON)
10020        kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None
10021
10022        # Attempt to parse the securable e.g. MySQL allows names
10023        # such as "foo.*", "*.*" which are not easily parseable yet
10024        securable = self._try_parse(self._parse_table_parts)
10025
10026        return privileges, kind, securable
10027
10028    def _parse_grant(self) -> exp.Grant | exp.Command:
10029        start = self._prev
10030
10031        privileges, kind, securable = self._parse_grant_revoke_common()
10032
10033        if not securable or not self._match_text_seq("TO"):
10034            return self._parse_as_command(start)
10035
10036        principals = self._parse_csv(self._parse_grant_principal)
10037
10038        grant_option = self._match_text_seq("WITH", "GRANT", "OPTION")
10039
10040        if self._curr:
10041            return self._parse_as_command(start)
10042
10043        return self.expression(
10044            exp.Grant(
10045                privileges=privileges,
10046                kind=kind,
10047                securable=securable,
10048                principals=principals,
10049                grant_option=grant_option,
10050            )
10051        )
10052
10053    def _parse_revoke(self) -> exp.Revoke | exp.Command:
10054        start = self._prev
10055
10056        grant_option = self._match_text_seq("GRANT", "OPTION", "FOR")
10057
10058        privileges, kind, securable = self._parse_grant_revoke_common()
10059
10060        if not securable or not self._match_text_seq("FROM"):
10061            return self._parse_as_command(start)
10062
10063        principals = self._parse_csv(self._parse_grant_principal)
10064
10065        cascade = None
10066        if self._match_texts(("CASCADE", "RESTRICT")):
10067            cascade = self._prev.text.upper()
10068
10069        if self._curr:
10070            return self._parse_as_command(start)
10071
10072        return self.expression(
10073            exp.Revoke(
10074                privileges=privileges,
10075                kind=kind,
10076                securable=securable,
10077                principals=principals,
10078                grant_option=grant_option,
10079                cascade=cascade,
10080            )
10081        )
10082
10083    def _parse_overlay(self) -> exp.Overlay:
10084        def _parse_overlay_arg(text: str) -> exp.Expr | None:
10085            return (
10086                self._parse_bitwise()
10087                if self._match(TokenType.COMMA) or self._match_text_seq(text)
10088                else None
10089            )
10090
10091        return self.expression(
10092            exp.Overlay(
10093                this=self._parse_bitwise(),
10094                expression=_parse_overlay_arg("PLACING"),
10095                from_=_parse_overlay_arg("FROM"),
10096                for_=_parse_overlay_arg("FOR"),
10097            )
10098        )
10099
10100    def _parse_format_name(self) -> exp.Property:
10101        # Note: Although not specified in the docs, Snowflake does accept a string/identifier
10102        # for FILE_FORMAT = <format_name>
10103        return self.expression(
10104            exp.Property(
10105                this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts()
10106            )
10107        )
10108
10109    def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F:
10110        is_distinct = self._match(TokenType.DISTINCT)
10111        if not is_distinct:
10112            self._match(TokenType.ALL)
10113
10114        args = [self._parse_lambda()]
10115        if self._match(TokenType.COMMA):
10116            args.extend(self._parse_function_args())
10117
10118        target = seq_get(args, distinct_index)
10119        if is_distinct and target:
10120            args[distinct_index] = self.expression(exp.Distinct(expressions=[target]))
10121
10122        return func.from_arg_list(args)
10123
10124    def _identifier_expression(
10125        self, token: Token | None = None, quoted: bool | None = None
10126    ) -> exp.Identifier:
10127        token = token or self._prev
10128        return self.expression(exp.Identifier(this=token.text, quoted=quoted), token)
10129
10130    def _build_pipe_cte(
10131        self,
10132        query: exp.Query,
10133        expressions: list[exp.Expr],
10134        alias_cte: exp.TableAlias | None = None,
10135    ) -> exp.Select:
10136        new_cte: str | exp.TableAlias | None
10137        if alias_cte:
10138            new_cte = alias_cte
10139        else:
10140            self._pipe_cte_counter += 1
10141            new_cte = f"__tmp{self._pipe_cte_counter}"
10142
10143        with_ = query.args.get("with_")
10144        ctes = with_.pop() if with_ else None
10145
10146        new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False)
10147        if ctes:
10148            new_select.set("with_", ctes)
10149
10150        return new_select.with_(new_cte, as_=query, copy=False)
10151
10152    def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select:
10153        select = self._parse_select(consume_pipe=False)
10154        if not select:
10155            return query
10156
10157        return self._build_pipe_cte(
10158            query=query.select(*select.expressions, append=False), expressions=[exp.Star()]
10159        )
10160
10161    def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select:
10162        limit = self._parse_limit()
10163        offset = self._parse_offset()
10164        if limit:
10165            curr_limit = query.args.get("limit", limit)
10166            if curr_limit.expression.to_py() >= limit.expression.to_py():
10167                query.limit(limit, copy=False)
10168        if offset:
10169            curr_offset = query.args.get("offset")
10170            curr_offset = curr_offset.expression.to_py() if curr_offset else 0
10171            query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False)
10172
10173        return query
10174
10175    def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None:
10176        this = self._parse_disjunction()
10177        if self._match_text_seq("GROUP", "AND", advance=False):
10178            return this
10179
10180        this = self._parse_alias(this)
10181
10182        if self._match_set((TokenType.ASC, TokenType.DESC), advance=False):
10183            return self._parse_ordered(lambda: this)
10184
10185        return this
10186
10187    def _parse_pipe_syntax_aggregate_group_order_by(
10188        self, query: exp.Select, group_by_exists: bool = True
10189    ) -> exp.Select:
10190        expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields)
10191        aggregates_or_groups, orders = [], []
10192        for element in expr:
10193            if isinstance(element, exp.Ordered):
10194                this = element.this
10195                if isinstance(this, exp.Alias):
10196                    element.set("this", this.args["alias"])
10197                orders.append(element)
10198            else:
10199                this = element
10200            aggregates_or_groups.append(this)
10201
10202        if group_by_exists:
10203            query.select(
10204                *aggregates_or_groups, *query.expressions, append=False, copy=False
10205            ).group_by(
10206                *[projection.args.get("alias", projection) for projection in aggregates_or_groups],
10207                copy=False,
10208            )
10209        else:
10210            query.select(*aggregates_or_groups, append=False, copy=False)
10211
10212        if orders:
10213            return query.order_by(*orders, append=False, copy=False)
10214
10215        return query
10216
10217    def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select:
10218        self._match_text_seq("AGGREGATE")
10219        query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False)
10220
10221        if self._match(TokenType.GROUP_BY) or (
10222            self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY)
10223        ):
10224            query = self._parse_pipe_syntax_aggregate_group_order_by(query)
10225
10226        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10227
10228    def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None:
10229        first_setop = self.parse_set_operation(this=query)
10230        if not first_setop:
10231            return None
10232
10233        def _parse_and_unwrap_query() -> exp.Expr | None:
10234            expr = self._parse_paren()
10235            return expr.assert_is(exp.Subquery).unnest() if expr else None
10236
10237        first_setop.this.pop()
10238
10239        setops = [
10240            first_setop.expression.pop().assert_is(exp.Subquery).unnest(),
10241            *self._parse_csv(_parse_and_unwrap_query),
10242        ]
10243
10244        query = self._build_pipe_cte(query=query, expressions=[exp.Star()])
10245        with_ = query.args.get("with_")
10246        ctes = with_.pop() if with_ else None
10247
10248        if isinstance(first_setop, exp.Union):
10249            query = query.union(*setops, copy=False, **first_setop.args)
10250        elif isinstance(first_setop, exp.Except):
10251            query = query.except_(*setops, copy=False, **first_setop.args)
10252        else:
10253            query = query.intersect(*setops, copy=False, **first_setop.args)
10254
10255        query.set("with_", ctes)
10256
10257        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10258
10259    def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None:
10260        join = self._parse_join()
10261        if not join:
10262            return None
10263
10264        if isinstance(query, exp.Select):
10265            return query.join(join, copy=False)
10266
10267        return query
10268
10269    def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select:
10270        pivots = self._parse_pivots()
10271        if not pivots:
10272            return query
10273
10274        from_ = query.args.get("from_")
10275        if from_:
10276            from_.this.set("pivots", pivots)
10277
10278        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10279
10280    def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select:
10281        self._match_text_seq("EXTEND")
10282        query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False)
10283        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10284
10285    def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select:
10286        sample = self._parse_table_sample()
10287
10288        with_ = query.args.get("with_")
10289        if with_:
10290            with_.expressions[-1].this.set("sample", sample)
10291        else:
10292            query.set("sample", sample)
10293
10294        return query
10295
10296    def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None:
10297        if isinstance(query, exp.Subquery):
10298            query = exp.select("*").from_(query, copy=False)
10299
10300        if not query.args.get("from_"):
10301            query = exp.select("*").from_(query.subquery(copy=False), copy=False)
10302
10303        while self._match(TokenType.PIPE_GT):
10304            start_index = self._index
10305            start_text = self._curr.text.upper()
10306            parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text)
10307            if not parser:
10308                # The set operators (UNION, etc) and the JOIN operator have a few common starting
10309                # keywords, making it tricky to disambiguate them without lookahead. The approach
10310                # here is to try and parse a set operation and if that fails, then try to parse a
10311                # join operator. If that fails as well, then the operator is not supported.
10312                parsed_query = self._parse_pipe_syntax_set_operator(query)
10313                parsed_query = parsed_query or self._parse_pipe_syntax_join(query)
10314                if not parsed_query:
10315                    self._retreat(start_index)
10316                    self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.")
10317                    break
10318                query = parsed_query
10319            else:
10320                query = parser(self, query)
10321
10322        return query
10323
10324    def _parse_declareitem(self) -> exp.DeclareItem | None:
10325        self._match_texts(("VAR", "VARIABLE"))
10326
10327        vars = self._parse_csv(self._parse_id_var)
10328        if not vars:
10329            return None
10330
10331        self._match(TokenType.ALIAS)
10332        kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types()
10333        default = (
10334            self._match(TokenType.DEFAULT) or self._match(TokenType.EQ)
10335        ) and self._parse_bitwise()
10336
10337        return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default))
10338
10339    def _parse_declare(self) -> exp.Declare | exp.Command:
10340        start = self._prev
10341        replace = self._match_text_seq("OR", "REPLACE")
10342        expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem))
10343
10344        if not expressions or self._curr:
10345            return self._parse_as_command(start)
10346
10347        return self.expression(exp.Declare(expressions=expressions, replace=replace))
10348
10349    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10350        exp_class = exp.Cast if strict else exp.TryCast
10351
10352        if exp_class == exp.TryCast:
10353            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10354
10355        return self.expression(exp_class(**kwargs))
10356
10357    def _parse_json_value(self) -> exp.JSONValue:
10358        this = self._parse_bitwise()
10359        self._match(TokenType.COMMA)
10360        path = self._parse_bitwise()
10361
10362        returning = self._match(TokenType.RETURNING) and self._parse_type()
10363
10364        return self.expression(
10365            exp.JSONValue(
10366                this=this,
10367                path=self.dialect.to_json_path(path),
10368                returning=returning,
10369                on_condition=self._parse_on_condition(),
10370            )
10371        )
10372
10373    def _parse_group_concat(self) -> exp.Expr | None:
10374        def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr:
10375            if isinstance(node, exp.Distinct) and len(node.expressions) > 1:
10376                concat_exprs = [
10377                    self.expression(
10378                        exp.Concat(
10379                            expressions=node.expressions,
10380                            safe=True,
10381                            coalesce=self.dialect.CONCAT_COALESCE,
10382                        )
10383                    )
10384                ]
10385                node.set("expressions", concat_exprs)
10386                return node
10387            if len(exprs) == 1:
10388                return exprs[0]
10389            return self.expression(
10390                exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE)
10391            )
10392
10393        args = self._parse_csv(self._parse_lambda)
10394
10395        if args:
10396            order = args[-1] if isinstance(args[-1], exp.Order) else None
10397
10398            if order:
10399                # Order By is the last (or only) expression in the list and has consumed the 'expr' before it,
10400                # remove 'expr' from exp.Order and add it back to args
10401                args[-1] = order.this
10402                order.set("this", concat_exprs(order.this, args))
10403
10404            this = order or concat_exprs(args[0], args)
10405        else:
10406            this = None
10407
10408        separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None
10409
10410        return self.expression(exp.GroupConcat(this=this, separator=separator))
10411
10412    def _parse_initcap(self) -> exp.Initcap:
10413        expr = exp.Initcap.from_arg_list(self._parse_function_args())
10414
10415        # attach dialect's default delimiters
10416        if expr.args.get("expression") is None:
10417            expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS))
10418
10419        return expr
10420
10421    def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None:
10422        while True:
10423            if not self._match(TokenType.L_PAREN):
10424                break
10425
10426            op = ""
10427            while self._curr and not self._match(TokenType.R_PAREN):
10428                op += self._curr.text
10429                self._advance()
10430
10431            comments = self._prev_comments
10432            this = self.expression(
10433                exp.Operator(this=this, operator=op, expression=self._parse_bitwise()),
10434                comments=comments,
10435            )
10436
10437            if not self._match(TokenType.OPERATOR):
10438                break
10439
10440        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))
329def build_jsonb_contains(self: Parser, this: exp.Expr, key: exp.Expr) -> exp.JSONBContains:
330    return self.expression(exp.JSONBContains(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:
  336class Parser:
  337    """
  338    Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.
  339
  340    Args:
  341        error_level: The desired error level.
  342            Default: ErrorLevel.IMMEDIATE
  343        error_message_context: The amount of context to capture from a query string when displaying
  344            the error message (in number of characters).
  345            Default: 100
  346        max_errors: Maximum number of error messages to include in a raised ParseError.
  347            This is only relevant if error_level is ErrorLevel.RAISE.
  348            Default: 3
  349        max_nodes: Maximum number of AST nodes to prevent memory exhaustion.
  350            Set to -1 (default) to disable the check.
  351    """
  352
  353    __slots__ = (
  354        "error_level",
  355        "error_message_context",
  356        "max_errors",
  357        "max_nodes",
  358        "dialect",
  359        "sql",
  360        "errors",
  361        "_tokens",
  362        "_index",
  363        "_curr",
  364        "_next",
  365        "_prev",
  366        "_prev_comments",
  367        "_pipe_cte_counter",
  368        "_chunks",
  369        "_chunk_index",
  370        "_tokens_size",
  371        "_node_count",
  372    )
  373
  374    FUNCTIONS: t.ClassVar[dict[str, t.Callable]] = {
  375        **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()},
  376        **dict.fromkeys(("COALESCE", "IFNULL", "NVL"), build_coalesce),
  377        "ARRAY": lambda args, dialect: exp.Array(expressions=args),
  378        "ARRAYAGG": lambda args, dialect: exp.ArrayAgg(
  379            this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None
  380        ),
  381        "ARRAY_AGG": lambda args, dialect: exp.ArrayAgg(
  382            this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None
  383        ),
  384        "ARRAY_APPEND": build_array_append,
  385        "ARRAY_CAT": build_array_concat,
  386        "ARRAY_CONCAT": build_array_concat,
  387        "ARRAY_INTERSECT": lambda args: exp.ArrayIntersect(expressions=args),
  388        "ARRAY_INTERSECTION": lambda args: exp.ArrayIntersect(expressions=args),
  389        "ARRAY_PREPEND": build_array_prepend,
  390        "ARRAY_REMOVE": build_array_remove,
  391        "COUNT": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True),
  392        "CONCAT": lambda args, dialect: exp.Concat(
  393            expressions=args,
  394            safe=not dialect.STRICT_STRING_CONCAT,
  395            coalesce=dialect.CONCAT_COALESCE,
  396        ),
  397        "CONCAT_WS": lambda args, dialect: exp.ConcatWs(
  398            expressions=args,
  399            safe=not dialect.STRICT_STRING_CONCAT,
  400            coalesce=dialect.CONCAT_WS_COALESCE,
  401        ),
  402        "CONVERT_TIMEZONE": build_convert_timezone,
  403        "DATE_TO_DATE_STR": lambda args: exp.Cast(
  404            this=seq_get(args, 0),
  405            to=exp.DataType(this=exp.DType.TEXT),
  406        ),
  407        "GENERATE_DATE_ARRAY": lambda args: exp.GenerateDateArray(
  408            start=seq_get(args, 0),
  409            end=seq_get(args, 1),
  410            step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var("DAY")),
  411        ),
  412        "GENERATE_UUID": lambda args, dialect: exp.Uuid(
  413            is_string=dialect.UUID_IS_STRING_TYPE or None
  414        ),
  415        "GLOB": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)),
  416        "GREATEST": lambda args, dialect: exp.Greatest(
  417            this=seq_get(args, 0),
  418            expressions=args[1:],
  419            ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS,
  420        ),
  421        "LEAST": lambda args, dialect: exp.Least(
  422            this=seq_get(args, 0),
  423            expressions=args[1:],
  424            ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS,
  425        ),
  426        "HEX": build_hex,
  427        "JSON_EXTRACT": build_extract_json_with_path(exp.JSONExtract),
  428        "JSON_EXTRACT_SCALAR": build_extract_json_with_path(exp.JSONExtractScalar),
  429        "JSON_EXTRACT_PATH_TEXT": build_extract_json_with_path(exp.JSONExtractScalar),
  430        "JSON_KEYS": lambda args, dialect: exp.JSONKeys(
  431            this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1))
  432        ),
  433        "LIKE": build_like,
  434        "LOG": build_logarithm,
  435        "LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)),
  436        "LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)),
  437        "LOWER": build_lower,
  438        "LPAD": lambda args: build_pad(args),
  439        "LEFTPAD": lambda args: build_pad(args),
  440        "LTRIM": lambda args: build_trim(args),
  441        "MOD": build_mod,
  442        "RIGHTPAD": lambda args: build_pad(args, is_left=False),
  443        "RPAD": lambda args: build_pad(args, is_left=False),
  444        "RTRIM": lambda args: build_trim(args, is_left=False),
  445        "SCOPE_RESOLUTION": lambda args: (
  446            exp.ScopeResolution(expression=seq_get(args, 0))
  447            if len(args) != 2
  448            else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1))
  449        ),
  450        "STRPOS": exp.StrPosition.from_arg_list,
  451        "CHARINDEX": lambda args: build_locate_strposition(args),
  452        "INSTR": exp.StrPosition.from_arg_list,
  453        "LOCATE": lambda args: build_locate_strposition(args),
  454        "TIME_TO_TIME_STR": lambda args: exp.Cast(
  455            this=seq_get(args, 0),
  456            to=exp.DataType(this=exp.DType.TEXT),
  457        ),
  458        "TO_HEX": build_hex,
  459        "TS_OR_DS_TO_DATE_STR": lambda args: exp.Substring(
  460            this=exp.Cast(
  461                this=seq_get(args, 0),
  462                to=exp.DataType(this=exp.DType.TEXT),
  463            ),
  464            start=exp.Literal.number(1),
  465            length=exp.Literal.number(10),
  466        ),
  467        "UNNEST": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))),
  468        "UPPER": build_upper,
  469        "UUID": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None),
  470        "UUID_STRING": lambda args, dialect: exp.Uuid(
  471            this=seq_get(args, 0),
  472            name=seq_get(args, 1),
  473            is_string=dialect.UUID_IS_STRING_TYPE or None,
  474        ),
  475        "VAR_MAP": build_var_map,
  476    }
  477
  478    NO_PAREN_FUNCTIONS: t.ClassVar[dict] = {
  479        TokenType.CURRENT_DATE: exp.CurrentDate,
  480        TokenType.CURRENT_DATETIME: exp.CurrentDate,
  481        TokenType.CURRENT_TIME: exp.CurrentTime,
  482        TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp,
  483        TokenType.CURRENT_USER: exp.CurrentUser,
  484        TokenType.CURRENT_ROLE: exp.CurrentRole,
  485    }
  486
  487    STRUCT_TYPE_TOKENS: t.ClassVar = {
  488        TokenType.NESTED,
  489        TokenType.OBJECT,
  490        TokenType.STRUCT,
  491        TokenType.UNION,
  492    }
  493
  494    NESTED_TYPE_TOKENS: t.ClassVar = {
  495        TokenType.ARRAY,
  496        TokenType.LIST,
  497        TokenType.LOWCARDINALITY,
  498        TokenType.MAP,
  499        TokenType.NULLABLE,
  500        TokenType.RANGE,
  501        *STRUCT_TYPE_TOKENS,
  502    }
  503
  504    ENUM_TYPE_TOKENS: t.ClassVar = {
  505        TokenType.DYNAMIC,
  506        TokenType.ENUM,
  507        TokenType.ENUM8,
  508        TokenType.ENUM16,
  509    }
  510
  511    AGGREGATE_TYPE_TOKENS: t.ClassVar = {
  512        TokenType.AGGREGATEFUNCTION,
  513        TokenType.SIMPLEAGGREGATEFUNCTION,
  514    }
  515
  516    TYPE_TOKENS: t.ClassVar = {
  517        TokenType.BIT,
  518        TokenType.BOOLEAN,
  519        TokenType.TINYINT,
  520        TokenType.UTINYINT,
  521        TokenType.SMALLINT,
  522        TokenType.USMALLINT,
  523        TokenType.INT,
  524        TokenType.UINT,
  525        TokenType.BIGINT,
  526        TokenType.UBIGINT,
  527        TokenType.BIGNUM,
  528        TokenType.INT128,
  529        TokenType.UINT128,
  530        TokenType.INT256,
  531        TokenType.UINT256,
  532        TokenType.MEDIUMINT,
  533        TokenType.UMEDIUMINT,
  534        TokenType.FIXEDSTRING,
  535        TokenType.FLOAT,
  536        TokenType.DOUBLE,
  537        TokenType.UDOUBLE,
  538        TokenType.CHAR,
  539        TokenType.NCHAR,
  540        TokenType.VARCHAR,
  541        TokenType.NVARCHAR,
  542        TokenType.BPCHAR,
  543        TokenType.TEXT,
  544        TokenType.MEDIUMTEXT,
  545        TokenType.LONGTEXT,
  546        TokenType.BLOB,
  547        TokenType.MEDIUMBLOB,
  548        TokenType.LONGBLOB,
  549        TokenType.BINARY,
  550        TokenType.VARBINARY,
  551        TokenType.JSON,
  552        TokenType.JSONB,
  553        TokenType.INTERVAL,
  554        TokenType.TINYBLOB,
  555        TokenType.TINYTEXT,
  556        TokenType.TIME,
  557        TokenType.TIMETZ,
  558        TokenType.TIME_NS,
  559        TokenType.TIMESTAMP,
  560        TokenType.TIMESTAMP_S,
  561        TokenType.TIMESTAMP_MS,
  562        TokenType.TIMESTAMP_NS,
  563        TokenType.TIMESTAMPTZ,
  564        TokenType.TIMESTAMPLTZ,
  565        TokenType.TIMESTAMPNTZ,
  566        TokenType.DATETIME,
  567        TokenType.DATETIME2,
  568        TokenType.DATETIME64,
  569        TokenType.SMALLDATETIME,
  570        TokenType.DATE,
  571        TokenType.DATE32,
  572        TokenType.INT4RANGE,
  573        TokenType.INT4MULTIRANGE,
  574        TokenType.INT8RANGE,
  575        TokenType.INT8MULTIRANGE,
  576        TokenType.NUMRANGE,
  577        TokenType.NUMMULTIRANGE,
  578        TokenType.TSRANGE,
  579        TokenType.TSMULTIRANGE,
  580        TokenType.TSTZRANGE,
  581        TokenType.TSTZMULTIRANGE,
  582        TokenType.DATERANGE,
  583        TokenType.DATEMULTIRANGE,
  584        TokenType.DECIMAL,
  585        TokenType.DECIMAL32,
  586        TokenType.DECIMAL64,
  587        TokenType.DECIMAL128,
  588        TokenType.DECIMAL256,
  589        TokenType.DECFLOAT,
  590        TokenType.UDECIMAL,
  591        TokenType.BIGDECIMAL,
  592        TokenType.UUID,
  593        TokenType.GEOGRAPHY,
  594        TokenType.GEOGRAPHYPOINT,
  595        TokenType.GEOMETRY,
  596        TokenType.POINT,
  597        TokenType.RING,
  598        TokenType.LINESTRING,
  599        TokenType.MULTILINESTRING,
  600        TokenType.POLYGON,
  601        TokenType.MULTIPOLYGON,
  602        TokenType.HLLSKETCH,
  603        TokenType.HSTORE,
  604        TokenType.PSEUDO_TYPE,
  605        TokenType.SUPER,
  606        TokenType.SERIAL,
  607        TokenType.SMALLSERIAL,
  608        TokenType.BIGSERIAL,
  609        TokenType.XML,
  610        TokenType.YEAR,
  611        TokenType.USERDEFINED,
  612        TokenType.MONEY,
  613        TokenType.SMALLMONEY,
  614        TokenType.ROWVERSION,
  615        TokenType.IMAGE,
  616        TokenType.VARIANT,
  617        TokenType.VECTOR,
  618        TokenType.VOID,
  619        TokenType.OBJECT,
  620        TokenType.OBJECT_IDENTIFIER,
  621        TokenType.INET,
  622        TokenType.IPADDRESS,
  623        TokenType.IPPREFIX,
  624        TokenType.IPV4,
  625        TokenType.IPV6,
  626        TokenType.UNKNOWN,
  627        TokenType.NOTHING,
  628        TokenType.NULL,
  629        TokenType.NAME,
  630        TokenType.TDIGEST,
  631        TokenType.DYNAMIC,
  632        *ENUM_TYPE_TOKENS,
  633        *NESTED_TYPE_TOKENS,
  634        *AGGREGATE_TYPE_TOKENS,
  635    }
  636
  637    SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = {
  638        TokenType.BIGINT: TokenType.UBIGINT,
  639        TokenType.INT: TokenType.UINT,
  640        TokenType.MEDIUMINT: TokenType.UMEDIUMINT,
  641        TokenType.SMALLINT: TokenType.USMALLINT,
  642        TokenType.TINYINT: TokenType.UTINYINT,
  643        TokenType.DECIMAL: TokenType.UDECIMAL,
  644        TokenType.DOUBLE: TokenType.UDOUBLE,
  645    }
  646
  647    SUBQUERY_PREDICATES: t.ClassVar = {
  648        TokenType.ANY: exp.Any,
  649        TokenType.ALL: exp.All,
  650        TokenType.EXISTS: exp.Exists,
  651        TokenType.SOME: exp.Any,
  652    }
  653
  654    SUBQUERY_TOKENS: t.ClassVar = {
  655        TokenType.SELECT,
  656        TokenType.WITH,
  657        TokenType.FROM,
  658    }
  659
  660    RESERVED_TOKENS: t.ClassVar = {
  661        *Tokenizer.SINGLE_TOKENS.values(),
  662        TokenType.SELECT,
  663    } - {TokenType.IDENTIFIER}
  664
  665    # Tokens whose text is extracted from delimited source text (e.g. quoted identifiers,
  666    # string literals), so they must never be treated as keywords when matching by text
  667    TEXT_MATCH_EXCLUDED_TOKENS: t.ClassVar[frozenset] = frozenset(
  668        {
  669            TokenType.BIT_STRING,
  670            TokenType.BYTE_STRING,
  671            TokenType.HEREDOC_STRING,
  672            TokenType.HEX_STRING,
  673            TokenType.IDENTIFIER,
  674            TokenType.NATIONAL_STRING,
  675            TokenType.RAW_STRING,
  676            TokenType.STRING,
  677            TokenType.UNICODE_STRING,
  678        }
  679    )
  680
  681    DB_CREATABLES: t.ClassVar = {
  682        TokenType.DATABASE,
  683        TokenType.DICTIONARY,
  684        TokenType.FILE_FORMAT,
  685        TokenType.MODEL,
  686        TokenType.NAMESPACE,
  687        TokenType.SCHEMA,
  688        TokenType.SEMANTIC_VIEW,
  689        TokenType.SEQUENCE,
  690        TokenType.SINK,
  691        TokenType.SOURCE,
  692        TokenType.STAGE,
  693        TokenType.STORAGE_INTEGRATION,
  694        TokenType.STREAMLIT,
  695        TokenType.TABLE,
  696        TokenType.TAG,
  697        TokenType.VIEW,
  698        TokenType.WAREHOUSE,
  699    }
  700
  701    CREATABLES: t.ClassVar = {
  702        TokenType.COLUMN,
  703        TokenType.CONSTRAINT,
  704        TokenType.FOREIGN_KEY,
  705        TokenType.FUNCTION,
  706        TokenType.INDEX,
  707        TokenType.PROCEDURE,
  708        TokenType.TRIGGER,
  709        TokenType.TYPE,
  710        *DB_CREATABLES,
  711    }
  712
  713    TRIGGER_EVENTS: t.ClassVar = {
  714        TokenType.INSERT,
  715        TokenType.UPDATE,
  716        TokenType.DELETE,
  717        TokenType.TRUNCATE,
  718    }
  719
  720    ALTERABLES: t.ClassVar = {
  721        TokenType.INDEX,
  722        TokenType.TABLE,
  723        TokenType.VIEW,
  724        TokenType.SESSION,
  725    }
  726
  727    # Tokens that can represent identifiers
  728    ID_VAR_TOKENS: t.ClassVar[set] = {
  729        TokenType.ALL,
  730        TokenType.ANALYZE,
  731        TokenType.ATTACH,
  732        TokenType.VAR,
  733        TokenType.ANTI,
  734        TokenType.APPLY,
  735        TokenType.ASC,
  736        TokenType.ASOF,
  737        TokenType.AUTO_INCREMENT,
  738        TokenType.BEGIN,
  739        TokenType.BPCHAR,
  740        TokenType.CACHE,
  741        TokenType.CASE,
  742        TokenType.COLLATE,
  743        TokenType.COMMAND,
  744        TokenType.COMMENT,
  745        TokenType.COMMIT,
  746        TokenType.CONSTRAINT,
  747        TokenType.COPY,
  748        TokenType.CUBE,
  749        TokenType.CURRENT_SCHEMA,
  750        TokenType.DECLARE,
  751        TokenType.DEFAULT,
  752        TokenType.DELETE,
  753        TokenType.DESC,
  754        TokenType.DESCRIBE,
  755        TokenType.DETACH,
  756        TokenType.DICTIONARY,
  757        TokenType.DIV,
  758        TokenType.END,
  759        TokenType.EXECUTE,
  760        TokenType.EXPORT,
  761        TokenType.ESCAPE,
  762        TokenType.FALSE,
  763        TokenType.FIRST,
  764        TokenType.FILE,
  765        TokenType.FILTER,
  766        TokenType.FINAL,
  767        TokenType.FORMAT,
  768        TokenType.FULL,
  769        TokenType.GET,
  770        TokenType.IDENTIFIER,
  771        TokenType.INOUT,
  772        TokenType.IS,
  773        TokenType.ISNULL,
  774        TokenType.INTERVAL,
  775        TokenType.KEEP,
  776        TokenType.KILL,
  777        TokenType.LEFT,
  778        TokenType.LIMIT,
  779        TokenType.LOAD,
  780        TokenType.LOCK,
  781        TokenType.MATCH,
  782        TokenType.MERGE,
  783        TokenType.NATURAL,
  784        TokenType.NEXT,
  785        TokenType.OFFSET,
  786        TokenType.OPERATOR,
  787        TokenType.ORDINALITY,
  788        TokenType.OUT,
  789        TokenType.OVER,
  790        TokenType.OVERLAPS,
  791        TokenType.OVERWRITE,
  792        TokenType.PARTITION,
  793        TokenType.PERCENT,
  794        TokenType.PIVOT,
  795        TokenType.PROJECTION,
  796        TokenType.PRAGMA,
  797        TokenType.PUT,
  798        TokenType.RANGE,
  799        TokenType.RECURSIVE,
  800        TokenType.REFERENCES,
  801        TokenType.REFRESH,
  802        TokenType.RENAME,
  803        TokenType.REPLACE,
  804        TokenType.RIGHT,
  805        TokenType.ROLLUP,
  806        TokenType.ROW,
  807        TokenType.ROWS,
  808        TokenType.SEMI,
  809        TokenType.SET,
  810        TokenType.SETTINGS,
  811        TokenType.SHOW,
  812        TokenType.STREAM,
  813        TokenType.STREAMLIT,
  814        TokenType.TEMPORARY,
  815        TokenType.TOP,
  816        TokenType.TRUE,
  817        TokenType.TRUNCATE,
  818        TokenType.UNIQUE,
  819        TokenType.UNNEST,
  820        TokenType.UNPIVOT,
  821        TokenType.UPDATE,
  822        TokenType.USE,
  823        TokenType.VOLATILE,
  824        TokenType.WINDOW,
  825        TokenType.CURRENT_CATALOG,
  826        TokenType.LOCALTIME,
  827        TokenType.LOCALTIMESTAMP,
  828        TokenType.SESSION_USER,
  829        TokenType.STRAIGHT_JOIN,
  830        *ALTERABLES,
  831        *CREATABLES,
  832        *SUBQUERY_PREDICATES,
  833        *TYPE_TOKENS,
  834        *NO_PAREN_FUNCTIONS,
  835    } - {TokenType.UNION}
  836
  837    TABLE_ALIAS_TOKENS: t.ClassVar[set] = ID_VAR_TOKENS - {
  838        TokenType.ANTI,
  839        TokenType.ASOF,
  840        TokenType.FULL,
  841        TokenType.LEFT,
  842        TokenType.LOCK,
  843        TokenType.NATURAL,
  844        TokenType.RIGHT,
  845        TokenType.SEMI,
  846        TokenType.WINDOW,
  847    }
  848
  849    ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS
  850
  851    COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS
  852
  853    ARRAY_CONSTRUCTORS: t.ClassVar = {
  854        "ARRAY": exp.Array,
  855        "LIST": exp.List,
  856    }
  857
  858    COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS}
  859
  860    UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET}
  861
  862    TRIM_TYPES: t.ClassVar = {"LEADING", "TRAILING", "BOTH"}
  863
  864    # Tokens that indicate a simple column reference
  865    IDENTIFIER_TOKENS: t.ClassVar[frozenset] = frozenset({TokenType.VAR, TokenType.IDENTIFIER})
  866
  867    BRACKETS: t.ClassVar[frozenset] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE})
  868
  869    # Postfix tokens that prevent the bare column fast path
  870    COLUMN_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  871        {
  872            TokenType.L_PAREN,
  873            TokenType.L_BRACKET,
  874            TokenType.L_BRACE,
  875            TokenType.COLON,
  876            TokenType.JOIN_MARKER,
  877        }
  878    )
  879
  880    TABLE_POSTFIX_TOKENS: t.ClassVar[frozenset] = frozenset(
  881        {
  882            TokenType.L_PAREN,
  883            TokenType.L_BRACKET,
  884            TokenType.L_BRACE,
  885            TokenType.PIVOT,
  886            TokenType.UNPIVOT,
  887            TokenType.TABLE_SAMPLE,
  888        }
  889    )
  890
  891    FUNC_TOKENS: t.ClassVar = {
  892        TokenType.COLLATE,
  893        TokenType.COMMAND,
  894        TokenType.CURRENT_DATE,
  895        TokenType.CURRENT_DATETIME,
  896        TokenType.CURRENT_SCHEMA,
  897        TokenType.CURRENT_TIMESTAMP,
  898        TokenType.CURRENT_TIME,
  899        TokenType.CURRENT_USER,
  900        TokenType.CURRENT_CATALOG,
  901        TokenType.DECLARE,
  902        TokenType.FILTER,
  903        TokenType.FIRST,
  904        TokenType.FORMAT,
  905        TokenType.GET,
  906        TokenType.GLOB,
  907        TokenType.IDENTIFIER,
  908        TokenType.INDEX,
  909        TokenType.ISNULL,
  910        TokenType.ILIKE,
  911        TokenType.INSERT,
  912        TokenType.LIKE,
  913        TokenType.LOCALTIME,
  914        TokenType.LOCALTIMESTAMP,
  915        TokenType.MERGE,
  916        TokenType.NEXT,
  917        TokenType.OFFSET,
  918        TokenType.PRIMARY_KEY,
  919        TokenType.RANGE,
  920        TokenType.REPLACE,
  921        TokenType.RLIKE,
  922        TokenType.ROW,
  923        TokenType.SESSION_USER,
  924        TokenType.UNNEST,
  925        TokenType.VAR,
  926        TokenType.LEFT,
  927        TokenType.RIGHT,
  928        TokenType.SEQUENCE,
  929        TokenType.DATE,
  930        TokenType.DATETIME,
  931        TokenType.TABLE,
  932        TokenType.TIMESTAMP,
  933        TokenType.TIMESTAMPTZ,
  934        TokenType.TRUNCATE,
  935        TokenType.UTC_DATE,
  936        TokenType.UTC_TIME,
  937        TokenType.UTC_TIMESTAMP,
  938        TokenType.WINDOW,
  939        TokenType.XOR,
  940        *TYPE_TOKENS,
  941        *SUBQUERY_PREDICATES,
  942    }
  943
  944    CONJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  945        TokenType.AND: exp.And,
  946    }
  947
  948    ASSIGNMENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  949        TokenType.COLON_EQ: exp.PropertyEQ,
  950    }
  951
  952    DISJUNCTION: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {
  953        TokenType.OR: exp.Or,
  954    }
  955
  956    EQUALITY: t.ClassVar = {
  957        TokenType.EQ: exp.EQ,
  958        TokenType.NEQ: exp.NEQ,
  959        TokenType.NULLSAFE_EQ: exp.NullSafeEQ,
  960    }
  961
  962    COMPARISON: t.ClassVar = {
  963        TokenType.GT: exp.GT,
  964        TokenType.GTE: exp.GTE,
  965        TokenType.LT: exp.LT,
  966        TokenType.LTE: exp.LTE,
  967    }
  968
  969    BITWISE: t.ClassVar = {
  970        TokenType.AMP: exp.BitwiseAnd,
  971        TokenType.CARET: exp.BitwiseXor,
  972        TokenType.PIPE: exp.BitwiseOr,
  973    }
  974
  975    TERM: t.ClassVar = {
  976        TokenType.DASH: exp.Sub,
  977        TokenType.PLUS: exp.Add,
  978        TokenType.MOD: exp.Mod,
  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.SLASH: exp.Div,
  987        TokenType.STAR: exp.Mul,
  988    }
  989
  990    EXPONENT: t.ClassVar[dict[TokenType, type[exp.Expr]]] = {}
  991
  992    TIMES: t.ClassVar = {
  993        TokenType.TIME,
  994        TokenType.TIMETZ,
  995    }
  996
  997    TIMESTAMPS: t.ClassVar = {
  998        TokenType.TIMESTAMP,
  999        TokenType.TIMESTAMPNTZ,
 1000        TokenType.TIMESTAMPTZ,
 1001        TokenType.TIMESTAMPLTZ,
 1002        *TIMES,
 1003    }
 1004
 1005    SET_OPERATIONS: t.ClassVar = {
 1006        TokenType.UNION,
 1007        TokenType.INTERSECT,
 1008        TokenType.EXCEPT,
 1009    }
 1010
 1011    JOIN_METHODS: t.ClassVar = {
 1012        TokenType.ASOF,
 1013        TokenType.NATURAL,
 1014        TokenType.POSITIONAL,
 1015    }
 1016
 1017    JOIN_SIDES: t.ClassVar = {
 1018        TokenType.LEFT,
 1019        TokenType.RIGHT,
 1020        TokenType.FULL,
 1021    }
 1022
 1023    JOIN_KINDS: t.ClassVar = {
 1024        TokenType.ANTI,
 1025        TokenType.CROSS,
 1026        TokenType.INNER,
 1027        TokenType.OUTER,
 1028        TokenType.SEMI,
 1029        TokenType.STRAIGHT_JOIN,
 1030    }
 1031
 1032    JOIN_HINTS: t.ClassVar[set[str]] = set()
 1033
 1034    # Tokens that unambiguously end a table reference on the fast path
 1035    TABLE_TERMINATORS: t.ClassVar[frozenset] = frozenset(
 1036        {
 1037            TokenType.COMMA,
 1038            TokenType.GROUP_BY,
 1039            TokenType.HAVING,
 1040            TokenType.JOIN,
 1041            TokenType.LIMIT,
 1042            TokenType.ON,
 1043            TokenType.ORDER_BY,
 1044            TokenType.R_PAREN,
 1045            TokenType.SEMICOLON,
 1046            TokenType.SENTINEL,
 1047            TokenType.WHERE,
 1048            *SET_OPERATIONS,
 1049            *JOIN_KINDS,
 1050            *JOIN_METHODS,
 1051            *JOIN_SIDES,
 1052        }
 1053    )
 1054
 1055    LAMBDAS: t.ClassVar = {
 1056        TokenType.ARROW: lambda self, expressions: self.expression(
 1057            exp.Lambda(
 1058                this=self._replace_lambda(
 1059                    self._parse_disjunction(),
 1060                    expressions,
 1061                ),
 1062                expressions=expressions,
 1063            )
 1064        ),
 1065        TokenType.FARROW: lambda self, expressions: self.expression(
 1066            exp.Kwarg(
 1067                this=exp.var(expressions[0].name),
 1068                expression=self._parse_disjunction() or self._parse_select(),
 1069            )
 1070        ),
 1071    }
 1072
 1073    # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake
 1074    TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False
 1075
 1076    LAMBDA_ARG_TERMINATORS: t.ClassVar[frozenset] = frozenset({TokenType.COMMA, TokenType.R_PAREN})
 1077
 1078    COLUMN_OPERATORS: t.ClassVar = {
 1079        TokenType.DOT: None,
 1080        TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)),
 1081        TokenType.DCOLON: lambda self, this, to: self.build_cast(
 1082            strict=self.STRICT_CAST, this=this, to=to
 1083        ),
 1084        TokenType.ARROW: lambda self, this, path: self.expression(
 1085            exp.JSONExtract(
 1086                this=this,
 1087                expression=self.dialect.to_json_path(path),
 1088                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1089            )
 1090        ),
 1091        TokenType.DARROW: lambda self, this, path: self.expression(
 1092            exp.JSONExtractScalar(
 1093                this=this,
 1094                expression=self.dialect.to_json_path(path),
 1095                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,
 1096                scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY,
 1097            )
 1098        ),
 1099        TokenType.HASH_ARROW: lambda self, this, path: self.expression(
 1100            exp.JSONBExtract(this=this, expression=path)
 1101        ),
 1102        TokenType.DHASH_ARROW: lambda self, this, path: self.expression(
 1103            exp.JSONBExtractScalar(this=this, expression=path)
 1104        ),
 1105        TokenType.PLACEHOLDER: lambda self, this, key: self.expression(
 1106            exp.JSONBContains(this=this, expression=key)
 1107        ),
 1108    }
 1109
 1110    # JSON/JSONB operators (extraction and containment) at Postgres's "any other operator"
 1111    # tier, below +/-, level with ||. Same value signature as COLUMN_OPERATORS: (self, this, rhs).
 1112    JSON_OPERATORS: t.ClassVar[dict[TokenType, t.Callable]] = {}
 1113
 1114    CAST_COLUMN_OPERATORS: t.ClassVar = {
 1115        TokenType.DOTCOLON,
 1116        TokenType.DCOLON,
 1117    }
 1118
 1119    EXPRESSION_PARSERS: t.ClassVar = {
 1120        exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY),
 1121        exp.Column: lambda self: self._parse_column(),
 1122        exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()),
 1123        exp.Condition: lambda self: self._parse_disjunction(),
 1124        exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True),
 1125        exp.Expr: lambda self: self._parse_expression(),
 1126        exp.From: lambda self: self._parse_from(joins=True),
 1127        exp.GrantPrincipal: lambda self: self._parse_grant_principal(),
 1128        exp.GrantPrivilege: lambda self: self._parse_grant_privilege(),
 1129        exp.Group: lambda self: self._parse_group(),
 1130        exp.Having: lambda self: self._parse_having(),
 1131        exp.Hint: lambda self: self._parse_hint_body(),
 1132        exp.Identifier: lambda self: self._parse_id_var(),
 1133        exp.Join: lambda self: self._parse_join(),
 1134        exp.Lambda: lambda self: self._parse_lambda(),
 1135        exp.Lateral: lambda self: self._parse_lateral(),
 1136        exp.Limit: lambda self: self._parse_limit(),
 1137        exp.Offset: lambda self: self._parse_offset(),
 1138        exp.Order: lambda self: self._parse_order(),
 1139        exp.Ordered: lambda self: self._parse_ordered(),
 1140        exp.Properties: lambda self: self._parse_properties(),
 1141        exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(),
 1142        exp.Qualify: lambda self: self._parse_qualify(),
 1143        exp.Returning: lambda self: self._parse_returning(),
 1144        exp.Select: lambda self: self._parse_select(),
 1145        exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY),
 1146        exp.Table: lambda self: self._parse_table_parts(),
 1147        exp.TableAlias: lambda self: self._parse_table_alias(),
 1148        exp.Tuple: lambda self: self._parse_value(values=False),
 1149        exp.Whens: lambda self: self._parse_when_matched(),
 1150        exp.Where: lambda self: self._parse_where(),
 1151        exp.Window: lambda self: self._parse_named_window(),
 1152        exp.With: lambda self: self._parse_with(),
 1153    }
 1154
 1155    STATEMENT_PARSERS: t.ClassVar = {
 1156        TokenType.ALTER: lambda self: self._parse_alter(),
 1157        TokenType.ANALYZE: lambda self: self._parse_analyze(),
 1158        TokenType.BEGIN: lambda self: self._parse_transaction(),
 1159        TokenType.CACHE: lambda self: self._parse_cache(),
 1160        TokenType.COMMENT: lambda self: self._parse_comment(),
 1161        TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(),
 1162        TokenType.COPY: lambda self: self._parse_copy(),
 1163        TokenType.CREATE: lambda self: self._parse_create(),
 1164        TokenType.DECLARE: lambda self: self._parse_declare(),
 1165        TokenType.DELETE: lambda self: self._parse_delete(),
 1166        TokenType.DESC: lambda self: self._parse_describe(),
 1167        TokenType.DESCRIBE: lambda self: self._parse_describe(),
 1168        TokenType.DROP: lambda self: self._parse_drop(),
 1169        TokenType.GRANT: lambda self: self._parse_grant(),
 1170        TokenType.REVOKE: lambda self: self._parse_revoke(),
 1171        TokenType.INSERT: lambda self: self._parse_insert(),
 1172        TokenType.KILL: lambda self: self._parse_kill(),
 1173        TokenType.LOAD: lambda self: self._parse_load(),
 1174        TokenType.MERGE: lambda self: self._parse_merge(),
 1175        TokenType.PIVOT: lambda self: self._parse_simplified_pivot(),
 1176        TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())),
 1177        TokenType.REFRESH: lambda self: self._parse_refresh(),
 1178        TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(),
 1179        TokenType.SET: lambda self: self._parse_set(),
 1180        TokenType.TRUNCATE: lambda self: self._parse_truncate_table(),
 1181        TokenType.UNCACHE: lambda self: self._parse_uncache(),
 1182        TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True),
 1183        TokenType.UPDATE: lambda self: self._parse_update(),
 1184        TokenType.USE: lambda self: self._parse_use(),
 1185        TokenType.SEMICOLON: lambda self: exp.Semicolon(),
 1186    }
 1187
 1188    UNARY_PARSERS: t.ClassVar = {
 1189        TokenType.PLUS: lambda self: self._parse_unary(),  # Unary + is handled as a no-op
 1190        TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())),
 1191        TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())),
 1192        TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())),
 1193        TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())),
 1194        TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())),
 1195    }
 1196
 1197    STRING_PARSERS: t.ClassVar = {
 1198        TokenType.HEREDOC_STRING: lambda self, token: self.expression(
 1199            exp.RawString(this=token.text), token
 1200        ),
 1201        TokenType.NATIONAL_STRING: lambda self, token: self.expression(
 1202            exp.National(this=token.text), token
 1203        ),
 1204        TokenType.RAW_STRING: lambda self, token: self.expression(
 1205            exp.RawString(this=token.text), token
 1206        ),
 1207        TokenType.STRING: lambda self, token: self.expression(
 1208            exp.Literal(this=token.text, is_string=True), token
 1209        ),
 1210        TokenType.UNICODE_STRING: lambda self, token: self.expression(
 1211            exp.UnicodeString(
 1212                this=token.text, escape=self._match_text_seq("UESCAPE") and self._parse_string()
 1213            ),
 1214            token,
 1215        ),
 1216    }
 1217
 1218    NUMERIC_PARSERS: t.ClassVar = {
 1219        TokenType.BIT_STRING: lambda self, token: self.expression(
 1220            exp.BitString(this=token.text), token
 1221        ),
 1222        TokenType.BYTE_STRING: lambda self, token: self.expression(
 1223            exp.ByteString(
 1224                this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None
 1225            ),
 1226            token,
 1227        ),
 1228        TokenType.HEX_STRING: lambda self, token: self.expression(
 1229            exp.HexString(
 1230                this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None
 1231            ),
 1232            token,
 1233        ),
 1234        TokenType.NUMBER: lambda self, token: self.expression(
 1235            exp.Literal(this=token.text, is_string=False), token
 1236        ),
 1237    }
 1238
 1239    PRIMARY_PARSERS: t.ClassVar = {
 1240        **STRING_PARSERS,
 1241        **NUMERIC_PARSERS,
 1242        TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token),
 1243        TokenType.NULL: lambda self, _: self.expression(exp.Null()),
 1244        TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)),
 1245        TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)),
 1246        TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(),
 1247        TokenType.STAR: lambda self, _: self._parse_star_ops(),
 1248    }
 1249
 1250    PLACEHOLDER_PARSERS: t.ClassVar = {
 1251        TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()),
 1252        TokenType.PARAMETER: lambda self: self._parse_parameter(),
 1253        TokenType.COLON: lambda self: (
 1254            self.expression(exp.Placeholder(this=self._prev.text))
 1255            if self._match_set(self.COLON_PLACEHOLDER_TOKENS)
 1256            else None
 1257        ),
 1258    }
 1259
 1260    RANGE_PARSERS: t.ClassVar = {
 1261        TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll),
 1262        TokenType.BETWEEN: lambda self, this: self._parse_between(this),
 1263        TokenType.GLOB: binary_range_parser(exp.Glob),
 1264        TokenType.ILIKE: binary_range_parser(exp.ILike),
 1265        TokenType.IN: lambda self, this: self._parse_in(this),
 1266        TokenType.IRLIKE: binary_range_parser(exp.RegexpILike),
 1267        TokenType.IS: lambda self, this: self._parse_is(this),
 1268        TokenType.LIKE: binary_range_parser(exp.Like),
 1269        TokenType.LT_AT: binary_range_parser(exp.ArrayContainedBy),
 1270        TokenType.OVERLAPS: binary_range_parser(exp.Overlaps),
 1271        TokenType.RLIKE: binary_range_parser(exp.RegexpLike),
 1272        TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo),
 1273        TokenType.FOR: lambda self, this: self._parse_comprehension(this),
 1274        TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys),
 1275        TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys),
 1276        TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath),
 1277        TokenType.AT_QMARK: binary_range_parser(exp.JSONBPathExists),
 1278        TokenType.ADJACENT: binary_range_parser(exp.Adjacent),
 1279        TokenType.OPERATOR: lambda self, this: self._parse_operator(this),
 1280        TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft),
 1281        TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight),
 1282    }
 1283
 1284    PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = {
 1285        "AGGREGATE": lambda self, query: self._parse_pipe_syntax_aggregate(query),
 1286        "AS": lambda self, query: self._build_pipe_cte(
 1287            query, [exp.Star()], self._parse_table_alias()
 1288        ),
 1289        "DISTINCT": lambda self, query: self._advance() or query.distinct(copy=False),
 1290        "EXTEND": lambda self, query: self._parse_pipe_syntax_extend(query),
 1291        "LIMIT": lambda self, query: self._parse_pipe_syntax_limit(query),
 1292        "ORDER BY": lambda self, query: query.order_by(
 1293            self._parse_order(), append=False, copy=False
 1294        ),
 1295        "PIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1296        "SELECT": lambda self, query: self._parse_pipe_syntax_select(query),
 1297        "TABLESAMPLE": lambda self, query: self._parse_pipe_syntax_tablesample(query),
 1298        "UNPIVOT": lambda self, query: self._parse_pipe_syntax_pivot(query),
 1299        "WHERE": lambda self, query: query.where(self._parse_where(), copy=False),
 1300    }
 1301
 1302    PROPERTY_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1303        "ALLOWED_VALUES": lambda self: self.expression(
 1304            exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary))
 1305        ),
 1306        "ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty),
 1307        "AUTO": lambda self: self._parse_auto_property(),
 1308        "AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty),
 1309        "BACKUP": lambda self: self.expression(
 1310            exp.BackupProperty(this=self._parse_var(any_token=True))
 1311        ),
 1312        "BLOCKCOMPRESSION": lambda self: self._parse_blockcompression(),
 1313        "CALLED": lambda self: self._parse_called_on_null_input_property(),
 1314        "CHARSET": lambda self, **kwargs: self._parse_character_set(**kwargs),
 1315        "CHECKSUM": lambda self: self._parse_checksum(),
 1316        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1317        "CLUSTERED": lambda self: self._parse_clustered_by(),
 1318        "COLLATE": lambda self, **kwargs: self._parse_property_assignment(
 1319            exp.CollateProperty, **kwargs
 1320        ),
 1321        "COMMENT": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty),
 1322        "CONTAINS": lambda self: self._parse_contains_property(),
 1323        "COPY": lambda self: self._parse_copy_property(),
 1324        "DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs),
 1325        "DATA_DELETION": lambda self: self._parse_data_deletion_property(),
 1326        "DEFINER": lambda self: self._parse_definer(),
 1327        "DETERMINISTIC": lambda self: self.expression(
 1328            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1329        ),
 1330        "DISTRIBUTED": lambda self: self._parse_distributed_property(),
 1331        "DUPLICATE": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty),
 1332        "DYNAMIC": lambda self: self.expression(exp.DynamicProperty()),
 1333        "DISTKEY": lambda self: self._parse_distkey(),
 1334        "DISTSTYLE": lambda self: self._parse_property_assignment(exp.DistStyleProperty),
 1335        "EMPTY": lambda self: self.expression(exp.EmptyProperty()),
 1336        "ENGINE": lambda self: self._parse_property_assignment(exp.EngineProperty),
 1337        "ENVIRONMENT": lambda self: self.expression(
 1338            exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment))
 1339        ),
 1340        "HANDLER": lambda self: self._parse_property_assignment(exp.HandlerProperty),
 1341        "EXECUTE": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty),
 1342        "EXTERNAL": lambda self: self.expression(exp.ExternalProperty()),
 1343        "FALLBACK": lambda self, **kwargs: self._parse_fallback(**kwargs),
 1344        "FORMAT": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1345        "FREESPACE": lambda self: self._parse_freespace(),
 1346        "GLOBAL": lambda self: self.expression(exp.GlobalProperty()),
 1347        "HEAP": lambda self: self.expression(exp.HeapProperty()),
 1348        "ICEBERG": lambda self: self.expression(exp.IcebergProperty()),
 1349        "IMMUTABLE": lambda self: self.expression(
 1350            exp.StabilityProperty(this=exp.Literal.string("IMMUTABLE"))
 1351        ),
 1352        "INHERITS": lambda self: self.expression(
 1353            exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table))
 1354        ),
 1355        "INPUT": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())),
 1356        "JOURNAL": lambda self, **kwargs: self._parse_journal(**kwargs),
 1357        "LANGUAGE": lambda self: self._parse_property_assignment(exp.LanguageProperty),
 1358        "LAYOUT": lambda self: self._parse_dict_property(this="LAYOUT"),
 1359        "LIFETIME": lambda self: self._parse_dict_range(this="LIFETIME"),
 1360        "LIKE": lambda self: self._parse_create_like(),
 1361        "LOCATION": lambda self: self._parse_property_assignment(exp.LocationProperty),
 1362        "LOCK": lambda self: self._parse_locking(),
 1363        "LOCKING": lambda self: self._parse_locking(),
 1364        "LOG": lambda self, **kwargs: self._parse_log(**kwargs),
 1365        "MATERIALIZED": lambda self: self.expression(exp.MaterializedProperty()),
 1366        "MERGEBLOCKRATIO": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs),
 1367        "MODIFIES": lambda self: self._parse_modifies_property(),
 1368        "MULTISET": lambda self: self.expression(exp.SetProperty(multi=True)),
 1369        "NO": lambda self: self._parse_no_property(),
 1370        "ON": lambda self: self._parse_on_property(),
 1371        "ORDER BY": lambda self: self._parse_order(skip_order_token=True),
 1372        "OUTPUT": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())),
 1373        "PARTITION": lambda self: self._parse_partitioned_of(),
 1374        "PARTITION BY": lambda self: self._parse_partitioned_by(),
 1375        "PARTITIONED BY": lambda self: self._parse_partitioned_by(),
 1376        "PARTITIONED_BY": lambda self: self._parse_partitioned_by(),
 1377        "PRIMARY KEY": lambda self: self._parse_primary_key(in_props=True),
 1378        "RANGE": lambda self: self._parse_dict_range(this="RANGE"),
 1379        "READS": lambda self: self._parse_reads_property(),
 1380        "REMOTE": lambda self: self._parse_remote_with_connection(),
 1381        "RETURNS": lambda self: self._parse_returns(),
 1382        "STRICT": lambda self: self.expression(exp.StrictProperty()),
 1383        "STREAMING": lambda self: self.expression(exp.StreamingTableProperty()),
 1384        "ROW": lambda self: self._parse_row(),
 1385        "ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty),
 1386        "SAMPLE": lambda self: self.expression(
 1387            exp.SampleProperty(this=self._match_text_seq("BY") and self._parse_bitwise())
 1388        ),
 1389        "SECURE": lambda self: self.expression(exp.SecureProperty()),
 1390        "SECURITY": lambda self: self._parse_sql_security(),
 1391        "SQL SECURITY": lambda self: self._parse_sql_security(),
 1392        "SET": lambda self: self.expression(exp.SetProperty(multi=False)),
 1393        "SETTINGS": lambda self: self._parse_settings_property(),
 1394        "SHARING": lambda self: self._parse_property_assignment(exp.SharingProperty),
 1395        "SORTKEY": lambda self: self._parse_sortkey(),
 1396        "SOURCE": lambda self: self._parse_dict_property(this="SOURCE"),
 1397        "STABLE": lambda self: self.expression(
 1398            exp.StabilityProperty(this=exp.Literal.string("STABLE"))
 1399        ),
 1400        "STORED": lambda self: self._parse_stored(),
 1401        "SYSTEM_VERSIONING": lambda self: self._parse_system_versioning_property(),
 1402        "TBLPROPERTIES": lambda self: self._parse_wrapped_properties(),
 1403        "TEMP": lambda self: self.expression(exp.TemporaryProperty()),
 1404        "TEMPORARY": lambda self: self.expression(exp.TemporaryProperty()),
 1405        "TO": lambda self: self._parse_to_table(),
 1406        "TRANSIENT": lambda self: self.expression(exp.TransientProperty()),
 1407        "TRANSFORM": lambda self: self.expression(
 1408            exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression))
 1409        ),
 1410        "TTL": lambda self: self._parse_ttl(),
 1411        "USING": lambda self: self._parse_property_assignment(exp.FileFormatProperty),
 1412        "UNLOGGED": lambda self: self.expression(exp.UnloggedProperty()),
 1413        "VOLATILE": lambda self: self._parse_volatile_property(),
 1414        "WITH": lambda self: self._parse_with_property(),
 1415    }
 1416
 1417    CONSTRAINT_PARSERS: t.ClassVar = {
 1418        "AUTOINCREMENT": lambda self: self._parse_auto_increment(),
 1419        "AUTO_INCREMENT": lambda self: self._parse_auto_increment(),
 1420        "CASESPECIFIC": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)),
 1421        "CHECK": lambda self: self._parse_check_constraint(),
 1422        "COLLATE": lambda self: self.expression(
 1423            exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column())
 1424        ),
 1425        "COMMENT": lambda self: self.expression(
 1426            exp.CommentColumnConstraint(this=self._parse_string())
 1427        ),
 1428        "COMPRESS": lambda self: self._parse_compress(),
 1429        "CLUSTERED": lambda self: self.expression(
 1430            exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1431        ),
 1432        "NONCLUSTERED": lambda self: self.expression(
 1433            exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))
 1434        ),
 1435        "DEFAULT": lambda self: self.expression(
 1436            exp.DefaultColumnConstraint(this=self._parse_bitwise())
 1437        ),
 1438        "ENCODE": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())),
 1439        "EPHEMERAL": lambda self: self.expression(
 1440            exp.EphemeralColumnConstraint(this=self._parse_bitwise())
 1441        ),
 1442        "EXCLUDE": lambda self: self.expression(
 1443            exp.ExcludeColumnConstraint(this=self._parse_index_params())
 1444        ),
 1445        "FOREIGN KEY": lambda self: self._parse_foreign_key(),
 1446        "FORMAT": lambda self: self.expression(
 1447            exp.DateFormatColumnConstraint(this=self._parse_var_or_string())
 1448        ),
 1449        "GENERATED": lambda self: self._parse_generated_as_identity(),
 1450        "IDENTITY": lambda self: self._parse_auto_increment(),
 1451        "INLINE": lambda self: self._parse_inline(),
 1452        "LIKE": lambda self: self._parse_create_like(),
 1453        "NOT": lambda self: self._parse_not_constraint(),
 1454        "NULL": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)),
 1455        "ON": lambda self: (
 1456            (
 1457                self._match(TokenType.UPDATE)
 1458                and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function()))
 1459            )
 1460            or self.expression(exp.OnProperty(this=self._parse_id_var()))
 1461        ),
 1462        "PATH": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())),
 1463        "PERIOD": lambda self: self._parse_period_for_system_time(),
 1464        "PRIMARY KEY": lambda self: self._parse_primary_key(),
 1465        "REFERENCES": lambda self: self._parse_references(match=False),
 1466        "TITLE": lambda self: self.expression(
 1467            exp.TitleColumnConstraint(this=self._parse_var_or_string())
 1468        ),
 1469        "TTL": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])),
 1470        "UNIQUE": lambda self: self._parse_unique(),
 1471        "UPPERCASE": lambda self: self.expression(exp.UppercaseColumnConstraint()),
 1472        "WITH": lambda self: self.expression(
 1473            exp.Properties(expressions=self._parse_wrapped_properties())
 1474        ),
 1475        "BUCKET": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1476        "TRUNCATE": lambda self: self._parse_partitioned_by_bucket_or_truncate(),
 1477    }
 1478
 1479    def _parse_partitioned_by_bucket_or_truncate(self) -> exp.Expr | None:
 1480        if not self._match(TokenType.L_PAREN, advance=False):
 1481            # Partitioning by bucket or truncate follows the syntax:
 1482            # PARTITION BY (BUCKET(..) | TRUNCATE(..))
 1483            # If we don't have parenthesis after each keyword, we should instead parse this as an identifier
 1484            self._retreat(self._index - 1)
 1485            return None
 1486
 1487        klass = (
 1488            exp.PartitionedByBucket
 1489            if self._prev.text.upper() == "BUCKET"
 1490            else exp.PartitionByTruncate
 1491        )
 1492
 1493        args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column())
 1494        this, expression = seq_get(args, 0), seq_get(args, 1)
 1495
 1496        if isinstance(this, exp.Literal):
 1497            # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order
 1498            #  - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)`
 1499            #  - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)`
 1500            # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)`
 1501            #
 1502            # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning
 1503            # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties
 1504            this, expression = expression, this
 1505
 1506        return self.expression(klass(this=this, expression=expression))
 1507
 1508    ALTER_PARSERS: t.ClassVar = {
 1509        "ADD": lambda self: self._parse_alter_table_add(),
 1510        "AS": lambda self: self._parse_select(),
 1511        "ALTER": lambda self: self._parse_alter_table_alter(),
 1512        "CLUSTER BY": lambda self: self._parse_cluster_property(),
 1513        "DELETE": lambda self: self.expression(exp.Delete(where=self._parse_where())),
 1514        "DROP": lambda self: self._parse_alter_table_drop(),
 1515        "RENAME": lambda self: self._parse_alter_table_rename(),
 1516        "SET": lambda self: self._parse_alter_table_set(),
 1517        "SWAP": lambda self: self.expression(
 1518            exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True))
 1519        ),
 1520    }
 1521
 1522    ALTER_ALTER_PARSERS: t.ClassVar = {
 1523        "DISTKEY": lambda self: self._parse_alter_diststyle(),
 1524        "DISTSTYLE": lambda self: self._parse_alter_diststyle(),
 1525        "SORTKEY": lambda self: self._parse_alter_sortkey(),
 1526        "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True),
 1527    }
 1528
 1529    SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = {
 1530        "CHECK",
 1531        "EXCLUDE",
 1532        "FOREIGN KEY",
 1533        "LIKE",
 1534        "PERIOD",
 1535        "PRIMARY KEY",
 1536        "UNIQUE",
 1537        "BUCKET",
 1538        "TRUNCATE",
 1539    }
 1540
 1541    NO_PAREN_FUNCTION_PARSERS: t.ClassVar = {
 1542        "ANY": lambda self: self.expression(exp.Any(this=self._parse_bitwise())),
 1543        "CASE": lambda self: self._parse_case(),
 1544        "CONNECT_BY_ROOT": lambda self: self.expression(
 1545            exp.ConnectByRoot(this=self._parse_column())
 1546        ),
 1547        "IF": lambda self: self._parse_if(),
 1548    }
 1549
 1550    INVALID_FUNC_NAME_TOKENS: t.ClassVar = {
 1551        TokenType.IDENTIFIER,
 1552        TokenType.STRING,
 1553    }
 1554
 1555    FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {"STRUCT"}
 1556
 1557    KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice)
 1558
 1559    FUNCTION_PARSERS: t.ClassVar[dict[str, t.Callable]] = {
 1560        **{
 1561            name: lambda self: self._parse_distinct_arg_function(exp.ArgMax)
 1562            for name in exp.ArgMax.sql_names()
 1563        },
 1564        **{
 1565            name: lambda self: self._parse_distinct_arg_function(exp.ArgMin)
 1566            for name in exp.ArgMin.sql_names()
 1567        },
 1568        "CAST": lambda self: self._parse_cast(self.STRICT_CAST),
 1569        "CEIL": lambda self: self._parse_ceil_floor(exp.Ceil),
 1570        "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST),
 1571        "CHAR": lambda self: self._parse_char(),
 1572        "CHR": lambda self: self._parse_char(),
 1573        "DECODE": lambda self: self._parse_decode(),
 1574        "EXTRACT": lambda self: self._parse_extract(),
 1575        "FLOOR": lambda self: self._parse_ceil_floor(exp.Floor),
 1576        "GAP_FILL": lambda self: self._parse_gap_fill(),
 1577        "INITCAP": lambda self: self._parse_initcap(),
 1578        "JSON_OBJECT": lambda self: self._parse_json_object(),
 1579        "JSON_OBJECTAGG": lambda self: self._parse_json_object(agg=True),
 1580        "JSON_TABLE": lambda self: self._parse_json_table(),
 1581        "MATCH": lambda self: self._parse_match_against(),
 1582        "NORMALIZE": lambda self: self._parse_normalize(),
 1583        "OPENJSON": lambda self: self._parse_open_json(),
 1584        "OVERLAY": lambda self: self._parse_overlay(),
 1585        "POSITION": lambda self: self._parse_position(),
 1586        "SAFE_CAST": lambda self: self._parse_cast(False, safe=True),
 1587        "STRING_AGG": lambda self: self._parse_string_agg(),
 1588        "SUBSTRING": lambda self: self._parse_substring(),
 1589        "TRIM": lambda self: self._parse_trim(),
 1590        "TRY_CAST": lambda self: self._parse_cast(False, safe=True),
 1591        "TRY_CONVERT": lambda self: self._parse_convert(False, safe=True),
 1592        "XMLELEMENT": lambda self: self._parse_xml_element(),
 1593        "XMLTABLE": lambda self: self._parse_xml_table(),
 1594    }
 1595
 1596    QUERY_MODIFIER_PARSERS: t.ClassVar = {
 1597        TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()),
 1598        TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()),
 1599        TokenType.WHERE: lambda self: ("where", self._parse_where()),
 1600        TokenType.GROUP_BY: lambda self: ("group", self._parse_group()),
 1601        TokenType.HAVING: lambda self: ("having", self._parse_having()),
 1602        TokenType.QUALIFY: lambda self: ("qualify", self._parse_qualify()),
 1603        TokenType.WINDOW: lambda self: ("windows", self._parse_window_clause()),
 1604        TokenType.ORDER_BY: lambda self: ("order", self._parse_order()),
 1605        TokenType.LIMIT: lambda self: ("limit", self._parse_limit()),
 1606        TokenType.FETCH: lambda self: ("limit", self._parse_limit()),
 1607        TokenType.OFFSET: lambda self: ("offset", self._parse_offset()),
 1608        TokenType.FOR: lambda self: ("locks", self._parse_locks()),
 1609        TokenType.LOCK: lambda self: ("locks", self._parse_locks()),
 1610        TokenType.TABLE_SAMPLE: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1611        TokenType.USING: lambda self: ("sample", self._parse_table_sample(as_modifier=True)),
 1612        TokenType.CLUSTER_BY: lambda self: (
 1613            "cluster",
 1614            self._parse_cluster(),
 1615        ),
 1616        TokenType.DISTRIBUTE_BY: lambda self: (
 1617            "distribute",
 1618            self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY),
 1619        ),
 1620        TokenType.SORT_BY: lambda self: ("sort", self._parse_sort(exp.Sort, TokenType.SORT_BY)),
 1621        TokenType.CONNECT_BY: lambda self: ("connect", self._parse_connect(skip_start_token=True)),
 1622    }
 1623    QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS)
 1624
 1625    SET_PARSERS: t.ClassVar = {
 1626        "GLOBAL": lambda self: self._parse_set_item_assignment("GLOBAL"),
 1627        "LOCAL": lambda self: self._parse_set_item_assignment("LOCAL"),
 1628        "SESSION": lambda self: self._parse_set_item_assignment("SESSION"),
 1629        "TRANSACTION": lambda self: self._parse_set_transaction(),
 1630    }
 1631
 1632    SHOW_PARSERS: t.ClassVar[dict[str, t.Callable]] = {}
 1633
 1634    TYPE_LITERAL_PARSERS: t.ClassVar = {
 1635        exp.DType.JSON: lambda self, this, _: self.expression(exp.ParseJSON(this=this)),
 1636    }
 1637
 1638    TYPE_CONVERTERS: t.ClassVar[dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {}
 1639
 1640    DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN}
 1641
 1642    PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE}
 1643
 1644    TRANSACTION_KIND: t.ClassVar = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"}
 1645    TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = {
 1646        "ISOLATION": (
 1647            ("LEVEL", "REPEATABLE", "READ"),
 1648            ("LEVEL", "READ", "COMMITTED"),
 1649            ("LEVEL", "READ", "UNCOMITTED"),
 1650            ("LEVEL", "SERIALIZABLE"),
 1651        ),
 1652        "READ": ("WRITE", "ONLY"),
 1653    }
 1654
 1655    CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1656        **dict.fromkeys(("ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK", "UPDATE"), tuple()),
 1657        "DO": ("NOTHING", "UPDATE"),
 1658    }
 1659
 1660    TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = {
 1661        "INSTEAD": (("OF",),),
 1662        "BEFORE": tuple(),
 1663        "AFTER": tuple(),
 1664    }
 1665
 1666    TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = {
 1667        "NOT": (("DEFERRABLE",),),
 1668        "DEFERRABLE": tuple(),
 1669    }
 1670
 1671    CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = {
 1672        "SCALE": ("EXTEND", "NOEXTEND"),
 1673        "SHARD": ("EXTEND", "NOEXTEND"),
 1674        "NO": ("CYCLE", "CACHE", "MAXVALUE", "MINVALUE"),
 1675        **dict.fromkeys(
 1676            (
 1677                "SESSION",
 1678                "GLOBAL",
 1679                "KEEP",
 1680                "NOKEEP",
 1681                "ORDER",
 1682                "NOORDER",
 1683                "NOCACHE",
 1684                "CYCLE",
 1685                "NOCYCLE",
 1686                "NOMINVALUE",
 1687                "NOMAXVALUE",
 1688                "NOSCALE",
 1689                "NOSHARD",
 1690            ),
 1691            tuple(),
 1692        ),
 1693    }
 1694
 1695    ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {"FOR": ("ALL", "INSERT", "NONE")}
 1696
 1697    USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1698        ("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA", "CATALOG"), tuple()
 1699    )
 1700
 1701    CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(("RENAME", "ADD"), ("FIELDS",))
 1702
 1703    SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1704        "TYPE": ("EVOLUTION",),
 1705        **dict.fromkeys(("BINDING", "COMPENSATION", "EVOLUTION"), tuple()),
 1706    }
 1707
 1708    PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {}
 1709
 1710    EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(
 1711        ("CALLER", "SELF", "OWNER"), tuple()
 1712    )
 1713
 1714    KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1715        "NOT": ("ENFORCED",),
 1716        "MATCH": (
 1717            "FULL",
 1718            "PARTIAL",
 1719            "SIMPLE",
 1720        ),
 1721        "INITIALLY": ("DEFERRED", "IMMEDIATE"),
 1722        "USING": (
 1723            "BTREE",
 1724            "HASH",
 1725        ),
 1726        **dict.fromkeys(("DEFERRABLE", "NORELY", "RELY"), tuple()),
 1727    }
 1728
 1729    WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {
 1730        "NO": ("OTHERS",),
 1731        "CURRENT": ("ROW",),
 1732        **dict.fromkeys(("GROUP", "TIES"), tuple()),
 1733    }
 1734
 1735    INSERT_ALTERNATIVES: t.ClassVar = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"}
 1736
 1737    CLONE_KEYWORDS: t.ClassVar = {"CLONE", "COPY"}
 1738    # Time travel clause prefixes, mapped to whether they pin a timestamp or a version
 1739    VERSION_PHRASES: t.ClassVar[dict[tuple[str, ...], str]] = {
 1740        ("FOR", "SYSTEM_TIME"): "TIMESTAMP",
 1741        ("FOR", "SYSTEM", "TIME"): "TIMESTAMP",
 1742        ("FOR", "TIMESTAMP"): "TIMESTAMP",
 1743        ("FOR", "VERSION"): "VERSION",
 1744        ("TIMESTAMP", "AS", "OF"): "TIMESTAMP",
 1745        ("VERSION", "AS", "OF"): "VERSION",
 1746    }
 1747
 1748    HISTORICAL_DATA_PREFIX: t.ClassVar = {"AT", "BEFORE", "END"}
 1749    HISTORICAL_DATA_KIND: t.ClassVar = {"OFFSET", "STATEMENT", "STREAM", "TIMESTAMP", "VERSION"}
 1750
 1751    OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {"ASC", "DESC", "NULLS", "WITH"}
 1752
 1753    OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN}
 1754
 1755    TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE}
 1756
 1757    VIEW_ATTRIBUTES: t.ClassVar = {"ENCRYPTION", "SCHEMABINDING", "VIEW_METADATA"}
 1758
 1759    WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS}
 1760    WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER}
 1761    WINDOW_SIDES: t.ClassVar = {"FOLLOWING", "PRECEDING"}
 1762
 1763    JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS}
 1764
 1765    FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT}
 1766
 1767    ADD_CONSTRAINT_TOKENS: t.ClassVar = {
 1768        TokenType.CONSTRAINT,
 1769        TokenType.FOREIGN_KEY,
 1770        TokenType.INDEX,
 1771        TokenType.KEY,
 1772        TokenType.PRIMARY_KEY,
 1773        TokenType.UNIQUE,
 1774    }
 1775
 1776    DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT}
 1777
 1778    UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS
 1779
 1780    SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT}
 1781
 1782    COPY_INTO_VARLEN_OPTIONS: t.ClassVar = {
 1783        "FILE_FORMAT",
 1784        "COPY_OPTIONS",
 1785        "FORMAT_OPTIONS",
 1786        "CREDENTIAL",
 1787    }
 1788
 1789    IS_JSON_PREDICATE_KIND: t.ClassVar = {"VALUE", "SCALAR", "ARRAY", "OBJECT"}
 1790
 1791    ODBC_DATETIME_LITERALS: t.ClassVar[dict[str, type[exp.Expr]]] = {}
 1792
 1793    ON_CONDITION_TOKENS: t.ClassVar = {"ERROR", "NULL", "TRUE", "FALSE", "EMPTY"}
 1794
 1795    PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN}
 1796
 1797    # The style options for the DESCRIBE statement
 1798    DESCRIBE_STYLES: t.ClassVar = {"ANALYZE", "EXTENDED", "FORMATTED", "HISTORY"}
 1799
 1800    SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {"=", ":=", "TO"}
 1801
 1802    # The style options for the ANALYZE statement
 1803    ANALYZE_STYLES: t.ClassVar = {
 1804        "BUFFER_USAGE_LIMIT",
 1805        "FULL",
 1806        "LOCAL",
 1807        "NO_WRITE_TO_BINLOG",
 1808        "SAMPLE",
 1809        "SKIP_LOCKED",
 1810        "VERBOSE",
 1811    }
 1812
 1813    ANALYZE_EXPRESSION_PARSERS: t.ClassVar = {
 1814        "ALL": lambda self: self._parse_analyze_columns(),
 1815        "COMPUTE": lambda self: self._parse_analyze_statistics(),
 1816        "DELETE": lambda self: self._parse_analyze_delete(),
 1817        "DROP": lambda self: self._parse_analyze_histogram(),
 1818        "ESTIMATE": lambda self: self._parse_analyze_statistics(),
 1819        "LIST": lambda self: self._parse_analyze_list(),
 1820        "PREDICATE": lambda self: self._parse_analyze_columns(),
 1821        "UPDATE": lambda self: self._parse_analyze_histogram(),
 1822        "VALIDATE": lambda self: self._parse_analyze_validate(),
 1823    }
 1824
 1825    PARTITION_KEYWORDS: t.ClassVar = {"PARTITION", "SUBPARTITION"}
 1826
 1827    AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET)
 1828
 1829    OPERATION_MODIFIERS: t.ClassVar[set[str]] = set()
 1830
 1831    RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {"BREADTH", "DEPTH", "CYCLE"}
 1832
 1833    SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {"DEFINER", "INVOKER", "NONE"}
 1834
 1835    MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values)
 1836
 1837    STRICT_CAST: t.ClassVar = True
 1838
 1839    PREFIXED_PIVOT_COLUMNS: t.ClassVar = False
 1840    IDENTIFY_PIVOT_STRINGS: t.ClassVar = False
 1841    # Whether an UNPIVOT outputs its value column(s) before the name column
 1842    UNPIVOT_VALUE_COLUMNS_FIRST: t.ClassVar = False
 1843    # Controls when an aggregation's name is included in a pivoted column's name:
 1844    # "agg_name_if_aliased" - only for aggregations that carry an explicit alias
 1845    # "agg_name_if_aliased_or_multiple" - if aliased, or whenever there are multiple aggregations
 1846    # "agg_name_if_multiple" - only when there are multiple aggregations (a lone agg is value-only)
 1847    PIVOT_COLUMN_NAMING: t.ClassVar[str] = "agg_name_if_aliased"
 1848
 1849    LOG_DEFAULTS_TO_LN: t.ClassVar = False
 1850
 1851    # Whether the table sample clause expects CSV syntax
 1852    TABLESAMPLE_CSV: t.ClassVar = False
 1853
 1854    # The default method used for table sampling
 1855    DEFAULT_SAMPLING_METHOD: t.ClassVar[str | None] = None
 1856
 1857    # Whether the SET command needs a delimiter (e.g. "=") for assignments
 1858    SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True
 1859
 1860    # Whether the TRIM function expects the characters to trim as its first argument
 1861    TRIM_PATTERN_FIRST: t.ClassVar = False
 1862
 1863    # Whether string aliases are supported `SELECT COUNT(*) 'count'`
 1864    STRING_ALIASES: t.ClassVar = False
 1865
 1866    # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand)
 1867    MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True
 1868    SET_OP_MODIFIERS: t.ClassVar = {"order", "limit", "offset"}
 1869
 1870    # Whether to parse IF statements that aren't followed by a left parenthesis as commands
 1871    NO_PAREN_IF_COMMANDS: t.ClassVar = True
 1872
 1873    # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres)
 1874    JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False
 1875
 1876    # Whether the `:` operator is used to extract a value from a VARIANT column
 1877    COLON_IS_VARIANT_EXTRACT: t.ClassVar = False
 1878
 1879    # Whether a chain of colon extractions (x:y:z) is a single extraction with a merged
 1880    # path (x:y.z, e.g. Snowflake) or each colon extracts from the previous result (e.g. Databricks)
 1881    COLON_CHAIN_IS_SINGLE_EXTRACT: t.ClassVar = True
 1882
 1883    # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause.
 1884    # If this is True and '(' is not found, the keyword will be treated as an identifier
 1885    VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True
 1886
 1887    # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift)
 1888    SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False
 1889
 1890    # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS
 1891    INTERVAL_SPANS: t.ClassVar = True
 1892
 1893    # Whether a PARTITION clause can follow a table reference
 1894    SUPPORTS_PARTITION_SELECTION: t.ClassVar = False
 1895
 1896    # Whether the `name AS expr` schema/column constraint requires parentheses around `expr`
 1897    WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True
 1898
 1899    # Whether the 'AS' keyword is optional in the CTE definition syntax
 1900    OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True
 1901
 1902    # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword
 1903    ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True
 1904
 1905    # Whether Alter statements are allowed to contain Partition specifications
 1906    ALTER_TABLE_PARTITIONS: t.ClassVar = False
 1907
 1908    # Whether all join types have the same precedence, i.e., they "naturally" produce a left-deep tree.
 1909    # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is
 1910    # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such
 1911    # as BigQuery, where all joins have the same precedence.
 1912    JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False
 1913
 1914    # Whether TIMESTAMP <literal> can produce a zone-aware timestamp
 1915    ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False
 1916
 1917    # Whether map literals support arbitrary expressions as keys.
 1918    # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB).
 1919    # When False, keys are typically restricted to identifiers.
 1920    MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False
 1921
 1922    # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this
 1923    # is true for Snowflake but not for BigQuery which can also process strings
 1924    JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False
 1925
 1926    # Dialects like Databricks support JOINS without join criteria
 1927    # Adding an ON TRUE, makes transpilation semantically correct for other dialects
 1928    ADD_JOIN_ON_TRUE: t.ClassVar = False
 1929
 1930    # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]'
 1931    # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND`
 1932    SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False
 1933
 1934    # Whether adjacent string literals like 'foo' 'bar' require a whitespace or comment between them
 1935    # to be considered valid syntactically. Such expressions evaluate to the strings' concatenation.
 1936    ADJACENT_STRINGS_CANNOT_BE_CONNECTED: t.ClassVar = False
 1937
 1938    SHOW_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SHOW_PARSERS)
 1939    SET_TRIE: t.ClassVar[dict] = new_trie(key.split(" ") for key in SET_PARSERS)
 1940
 1941    def __init__(
 1942        self,
 1943        error_level: ErrorLevel | None = None,
 1944        error_message_context: int = 100,
 1945        max_errors: int = 3,
 1946        max_nodes: int = -1,
 1947        dialect: DialectType = None,
 1948    ):
 1949        self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE
 1950        self.error_message_context: int = error_message_context
 1951        self.max_errors: int = max_errors
 1952        self.max_nodes: int = max_nodes
 1953        self.dialect: t.Any = _resolve_dialect(dialect)
 1954        self.sql: str = ""
 1955        self.errors: list[ParseError] = []
 1956        self._tokens: list[Token] = []
 1957        self._tokens_size: i64 = 0
 1958        self._index: i64 = 0
 1959        self._curr: Token = SENTINEL_NONE
 1960        self._next: Token = SENTINEL_NONE
 1961        self._prev: Token = SENTINEL_NONE
 1962        self._prev_comments: list[str] = []
 1963        self._pipe_cte_counter: int = 0
 1964        self._chunks: list[list[Token]] = []
 1965        self._chunk_index: i64 = 0
 1966        self._node_count: int = 0
 1967
 1968    def reset(self) -> None:
 1969        self.sql = ""
 1970        self.errors = []
 1971        self._tokens = []
 1972        self._tokens_size = 0
 1973        self._index = 0
 1974        self._curr = SENTINEL_NONE
 1975        self._next = SENTINEL_NONE
 1976        self._prev = SENTINEL_NONE
 1977        self._prev_comments = []
 1978        self._pipe_cte_counter = 0
 1979        self._chunks = []
 1980        self._chunk_index = 0
 1981        self._node_count = 0
 1982
 1983    def _advance(self, times: i64 = 1) -> None:
 1984        index = self._index + times
 1985        self._index = index
 1986        tokens = self._tokens
 1987        size = self._tokens_size
 1988        self._curr = tokens[index] if index < size else SENTINEL_NONE
 1989        self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE
 1990
 1991        if index > 0:
 1992            prev = tokens[index - 1]
 1993            self._prev = prev
 1994            self._prev_comments = prev.comments
 1995        else:
 1996            self._prev = SENTINEL_NONE
 1997            self._prev_comments = []
 1998
 1999    def _advance_chunk(self) -> None:
 2000        self._index = -1
 2001        self._tokens = self._chunks[self._chunk_index]
 2002        self._tokens_size = i64(len(self._tokens))
 2003        self._chunk_index += 1
 2004        self._advance()
 2005
 2006    def _retreat(self, index: i64) -> None:
 2007        if index != self._index:
 2008            self._advance(index - self._index)
 2009
 2010    def _add_comments(self, expression: exp.Expr | None) -> None:
 2011        if expression and self._prev_comments:
 2012            expression.add_comments(self._prev_comments)
 2013            self._prev_comments = []
 2014
 2015    def _match(
 2016        self, token_type: TokenType, advance: bool = True, expression: exp.Expr | None = None
 2017    ) -> bool:
 2018        if self._curr.token_type == token_type:
 2019            if advance:
 2020                self._advance()
 2021            self._add_comments(expression)
 2022            return True
 2023        return False
 2024
 2025    def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool:
 2026        if self._curr.token_type in types:
 2027            if advance:
 2028                self._advance()
 2029            return True
 2030        return False
 2031
 2032    def _match_pair(
 2033        self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True
 2034    ) -> bool:
 2035        if self._curr.token_type == token_type_a and self._next.token_type == token_type_b:
 2036            if advance:
 2037                self._advance(2)
 2038            return True
 2039        return False
 2040
 2041    def _match_texts(self, texts: TEXTS_TYPE, advance: bool = True) -> bool:
 2042        if (
 2043            self._curr.token_type not in self.TEXT_MATCH_EXCLUDED_TOKENS
 2044            and self._curr.text.upper() in texts
 2045        ):
 2046            if advance:
 2047                self._advance()
 2048            return True
 2049        return False
 2050
 2051    def _match_text_seq(self, *texts: str, advance: bool = True) -> bool:
 2052        index = self._index
 2053        excluded_tokens = self.TEXT_MATCH_EXCLUDED_TOKENS
 2054        for text in texts:
 2055            if self._curr.token_type not in excluded_tokens and self._curr.text.upper() == text:
 2056                self._advance()
 2057            else:
 2058                self._retreat(index)
 2059                return False
 2060
 2061        if not advance:
 2062            self._retreat(index)
 2063
 2064        return True
 2065
 2066    def _is_connected(self) -> bool:
 2067        prev = self._prev
 2068        curr = self._curr
 2069        return bool(prev and curr and prev.end + 1 == curr.start)
 2070
 2071    def _find_sql(self, start: Token, end: Token) -> str:
 2072        return self.sql[start.start : end.end + 1]
 2073
 2074    def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None:
 2075        token = token or self._curr or self._prev or Token.string("")
 2076        formatted_sql, start_context, highlight, end_context = highlight_sql(
 2077            sql=self.sql,
 2078            positions=[(token.start, token.end)],
 2079            context_length=self.error_message_context,
 2080        )
 2081        formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n  {formatted_sql}"
 2082
 2083        error = ParseError.new(
 2084            formatted_message,
 2085            description=message,
 2086            line=token.line,
 2087            col=token.col,
 2088            start_context=start_context,
 2089            highlight=highlight,
 2090            end_context=end_context,
 2091        )
 2092
 2093        if self.error_level == ErrorLevel.IMMEDIATE:
 2094            raise error
 2095
 2096        self.errors.append(error)
 2097
 2098    def validate_expression(self, expression: E, args: list | None = None) -> E:
 2099        if self.max_nodes > -1:
 2100            self._node_count += 1
 2101            if self._node_count > self.max_nodes:
 2102                self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded")
 2103        if self.error_level != ErrorLevel.IGNORE:
 2104            for error_message in expression.error_messages(args):
 2105                self.raise_error(error_message)
 2106        return expression
 2107
 2108    def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> T | None:
 2109        index = self._index
 2110        error_level = self.error_level
 2111        this: T | None = None
 2112
 2113        self.error_level = ErrorLevel.IMMEDIATE
 2114        try:
 2115            this = parse_method()
 2116        except ParseError:
 2117            this = None
 2118        finally:
 2119            if not this or retreat:
 2120                self._retreat(index)
 2121            self.error_level = error_level
 2122
 2123        return this
 2124
 2125    def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]:
 2126        """
 2127        Parses a list of tokens and returns a list of syntax trees, one tree
 2128        per parsed SQL statement.
 2129
 2130        Args:
 2131            raw_tokens: The list of tokens.
 2132            sql: The original SQL string.
 2133
 2134        Returns:
 2135            The list of the produced syntax trees.
 2136        """
 2137        return self._parse(
 2138            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
 2139        )
 2140
 2141    def parse_into(
 2142        self,
 2143        expression_types: exp.IntoType,
 2144        raw_tokens: list[Token],
 2145        sql: str | None = None,
 2146    ) -> list[exp.Expr | None]:
 2147        """
 2148        Parses a list of tokens into a given Expr type. If a collection of Expr
 2149        types is given instead, this method will try to parse the token list into each one
 2150        of them, stopping at the first for which the parsing succeeds.
 2151
 2152        Args:
 2153            expression_types: The expression type(s) to try and parse the token list into.
 2154            raw_tokens: The list of tokens.
 2155            sql: The original SQL string, used to produce helpful debug messages.
 2156
 2157        Returns:
 2158            The target Expr.
 2159        """
 2160        errors = []
 2161        for expression_type in ensure_list(expression_types):
 2162            parser = self.EXPRESSION_PARSERS.get(t.cast(type[exp.Expr], expression_type))
 2163            if not parser:
 2164                raise TypeError(f"No parser registered for {expression_type}")
 2165
 2166            try:
 2167                return self._parse(parser, raw_tokens, sql)
 2168            except ParseError as e:
 2169                e.errors[0]["into_expression"] = expression_type
 2170                errors.append(e)
 2171
 2172        raise ParseError(
 2173            f"Failed to parse '{sql or raw_tokens}' into {expression_types}",
 2174            errors=merge_errors(errors),
 2175        ) from errors[-1]
 2176
 2177    def check_errors(self) -> None:
 2178        """Logs or raises any found errors, depending on the chosen error level setting."""
 2179        if self.error_level == ErrorLevel.WARN:
 2180            for error in self.errors:
 2181                logger.error(str(error))
 2182        elif self.error_level == ErrorLevel.RAISE and self.errors:
 2183            raise ParseError(
 2184                concat_messages(self.errors, self.max_errors),
 2185                errors=merge_errors(self.errors),
 2186            )
 2187
 2188    def expression(
 2189        self,
 2190        instance: E,
 2191        token: Token | None = None,
 2192        comments: list[str] | None = None,
 2193    ) -> E:
 2194        if token:
 2195            instance.update_positions(token)
 2196        instance.add_comments(comments) if comments else self._add_comments(instance)
 2197        if not instance.is_primitive:
 2198            instance = self.validate_expression(instance)
 2199        return instance
 2200
 2201    def _parse_batch_statements(
 2202        self,
 2203        parse_method: t.Callable[[Parser], exp.Expr | None],
 2204        sep_first_statement: bool = True,
 2205    ) -> list[exp.Expr | None]:
 2206        expressions = []
 2207
 2208        # Chunkification binds if/while statements with the first statement of the body
 2209        if sep_first_statement:
 2210            self._match(TokenType.BEGIN)
 2211            expressions.append(parse_method(self))
 2212
 2213        chunks_length = len(self._chunks)
 2214        while self._chunk_index < chunks_length:
 2215            self._advance_chunk()
 2216
 2217            if self._match(TokenType.ELSE, advance=False):
 2218                return expressions
 2219
 2220            if expressions and not self._next and self._match(TokenType.END):
 2221                expressions.append(exp.EndStatement())
 2222                continue
 2223
 2224            expressions.append(parse_method(self))
 2225
 2226            if self._index < self._tokens_size:
 2227                self.raise_error("Invalid expression / Unexpected token")
 2228
 2229            self.check_errors()
 2230
 2231        return expressions
 2232
 2233    def _parse(
 2234        self,
 2235        parse_method: t.Callable[[Parser], exp.Expr | None],
 2236        raw_tokens: list[Token],
 2237        sql: str | None = None,
 2238    ) -> list[exp.Expr | None]:
 2239        self.reset()
 2240        self.sql = sql or ""
 2241
 2242        total = len(raw_tokens)
 2243        chunks: list[list[Token]] = [[]]
 2244
 2245        for i, token in enumerate(raw_tokens):
 2246            if token.token_type == TokenType.SEMICOLON:
 2247                if token.comments:
 2248                    chunks.append([token])
 2249
 2250                if i < total - 1:
 2251                    chunks.append([])
 2252            else:
 2253                chunks[-1].append(token)
 2254
 2255        self._chunks = chunks
 2256
 2257        return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False)
 2258
 2259    def _warn_unsupported(self) -> None:
 2260        if self._tokens_size <= 1:
 2261            return
 2262
 2263        # We use _find_sql because self.sql may comprise multiple chunks, and we're only
 2264        # interested in emitting a warning for the one being currently processed.
 2265        sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context]
 2266
 2267        logger.warning(
 2268            f"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'."
 2269        )
 2270
 2271    def _parse_command(self) -> exp.Command:
 2272        self._warn_unsupported()
 2273        comments = self._prev_comments
 2274        return self.expression(
 2275            exp.Command(this=self._prev.text.upper(), expression=self._parse_string()),
 2276            comments=comments,
 2277        )
 2278
 2279    def _parse_comment(self, allow_exists: bool = True) -> exp.Expr:
 2280        start = self._prev
 2281        exists = self._parse_exists() if allow_exists else None
 2282
 2283        self._match(TokenType.ON)
 2284
 2285        materialized = self._match_text_seq("MATERIALIZED")
 2286        kind = self._match_set(self.CREATABLES) and self._prev
 2287        if not kind:
 2288            return self._parse_as_command(start)
 2289
 2290        if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2291            this = self._parse_user_defined_function(kind=kind.token_type)
 2292        elif kind.token_type == TokenType.TABLE:
 2293            this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS)
 2294        elif kind.token_type == TokenType.COLUMN:
 2295            this = self._parse_column()
 2296        else:
 2297            this = self._parse_table_parts(schema=True)
 2298
 2299        self._match(TokenType.IS)
 2300
 2301        return self.expression(
 2302            exp.Comment(
 2303                this=this,
 2304                kind=kind.text,
 2305                expression=self._parse_string(),
 2306                exists=exists,
 2307                materialized=materialized,
 2308            )
 2309        )
 2310
 2311    def _parse_to_table(
 2312        self,
 2313    ) -> exp.ToTableProperty:
 2314        table = self._parse_table_parts(schema=True)
 2315        return self.expression(exp.ToTableProperty(this=table))
 2316
 2317    # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl
 2318    def _parse_ttl(self) -> exp.Expr:
 2319        def _parse_ttl_action() -> exp.Expr | None:
 2320            this = self._parse_bitwise()
 2321
 2322            if self._match_text_seq("DELETE"):
 2323                return self.expression(exp.MergeTreeTTLAction(this=this, delete=True))
 2324            if self._match_text_seq("RECOMPRESS"):
 2325                return self.expression(
 2326                    exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise())
 2327                )
 2328            if self._match_text_seq("TO", "DISK"):
 2329                return self.expression(
 2330                    exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string())
 2331                )
 2332            if self._match_text_seq("TO", "VOLUME"):
 2333                return self.expression(
 2334                    exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string())
 2335                )
 2336
 2337            return this
 2338
 2339        expressions = self._parse_csv(_parse_ttl_action)
 2340        where = self._parse_where()
 2341        group = self._parse_group()
 2342
 2343        aggregates = None
 2344        if group and self._match(TokenType.SET):
 2345            aggregates = self._parse_csv(self._parse_set_item)
 2346
 2347        return self.expression(
 2348            exp.MergeTreeTTL(
 2349                expressions=expressions, where=where, group=group, aggregates=aggregates
 2350            )
 2351        )
 2352
 2353    def _parse_condition(self) -> exp.Expr | None:
 2354        return self._parse_wrapped(parse_method=self._parse_expression, optional=True)
 2355
 2356    def _parse_block(self) -> exp.Block:
 2357        return self.expression(
 2358            exp.Block(
 2359                expressions=self._parse_batch_statements(
 2360                    parse_method=lambda self: self._parse_statement()
 2361                )
 2362            )
 2363        )
 2364
 2365    def _parse_whileblock(self) -> exp.WhileBlock:
 2366        return self.expression(
 2367            exp.WhileBlock(this=self._parse_condition(), body=self._parse_block())
 2368        )
 2369
 2370    def _parse_statement(self) -> exp.Expr | None:
 2371        if not self._curr:
 2372            return None
 2373
 2374        if self._match_set(self.STATEMENT_PARSERS):
 2375            comments = self._prev_comments
 2376            stmt = self.STATEMENT_PARSERS[self._prev.token_type](self)
 2377            stmt.add_comments(comments, prepend=True)
 2378            return stmt
 2379
 2380        if self._match_set(self.dialect.tokenizer_class.COMMANDS):
 2381            return self._parse_command()
 2382
 2383        if self._match_text_seq("WHILE"):
 2384            return self._parse_whileblock()
 2385
 2386        expression = self._parse_expression()
 2387        expression = self._parse_set_operations(expression) if expression else self._parse_select()
 2388
 2389        if isinstance(expression, exp.Subquery) and self._match(TokenType.PIPE_GT, advance=False):
 2390            expression = self._parse_pipe_syntax_query(expression)
 2391
 2392        return self._parse_query_modifiers(expression)
 2393
 2394    def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command:
 2395        start = self._prev
 2396        temporary = self._match(TokenType.TEMPORARY)
 2397        materialized = self._match_text_seq("MATERIALIZED")
 2398        iceberg = self._match_text_seq("ICEBERG")
 2399
 2400        kind = self._match_set(self.CREATABLES) and self._prev.text.upper()
 2401        if not kind or (iceberg and kind and kind != "TABLE"):
 2402            return self._parse_as_command(start)
 2403
 2404        concurrently = self._match_text_seq("CONCURRENTLY")
 2405        if_exists = exists or self._parse_exists()
 2406
 2407        if kind == "COLUMN":
 2408            this = self._parse_column()
 2409        else:
 2410            this = self._parse_table_parts(schema=True, is_db_reference=kind == "SCHEMA")
 2411
 2412        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 2413
 2414        if self._match(TokenType.L_PAREN, advance=False):
 2415            expressions = self._parse_wrapped_csv(self._parse_types)
 2416        else:
 2417            expressions = None
 2418
 2419        cascade_or_restrict = self._match_texts(("CASCADE", "RESTRICT")) and self._prev.text.upper()
 2420
 2421        return self.expression(
 2422            exp.Drop(
 2423                exists=if_exists,
 2424                this=this,
 2425                expressions=expressions,
 2426                kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind,
 2427                temporary=temporary,
 2428                materialized=materialized,
 2429                cascade=cascade_or_restrict == "CASCADE",
 2430                restrict=cascade_or_restrict == "RESTRICT",
 2431                constraints=self._match_text_seq("CONSTRAINTS"),
 2432                purge=self._match_text_seq("PURGE"),
 2433                cluster=cluster,
 2434                concurrently=concurrently,
 2435                sync=self._match_text_seq("SYNC"),
 2436                iceberg=iceberg,
 2437                force=self._match_text_seq("FORCE"),
 2438            )
 2439        )
 2440
 2441    def _parse_exists(self, not_: bool = False) -> bool | None:
 2442        return (
 2443            self._match_text_seq("IF")
 2444            and (not not_ or self._match(TokenType.NOT))
 2445            and self._match(TokenType.EXISTS)
 2446        )
 2447
 2448    def _parse_create(self) -> exp.Create | exp.Command:
 2449        # Note: this can't be None because we've matched a statement parser
 2450        start = self._prev
 2451
 2452        replace = (
 2453            start.token_type == TokenType.REPLACE
 2454            or self._match_pair(TokenType.OR, TokenType.REPLACE)
 2455            or self._match_pair(TokenType.OR, TokenType.ALTER)
 2456        )
 2457        refresh = self._match_pair(TokenType.OR, TokenType.REFRESH)
 2458
 2459        unique = self._match(TokenType.UNIQUE)
 2460
 2461        if self._match_text_seq("CLUSTERED", "COLUMNSTORE"):
 2462            clustered = True
 2463        elif self._match_text_seq("NONCLUSTERED", "COLUMNSTORE") or self._match_text_seq(
 2464            "COLUMNSTORE"
 2465        ):
 2466            clustered = False
 2467        else:
 2468            clustered = None
 2469
 2470        if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False):
 2471            self._advance()
 2472
 2473        properties = None
 2474        create_token = self._match_set(self.CREATABLES) and self._prev
 2475
 2476        if not create_token:
 2477            # exp.Properties.Location.POST_CREATE
 2478            properties = self._parse_properties()
 2479            create_token = self._match_set(self.CREATABLES) and self._prev
 2480
 2481            if not properties or not create_token:
 2482                return self._parse_as_command(start)
 2483
 2484        create_token_type = t.cast(Token, create_token).token_type
 2485
 2486        concurrently = self._match_text_seq("CONCURRENTLY")
 2487        exists = self._parse_exists(not_=True)
 2488        this = None
 2489        expression: exp.Expr | None = None
 2490        indexes = None
 2491        no_schema_binding = None
 2492        begin = None
 2493        clone = None
 2494
 2495        def extend_props(temp_props: exp.Properties | None) -> None:
 2496            nonlocal properties
 2497            if properties and temp_props:
 2498                properties.expressions.extend(temp_props.expressions)
 2499            elif temp_props:
 2500                properties = temp_props
 2501
 2502        if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):
 2503            this = self._parse_user_defined_function(kind=create_token_type)
 2504
 2505            # exp.Properties.Location.POST_SCHEMA ("schema" here is the UDF's type signature)
 2506            extend_props(self._parse_properties())
 2507
 2508            expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None
 2509
 2510            if (
 2511                not expression
 2512                and create_token_type == TokenType.FUNCTION
 2513                and isinstance(this, exp.UserDefinedFunction)
 2514                and this.args.get("wrapped")
 2515            ):
 2516                pre_table_index = self._index
 2517                is_table = self._match(TokenType.TABLE)
 2518
 2519                expression = self._parse_expression()
 2520                overload_mode = bool(
 2521                    expression
 2522                    and self._curr.token_type == TokenType.COMMA
 2523                    and self._next.token_type == TokenType.L_PAREN
 2524                )
 2525                if not overload_mode:
 2526                    self._retreat(pre_table_index)
 2527                    is_table = False
 2528                    expression = None
 2529            else:
 2530                is_table = False
 2531                overload_mode = False
 2532
 2533            extend_props(self._parse_function_properties())
 2534
 2535            if not expression:
 2536                if self._match(TokenType.COMMAND):
 2537                    expression = self._parse_as_command(self._prev)
 2538                else:
 2539                    begin = self._match(TokenType.BEGIN)
 2540                    return_ = self._match_text_seq("RETURN")
 2541
 2542                    if self._match(TokenType.STRING, advance=False):
 2543                        # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property
 2544                        # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement
 2545                        expression = self._parse_string()
 2546                        extend_props(self._parse_properties())
 2547                    else:
 2548                        expression = (
 2549                            self._parse_user_defined_function_expression()
 2550                            if create_token_type == TokenType.FUNCTION
 2551                            else self._parse_block()
 2552                        )
 2553
 2554                    if return_:
 2555                        expression = self.expression(exp.Return(this=expression))
 2556
 2557            if overload_mode and expression:
 2558                expression = self._parse_macro_overloads(
 2559                    t.cast(exp.UserDefinedFunction, this), expression, is_table
 2560                )
 2561        elif create_token_type == TokenType.INDEX:
 2562            # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c)
 2563            if not self._match(TokenType.ON):
 2564                index = self._parse_id_var()
 2565                anonymous = False
 2566            else:
 2567                index = None
 2568                anonymous = True
 2569
 2570            this = self._parse_index(index=index, anonymous=anonymous)
 2571        elif (
 2572            create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER)
 2573        ) or create_token_type == TokenType.TRIGGER:
 2574            if is_constraint := (create_token_type == TokenType.CONSTRAINT):
 2575                create_token = self._prev
 2576
 2577            trigger_name = self._parse_id_var()
 2578            if not trigger_name:
 2579                return self._parse_as_command(start)
 2580
 2581            timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False)
 2582            timing = timing_var.this if timing_var else None
 2583            if not timing:
 2584                return self._parse_as_command(start)
 2585
 2586            events = self._parse_trigger_events()
 2587            if not self._match(TokenType.ON):
 2588                self.raise_error("Expected ON in trigger definition")
 2589
 2590            table = self._parse_table_parts()
 2591            referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None
 2592            deferrable, initially = self._parse_trigger_deferrable()
 2593            referencing = self._parse_trigger_referencing()
 2594            for_each = self._parse_trigger_for_each()
 2595            when = self._match_text_seq("WHEN") and self._parse_wrapped(
 2596                self._parse_disjunction, optional=True
 2597            )
 2598            execute = self._parse_trigger_execute()
 2599
 2600            if execute is None:
 2601                return self._parse_as_command(start)
 2602
 2603            trigger_props = self.expression(
 2604                exp.TriggerProperties(
 2605                    table=table,
 2606                    timing=timing,
 2607                    events=events,
 2608                    execute=execute,
 2609                    constraint=is_constraint,
 2610                    referenced_table=referenced_table,
 2611                    deferrable=deferrable,
 2612                    initially=initially,
 2613                    referencing=referencing,
 2614                    for_each=for_each,
 2615                    when=when,
 2616                )
 2617            )
 2618
 2619            this = trigger_name
 2620            extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else []))
 2621        elif create_token_type == TokenType.TYPE:
 2622            this = self._parse_table_parts(schema=True)
 2623            if not this or not self._match(TokenType.ALIAS):
 2624                return self._parse_as_command(start)
 2625
 2626            if self._match(TokenType.ENUM):
 2627                expression = exp.DataType(
 2628                    this=exp.DType.ENUM,
 2629                    expressions=self._parse_wrapped_csv(self._parse_string),
 2630                )
 2631            elif self._match(TokenType.L_PAREN, advance=False):
 2632                expression = self._parse_schema()
 2633            else:
 2634                return self._parse_as_command(start)
 2635        elif create_token_type in self.DB_CREATABLES:
 2636            table_parts = self._parse_table_parts(
 2637                schema=True, is_db_reference=create_token_type == TokenType.SCHEMA
 2638            )
 2639
 2640            # exp.Properties.Location.POST_NAME
 2641            self._match(TokenType.COMMA)
 2642            extend_props(self._parse_properties(before=True))
 2643
 2644            this = self._parse_schema(this=table_parts)
 2645
 2646            # exp.Properties.Location.POST_SCHEMA and POST_WITH
 2647            extend_props(self._parse_properties())
 2648
 2649            has_alias = self._match(TokenType.ALIAS)
 2650            if not self._match_set(self.DDL_SELECT_TOKENS, advance=False):
 2651                # exp.Properties.Location.POST_ALIAS
 2652                extend_props(self._parse_properties())
 2653
 2654            if create_token_type == TokenType.SEQUENCE:
 2655                expression = self._parse_types()
 2656                props = self._parse_properties()
 2657                if props:
 2658                    sequence_props = exp.SequenceProperties()
 2659                    options = []
 2660                    for prop in props:
 2661                        if isinstance(prop, exp.SequenceProperties):
 2662                            for arg, value in prop.args.items():
 2663                                if arg == "options":
 2664                                    options.extend(value)
 2665                                else:
 2666                                    sequence_props.set(arg, value)
 2667                            prop.pop()
 2668
 2669                    if options:
 2670                        sequence_props.set("options", options)
 2671
 2672                    props.append("expressions", sequence_props)
 2673                    extend_props(props)
 2674            else:
 2675                expression = self._parse_ddl_select()
 2676
 2677                # Some dialects also support using a table as an alias instead of a SELECT.
 2678                # Here we fallback to this as an alternative.
 2679                if not expression and has_alias:
 2680                    expression = self._try_parse(self._parse_table_parts)
 2681
 2682            if create_token_type == TokenType.TABLE:
 2683                # exp.Properties.Location.POST_EXPRESSION
 2684                extend_props(self._parse_properties())
 2685
 2686                indexes = []
 2687                while True:
 2688                    index = self._parse_index()
 2689
 2690                    # exp.Properties.Location.POST_INDEX
 2691                    extend_props(self._parse_properties())
 2692                    if not index:
 2693                        break
 2694                    else:
 2695                        self._match(TokenType.COMMA)
 2696                        indexes.append(index)
 2697            elif create_token_type == TokenType.VIEW:
 2698                if self._match_text_seq("WITH", "NO", "SCHEMA", "BINDING"):
 2699                    no_schema_binding = True
 2700            elif create_token_type in (TokenType.SINK, TokenType.SOURCE):
 2701                extend_props(self._parse_properties())
 2702
 2703            shallow = self._match_text_seq("SHALLOW")
 2704
 2705            if self._match_texts(self.CLONE_KEYWORDS):
 2706                copy = self._prev.text.lower() == "copy"
 2707                clone = self.expression(
 2708                    exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy)
 2709                )
 2710
 2711        if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False):
 2712            return self._parse_as_command(start)
 2713
 2714        create_kind_text = create_token.text.upper()
 2715        return self.expression(
 2716            exp.Create(
 2717                this=this,
 2718                kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text,
 2719                replace=replace,
 2720                refresh=refresh,
 2721                unique=unique,
 2722                expression=expression,
 2723                exists=exists,
 2724                properties=properties,
 2725                indexes=indexes,
 2726                no_schema_binding=no_schema_binding,
 2727                begin=begin,
 2728                clone=clone,
 2729                concurrently=concurrently,
 2730                clustered=clustered,
 2731            )
 2732        )
 2733
 2734    def _parse_sequence_properties(self) -> exp.SequenceProperties | None:
 2735        seq = exp.SequenceProperties()
 2736
 2737        options = []
 2738        index = self._index
 2739
 2740        while self._curr:
 2741            self._match(TokenType.COMMA)
 2742            if self._match_text_seq("INCREMENT"):
 2743                self._match_text_seq("BY")
 2744                self._match_text_seq("=")
 2745                seq.set("increment", self._parse_term())
 2746            elif self._match_text_seq("MINVALUE"):
 2747                seq.set("minvalue", self._parse_term())
 2748            elif self._match_text_seq("MAXVALUE"):
 2749                seq.set("maxvalue", self._parse_term())
 2750            elif self._match_text_seq("START"):
 2751                self._match_text_seq("WITH")
 2752                self._match_text_seq("=")
 2753                seq.set("start", self._parse_term())
 2754            elif self._match_text_seq("CACHE"):
 2755                # T-SQL allows empty CACHE which is initialized dynamically
 2756                seq.set("cache", self._parse_number() or True)
 2757            elif self._match_text_seq("OWNED", "BY"):
 2758                # "OWNED BY NONE" is the default
 2759                seq.set("owned", None if self._match_text_seq("NONE") else self._parse_column())
 2760            else:
 2761                opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False)
 2762                if opt:
 2763                    options.append(opt)
 2764                else:
 2765                    break
 2766
 2767        seq.set("options", options if options else None)
 2768        return None if self._index == index else seq
 2769
 2770    def _parse_trigger_events(self) -> list[exp.TriggerEvent]:
 2771        events = []
 2772
 2773        while True:
 2774            event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper()
 2775
 2776            if not event_type:
 2777                self.raise_error("Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)")
 2778
 2779            columns = (
 2780                self._parse_csv(self._parse_column)
 2781                if event_type == "UPDATE" and self._match_text_seq("OF")
 2782                else None
 2783            )
 2784
 2785            events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns)))
 2786
 2787            if not self._match(TokenType.OR):
 2788                break
 2789
 2790        return events
 2791
 2792    def _parse_trigger_deferrable(
 2793        self,
 2794    ) -> tuple[str | None, str | None]:
 2795        deferrable_var = self._parse_var_from_options(
 2796            self.TRIGGER_DEFERRABLE, raise_unmatched=False
 2797        )
 2798        deferrable = deferrable_var.this if deferrable_var else None
 2799
 2800        initially = None
 2801        if deferrable and self._match_text_seq("INITIALLY"):
 2802            initially = (
 2803                self._prev.text.upper() if self._match_texts(("IMMEDIATE", "DEFERRED")) else None
 2804            )
 2805
 2806        return deferrable, initially
 2807
 2808    def _parse_trigger_referencing_clause(self, keyword: str) -> exp.Expr | None:
 2809        if not self._match_text_seq(keyword):
 2810            return None
 2811        if not self._match_text_seq("TABLE"):
 2812            self.raise_error(f"Expected TABLE after {keyword} in REFERENCING clause")
 2813        self._match_text_seq("AS")
 2814        return self._parse_id_var()
 2815
 2816    def _parse_trigger_referencing(self) -> exp.TriggerReferencing | None:
 2817        if not self._match_text_seq("REFERENCING"):
 2818            return None
 2819
 2820        old_alias = None
 2821        new_alias = None
 2822
 2823        while True:
 2824            if alias := self._parse_trigger_referencing_clause("OLD"):
 2825                if old_alias is not None:
 2826                    self.raise_error("Duplicate OLD clause in REFERENCING")
 2827                old_alias = alias
 2828            elif alias := self._parse_trigger_referencing_clause("NEW"):
 2829                if new_alias is not None:
 2830                    self.raise_error("Duplicate NEW clause in REFERENCING")
 2831                new_alias = alias
 2832            else:
 2833                break
 2834
 2835        if old_alias is None and new_alias is None:
 2836            self.raise_error("REFERENCING clause requires at least OLD TABLE or NEW TABLE")
 2837
 2838        return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias))
 2839
 2840    def _parse_trigger_for_each(self) -> str | None:
 2841        if not self._match_text_seq("FOR", "EACH"):
 2842            return None
 2843
 2844        return self._prev.text.upper() if self._match_texts(("ROW", "STATEMENT")) else None
 2845
 2846    def _parse_trigger_execute(self) -> exp.TriggerExecute | None:
 2847        if not self._match(TokenType.EXECUTE):
 2848            return None
 2849
 2850        if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)):
 2851            self.raise_error("Expected FUNCTION or PROCEDURE after EXECUTE")
 2852
 2853        func_call = self._parse_column()
 2854        return self.expression(exp.TriggerExecute(this=func_call))
 2855
 2856    def _parse_property_before(self) -> exp.Expr | list[exp.Expr] | None:
 2857        # only used for teradata currently
 2858        self._match(TokenType.COMMA)
 2859
 2860        kwargs = {
 2861            "no": self._match_text_seq("NO"),
 2862            "dual": self._match_text_seq("DUAL"),
 2863            "before": self._match_text_seq("BEFORE"),
 2864            "default": self._match_text_seq("DEFAULT"),
 2865            "local": (self._match_text_seq("LOCAL") and "LOCAL")
 2866            or (self._match_text_seq("NOT", "LOCAL") and "NOT LOCAL"),
 2867            "after": self._match_text_seq("AFTER"),
 2868            "minimum": self._match_texts(("MIN", "MINIMUM")),
 2869            "maximum": self._match_texts(("MAX", "MAXIMUM")),
 2870        }
 2871
 2872        if self._match_texts(self.PROPERTY_PARSERS):
 2873            parser = self.PROPERTY_PARSERS[self._prev.text.upper()]
 2874            try:
 2875                return parser(self, **{k: v for k, v in kwargs.items() if v})
 2876            except TypeError:
 2877                self.raise_error(f"Cannot parse property '{self._prev.text}'")
 2878
 2879        if self._match_text_seq("CHARACTER", "SET"):
 2880            return self._parse_character_set(default=bool(kwargs["default"]))
 2881
 2882        return None
 2883
 2884    def _parse_wrapped_properties(self) -> list[exp.Expr | list[exp.Expr]]:
 2885        return self._parse_wrapped_csv(self._parse_property)
 2886
 2887    def _parse_property(self) -> exp.Expr | list[exp.Expr] | None:
 2888        if self._match_texts(self.PROPERTY_PARSERS):
 2889            return self.PROPERTY_PARSERS[self._prev.text.upper()](self)
 2890
 2891        if self._match_text_seq("CHARACTER", "SET"):
 2892            return self._parse_character_set()
 2893
 2894        if self._match(TokenType.DEFAULT):
 2895            if self._match_texts(self.PROPERTY_PARSERS):
 2896                return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True)
 2897
 2898            if self._match_text_seq("CHARACTER", "SET"):
 2899                return self._parse_character_set(default=True)
 2900
 2901        if self._match_text_seq("COMPOUND", "SORTKEY"):
 2902            return self._parse_sortkey(compound=True)
 2903
 2904        if self._match_text_seq("PARAMETER", "STYLE", "PANDAS"):
 2905            return self.expression(exp.ParameterStyleProperty(this="PANDAS"))
 2906
 2907        index = self._index
 2908
 2909        seq_props = self._parse_sequence_properties()
 2910        if seq_props:
 2911            return seq_props
 2912
 2913        self._retreat(index)
 2914        return self._parse_key_value_property()
 2915
 2916    def _parse_key_value_property(
 2917        self, parse_value: t.Callable[[], exp.Expr | None] | None = None
 2918    ) -> exp.Property | None:
 2919        index = self._index
 2920        key = self._parse_column()
 2921
 2922        if not self._match(TokenType.EQ):
 2923            self._retreat(index)
 2924            return None
 2925
 2926        # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise
 2927        if isinstance(key, exp.Column):
 2928            key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name)
 2929
 2930        value = (
 2931            parse_value()
 2932            if parse_value
 2933            else self._parse_bitwise() or self._parse_var(any_token=True)
 2934        )
 2935
 2936        # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier())
 2937        if isinstance(value, exp.Column):
 2938            value = exp.var(value.name)
 2939
 2940        return self.expression(exp.Property(this=key, value=value))
 2941
 2942    def _parse_stored(self) -> exp.FileFormatProperty | exp.StorageHandlerProperty:
 2943        if self._match_text_seq("BY"):
 2944            return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string()))
 2945
 2946        self._match(TokenType.ALIAS)
 2947        input_format = self._parse_string() if self._match_text_seq("INPUTFORMAT") else None
 2948        output_format = self._parse_string() if self._match_text_seq("OUTPUTFORMAT") else None
 2949
 2950        return self.expression(
 2951            exp.FileFormatProperty(
 2952                this=(
 2953                    self.expression(
 2954                        exp.InputOutputFormat(
 2955                            input_format=input_format, output_format=output_format
 2956                        )
 2957                    )
 2958                    if input_format or output_format
 2959                    else self._parse_var_or_string() or self._parse_number() or self._parse_id_var()
 2960                ),
 2961                hive_format=True,
 2962            )
 2963        )
 2964
 2965    def _parse_unquoted_field(self) -> exp.Expr | None:
 2966        field = self._parse_field()
 2967        if isinstance(field, exp.Identifier) and not field.quoted:
 2968            field = exp.var(field)
 2969
 2970        return field
 2971
 2972    def _parse_property_assignment(self, exp_class: type[E], **kwargs: t.Any) -> E:
 2973        self._match(TokenType.EQ)
 2974        self._match(TokenType.ALIAS)
 2975
 2976        return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs))
 2977
 2978    def _parse_properties(self, before: bool | None = None) -> exp.Properties | None:
 2979        properties = []
 2980        while True:
 2981            if before:
 2982                prop = self._parse_property_before()
 2983            else:
 2984                prop = self._parse_property()
 2985            if not prop:
 2986                break
 2987            for p in ensure_list(prop):
 2988                properties.append(p)
 2989
 2990        if properties:
 2991            return self.expression(exp.Properties(expressions=properties))
 2992
 2993        return None
 2994
 2995    def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty:
 2996        return self.expression(
 2997            exp.FallbackProperty(no=no, protection=self._match_text_seq("PROTECTION"))
 2998        )
 2999
 3000    def _parse_sql_security(self) -> exp.SqlSecurityProperty:
 3001        return self.expression(
 3002            exp.SqlSecurityProperty(
 3003                this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper()
 3004            )
 3005        )
 3006
 3007    def _parse_settings_property(self) -> exp.SettingsProperty:
 3008        return self.expression(
 3009            exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment))
 3010        )
 3011
 3012    def _parse_called_on_null_input_property(self) -> exp.CalledOnNullInputProperty | None:
 3013        if not self._match_text_seq("ON", "NULL", "INPUT"):
 3014            self._retreat(self._index - 1)
 3015            return None
 3016
 3017        return self.expression(exp.CalledOnNullInputProperty())
 3018
 3019    def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty:
 3020        if self._index >= 2:
 3021            pre_volatile_token = self._tokens[self._index - 2]
 3022        else:
 3023            pre_volatile_token = None
 3024
 3025        if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS:
 3026            return exp.VolatileProperty()
 3027
 3028        return self.expression(exp.StabilityProperty(this=exp.Literal.string("VOLATILE")))
 3029
 3030    def _parse_retention_period(self) -> exp.Var:
 3031        # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...}
 3032        number = self._parse_number()
 3033        number_str = f"{number} " if number else ""
 3034        unit = self._parse_var(any_token=True)
 3035        return exp.var(f"{number_str}{unit}")
 3036
 3037    def _parse_system_versioning_property(
 3038        self, with_: bool = False
 3039    ) -> exp.WithSystemVersioningProperty:
 3040        self._match(TokenType.EQ)
 3041        prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_))
 3042
 3043        if self._match_text_seq("OFF"):
 3044            prop.set("on", False)
 3045            return prop
 3046
 3047        self._match(TokenType.ON)
 3048        if self._match(TokenType.L_PAREN):
 3049            while self._curr and not self._match(TokenType.R_PAREN):
 3050                if self._match_text_seq("HISTORY_TABLE", "="):
 3051                    prop.set("this", self._parse_table_parts())
 3052                elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="):
 3053                    prop.set("data_consistency", self._advance_any() and self._prev.text.upper())
 3054                elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="):
 3055                    prop.set("retention_period", self._parse_retention_period())
 3056
 3057                self._match(TokenType.COMMA)
 3058
 3059        return prop
 3060
 3061    def _parse_data_deletion_property(self) -> exp.DataDeletionProperty:
 3062        self._match(TokenType.EQ)
 3063        on = self._match_text_seq("ON") or not self._match_text_seq("OFF")
 3064        prop = self.expression(exp.DataDeletionProperty(on=on))
 3065
 3066        if self._match(TokenType.L_PAREN):
 3067            while self._curr and not self._match(TokenType.R_PAREN):
 3068                if self._match_text_seq("FILTER_COLUMN", "="):
 3069                    prop.set("filter_column", self._parse_column())
 3070                elif self._match_text_seq("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_distributed_property(self) -> exp.DistributedByProperty:
 3078        kind = "HASH"
 3079        expressions: list[exp.Expr] | None = None
 3080        if self._match_text_seq("BY", "HASH"):
 3081            expressions = self._parse_wrapped_csv(self._parse_id_var)
 3082        elif self._match_text_seq("BY", "RANDOM"):
 3083            kind = "RANDOM"
 3084
 3085        # If the BUCKETS keyword is not present, the number of buckets is AUTO
 3086        buckets: exp.Expr | None = None
 3087        if self._match_text_seq("BUCKETS") and not self._match_text_seq("AUTO"):
 3088            buckets = self._parse_number()
 3089
 3090        return self.expression(
 3091            exp.DistributedByProperty(
 3092                expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order()
 3093            )
 3094        )
 3095
 3096    def _parse_composite_key_property(self, expr_type: type[E]) -> E:
 3097        self._match_text_seq("KEY")
 3098        expressions = self._parse_wrapped_id_vars()
 3099        return self.expression(expr_type(expressions=expressions))
 3100
 3101    def _parse_with_property(self) -> exp.Expr | None | list[exp.Expr]:
 3102        if self._match_text_seq("(", "SYSTEM_VERSIONING"):
 3103            prop = self._parse_system_versioning_property(with_=True)
 3104            self._match_r_paren()
 3105            return prop
 3106
 3107        if self._match(TokenType.L_PAREN, advance=False):
 3108            result: list[exp.Expr] = []
 3109            for i in self._parse_wrapped_properties():
 3110                result.extend(i) if isinstance(i, list) else result.append(i)
 3111            return result
 3112
 3113        if self._match_text_seq("JOURNAL"):
 3114            return self._parse_withjournaltable()
 3115
 3116        if self._match_texts(self.VIEW_ATTRIBUTES):
 3117            return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper()))
 3118
 3119        if self._match_text_seq("DATA"):
 3120            return self._parse_withdata(no=False)
 3121        elif self._match_text_seq("NO", "DATA"):
 3122            return self._parse_withdata(no=True)
 3123
 3124        if self._match(TokenType.SERDE_PROPERTIES, advance=False):
 3125            return self._parse_serde_properties(with_=True)
 3126
 3127        if self._match(TokenType.SCHEMA):
 3128            return self.expression(
 3129                exp.WithSchemaBindingProperty(
 3130                    this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS)
 3131                )
 3132            )
 3133
 3134        if self._match_texts(self.PROCEDURE_OPTIONS, advance=False):
 3135            return self.expression(
 3136                exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option))
 3137            )
 3138
 3139        if not self._next:
 3140            return None
 3141
 3142        return self._parse_withisolatedloading()
 3143
 3144    def _parse_procedure_option(self) -> exp.Expr | None:
 3145        if self._match_text_seq("EXECUTE", "AS"):
 3146            return self.expression(
 3147                exp.ExecuteAsProperty(
 3148                    this=self._parse_var_from_options(
 3149                        self.EXECUTE_AS_OPTIONS, raise_unmatched=False
 3150                    )
 3151                    or self._parse_string()
 3152                )
 3153            )
 3154
 3155        return self._parse_var_from_options(self.PROCEDURE_OPTIONS)
 3156
 3157    # https://dev.mysql.com/doc/refman/8.0/en/create-view.html
 3158    def _parse_definer(self) -> exp.DefinerProperty | None:
 3159        self._match(TokenType.EQ)
 3160
 3161        user = self._parse_id_var()
 3162        self._match(TokenType.PARAMETER)
 3163        host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text)
 3164
 3165        if not user or not host:
 3166            return None
 3167
 3168        return exp.DefinerProperty(this=f"{user}@{host}")
 3169
 3170    def _parse_withjournaltable(self) -> exp.WithJournalTableProperty:
 3171        self._match(TokenType.TABLE)
 3172        self._match(TokenType.EQ)
 3173        return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts()))
 3174
 3175    def _parse_log(self, no: bool = False) -> exp.LogProperty:
 3176        return self.expression(exp.LogProperty(no=no))
 3177
 3178    def _parse_journal(self, **kwargs) -> exp.JournalProperty:
 3179        return self.expression(exp.JournalProperty(**kwargs))
 3180
 3181    def _parse_checksum(self) -> exp.ChecksumProperty:
 3182        self._match(TokenType.EQ)
 3183
 3184        on = None
 3185        if self._match(TokenType.ON):
 3186            on = True
 3187        elif self._match_text_seq("OFF"):
 3188            on = False
 3189
 3190        return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT)))
 3191
 3192    def _parse_cluster(self) -> exp.Cluster:
 3193        self._match(TokenType.CLUSTER_BY)
 3194        return self.expression(
 3195            exp.Cluster(
 3196                expressions=self._parse_csv(self._parse_column),
 3197            )
 3198        )
 3199
 3200    def _parse_cluster_property(self) -> exp.ClusterProperty:
 3201        return self.expression(
 3202            exp.ClusterProperty(
 3203                expressions=self._parse_wrapped_csv(self._parse_column),
 3204            )
 3205        )
 3206
 3207    def _parse_clustered_by(self) -> exp.ClusteredByProperty:
 3208        self._match_text_seq("BY")
 3209
 3210        self._match_l_paren()
 3211        expressions = self._parse_csv(self._parse_column)
 3212        self._match_r_paren()
 3213
 3214        if self._match_text_seq("SORTED", "BY"):
 3215            self._match_l_paren()
 3216            sorted_by = self._parse_csv(self._parse_ordered)
 3217            self._match_r_paren()
 3218        else:
 3219            sorted_by = None
 3220
 3221        self._match(TokenType.INTO)
 3222        buckets = self._parse_number()
 3223        self._match_text_seq("BUCKETS")
 3224
 3225        return self.expression(
 3226            exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets)
 3227        )
 3228
 3229    def _parse_copy_property(self) -> exp.CopyGrantsProperty | None:
 3230        if not self._match_text_seq("GRANTS"):
 3231            self._retreat(self._index - 1)
 3232            return None
 3233
 3234        return self.expression(exp.CopyGrantsProperty())
 3235
 3236    def _parse_freespace(self) -> exp.FreespaceProperty:
 3237        self._match(TokenType.EQ)
 3238        return self.expression(
 3239            exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT))
 3240        )
 3241
 3242    def _parse_mergeblockratio(
 3243        self, no: bool = False, default: bool = False
 3244    ) -> exp.MergeBlockRatioProperty:
 3245        if self._match(TokenType.EQ):
 3246            return self.expression(
 3247                exp.MergeBlockRatioProperty(
 3248                    this=self._parse_number(), percent=self._match(TokenType.PERCENT)
 3249                )
 3250            )
 3251
 3252        return self.expression(exp.MergeBlockRatioProperty(no=no, default=default))
 3253
 3254    def _parse_datablocksize(
 3255        self,
 3256        default: bool | None = None,
 3257        minimum: bool | None = None,
 3258        maximum: bool | None = None,
 3259    ) -> exp.DataBlocksizeProperty:
 3260        self._match(TokenType.EQ)
 3261        size = self._parse_number()
 3262
 3263        units = None
 3264        if self._match_texts(("BYTES", "KBYTES", "KILOBYTES")):
 3265            units = self._prev.text
 3266
 3267        return self.expression(
 3268            exp.DataBlocksizeProperty(
 3269                size=size, units=units, default=default, minimum=minimum, maximum=maximum
 3270            )
 3271        )
 3272
 3273    def _parse_blockcompression(self) -> exp.BlockCompressionProperty:
 3274        self._match(TokenType.EQ)
 3275        always = self._match_text_seq("ALWAYS")
 3276        manual = self._match_text_seq("MANUAL")
 3277        never = self._match_text_seq("NEVER")
 3278        default = self._match_text_seq("DEFAULT")
 3279
 3280        autotemp = None
 3281        if self._match_text_seq("AUTOTEMP"):
 3282            autotemp = self._parse_schema()
 3283
 3284        return self.expression(
 3285            exp.BlockCompressionProperty(
 3286                always=always, manual=manual, never=never, default=default, autotemp=autotemp
 3287            )
 3288        )
 3289
 3290    def _parse_withisolatedloading(self) -> exp.IsolatedLoadingProperty | None:
 3291        index = self._index
 3292        no = self._match_text_seq("NO")
 3293        concurrent = self._match_text_seq("CONCURRENT")
 3294
 3295        if not self._match_text_seq("ISOLATED", "LOADING"):
 3296            self._retreat(index)
 3297            return None
 3298
 3299        target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False)
 3300        return self.expression(
 3301            exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target)
 3302        )
 3303
 3304    def _parse_locking(self) -> exp.LockingProperty:
 3305        if self._match(TokenType.TABLE):
 3306            kind = "TABLE"
 3307        elif self._match(TokenType.VIEW):
 3308            kind = "VIEW"
 3309        elif self._match(TokenType.ROW):
 3310            kind = "ROW"
 3311        elif self._match_text_seq("DATABASE"):
 3312            kind = "DATABASE"
 3313        else:
 3314            kind = None
 3315
 3316        if kind in ("DATABASE", "TABLE", "VIEW"):
 3317            this = self._parse_table_parts()
 3318        else:
 3319            this = None
 3320
 3321        if self._match(TokenType.FOR):
 3322            for_or_in = "FOR"
 3323        elif self._match(TokenType.IN):
 3324            for_or_in = "IN"
 3325        else:
 3326            for_or_in = None
 3327
 3328        if self._match_text_seq("ACCESS"):
 3329            lock_type = "ACCESS"
 3330        elif self._match_texts(("EXCL", "EXCLUSIVE")):
 3331            lock_type = "EXCLUSIVE"
 3332        elif self._match_text_seq("SHARE"):
 3333            lock_type = "SHARE"
 3334        elif self._match_text_seq("READ"):
 3335            lock_type = "READ"
 3336        elif self._match_text_seq("WRITE"):
 3337            lock_type = "WRITE"
 3338        elif self._match_text_seq("CHECKSUM"):
 3339            lock_type = "CHECKSUM"
 3340        else:
 3341            lock_type = None
 3342
 3343        override = self._match_text_seq("OVERRIDE")
 3344
 3345        return self.expression(
 3346            exp.LockingProperty(
 3347                this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override
 3348            )
 3349        )
 3350
 3351    def _parse_partition_by(self) -> list[exp.Expr]:
 3352        if self._match(TokenType.PARTITION_BY):
 3353            return self._parse_csv(self._parse_disjunction)
 3354        return []
 3355
 3356    def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec:
 3357        def _parse_partition_bound_expr() -> exp.Expr | None:
 3358            if self._match_text_seq("MINVALUE"):
 3359                return exp.var("MINVALUE")
 3360            if self._match_text_seq("MAXVALUE"):
 3361                return exp.var("MAXVALUE")
 3362            return self._parse_bitwise()
 3363
 3364        this: exp.Expr | list[exp.Expr] | None = None
 3365        expression = None
 3366        from_expressions = None
 3367        to_expressions = None
 3368
 3369        if self._match(TokenType.IN):
 3370            this = self._parse_wrapped_csv(self._parse_bitwise)
 3371        elif self._match(TokenType.FROM):
 3372            from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3373            self._match_text_seq("TO")
 3374            to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)
 3375        elif self._match_text_seq("WITH", "(", "MODULUS"):
 3376            this = self._parse_number()
 3377            self._match_text_seq(",", "REMAINDER")
 3378            expression = self._parse_number()
 3379            self._match_r_paren()
 3380        else:
 3381            self.raise_error("Failed to parse partition bound spec.")
 3382
 3383        return self.expression(
 3384            exp.PartitionBoundSpec(
 3385                this=this,
 3386                expression=expression,
 3387                from_expressions=from_expressions,
 3388                to_expressions=to_expressions,
 3389            )
 3390        )
 3391
 3392    # https://www.postgresql.org/docs/current/sql-createtable.html
 3393    def _parse_partitioned_of(self) -> exp.PartitionedOfProperty | None:
 3394        if not self._match_text_seq("OF"):
 3395            self._retreat(self._index - 1)
 3396            return None
 3397
 3398        this = self._parse_table(schema=True)
 3399
 3400        if self._match(TokenType.DEFAULT):
 3401            expression: exp.Var | exp.PartitionBoundSpec = exp.var("DEFAULT")
 3402        elif self._match_text_seq("FOR", "VALUES"):
 3403            expression = self._parse_partition_bound_spec()
 3404        else:
 3405            self.raise_error("Expecting either DEFAULT or FOR VALUES clause.")
 3406
 3407        return self.expression(exp.PartitionedOfProperty(this=this, expression=expression))
 3408
 3409    def _parse_partitioned_by(self) -> exp.PartitionedByProperty:
 3410        self._match(TokenType.EQ)
 3411        return self.expression(
 3412            exp.PartitionedByProperty(
 3413                this=self._parse_schema() or self._parse_bracket(self._parse_field())
 3414            )
 3415        )
 3416
 3417    def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty:
 3418        if self._match_text_seq("AND", "STATISTICS"):
 3419            statistics = True
 3420        elif self._match_text_seq("AND", "NO", "STATISTICS"):
 3421            statistics = False
 3422        else:
 3423            statistics = None
 3424
 3425        return self.expression(exp.WithDataProperty(no=no, statistics=statistics))
 3426
 3427    def _parse_contains_property(self) -> exp.SqlReadWriteProperty | None:
 3428        if self._match_text_seq("SQL"):
 3429            return self.expression(exp.SqlReadWriteProperty(this="CONTAINS SQL"))
 3430        return None
 3431
 3432    def _parse_modifies_property(self) -> exp.SqlReadWriteProperty | None:
 3433        if self._match_text_seq("SQL", "DATA"):
 3434            return self.expression(exp.SqlReadWriteProperty(this="MODIFIES SQL DATA"))
 3435        return None
 3436
 3437    def _parse_no_property(self) -> exp.Expr | None:
 3438        if self._match_text_seq("PRIMARY", "INDEX"):
 3439            return exp.NoPrimaryIndexProperty()
 3440        if self._match_text_seq("SQL"):
 3441            return self.expression(exp.SqlReadWriteProperty(this="NO SQL"))
 3442        return None
 3443
 3444    def _parse_on_property(self) -> exp.Expr | None:
 3445        if self._match_text_seq("COMMIT", "PRESERVE", "ROWS"):
 3446            return exp.OnCommitProperty()
 3447        if self._match_text_seq("COMMIT", "DELETE", "ROWS"):
 3448            return exp.OnCommitProperty(delete=True)
 3449        return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var())))
 3450
 3451    def _parse_reads_property(self) -> exp.SqlReadWriteProperty | None:
 3452        if self._match_text_seq("SQL", "DATA"):
 3453            return self.expression(exp.SqlReadWriteProperty(this="READS SQL DATA"))
 3454        return None
 3455
 3456    def _parse_distkey(self) -> exp.DistKeyProperty:
 3457        return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var)))
 3458
 3459    def _parse_create_like(self) -> exp.LikeProperty | None:
 3460        table = self._parse_table(schema=True)
 3461
 3462        options = []
 3463        while self._match_texts(("INCLUDING", "EXCLUDING")):
 3464            this = self._prev.text.upper()
 3465
 3466            id_var = self._parse_id_var()
 3467            if not id_var:
 3468                return None
 3469
 3470            options.append(
 3471                self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper())))
 3472            )
 3473
 3474        return self.expression(exp.LikeProperty(this=table, expressions=options))
 3475
 3476    def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty:
 3477        return self.expression(
 3478            exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound)
 3479        )
 3480
 3481    def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty:
 3482        self._match(TokenType.EQ)
 3483        return self.expression(
 3484            exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default)
 3485        )
 3486
 3487    def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty:
 3488        self._match_text_seq("WITH", "CONNECTION")
 3489        return self.expression(
 3490            exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts())
 3491        )
 3492
 3493    def _parse_returns(self) -> exp.ReturnsProperty:
 3494        value: exp.Expr | None
 3495        null = None
 3496        is_table = self._match(TokenType.TABLE)
 3497
 3498        if is_table:
 3499            if self._match(TokenType.LT):
 3500                value = self.expression(
 3501                    exp.Schema(this="TABLE", expressions=self._parse_csv(self._parse_struct_types))
 3502                )
 3503                if not self._match(TokenType.GT):
 3504                    self.raise_error("Expecting >")
 3505            else:
 3506                value = self._parse_schema(exp.var("TABLE"))
 3507        elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"):
 3508            null = True
 3509            value = None
 3510        else:
 3511            value = self._parse_types()
 3512
 3513        return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null))
 3514
 3515    def _parse_describe(self) -> exp.Describe:
 3516        kind = self._prev.text if self._match_set(self.CREATABLES) else None
 3517        style: str | None = (
 3518            self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None
 3519        )
 3520        if self._match(TokenType.DOT):
 3521            style = None
 3522            self._retreat(self._index - 2)
 3523
 3524        format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None
 3525
 3526        if self._match_set(self.STATEMENT_PARSERS, advance=False):
 3527            this = self._parse_statement()
 3528        else:
 3529            this = self._parse_table(schema=True)
 3530
 3531        properties = self._parse_properties()
 3532        expressions = properties.expressions if properties else None
 3533        partition = self._parse_partition()
 3534        return self.expression(
 3535            exp.Describe(
 3536                this=this,
 3537                style=style,
 3538                kind=kind,
 3539                expressions=expressions,
 3540                partition=partition,
 3541                format=format,
 3542                as_json=self._match_text_seq("AS", "JSON"),
 3543            )
 3544        )
 3545
 3546    def _parse_multitable_inserts(self, comments: list[str] | None) -> exp.MultitableInserts:
 3547        kind = self._prev.text.upper()
 3548        expressions = []
 3549
 3550        def parse_conditional_insert() -> exp.ConditionalInsert | None:
 3551            if self._match(TokenType.WHEN):
 3552                expression = self._parse_disjunction()
 3553                self._match(TokenType.THEN)
 3554            else:
 3555                expression = None
 3556
 3557            else_ = self._match(TokenType.ELSE)
 3558
 3559            if not self._match(TokenType.INTO):
 3560                return None
 3561
 3562            return self.expression(
 3563                exp.ConditionalInsert(
 3564                    this=self.expression(
 3565                        exp.Insert(
 3566                            this=self._parse_table(schema=True),
 3567                            expression=self._parse_derived_table_values(),
 3568                        )
 3569                    ),
 3570                    expression=expression,
 3571                    else_=else_,
 3572                )
 3573            )
 3574
 3575        expression = parse_conditional_insert()
 3576        while expression is not None:
 3577            expressions.append(expression)
 3578            expression = parse_conditional_insert()
 3579
 3580        return self.expression(
 3581            exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()),
 3582            comments=comments,
 3583        )
 3584
 3585    def _parse_insert(self) -> exp.Insert | exp.MultitableInserts:
 3586        comments: list[str] = []
 3587        hint = self._parse_hint()
 3588        overwrite = self._match(TokenType.OVERWRITE)
 3589        ignore = self._match(TokenType.IGNORE)
 3590        local = self._match_text_seq("LOCAL")
 3591        alternative = None
 3592        is_function = None
 3593
 3594        if self._match_text_seq("DIRECTORY"):
 3595            this: exp.Expr | None = self.expression(
 3596                exp.Directory(
 3597                    this=self._parse_var_or_string(),
 3598                    local=local,
 3599                    row_format=self._parse_row_format(match_row=True),
 3600                )
 3601            )
 3602        else:
 3603            if self._match_set((TokenType.FIRST, TokenType.ALL)):
 3604                comments += ensure_list(self._prev_comments)
 3605                return self._parse_multitable_inserts(comments)
 3606
 3607            if self._match(TokenType.OR):
 3608                alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text
 3609
 3610            self._match(TokenType.INTO)
 3611            comments += ensure_list(self._prev_comments)
 3612            self._match(TokenType.TABLE)
 3613            is_function = self._match(TokenType.FUNCTION)
 3614
 3615            this = self._parse_function() if is_function else self._parse_insert_table()
 3616
 3617        # MySQL's INSERT ... SET is normalized into the INSERT ... (cols) VALUES (vals) variant
 3618        set_values = None
 3619        if self._match(TokenType.SET):
 3620            columns = []
 3621            values = []
 3622
 3623            def _parse_set_assignment() -> exp.Expr | None:
 3624                target = self._parse_column()
 3625                if isinstance(target, exp.Column) and self._match(TokenType.EQ):
 3626                    if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3627                        value: exp.Expr | None = exp.var(self._prev.text.upper())
 3628                    else:
 3629                        value = self._parse_disjunction()
 3630
 3631                    if value:
 3632                        columns.append(target.this)
 3633                        values.append(value)
 3634                        return value
 3635
 3636                self.raise_error("Expected column assignment in INSERT ... SET")
 3637                return None
 3638
 3639            self._parse_csv(_parse_set_assignment)
 3640
 3641            this = self.expression(exp.Schema(this=this, expressions=columns))
 3642            set_values = self.expression(
 3643                exp.Values(
 3644                    expressions=[exp.Tuple(expressions=values)],
 3645                    alias=self._parse_table_alias(),
 3646                )
 3647            )
 3648
 3649        returning = self._parse_returning()  # TSQL allows RETURNING before source
 3650
 3651        stored = self._match_text_seq("STORED") and self._parse_stored()
 3652        by_name = self._match_text_seq("BY", "NAME")
 3653        exists = self._parse_exists()
 3654        replace_where = None
 3655        replace_using = None
 3656
 3657        if self._match(TokenType.REPLACE):
 3658            if self._match(TokenType.WHERE):
 3659                replace_where = self._parse_disjunction()
 3660            elif self._match(TokenType.USING):
 3661                replace_using = self._parse_using_identifiers()
 3662
 3663        return self.expression(
 3664            exp.Insert(
 3665                hint=hint,
 3666                is_function=is_function,
 3667                this=this,
 3668                stored=stored,
 3669                by_name=by_name,
 3670                exists=exists,
 3671                where=replace_where,
 3672                using=replace_using,
 3673                partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(),
 3674                settings=self._match_text_seq("SETTINGS") and self._parse_settings_property(),
 3675                default=self._match_text_seq("DEFAULT", "VALUES"),
 3676                expression=set_values
 3677                or self._parse_derived_table_values()
 3678                or self._parse_ddl_select(),
 3679                conflict=self._parse_on_conflict(),
 3680                returning=returning or self._parse_returning(),
 3681                overwrite=overwrite,
 3682                alternative=alternative,
 3683                ignore=ignore,
 3684                source=self._match(TokenType.TABLE) and self._parse_table(),
 3685            ),
 3686            comments=comments,
 3687        )
 3688
 3689    def _parse_insert_table(self) -> exp.Expr | None:
 3690        this = self._parse_table(schema=True, parse_partition=True)
 3691        if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False):
 3692            this.set("alias", self._parse_table_alias())
 3693        return this
 3694
 3695    def _parse_kill(self) -> exp.Kill:
 3696        kind = exp.var(self._prev.text) if self._match_texts(("CONNECTION", "QUERY")) else None
 3697
 3698        return self.expression(exp.Kill(this=self._parse_primary(), kind=kind))
 3699
 3700    def _parse_on_conflict(self) -> exp.OnConflict | None:
 3701        conflict = self._match_text_seq("ON", "CONFLICT")
 3702        duplicate = self._match_text_seq("ON", "DUPLICATE", "KEY")
 3703
 3704        if not conflict and not duplicate:
 3705            return None
 3706
 3707        conflict_keys = None
 3708        constraint = None
 3709
 3710        if conflict:
 3711            if self._match_text_seq("ON", "CONSTRAINT"):
 3712                constraint = self._parse_id_var()
 3713            elif self._match(TokenType.L_PAREN):
 3714                conflict_keys = self._parse_csv(self._parse_indexed_column)
 3715                self._match_r_paren()
 3716
 3717        index_predicate = self._parse_where()
 3718
 3719        action = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 3720        if self._prev.token_type == TokenType.UPDATE:
 3721            self._match(TokenType.SET)
 3722            expressions = self._parse_csv(self._parse_equality)
 3723        else:
 3724            expressions = None
 3725
 3726        return self.expression(
 3727            exp.OnConflict(
 3728                duplicate=duplicate,
 3729                expressions=expressions,
 3730                action=action,
 3731                conflict_keys=conflict_keys,
 3732                index_predicate=index_predicate,
 3733                constraint=constraint,
 3734                where=self._parse_where(),
 3735            )
 3736        )
 3737
 3738    def _parse_returning(self) -> exp.Returning | None:
 3739        if not self._match(TokenType.RETURNING):
 3740            return None
 3741        return self.expression(
 3742            exp.Returning(
 3743                expressions=self._parse_csv(self._parse_expression),
 3744                into=self._match(TokenType.INTO) and self._parse_table_part(),
 3745            )
 3746        )
 3747
 3748    def _parse_row(self) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3749        if not self._match(TokenType.FORMAT):
 3750            return None
 3751        return self._parse_row_format()
 3752
 3753    def _parse_serde_properties(self, with_: bool = False) -> exp.SerdeProperties | None:
 3754        index = self._index
 3755        with_ = with_ or self._match_text_seq("WITH")
 3756
 3757        if not self._match(TokenType.SERDE_PROPERTIES):
 3758            self._retreat(index)
 3759            return None
 3760        return self.expression(
 3761            exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_)
 3762        )
 3763
 3764    def _parse_row_format(
 3765        self, match_row: bool = False
 3766    ) -> exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty | None:
 3767        if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT):
 3768            return None
 3769
 3770        if self._match_text_seq("SERDE"):
 3771            this = self._parse_string()
 3772
 3773            serde_properties = self._parse_serde_properties()
 3774
 3775            return self.expression(
 3776                exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties)
 3777            )
 3778
 3779        self._match_text_seq("DELIMITED")
 3780
 3781        kwargs = {}
 3782
 3783        if self._match_text_seq("FIELDS", "TERMINATED", "BY"):
 3784            kwargs["fields"] = self._parse_string()
 3785            if self._match_text_seq("ESCAPED", "BY"):
 3786                kwargs["escaped"] = self._parse_string()
 3787        if self._match_text_seq("COLLECTION", "ITEMS", "TERMINATED", "BY"):
 3788            kwargs["collection_items"] = self._parse_string()
 3789        if self._match_text_seq("MAP", "KEYS", "TERMINATED", "BY"):
 3790            kwargs["map_keys"] = self._parse_string()
 3791        if self._match_text_seq("LINES", "TERMINATED", "BY"):
 3792            kwargs["lines"] = self._parse_string()
 3793        if self._match_text_seq("NULL", "DEFINED", "AS"):
 3794            kwargs["null"] = self._parse_string()
 3795
 3796        return self.expression(exp.RowFormatDelimitedProperty(**kwargs))  # type: ignore
 3797
 3798    def _parse_load(self) -> exp.LoadData | exp.Command:
 3799        if self._match_text_seq("DATA"):
 3800            local = self._match_text_seq("LOCAL")
 3801            self._match_text_seq("INPATH")
 3802            inpath = self._parse_string()
 3803            overwrite = self._match(TokenType.OVERWRITE)
 3804            temp: bool | None = None
 3805            if self._match(TokenType.INTO):
 3806                temp = self._match(TokenType.TEMPORARY)
 3807                self._match(TokenType.TABLE)
 3808
 3809            return self.expression(
 3810                exp.LoadData(
 3811                    this=self._parse_table(schema=True),
 3812                    local=local,
 3813                    overwrite=overwrite,
 3814                    temp=temp,
 3815                    inpath=inpath,
 3816                    files=self._match_text_seq("FROM", "FILES")
 3817                    and exp.Properties(expressions=self._parse_wrapped_properties()),
 3818                    partition=self._parse_partition(),
 3819                    input_format=self._match_text_seq("INPUTFORMAT") and self._parse_string(),
 3820                    serde=self._match_text_seq("SERDE") and self._parse_string(),
 3821                )
 3822            )
 3823        return self._parse_as_command(self._prev)
 3824
 3825    def _parse_delete(self) -> exp.Delete:
 3826        hint = self._parse_hint()
 3827
 3828        # This handles MySQL's "Multiple-Table Syntax"
 3829        # https://dev.mysql.com/doc/refman/8.0/en/delete.html
 3830        tables = None
 3831        if not self._match(TokenType.FROM, advance=False):
 3832            tables = self._parse_csv(self._parse_table) or None
 3833
 3834        returning = self._parse_returning()
 3835
 3836        return self.expression(
 3837            exp.Delete(
 3838                hint=hint,
 3839                tables=tables,
 3840                this=self._match(TokenType.FROM) and self._parse_table(joins=True),
 3841                using=self._match(TokenType.USING)
 3842                and self._parse_csv(lambda: self._parse_table(joins=True)),
 3843                cluster=self._match(TokenType.ON) and self._parse_on_property(),
 3844                where=self._parse_where(),
 3845                returning=returning or self._parse_returning(),
 3846                order=self._parse_order(),
 3847                limit=self._parse_limit(),
 3848            )
 3849        )
 3850
 3851    def _parse_update(self) -> exp.Update:
 3852        hint = self._parse_hint()
 3853        kwargs: dict[str, object] = {
 3854            "hint": hint,
 3855            "this": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS),
 3856        }
 3857        while self._curr:
 3858            if self._match(TokenType.SET):
 3859                kwargs["expressions"] = self._parse_csv(self._parse_equality)
 3860            elif self._match(TokenType.RETURNING, advance=False):
 3861                kwargs["returning"] = self._parse_returning()
 3862            elif self._match(TokenType.FROM, advance=False):
 3863                from_ = self._parse_from(joins=True)
 3864                table = from_.this if from_ else None
 3865                if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False):
 3866                    table.set("joins", list(self._parse_joins()) or None)
 3867
 3868                kwargs["from_"] = from_
 3869            elif self._match(TokenType.WHERE, advance=False):
 3870                kwargs["where"] = self._parse_where()
 3871            elif self._match(TokenType.ORDER_BY, advance=False):
 3872                kwargs["order"] = self._parse_order()
 3873            elif self._match(TokenType.LIMIT, advance=False):
 3874                kwargs["limit"] = self._parse_limit()
 3875            else:
 3876                break
 3877
 3878        return self.expression(exp.Update(**kwargs))
 3879
 3880    def _parse_use(self) -> exp.Use:
 3881        return self.expression(
 3882            exp.Use(
 3883                kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False),
 3884                this=self._parse_table(schema=False),
 3885            )
 3886        )
 3887
 3888    def _parse_uncache(self) -> exp.Uncache:
 3889        if not self._match(TokenType.TABLE):
 3890            self.raise_error("Expecting TABLE after UNCACHE")
 3891
 3892        return self.expression(
 3893            exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True))
 3894        )
 3895
 3896    def _parse_cache(self) -> exp.Cache:
 3897        lazy = self._match_text_seq("LAZY")
 3898        self._match(TokenType.TABLE)
 3899        table = self._parse_table(schema=True)
 3900
 3901        options = []
 3902        if self._match_text_seq("OPTIONS"):
 3903            self._match_l_paren()
 3904            k = self._parse_string()
 3905            self._match(TokenType.EQ)
 3906            v = self._parse_string()
 3907            options = [k, v]
 3908            self._match_r_paren()
 3909
 3910        self._match(TokenType.ALIAS)
 3911        return self.expression(
 3912            exp.Cache(
 3913                this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True)
 3914            )
 3915        )
 3916
 3917    def _parse_partition(self) -> exp.Partition | None:
 3918        if not self._match_texts(self.PARTITION_KEYWORDS):
 3919            return None
 3920
 3921        return self.expression(
 3922            exp.Partition(
 3923                subpartition=self._prev.text.upper() == "SUBPARTITION",
 3924                expressions=self._parse_wrapped_csv(self._parse_disjunction),
 3925            )
 3926        )
 3927
 3928    def _parse_value(self, values: bool = True) -> exp.Tuple | None:
 3929        def _parse_value_expression() -> exp.Expr | None:
 3930            if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):
 3931                return exp.var(self._prev.text.upper())
 3932            return self._parse_expression()
 3933
 3934        if self._match(TokenType.L_PAREN):
 3935            expressions = self._parse_csv(_parse_value_expression)
 3936            self._match_r_paren()
 3937            return self.expression(exp.Tuple(expressions=expressions))
 3938
 3939        # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows.
 3940        expression = self._parse_expression()
 3941        if expression:
 3942            return self.expression(exp.Tuple(expressions=[expression]))
 3943        return None
 3944
 3945    def _parse_projections(
 3946        self,
 3947    ) -> tuple[list[exp.Expr], list[exp.Expr] | None]:
 3948        return self._parse_expressions(), None
 3949
 3950    def _parse_wrapped_select(self, table: bool = False) -> exp.Expr | None:
 3951        if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)):
 3952            this: exp.Expr | None = self._parse_simplified_pivot(
 3953                is_unpivot=self._prev.token_type == TokenType.UNPIVOT
 3954            )
 3955        elif self._match(TokenType.FROM):
 3956            from_ = self._parse_from(joins=True, skip_from_token=True, consume_pipe=True)
 3957            # Support parentheses for duckdb FROM-first syntax
 3958            select = self._parse_select(from_=from_)
 3959            if select:
 3960                if not select.args.get("from_"):
 3961                    select.set("from_", from_)
 3962                this = select
 3963            else:
 3964                this = exp.select("*").from_(t.cast(exp.From, from_))
 3965                this = self._parse_query_modifiers(self._parse_set_operations(this))
 3966        else:
 3967            this = (
 3968                self._parse_table(consume_pipe=True)
 3969                if table
 3970                else self._parse_select(nested=True, parse_set_operation=False)
 3971            )
 3972
 3973            # Transform exp.Values into a exp.Table to pass through parse_query_modifiers
 3974            # in case a modifier (e.g. join) is following
 3975            if table and isinstance(this, exp.Values) and this.alias:
 3976                alias = this.args["alias"].pop()
 3977                this = exp.Table(this=this, alias=alias)
 3978
 3979            this = self._parse_query_modifiers(self._parse_set_operations(this))
 3980
 3981        return this
 3982
 3983    def _parse_select(
 3984        self,
 3985        nested: bool = False,
 3986        table: bool = False,
 3987        parse_subquery_alias: bool = True,
 3988        parse_set_operation: bool = True,
 3989        consume_pipe: bool = True,
 3990        from_: exp.From | None = None,
 3991    ) -> exp.Expr | None:
 3992        query = self._parse_select_query(
 3993            nested=nested,
 3994            table=table,
 3995            parse_subquery_alias=parse_subquery_alias,
 3996            parse_set_operation=parse_set_operation,
 3997        )
 3998
 3999        if consume_pipe and self._match(TokenType.PIPE_GT, advance=False):
 4000            if not query and from_:
 4001                query = exp.select("*").from_(from_)
 4002            if isinstance(query, exp.Query):
 4003                query = self._parse_pipe_syntax_query(query)
 4004                query = query.subquery(copy=False) if query and table else query
 4005
 4006        return query
 4007
 4008    def _parse_select_query(
 4009        self,
 4010        nested: bool = False,
 4011        table: bool = False,
 4012        parse_subquery_alias: bool = True,
 4013        parse_set_operation: bool = True,
 4014    ) -> exp.Expr | None:
 4015        cte = self._parse_with()
 4016
 4017        if cte:
 4018            this = self._parse_statement()
 4019
 4020            if not this:
 4021                self.raise_error("Failed to parse any statement following CTE")
 4022                return cte
 4023
 4024            while isinstance(this, exp.Subquery) and this.is_wrapper:
 4025                this = this.this
 4026
 4027            assert this is not None
 4028            if "with_" in this.arg_types:
 4029                if inner_cte := this.args.get("with_"):
 4030                    cte.set("expressions", cte.expressions + inner_cte.expressions)
 4031                    if inner_cte.args.get("recursive"):
 4032                        cte.set("recursive", True)
 4033                this.set("with_", cte)
 4034            else:
 4035                self.raise_error(f"{this.key} does not support CTE")
 4036                this = cte
 4037
 4038            return this
 4039
 4040        # duckdb supports leading with FROM x
 4041        from_ = (
 4042            self._parse_from(joins=True, consume_pipe=True)
 4043            if self._match(TokenType.FROM, advance=False)
 4044            else None
 4045        )
 4046
 4047        if self._match(TokenType.SELECT):
 4048            comments = self._prev_comments
 4049
 4050            hint = self._parse_hint()
 4051
 4052            if self._next and not self._next.token_type == TokenType.DOT:
 4053                all_ = self._match(TokenType.ALL)
 4054                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4055            else:
 4056                all_, matched_distinct = None, False
 4057
 4058            kind = (
 4059                self._prev.text.upper()
 4060                if self._match(TokenType.ALIAS) and self._match_texts(("STRUCT", "VALUE"))
 4061                else None
 4062            )
 4063
 4064            distinct: exp.Expr | None = (
 4065                self.expression(
 4066                    exp.Distinct(
 4067                        on=self._parse_value(values=False) if self._match(TokenType.ON) else None
 4068                    )
 4069                )
 4070                if matched_distinct
 4071                else None
 4072            )
 4073
 4074            operation_modifiers = []
 4075            while self._curr and self._match_texts(self.OPERATION_MODIFIERS):
 4076                operation_modifiers.append(exp.var(self._prev.text.upper()))
 4077
 4078            limit = self._parse_limit(top=True)
 4079
 4080            # Some dialects (e.g. Redshift, T-SQL) allow SELECT TOP N DISTINCT ...
 4081            if limit and not matched_distinct and not all_:
 4082                matched_distinct = self._match_set(self.DISTINCT_TOKENS)
 4083                if matched_distinct:
 4084                    distinct = self.expression(
 4085                        exp.Distinct(
 4086                            on=self._parse_value(values=False)
 4087                            if self._match(TokenType.ON)
 4088                            else None
 4089                        )
 4090                    )
 4091                else:
 4092                    all_ = self._match(TokenType.ALL)
 4093
 4094            if all_ and distinct:
 4095                self.raise_error("Cannot specify both ALL and DISTINCT after SELECT")
 4096
 4097            projections, exclude = self._parse_projections()
 4098
 4099            this = self.expression(
 4100                exp.Select(
 4101                    kind=kind,
 4102                    hint=hint,
 4103                    distinct=distinct,
 4104                    expressions=projections,
 4105                    limit=limit,
 4106                    exclude=exclude,
 4107                    operation_modifiers=operation_modifiers or None,
 4108                )
 4109            )
 4110            this.comments = comments
 4111
 4112            into = self._parse_into()
 4113            if into:
 4114                this.set("into", into)
 4115
 4116            if not from_:
 4117                from_ = self._parse_from()
 4118
 4119            if from_:
 4120                this.set("from_", from_)
 4121
 4122            this = self._parse_query_modifiers(this)
 4123        elif (table or nested) and self._match(TokenType.L_PAREN):
 4124            comments = self._prev_comments
 4125            this = self._parse_wrapped_select(table=table)
 4126
 4127            if this:
 4128                this.add_comments(comments, prepend=True)
 4129
 4130            # We return early here so that the UNION isn't attached to the subquery by the
 4131            # following call to _parse_set_operations, but instead becomes the parent node
 4132            self._match_r_paren()
 4133            return self._parse_subquery(this, parse_alias=parse_subquery_alias)
 4134        elif self._match(TokenType.VALUES, advance=False):
 4135            this = self._parse_derived_table_values()
 4136        elif from_:
 4137            this = exp.select("*").from_(from_.this, copy=False)
 4138            this = self._parse_query_modifiers(this)
 4139        elif self._match(TokenType.SUMMARIZE):
 4140            table = self._match(TokenType.TABLE)
 4141            this = self._parse_select() or self._parse_string() or self._parse_table()
 4142            return self.expression(exp.Summarize(this=this, table=table))
 4143        elif self._match(TokenType.DESCRIBE):
 4144            this = self._parse_describe()
 4145        else:
 4146            this = None
 4147
 4148        return self._parse_set_operations(this) if parse_set_operation else this
 4149
 4150    def _parse_recursive_with_search(self) -> exp.RecursiveWithSearch | None:
 4151        self._match_text_seq("SEARCH")
 4152
 4153        kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper()
 4154
 4155        if not kind:
 4156            return None
 4157
 4158        self._match_text_seq("FIRST", "BY")
 4159
 4160        return self.expression(
 4161            exp.RecursiveWithSearch(
 4162                kind=kind,
 4163                this=self._parse_id_var(),
 4164                expression=self._match_text_seq("SET") and self._parse_id_var(),
 4165                using=self._match_text_seq("USING") and self._parse_id_var(),
 4166            )
 4167        )
 4168
 4169    def _parse_with(self, skip_with_token: bool = False) -> exp.With | None:
 4170        if not skip_with_token and not self._match(TokenType.WITH):
 4171            return None
 4172
 4173        comments = self._prev_comments
 4174        recursive = self._match(TokenType.RECURSIVE)
 4175
 4176        last_comments = None
 4177        expressions = []
 4178        udfs = []
 4179        while True:
 4180            cte = self._parse_cte()
 4181            if cte:
 4182                if isinstance(cte, exp.FunctionSpecification):
 4183                    udfs.append(cte)
 4184                else:
 4185                    expressions.append(cte)
 4186
 4187                if last_comments:
 4188                    cte.add_comments(last_comments)
 4189
 4190            if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH):
 4191                break
 4192            else:
 4193                self._match(TokenType.WITH)
 4194                recursive = self._match(TokenType.RECURSIVE) or recursive
 4195
 4196            last_comments = self._prev_comments
 4197
 4198        return self.expression(
 4199            exp.With(
 4200                expressions=expressions,
 4201                recursive=recursive or None,
 4202                search=self._parse_recursive_with_search(),
 4203                udfs=udfs or None,
 4204            ),
 4205            comments=comments,
 4206        )
 4207
 4208    def _parse_cte(self) -> exp.CTE | exp.FunctionSpecification | None:
 4209        index = self._index
 4210
 4211        alias = self._parse_table_alias(self.ID_VAR_TOKENS)
 4212        if not alias or not alias.this:
 4213            self.raise_error("Expected CTE to have alias")
 4214
 4215        key_expressions = (
 4216            self._parse_wrapped_id_vars() if self._match_text_seq("USING", "KEY") else None
 4217        )
 4218
 4219        if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE:
 4220            self._retreat(index)
 4221            return None
 4222
 4223        comments = self._prev_comments
 4224
 4225        if self._match_text_seq("NOT", "MATERIALIZED"):
 4226            materialized = False
 4227        elif self._match_text_seq("MATERIALIZED"):
 4228            materialized = True
 4229        else:
 4230            materialized = None
 4231
 4232        cte = self.expression(
 4233            exp.CTE(
 4234                this=self._parse_wrapped(self._parse_statement),
 4235                alias=alias,
 4236                materialized=materialized,
 4237                key_expressions=key_expressions,
 4238            ),
 4239            comments=comments,
 4240        )
 4241
 4242        values = cte.this
 4243        if isinstance(values, exp.Values):
 4244            cte.set("this", self._values_to_select(values))
 4245
 4246        return cte
 4247
 4248    def _values_to_select(self, values: exp.Values) -> exp.Select:
 4249        if values.alias:
 4250            return exp.select("*").from_(values)
 4251        return exp.select("*").from_(exp.alias_(values, "_values", table=True))
 4252
 4253    def _parse_table_alias(
 4254        self, alias_tokens: t.Collection[TokenType] | None = None
 4255    ) -> exp.TableAlias | None:
 4256        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 4257        # so this section tries to parse the clause version and if it fails, it treats the token
 4258        # as an identifier (alias)
 4259        if self._can_parse_limit_or_offset():
 4260            return None
 4261
 4262        # START is never treated as an implicit alias when followed by WITH, since that
 4263        # would swallow the beginning of a START WITH ... CONNECT BY clause
 4264        if self._curr.text.upper() == "START" and self._next.text.upper() == "WITH":
 4265            return None
 4266
 4267        any_token = self._match(TokenType.ALIAS)
 4268        alias = (
 4269            self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 4270            or self._parse_string_as_identifier()
 4271        )
 4272
 4273        index = self._index
 4274        if self._match(TokenType.L_PAREN):
 4275            columns = self._parse_csv(self._parse_function_parameter)
 4276            self._match_r_paren() if columns else self._retreat(index)
 4277        else:
 4278            columns = None
 4279
 4280        if not alias and not columns:
 4281            return None
 4282
 4283        table_alias = self.expression(exp.TableAlias(this=alias, columns=columns))
 4284
 4285        # We bubble up comments from the Identifier to the TableAlias
 4286        if isinstance(alias, exp.Identifier):
 4287            table_alias.add_comments(alias.pop_comments())
 4288
 4289        return table_alias
 4290
 4291    def _parse_subquery(
 4292        self, this: exp.Expr | None, parse_alias: bool = True
 4293    ) -> exp.Subquery | None:
 4294        if not this:
 4295            return None
 4296
 4297        return self.expression(
 4298            exp.Subquery(
 4299                this=this,
 4300                pivots=self._parse_pivots(),
 4301                alias=self._parse_table_alias() if parse_alias else None,
 4302                sample=self._parse_table_sample(),
 4303            )
 4304        )
 4305
 4306    def _implicit_unnests_to_explicit(self, this: E) -> E:
 4307        from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm
 4308
 4309        refs = {_norm(this.args["from_"].this.copy(), dialect=self.dialect).alias_or_name}
 4310        for i, join in enumerate(this.args.get("joins") or []):
 4311            table = join.this
 4312            normalized_table = table.copy()
 4313            normalized_table.meta["maybe_column"] = True
 4314            normalized_table = _norm(normalized_table, dialect=self.dialect)
 4315
 4316            if isinstance(table, exp.Table) and not join.args.get("on"):
 4317                if len(normalized_table.parts) > 1 and normalized_table.parts[0].name in refs:
 4318                    table_as_column = table.to_column()
 4319                    unnest = exp.Unnest(expressions=[table_as_column])
 4320
 4321                    # Table.to_column creates a parent Alias node that we want to convert to
 4322                    # a TableAlias and attach to the Unnest, so it matches the parser's output
 4323                    if isinstance(table.args.get("alias"), exp.TableAlias):
 4324                        table_as_column.replace(table_as_column.this)
 4325                        exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False)
 4326
 4327                    table.replace(unnest)
 4328
 4329            refs.add(normalized_table.alias_or_name)
 4330
 4331        return this
 4332
 4333    @t.overload
 4334    def _parse_query_modifiers(self, this: E) -> E: ...
 4335
 4336    @t.overload
 4337    def _parse_query_modifiers(self, this: None) -> None: ...
 4338
 4339    def _parse_query_modifiers(self, this):
 4340        if isinstance(this, self.MODIFIABLES):
 4341            for join in self._parse_joins():
 4342                this.append("joins", join)
 4343            for lateral in iter(self._parse_lateral, None):
 4344                this.append("laterals", lateral)
 4345
 4346            while True:
 4347                if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):
 4348                    modifier_token = self._curr
 4349                    parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type]
 4350                    key, expression = parser(self)
 4351
 4352                    if expression:
 4353                        if this.args.get(key):
 4354                            self.raise_error(
 4355                                f"Found multiple '{modifier_token.text.upper()}' clauses",
 4356                                token=modifier_token,
 4357                            )
 4358
 4359                        this.set(key, expression)
 4360                        if key == "limit":
 4361                            offset = expression.args.get("offset")
 4362                            expression.set("offset", None)
 4363
 4364                            if offset:
 4365                                offset = exp.Offset(expression=offset)
 4366                                this.set("offset", offset)
 4367
 4368                                limit_by_expressions = expression.expressions
 4369                                expression.set("expressions", None)
 4370                                offset.set("expressions", limit_by_expressions)
 4371                        continue
 4372
 4373                if self._curr.text.upper() == "START":
 4374                    modifier_token = self._curr
 4375                    connect = self._parse_connect()
 4376                    if connect:
 4377                        if this.args.get("connect"):
 4378                            self.raise_error(
 4379                                "Found multiple 'START WITH' clauses", token=modifier_token
 4380                            )
 4381
 4382                        this.set("connect", connect)
 4383                        continue
 4384                break
 4385
 4386        if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get("from_"):
 4387            this = self._implicit_unnests_to_explicit(this)
 4388
 4389        return this
 4390
 4391    def _parse_hint_fallback_to_string(self) -> exp.Hint | None:
 4392        start = self._curr
 4393        while self._curr:
 4394            self._advance()
 4395
 4396        end = self._tokens[self._index - 1]
 4397        return exp.Hint(expressions=[self._find_sql(start, end)])
 4398
 4399    def _parse_hint_function_call(self) -> exp.Expr | None:
 4400        return self._parse_function_call()
 4401
 4402    def _parse_hint_body(self) -> exp.Hint | None:
 4403        start_index = self._index
 4404        should_fallback_to_string = False
 4405
 4406        hints = []
 4407        try:
 4408            for hint in iter(
 4409                lambda: self._parse_csv(
 4410                    lambda: self._parse_hint_function_call() or self._parse_var(upper=True),
 4411                ),
 4412                [],
 4413            ):
 4414                hints.extend(hint)
 4415        except ParseError:
 4416            should_fallback_to_string = True
 4417
 4418        if should_fallback_to_string or self._curr:
 4419            self._retreat(start_index)
 4420            return self._parse_hint_fallback_to_string()
 4421
 4422        return self.expression(exp.Hint(expressions=hints))
 4423
 4424    def _parse_hint(self) -> exp.Hint | None:
 4425        if self._match(TokenType.HINT) and self._prev_comments:
 4426            return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect)
 4427
 4428        return None
 4429
 4430    def _parse_into(self) -> exp.Into | None:
 4431        if not self._match(TokenType.INTO):
 4432            return None
 4433
 4434        temp = self._match(TokenType.TEMPORARY)
 4435        unlogged = self._match_text_seq("UNLOGGED")
 4436        self._match(TokenType.TABLE)
 4437
 4438        return self.expression(
 4439            exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged)
 4440        )
 4441
 4442    def _parse_from(
 4443        self,
 4444        joins: bool = False,
 4445        skip_from_token: bool = False,
 4446        consume_pipe: bool = False,
 4447    ) -> exp.From | None:
 4448        if not skip_from_token and not self._match(TokenType.FROM):
 4449            return None
 4450
 4451        comments = self._prev_comments
 4452        return self.expression(
 4453            exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)),
 4454            comments=comments,
 4455        )
 4456
 4457    def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure:
 4458        return self.expression(
 4459            exp.MatchRecognizeMeasure(
 4460                window_frame=self._match_texts(("FINAL", "RUNNING")) and self._prev.text.upper(),
 4461                this=self._parse_expression(),
 4462            )
 4463        )
 4464
 4465    def _parse_match_recognize(self) -> exp.MatchRecognize | None:
 4466        if not self._match(TokenType.MATCH_RECOGNIZE):
 4467            return None
 4468
 4469        self._match_l_paren()
 4470
 4471        partition = self._parse_partition_by()
 4472        order = self._parse_order()
 4473
 4474        measures = (
 4475            self._parse_csv(self._parse_match_recognize_measure)
 4476            if self._match_text_seq("MEASURES")
 4477            else None
 4478        )
 4479
 4480        if self._match_text_seq("ONE", "ROW", "PER", "MATCH"):
 4481            rows = exp.var("ONE ROW PER MATCH")
 4482        elif self._match_text_seq("ALL", "ROWS", "PER", "MATCH"):
 4483            text = "ALL ROWS PER MATCH"
 4484            if self._match_text_seq("SHOW", "EMPTY", "MATCHES"):
 4485                text += " SHOW EMPTY MATCHES"
 4486            elif self._match_text_seq("OMIT", "EMPTY", "MATCHES"):
 4487                text += " OMIT EMPTY MATCHES"
 4488            elif self._match_text_seq("WITH", "UNMATCHED", "ROWS"):
 4489                text += " WITH UNMATCHED ROWS"
 4490            rows = exp.var(text)
 4491        else:
 4492            rows = None
 4493
 4494        if self._match_text_seq("AFTER", "MATCH", "SKIP"):
 4495            text = "AFTER MATCH SKIP"
 4496            if self._match_text_seq("PAST", "LAST", "ROW"):
 4497                text += " PAST LAST ROW"
 4498            elif self._match_text_seq("TO", "NEXT", "ROW"):
 4499                text += " TO NEXT ROW"
 4500            elif self._match_text_seq("TO", "FIRST") or self._match_text_seq("TO", "LAST"):
 4501                direction = self._prev.text.upper()
 4502                pattern_var = self._advance_any()
 4503                if not pattern_var:
 4504                    self.raise_error(
 4505                        f"Expecting pattern variable after AFTER MATCH SKIP TO {direction}"
 4506                    )
 4507                text += f" TO {direction} {pattern_var.text if pattern_var else ''}"
 4508            after = exp.var(text)
 4509        else:
 4510            after = None
 4511
 4512        if self._match_text_seq("PATTERN"):
 4513            self._match_l_paren()
 4514
 4515            if not self._curr:
 4516                self.raise_error("Expecting )", self._curr)
 4517
 4518            paren = 1
 4519            start = self._curr
 4520
 4521            while self._curr and paren > 0:
 4522                if self._curr.token_type == TokenType.L_PAREN:
 4523                    paren += 1
 4524                if self._curr.token_type == TokenType.R_PAREN:
 4525                    paren -= 1
 4526
 4527                end = self._prev
 4528                self._advance()
 4529
 4530            if paren > 0:
 4531                self.raise_error("Expecting )", self._curr)
 4532
 4533            pattern = exp.var(self._find_sql(start, end))
 4534        else:
 4535            pattern = None
 4536
 4537        define = (
 4538            self._parse_csv(self._parse_name_as_expression)
 4539            if self._match_text_seq("DEFINE")
 4540            else None
 4541        )
 4542
 4543        self._match_r_paren()
 4544
 4545        return self.expression(
 4546            exp.MatchRecognize(
 4547                partition_by=partition,
 4548                order=order,
 4549                measures=measures,
 4550                rows=rows,
 4551                after=after,
 4552                pattern=pattern,
 4553                define=define,
 4554                alias=self._parse_table_alias(),
 4555            )
 4556        )
 4557
 4558    def _parse_lateral(self) -> exp.Lateral | None:
 4559        cross_apply: bool | None = None
 4560        if self._match_pair(TokenType.CROSS, TokenType.APPLY):
 4561            cross_apply = True
 4562        elif self._match_pair(TokenType.OUTER, TokenType.APPLY):
 4563            cross_apply = False
 4564
 4565        if cross_apply is not None:
 4566            this = self._parse_select(table=True)
 4567            view = None
 4568            outer = None
 4569        elif self._match(TokenType.LATERAL):
 4570            this = self._parse_select(table=True)
 4571            view = self._match(TokenType.VIEW)
 4572            outer = self._match(TokenType.OUTER)
 4573        else:
 4574            return None
 4575
 4576        if not this:
 4577            this = (
 4578                self._parse_unnest()
 4579                or self._parse_function()
 4580                or self._parse_id_var(any_token=False)
 4581            )
 4582
 4583            while self._match(TokenType.DOT):
 4584                this = exp.Dot(
 4585                    this=this,
 4586                    expression=self._parse_function() or self._parse_id_var(any_token=False),
 4587                )
 4588
 4589        ordinality: bool | None = None
 4590
 4591        if view:
 4592            table = self._parse_id_var(any_token=False)
 4593            columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else []
 4594            table_alias: exp.TableAlias | None = self.expression(
 4595                exp.TableAlias(this=table, columns=columns)
 4596            )
 4597        elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias:
 4598            # We move the alias from the lateral's child node to the lateral itself
 4599            table_alias = this.args["alias"].pop()
 4600        else:
 4601            ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 4602            table_alias = self._parse_table_alias()
 4603
 4604        return self.expression(
 4605            exp.Lateral(
 4606                this=this,
 4607                view=view,
 4608                outer=outer,
 4609                alias=table_alias,
 4610                cross_apply=cross_apply,
 4611                ordinality=ordinality,
 4612            )
 4613        )
 4614
 4615    def _parse_stream(self) -> exp.Stream | None:
 4616        index = self._index
 4617        if self._match(TokenType.STREAM):
 4618            if this := self._try_parse(self._parse_table):
 4619                return self.expression(exp.Stream(this=this))
 4620            self._retreat(index)
 4621        return None
 4622
 4623    def _parse_join_parts(
 4624        self,
 4625    ) -> tuple[Token | None, Token | None, Token | None]:
 4626        return (
 4627            self._prev if self._match_set(self.JOIN_METHODS) else None,
 4628            self._prev if self._match_set(self.JOIN_SIDES) else None,
 4629            self._prev if self._match_set(self.JOIN_KINDS) else None,
 4630        )
 4631
 4632    def _parse_using_identifiers(self) -> list[exp.Expr]:
 4633        def _parse_column_as_identifier() -> exp.Expr | None:
 4634            this = self._parse_column()
 4635            if isinstance(this, exp.Column):
 4636                return this.this
 4637            return this
 4638
 4639        return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True)
 4640
 4641    def _parse_join(
 4642        self,
 4643        skip_join_token: bool = False,
 4644        parse_bracket: bool = False,
 4645        alias_tokens: t.Collection[TokenType] | None = None,
 4646    ) -> exp.Join | None:
 4647        if self._match(TokenType.COMMA):
 4648            table = self._try_parse(lambda: self._parse_table(alias_tokens=alias_tokens))
 4649            cross_join = self.expression(exp.Join(this=table)) if table else None
 4650
 4651            if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE:
 4652                cross_join.set("kind", "CROSS")
 4653
 4654            return cross_join
 4655
 4656        index = self._index
 4657        method, side, kind = self._parse_join_parts()
 4658        directed = self._match_text_seq("DIRECTED")
 4659        hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None
 4660        join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN)
 4661        join_comments = self._prev_comments
 4662
 4663        if not skip_join_token and not join:
 4664            self._retreat(index)
 4665            kind = None
 4666            method = None
 4667            side = None
 4668
 4669        outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False)
 4670        cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False)
 4671
 4672        if not skip_join_token and not join and not outer_apply and not cross_apply:
 4673            return None
 4674
 4675        kwargs: dict[str, t.Any] = {
 4676            "this": self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4677        }
 4678        if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA):
 4679            kwargs["expressions"] = self._parse_csv(
 4680                lambda: self._parse_table(parse_bracket=parse_bracket, alias_tokens=alias_tokens)
 4681            )
 4682
 4683        if method:
 4684            kwargs["method"] = method.text.upper()
 4685        if side:
 4686            kwargs["side"] = side.text.upper()
 4687        if kind:
 4688            kwargs["kind"] = kind.text.upper()
 4689        if hint:
 4690            kwargs["hint"] = hint
 4691
 4692        if self._match(TokenType.MATCH_CONDITION):
 4693            kwargs["match_condition"] = self._parse_wrapped(self._parse_comparison)
 4694
 4695        if self._match(TokenType.ON):
 4696            kwargs["on"] = self._parse_disjunction()
 4697        elif self._match(TokenType.USING):
 4698            kwargs["using"] = self._parse_using_identifiers()
 4699        elif (
 4700            not method
 4701            and not (outer_apply or cross_apply)
 4702            and not isinstance(kwargs["this"], exp.Unnest)
 4703            and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY))
 4704        ):
 4705            index = self._index
 4706            joins: list | None = list(self._parse_joins(alias_tokens=alias_tokens))
 4707
 4708            if joins and self._match(TokenType.ON):
 4709                kwargs["on"] = self._parse_disjunction()
 4710            elif joins and self._match(TokenType.USING):
 4711                kwargs["using"] = self._parse_using_identifiers()
 4712            else:
 4713                joins = None
 4714                self._retreat(index)
 4715
 4716            kwargs["this"].set("joins", joins if joins else None)
 4717
 4718        kwargs["pivots"] = self._parse_pivots()
 4719
 4720        comments = [c for token in (method, side, kind) if token for c in token.comments]
 4721        comments = (join_comments or []) + comments
 4722
 4723        if (
 4724            self.ADD_JOIN_ON_TRUE
 4725            and not kwargs.get("on")
 4726            and not kwargs.get("using")
 4727            and not kwargs.get("method")
 4728            and kwargs.get("kind") in (None, "INNER", "OUTER")
 4729        ):
 4730            kwargs["on"] = exp.true()
 4731
 4732        if directed:
 4733            kwargs["directed"] = directed
 4734
 4735        return self.expression(exp.Join(**kwargs), comments=comments)
 4736
 4737    def _parse_opclass(self) -> exp.Expr | None:
 4738        this = self._parse_disjunction()
 4739
 4740        if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False):
 4741            return this
 4742
 4743        if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False):
 4744            return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts()))
 4745
 4746        return this
 4747
 4748    def _parse_index_params(self) -> exp.IndexParameters:
 4749        using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None
 4750
 4751        if self._match(TokenType.L_PAREN, advance=False):
 4752            columns = self._parse_wrapped_csv(self._parse_with_operator)
 4753        else:
 4754            columns = None
 4755
 4756        include = self._parse_wrapped_id_vars() if self._match_text_seq("INCLUDE") else None
 4757        partition_by = self._parse_partition_by()
 4758        with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties()
 4759        tablespace = (
 4760            self._parse_var(any_token=True)
 4761            if self._match_text_seq("USING", "INDEX", "TABLESPACE")
 4762            else None
 4763        )
 4764        where = self._parse_where()
 4765
 4766        on = self._parse_field() if self._match(TokenType.ON) else None
 4767
 4768        return self.expression(
 4769            exp.IndexParameters(
 4770                using=using,
 4771                columns=columns,
 4772                include=include,
 4773                partition_by=partition_by,
 4774                where=where,
 4775                with_storage=with_storage,
 4776                tablespace=tablespace,
 4777                on=on,
 4778            )
 4779        )
 4780
 4781    def _parse_index(
 4782        self, index: exp.Expr | None = None, anonymous: bool = False
 4783    ) -> exp.Index | None:
 4784        if index or anonymous:
 4785            unique = None
 4786            primary = None
 4787            amp = None
 4788
 4789            self._match(TokenType.ON)
 4790            self._match(TokenType.TABLE)  # hive
 4791            table = self._parse_table_parts(schema=True)
 4792        else:
 4793            unique = self._match(TokenType.UNIQUE)
 4794            primary = self._match_text_seq("PRIMARY")
 4795            amp = self._match_text_seq("AMP")
 4796
 4797            if not self._match(TokenType.INDEX):
 4798                return None
 4799
 4800            index = self._parse_id_var()
 4801            table = None
 4802
 4803        params = self._parse_index_params()
 4804
 4805        return self.expression(
 4806            exp.Index(
 4807                this=index, table=table, unique=unique, primary=primary, amp=amp, params=params
 4808            )
 4809        )
 4810
 4811    def _parse_table_hints(self) -> list[exp.Expr] | None:
 4812        hints: list[exp.Expr] = []
 4813        if self._match_pair(TokenType.WITH, TokenType.L_PAREN):
 4814            # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16
 4815            hints.append(
 4816                self.expression(
 4817                    exp.WithTableHint(
 4818                        expressions=self._parse_csv(
 4819                            lambda: self._parse_function() or self._parse_var(any_token=True)
 4820                        )
 4821                    )
 4822                )
 4823            )
 4824            self._match_r_paren()
 4825        else:
 4826            # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
 4827            while self._match_set(self.TABLE_INDEX_HINT_TOKENS):
 4828                hint = exp.IndexTableHint(this=self._prev.text.upper())
 4829
 4830                self._match_set((TokenType.INDEX, TokenType.KEY))
 4831                if self._match(TokenType.FOR):
 4832                    hint.set("target", self._advance_any() and self._prev.text.upper())
 4833
 4834                hint.set("expressions", self._parse_wrapped_id_vars())
 4835                hints.append(hint)
 4836
 4837        return hints or None
 4838
 4839    def _parse_table_part(self, schema: bool = False) -> exp.Expr | None:
 4840        return (
 4841            (not schema and self._parse_function(optional_parens=False))
 4842            or self._parse_id_var(any_token=False)
 4843            or self._parse_string_as_identifier()
 4844            or self._parse_placeholder()
 4845        )
 4846
 4847    def _parse_table_parts_fast(self) -> exp.Table | None:
 4848        index = self._index
 4849        parts: list[exp.Identifier] | None = None
 4850        all_comments: list[str] | None = None
 4851
 4852        while self._match_set(self.IDENTIFIER_TOKENS):
 4853            token = self._prev
 4854            comments = self._prev_comments
 4855
 4856            has_dot = self._match(TokenType.DOT)
 4857            curr_tt = self._curr.token_type
 4858
 4859            if not has_dot:
 4860                if curr_tt in self.TABLE_POSTFIX_TOKENS:
 4861                    self._retreat(index)
 4862                    return None
 4863            elif curr_tt not in self.IDENTIFIER_TOKENS:
 4864                self._retreat(index)
 4865                return None
 4866
 4867            if parts is None:
 4868                parts = []
 4869
 4870            if comments:
 4871                if all_comments is None:
 4872                    all_comments = []
 4873                all_comments.extend(comments)
 4874                self._prev_comments = []
 4875
 4876            parts.append(
 4877                self.expression(
 4878                    exp.Identifier(
 4879                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 4880                    ),
 4881                    token,
 4882                )
 4883            )
 4884
 4885            if not has_dot:
 4886                break
 4887
 4888        if parts is None:
 4889            return None
 4890
 4891        n = len(parts)
 4892
 4893        if n == 1:
 4894            table: exp.Table = exp.Table(this=parts[0])
 4895        elif n == 2:
 4896            table = exp.Table(this=parts[1], db=parts[0])
 4897        elif n >= 3:
 4898            this: exp.Identifier | exp.Dot = parts[2]
 4899            for i in range(3, n):
 4900                this = exp.Dot(this=this, expression=parts[i])
 4901
 4902            table = exp.Table(this=this, db=parts[1], catalog=parts[0])
 4903
 4904        if table is None:
 4905            self._retreat(index)
 4906        elif all_comments:
 4907            table.add_comments(all_comments)
 4908        return table
 4909
 4910    def _parse_table_parts(
 4911        self,
 4912        schema: bool = False,
 4913        is_db_reference: bool = False,
 4914        wildcard: bool = False,
 4915        fast: bool = False,
 4916    ) -> exp.Table | exp.Dot | None:
 4917        if fast:
 4918            return self._parse_table_parts_fast()
 4919
 4920        catalog: exp.Expr | str | None = None
 4921        db: exp.Expr | str | None = None
 4922        table: exp.Expr | str | None = self._parse_table_part(schema=schema)
 4923
 4924        while self._match(TokenType.DOT):
 4925            if catalog:
 4926                # This allows nesting the table in arbitrarily many dot expressions if needed
 4927                table = self.expression(
 4928                    exp.Dot(this=table, expression=self._parse_table_part(schema=schema))
 4929                )
 4930            else:
 4931                catalog = db
 4932                db = table
 4933                # "" used for tsql FROM a..b case
 4934                table = self._parse_table_part(schema=schema) or ""
 4935
 4936        if (
 4937            wildcard
 4938            and self._is_connected()
 4939            and (isinstance(table, exp.Identifier) or not table)
 4940            and self._match(TokenType.STAR)
 4941        ):
 4942            if isinstance(table, exp.Identifier):
 4943                table.args["this"] += "*"
 4944            else:
 4945                table = exp.Identifier(this="*")
 4946
 4947        if is_db_reference:
 4948            catalog = db
 4949            db = table
 4950            table = None
 4951
 4952        if not table and not is_db_reference:
 4953            self.raise_error(f"Expected table name but got {self._curr}")
 4954        if not db and is_db_reference:
 4955            self.raise_error(f"Expected database name but got {self._curr}")
 4956
 4957        table = self.expression(exp.Table(this=table, db=db, catalog=catalog))
 4958
 4959        # Bubble up comments from identifier parts to the Table
 4960        comments = []
 4961        for part in table.parts:
 4962            if part_comments := part.pop_comments():
 4963                comments.extend(part_comments)
 4964        if comments:
 4965            table.add_comments(comments)
 4966
 4967        changes = self._parse_changes()
 4968        if changes:
 4969            table.set("changes", changes)
 4970
 4971        at_before = self._parse_historical_data()
 4972        if at_before:
 4973            table.set("when", at_before)
 4974
 4975        pivots = self._parse_pivots()
 4976        if pivots:
 4977            table.set("pivots", pivots)
 4978
 4979        return table
 4980
 4981    def _parse_table(
 4982        self,
 4983        schema: bool = False,
 4984        joins: bool = False,
 4985        alias_tokens: t.Collection[TokenType] | None = None,
 4986        parse_bracket: bool = False,
 4987        is_db_reference: bool = False,
 4988        parse_partition: bool = False,
 4989        consume_pipe: bool = False,
 4990    ) -> exp.Expr | None:
 4991        if not schema and not is_db_reference and not consume_pipe and not joins:
 4992            index = self._index
 4993            table = self._parse_table_parts(fast=True)
 4994
 4995            if table is not None:
 4996                curr_tt = self._curr.token_type
 4997                next_tt = self._next.token_type
 4998
 4999                fast_terminators = self.TABLE_TERMINATORS
 5000
 5001                # only return the table if we're sure there are no other operators
 5002                # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT
 5003                if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION:
 5004                    return table
 5005
 5006                postfix_tokens = self.TABLE_POSTFIX_TOKENS
 5007
 5008                if curr_tt not in postfix_tokens and next_tt not in postfix_tokens:
 5009                    if alias := self._parse_table_alias(
 5010                        alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS
 5011                    ):
 5012                        table.set("alias", alias)
 5013
 5014                    if self._curr.token_type in fast_terminators:
 5015                        return table
 5016
 5017                self._retreat(index)
 5018
 5019        if stream := self._parse_stream():
 5020            return stream
 5021
 5022        if lateral := self._parse_lateral():
 5023            return lateral
 5024
 5025        if unnest := self._parse_unnest():
 5026            return unnest
 5027
 5028        if values := self._parse_derived_table_values():
 5029            return values
 5030
 5031        if subquery := self._parse_select(table=True, consume_pipe=consume_pipe):
 5032            if not subquery.args.get("pivots"):
 5033                subquery.set("pivots", self._parse_pivots())
 5034            if joins:
 5035                for join in self._parse_joins():
 5036                    subquery.append("joins", join)
 5037            return subquery
 5038
 5039        bracket = parse_bracket and self._parse_bracket(None)
 5040        bracket = self.expression(exp.Table(this=bracket)) if bracket else None
 5041
 5042        rows_from_tables = (
 5043            self._parse_wrapped_csv(self._parse_table)
 5044            if self._match_text_seq("ROWS", "FROM")
 5045            else None
 5046        )
 5047        rows_from = (
 5048            self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None
 5049        )
 5050
 5051        only = self._match(TokenType.ONLY)
 5052
 5053        this = t.cast(
 5054            exp.Expr,
 5055            bracket
 5056            or rows_from
 5057            or self._parse_bracket(
 5058                self._parse_table_parts(schema=schema, is_db_reference=is_db_reference)
 5059            ),
 5060        )
 5061
 5062        if only:
 5063            this.set("only", only)
 5064
 5065        # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context
 5066        self._match(TokenType.STAR)
 5067
 5068        parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION
 5069        if parse_partition and self._match(TokenType.PARTITION, advance=False):
 5070            this.set("partition", self._parse_partition())
 5071
 5072        if schema:
 5073            return self._parse_schema(this=this)
 5074
 5075        if self.dialect.ALIAS_POST_VERSION:
 5076            this.set("version", self._parse_version())
 5077
 5078        if self.dialect.ALIAS_POST_TABLESAMPLE:
 5079            this.set("sample", self._parse_table_sample())
 5080
 5081        alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)
 5082        if alias:
 5083            this.set("alias", alias)
 5084
 5085            # DuckDB requires the time-travel clause to come after the alias, e.g.
 5086            # SELECT * FROM t AS a AT (VERSION => 1)
 5087            if isinstance(this, exp.Table) and not this.args.get("when"):
 5088                this.set("when", self._parse_historical_data())
 5089
 5090        if self._match(TokenType.INDEXED_BY):
 5091            this.set("indexed", self._parse_table_parts())
 5092        elif self._match_text_seq("NOT", "INDEXED"):
 5093            this.set("indexed", False)
 5094
 5095        if isinstance(this, exp.Table) and self._match_text_seq("AT"):
 5096            return self.expression(
 5097                exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var())
 5098            )
 5099
 5100        this.set("hints", self._parse_table_hints())
 5101
 5102        if not this.args.get("pivots"):
 5103            this.set("pivots", self._parse_pivots())
 5104
 5105        if not self.dialect.ALIAS_POST_TABLESAMPLE:
 5106            this.set("sample", self._parse_table_sample())
 5107
 5108        if not self.dialect.ALIAS_POST_VERSION:
 5109            this.set("version", self._parse_version())
 5110
 5111        if joins:
 5112            for join in self._parse_joins(alias_tokens=alias_tokens):
 5113                this.append("joins", join)
 5114
 5115        if self._match_pair(TokenType.WITH, TokenType.ORDINALITY):
 5116            this.set("ordinality", True)
 5117            this.set("alias", self._parse_table_alias())
 5118
 5119        return this
 5120
 5121    def _parse_version(self) -> exp.Version | None:
 5122        for phrase, this in self.VERSION_PHRASES.items():
 5123            if self._match_text_seq(*phrase):
 5124                break
 5125        else:
 5126            return None
 5127
 5128        if self._match_set((TokenType.FROM, TokenType.BETWEEN)):
 5129            kind = self._prev.text.upper()
 5130            start = self._parse_bitwise()
 5131            self._match_texts(("TO", "AND"))
 5132            end = self._parse_bitwise()
 5133            expression: exp.Expr | None = self.expression(exp.Tuple(expressions=[start, end]))
 5134        elif self._match_text_seq("CONTAINED", "IN"):
 5135            kind = "CONTAINED IN"
 5136            expression = self.expression(
 5137                exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise))
 5138            )
 5139        elif self._match(TokenType.ALL):
 5140            kind = "ALL"
 5141            expression = None
 5142        else:
 5143            self._match_text_seq("AS", "OF")
 5144            kind = "AS OF"
 5145            expression = self._parse_type()
 5146
 5147        return self.expression(exp.Version(this=this, expression=expression, kind=kind))
 5148
 5149    def _parse_historical_data(self) -> exp.HistoricalData | None:
 5150        # https://docs.snowflake.com/en/sql-reference/constructs/at-before
 5151        index = self._index
 5152        historical_data = None
 5153        if self._match_texts(self.HISTORICAL_DATA_PREFIX):
 5154            this = self._prev.text.upper()
 5155            kind = (
 5156                self._match(TokenType.L_PAREN)
 5157                and self._match_texts(self.HISTORICAL_DATA_KIND)
 5158                and self._prev.text.upper()
 5159            )
 5160            expression = self._match(TokenType.FARROW) and self._parse_bitwise()
 5161
 5162            if expression:
 5163                self._match_r_paren()
 5164                historical_data = self.expression(
 5165                    exp.HistoricalData(this=this, kind=kind, expression=expression)
 5166                )
 5167            else:
 5168                self._retreat(index)
 5169
 5170        return historical_data
 5171
 5172    def _parse_changes(self) -> exp.Changes | None:
 5173        if not self._match_text_seq("CHANGES", "(", "INFORMATION", "=>"):
 5174            return None
 5175
 5176        information = self._parse_var(any_token=True)
 5177        self._match_r_paren()
 5178
 5179        return self.expression(
 5180            exp.Changes(
 5181                information=information,
 5182                at_before=self._parse_historical_data(),
 5183                end=self._parse_historical_data(),
 5184            )
 5185        )
 5186
 5187    def _parse_unnest(self, with_alias: bool = True) -> exp.Unnest | None:
 5188        if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False):
 5189            return None
 5190
 5191        self._advance()
 5192
 5193        expressions = self._parse_wrapped_csv(self._parse_equality)
 5194        offset: bool | exp.Expr = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)
 5195
 5196        alias = self._parse_table_alias() if with_alias else None
 5197
 5198        if alias:
 5199            if self.dialect.UNNEST_COLUMN_ONLY:
 5200                if alias.args.get("columns"):
 5201                    self.raise_error("Unexpected extra column alias in unnest.")
 5202
 5203                alias.set("columns", [alias.this])
 5204                alias.set("this", None)
 5205
 5206            columns = alias.args.get("columns") or []
 5207            if offset and len(expressions) < len(columns):
 5208                offset = columns.pop()
 5209
 5210        if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET):
 5211            self._match(TokenType.ALIAS)
 5212            offset = self._parse_id_var(
 5213                any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS
 5214            ) or exp.to_identifier("offset")
 5215
 5216        return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset))
 5217
 5218    def _parse_derived_table_values(self) -> exp.Values | None:
 5219        is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES)
 5220        if not is_derived and not (
 5221            # ClickHouse's `FORMAT Values` is equivalent to `VALUES`
 5222            self._match_text_seq("VALUES") or self._match_text_seq("FORMAT", "VALUES")
 5223        ):
 5224            return None
 5225
 5226        expressions = self._parse_csv(self._parse_value)
 5227        alias = self._parse_table_alias()
 5228
 5229        if is_derived:
 5230            self._match_r_paren()
 5231
 5232        return self.expression(
 5233            exp.Values(expressions=expressions, alias=alias or self._parse_table_alias())
 5234        )
 5235
 5236    def _parse_table_sample(self, as_modifier: bool = False) -> exp.TableSample | None:
 5237        if not self._match(TokenType.TABLE_SAMPLE) and not (
 5238            as_modifier and self._match_text_seq("USING", "SAMPLE")
 5239        ):
 5240            return None
 5241
 5242        bucket_numerator = None
 5243        bucket_denominator = None
 5244        bucket_field = None
 5245        percent = None
 5246        size = None
 5247        seed = None
 5248
 5249        method = self._parse_var(tokens=(TokenType.ROW,), upper=True)
 5250        matched_l_paren = self._match(TokenType.L_PAREN)
 5251
 5252        if self.TABLESAMPLE_CSV:
 5253            num = None
 5254            expressions = self._parse_csv(self._parse_primary)
 5255        else:
 5256            expressions = None
 5257            num = (
 5258                self._parse_factor()
 5259                if self._match(TokenType.NUMBER, advance=False)
 5260                else self._parse_primary() or self._parse_placeholder()
 5261            )
 5262
 5263        if self._match_text_seq("BUCKET"):
 5264            bucket_numerator = self._parse_number()
 5265            self._match_text_seq("OUT", "OF")
 5266            bucket_denominator = bucket_denominator = self._parse_number()
 5267            self._match(TokenType.ON)
 5268            bucket_field = self._parse_field()
 5269        elif self._match_set((TokenType.PERCENT, TokenType.MOD)):
 5270            percent = num
 5271        elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:
 5272            size = num
 5273        else:
 5274            percent = num
 5275
 5276        if matched_l_paren:
 5277            self._match_r_paren()
 5278
 5279        if self._match(TokenType.L_PAREN):
 5280            method = self._parse_var(upper=True)
 5281            seed = self._match(TokenType.COMMA) and self._parse_number()
 5282            self._match_r_paren()
 5283        elif self._match_texts(("SEED", "REPEATABLE")):
 5284            seed = self._parse_wrapped(self._parse_number)
 5285
 5286        if not method and self.DEFAULT_SAMPLING_METHOD:
 5287            method = exp.var(self.DEFAULT_SAMPLING_METHOD)
 5288
 5289        return self.expression(
 5290            exp.TableSample(
 5291                expressions=expressions,
 5292                method=method,
 5293                bucket_numerator=bucket_numerator,
 5294                bucket_denominator=bucket_denominator,
 5295                bucket_field=bucket_field,
 5296                percent=percent,
 5297                size=size,
 5298                seed=seed,
 5299            )
 5300        )
 5301
 5302    def _parse_pivots(self) -> list[exp.Pivot] | None:
 5303        if self._curr.token_type not in (TokenType.PIVOT, TokenType.UNPIVOT):
 5304            return None
 5305        return list(iter(self._parse_pivot, None)) or None
 5306
 5307    def _parse_joins(
 5308        self, alias_tokens: t.Collection[TokenType] | None = None
 5309    ) -> t.Iterator[exp.Join]:
 5310        return iter(lambda: self._parse_join(alias_tokens=alias_tokens), None)
 5311
 5312    def _parse_unpivot_columns(self) -> exp.UnpivotColumns | None:
 5313        if not self._match(TokenType.INTO):
 5314            return None
 5315
 5316        return self.expression(
 5317            exp.UnpivotColumns(
 5318                this=self._match_text_seq("NAME") and self._parse_column(),
 5319                expressions=self._match_text_seq("VALUE") and self._parse_csv(self._parse_column),
 5320            )
 5321        )
 5322
 5323    # https://duckdb.org/docs/sql/statements/pivot
 5324    def _parse_simplified_pivot(self, is_unpivot: bool | None = None) -> exp.Pivot:
 5325        def _parse_on() -> exp.Expr | None:
 5326            this = self._parse_bitwise()
 5327
 5328            if self._match(TokenType.IN):
 5329                # PIVOT ... ON col IN (row_val1, row_val2)
 5330                return self._parse_in(this)
 5331            if self._match(TokenType.ALIAS, advance=False):
 5332                # UNPIVOT ... ON (col1, col2, col3) AS row_val
 5333                return self._parse_alias(this)
 5334
 5335            return this
 5336
 5337        this = self._parse_table()
 5338        expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on)
 5339        into = self._parse_unpivot_columns()
 5340        using = self._match(TokenType.USING) and self._parse_csv(
 5341            lambda: self._parse_alias(self._parse_column())
 5342        )
 5343        group = self._parse_group()
 5344
 5345        return self.expression(
 5346            exp.Pivot(
 5347                this=this,
 5348                expressions=expressions,
 5349                using=using,
 5350                group=group,
 5351                unpivot=is_unpivot,
 5352                into=into,
 5353            )
 5354        )
 5355
 5356    def _parse_pivot_in(self) -> exp.In:
 5357        def _parse_aliased_expression() -> exp.Expr | None:
 5358            this = self._parse_select_or_expression()
 5359
 5360            self._match(TokenType.ALIAS)
 5361            alias = self._parse_bitwise()
 5362            if alias:
 5363                if isinstance(alias, exp.Column) and not alias.db:
 5364                    alias = alias.this
 5365                return self.expression(exp.PivotAlias(this=this, alias=alias))
 5366
 5367            return this
 5368
 5369        value = self._parse_column()
 5370
 5371        if not self._match(TokenType.IN):
 5372            self.raise_error("Expecting IN")
 5373
 5374        if self._match(TokenType.L_PAREN):
 5375            if self._match(TokenType.ANY):
 5376                exprs: list[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order()))
 5377            else:
 5378                exprs = self._parse_csv(_parse_aliased_expression)
 5379            self._match_r_paren()
 5380            return self.expression(exp.In(this=value, expressions=exprs))
 5381
 5382        return self.expression(exp.In(this=value, field=self._parse_id_var()))
 5383
 5384    def _parse_pivot_aggregation(self) -> exp.Expr | None:
 5385        func = self._parse_function()
 5386        if not func:
 5387            if self._prev.token_type == TokenType.COMMA:
 5388                return None
 5389            self.raise_error("Expecting an aggregation function in PIVOT")
 5390
 5391        return self._parse_alias(func)
 5392
 5393    def _parse_pivot(self) -> exp.Pivot | None:
 5394        index = self._index
 5395        include_nulls = None
 5396
 5397        if self._match(TokenType.PIVOT):
 5398            unpivot = False
 5399        elif self._match(TokenType.UNPIVOT):
 5400            unpivot = True
 5401
 5402            # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax
 5403            if self._match_text_seq("INCLUDE", "NULLS"):
 5404                include_nulls = True
 5405            elif self._match_text_seq("EXCLUDE", "NULLS"):
 5406                include_nulls = False
 5407        else:
 5408            return None
 5409
 5410        expressions = []
 5411
 5412        if not self._match(TokenType.L_PAREN):
 5413            self._retreat(index)
 5414            return None
 5415
 5416        if unpivot:
 5417            expressions = self._parse_csv(self._parse_column)
 5418        else:
 5419            expressions = self._parse_csv(self._parse_pivot_aggregation)
 5420
 5421        if not expressions:
 5422            self.raise_error("Failed to parse PIVOT's aggregation list")
 5423
 5424        if not self._match(TokenType.FOR):
 5425            self.raise_error("Expecting FOR")
 5426
 5427        fields = []
 5428        while True:
 5429            field = self._try_parse(self._parse_pivot_in)
 5430            if not field:
 5431                break
 5432            fields.append(field)
 5433
 5434        default_on_null = self._match_text_seq("DEFAULT", "ON", "NULL") and self._parse_wrapped(
 5435            self._parse_bitwise
 5436        )
 5437
 5438        group = self._parse_group()
 5439
 5440        self._match_r_paren()
 5441
 5442        pivot = self.expression(
 5443            exp.Pivot(
 5444                expressions=expressions,
 5445                fields=fields,
 5446                unpivot=unpivot,
 5447                include_nulls=include_nulls,
 5448                default_on_null=default_on_null,
 5449                group=group,
 5450            )
 5451        )
 5452
 5453        if unpivot:
 5454            pivot.set("expressions", [_unpivot_target(e) for e in pivot.expressions])
 5455            for pivot_field in pivot.fields:
 5456                if isinstance(pivot_field, exp.In):
 5457                    pivot_field.set("this", _unpivot_target(pivot_field.this))
 5458
 5459            pivot.set("value_columns_first", self.UNPIVOT_VALUE_COLUMNS_FIRST)
 5460
 5461        if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False):
 5462            pivot.set("alias", self._parse_table_alias())
 5463
 5464        if not unpivot:
 5465            names = self._pivot_column_names(t.cast(list[exp.Expr], expressions))
 5466
 5467            columns: list[exp.Expr] = []
 5468            all_fields = []
 5469            for pivot_field in pivot.fields:
 5470                pivot_field_expressions = pivot_field.expressions
 5471
 5472                # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case.
 5473                if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny):
 5474                    continue
 5475
 5476                all_fields.append(
 5477                    [
 5478                        # An explicit `<field> AS <alias>` names the output column directly,
 5479                        # so it wins over the dialect's string-identifying convention
 5480                        fld.sql()
 5481                        if self.IDENTIFY_PIVOT_STRINGS and not isinstance(fld, exp.PivotAlias)
 5482                        else fld.alias_or_name
 5483                        for fld in pivot_field_expressions
 5484                    ]
 5485                )
 5486
 5487            if all_fields:
 5488                if names:
 5489                    all_fields.append(names)
 5490
 5491                # Generate all possible combinations of the pivot columns
 5492                # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US'))
 5493                # generates the product between [[2000, 2010], ['NL', 'US'], ['total']]
 5494                for fld_parts_tuple in itertools.product(*all_fields):
 5495                    fld_parts = list(fld_parts_tuple)
 5496
 5497                    if names and self.PREFIXED_PIVOT_COLUMNS:
 5498                        # Move the "name" to the front of the list
 5499                        fld_parts.insert(0, fld_parts.pop(-1))
 5500
 5501                    columns.append(exp.to_identifier("_".join(fld_parts)))
 5502
 5503            pivot.set("columns", columns)
 5504            pivot.set("identify_pivot_strings", self.IDENTIFY_PIVOT_STRINGS)
 5505            pivot.set("prefixed_pivot_columns", self.PREFIXED_PIVOT_COLUMNS)
 5506            pivot.set("pivot_column_naming", self.PIVOT_COLUMN_NAMING)
 5507
 5508        return pivot
 5509
 5510    def _pivot_column_names(self, aggregations: list[exp.Expr]) -> list[str]:
 5511        return [agg.alias for agg in aggregations if agg.alias]
 5512
 5513    def _parse_prewhere(self, skip_where_token: bool = False) -> exp.PreWhere | None:
 5514        if not skip_where_token and not self._match(TokenType.PREWHERE):
 5515            return None
 5516
 5517        comments = self._prev_comments
 5518        return self.expression(
 5519            exp.PreWhere(this=self._parse_disjunction()),
 5520            comments=comments,
 5521        )
 5522
 5523    def _parse_where(self, skip_where_token: bool = False) -> exp.Where | None:
 5524        if not skip_where_token and not self._match(TokenType.WHERE):
 5525            return None
 5526
 5527        comments = self._prev_comments
 5528        return self.expression(
 5529            exp.Where(this=self._parse_disjunction()),
 5530            comments=comments,
 5531        )
 5532
 5533    def _parse_group(self, skip_group_by_token: bool = False) -> exp.Group | None:
 5534        if not skip_group_by_token and not self._match(TokenType.GROUP_BY):
 5535            return None
 5536        comments = self._prev_comments
 5537
 5538        elements: dict[str, t.Any] = defaultdict(list)
 5539
 5540        if self._match(TokenType.ALL):
 5541            elements["all"] = True
 5542        elif self._match(TokenType.DISTINCT):
 5543            elements["all"] = False
 5544
 5545        if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False):
 5546            return self.expression(exp.Group(**elements), comments=comments)  # type: ignore
 5547
 5548        while True:
 5549            index = self._index
 5550
 5551            elements["expressions"].extend(
 5552                self._parse_csv(
 5553                    lambda: (
 5554                        None
 5555                        if self._match_set((TokenType.CUBE, TokenType.ROLLUP), advance=False)
 5556                        else self._parse_disjunction()
 5557                    )
 5558                )
 5559            )
 5560
 5561            before_with_index = self._index
 5562            with_prefix = self._match(TokenType.WITH)
 5563
 5564            if cube_or_rollup := self._parse_cube_or_rollup(with_prefix=with_prefix):
 5565                key = "rollup" if isinstance(cube_or_rollup, exp.Rollup) else "cube"
 5566                elements[key].append(cube_or_rollup)
 5567            elif grouping_sets := self._parse_grouping_sets():
 5568                elements["grouping_sets"].append(grouping_sets)
 5569            elif self._match_text_seq("TOTALS"):
 5570                elements["totals"] = True  # type: ignore
 5571
 5572            if before_with_index <= self._index <= before_with_index + 1:
 5573                self._retreat(before_with_index)
 5574                break
 5575
 5576            if index == self._index:
 5577                break
 5578
 5579        return self.expression(exp.Group(**elements), comments=comments)  # type: ignore
 5580
 5581    def _parse_cube_or_rollup(self, with_prefix: bool = False) -> exp.Cube | exp.Rollup | None:
 5582        if self._match(TokenType.CUBE):
 5583            kind: type[exp.Cube | exp.Rollup] = exp.Cube
 5584        elif self._match(TokenType.ROLLUP):
 5585            kind = exp.Rollup
 5586        else:
 5587            return None
 5588
 5589        return self.expression(
 5590            kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise))
 5591        )
 5592
 5593    def _parse_grouping_sets(self) -> exp.GroupingSets | None:
 5594        if self._match(TokenType.GROUPING_SETS):
 5595            return self.expression(
 5596                exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set))
 5597            )
 5598        return None
 5599
 5600    def _parse_grouping_set(self) -> exp.Expr | None:
 5601        return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise()
 5602
 5603    def _parse_having(self, skip_having_token: bool = False) -> exp.Having | None:
 5604        if not skip_having_token and not self._match(TokenType.HAVING):
 5605            return None
 5606        comments = self._prev_comments
 5607        return self.expression(
 5608            exp.Having(this=self._parse_disjunction()),
 5609            comments=comments,
 5610        )
 5611
 5612    def _parse_qualify(self) -> exp.Qualify | None:
 5613        if not self._match(TokenType.QUALIFY):
 5614            return None
 5615        return self.expression(exp.Qualify(this=self._parse_disjunction()))
 5616
 5617    def _parse_connect_with_prior(self) -> exp.Expr | None:
 5618        self.NO_PAREN_FUNCTION_PARSERS["PRIOR"] = lambda self: self.expression(
 5619            exp.Prior(this=self._parse_bitwise())
 5620        )
 5621        connect = self._parse_disjunction()
 5622        self.NO_PAREN_FUNCTION_PARSERS.pop("PRIOR")
 5623        return connect
 5624
 5625    def _parse_connect(self, skip_start_token: bool = False) -> exp.Connect | None:
 5626        if skip_start_token:
 5627            start = None
 5628        elif self._match_text_seq("START", "WITH"):
 5629            start = self._parse_disjunction()
 5630        else:
 5631            return None
 5632
 5633        self._match(TokenType.CONNECT_BY)
 5634        nocycle = self._match_text_seq("NOCYCLE")
 5635        connect = self._parse_connect_with_prior()
 5636
 5637        if not start and self._match_text_seq("START", "WITH"):
 5638            start = self._parse_disjunction()
 5639
 5640        return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle))
 5641
 5642    def _parse_name_as_expression(self) -> exp.Expr | None:
 5643        this = self._parse_id_var(any_token=True)
 5644        if self._match(TokenType.ALIAS):
 5645            this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction()))
 5646        return this
 5647
 5648    def _parse_interpolate(self) -> list[exp.Expr] | None:
 5649        if self._match_text_seq("INTERPOLATE"):
 5650            return self._parse_wrapped_csv(self._parse_name_as_expression)
 5651        return None
 5652
 5653    def _parse_order(
 5654        self, this: exp.Expr | None = None, skip_order_token: bool = False
 5655    ) -> exp.Expr | None:
 5656        siblings = None
 5657        if not skip_order_token and not self._match(TokenType.ORDER_BY):
 5658            if not self._match(TokenType.ORDER_SIBLINGS_BY):
 5659                return this
 5660
 5661            siblings = True
 5662
 5663        comments = self._prev_comments
 5664        return self.expression(
 5665            exp.Order(
 5666                this=this,
 5667                expressions=self._parse_csv(self._parse_ordered),
 5668                siblings=siblings,
 5669            ),
 5670            comments=comments,
 5671        )
 5672
 5673    def _parse_sort(self, exp_class: type[E], token: TokenType) -> E | None:
 5674        if not self._match(token):
 5675            return None
 5676        return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered)))
 5677
 5678    def _parse_ordered(
 5679        self, parse_method: t.Callable[[], exp.Expr | None] | None = None
 5680    ) -> exp.Ordered | None:
 5681        this = parse_method() if parse_method else self._parse_disjunction()
 5682        if not this:
 5683            return None
 5684
 5685        if this.name.upper() == "ALL" and self.dialect.SUPPORTS_ORDER_BY_ALL:
 5686            this = exp.var("ALL")
 5687
 5688        asc = self._match(TokenType.ASC)
 5689        desc: bool | None = True if self._match(TokenType.DESC) else (False if asc else None)
 5690
 5691        is_nulls_first = self._match_text_seq("NULLS", "FIRST")
 5692        is_nulls_last = self._match_text_seq("NULLS", "LAST")
 5693
 5694        nulls_first = is_nulls_first or False
 5695        explicitly_null_ordered = is_nulls_first or is_nulls_last
 5696
 5697        if (
 5698            not explicitly_null_ordered
 5699            and (
 5700                (not desc and self.dialect.NULL_ORDERING == "nulls_are_small")
 5701                or (desc and self.dialect.NULL_ORDERING != "nulls_are_small")
 5702            )
 5703            and self.dialect.NULL_ORDERING != "nulls_are_last"
 5704        ):
 5705            nulls_first = True
 5706
 5707        if self._match_text_seq("WITH", "FILL"):
 5708            with_fill = self.expression(
 5709                exp.WithFill(
 5710                    from_=self._match(TokenType.FROM) and self._parse_bitwise(),
 5711                    to=self._match_text_seq("TO") and self._parse_bitwise(),
 5712                    step=self._match_text_seq("STEP") and self._parse_bitwise(),
 5713                    interpolate=self._parse_interpolate(),
 5714                )
 5715            )
 5716        else:
 5717            with_fill = None
 5718
 5719        return self.expression(
 5720            exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill)
 5721        )
 5722
 5723    def _parse_limit_options(self) -> exp.LimitOptions | None:
 5724        percent = self._match_set((TokenType.PERCENT, TokenType.MOD))
 5725        rows = self._match_set((TokenType.ROW, TokenType.ROWS))
 5726        self._match_text_seq("ONLY")
 5727        with_ties = self._match_text_seq("WITH", "TIES")
 5728
 5729        if not (percent or rows or with_ties):
 5730            return None
 5731
 5732        return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties))
 5733
 5734    def _parse_limit(
 5735        self,
 5736        this: exp.Expr | None = None,
 5737        top: bool = False,
 5738        skip_limit_token: bool = False,
 5739    ) -> exp.Expr | None:
 5740        if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT):
 5741            comments = self._prev_comments
 5742            if top:
 5743                limit_paren = self._match(TokenType.L_PAREN)
 5744                expression = (
 5745                    self._parse_term() or self._parse_select()
 5746                    if limit_paren
 5747                    else self._parse_number()
 5748                )
 5749
 5750                if limit_paren:
 5751                    self._match_r_paren()
 5752
 5753            else:
 5754                if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL):
 5755                    return this
 5756
 5757                # Parsing LIMIT x% (i.e x PERCENT) as a term leads to an error, since
 5758                # we try to build an exp.Mod expr. For that matter, we backtrack and instead
 5759                # consume the factor plus parse the percentage separately
 5760                index = self._index
 5761                expression = self._try_parse(self._parse_term)
 5762                if isinstance(expression, exp.Mod):
 5763                    self._retreat(index)
 5764                    expression = self._parse_factor()
 5765                elif not expression:
 5766                    expression = self._parse_factor()
 5767            limit_options = self._parse_limit_options()
 5768
 5769            if self._match(TokenType.COMMA):
 5770                offset = expression
 5771                expression = self._parse_term()
 5772            else:
 5773                offset = None
 5774
 5775            limit_exp = self.expression(
 5776                exp.Limit(
 5777                    this=this,
 5778                    expression=expression,
 5779                    offset=offset,
 5780                    limit_options=limit_options,
 5781                    expressions=self._parse_limit_by(),
 5782                ),
 5783                comments=comments,
 5784            )
 5785
 5786            return limit_exp
 5787
 5788        if self._match(TokenType.FETCH):
 5789            direction = (
 5790                self._prev.text.upper()
 5791                if self._match_set((TokenType.FIRST, TokenType.NEXT))
 5792                else "FIRST"
 5793            )
 5794
 5795            count = self._parse_field(tokens=self.FETCH_TOKENS)
 5796
 5797            return self.expression(
 5798                exp.Fetch(
 5799                    direction=direction, count=count, limit_options=self._parse_limit_options()
 5800                )
 5801            )
 5802
 5803        return this
 5804
 5805    def _parse_offset(self, this: exp.Expr | None = None) -> exp.Expr | None:
 5806        if not self._match(TokenType.OFFSET):
 5807            return this
 5808
 5809        count = self._parse_term()
 5810        self._match_set((TokenType.ROW, TokenType.ROWS))
 5811
 5812        return self.expression(
 5813            exp.Offset(this=this, expression=count, expressions=self._parse_limit_by())
 5814        )
 5815
 5816    def _can_parse_limit_or_offset(self) -> bool:
 5817        if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False):
 5818            return False
 5819
 5820        index = self._index
 5821        result = bool(
 5822            self._try_parse(self._parse_limit, retreat=True)
 5823            or self._try_parse(self._parse_offset, retreat=True)
 5824        )
 5825        self._retreat(index)
 5826
 5827        # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset
 5828        if self._next.token_type == TokenType.MATCH_CONDITION:
 5829            result = False
 5830
 5831        return result
 5832
 5833    def _can_parse_named_window(self) -> bool:
 5834        # `WINDOW` is in ID_VAR_TOKENS so it could be mistakenly consumed as an implicit alias.
 5835        # Refuse only when the following tokens look like a named-window clause: `WINDOW <id> AS (`.
 5836        if not self._match(TokenType.WINDOW, advance=False):
 5837            return False
 5838
 5839        name = self._tokens[self._index + 1] if self._index + 1 < len(self._tokens) else None
 5840        if name is None or name.token_type not in self.ID_VAR_TOKENS:
 5841            return False
 5842
 5843        alias_tok = self._tokens[self._index + 2] if self._index + 2 < len(self._tokens) else None
 5844        if alias_tok is None or alias_tok.token_type != TokenType.ALIAS:
 5845            return False
 5846
 5847        body = self._tokens[self._index + 3] if self._index + 3 < len(self._tokens) else None
 5848        return body is not None and body.token_type == TokenType.L_PAREN
 5849
 5850    def _parse_limit_by(self) -> list[exp.Expr] | None:
 5851        return self._parse_csv(self._parse_bitwise) if self._match_text_seq("BY") else None
 5852
 5853    def _parse_locks(self) -> list[exp.Lock]:
 5854        locks = []
 5855        while True:
 5856            update, key = None, None
 5857            if self._match_text_seq("FOR", "UPDATE"):
 5858                update = True
 5859            elif self._match_text_seq("FOR", "SHARE") or self._match_text_seq(
 5860                "LOCK", "IN", "SHARE", "MODE"
 5861            ):
 5862                update = False
 5863            elif self._match_text_seq("FOR", "KEY", "SHARE"):
 5864                update, key = False, True
 5865            elif self._match_text_seq("FOR", "NO", "KEY", "UPDATE"):
 5866                update, key = True, True
 5867            else:
 5868                break
 5869
 5870            expressions = None
 5871            if self._match_text_seq("OF"):
 5872                expressions = self._parse_csv(lambda: self._parse_table(schema=True))
 5873
 5874            wait: bool | exp.Expr | None = None
 5875            if self._match_text_seq("NOWAIT"):
 5876                wait = True
 5877            elif self._match_text_seq("WAIT"):
 5878                wait = self._parse_primary()
 5879            elif self._match_text_seq("SKIP", "LOCKED"):
 5880                wait = False
 5881
 5882            locks.append(
 5883                self.expression(
 5884                    exp.Lock(update=update, expressions=expressions, wait=wait, key=key)
 5885                )
 5886            )
 5887
 5888        return locks
 5889
 5890    def parse_set_operation(
 5891        self, this: exp.Expr | None, consume_pipe: bool = False
 5892    ) -> exp.Expr | None:
 5893        start = self._index
 5894        _, side_token, kind_token = self._parse_join_parts()
 5895
 5896        side = side_token.text if side_token else None
 5897        kind = kind_token.text if kind_token else None
 5898
 5899        if not self._match_set(self.SET_OPERATIONS):
 5900            self._retreat(start)
 5901            return None
 5902
 5903        token_type = self._prev.token_type
 5904
 5905        if token_type == TokenType.UNION:
 5906            operation: type[exp.SetOperation] = exp.Union
 5907        elif token_type == TokenType.EXCEPT:
 5908            operation = exp.Except
 5909        else:
 5910            operation = exp.Intersect
 5911
 5912        comments = self._prev.comments
 5913
 5914        if self._match(TokenType.DISTINCT):
 5915            distinct: bool | None = True
 5916        elif self._match(TokenType.ALL):
 5917            distinct = False
 5918        else:
 5919            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
 5920            if distinct is None:
 5921                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
 5922
 5923        by_name = (
 5924            self._match_text_seq("BY", "NAME")
 5925            or self._match_text_seq("STRICT", "CORRESPONDING")
 5926            or None
 5927        )
 5928        if self._match_text_seq("CORRESPONDING"):
 5929            by_name = True
 5930            if not side and not kind:
 5931                kind = "INNER"
 5932
 5933        on_column_list = None
 5934        if by_name and self._match_texts(("ON", "BY")):
 5935            on_column_list = self._parse_wrapped_csv(self._parse_column)
 5936
 5937        expression = self._parse_select(
 5938            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
 5939        )
 5940
 5941        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
 5942        # in _parse_cte and so that alias pushdown can reach into set operation branches
 5943        if isinstance(this, exp.Values):
 5944            this = self._values_to_select(this)
 5945        if isinstance(expression, exp.Values):
 5946            expression = self._values_to_select(expression)
 5947
 5948        return self.expression(
 5949            operation(
 5950                this=this,
 5951                distinct=distinct,
 5952                by_name=by_name,
 5953                expression=expression,
 5954                side=side,
 5955                kind=kind,
 5956                on=on_column_list,
 5957            ),
 5958            comments=comments,
 5959        )
 5960
 5961    def _parse_set_operations(self, this: exp.Expr | None) -> exp.Expr | None:
 5962        while this:
 5963            setop = self.parse_set_operation(this)
 5964            if not setop:
 5965                break
 5966            this = setop
 5967
 5968        if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP:
 5969            expression = this.expression
 5970
 5971            if expression:
 5972                for arg in self.SET_OP_MODIFIERS:
 5973                    expr = expression.args.get(arg)
 5974                    if expr:
 5975                        this.set(arg, expr.pop())
 5976
 5977        return this
 5978
 5979    def _parse_expression(self) -> exp.Expr | None:
 5980        return self._parse_alias(self._parse_assignment())
 5981
 5982    def _parse_assignment(self) -> exp.Expr | None:
 5983        this = self._parse_disjunction()
 5984        if not this and self._next.token_type in self.ASSIGNMENT:
 5985            # This allows us to parse <non-identifier token> := <expr>
 5986            this = exp.column(
 5987                t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text)
 5988            )
 5989
 5990        while self._match_set(self.ASSIGNMENT):
 5991            if isinstance(this, exp.Column) and len(this.parts) == 1:
 5992                this = this.this
 5993
 5994            comments = self._prev_comments
 5995            this = self.expression(
 5996                self.ASSIGNMENT[self._prev.token_type](
 5997                    this=this, expression=self._parse_assignment()
 5998                ),
 5999                comments=comments,
 6000            )
 6001
 6002        return this
 6003
 6004    def _parse_disjunction(self) -> exp.Expr | None:
 6005        this = self._parse_conjunction()
 6006        while self._match_set(self.DISJUNCTION):
 6007            comments = self._prev_comments
 6008            this = self.expression(
 6009                self.DISJUNCTION[self._prev.token_type](
 6010                    this=this, expression=self._parse_conjunction()
 6011                ),
 6012                comments=comments,
 6013            )
 6014        return this
 6015
 6016    def _parse_conjunction(self) -> exp.Expr | None:
 6017        this = self._parse_equality()
 6018        while self._match_set(self.CONJUNCTION):
 6019            comments = self._prev_comments
 6020            this = self.expression(
 6021                self.CONJUNCTION[self._prev.token_type](
 6022                    this=this, expression=self._parse_equality()
 6023                ),
 6024                comments=comments,
 6025            )
 6026        return this
 6027
 6028    def _parse_equality(self) -> exp.Expr | None:
 6029        this = self._parse_comparison()
 6030        while self._match_set(self.EQUALITY):
 6031            comments = self._prev_comments
 6032            this = self.expression(
 6033                self.EQUALITY[self._prev.token_type](
 6034                    this=this, expression=self._parse_comparison()
 6035                ),
 6036                comments=comments,
 6037            )
 6038        return this
 6039
 6040    def _parse_comparison(self) -> exp.Expr | None:
 6041        this = self._parse_range()
 6042        while self._match_set(self.COMPARISON):
 6043            comments = self._prev_comments
 6044            this = self.expression(
 6045                self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()),
 6046                comments=comments,
 6047            )
 6048        return this
 6049
 6050    def _parse_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6051        this = this or self._parse_bitwise()
 6052
 6053        while True:
 6054            negate = self._match(TokenType.NOT)
 6055            if self._match_set(self.RANGE_PARSERS):
 6056                expression = self.RANGE_PARSERS[self._prev.token_type](self, this)
 6057                if not expression:
 6058                    return this
 6059
 6060                this = expression
 6061            elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)):
 6062                this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6063            elif self._match(TokenType.NOTNULL):
 6064                # Postgres supports ISNULL and NOTNULL for conditions.
 6065                # https://blog.andreiavram.ro/postgresql-null-composite-type/
 6066                if self.dialect.NORMALIZE_NOT_NULL:
 6067                    this = self.expression(exp.Is(this=this, expression=exp.Null()))
 6068                    this = self.expression(exp.Not(this=this))
 6069                else:
 6070                    this = self.expression(exp.Is(this=this, expression=exp.Null(), negate=True))
 6071            else:
 6072                if negate:
 6073                    self._retreat(self._index - 1)
 6074                break
 6075
 6076            if negate:
 6077                this = self._negate_range(this)
 6078                if self._curr and (
 6079                    self._curr.token_type == TokenType.NOT
 6080                    or self._curr.token_type in self.RANGE_PARSERS
 6081                ):
 6082                    this = self.expression(exp.Paren(this=this))
 6083
 6084        return this
 6085
 6086    def _negate_range(self, this: exp.Expr | None = None) -> exp.Expr | None:
 6087        if not this:
 6088            return this
 6089
 6090        expression = this.this if isinstance(this, exp.Escape) else this
 6091        if isinstance(expression, (exp.Like, exp.ILike)):
 6092            expression.set("negate", True)
 6093            return this
 6094
 6095        return self.expression(exp.Not(this=this))
 6096
 6097    def _parse_is(self, this: exp.Expr | None) -> exp.Expr | None:
 6098        index = self._index - 1
 6099        negate = self._match(TokenType.NOT)
 6100
 6101        if self._match_text_seq("DISTINCT", "FROM"):
 6102            klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ
 6103            return self.expression(klass(this=this, expression=self._parse_bitwise()))
 6104
 6105        if self._match(TokenType.JSON):
 6106            kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper()
 6107
 6108            if self._match_text_seq("WITH"):
 6109                _with = True
 6110            elif self._match_text_seq("WITHOUT"):
 6111                _with = False
 6112            else:
 6113                _with = None
 6114
 6115            unique = self._match(TokenType.UNIQUE)
 6116            self._match_text_seq("KEYS")
 6117            expression: exp.Expr | None = self.expression(
 6118                exp.JSON(this=kind, with_=_with, unique=unique)
 6119            )
 6120        else:
 6121            expression = self._parse_null() or self._parse_bitwise()
 6122            if not expression:
 6123                self._retreat(index)
 6124                return None
 6125
 6126        if negate and isinstance(expression, exp.Null) and not self.dialect.NORMALIZE_NOT_NULL:
 6127            this = self.expression(exp.Is(this=this, expression=expression, negate=True))
 6128        else:
 6129            this = self.expression(exp.Is(this=this, expression=expression))
 6130            this = self.expression(exp.Not(this=this)) if negate else this
 6131
 6132        return self._parse_column_ops(this)
 6133
 6134    def _parse_in(self, this: exp.Expr | None, alias: bool = False) -> exp.In:
 6135        unnest = self._parse_unnest(with_alias=False)
 6136        if unnest:
 6137            this = self.expression(exp.In(this=this, unnest=unnest))
 6138        elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)):
 6139            matched_l_paren = self._prev.token_type == TokenType.L_PAREN
 6140            expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias))
 6141
 6142            if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query):
 6143                this = self.expression(
 6144                    exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False))
 6145                )
 6146            else:
 6147                this = self.expression(exp.In(this=this, expressions=expressions))
 6148
 6149            if matched_l_paren:
 6150                self._match_r_paren(this)
 6151            elif not self._match(TokenType.R_BRACKET, expression=this):
 6152                self.raise_error("Expecting ]")
 6153        else:
 6154            this = self.expression(exp.In(this=this, field=self._parse_column()))
 6155
 6156        return this
 6157
 6158    def _parse_between(self, this: exp.Expr | None) -> exp.Between:
 6159        symmetric = None
 6160        if self._match_text_seq("SYMMETRIC"):
 6161            symmetric = True
 6162        elif self._match_text_seq("ASYMMETRIC"):
 6163            symmetric = False
 6164
 6165        low = self._parse_bitwise()
 6166        self._match(TokenType.AND)
 6167        high = self._parse_bitwise()
 6168
 6169        return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric))
 6170
 6171    def _parse_escape(self, this: exp.Expr | None) -> exp.Expr | None:
 6172        if not self._match(TokenType.ESCAPE):
 6173            return this
 6174        return self.expression(
 6175            exp.Escape(this=this, expression=self._parse_string() or self._parse_null())
 6176        )
 6177
 6178    def _parse_interval_span(
 6179        self, this: exp.Expr, parse_function_unit: bool = True
 6180    ) -> exp.Interval:
 6181        # handle day-time format interval span with omitted units:
 6182        #   INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`>
 6183        interval_span_units_omitted = None
 6184        if (
 6185            this
 6186            and this.is_string
 6187            and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
 6188            and exp.INTERVAL_DAY_TIME_RE.match(this.name)
 6189        ):
 6190            index = self._index
 6191
 6192            # Var "TO" Var
 6193            first_unit = self._parse_var(any_token=True, upper=True)
 6194            second_unit = None
 6195            if first_unit and self._match_text_seq("TO"):
 6196                second_unit = self._parse_var(any_token=True, upper=True)
 6197
 6198            interval_span_units_omitted = not (first_unit and second_unit)
 6199
 6200            self._retreat(index)
 6201
 6202        unit_index = self._index
 6203        if interval_span_units_omitted:
 6204            unit = None
 6205        else:
 6206            # Only attempt to parse a unit if the current token can actually be one, so that a
 6207            # trailing operator isn't swallowed, e.g. INTERVAL '1 day' AND (x)
 6208            is_unit = self._curr is not None and (
 6209                self._curr.token_type == TokenType.VAR
 6210                or self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS
 6211            )
 6212            unit = self._parse_function() if parse_function_unit and is_unit else None
 6213            if not unit and is_unit:
 6214                unit = self._parse_var(any_token=True, upper=True)
 6215
 6216        # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse
 6217        # each INTERVAL expression into this canonical form so it's easy to transpile
 6218        if this and this.is_number:
 6219            try:
 6220                this = exp.Literal.string(this.to_py())
 6221            except ValueError:
 6222                self.raise_error(f"Invalid numeric interval literal: {this.name!r}")
 6223        elif this and this.is_string:
 6224            parts = exp.INTERVAL_STRING_RE.findall(this.name)
 6225            if parts and unit:
 6226                # Unconsume the eagerly-parsed unit, since the real unit was part of the string
 6227                unit = None
 6228                self._retreat(unit_index)
 6229
 6230            if len(parts) == 1:
 6231                this = exp.Literal.string(parts[0][0])
 6232                unit = self.expression(exp.Var(this=parts[0][1].upper()))
 6233
 6234        if self.INTERVAL_SPANS and self._match_text_seq("TO"):
 6235            unit = self.expression(
 6236                exp.IntervalSpan(
 6237                    this=unit,
 6238                    expression=self._parse_function()
 6239                    or self._parse_var(any_token=True, upper=True),
 6240                )
 6241            )
 6242
 6243        return self.expression(exp.Interval(this=this, unit=unit))
 6244
 6245    def _parse_interval(
 6246        self, require_interval: bool = True, parse_function_unit: bool = True
 6247    ) -> exp.Add | exp.Interval | None:
 6248        index = self._index
 6249
 6250        if not self._match(TokenType.INTERVAL) and require_interval:
 6251            return None
 6252
 6253        if self._match(TokenType.STRING, advance=False):
 6254            this = self._parse_primary()
 6255        else:
 6256            this = self._parse_term()
 6257
 6258        if not this or (
 6259            isinstance(this, exp.Column)
 6260            and not this.table
 6261            and not this.this.quoted
 6262            and self._curr
 6263            and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS
 6264        ):
 6265            self._retreat(index)
 6266            return None
 6267
 6268        interval = self._parse_interval_span(this, parse_function_unit=parse_function_unit)
 6269
 6270        index = self._index
 6271        self._match(TokenType.PLUS)
 6272
 6273        # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals
 6274        if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False):
 6275            return self.expression(
 6276                exp.Add(
 6277                    this=interval,
 6278                    expression=self._parse_interval(False, parse_function_unit=parse_function_unit),
 6279                )
 6280            )
 6281
 6282        self._retreat(index)
 6283        return interval
 6284
 6285    def _parse_bitwise(self) -> exp.Expr | None:
 6286        this = self._parse_term()
 6287
 6288        while True:
 6289            if self._match_set(self.BITWISE):
 6290                this = self.expression(
 6291                    self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term())
 6292                )
 6293            elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE):
 6294                this = self.expression(
 6295                    exp.DPipe(
 6296                        this=this,
 6297                        expression=self._parse_term(),
 6298                        safe=not self.dialect.STRICT_STRING_CONCAT,
 6299                    )
 6300                )
 6301            elif self._match(TokenType.DQMARK):
 6302                this = self.expression(
 6303                    exp.Coalesce(this=this, expressions=ensure_list(self._parse_term()))
 6304                )
 6305            elif self._match_pair(TokenType.LT, TokenType.LT):
 6306                this = self.expression(
 6307                    exp.BitwiseLeftShift(this=this, expression=self._parse_term())
 6308                )
 6309            elif self._match_pair(TokenType.GT, TokenType.GT):
 6310                this = self.expression(
 6311                    exp.BitwiseRightShift(this=this, expression=self._parse_term())
 6312                )
 6313            elif self.JSON_OPERATORS and self._match_set(self.JSON_OPERATORS):
 6314                this = self.JSON_OPERATORS[self._prev.token_type](self, this, self._parse_term())
 6315            else:
 6316                break
 6317
 6318        return this
 6319
 6320    def _parse_term(self) -> exp.Expr | None:
 6321        this = self._parse_factor()
 6322
 6323        while self._match_set(self.TERM):
 6324            klass = self.TERM[self._prev.token_type]
 6325            comments = self._prev_comments
 6326            expression = self._parse_factor()
 6327
 6328            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6329
 6330            if isinstance(this, exp.Collate):
 6331                self._normalize_collate(this)
 6332
 6333        return this
 6334
 6335    def _normalize_collate(self, collate: exp.Collate) -> None:
 6336        expr = collate.expression
 6337
 6338        # Preserve collations such as pg_catalog."default" (Postgres) as columns, otherwise
 6339        # fallback to Identifier / Var
 6340        if isinstance(expr, exp.Column) and len(expr.parts) == 1:
 6341            ident = expr.this
 6342            if isinstance(ident, exp.Identifier):
 6343                collate.set("expression", ident if ident.quoted else exp.var(ident.name))
 6344
 6345    def _parse_factor(self) -> exp.Expr | None:
 6346        parse_method = self._parse_factor_operand
 6347        this = self._parse_at_time_zone(parse_method())
 6348
 6349        while self._match_set(self.FACTOR):
 6350            klass = self.FACTOR[self._prev.token_type]
 6351            comments = self._prev_comments
 6352            expression = parse_method()
 6353
 6354            if not expression and klass is exp.IntDiv and self._prev.text.isalpha():
 6355                self._retreat(self._index - 1)
 6356                return this
 6357
 6358            this = self.expression(klass(this=this, expression=expression), comments=comments)
 6359
 6360            if isinstance(this, exp.Div):
 6361                this.set("typed", self.dialect.TYPED_DIVISION)
 6362                this.set("safe", self.dialect.SAFE_DIVISION)
 6363
 6364        return this
 6365
 6366    def _parse_factor_operand(self) -> exp.Expr | None:
 6367        return self._parse_exponent() if self.EXPONENT else self._parse_unary()
 6368
 6369    def _parse_exponent(self) -> exp.Expr | None:
 6370        this = self._parse_unary()
 6371        while self._match_set(self.EXPONENT):
 6372            comments = self._prev_comments
 6373            this = self.expression(
 6374                self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()),
 6375                comments=comments,
 6376            )
 6377        return this
 6378
 6379    def _parse_unary(self) -> exp.Expr | None:
 6380        if self._match_set(self.UNARY_PARSERS):
 6381            return self.UNARY_PARSERS[self._prev.token_type](self)
 6382        return self._parse_type()
 6383
 6384    def _parse_type(
 6385        self, parse_interval: bool = True, fallback_to_identifier: bool = False
 6386    ) -> exp.Expr | None:
 6387        if not fallback_to_identifier and (atom := self._parse_atom()) is not None:
 6388            return atom
 6389
 6390        if interval := parse_interval and self._parse_interval():
 6391            return self._parse_column_ops(interval)
 6392
 6393        index = self._index
 6394        data_type = self._parse_types(check_func=True, allow_identifiers=False)
 6395
 6396        # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g.
 6397        # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>)
 6398        if isinstance(data_type, exp.Cast):
 6399            # This constructor can contain ops directly after it, for instance struct unnesting:
 6400            # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).*
 6401            return self._parse_column_ops(data_type)
 6402
 6403        if data_type:
 6404            index2 = self._index
 6405            this = self._parse_primary()
 6406
 6407            if isinstance(this, exp.Literal):
 6408                literal = this.name
 6409                this = self._parse_column_ops(this)
 6410
 6411                parser = self.TYPE_LITERAL_PARSERS.get(data_type.this)
 6412                if parser:
 6413                    return parser(self, this, data_type)
 6414
 6415                if (
 6416                    self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR
 6417                    and data_type.is_type(exp.DType.TIMESTAMP)
 6418                    and TIME_ZONE_RE.search(literal)
 6419                ):
 6420                    data_type = exp.DType.TIMESTAMPTZ.into_expr()
 6421
 6422                return self.expression(exp.Cast(this=this, to=data_type))
 6423
 6424            # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0)
 6425            # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 )
 6426            #
 6427            # If the index difference here is greater than 1, that means the parser itself must have
 6428            # consumed additional tokens such as the DECIMAL scale and precision in the above example.
 6429            #
 6430            # If it's not greater than 1, then it must be 1, because we've consumed at least the type
 6431            # keyword, meaning that the expressions arg of the DataType must have gotten set by a
 6432            # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to
 6433            # DECIMAL(38, 0)) in order to facilitate the data type's transpilation.
 6434            #
 6435            # In these cases, we don't really want to return the converted type, but instead retreat
 6436            # and try to parse a Column or Identifier in the section below.
 6437            if data_type.expressions and index2 - index > 1:
 6438                self._retreat(index2)
 6439                return self._parse_column_ops(data_type)
 6440
 6441            self._retreat(index)
 6442
 6443        if fallback_to_identifier:
 6444            return self._parse_id_var()
 6445
 6446        return self._parse_column()
 6447
 6448    def _parse_type_size(self) -> exp.DataTypeParam | None:
 6449        this = self._parse_type()
 6450        if not this:
 6451            return None
 6452
 6453        if isinstance(this, exp.Column) and not this.table:
 6454            this = exp.var(this.name.upper())
 6455
 6456        return self.expression(
 6457            exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True))
 6458        )
 6459
 6460    def _parse_user_defined_type(self, identifier: exp.Identifier) -> exp.Expr | None:
 6461        type_name = identifier.name
 6462
 6463        while self._match(TokenType.DOT):
 6464            type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
 6465
 6466        return exp.DataType.from_str(type_name, dialect=self.dialect, udt=True)
 6467
 6468    def _parse_types(
 6469        self,
 6470        check_func: bool = False,
 6471        schema: bool = False,
 6472        allow_identifiers: bool = True,
 6473        with_collation: bool = False,
 6474    ) -> exp.Expr | None:
 6475        index = self._index
 6476        this: exp.Expr | None = None
 6477
 6478        if self._match_set(self.TYPE_TOKENS):
 6479            type_token = self._prev.token_type
 6480        else:
 6481            type_token = None
 6482            identifier = allow_identifiers and self._parse_id_var(
 6483                any_token=False, tokens=(TokenType.VAR,)
 6484            )
 6485            if isinstance(identifier, exp.Identifier):
 6486                try:
 6487                    tokens = self.dialect.tokenize(identifier.name)
 6488                except TokenError:
 6489                    tokens = None
 6490
 6491                if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS:
 6492                    if len(tokens) > 1:
 6493                        return exp.DataType.from_str(identifier.name, dialect=self.dialect)
 6494                elif self.dialect.SUPPORTS_USER_DEFINED_TYPES:
 6495                    this = self._parse_user_defined_type(identifier)
 6496                else:
 6497                    self._retreat(self._index - 1)
 6498                    return None
 6499            else:
 6500                return None
 6501
 6502        if type_token == TokenType.PSEUDO_TYPE:
 6503            return self.expression(exp.PseudoType(this=self._prev.text.upper()))
 6504
 6505        if type_token == TokenType.OBJECT_IDENTIFIER:
 6506            return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper()))
 6507
 6508        # https://materialize.com/docs/sql/types/map/
 6509        if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET):
 6510            key_type = self._parse_types(
 6511                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6512            )
 6513            if not self._match(TokenType.FARROW):
 6514                self._retreat(index)
 6515                return None
 6516
 6517            value_type = self._parse_types(
 6518                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6519            )
 6520            if not self._match(TokenType.R_BRACKET):
 6521                self._retreat(index)
 6522                return None
 6523
 6524            return exp.DataType(
 6525                this=exp.DType.MAP,
 6526                expressions=[key_type, value_type],
 6527                nested=True,
 6528            )
 6529
 6530        nested = type_token in self.NESTED_TYPE_TOKENS
 6531        is_struct = type_token in self.STRUCT_TYPE_TOKENS
 6532        is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS
 6533        expressions = None
 6534        maybe_func = False
 6535
 6536        if self._match(TokenType.L_PAREN):
 6537            if is_struct:
 6538                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6539            elif nested:
 6540                expressions = self._parse_csv(
 6541                    lambda: self._parse_types(
 6542                        check_func=check_func, schema=schema, allow_identifiers=allow_identifiers
 6543                    )
 6544                )
 6545                if type_token == TokenType.NULLABLE and len(expressions) == 1:
 6546                    this = expressions[0]
 6547                    this.set("nullable", True)
 6548                    self._match_r_paren()
 6549                    return this
 6550            elif type_token in self.ENUM_TYPE_TOKENS:
 6551                expressions = self._parse_csv(self._parse_equality)
 6552            elif type_token == TokenType.JSON:
 6553                # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value)
 6554                # https://clickhouse.com/docs/sql-reference/data-types/newjson
 6555                expressions = self._parse_csv(self._parse_json_type_arg)
 6556            elif is_aggregate:
 6557                func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var(
 6558                    any_token=False, tokens=(TokenType.VAR, TokenType.ANY)
 6559                )
 6560                if not func_or_ident:
 6561                    return None
 6562                expressions = [func_or_ident]
 6563                if self._match(TokenType.COMMA):
 6564                    expressions.extend(
 6565                        self._parse_csv(
 6566                            lambda: self._parse_types(
 6567                                check_func=check_func,
 6568                                schema=schema,
 6569                                allow_identifiers=allow_identifiers,
 6570                            )
 6571                        )
 6572                    )
 6573            else:
 6574                expressions = self._parse_csv(self._parse_type_size)
 6575
 6576                # https://docs.snowflake.com/en/sql-reference/data-types-vector
 6577                if type_token == TokenType.VECTOR and len(expressions) == 2:
 6578                    expressions = self._parse_vector_expressions(expressions)
 6579
 6580            if not self._match(TokenType.R_PAREN):
 6581                self._retreat(index)
 6582                return None
 6583
 6584            maybe_func = True
 6585
 6586        values: list[exp.Expr] | None = None
 6587
 6588        if nested and self._match(TokenType.LT):
 6589            if is_struct:
 6590                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))
 6591            else:
 6592                expressions = self._parse_csv(
 6593                    lambda: self._parse_types(
 6594                        check_func=check_func,
 6595                        schema=schema,
 6596                        allow_identifiers=allow_identifiers,
 6597                        with_collation=True,
 6598                    )
 6599                )
 6600
 6601            if not self._match(TokenType.GT):
 6602                self.raise_error("Expecting >")
 6603
 6604            if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)):
 6605                values = self._parse_csv(self._parse_disjunction)
 6606                if not values and is_struct:
 6607                    values = None
 6608                    self._retreat(self._index - 1)
 6609                else:
 6610                    self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN))
 6611
 6612        if type_token in self.TIMESTAMPS:
 6613            if self._match_text_seq("WITH", "TIME", "ZONE"):
 6614                maybe_func = False
 6615                tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ
 6616                this = exp.DataType(this=tz_type, expressions=expressions)
 6617            elif self._match_text_seq("WITH", "LOCAL", "TIME", "ZONE"):
 6618                maybe_func = False
 6619                this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions)
 6620            elif self._match_text_seq("WITHOUT", "TIME", "ZONE"):
 6621                maybe_func = False
 6622        elif type_token == TokenType.INTERVAL:
 6623            if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS:
 6624                unit = self._parse_var(upper=True)
 6625                if self._match_text_seq("TO"):
 6626                    unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True))
 6627
 6628                this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit))))
 6629            else:
 6630                this = self.expression(exp.DataType(this=exp.DType.INTERVAL))
 6631        elif type_token == TokenType.VOID:
 6632            this = exp.DataType(this=exp.DType.NULL)
 6633
 6634        if maybe_func and check_func:
 6635            index2 = self._index
 6636            peek = self._parse_string()
 6637
 6638            if not peek:
 6639                self._retreat(index)
 6640                return None
 6641
 6642            self._retreat(index2)
 6643
 6644        if not this:
 6645            assert type_token is not None
 6646            if self._match_text_seq("UNSIGNED"):
 6647                unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token)
 6648                if not unsigned_type_token:
 6649                    self.raise_error(f"Cannot convert {type_token.name} to unsigned.")
 6650
 6651                type_token = unsigned_type_token or type_token
 6652
 6653            # NULLABLE without parentheses can be a column (Presto/Trino)
 6654            if type_token == TokenType.NULLABLE and not expressions:
 6655                self._retreat(index)
 6656                return None
 6657
 6658            this = exp.DataType(
 6659                this=exp.DType[type_token.name],
 6660                expressions=expressions,
 6661                nested=nested,
 6662            )
 6663
 6664            # Empty arrays/structs are allowed
 6665            if values is not None:
 6666                cls = exp.Struct if is_struct else exp.Array
 6667                this = exp.cast(cls(expressions=values), this, copy=False)
 6668
 6669        elif expressions:
 6670            this.set("expressions", expressions)
 6671
 6672        # https://materialize.com/docs/sql/types/list/#type-name
 6673        while self._match(TokenType.LIST):
 6674            this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True)
 6675
 6676        index = self._index
 6677
 6678        # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3]
 6679        matched_array = self._match(TokenType.ARRAY)
 6680
 6681        while self._curr:
 6682            datatype_token = self._prev.token_type
 6683            matched_l_bracket = self._match(TokenType.L_BRACKET)
 6684
 6685            if (not matched_l_bracket and not matched_array) or (
 6686                datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET)
 6687            ):
 6688                # Postgres allows casting empty arrays such as ARRAY[]::INT[],
 6689                # not to be confused with the fixed size array parsing
 6690                break
 6691
 6692            matched_array = False
 6693            values = self._parse_csv(self._parse_disjunction) or None
 6694            if (
 6695                values
 6696                and not schema
 6697                and (
 6698                    not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS
 6699                    or datatype_token == TokenType.ARRAY
 6700                    or not self._match(TokenType.R_BRACKET, advance=False)
 6701                )
 6702            ):
 6703                # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB
 6704                # 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
 6705                self._retreat(index)
 6706                break
 6707
 6708            this = exp.DataType(
 6709                this=exp.DType.ARRAY, expressions=[this], values=values, nested=True
 6710            )
 6711            self._match(TokenType.R_BRACKET)
 6712
 6713        if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType):
 6714            converter = self.TYPE_CONVERTERS.get(this.this)
 6715            if converter:
 6716                this = converter(t.cast(exp.DataType, this))
 6717
 6718        if with_collation and isinstance(this, exp.DataType) and self._match(TokenType.COLLATE):
 6719            this.set("collate", self._parse_identifier() or self._parse_column())
 6720
 6721        return this
 6722
 6723    def _parse_json_type_arg(self) -> exp.Expr | None:
 6724        """Parse a single argument to ClickHouse's JSON type."""
 6725
 6726        # SKIP col or SKIP REGEXP 'pattern'
 6727        if self._match_text_seq("SKIP"):
 6728            regexp = self._match(TokenType.RLIKE)
 6729            arg = self._parse_column()
 6730            if isinstance(arg, exp.Column):
 6731                arg = arg.to_dot()
 6732            return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg))
 6733
 6734        param_or_col = self._parse_column()
 6735        if not isinstance(param_or_col, exp.Column):
 6736            return None
 6737
 6738        # Parameter: name=value (e.g., max_dynamic_paths=2)
 6739        if len(param_or_col.parts) == 1 and self._match(TokenType.EQ):
 6740            param = param_or_col.name
 6741            value = self._parse_primary()
 6742            return self.expression(exp.EQ(this=exp.var(param), expression=value))
 6743
 6744        # Column type hint: col_name Type
 6745        col = param_or_col.to_dot()
 6746        kind = self._parse_types(check_func=False, allow_identifiers=False)
 6747        return self.expression(exp.ColumnDef(this=col, kind=kind))
 6748
 6749    def _parse_vector_expressions(self, expressions: list[exp.Expr]) -> list[exp.Expr]:
 6750        return [exp.DataType.from_str(expressions[0].name, dialect=self.dialect), *expressions[1:]]
 6751
 6752    def _parse_struct_types(self, type_required: bool = False) -> exp.Expr | None:
 6753        index = self._index
 6754
 6755        if (
 6756            self._curr
 6757            and self._next
 6758            and self._curr.token_type in self.TYPE_TOKENS
 6759            and self._next.token_type in self.TYPE_TOKENS
 6760        ):
 6761            # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a
 6762            # type token. Without this, the list will be parsed as a type and we'll eventually crash
 6763            this = self._parse_id_var()
 6764        else:
 6765            this = (
 6766                self._parse_type(parse_interval=False, fallback_to_identifier=True)
 6767                or self._parse_id_var()
 6768            )
 6769
 6770        self._match(TokenType.COLON)
 6771
 6772        if (
 6773            type_required
 6774            and not isinstance(this, exp.DataType)
 6775            and not self._match_set(self.TYPE_TOKENS, advance=False)
 6776        ):
 6777            self._retreat(index)
 6778            return self._parse_types()
 6779
 6780        return self._parse_column_def(this)
 6781
 6782    def _parse_at_time_zone(self, this: exp.Expr | None) -> exp.Expr | None:
 6783        if not self._match_text_seq("AT", "TIME", "ZONE"):
 6784            return this
 6785        return self._parse_at_time_zone(
 6786            self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary()))
 6787        )
 6788
 6789    def _parse_atom(self) -> exp.Expr | None:
 6790        if (
 6791            self._curr.token_type in self.IDENTIFIER_TOKENS
 6792            and (column := self._parse_column()) is not None
 6793        ):
 6794            return column
 6795
 6796        token = self._curr
 6797        token_type = token.token_type
 6798
 6799        if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)):
 6800            return None
 6801
 6802        next_type = self._next.token_type
 6803
 6804        if (
 6805            next_type in self.COLUMN_OPERATORS
 6806            or next_type in self.COLUMN_POSTFIX_TOKENS
 6807            or (token_type == TokenType.STRING and next_type == TokenType.STRING)
 6808        ):
 6809            return None
 6810
 6811        self._advance()
 6812        return primary_parser(self, token)
 6813
 6814    def _parse_column(self) -> exp.Expr | None:
 6815        column: exp.Expr | None = self._parse_column_parts_fast()
 6816        if column is None:
 6817            this = self._parse_column_reference()
 6818            if not this:
 6819                this = self._parse_bracket(this)
 6820            column = self._parse_column_ops(this) if this else this
 6821
 6822        if column:
 6823            if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:
 6824                column.set("join_mark", self._match(TokenType.JOIN_MARKER))
 6825            if self.COLON_IS_VARIANT_EXTRACT:
 6826                column = self._parse_colon_as_variant_extract(column)
 6827
 6828        return column
 6829
 6830    def _parse_column_parts_fast(self) -> exp.Column | exp.Dot | None:
 6831        """Fast path for simple column and dot references (a, a.b, ...).
 6832
 6833        Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks
 6834        that nothing complex follows. If it does, retreats and returns None so
 6835        the slow path can handle it. For >4 parts, wraps in exp.Dot nodes.
 6836        """
 6837        index = self._index
 6838        parts: list[exp.Identifier] | None = None
 6839        all_comments: list[str] | None = None
 6840
 6841        while self._match_set(self.IDENTIFIER_TOKENS):
 6842            token = self._prev
 6843            comments = self._prev_comments
 6844
 6845            if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS:
 6846                self._retreat(index)
 6847                return None
 6848
 6849            has_dot = self._match(TokenType.DOT)
 6850            curr_tt = self._curr.token_type
 6851
 6852            if not has_dot:
 6853                if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS:
 6854                    self._retreat(index)
 6855                    return None
 6856            elif curr_tt not in self.IDENTIFIER_TOKENS:
 6857                self._retreat(index)
 6858                return None
 6859
 6860            if parts is None:
 6861                parts = []
 6862
 6863            if comments:
 6864                if all_comments is None:
 6865                    all_comments = []
 6866                all_comments.extend(comments)
 6867                self._prev_comments = []
 6868
 6869            parts.append(
 6870                self.expression(
 6871                    exp.Identifier(
 6872                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER
 6873                    ),
 6874                    token,
 6875                )
 6876            )
 6877
 6878            if not has_dot:
 6879                break
 6880
 6881        if parts is None:
 6882            return None
 6883
 6884        n = len(parts)
 6885
 6886        if n == 1:
 6887            column: exp.Column | exp.Dot = exp.Column(this=parts[0])
 6888        elif n == 2:
 6889            column = exp.Column(this=parts[1], table=parts[0])
 6890        elif n == 3:
 6891            column = exp.Column(this=parts[2], table=parts[1], db=parts[0])
 6892        else:
 6893            column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0])
 6894
 6895            for i in range(4, n):
 6896                column = exp.Dot(this=column, expression=parts[i])
 6897
 6898        if all_comments:
 6899            column.add_comments(all_comments)
 6900
 6901        return column
 6902
 6903    def _parse_column_reference(self) -> exp.Expr | None:
 6904        this = self._parse_field()
 6905        if (
 6906            not this
 6907            and self._match(TokenType.VALUES, advance=False)
 6908            and self.VALUES_FOLLOWED_BY_PAREN
 6909            and (not self._next or self._next.token_type != TokenType.L_PAREN)
 6910        ):
 6911            this = self._parse_id_var()
 6912
 6913        if isinstance(this, exp.Identifier):
 6914            # We bubble up comments from the Identifier to the Column
 6915            this = self.expression(exp.Column(this=this), comments=this.pop_comments())
 6916
 6917        return this
 6918
 6919    def _build_json_extract(
 6920        self,
 6921        this: exp.Expr | None,
 6922        path_parts: list[exp.JSONPathPart],
 6923    ) -> tuple[exp.Expr | None, list[exp.JSONPathPart]]:
 6924        if len(path_parts) > 1:
 6925            this = self.expression(
 6926                exp.JSONExtract(
 6927                    this=this,
 6928                    expression=exp.JSONPath(expressions=path_parts),
 6929                    variant_extract=True,
 6930                    requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION,
 6931                )
 6932            )
 6933            path_parts = [exp.JSONPathRoot()]
 6934
 6935        return this, path_parts
 6936
 6937    def _parse_colon_as_variant_extract(self, this: exp.Expr | None) -> exp.Expr | None:
 6938        path_parts: list[exp.JSONPathPart] = [exp.JSONPathRoot()]
 6939
 6940        while self._match(TokenType.COLON):
 6941            if not self.COLON_CHAIN_IS_SINGLE_EXTRACT:
 6942                this, path_parts = self._build_json_extract(this, path_parts)
 6943
 6944            key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 6945
 6946            if key:
 6947                quoted = isinstance(key, exp.Identifier) and key.quoted
 6948                path_parts.append(exp.JSONPathKey(this=key.name, quoted=quoted))
 6949
 6950            while True:
 6951                if self._match(TokenType.DOT):
 6952                    next_key = self._parse_id_var(any_token=True, tokens=(TokenType.SELECT,))
 6953
 6954                    if next_key:
 6955                        quoted = isinstance(next_key, exp.Identifier) and next_key.quoted
 6956                        path_parts.append(exp.JSONPathKey(this=next_key.name, quoted=quoted))
 6957                elif self._match(TokenType.L_BRACKET):
 6958                    bracket_expr = self._parse_bracket_key_value()
 6959
 6960                    if not self._match(TokenType.R_BRACKET):
 6961                        self.raise_error("Expected ]")
 6962
 6963                    if bracket_expr:
 6964                        if bracket_expr.is_string:
 6965                            path_parts.append(exp.JSONPathKey(this=bracket_expr.name, quoted=True))
 6966                        elif bracket_expr.is_star:
 6967                            path_parts.append(exp.JSONPathSubscript(this=exp.JSONPathWildcard()))
 6968                        elif bracket_expr.is_number:
 6969                            path_parts.append(exp.JSONPathSubscript(this=bracket_expr.to_py()))
 6970                        else:
 6971                            this, path_parts = self._build_json_extract(this, path_parts)
 6972
 6973                            this = self.expression(
 6974                                exp.Bracket(
 6975                                    this=this, expressions=[bracket_expr], json_access=True
 6976                                ),
 6977                            )
 6978
 6979                elif self._match(TokenType.DCOLON):
 6980                    this, path_parts = self._build_json_extract(this, path_parts)
 6981
 6982                    cast_type = self._parse_types()
 6983                    if cast_type:
 6984                        this = self.expression(exp.Cast(this=this, to=cast_type))
 6985                    else:
 6986                        self.raise_error("Expected type after '::'")
 6987                else:
 6988                    break
 6989
 6990        this, _ = self._build_json_extract(this, path_parts)
 6991
 6992        return this
 6993
 6994    def _parse_dcolon(self) -> exp.Expr | None:
 6995        return self._parse_types()
 6996
 6997    def _parse_column_ops(self, this: exp.Expr | None) -> exp.Expr | None:
 6998        while self._curr.token_type in self.BRACKETS:
 6999            this = self._parse_bracket(this)
 7000
 7001        column_operators = self.COLUMN_OPERATORS
 7002        cast_column_operators = self.CAST_COLUMN_OPERATORS
 7003        while self._curr:
 7004            op_token = self._curr.token_type
 7005
 7006            if op_token not in column_operators:
 7007                break
 7008            op = column_operators[op_token]
 7009            self._advance()
 7010
 7011            if op_token in cast_column_operators:
 7012                field = self._parse_dcolon()
 7013                if not field:
 7014                    self.raise_error("Expected type")
 7015            elif op and self._curr:
 7016                field = self._parse_column_reference() or self._parse_bitwise()
 7017                if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False):
 7018                    field = self._parse_column_ops(field)
 7019            else:
 7020                dot = self._is_connected() and self._prev.token_type == TokenType.DOT
 7021                field = self._parse_field(any_token=True, anonymous_func=True)
 7022
 7023                # In t.true, t.null we should produce an Identifier node
 7024                if dot and isinstance(field, (exp.Null, exp.Boolean)):
 7025                    field = self.expression(
 7026                        exp.Identifier(this=self._prev.text),
 7027                        comments=field.comments,
 7028                    )
 7029
 7030            # Function calls can be qualified, e.g., x.y.FOO()
 7031            # This converts the final AST to a series of Dots leading to the function call
 7032            # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules
 7033            if isinstance(field, (exp.Func, exp.Window)) and this:
 7034                this = this.transform(
 7035                    lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n
 7036                )
 7037
 7038            if op:
 7039                this = op(self, this, field)
 7040            elif isinstance(this, exp.Column) and not this.args.get("catalog"):
 7041                this = self.expression(
 7042                    exp.Column(
 7043                        this=field,
 7044                        table=this.this,
 7045                        db=this.args.get("table"),
 7046                        catalog=this.args.get("db"),
 7047                    ),
 7048                    comments=this.comments,
 7049                )
 7050            elif isinstance(field, exp.Window):
 7051                # Move the exp.Dot's to the window's function
 7052                window_func = self.expression(exp.Dot(this=this, expression=field.this))
 7053                field.set("this", window_func)
 7054                this = field
 7055            else:
 7056                this = self.expression(exp.Dot(this=this, expression=field))
 7057
 7058            if field and field.comments:
 7059                t.cast(exp.Expr, this).add_comments(field.pop_comments())
 7060
 7061            this = self._parse_bracket(this)
 7062
 7063        return this
 7064
 7065    def _parse_paren(self) -> exp.Expr | None:
 7066        if not self._match(TokenType.L_PAREN):
 7067            return None
 7068
 7069        comments = self._prev_comments
 7070        query = self._parse_select()
 7071
 7072        if query:
 7073            expressions = [query]
 7074        else:
 7075            expressions = self._parse_expressions()
 7076
 7077        this = seq_get(expressions, 0)
 7078
 7079        if not this and self._match(TokenType.R_PAREN, advance=False):
 7080            this = self.expression(exp.Tuple())
 7081        elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA:
 7082            this = self.expression(exp.Tuple(expressions=expressions))
 7083        elif isinstance(this, exp.UNWRAPPED_QUERIES):
 7084            this = self._parse_subquery(this=this, parse_alias=False)
 7085        elif isinstance(this, (exp.Subquery, exp.Values)):
 7086            this = self._parse_subquery(
 7087                this=self._parse_query_modifiers(self._parse_set_operations(this)),
 7088                parse_alias=False,
 7089            )
 7090        else:
 7091            this = self.expression(exp.Paren(this=this))
 7092
 7093        if this:
 7094            this.add_comments(comments)
 7095
 7096        self._match_r_paren(expression=this)
 7097
 7098        if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc):
 7099            return self._parse_window(this)
 7100
 7101        return this
 7102
 7103    def _parse_primary(self) -> exp.Expr | None:
 7104        if self._match_set(self.PRIMARY_PARSERS):
 7105            token_type = self._prev.token_type
 7106            primary = self.PRIMARY_PARSERS[token_type](self, self._prev)
 7107
 7108            if token_type == TokenType.STRING:
 7109                expressions = [primary]
 7110                while self._match(TokenType.STRING, advance=False):
 7111                    if self._is_connected() and self.ADJACENT_STRINGS_CANNOT_BE_CONNECTED:
 7112                        self.raise_error(
 7113                            "Adjacent string literals need to be separated by whitespace or comments"
 7114                        )
 7115
 7116                    self._advance()
 7117                    expressions.append(exp.Literal.string(self._prev.text))
 7118
 7119                if len(expressions) > 1:
 7120                    return self.expression(
 7121                        exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE)
 7122                    )
 7123
 7124            return primary
 7125
 7126        if self._match_pair(TokenType.DOT, TokenType.NUMBER):
 7127            return exp.Literal.number(f"0.{self._prev.text}")
 7128
 7129        return self._parse_paren()
 7130
 7131    def _parse_field(
 7132        self,
 7133        any_token: bool = False,
 7134        tokens: t.Collection[TokenType] | None = None,
 7135        anonymous_func: bool = False,
 7136    ) -> exp.Expr | None:
 7137        if anonymous_func:
 7138            field = (
 7139                self._parse_function(anonymous=anonymous_func, any_token=any_token)
 7140                or self._parse_primary()
 7141            )
 7142        else:
 7143            field = self._parse_primary() or self._parse_function(
 7144                anonymous=anonymous_func, any_token=any_token
 7145            )
 7146        return field or self._parse_id_var(any_token=any_token, tokens=tokens)
 7147
 7148    def _parse_function(
 7149        self,
 7150        functions: dict[str, t.Callable] | None = None,
 7151        anonymous: bool = False,
 7152        optional_parens: bool = True,
 7153        any_token: bool = False,
 7154    ) -> exp.Expr | None:
 7155        # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this)
 7156        # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences
 7157        fn_syntax = False
 7158        if (
 7159            self._match(TokenType.L_BRACE, advance=False)
 7160            and self._next
 7161            and self._next.text.upper() == "FN"
 7162        ):
 7163            self._advance(2)
 7164            fn_syntax = True
 7165
 7166        func = self._parse_function_call(
 7167            functions=functions,
 7168            anonymous=anonymous,
 7169            optional_parens=optional_parens,
 7170            any_token=any_token,
 7171        )
 7172
 7173        if fn_syntax:
 7174            self._match(TokenType.R_BRACE)
 7175
 7176        return func
 7177
 7178    def _parse_function_args(self, alias: bool = False) -> list[exp.Expr]:
 7179        return self._parse_csv(lambda: self._parse_lambda(alias=alias))
 7180
 7181    def _parse_connector_function(self, connector: t.Callable[..., exp.Condition]) -> exp.Paren:
 7182        args = self._parse_function_args(alias=False)
 7183        if not args:
 7184            self.raise_error("Expected at least one argument")
 7185
 7186        # Wrapped so the connector keeps its precedence in the parent context
 7187        return exp.Paren(this=connector(*args, copy=False))
 7188
 7189    def _parse_function_call(
 7190        self,
 7191        functions: dict[str, t.Callable] | None = None,
 7192        anonymous: bool = False,
 7193        optional_parens: bool = True,
 7194        any_token: bool = False,
 7195    ) -> exp.Expr | None:
 7196        if not self._curr:
 7197            return None
 7198
 7199        comments = self._curr.comments
 7200        prev = self._prev
 7201        token = self._curr
 7202        token_type = self._curr.token_type
 7203        this: str | exp.Expr = self._curr.text
 7204        upper = self._curr.text.upper()
 7205
 7206        after_dot = prev.token_type == TokenType.DOT
 7207        parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper)
 7208        if (
 7209            optional_parens
 7210            and parser
 7211            and token_type not in self.INVALID_FUNC_NAME_TOKENS
 7212            and not after_dot
 7213        ):
 7214            self._advance()
 7215            return self._parse_window(parser(self))
 7216
 7217        if self._next.token_type != TokenType.L_PAREN:
 7218            if optional_parens and token_type in self.NO_PAREN_FUNCTIONS and not after_dot:
 7219                self._advance()
 7220                return self.expression(self.NO_PAREN_FUNCTIONS[token_type]())
 7221
 7222            return None
 7223
 7224        if any_token:
 7225            if token_type in self.RESERVED_TOKENS:
 7226                return None
 7227        elif token_type not in self.FUNC_TOKENS:
 7228            return None
 7229
 7230        self._advance(2)
 7231
 7232        parser = self.FUNCTION_PARSERS.get(upper)
 7233        if parser and not anonymous:
 7234            result = parser(self)
 7235        else:
 7236            subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type)
 7237
 7238            if subquery_predicate:
 7239                expr = None
 7240                if self._curr.token_type in self.SUBQUERY_TOKENS:
 7241                    expr = self._parse_select()
 7242                    self._match_r_paren()
 7243                elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE):
 7244                    # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like
 7245                    # to parse "LIKE [ANY | ALL] (...)" as a whole into an exp.Tuple or exp.Paren
 7246                    self._advance(-1)
 7247                    expr = self._parse_bitwise()
 7248
 7249                if expr:
 7250                    return self.expression(subquery_predicate(this=expr), comments=comments)
 7251
 7252            if functions is None:
 7253                functions = self.FUNCTIONS
 7254
 7255            function = functions.get(upper)
 7256            known_function = function and not anonymous
 7257
 7258            alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS
 7259            args = self._parse_function_args(alias)
 7260
 7261            post_func_comments = self._curr.comments if self._curr else None
 7262            if known_function and post_func_comments:
 7263                # If the user-inputted comment "/* sqlglot.anonymous */" is following the function
 7264                # call we'll construct it as exp.Anonymous, even if it's "known"
 7265                if any(
 7266                    comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS)
 7267                    for comment in post_func_comments
 7268                ):
 7269                    known_function = False
 7270
 7271            if alias and known_function:
 7272                args = self._kv_to_prop_eq(args)
 7273
 7274            if known_function:
 7275                func_builder = t.cast(t.Callable, function)
 7276
 7277                # mypyc compiled functions don't have __code__, so we use
 7278                # try/except to check if func_builder accepts 'dialect'.
 7279                try:
 7280                    func = func_builder(args)
 7281                except TypeError:
 7282                    func = func_builder(args, dialect=self.dialect)
 7283
 7284                func = self.validate_expression(func, args)
 7285                if self.dialect.PRESERVE_ORIGINAL_NAMES:
 7286                    func.meta["name"] = this
 7287
 7288                result = func
 7289            else:
 7290                if token_type == TokenType.IDENTIFIER:
 7291                    this = exp.Identifier(this=this, quoted=True).update_positions(token)
 7292
 7293                result = self.expression(exp.Anonymous(this=this, expressions=args))
 7294
 7295            result = result.update_positions(token)
 7296
 7297        if isinstance(result, exp.Expr):
 7298            result.add_comments(comments)
 7299
 7300        if parser:
 7301            self._match(TokenType.R_PAREN, expression=result)
 7302        else:
 7303            self._match_r_paren(result)
 7304        return self._parse_window(result)
 7305
 7306    def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr:
 7307        return expression
 7308
 7309    def _kv_to_prop_eq(
 7310        self, expressions: list[exp.Expr], parse_map: bool = False
 7311    ) -> list[exp.Expr]:
 7312        transformed = []
 7313
 7314        for index, e in enumerate(expressions):
 7315            if isinstance(e, self.KEY_VALUE_DEFINITIONS):
 7316                if isinstance(e, exp.Alias):
 7317                    e = self.expression(exp.PropertyEQ(this=e.args.get("alias"), expression=e.this))
 7318
 7319                if not isinstance(e, exp.PropertyEQ):
 7320                    e = self.expression(
 7321                        exp.PropertyEQ(
 7322                            this=e.this if parse_map else exp.to_identifier(e.this.name),
 7323                            expression=e.expression,
 7324                        )
 7325                    )
 7326
 7327                if isinstance(e.this, exp.Column):
 7328                    e.this.replace(e.this.this)
 7329            else:
 7330                e = self._to_prop_eq(e, index)
 7331
 7332            transformed.append(e)
 7333
 7334        return transformed
 7335
 7336    def _parse_function_properties(self) -> exp.Properties | None:
 7337        # Skip the generic `key = value` fallback in _parse_property since this
 7338        # runs post-AS where a function body like `name = expr` can be misread
 7339        # as a property.
 7340        properties = []
 7341        while True:
 7342            if self._match_texts(self.PROPERTY_PARSERS):
 7343                keyword = self._prev.text.upper()
 7344                prop = self.PROPERTY_PARSERS[keyword](self)
 7345            elif self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS):
 7346                keyword = self._prev.text.upper()
 7347                prop = self.PROPERTY_PARSERS[keyword](self, default=True)
 7348            else:
 7349                break
 7350            if not prop:
 7351                self.raise_error(f"Failed to parse property '{keyword}'")
 7352                break
 7353            for p in ensure_list(prop):
 7354                properties.append(p)
 7355
 7356        return self.expression(exp.Properties(expressions=properties)) if properties else None
 7357
 7358    def _parse_user_defined_function_expression(self) -> exp.Expr | None:
 7359        return self._parse_statement()
 7360
 7361    def _parse_function_parameter(self) -> exp.Expr | None:
 7362        return self._parse_column_def(this=self._parse_id_var(), computed_column=False)
 7363
 7364    def _parse_user_defined_function(self, kind: TokenType | None = None) -> exp.Expr | None:
 7365        this = self._parse_table_parts(schema=True)
 7366
 7367        if not self._match(TokenType.L_PAREN):
 7368            return this
 7369
 7370        expressions = self._parse_csv(self._parse_function_parameter)
 7371        self._match_r_paren()
 7372        return self.expression(
 7373            exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True)
 7374        )
 7375
 7376    def _parse_macro_overloads(
 7377        self,
 7378        this: exp.UserDefinedFunction,
 7379        first_body: exp.Expr,
 7380        first_is_table: bool = False,
 7381    ) -> exp.MacroOverloads:
 7382        overloads = [
 7383            self.expression(
 7384                exp.MacroOverload(
 7385                    this=first_body,
 7386                    expressions=this.expressions or None,
 7387                    is_table=first_is_table,
 7388                )
 7389            )
 7390        ]
 7391        this.set("expressions", None)
 7392        this.set("wrapped", False)
 7393
 7394        while self._match(TokenType.COMMA):
 7395            if not self._match(TokenType.L_PAREN):
 7396                break
 7397
 7398            params = self._parse_csv(self._parse_function_parameter)
 7399            self._match_r_paren()
 7400
 7401            if not self._match(TokenType.ALIAS):
 7402                break
 7403
 7404            is_table = self._match(TokenType.TABLE)
 7405            body = self._parse_expression()
 7406            macro = exp.MacroOverload(this=body, expressions=params, is_table=is_table)
 7407            overloads.append(self.expression(macro))
 7408
 7409        return self.expression(exp.MacroOverloads(expressions=overloads))
 7410
 7411    def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier:
 7412        literal = self._parse_primary()
 7413        if literal:
 7414            return self.expression(exp.Introducer(this=token.text, expression=literal), token)
 7415
 7416        return self._identifier_expression(token)
 7417
 7418    def _parse_session_parameter(self) -> exp.SessionParameter:
 7419        kind = None
 7420        this = self._parse_id_var() or self._parse_primary()
 7421
 7422        if this and self._match(TokenType.DOT):
 7423            kind = this.name
 7424            this = self._parse_var() or self._parse_primary()
 7425
 7426        return self.expression(exp.SessionParameter(this=this, kind=kind))
 7427
 7428    def _parse_lambda_arg(self) -> exp.Expr | None:
 7429        return self._parse_id_var()
 7430
 7431    def _parse_lambda(self, alias: bool = False) -> exp.Expr | None:
 7432        next_token_type = self._next.token_type
 7433
 7434        # Fast path: simple atom (column, literal, null, bool) followed by , or )
 7435        if (
 7436            next_token_type in self.LAMBDA_ARG_TERMINATORS
 7437            and (atom := self._parse_atom()) is not None
 7438        ):
 7439            return atom
 7440
 7441        index = self._index
 7442
 7443        if self._match(TokenType.L_PAREN):
 7444            expressions = t.cast(
 7445                list[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg)
 7446            )
 7447
 7448            if not self._match(TokenType.R_PAREN):
 7449                self._retreat(index)
 7450            elif self._match_set(self.LAMBDAS):
 7451                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7452            else:
 7453                self._retreat(index)
 7454        elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS:
 7455            expressions = [self._parse_lambda_arg()]
 7456
 7457            if self._match_set(self.LAMBDAS):
 7458                return self.LAMBDAS[self._prev.token_type](self, expressions)
 7459
 7460            self._retreat(index)
 7461
 7462        this: exp.Expr | None
 7463
 7464        if self._match(TokenType.DISTINCT):
 7465            this = self.expression(
 7466                exp.Distinct(expressions=self._parse_csv(self._parse_disjunction))
 7467            )
 7468        else:
 7469            self._match(TokenType.ALL)  # ALL is the default/no-op aggregate modifier (SQL-92)
 7470            this = self._parse_select_or_expression(alias=alias)
 7471
 7472        return self._parse_limit(
 7473            self._parse_respect_or_ignore_nulls(
 7474                self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this)))
 7475            )
 7476        )
 7477
 7478    def _parse_schema(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7479        index = self._index
 7480        if not self._match(TokenType.L_PAREN):
 7481            return this
 7482
 7483        # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>),
 7484        # expr can be of both types
 7485        if self._match_set(self.SELECT_START_TOKENS):
 7486            self._retreat(index)
 7487            return this
 7488        args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def())
 7489        self._match_r_paren()
 7490        return self.expression(exp.Schema(this=this, expressions=args))
 7491
 7492    def _parse_field_def(self) -> exp.Expr | None:
 7493        return self._parse_column_def(self._parse_field(any_token=True))
 7494
 7495    def _parse_column_def(
 7496        self, this: exp.Expr | None, computed_column: bool = True
 7497    ) -> exp.Expr | None:
 7498        # column defs are not really columns, they're identifiers
 7499        if isinstance(this, exp.Column):
 7500            this = this.this
 7501
 7502        if not computed_column:
 7503            self._match(TokenType.ALIAS)
 7504
 7505        kind = self._parse_types(schema=True)
 7506
 7507        if self._match_text_seq("FOR", "ORDINALITY"):
 7508            return self.expression(exp.ColumnDef(this=this, ordinality=True))
 7509
 7510        constraints: list[exp.Expr] = []
 7511
 7512        if (not kind and self._match(TokenType.ALIAS)) or self._match_texts(
 7513            ("ALIAS", "MATERIALIZED")
 7514        ):
 7515            # Match storage before _parse_types so STORED is not treated as a data type
 7516            # (needed for typeless columns, e.g. SQLite `b AS (a * 2) STORED`).
 7517            persisted = self._prev.text.upper() == "MATERIALIZED"
 7518            expression = self._parse_disjunction()
 7519            if not persisted:
 7520                if self._match_text_seq("PERSISTED"):
 7521                    persisted = True
 7522                elif self._match_texts(("STORED", "VIRTUAL")):
 7523                    persisted = self._prev.text.upper() == "STORED"
 7524            constraint_kind = exp.ComputedColumnConstraint(
 7525                this=expression,
 7526                persisted=persisted,
 7527                data_type=exp.Var(this="AUTO")
 7528                if self._match_text_seq("AUTO")
 7529                else self._parse_types(),
 7530                not_null=self._match_pair(TokenType.NOT, TokenType.NULL),
 7531            )
 7532            constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind)))
 7533        elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False):
 7534            in_out_constraint = self.expression(
 7535                exp.InOutColumnConstraint(
 7536                    input_=self._match(TokenType.IN), output=self._match(TokenType.OUT)
 7537                )
 7538            )
 7539            constraints.append(in_out_constraint)
 7540            kind = self._parse_types()
 7541        elif (
 7542            kind
 7543            and self._match(TokenType.ALIAS, advance=False)
 7544            and (
 7545                not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT
 7546                or self._next.token_type == TokenType.L_PAREN
 7547            )
 7548        ):
 7549            self._advance()
 7550            constraints.append(
 7551                self.expression(
 7552                    exp.ColumnConstraint(
 7553                        kind=exp.ComputedColumnConstraint(
 7554                            this=self._parse_disjunction(),
 7555                            persisted=self._match_texts(("STORED", "VIRTUAL"))
 7556                            and self._prev.text.upper() == "STORED",
 7557                        )
 7558                    )
 7559                )
 7560            )
 7561
 7562        while True:
 7563            constraint = self._parse_column_constraint()
 7564            if not constraint:
 7565                break
 7566            constraints.append(constraint)
 7567
 7568        if not kind and not constraints:
 7569            return this
 7570
 7571        position = None
 7572        if self._match_texts(("FIRST", "AFTER")):
 7573            pos = self._prev.text
 7574            position = self.expression(exp.ColumnPosition(this=self._parse_column(), position=pos))
 7575
 7576        return self.expression(
 7577            exp.ColumnDef(this=this, kind=kind, constraints=constraints, position=position)
 7578        )
 7579
 7580    def _parse_auto_increment(
 7581        self,
 7582    ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint:
 7583        start = None
 7584        increment = None
 7585        order = None
 7586
 7587        if self._match(TokenType.L_PAREN, advance=False):
 7588            args = self._parse_wrapped_csv(self._parse_bitwise)
 7589            start = seq_get(args, 0)
 7590            increment = seq_get(args, 1)
 7591
 7592        # The remaining parts form an unordered bag and any of them can be omitted, in which
 7593        # case the engine falls back to its own default, so they're parsed independently.
 7594        while True:
 7595            if self._match_text_seq("START"):
 7596                start = self._parse_bitwise()
 7597            elif self._match_text_seq("INCREMENT"):
 7598                increment = self._parse_bitwise()
 7599            elif self._match_text_seq("ORDER"):
 7600                order = True
 7601            elif self._match_text_seq("NOORDER"):
 7602                order = False
 7603            else:
 7604                break
 7605
 7606        if start or increment or order is not None:
 7607            return exp.GeneratedAsIdentityColumnConstraint(
 7608                start=start, increment=increment, this=False, order=order
 7609            )
 7610
 7611        return exp.AutoIncrementColumnConstraint()
 7612
 7613    def _parse_check_constraint(self) -> exp.CheckColumnConstraint | None:
 7614        if not self._match(TokenType.L_PAREN, advance=False):
 7615            return None
 7616
 7617        return self.expression(
 7618            exp.CheckColumnConstraint(
 7619                this=self._parse_wrapped(self._parse_assignment),
 7620                enforced=self._match_text_seq("ENFORCED"),
 7621            )
 7622        )
 7623
 7624    def _parse_auto_property(self) -> exp.AutoRefreshProperty | None:
 7625        if not self._match_text_seq("REFRESH"):
 7626            self._retreat(self._index - 1)
 7627            return None
 7628        return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True)))
 7629
 7630    def _parse_compress(self) -> exp.CompressColumnConstraint:
 7631        if self._match(TokenType.L_PAREN, advance=False):
 7632            return self.expression(
 7633                exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise))
 7634            )
 7635
 7636        return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise()))
 7637
 7638    def _parse_generated_as_identity(
 7639        self,
 7640    ) -> (
 7641        exp.GeneratedAsIdentityColumnConstraint
 7642        | exp.ComputedColumnConstraint
 7643        | exp.GeneratedAsRowColumnConstraint
 7644    ):
 7645        if self._match_text_seq("BY", "DEFAULT"):
 7646            on_null = self._match_pair(TokenType.ON, TokenType.NULL)
 7647            this = self.expression(
 7648                exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null)
 7649            )
 7650        else:
 7651            self._match_text_seq("ALWAYS")
 7652            this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True))
 7653
 7654        self._match(TokenType.ALIAS)
 7655
 7656        if self._match_text_seq("ROW"):
 7657            start = self._match_text_seq("START")
 7658            if not start:
 7659                self._match(TokenType.END)
 7660            hidden = self._match_text_seq("HIDDEN")
 7661            return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden))
 7662
 7663        identity = self._match_text_seq("IDENTITY")
 7664
 7665        if self._match(TokenType.L_PAREN):
 7666            if self._match_text_seq("START", "WITH"):
 7667                this.set("start", self._parse_bitwise())
 7668            if self._match_text_seq("INCREMENT", "BY"):
 7669                this.set("increment", self._parse_bitwise())
 7670            if self._match_text_seq("MINVALUE"):
 7671                this.set("minvalue", self._parse_bitwise())
 7672            if self._match_text_seq("MAXVALUE"):
 7673                this.set("maxvalue", self._parse_bitwise())
 7674
 7675            if self._match_text_seq("CYCLE"):
 7676                this.set("cycle", True)
 7677            elif self._match_text_seq("NO", "CYCLE"):
 7678                this.set("cycle", False)
 7679
 7680            if not identity:
 7681                this.set("expression", self._parse_range())
 7682            elif not this.args.get("start") and self._match(TokenType.NUMBER, advance=False):
 7683                args = self._parse_csv(self._parse_bitwise)
 7684                this.set("start", seq_get(args, 0))
 7685                this.set("increment", seq_get(args, 1))
 7686
 7687            self._match_r_paren()
 7688
 7689        return this
 7690
 7691    def _parse_inline(self) -> exp.InlineLengthColumnConstraint:
 7692        self._match_text_seq("LENGTH")
 7693        return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise()))
 7694
 7695    def _parse_not_constraint(self) -> exp.Expr | None:
 7696        if self._match_text_seq("NULL"):
 7697            return self.expression(exp.NotNullColumnConstraint())
 7698        if self._match_text_seq("CASESPECIFIC"):
 7699            return self.expression(exp.CaseSpecificColumnConstraint(not_=True))
 7700        if self._match_text_seq("FOR", "REPLICATION"):
 7701            return self.expression(exp.NotForReplicationColumnConstraint())
 7702
 7703        # Unconsume the `NOT` token
 7704        self._retreat(self._index - 1)
 7705        return None
 7706
 7707    def _parse_column_constraint(self) -> exp.Expr | None:
 7708        this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None
 7709
 7710        procedure_option_follows = (
 7711            self._match(TokenType.WITH, advance=False)
 7712            and self._next
 7713            and self._next.text.upper() in self.PROCEDURE_OPTIONS
 7714        )
 7715
 7716        if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS):
 7717            constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self)
 7718            if not constraint:
 7719                self._retreat(self._index - 1)
 7720                return None
 7721
 7722            return self.expression(exp.ColumnConstraint(this=this, kind=constraint))
 7723
 7724        if self._match_text_seq("CHARACTER", "SET"):
 7725            return self.expression(
 7726                exp.ColumnConstraint(
 7727                    this=this,
 7728                    kind=self.expression(
 7729                        exp.CharacterSetColumnConstraint(this=self._parse_var_or_string())
 7730                    ),
 7731                )
 7732            )
 7733
 7734        return this
 7735
 7736    def _parse_constraint(self) -> exp.Expr | None:
 7737        if not self._match(TokenType.CONSTRAINT):
 7738            return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS)
 7739
 7740        return self.expression(
 7741            exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints())
 7742        )
 7743
 7744    def _parse_unnamed_constraints(self) -> list[exp.Expr]:
 7745        constraints = []
 7746        while True:
 7747            constraint = self._parse_unnamed_constraint() or self._parse_function()
 7748            if not constraint:
 7749                break
 7750            constraints.append(constraint)
 7751
 7752        return constraints
 7753
 7754    def _parse_unnamed_constraint(self, constraints: TEXTS_TYPE | None = None) -> exp.Expr | None:
 7755        index = self._index
 7756
 7757        if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts(
 7758            constraints or self.CONSTRAINT_PARSERS
 7759        ):
 7760            return None
 7761
 7762        constraint_key = self._prev.text.upper()
 7763        if constraint_key not in self.CONSTRAINT_PARSERS:
 7764            self.raise_error(f"No parser found for schema constraint {constraint_key}.")
 7765
 7766        result = self.CONSTRAINT_PARSERS[constraint_key](self)
 7767        if not result:
 7768            self._retreat(index)
 7769
 7770        return result
 7771
 7772    def _parse_unique_key(self) -> exp.Expr | None:
 7773        if (
 7774            self._curr
 7775            and self._curr.token_type != TokenType.IDENTIFIER
 7776            and self._curr.text.upper() in self.CONSTRAINT_PARSERS
 7777        ):
 7778            return None
 7779        return self._parse_id_var(any_token=False)
 7780
 7781    def _parse_unique(self) -> exp.UniqueColumnConstraint:
 7782        self._match_texts(("KEY", "INDEX"))
 7783        return self.expression(
 7784            exp.UniqueColumnConstraint(
 7785                nulls=self._match_text_seq("NULLS", "NOT", "DISTINCT"),
 7786                this=self._parse_schema(self._parse_unique_key()),
 7787                index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text,
 7788                on_conflict=self._parse_on_conflict(),
 7789                options=self._parse_key_constraint_options(),
 7790            )
 7791        )
 7792
 7793    def _parse_key_constraint_options(self) -> list[str]:
 7794        options = []
 7795        while True:
 7796            if not self._curr:
 7797                break
 7798
 7799            if self._match(TokenType.ON):
 7800                action = None
 7801                on = self._advance_any() and self._prev.text
 7802
 7803                if self._match_text_seq("NO", "ACTION"):
 7804                    action = "NO ACTION"
 7805                elif self._match_text_seq("CASCADE"):
 7806                    action = "CASCADE"
 7807                elif self._match_text_seq("RESTRICT"):
 7808                    action = "RESTRICT"
 7809                elif self._match_pair(TokenType.SET, TokenType.NULL):
 7810                    action = "SET NULL"
 7811                elif self._match_pair(TokenType.SET, TokenType.DEFAULT):
 7812                    action = "SET DEFAULT"
 7813                else:
 7814                    self.raise_error("Invalid key constraint")
 7815
 7816                options.append(f"ON {on} {action}")
 7817            else:
 7818                var = self._parse_var_from_options(
 7819                    self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False
 7820                )
 7821                if not var:
 7822                    break
 7823                options.append(var.name)
 7824
 7825        return options
 7826
 7827    def _parse_references(self, match: bool = True) -> exp.Reference | None:
 7828        if match and not self._match(TokenType.REFERENCES):
 7829            return None
 7830
 7831        expressions: list | None = None
 7832        this = self._parse_table(schema=True)
 7833        options = self._parse_key_constraint_options()
 7834        return self.expression(exp.Reference(this=this, expressions=expressions, options=options))
 7835
 7836    def _parse_foreign_key(self) -> exp.ForeignKey:
 7837        expressions = (
 7838            self._parse_wrapped_id_vars()
 7839            if not self._match(TokenType.REFERENCES, advance=False)
 7840            else None
 7841        )
 7842        reference = self._parse_references()
 7843        on_options = {}
 7844
 7845        while self._match(TokenType.ON):
 7846            if not self._match_set((TokenType.DELETE, TokenType.UPDATE)):
 7847                self.raise_error("Expected DELETE or UPDATE")
 7848
 7849            kind = self._prev.text.lower()
 7850
 7851            if self._match_text_seq("NO", "ACTION"):
 7852                action = "NO ACTION"
 7853            elif self._match(TokenType.SET):
 7854                self._match_set((TokenType.NULL, TokenType.DEFAULT))
 7855                action = "SET " + self._prev.text.upper()
 7856            else:
 7857                self._advance()
 7858                action = self._prev.text.upper()
 7859
 7860            on_options[kind] = action
 7861
 7862        return self.expression(
 7863            exp.ForeignKey(
 7864                expressions=expressions,
 7865                reference=reference,
 7866                options=self._parse_key_constraint_options(),
 7867                **on_options,
 7868            )
 7869        )
 7870
 7871    def _parse_primary_key_part(self) -> exp.Expr | None:
 7872        return self._parse_field()
 7873
 7874    def _parse_period_for_system_time(self) -> exp.PeriodForSystemTimeConstraint | None:
 7875        if not self._match_text_seq("FOR", "SYSTEM_TIME"):
 7876            self._retreat(self._index - 1)
 7877            return None
 7878
 7879        id_vars = self._parse_wrapped_id_vars()
 7880        return self.expression(
 7881            exp.PeriodForSystemTimeConstraint(
 7882                this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1)
 7883            )
 7884        )
 7885
 7886    def _parse_primary_key(
 7887        self,
 7888        wrapped_optional: bool = False,
 7889        in_props: bool = False,
 7890        named_primary_key: bool = False,
 7891    ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:
 7892        desc = (
 7893            self._prev.token_type == TokenType.DESC
 7894            if self._match_set((TokenType.ASC, TokenType.DESC))
 7895            else None
 7896        )
 7897
 7898        this = None
 7899        if (
 7900            named_primary_key
 7901            and self._curr.text.upper() not in self.CONSTRAINT_PARSERS
 7902            and self._next
 7903            and self._next.token_type == TokenType.L_PAREN
 7904        ):
 7905            this = self._parse_id_var()
 7906
 7907        if not in_props and not self._match(TokenType.L_PAREN, advance=False):
 7908            return self.expression(
 7909                exp.PrimaryKeyColumnConstraint(
 7910                    desc=desc, options=self._parse_key_constraint_options()
 7911                )
 7912            )
 7913
 7914        expressions = self._parse_wrapped_csv(
 7915            self._parse_primary_key_part, optional=wrapped_optional
 7916        )
 7917
 7918        return self.expression(
 7919            exp.PrimaryKey(
 7920                this=this,
 7921                expressions=expressions,
 7922                include=self._parse_index_params(),
 7923                options=self._parse_key_constraint_options(),
 7924            )
 7925        )
 7926
 7927    def _parse_bracket_key_value(self, is_map: bool = False) -> exp.Expr | None:
 7928        return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True))
 7929
 7930    def _parse_odbc_datetime_literal(self) -> exp.Expr:
 7931        """
 7932        Parses a datetime column in ODBC format. We parse the column into the corresponding
 7933        types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the
 7934        same as we did for `DATE('yyyy-mm-dd')`.
 7935
 7936        Reference:
 7937        https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals
 7938        """
 7939        self._match(TokenType.VAR)
 7940        exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()]
 7941        expression = self.expression(exp_class(this=self._parse_string()))
 7942        if not self._match(TokenType.R_BRACE):
 7943            self.raise_error("Expected }")
 7944        return expression
 7945
 7946    def _parse_bracket(self, this: exp.Expr | None = None) -> exp.Expr | None:
 7947        if not self._match_set(self.BRACKETS):
 7948            return this
 7949
 7950        if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS:
 7951            map_token = seq_get(self._tokens, self._index - 2)
 7952            parse_map = map_token is not None and map_token.text.upper() == "MAP"
 7953        else:
 7954            parse_map = False
 7955
 7956        bracket_kind = self._prev.token_type
 7957        if (
 7958            bracket_kind == TokenType.L_BRACE
 7959            and self._curr
 7960            and self._curr.token_type == TokenType.VAR
 7961            and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS
 7962        ):
 7963            return self._parse_odbc_datetime_literal()
 7964
 7965        expressions = self._parse_csv(
 7966            lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE)
 7967        )
 7968
 7969        if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET):
 7970            self.raise_error("Expected ]")
 7971        elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE):
 7972            self.raise_error("Expected }")
 7973
 7974        # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs
 7975        if bracket_kind == TokenType.L_BRACE:
 7976            this = self.expression(
 7977                exp.Struct(
 7978                    expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map)
 7979                )
 7980            )
 7981        elif not this:
 7982            this = build_array_constructor(
 7983                exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect
 7984            )
 7985        else:
 7986            constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper())
 7987            if constructor_type:
 7988                return build_array_constructor(
 7989                    constructor_type,
 7990                    args=expressions,
 7991                    bracket_kind=bracket_kind,
 7992                    dialect=self.dialect,
 7993                )
 7994
 7995            expressions = apply_index_offset(
 7996                this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect
 7997            )
 7998            this = self.expression(
 7999                exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments()
 8000            )
 8001
 8002        self._add_comments(this)
 8003        return self._parse_bracket(this)
 8004
 8005    def _parse_slice(self, this: exp.Expr | None) -> exp.Expr | None:
 8006        if not self._match(TokenType.COLON):
 8007            return this
 8008
 8009        if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False):
 8010            self._advance()
 8011            end: exp.Expr | None = -exp.Literal.number("1")
 8012        else:
 8013            end = self._parse_assignment()
 8014        step = self._parse_unary() if self._match(TokenType.COLON) else None
 8015        return self.expression(exp.Slice(this=this, expression=end, step=step))
 8016
 8017    def _parse_case(self) -> exp.Expr | None:
 8018        if self._match(TokenType.DOT, advance=False):
 8019            # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake
 8020            self._retreat(self._index - 1)
 8021            return None
 8022
 8023        ifs = []
 8024        default = None
 8025
 8026        comments = self._prev_comments
 8027        expression = self._parse_disjunction()
 8028
 8029        while self._match(TokenType.WHEN):
 8030            this = self._parse_disjunction()
 8031            self._match(TokenType.THEN)
 8032            then = self._parse_disjunction()
 8033            ifs.append(self.expression(exp.If(this=this, true=then)))
 8034
 8035        if self._match(TokenType.ELSE):
 8036            default = self._parse_disjunction()
 8037
 8038        if not self._match(TokenType.END):
 8039            if isinstance(default, exp.Interval) and default.this.sql().upper() == "END":
 8040                default = exp.column("interval")
 8041            else:
 8042                self.raise_error("Expected END after CASE", self._prev)
 8043
 8044        return self.expression(
 8045            exp.Case(this=expression, ifs=ifs, default=default), comments=comments
 8046        )
 8047
 8048    def _parse_if(self) -> exp.Expr | None:
 8049        if self._match(TokenType.L_PAREN):
 8050            args = self._parse_csv(
 8051                lambda: self._parse_alias(self._parse_assignment(), explicit=True)
 8052            )
 8053            this = self.validate_expression(exp.If.from_arg_list(args), args)
 8054            self._match_r_paren()
 8055        else:
 8056            index = self._index - 1
 8057
 8058            if self.NO_PAREN_IF_COMMANDS and index == 0:
 8059                return self._parse_as_command(self._prev)
 8060
 8061            condition = self._parse_disjunction()
 8062
 8063            if not condition:
 8064                self._retreat(index)
 8065                return None
 8066
 8067            self._match(TokenType.THEN)
 8068            true = self._parse_disjunction()
 8069            false = self._parse_disjunction() if self._match(TokenType.ELSE) else None
 8070            self._match(TokenType.END)
 8071            this = self.expression(exp.If(this=condition, true=true, false=false))
 8072
 8073        return this
 8074
 8075    def _parse_next_value_for(self) -> exp.Expr | None:
 8076        if not self._match_text_seq("VALUE", "FOR"):
 8077            self._retreat(self._index - 1)
 8078            return None
 8079
 8080        return self.expression(
 8081            exp.NextValueFor(
 8082                this=self._parse_column(),
 8083                order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order),
 8084            )
 8085        )
 8086
 8087    def _parse_extract(self) -> exp.Extract | exp.Anonymous:
 8088        this = self._parse_function() or self._parse_var_or_string(upper=True)
 8089
 8090        if self._match(TokenType.FROM):
 8091            return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8092
 8093        if not self._match(TokenType.COMMA):
 8094            self.raise_error("Expected FROM or comma after EXTRACT", self._prev)
 8095
 8096        return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))
 8097
 8098    def _parse_gap_fill(self) -> exp.GapFill:
 8099        self._match(TokenType.TABLE)
 8100        this = self._parse_table()
 8101
 8102        self._match(TokenType.COMMA)
 8103        args = [this, *self._parse_csv(self._parse_lambda)]
 8104
 8105        gap_fill = exp.GapFill.from_arg_list(args)
 8106        return self.validate_expression(gap_fill, args)
 8107
 8108    def _parse_char(self) -> exp.Chr:
 8109        return self.expression(
 8110            exp.Chr(
 8111                expressions=self._parse_csv(self._parse_assignment),
 8112                charset=self._match(TokenType.USING) and self._parse_charset_name(),
 8113            )
 8114        )
 8115
 8116    def _parse_charset_name(self) -> exp.Expr | None:
 8117        """
 8118        Parse a charset name after USING or CHARACTER SET. Dialects that need to preserve quoting
 8119        for specific name shapes override this.
 8120        """
 8121        return self._parse_var(
 8122            tokens={TokenType.BINARY, TokenType.IDENTIFIER},
 8123        )
 8124
 8125    def _parse_cast(self, strict: bool, safe: bool | None = None) -> exp.Expr:
 8126        this = self._parse_assignment()
 8127
 8128        if not self._match(TokenType.ALIAS):
 8129            if self._match(TokenType.COMMA):
 8130                return self.expression(exp.CastToStrType(this=this, to=self._parse_string()))
 8131
 8132            self.raise_error("Expected AS after CAST")
 8133
 8134        fmt = None
 8135        to = self._parse_types(with_collation=True)
 8136
 8137        default = None
 8138        if self._match(TokenType.DEFAULT):
 8139            default = self._parse_bitwise()
 8140            self._match_text_seq("ON", "CONVERSION", "ERROR")
 8141
 8142        if self._match_set((TokenType.FORMAT, TokenType.COMMA)):
 8143            fmt_string = self._parse_wrapped(self._parse_string, optional=True)
 8144            fmt = self._parse_at_time_zone(fmt_string)
 8145
 8146            if not to:
 8147                to = exp.DType.UNKNOWN.into_expr()
 8148            if to.this in exp.DataType.TEMPORAL_TYPES:
 8149                this = self.expression(
 8150                    (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)(
 8151                        this=this,
 8152                        format=exp.Literal.string(
 8153                            format_time(
 8154                                fmt_string.this if fmt_string else "",
 8155                                self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING,
 8156                                self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE,
 8157                            )
 8158                        ),
 8159                        safe=safe,
 8160                    )
 8161                )
 8162
 8163                if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime):
 8164                    this.set("zone", fmt.args["zone"])
 8165                return this
 8166        elif not to:
 8167            self.raise_error("Expected TYPE after CAST")
 8168        elif isinstance(to, exp.Identifier):
 8169            to = exp.DataType.from_str(to.name, dialect=self.dialect, udt=True)
 8170        elif to.this == exp.DType.CHAR and (
 8171            self._match(TokenType.CHARACTER_SET) or self._match_text_seq("CHARACTER", "SET")
 8172        ):
 8173            to = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_var_or_string())
 8174
 8175        return self.build_cast(
 8176            strict=strict,
 8177            this=this,
 8178            to=to,
 8179            format=fmt,
 8180            safe=safe,
 8181            action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False),
 8182            default=default,
 8183        )
 8184
 8185    def _parse_string_agg(self) -> exp.GroupConcat:
 8186        if self._match(TokenType.DISTINCT):
 8187            args: list[exp.Expr | None] = [
 8188                self.expression(exp.Distinct(expressions=[self._parse_disjunction()]))
 8189            ]
 8190            if self._match(TokenType.COMMA):
 8191                args.extend(self._parse_csv(self._parse_disjunction))
 8192        else:
 8193            args = self._parse_csv(self._parse_disjunction)  # type: ignore
 8194
 8195        if self._match_text_seq("ON", "OVERFLOW"):
 8196            # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior])
 8197            if self._match_text_seq("ERROR"):
 8198                on_overflow: exp.Expr | None = exp.var("ERROR")
 8199            else:
 8200                self._match_text_seq("TRUNCATE")
 8201                on_overflow = self.expression(
 8202                    exp.OverflowTruncateBehavior(
 8203                        this=self._parse_string(),
 8204                        with_count=(
 8205                            self._match_text_seq("WITH", "COUNT")
 8206                            or not self._match_text_seq("WITHOUT", "COUNT")
 8207                        ),
 8208                    )
 8209                )
 8210        else:
 8211            on_overflow = None
 8212
 8213        index = self._index
 8214        if not self._match(TokenType.R_PAREN) and args:
 8215            # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]])
 8216            # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n])
 8217            # The order is parsed through `this` as a canonicalization for WITHIN GROUPs
 8218            args[0] = self._parse_limit(this=self._parse_order(this=args[0]))
 8219            return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1)))
 8220
 8221        # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]).
 8222        # This is done "manually", instead of letting _parse_window parse it into an exp.WithinGroup node, so that
 8223        # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them.
 8224        if not self._match_text_seq("WITHIN", "GROUP"):
 8225            self._retreat(index)
 8226            return self.validate_expression(exp.GroupConcat.from_arg_list(args), args)
 8227
 8228        # The corresponding match_r_paren will be called in parse_function (caller)
 8229        self._match_l_paren()
 8230
 8231        return self.expression(
 8232            exp.GroupConcat(
 8233                this=self._parse_order(this=seq_get(args, 0)),
 8234                separator=seq_get(args, 1),
 8235                on_overflow=on_overflow,
 8236            )
 8237        )
 8238
 8239    def _parse_convert(self, strict: bool, safe: bool | None = None) -> exp.Expr | None:
 8240        this = self._parse_bitwise()
 8241
 8242        if self._match(TokenType.USING):
 8243            to: exp.Expr | None = exp.DType.CHARACTER_SET.into_expr(kind=self._parse_charset_name())
 8244        elif self._match(TokenType.COMMA):
 8245            to = self._parse_types()
 8246        else:
 8247            to = None
 8248
 8249        return self.build_cast(strict=strict, this=this, to=to, safe=safe)
 8250
 8251    def _parse_xml_element(self) -> exp.XMLElement:
 8252        if self._match_text_seq("EVALNAME"):
 8253            evalname = True
 8254            this = self._parse_bitwise()
 8255        else:
 8256            evalname = None
 8257            self._match_text_seq("NAME")
 8258            this = self._parse_id_var()
 8259
 8260        return self.expression(
 8261            exp.XMLElement(
 8262                this=this,
 8263                expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise),
 8264                evalname=evalname,
 8265            )
 8266        )
 8267
 8268    def _parse_xml_table(self) -> exp.XMLTable:
 8269        namespaces = None
 8270        passing = None
 8271        columns = None
 8272
 8273        if self._match_text_seq("XMLNAMESPACES", "("):
 8274            namespaces = self._parse_xml_namespace()
 8275            self._match_text_seq(")", ",")
 8276
 8277        this = self._parse_string()
 8278
 8279        if self._match_text_seq("PASSING"):
 8280            # The BY VALUE keywords are optional and are provided for semantic clarity
 8281            self._match_text_seq("BY", "VALUE")
 8282            passing = self._parse_csv(self._parse_column)
 8283
 8284        by_ref = self._match_text_seq("RETURNING", "SEQUENCE", "BY", "REF")
 8285
 8286        if self._match_text_seq("COLUMNS"):
 8287            columns = self._parse_csv(self._parse_field_def)
 8288
 8289        return self.expression(
 8290            exp.XMLTable(
 8291                this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref
 8292            )
 8293        )
 8294
 8295    def _parse_xml_namespace(self) -> list[exp.XMLNamespace]:
 8296        namespaces = []
 8297
 8298        while True:
 8299            if self._match(TokenType.DEFAULT):
 8300                uri = self._parse_string()
 8301            else:
 8302                uri = self._parse_alias(self._parse_string())
 8303            namespaces.append(self.expression(exp.XMLNamespace(this=uri)))
 8304            if not self._match(TokenType.COMMA):
 8305                break
 8306
 8307        return namespaces
 8308
 8309    def _parse_decode(self) -> exp.Decode | exp.DecodeCase | None:
 8310        args = self._parse_csv(self._parse_disjunction)
 8311
 8312        if len(args) < 3:
 8313            return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1)))
 8314
 8315        return self.expression(exp.DecodeCase(expressions=args))
 8316
 8317    def _parse_json_key_value(self) -> exp.JSONKeyValue | None:
 8318        self._match_text_seq("KEY")
 8319        key = self._parse_column()
 8320        self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS)
 8321        self._match_text_seq("VALUE")
 8322        value = self._parse_bitwise()
 8323
 8324        if not key and not value:
 8325            return None
 8326        return self.expression(exp.JSONKeyValue(this=key, expression=value))
 8327
 8328    def _parse_format_json(self, this: exp.Expr | None) -> exp.Expr | None:
 8329        if not this or not self._match_text_seq("FORMAT", "JSON"):
 8330            return this
 8331
 8332        return self.expression(exp.FormatJson(this=this))
 8333
 8334    def _parse_on_condition(self) -> exp.OnCondition | None:
 8335        # MySQL uses "X ON EMPTY Y ON ERROR" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS)
 8336        if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR:
 8337            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8338            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8339        else:
 8340            error = self._parse_on_handling("ERROR", *self.ON_CONDITION_TOKENS)
 8341            empty = self._parse_on_handling("EMPTY", *self.ON_CONDITION_TOKENS)
 8342
 8343        null = self._parse_on_handling("NULL", *self.ON_CONDITION_TOKENS)
 8344
 8345        if not empty and not error and not null:
 8346            return None
 8347
 8348        return self.expression(exp.OnCondition(empty=empty, error=error, null=null))
 8349
 8350    def _parse_on_handling(self, on: str, *values: str) -> str | None | exp.Expr | None:
 8351        # Parses the "X ON Y" or "DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL)
 8352        for value in values:
 8353            if self._match_text_seq(value, "ON", on):
 8354                return f"{value} ON {on}"
 8355
 8356        index = self._index
 8357        if self._match(TokenType.DEFAULT):
 8358            default_value = self._parse_bitwise()
 8359            if self._match_text_seq("ON", on):
 8360                return default_value
 8361
 8362            self._retreat(index)
 8363
 8364        return None
 8365
 8366    @t.overload
 8367    def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ...
 8368
 8369    @t.overload
 8370    def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ...
 8371
 8372    def _parse_json_object(self, agg=False):
 8373        star = self._parse_star()
 8374        expressions = (
 8375            [star]
 8376            if star
 8377            else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value()))
 8378        )
 8379        null_handling = self._parse_on_handling("NULL", "NULL", "ABSENT")
 8380
 8381        unique_keys = None
 8382        if self._match_text_seq("WITH", "UNIQUE"):
 8383            unique_keys = True
 8384        elif self._match_text_seq("WITHOUT", "UNIQUE"):
 8385            unique_keys = False
 8386
 8387        self._match_text_seq("KEYS")
 8388
 8389        return_type = self._match_text_seq("RETURNING") and self._parse_format_json(
 8390            self._parse_type()
 8391        )
 8392        encoding = self._match_text_seq("ENCODING") and self._parse_var()
 8393
 8394        return self.expression(
 8395            (exp.JSONObjectAgg if agg else exp.JSONObject)(
 8396                expressions=expressions,
 8397                null_handling=null_handling,
 8398                unique_keys=unique_keys,
 8399                return_type=return_type,
 8400                encoding=encoding,
 8401            )
 8402        )
 8403
 8404    # Note: this is currently incomplete; it only implements the "JSON_value_column" part
 8405    def _parse_json_column_def(self) -> exp.JSONColumnDef:
 8406        if not self._match_text_seq("NESTED"):
 8407            this = self._parse_id_var()
 8408            ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY)
 8409            kind = self._parse_types(allow_identifiers=False)
 8410            nested = None
 8411        else:
 8412            this = None
 8413            ordinality = None
 8414            kind = None
 8415            nested = True
 8416
 8417        format_json = self._match_text_seq("FORMAT", "JSON")
 8418        path = self._match_text_seq("PATH") and self._parse_string()
 8419        nested_schema = nested and self._parse_json_schema()
 8420
 8421        return self.expression(
 8422            exp.JSONColumnDef(
 8423                this=this,
 8424                kind=kind,
 8425                path=path,
 8426                nested_schema=nested_schema,
 8427                ordinality=ordinality,
 8428                format_json=format_json,
 8429            )
 8430        )
 8431
 8432    def _parse_json_schema(self) -> exp.JSONSchema:
 8433        self._match_text_seq("COLUMNS")
 8434        return self.expression(
 8435            exp.JSONSchema(
 8436                expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True)
 8437            )
 8438        )
 8439
 8440    def _parse_json_table(self) -> exp.JSONTable:
 8441        this = self._parse_format_json(self._parse_bitwise())
 8442        path = self._match(TokenType.COMMA) and self._parse_string()
 8443        error_handling = self._parse_on_handling("ERROR", "ERROR", "NULL")
 8444        empty_handling = self._parse_on_handling("EMPTY", "ERROR", "NULL")
 8445        schema = self._parse_json_schema()
 8446
 8447        return exp.JSONTable(
 8448            this=this,
 8449            schema=schema,
 8450            path=path,
 8451            error_handling=error_handling,
 8452            empty_handling=empty_handling,
 8453        )
 8454
 8455    def _parse_match_against(self) -> exp.MatchAgainst:
 8456        if self._match_text_seq("TABLE"):
 8457            # parse SingleStore MATCH(TABLE ...) syntax
 8458            # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/
 8459            expressions = []
 8460            table = self._parse_table()
 8461            if table:
 8462                expressions = [table]
 8463        else:
 8464            expressions = self._parse_csv(self._parse_column)
 8465
 8466        self._match_text_seq(")", "AGAINST", "(")
 8467
 8468        this = self._parse_string()
 8469
 8470        if self._match_text_seq("IN", "NATURAL", "LANGUAGE", "MODE"):
 8471            modifier = "IN NATURAL LANGUAGE MODE"
 8472            if self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8473                modifier = f"{modifier} WITH QUERY EXPANSION"
 8474        elif self._match_text_seq("IN", "BOOLEAN", "MODE"):
 8475            modifier = "IN BOOLEAN MODE"
 8476        elif self._match_text_seq("WITH", "QUERY", "EXPANSION"):
 8477            modifier = "WITH QUERY EXPANSION"
 8478        else:
 8479            modifier = None
 8480
 8481        return self.expression(
 8482            exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier)
 8483        )
 8484
 8485    # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16
 8486    def _parse_open_json(self) -> exp.OpenJSON:
 8487        this = self._parse_bitwise()
 8488        path = self._match(TokenType.COMMA) and self._parse_string()
 8489
 8490        def _parse_open_json_column_def() -> exp.OpenJSONColumnDef:
 8491            this = self._parse_field(any_token=True)
 8492            kind = self._parse_types()
 8493            path = self._parse_string()
 8494            as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON)
 8495
 8496            return self.expression(
 8497                exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json)
 8498            )
 8499
 8500        expressions = None
 8501        if self._match_pair(TokenType.R_PAREN, TokenType.WITH):
 8502            self._match_l_paren()
 8503            expressions = self._parse_csv(_parse_open_json_column_def)
 8504
 8505        return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions))
 8506
 8507    def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition:
 8508        args = self._parse_csv(self._parse_bitwise)
 8509
 8510        if self._match(TokenType.IN):
 8511            return self.expression(
 8512                exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0))
 8513            )
 8514
 8515        if haystack_first:
 8516            haystack = seq_get(args, 0)
 8517            needle = seq_get(args, 1)
 8518        else:
 8519            haystack = seq_get(args, 1)
 8520            needle = seq_get(args, 0)
 8521
 8522        return self.expression(
 8523            exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2))
 8524        )
 8525
 8526    def _parse_join_hint(self, func_name: str) -> exp.JoinHint:
 8527        args = self._parse_csv(self._parse_table)
 8528        return exp.JoinHint(this=func_name.upper(), expressions=args)
 8529
 8530    def _parse_substring(self) -> exp.Substring:
 8531        # Postgres supports the form: substring(string [from int] [for int])
 8532        # (despite being undocumented, the reverse order also works)
 8533        # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6
 8534
 8535        args = t.cast(list[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise))
 8536
 8537        start, length = None, None
 8538
 8539        while self._curr:
 8540            if self._match(TokenType.FROM):
 8541                start = self._parse_bitwise()
 8542            elif self._match(TokenType.FOR):
 8543                if not start:
 8544                    start = exp.Literal.number(1)
 8545                length = self._parse_bitwise()
 8546            else:
 8547                break
 8548
 8549        if start:
 8550            args.append(start)
 8551        if length:
 8552            args.append(length)
 8553
 8554        return self.validate_expression(exp.Substring.from_arg_list(args), args)
 8555
 8556    def _parse_trim(self) -> exp.Trim:
 8557        # https://www.w3resource.com/sql/character-functions/trim.php
 8558        # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html
 8559
 8560        position = None
 8561        collation = None
 8562        expression = None
 8563
 8564        if self._match_texts(self.TRIM_TYPES):
 8565            position = self._prev.text.upper()
 8566
 8567        this = self._parse_bitwise()
 8568        if self._match_set((TokenType.FROM, TokenType.COMMA)):
 8569            invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST
 8570            expression = self._parse_bitwise()
 8571
 8572            if invert_order:
 8573                this, expression = expression, this
 8574
 8575        if self._match(TokenType.COLLATE):
 8576            collation = self._parse_bitwise()
 8577
 8578        return self.expression(
 8579            exp.Trim(this=this, position=position, expression=expression, collation=collation)
 8580        )
 8581
 8582    def _parse_window_clause(self) -> list[exp.Expr] | None:
 8583        return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None
 8584
 8585    def _parse_named_window(self) -> exp.Expr | None:
 8586        return self._parse_window(self._parse_id_var(), alias=True)
 8587
 8588    def _parse_respect_or_ignore_nulls(self, this: exp.Expr | None) -> exp.Expr | None:
 8589        if self._curr.token_type == TokenType.VAR:
 8590            if self._match_text_seq("IGNORE", "NULLS"):
 8591                return self.expression(exp.IgnoreNulls(this=this))
 8592            if self._match_text_seq("RESPECT", "NULLS"):
 8593                return self.expression(exp.RespectNulls(this=this))
 8594        return this
 8595
 8596    def _parse_having_max(self, this: exp.Expr | None) -> exp.Expr | None:
 8597        if self._match(TokenType.HAVING):
 8598            self._match_texts(("MAX", "MIN"))
 8599            max = self._prev.text.upper() != "MIN"
 8600            return self.expression(
 8601                exp.HavingMax(this=this, expression=self._parse_column(), max=max)
 8602            )
 8603
 8604        return this
 8605
 8606    def _parse_window(self, this: exp.Expr | None, alias: bool = False) -> exp.Expr | None:
 8607        func = this
 8608        comments = func.comments if isinstance(func, exp.Expr) else None
 8609
 8610        # T-SQL allows the OVER (...) syntax after WITHIN GROUP.
 8611        # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16
 8612        if self._match_text_seq("WITHIN", "GROUP"):
 8613            order = self._parse_wrapped(self._parse_order)
 8614            this = self.expression(exp.WithinGroup(this=this, expression=order))
 8615
 8616        if self._match_pair(TokenType.FILTER, TokenType.L_PAREN):
 8617            self._match(TokenType.WHERE)
 8618            this = self.expression(
 8619                exp.Filter(this=this, expression=self._parse_where(skip_where_token=True))
 8620            )
 8621            self._match_r_paren()
 8622
 8623        # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER
 8624        # Some dialects choose to implement and some do not.
 8625        # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html
 8626
 8627        # There is some code above in _parse_lambda that handles
 8628        #   SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ...
 8629
 8630        # The below changes handle
 8631        #   SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ...
 8632
 8633        # Oracle allows both formats
 8634        #   (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html)
 8635        #   and Snowflake chose to do the same for familiarity
 8636        #   https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes
 8637        if isinstance(this, exp.AggFunc):
 8638            ignore_respect = find_in_scope(this, exp.IgnoreNulls, exp.RespectNulls)
 8639
 8640            if ignore_respect and ignore_respect is not this:
 8641                ignore_respect.replace(ignore_respect.this)
 8642                this = self.expression(ignore_respect.__class__(this=this))
 8643
 8644        this = self._parse_respect_or_ignore_nulls(this)
 8645
 8646        # bigquery select from window x AS (partition by ...)
 8647        if alias:
 8648            over = None
 8649            self._match(TokenType.ALIAS)
 8650        elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS):
 8651            return this
 8652        else:
 8653            over = self._prev.text.upper()
 8654
 8655        if comments and isinstance(func, exp.Expr):
 8656            func.pop_comments()
 8657
 8658        if not self._match(TokenType.L_PAREN):
 8659            return self.expression(
 8660                exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments
 8661            )
 8662
 8663        window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS)
 8664
 8665        first: bool | None = True if self._match(TokenType.FIRST) else None
 8666        if self._match_text_seq("LAST"):
 8667            first = False
 8668
 8669        partition, order = self._parse_partition_and_order()
 8670        kind = (
 8671            self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq("GROUPS")
 8672        ) and self._prev.text
 8673
 8674        if kind:
 8675            self._match(TokenType.BETWEEN)
 8676            start = self._parse_window_spec()
 8677
 8678            end = self._parse_window_spec() if self._match(TokenType.AND) else {}
 8679            exclude = (
 8680                self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS)
 8681                if self._match_text_seq("EXCLUDE")
 8682                else None
 8683            )
 8684
 8685            spec = self.expression(
 8686                exp.WindowSpec(
 8687                    kind=kind,
 8688                    start=start["value"],
 8689                    start_side=start["side"],
 8690                    end=end.get("value"),
 8691                    end_side=end.get("side"),
 8692                    exclude=exclude,
 8693                )
 8694            )
 8695        else:
 8696            spec = None
 8697
 8698        self._match_r_paren()
 8699
 8700        window = self.expression(
 8701            exp.Window(
 8702                this=this,
 8703                partition_by=partition,
 8704                order=order,
 8705                spec=spec,
 8706                alias=window_alias,
 8707                over=over,
 8708                first=first,
 8709            ),
 8710            comments=comments,
 8711        )
 8712
 8713        # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...)
 8714        if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False):
 8715            return self._parse_window(window, alias=alias)
 8716
 8717        return window
 8718
 8719    def _parse_partition_and_order(
 8720        self,
 8721    ) -> tuple[list[exp.Expr], exp.Expr | None]:
 8722        return self._parse_partition_by(), self._parse_order()
 8723
 8724    def _parse_window_spec(self) -> dict[str, str | exp.Expr | None]:
 8725        self._match(TokenType.BETWEEN)
 8726
 8727        return {
 8728            "value": (
 8729                (self._match_text_seq("UNBOUNDED") and "UNBOUNDED")
 8730                or (self._match_text_seq("CURRENT", "ROW") and "CURRENT ROW")
 8731                or self._parse_bitwise()
 8732            ),
 8733            "side": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None,
 8734        }
 8735
 8736    def _parse_alias(self, this: exp.Expr | None, explicit: bool = False) -> exp.Expr | None:
 8737        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)
 8738        # so this section tries to parse the clause version and if it fails, it treats the token
 8739        # as an identifier (alias)
 8740        if self._can_parse_limit_or_offset():
 8741            return this
 8742
 8743        # WINDOW is in ID_VAR_TOKENS, so it can be consumed as an implicit alias. Detect the
 8744        # named-window clause shape (`WINDOW <ident> AS (...)`) and avoid swallowing it.
 8745        if self._can_parse_named_window():
 8746            return this
 8747
 8748        any_token = self._match(TokenType.ALIAS)
 8749        comments = self._prev_comments
 8750
 8751        if explicit and not any_token:
 8752            return this
 8753
 8754        if self._match(TokenType.L_PAREN):
 8755            aliases = self.expression(
 8756                exp.Aliases(
 8757                    this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token))
 8758                ),
 8759                comments=comments,
 8760            )
 8761            self._match_r_paren(aliases)
 8762            return aliases
 8763
 8764        alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or (
 8765            self.STRING_ALIASES and self._parse_string_as_identifier()
 8766        )
 8767
 8768        if alias:
 8769            comments.extend(alias.pop_comments())
 8770            this = self.expression(exp.Alias(this=this, alias=alias), comments=comments)
 8771            column = this.this
 8772
 8773            # Moves the comment next to the alias in `expr /* comment */ AS alias`
 8774            if not this.comments and column and column.comments:
 8775                this.comments = column.pop_comments()
 8776
 8777        return this
 8778
 8779    def _parse_id_var(
 8780        self,
 8781        any_token: bool = True,
 8782        tokens: t.Collection[TokenType] | None = None,
 8783    ) -> exp.Expr | None:
 8784        expression = self._parse_identifier()
 8785        if not expression and (
 8786            (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS)
 8787        ):
 8788            quoted = self._prev.token_type == TokenType.STRING
 8789            expression = self._identifier_expression(quoted=quoted)
 8790
 8791        return expression
 8792
 8793    def _parse_string(self) -> exp.Expr | None:
 8794        if self._match_set(self.STRING_PARSERS):
 8795            return self.STRING_PARSERS[self._prev.token_type](self, self._prev)
 8796        return self._parse_placeholder()
 8797
 8798    def _parse_string_as_identifier(self) -> exp.Identifier | None:
 8799        if not self._match(TokenType.STRING):
 8800            return None
 8801        output = exp.to_identifier(self._prev.text, quoted=True)
 8802        output.update_positions(self._prev)
 8803        return output
 8804
 8805    def _parse_number(self) -> exp.Expr | None:
 8806        if self._match_set(self.NUMERIC_PARSERS):
 8807            return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev)
 8808        return self._parse_placeholder()
 8809
 8810    def _parse_identifier(self) -> exp.Expr | None:
 8811        if self._match(TokenType.IDENTIFIER):
 8812            return self._identifier_expression(quoted=True)
 8813        return self._parse_placeholder()
 8814
 8815    def _parse_var(
 8816        self,
 8817        any_token: bool = False,
 8818        tokens: t.Collection[TokenType] | None = None,
 8819        upper: bool = False,
 8820    ) -> exp.Expr | None:
 8821        if (
 8822            (any_token and self._advance_any())
 8823            or self._match(TokenType.VAR)
 8824            or (self._match_set(tokens) if tokens else False)
 8825        ):
 8826            return self.expression(
 8827                exp.Var(this=self._prev.text.upper() if upper else self._prev.text)
 8828            )
 8829        return self._parse_placeholder()
 8830
 8831    def _advance_any(self, ignore_reserved: bool = False) -> Token | None:
 8832        if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS):
 8833            self._advance()
 8834            return self._prev
 8835        return None
 8836
 8837    def _parse_var_or_string(self, upper: bool = False) -> exp.Expr | None:
 8838        return self._parse_string() or self._parse_var(any_token=True, upper=upper)
 8839
 8840    def _parse_primary_or_var(self) -> exp.Expr | None:
 8841        return self._parse_primary() or self._parse_var(any_token=True)
 8842
 8843    def _parse_null(self) -> exp.Expr | None:
 8844        if self._match_set((TokenType.NULL, TokenType.UNKNOWN)):
 8845            return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev)
 8846        return self._parse_placeholder()
 8847
 8848    def _parse_boolean(self) -> exp.Expr | None:
 8849        if self._match(TokenType.TRUE):
 8850            return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev)
 8851        if self._match(TokenType.FALSE):
 8852            return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev)
 8853        return self._parse_placeholder()
 8854
 8855    def _parse_star(self) -> exp.Expr | None:
 8856        if self._match(TokenType.STAR):
 8857            return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev)
 8858        return self._parse_placeholder()
 8859
 8860    def _parse_parameter(self) -> exp.Parameter:
 8861        this = self._parse_identifier() or self._parse_primary_or_var()
 8862        return self.expression(exp.Parameter(this=this))
 8863
 8864    def _parse_placeholder(self) -> exp.Expr | None:
 8865        if self._match_set(self.PLACEHOLDER_PARSERS):
 8866            placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self)
 8867            if placeholder:
 8868                return placeholder
 8869            self._advance(-1)
 8870        return None
 8871
 8872    def _parse_star_op(self, *keywords: str) -> list[exp.Expr] | None:
 8873        if not self._match_texts(keywords):
 8874            return None
 8875        if self._match(TokenType.L_PAREN, advance=False):
 8876            return self._parse_wrapped_csv(self._parse_expression)
 8877
 8878        expression = self._parse_alias(self._parse_disjunction(), explicit=True)
 8879        return [expression] if expression else None
 8880
 8881    def _parse_csv(
 8882        self, parse_method: t.Callable[[], T | None], sep: TokenType = TokenType.COMMA
 8883    ) -> list[T]:
 8884        parse_result = parse_method()
 8885        items = [parse_result] if parse_result is not None else []
 8886
 8887        while self._match(sep):
 8888            if isinstance(parse_result, exp.Expr):
 8889                self._add_comments(parse_result)
 8890            parse_result = parse_method()
 8891            if parse_result is not None:
 8892                items.append(parse_result)
 8893
 8894        return items
 8895
 8896    def _parse_wrapped_id_vars(self, optional: bool = False) -> list[exp.Expr]:
 8897        return self._parse_wrapped_csv(self._parse_id_var, optional=optional)
 8898
 8899    def _parse_wrapped_csv(
 8900        self,
 8901        parse_method: t.Callable[[], T | None],
 8902        sep: TokenType = TokenType.COMMA,
 8903        optional: bool = False,
 8904    ) -> list[T]:
 8905        return self._parse_wrapped(
 8906            lambda: self._parse_csv(parse_method, sep=sep), optional=optional
 8907        )
 8908
 8909    def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T:
 8910        wrapped = self._match(TokenType.L_PAREN)
 8911        if not wrapped and not optional:
 8912            self.raise_error("Expecting (")
 8913        parse_result = parse_method()
 8914        if wrapped:
 8915            self._match_r_paren()
 8916        return parse_result
 8917
 8918    def _parse_expressions(self) -> list[exp.Expr]:
 8919        return self._parse_csv(self._parse_expression)
 8920
 8921    def _parse_select_or_expression(self, alias: bool = False) -> exp.Expr | None:
 8922        return (
 8923            self._parse_set_operations(
 8924                self._parse_alias(self._parse_assignment(), explicit=True)
 8925                if alias
 8926                else self._parse_assignment()
 8927            )
 8928            or self._parse_select()
 8929        )
 8930
 8931    def _parse_ddl_select(self) -> exp.Expr | None:
 8932        return self._parse_query_modifiers(
 8933            self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False))
 8934        )
 8935
 8936    def _parse_transaction(self) -> exp.Transaction | exp.Command:
 8937        this = None
 8938        if self._match_texts(self.TRANSACTION_KIND):
 8939            this = self._prev.text
 8940
 8941        self._match_texts(("TRANSACTION", "WORK"))
 8942
 8943        modes = []
 8944        while True:
 8945            mode = []
 8946            while self._match(TokenType.VAR) or self._match(TokenType.NOT):
 8947                mode.append(self._prev.text)
 8948
 8949            if mode:
 8950                modes.append(" ".join(mode))
 8951            if not self._match(TokenType.COMMA):
 8952                break
 8953
 8954        return self.expression(exp.Transaction(this=this, modes=modes))
 8955
 8956    def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback:
 8957        chain = None
 8958        savepoint = None
 8959        is_rollback = self._prev.token_type == TokenType.ROLLBACK
 8960
 8961        self._match_texts(("TRANSACTION", "WORK"))
 8962
 8963        if self._match_text_seq("TO"):
 8964            self._match_text_seq("SAVEPOINT")
 8965            savepoint = self._parse_id_var()
 8966
 8967        if self._match(TokenType.AND):
 8968            chain = not self._match_text_seq("NO")
 8969            self._match_text_seq("CHAIN")
 8970
 8971        if is_rollback:
 8972            return self.expression(exp.Rollback(savepoint=savepoint))
 8973
 8974        return self.expression(exp.Commit(chain=chain))
 8975
 8976    def _parse_refresh(self) -> exp.Refresh | exp.Command:
 8977        if self._match_text_seq("EXTERNAL", "TABLE"):
 8978            kind = "EXTERNAL TABLE"
 8979        elif self._match(TokenType.TABLE):
 8980            kind = "TABLE"
 8981        elif self._match_text_seq("MATERIALIZED", "VIEW"):
 8982            kind = "MATERIALIZED VIEW"
 8983        else:
 8984            kind = ""
 8985
 8986        this = self._parse_string() or self._parse_table()
 8987        if not kind and not isinstance(this, exp.Literal):
 8988            return self._parse_as_command(self._prev)
 8989
 8990        return self.expression(exp.Refresh(this=this, kind=kind))
 8991
 8992    def _parse_column_def_with_exists(self):
 8993        start = self._index
 8994        self._match(TokenType.COLUMN)
 8995
 8996        exists_column = self._parse_exists(not_=True)
 8997        expression = self._parse_field_def()
 8998
 8999        if not isinstance(expression, exp.ColumnDef):
 9000            self._retreat(start)
 9001            return None
 9002
 9003        expression.set("exists", exists_column)
 9004
 9005        return expression
 9006
 9007    def _parse_add_column(self) -> exp.ColumnDef | None:
 9008        if not self._prev.text.upper() == "ADD":
 9009            return None
 9010
 9011        return self._parse_column_def_with_exists()
 9012
 9013    def _parse_drop_column(self) -> exp.Drop | exp.Command | None:
 9014        drop = self._parse_drop() if self._match(TokenType.DROP) else None
 9015        if drop and not isinstance(drop, exp.Command):
 9016            drop.set("kind", drop.args.get("kind", "COLUMN"))
 9017        return drop
 9018
 9019    def _parse_alter_drop_action(self) -> exp.Expr | None:
 9020        return self._parse_drop_column()
 9021
 9022    # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html
 9023    def _parse_drop_partition(self, exists: bool | None = None) -> exp.DropPartition:
 9024        return self.expression(
 9025            exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists)
 9026        )
 9027
 9028    def _parse_alter_table_add(self) -> list[exp.Expr]:
 9029        def _parse_add_alteration() -> exp.Expr | None:
 9030            self._match_text_seq("ADD")
 9031            if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False):
 9032                return self.expression(
 9033                    exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint))
 9034                )
 9035
 9036            column_def = self._parse_add_column()
 9037            if isinstance(column_def, exp.ColumnDef):
 9038                return column_def
 9039
 9040            exists = self._parse_exists(not_=True)
 9041            if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False):
 9042                return self.expression(
 9043                    exp.AddPartition(
 9044                        exists=exists,
 9045                        this=self._parse_field(any_token=True),
 9046                        location=self._match_text_seq("LOCATION", advance=False)
 9047                        and self._parse_property(),
 9048                    )
 9049                )
 9050
 9051            return None
 9052
 9053        if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and (
 9054            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN
 9055            or self._match_text_seq("COLUMNS")
 9056        ):
 9057            schema = self._parse_schema()
 9058
 9059            return (
 9060                ensure_list(schema)
 9061                if schema
 9062                else self._parse_csv(self._parse_column_def_with_exists)
 9063            )
 9064
 9065        return self._parse_csv(_parse_add_alteration)
 9066
 9067    def _parse_alter_table_alter(self) -> exp.Expr | None:
 9068        if self._match_texts(self.ALTER_ALTER_PARSERS):
 9069            return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self)
 9070
 9071        # Many dialects support the ALTER [COLUMN] syntax, so if there is no
 9072        # keyword after ALTER we default to parsing this statement
 9073        self._match(TokenType.COLUMN)
 9074        exists = self._parse_exists()
 9075        column = self._parse_field(any_token=True)
 9076
 9077        if self._match_pair(TokenType.DROP, TokenType.DEFAULT):
 9078            return self.expression(exp.AlterColumn(this=column, drop=True, exists=exists or None))
 9079        if self._match_pair(TokenType.SET, TokenType.DEFAULT):
 9080            return self.expression(
 9081                exp.AlterColumn(
 9082                    this=column, default=self._parse_disjunction(), exists=exists or None
 9083                )
 9084            )
 9085        if self._match(TokenType.COMMENT):
 9086            return self.expression(
 9087                exp.AlterColumn(this=column, comment=self._parse_string(), exists=exists or None)
 9088            )
 9089        if self._match_text_seq("DROP", "NOT", "NULL"):
 9090            return self.expression(
 9091                exp.AlterColumn(this=column, drop=True, allow_null=True, exists=exists or None)
 9092            )
 9093        if self._match_text_seq("SET", "NOT", "NULL"):
 9094            return self.expression(
 9095                exp.AlterColumn(this=column, allow_null=False, exists=exists or None)
 9096            )
 9097
 9098        if self._match_text_seq("SET", "VISIBLE"):
 9099            return self.expression(
 9100                exp.AlterColumn(this=column, visible="VISIBLE", exists=exists or None)
 9101            )
 9102        if self._match_text_seq("SET", "INVISIBLE"):
 9103            return self.expression(
 9104                exp.AlterColumn(this=column, visible="INVISIBLE", exists=exists or None)
 9105            )
 9106
 9107        self._match_text_seq("SET", "DATA")
 9108        self._match_text_seq("TYPE")
 9109        return self.expression(
 9110            exp.AlterColumn(
 9111                this=column,
 9112                dtype=self._parse_types(),
 9113                collate=self._match(TokenType.COLLATE) and self._parse_term(),
 9114                using=self._match(TokenType.USING) and self._parse_disjunction(),
 9115                exists=exists or None,
 9116            )
 9117        )
 9118
 9119    def _parse_alter_diststyle(self) -> exp.AlterDistStyle:
 9120        if self._match_texts(("ALL", "EVEN", "AUTO")):
 9121            return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper())))
 9122
 9123        self._match_text_seq("KEY", "DISTKEY")
 9124        return self.expression(exp.AlterDistStyle(this=self._parse_column()))
 9125
 9126    def _parse_alter_sortkey(self, compound: bool | None = None) -> exp.AlterSortKey:
 9127        if compound:
 9128            self._match_text_seq("SORTKEY")
 9129
 9130        if self._match(TokenType.L_PAREN, advance=False):
 9131            return self.expression(
 9132                exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound)
 9133            )
 9134
 9135        self._match_texts(("AUTO", "NONE"))
 9136        return self.expression(
 9137            exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound)
 9138        )
 9139
 9140    def _parse_alter_table_drop(self) -> list[exp.Expr]:
 9141        index = self._index - 1
 9142
 9143        partition_exists = self._parse_exists()
 9144        if self._match(TokenType.PARTITION, advance=False):
 9145            return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists))
 9146
 9147        self._retreat(index)
 9148        return self._parse_csv(self._parse_alter_drop_action)
 9149
 9150    def _parse_alter_table_rename(self) -> exp.AlterRename | exp.RenameColumn | None:
 9151        if self._match(TokenType.COLUMN) or (
 9152            not self.ALTER_RENAME_REQUIRES_COLUMN and not self._match_text_seq("TO", advance=False)
 9153        ):
 9154            exists = self._parse_exists()
 9155            old_column = self._parse_column()
 9156            to = self._match_text_seq("TO")
 9157            new_column = self._parse_column()
 9158
 9159            if old_column is None or not to or new_column is None:
 9160                return None
 9161
 9162            return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists))
 9163
 9164        self._match_text_seq("TO")
 9165        return self.expression(exp.AlterRename(this=self._parse_table(schema=True)))
 9166
 9167    def _parse_alter_table_set(self) -> exp.AlterSet:
 9168        alter_set = self.expression(exp.AlterSet())
 9169
 9170        if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq(
 9171            "TABLE", "PROPERTIES"
 9172        ):
 9173            alter_set.set("expressions", self._parse_wrapped_csv(self._parse_assignment))
 9174        elif self._match_text_seq("FILESTREAM_ON", advance=False):
 9175            alter_set.set("expressions", [self._parse_assignment()])
 9176        elif self._match_texts(("LOGGED", "UNLOGGED")):
 9177            alter_set.set("option", exp.var(self._prev.text.upper()))
 9178        elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")):
 9179            alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}"))
 9180        elif self._match_text_seq("LOCATION"):
 9181            alter_set.set("location", self._parse_field())
 9182        elif self._match_text_seq("ACCESS", "METHOD"):
 9183            alter_set.set("access_method", self._parse_field())
 9184        elif self._match_text_seq("TABLESPACE"):
 9185            alter_set.set("tablespace", self._parse_field())
 9186        elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"):
 9187            alter_set.set("file_format", [self._parse_field()])
 9188        elif self._match_text_seq("STAGE_FILE_FORMAT"):
 9189            alter_set.set("file_format", self._parse_wrapped_options())
 9190        elif self._match_text_seq("STAGE_COPY_OPTIONS"):
 9191            alter_set.set("copy_options", self._parse_wrapped_options())
 9192        elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"):
 9193            alter_set.set("tag", self._parse_csv(self._parse_assignment))
 9194        else:
 9195            if self._match_text_seq("SERDE"):
 9196                alter_set.set("serde", self._parse_field())
 9197
 9198            properties = self._parse_wrapped(self._parse_properties, optional=True)
 9199            alter_set.set("expressions", [properties])
 9200
 9201        return alter_set
 9202
 9203    def _parse_alter_session(self) -> exp.AlterSession:
 9204        """Parse ALTER SESSION SET/UNSET statements."""
 9205        if self._match(TokenType.SET):
 9206            expressions = self._parse_csv(lambda: self._parse_set_item_assignment())
 9207            return self.expression(exp.AlterSession(expressions=expressions, unset=False))
 9208
 9209        self._match_text_seq("UNSET")
 9210        expressions = self._parse_csv(
 9211            lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True)))
 9212        )
 9213        return self.expression(exp.AlterSession(expressions=expressions, unset=True))
 9214
 9215    def _parse_alter(self) -> exp.Alter | exp.Command:
 9216        start = self._prev
 9217
 9218        iceberg = self._match_text_seq("ICEBERG")
 9219
 9220        alter_token = self._match_set(self.ALTERABLES) and self._prev
 9221        if not alter_token:
 9222            return self._parse_as_command(start)
 9223        if iceberg and alter_token.token_type != TokenType.TABLE:
 9224            return self._parse_as_command(start)
 9225
 9226        exists = self._parse_exists()
 9227        only = self._match_text_seq("ONLY")
 9228
 9229        if alter_token.token_type == TokenType.SESSION:
 9230            this = None
 9231            check = None
 9232            cluster = None
 9233        else:
 9234            this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS)
 9235            check = self._match_text_seq("WITH", "CHECK")
 9236            cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9237
 9238            if self._next:
 9239                self._advance()
 9240
 9241        parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None
 9242        if parser:
 9243            actions = ensure_list(parser(self))
 9244            not_valid = self._match_text_seq("NOT", "VALID")
 9245            options = self._parse_csv(self._parse_property)
 9246            cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq("CASCADE")
 9247
 9248            if not self._curr and actions:
 9249                return self.expression(
 9250                    exp.Alter(
 9251                        this=this,
 9252                        kind=alter_token.text.upper(),
 9253                        exists=exists,
 9254                        actions=actions,
 9255                        only=only,
 9256                        options=options,
 9257                        cluster=cluster,
 9258                        not_valid=not_valid,
 9259                        check=check,
 9260                        cascade=cascade,
 9261                        iceberg=iceberg,
 9262                    )
 9263                )
 9264
 9265        return self._parse_as_command(start)
 9266
 9267    def _parse_analyze(self) -> exp.Analyze | exp.Command:
 9268        start = self._prev
 9269        # https://duckdb.org/docs/sql/statements/analyze
 9270        if not self._curr:
 9271            return self.expression(exp.Analyze())
 9272
 9273        options = []
 9274        while self._match_texts(self.ANALYZE_STYLES):
 9275            if self._prev.text.upper() == "BUFFER_USAGE_LIMIT":
 9276                options.append(f"BUFFER_USAGE_LIMIT {self._parse_number()}")
 9277            else:
 9278                options.append(self._prev.text.upper())
 9279
 9280        this: exp.Expr | None = None
 9281        inner_expression: exp.Expr | None = None
 9282
 9283        kind = self._curr.text.upper() if self._curr else None
 9284
 9285        if self._match(TokenType.TABLE) or self._match(TokenType.INDEX):
 9286            this = self._parse_table_parts()
 9287        elif self._match_text_seq("TABLES"):
 9288            if self._match_set((TokenType.FROM, TokenType.IN)):
 9289                kind = f"{kind} {self._prev.text.upper()}"
 9290                this = self._parse_table(schema=True, is_db_reference=True)
 9291        elif self._match_text_seq("DATABASE"):
 9292            this = self._parse_table(schema=True, is_db_reference=True)
 9293        elif self._match_text_seq("CLUSTER"):
 9294            this = self._parse_table()
 9295        # Try matching inner expr keywords before fallback to parse table.
 9296        elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9297            kind = None
 9298            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9299        else:
 9300            # Empty kind  https://prestodb.io/docs/current/sql/analyze.html
 9301            kind = None
 9302            this = self._parse_table_parts()
 9303
 9304        partition = self._try_parse(self._parse_partition)
 9305        if not partition and self._match_texts(self.PARTITION_KEYWORDS):
 9306            return self._parse_as_command(start)
 9307
 9308        # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9309        if self._match_text_seq("WITH", "SYNC", "MODE") or self._match_text_seq(
 9310            "WITH", "ASYNC", "MODE"
 9311        ):
 9312            mode = f"WITH {self._tokens[self._index - 2].text.upper()} MODE"
 9313        else:
 9314            mode = None
 9315
 9316        if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):
 9317            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)
 9318
 9319        properties = self._parse_properties()
 9320        return self.expression(
 9321            exp.Analyze(
 9322                kind=kind,
 9323                this=this,
 9324                mode=mode,
 9325                partition=partition,
 9326                properties=properties,
 9327                expression=inner_expression,
 9328                options=options,
 9329            )
 9330        )
 9331
 9332    # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html
 9333    def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics:
 9334        this = None
 9335        kind = self._prev.text.upper()
 9336        option = self._prev.text.upper() if self._match_text_seq("DELTA") else None
 9337        expressions = []
 9338
 9339        if not self._match_text_seq("STATISTICS"):
 9340            self.raise_error("Expecting token STATISTICS")
 9341
 9342        if self._match_text_seq("NOSCAN"):
 9343            this = "NOSCAN"
 9344        elif self._match(TokenType.FOR):
 9345            if self._match_text_seq("ALL", "COLUMNS"):
 9346                this = "FOR ALL COLUMNS"
 9347            if self._match_text_seq("COLUMNS"):
 9348                this = "FOR COLUMNS"
 9349                expressions = self._parse_csv(self._parse_column_reference)
 9350        elif self._match_text_seq("SAMPLE"):
 9351            sample = self._parse_number()
 9352            expressions = [
 9353                self.expression(
 9354                    exp.AnalyzeSample(
 9355                        sample=sample,
 9356                        kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None,
 9357                    )
 9358                )
 9359            ]
 9360
 9361        return self.expression(
 9362            exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions)
 9363        )
 9364
 9365    # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html
 9366    def _parse_analyze_validate(self) -> exp.AnalyzeValidate:
 9367        kind = None
 9368        this = None
 9369        expression: exp.Expr | None = None
 9370        if self._match_text_seq("REF", "UPDATE"):
 9371            kind = "REF"
 9372            this = "UPDATE"
 9373            if self._match_text_seq("SET", "DANGLING", "TO", "NULL"):
 9374                this = "UPDATE SET DANGLING TO NULL"
 9375        elif self._match_text_seq("STRUCTURE"):
 9376            kind = "STRUCTURE"
 9377            if self._match_text_seq("CASCADE", "FAST"):
 9378                this = "CASCADE FAST"
 9379            elif self._match_text_seq("CASCADE", "COMPLETE") and self._match_texts(
 9380                ("ONLINE", "OFFLINE")
 9381            ):
 9382                this = f"CASCADE COMPLETE {self._prev.text.upper()}"
 9383                expression = self._parse_into()
 9384
 9385        return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression))
 9386
 9387    def _parse_analyze_columns(self) -> exp.AnalyzeColumns | None:
 9388        this = self._prev.text.upper()
 9389        if self._match_text_seq("COLUMNS"):
 9390            return self.expression(exp.AnalyzeColumns(this=f"{this} {self._prev.text.upper()}"))
 9391        return None
 9392
 9393    def _parse_analyze_delete(self) -> exp.AnalyzeDelete | None:
 9394        kind = self._prev.text.upper() if self._match_text_seq("SYSTEM") else None
 9395        if self._match_text_seq("STATISTICS"):
 9396            return self.expression(exp.AnalyzeDelete(kind=kind))
 9397        return None
 9398
 9399    def _parse_analyze_list(self) -> exp.AnalyzeListChainedRows | None:
 9400        if self._match_text_seq("CHAINED", "ROWS"):
 9401            return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into()))
 9402        return None
 9403
 9404    # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html
 9405    def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram:
 9406        this = self._prev.text.upper()
 9407        expression: exp.Expr | None = None
 9408        expressions = []
 9409        update_options = None
 9410
 9411        if self._match_text_seq("HISTOGRAM", "ON"):
 9412            expressions = self._parse_csv(self._parse_column_reference)
 9413            with_expressions = []
 9414            while self._match(TokenType.WITH):
 9415                # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/
 9416                if self._match_texts(("SYNC", "ASYNC")):
 9417                    if self._match_text_seq("MODE", advance=False):
 9418                        with_expressions.append(f"{self._prev.text.upper()} MODE")
 9419                        self._advance()
 9420                else:
 9421                    buckets = self._parse_number()
 9422                    if self._match_text_seq("BUCKETS"):
 9423                        with_expressions.append(f"{buckets} BUCKETS")
 9424            if with_expressions:
 9425                expression = self.expression(exp.AnalyzeWith(expressions=with_expressions))
 9426
 9427            if self._match_texts(("MANUAL", "AUTO")) and self._match(
 9428                TokenType.UPDATE, advance=False
 9429            ):
 9430                update_options = self._prev.text.upper()
 9431                self._advance()
 9432            elif self._match_text_seq("USING", "DATA"):
 9433                expression = self.expression(exp.UsingData(this=self._parse_string()))
 9434
 9435        return self.expression(
 9436            exp.AnalyzeHistogram(
 9437                this=this,
 9438                expressions=expressions,
 9439                expression=expression,
 9440                update_options=update_options,
 9441            )
 9442        )
 9443
 9444    def _parse_merge(self) -> exp.Merge:
 9445        self._match(TokenType.INTO)
 9446        target = self._parse_table()
 9447
 9448        if target and self._match(TokenType.ALIAS, advance=False):
 9449            target.set("alias", self._parse_table_alias())
 9450
 9451        self._match(TokenType.USING)
 9452        using = self._parse_table()
 9453
 9454        return self.expression(
 9455            exp.Merge(
 9456                this=target,
 9457                using=using,
 9458                on=self._match(TokenType.ON) and self._parse_disjunction(),
 9459                using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(),
 9460                whens=self._parse_when_matched(),
 9461                returning=self._parse_returning(),
 9462            )
 9463        )
 9464
 9465    def _parse_when_matched(self) -> exp.Whens:
 9466        whens = []
 9467
 9468        while self._match(TokenType.WHEN):
 9469            matched = not self._match(TokenType.NOT)
 9470            self._match_text_seq("MATCHED")
 9471            source = (
 9472                False
 9473                if self._match_text_seq("BY", "TARGET")
 9474                else self._match_text_seq("BY", "SOURCE")
 9475            )
 9476            condition = self._parse_disjunction() if self._match(TokenType.AND) else None
 9477
 9478            self._match(TokenType.THEN)
 9479
 9480            if self._match(TokenType.INSERT):
 9481                this = self._parse_star()
 9482                if this:
 9483                    then: exp.Expr | None = self.expression(exp.Insert(this=this))
 9484                else:
 9485                    then = self.expression(
 9486                        exp.Insert(
 9487                            this=exp.var("ROW")
 9488                            if self._match_text_seq("ROW")
 9489                            else self._parse_value(values=False),
 9490                            expression=self._match_text_seq("VALUES") and self._parse_value(),
 9491                            where=self._parse_where(),
 9492                        )
 9493                    )
 9494            elif self._match(TokenType.UPDATE):
 9495                expressions = self._parse_star()
 9496                if expressions:
 9497                    then = self.expression(exp.Update(expressions=expressions))
 9498                else:
 9499                    then = self.expression(
 9500                        exp.Update(
 9501                            expressions=self._match(TokenType.SET)
 9502                            and self._parse_csv(self._parse_equality),
 9503                            where=self._parse_where(),
 9504                        )
 9505                    )
 9506            elif self._match(TokenType.DELETE):
 9507                then = self.expression(exp.Var(this=self._prev.text))
 9508            else:
 9509                then = self._parse_var_from_options(self.CONFLICT_ACTIONS)
 9510
 9511            whens.append(
 9512                self.expression(
 9513                    exp.When(matched=matched, source=source, condition=condition, then=then)
 9514                )
 9515            )
 9516        return self.expression(exp.Whens(expressions=whens))
 9517
 9518    def _parse_show(self) -> exp.Expr | None:
 9519        parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE)
 9520        if parser:
 9521            return parser(self)
 9522        return self._parse_as_command(self._prev)
 9523
 9524    def _parse_set_item_assignment(self, kind: str | None = None) -> exp.Expr | None:
 9525        index = self._index
 9526
 9527        if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"):
 9528            return self._parse_set_transaction(global_=kind == "GLOBAL")
 9529
 9530        left = self._parse_primary() or self._parse_column()
 9531        assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS)
 9532
 9533        if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter):
 9534            self._retreat(index)
 9535            return None
 9536
 9537        right = self._parse_statement() or self._parse_id_var()
 9538        if isinstance(right, (exp.Column, exp.Identifier)):
 9539            right = exp.var(right.name)
 9540
 9541        this = self.expression(exp.EQ(this=left, expression=right))
 9542        return self.expression(exp.SetItem(this=this, kind=kind))
 9543
 9544    def _parse_set_transaction(self, global_: bool = False) -> exp.Expr:
 9545        self._match_text_seq("TRANSACTION")
 9546        characteristics = self._parse_csv(
 9547            lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS)
 9548        )
 9549        return self.expression(
 9550            exp.SetItem(expressions=characteristics, kind="TRANSACTION", global_=global_)
 9551        )
 9552
 9553    def _parse_set_item(self) -> exp.Expr | None:
 9554        parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE)
 9555        return parser(self) if parser else self._parse_set_item_assignment(kind=None)
 9556
 9557    def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command:
 9558        index = self._index
 9559        set_ = self.expression(
 9560            exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag)
 9561        )
 9562
 9563        if self._curr:
 9564            self._retreat(index)
 9565            return self._parse_as_command(self._prev)
 9566
 9567        return set_
 9568
 9569    def _parse_var_from_options(
 9570        self, options: OPTIONS_TYPE, raise_unmatched: bool = True
 9571    ) -> exp.Var | None:
 9572        start = self._curr
 9573        if not start:
 9574            return None
 9575
 9576        option = start.text.upper()
 9577        continuations = (
 9578            None if start.token_type in self.TEXT_MATCH_EXCLUDED_TOKENS else options.get(option)
 9579        )
 9580
 9581        index = self._index
 9582        self._advance()
 9583        for keywords in continuations or []:
 9584            if isinstance(keywords, str):
 9585                keywords = (keywords,)
 9586
 9587            if self._match_text_seq(*keywords):
 9588                option = f"{option} {' '.join(keywords)}"
 9589                break
 9590        else:
 9591            if continuations or continuations is None:
 9592                if raise_unmatched:
 9593                    self.raise_error(f"Unknown option {option}")
 9594
 9595                self._retreat(index)
 9596                return None
 9597
 9598        return exp.var(option)
 9599
 9600    def _parse_as_command(self, start: Token) -> exp.Command:
 9601        while self._curr:
 9602            self._advance()
 9603        text = self._find_sql(start, self._prev)
 9604        size = len(start.text)
 9605        self._warn_unsupported()
 9606        return exp.Command(this=text[:size], expression=text[size:])
 9607
 9608    def _parse_dict_property(self, this: str) -> exp.DictProperty:
 9609        settings = []
 9610
 9611        self._match_l_paren()
 9612        kind = self._parse_id_var()
 9613
 9614        if self._match(TokenType.L_PAREN):
 9615            while True:
 9616                key = self._parse_id_var()
 9617                value = self._parse_function() or self._parse_primary_or_var()
 9618                if not key and value is None:
 9619                    break
 9620                settings.append(self.expression(exp.DictSubProperty(this=key, value=value)))
 9621            self._match(TokenType.R_PAREN)
 9622
 9623        self._match_r_paren()
 9624
 9625        return self.expression(
 9626            exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings)
 9627        )
 9628
 9629    def _parse_dict_range(self, this: str) -> exp.DictRange:
 9630        self._match_l_paren()
 9631        has_min = self._match_text_seq("MIN")
 9632        if has_min:
 9633            min = self._parse_var() or self._parse_primary()
 9634            self._match_text_seq("MAX")
 9635            max = self._parse_var() or self._parse_primary()
 9636        else:
 9637            max = self._parse_var() or self._parse_primary()
 9638            min = exp.Literal.number(0)
 9639        self._match_r_paren()
 9640        return self.expression(exp.DictRange(this=this, min=min, max=max))
 9641
 9642    def _parse_comprehension(self, this: exp.Expr | None) -> exp.Comprehension | None:
 9643        index = self._index
 9644        expression = self._parse_column()
 9645        position = self._match(TokenType.COMMA) and self._parse_column()
 9646
 9647        if not self._match(TokenType.IN):
 9648            self._retreat(index - 1)
 9649            return None
 9650        iterator = self._parse_column()
 9651        condition = self._parse_disjunction() if self._match_text_seq("IF") else None
 9652        return self.expression(
 9653            exp.Comprehension(
 9654                this=this,
 9655                expression=expression,
 9656                position=position,
 9657                iterator=iterator,
 9658                condition=condition,
 9659            )
 9660        )
 9661
 9662    def _parse_heredoc(self) -> exp.Heredoc | None:
 9663        if self._match(TokenType.HEREDOC_STRING):
 9664            return self.expression(exp.Heredoc(this=self._prev.text))
 9665
 9666        if not self._match_text_seq("$"):
 9667            return None
 9668
 9669        tags = ["$"]
 9670        tag_text = None
 9671
 9672        if self._is_connected():
 9673            self._advance()
 9674            tags.append(self._prev.text.upper())
 9675        else:
 9676            self.raise_error("No closing $ found")
 9677
 9678        if tags[-1] != "$":
 9679            if self._is_connected() and self._match_text_seq("$"):
 9680                tag_text = tags[-1]
 9681                tags.append("$")
 9682            else:
 9683                self.raise_error("No closing $ found")
 9684
 9685        heredoc_start = self._curr
 9686
 9687        while self._curr:
 9688            if self._match_text_seq(*tags, advance=False):
 9689                this = self._find_sql(heredoc_start, self._prev)
 9690                self._advance(len(tags))
 9691                return self.expression(exp.Heredoc(this=this, tag=tag_text))
 9692
 9693            self._advance()
 9694
 9695        self.raise_error(f"No closing {''.join(tags)} found")
 9696        return None
 9697
 9698    def _find_parser(self, parsers: dict[str, t.Callable], trie: dict) -> t.Callable | None:
 9699        if not self._curr:
 9700            return None
 9701
 9702        index = self._index
 9703        this = []
 9704        while True:
 9705            # The current token might be multiple words
 9706            curr = self._curr.text.upper()
 9707            key = curr.split(" ")
 9708            this.append(curr)
 9709
 9710            self._advance()
 9711            result, trie = in_trie(trie, key)
 9712            if result == TrieResult.FAILED:
 9713                break
 9714
 9715            if result == TrieResult.EXISTS:
 9716                subparser = parsers[" ".join(this)]
 9717                return subparser
 9718
 9719        self._retreat(index)
 9720        return None
 9721
 9722    def _match_l_paren(self, expression: exp.Expr | None = None) -> None:
 9723        if not self._match(TokenType.L_PAREN, expression=expression):
 9724            self.raise_error("Expecting (")
 9725
 9726    def _match_r_paren(self, expression: exp.Expr | None = None) -> None:
 9727        if not self._match(TokenType.R_PAREN, expression=expression):
 9728            self.raise_error("Expecting )")
 9729
 9730    def _replace_lambda(
 9731        self, node: exp.Expr | None, expressions: list[exp.Expr]
 9732    ) -> exp.Expr | None:
 9733        if not node:
 9734            return node
 9735
 9736        lambda_types = {e.name: e.args.get("to") or False for e in expressions}
 9737
 9738        for column in node.find_all(exp.Column):
 9739            typ = lambda_types.get(column.parts[0].name)
 9740            if typ is not None:
 9741                dot_or_id = column.to_dot() if column.table else column.this
 9742
 9743                if typ:
 9744                    dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ))
 9745
 9746                parent = column.parent
 9747
 9748                while isinstance(parent, exp.Dot):
 9749                    if not isinstance(parent.parent, exp.Dot):
 9750                        parent.replace(dot_or_id)
 9751                        break
 9752                    parent = parent.parent
 9753                else:
 9754                    if column is node:
 9755                        node = dot_or_id
 9756                    else:
 9757                        column.replace(dot_or_id)
 9758        return node
 9759
 9760    def _parse_truncate_table(self) -> exp.TruncateTable | None | exp.Expr:
 9761        start = self._prev
 9762
 9763        # Not to be confused with TRUNCATE(number, decimals) function call
 9764        if self._match(TokenType.L_PAREN):
 9765            self._retreat(self._index - 2)
 9766            return self._parse_function()
 9767
 9768        # Clickhouse supports TRUNCATE DATABASE as well
 9769        is_database = self._match(TokenType.DATABASE)
 9770
 9771        self._match(TokenType.TABLE)
 9772
 9773        exists = self._parse_exists(not_=False)
 9774
 9775        expressions = self._parse_csv(
 9776            lambda: self._parse_table(schema=True, is_db_reference=is_database)
 9777        )
 9778
 9779        cluster = self._parse_on_property() if self._match(TokenType.ON) else None
 9780
 9781        if self._match_text_seq("RESTART", "IDENTITY"):
 9782            identity = "RESTART"
 9783        elif self._match_text_seq("CONTINUE", "IDENTITY"):
 9784            identity = "CONTINUE"
 9785        else:
 9786            identity = None
 9787
 9788        if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"):
 9789            option = self._prev.text
 9790        else:
 9791            option = None
 9792
 9793        partition = self._parse_partition()
 9794
 9795        # Fallback case
 9796        if self._curr:
 9797            return self._parse_as_command(start)
 9798
 9799        return self.expression(
 9800            exp.TruncateTable(
 9801                expressions=expressions,
 9802                is_database=is_database,
 9803                exists=exists,
 9804                cluster=cluster,
 9805                identity=identity,
 9806                option=option,
 9807                partition=partition,
 9808            )
 9809        )
 9810
 9811    def _parse_indexed_column(self) -> exp.Expr | None:
 9812        return self._parse_ordered(self._parse_opclass)
 9813
 9814    def _parse_with_operator(self) -> exp.Expr | None:
 9815        this = self._parse_indexed_column()
 9816
 9817        if not self._match(TokenType.WITH):
 9818            return this
 9819
 9820        op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS)
 9821
 9822        return self.expression(exp.WithOperator(this=this, op=op))
 9823
 9824    def _parse_wrapped_options(self) -> list[exp.Expr]:
 9825        self._match(TokenType.EQ)
 9826        self._match(TokenType.L_PAREN)
 9827
 9828        opts: list[exp.Expr] = []
 9829        option: exp.Expr | list[exp.Expr] | None
 9830        while self._curr and not self._match(TokenType.R_PAREN):
 9831            if self._match_text_seq("FORMAT_NAME", "="):
 9832                # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL
 9833                option = self._parse_format_name()
 9834            else:
 9835                option = self._parse_property()
 9836
 9837            if option is None:
 9838                self.raise_error("Unable to parse option")
 9839                break
 9840
 9841            opts.extend(ensure_list(option))
 9842
 9843        return opts
 9844
 9845    def _parse_copy_parameters(self) -> list[exp.CopyParameter]:
 9846        sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None
 9847
 9848        options = []
 9849        while self._curr and not self._match(TokenType.R_PAREN, advance=False):
 9850            option = self._parse_var(any_token=True)
 9851            prev = self._prev.text.upper()
 9852
 9853            # Different dialects might separate options and values by white space, "=" and "AS"
 9854            self._match(TokenType.EQ)
 9855            self._match(TokenType.ALIAS)
 9856
 9857            param = self.expression(exp.CopyParameter(this=option))
 9858
 9859            if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match(
 9860                TokenType.L_PAREN, advance=False
 9861            ):
 9862                # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options
 9863                param.set("expressions", self._parse_wrapped_options())
 9864            elif prev == "FILE_FORMAT":
 9865                # T-SQL's external file format case
 9866                param.set("expression", self._parse_field())
 9867            elif (
 9868                prev == "FORMAT"
 9869                and self._prev.token_type == TokenType.ALIAS
 9870                and self._match_texts(("AVRO", "JSON"))
 9871            ):
 9872                param.set("this", exp.var(f"FORMAT AS {self._prev.text.upper()}"))
 9873                param.set("expression", self._parse_field())
 9874            else:
 9875                param.set("expression", self._parse_unquoted_field() or self._parse_bracket())
 9876
 9877            options.append(param)
 9878
 9879            if sep:
 9880                self._match(sep)
 9881
 9882        return options
 9883
 9884    def _parse_credentials(self) -> exp.Credentials | None:
 9885        expr = self.expression(exp.Credentials())
 9886
 9887        if self._match_text_seq("STORAGE_INTEGRATION", "="):
 9888            expr.set("storage", self._parse_field())
 9889        if self._match_text_seq("CREDENTIALS"):
 9890            # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string>
 9891            creds = (
 9892                self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field()
 9893            )
 9894            expr.set("credentials", creds)
 9895        if self._match_text_seq("ENCRYPTION"):
 9896            expr.set("encryption", self._parse_wrapped_options())
 9897        if self._match_text_seq("IAM_ROLE"):
 9898            expr.set(
 9899                "iam_role",
 9900                exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(),
 9901            )
 9902        if self._match_text_seq("REGION"):
 9903            expr.set("region", self._parse_field())
 9904
 9905        return expr
 9906
 9907    def _parse_file_location(self) -> exp.Expr | None:
 9908        return self._parse_field()
 9909
 9910    def _parse_copy(self) -> exp.Copy | exp.Command:
 9911        start = self._prev
 9912
 9913        self._match(TokenType.INTO)
 9914
 9915        this = (
 9916            self._parse_select(nested=True, parse_subquery_alias=False)
 9917            if self._match(TokenType.L_PAREN, advance=False)
 9918            else self._parse_table(schema=True)
 9919        )
 9920
 9921        kind = self._match(TokenType.FROM) or not self._match_text_seq("TO")
 9922
 9923        files = self._parse_csv(self._parse_file_location)
 9924        if self._match(TokenType.EQ, advance=False):
 9925            # Backtrack one token since we've consumed the lhs of a parameter assignment here.
 9926            # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter
 9927            # list via `_parse_wrapped(..)` below.
 9928            self._advance(-1)
 9929            files = []
 9930
 9931        credentials = self._parse_credentials()
 9932
 9933        self._match_text_seq("WITH")
 9934
 9935        params = self._parse_wrapped(self._parse_copy_parameters, optional=True)
 9936
 9937        # Fallback case
 9938        if self._curr:
 9939            return self._parse_as_command(start)
 9940
 9941        return self.expression(
 9942            exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params)
 9943        )
 9944
 9945    def _parse_normalize(self) -> exp.Normalize:
 9946        return self.expression(
 9947            exp.Normalize(
 9948                this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var()
 9949            )
 9950        )
 9951
 9952    def _parse_ceil_floor(self, expr_type: type[TCeilFloor]) -> TCeilFloor:
 9953        args = self._parse_csv(lambda: self._parse_lambda())
 9954
 9955        this = seq_get(args, 0)
 9956        decimals = seq_get(args, 1)
 9957
 9958        return expr_type(
 9959            this=this,
 9960            decimals=decimals,
 9961            to=self._parse_var() if self._match_text_seq("TO") else None,
 9962        )
 9963
 9964    def _parse_star_ops(self) -> exp.Expr | None:
 9965        star_token = self._prev
 9966
 9967        if self._match_text_seq("COLUMNS", "(", advance=False):
 9968            this = self._parse_function()
 9969            if isinstance(this, exp.Columns):
 9970                this.set("unpack", True)
 9971            return this
 9972
 9973        index = self._index
 9974        ilike = self._parse_string() if self._match(TokenType.ILIKE) else None
 9975        if not ilike:
 9976            # ILIKE without a string pattern is not a star filter, e.g. `* ILIKE (foo)`
 9977            self._retreat(index)
 9978
 9979        return self.expression(
 9980            exp.Star(
 9981                ilike=ilike,
 9982                except_=self._parse_star_op("EXCEPT", "EXCLUDE"),
 9983                replace=self._parse_star_op("REPLACE"),
 9984                rename=self._parse_star_op("RENAME"),
 9985            )
 9986        ).update_positions(star_token)
 9987
 9988    def _parse_grant_privilege(self) -> exp.GrantPrivilege | None:
 9989        privilege_parts = []
 9990
 9991        # Keep consuming consecutive keywords until comma (end of this privilege) or ON
 9992        # (end of privilege list) or L_PAREN (start of column list) are met
 9993        while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False):
 9994            privilege_parts.append(self._curr.text.upper())
 9995            self._advance()
 9996
 9997        this = exp.var(" ".join(privilege_parts))
 9998        expressions = (
 9999            self._parse_wrapped_csv(self._parse_column)
10000            if self._match(TokenType.L_PAREN, advance=False)
10001            else None
10002        )
10003
10004        return self.expression(exp.GrantPrivilege(this=this, expressions=expressions))
10005
10006    def _parse_grant_principal(self) -> exp.GrantPrincipal | None:
10007        kind = self._match_texts(("ROLE", "GROUP")) and self._prev.text.upper()
10008        principal = self._parse_id_var()
10009
10010        if not principal:
10011            return None
10012
10013        return self.expression(exp.GrantPrincipal(this=principal, kind=kind))
10014
10015    def _parse_grant_revoke_common(
10016        self,
10017    ) -> tuple[list | None, str | None, exp.Expr | None]:
10018        privileges = self._parse_csv(self._parse_grant_privilege)
10019
10020        self._match(TokenType.ON)
10021        kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None
10022
10023        # Attempt to parse the securable e.g. MySQL allows names
10024        # such as "foo.*", "*.*" which are not easily parseable yet
10025        securable = self._try_parse(self._parse_table_parts)
10026
10027        return privileges, kind, securable
10028
10029    def _parse_grant(self) -> exp.Grant | exp.Command:
10030        start = self._prev
10031
10032        privileges, kind, securable = self._parse_grant_revoke_common()
10033
10034        if not securable or not self._match_text_seq("TO"):
10035            return self._parse_as_command(start)
10036
10037        principals = self._parse_csv(self._parse_grant_principal)
10038
10039        grant_option = self._match_text_seq("WITH", "GRANT", "OPTION")
10040
10041        if self._curr:
10042            return self._parse_as_command(start)
10043
10044        return self.expression(
10045            exp.Grant(
10046                privileges=privileges,
10047                kind=kind,
10048                securable=securable,
10049                principals=principals,
10050                grant_option=grant_option,
10051            )
10052        )
10053
10054    def _parse_revoke(self) -> exp.Revoke | exp.Command:
10055        start = self._prev
10056
10057        grant_option = self._match_text_seq("GRANT", "OPTION", "FOR")
10058
10059        privileges, kind, securable = self._parse_grant_revoke_common()
10060
10061        if not securable or not self._match_text_seq("FROM"):
10062            return self._parse_as_command(start)
10063
10064        principals = self._parse_csv(self._parse_grant_principal)
10065
10066        cascade = None
10067        if self._match_texts(("CASCADE", "RESTRICT")):
10068            cascade = self._prev.text.upper()
10069
10070        if self._curr:
10071            return self._parse_as_command(start)
10072
10073        return self.expression(
10074            exp.Revoke(
10075                privileges=privileges,
10076                kind=kind,
10077                securable=securable,
10078                principals=principals,
10079                grant_option=grant_option,
10080                cascade=cascade,
10081            )
10082        )
10083
10084    def _parse_overlay(self) -> exp.Overlay:
10085        def _parse_overlay_arg(text: str) -> exp.Expr | None:
10086            return (
10087                self._parse_bitwise()
10088                if self._match(TokenType.COMMA) or self._match_text_seq(text)
10089                else None
10090            )
10091
10092        return self.expression(
10093            exp.Overlay(
10094                this=self._parse_bitwise(),
10095                expression=_parse_overlay_arg("PLACING"),
10096                from_=_parse_overlay_arg("FROM"),
10097                for_=_parse_overlay_arg("FOR"),
10098            )
10099        )
10100
10101    def _parse_format_name(self) -> exp.Property:
10102        # Note: Although not specified in the docs, Snowflake does accept a string/identifier
10103        # for FILE_FORMAT = <format_name>
10104        return self.expression(
10105            exp.Property(
10106                this=exp.var("FORMAT_NAME"), value=self._parse_string() or self._parse_table_parts()
10107            )
10108        )
10109
10110    def _parse_distinct_arg_function(self, func: type[F], distinct_index: int = 0) -> F:
10111        is_distinct = self._match(TokenType.DISTINCT)
10112        if not is_distinct:
10113            self._match(TokenType.ALL)
10114
10115        args = [self._parse_lambda()]
10116        if self._match(TokenType.COMMA):
10117            args.extend(self._parse_function_args())
10118
10119        target = seq_get(args, distinct_index)
10120        if is_distinct and target:
10121            args[distinct_index] = self.expression(exp.Distinct(expressions=[target]))
10122
10123        return func.from_arg_list(args)
10124
10125    def _identifier_expression(
10126        self, token: Token | None = None, quoted: bool | None = None
10127    ) -> exp.Identifier:
10128        token = token or self._prev
10129        return self.expression(exp.Identifier(this=token.text, quoted=quoted), token)
10130
10131    def _build_pipe_cte(
10132        self,
10133        query: exp.Query,
10134        expressions: list[exp.Expr],
10135        alias_cte: exp.TableAlias | None = None,
10136    ) -> exp.Select:
10137        new_cte: str | exp.TableAlias | None
10138        if alias_cte:
10139            new_cte = alias_cte
10140        else:
10141            self._pipe_cte_counter += 1
10142            new_cte = f"__tmp{self._pipe_cte_counter}"
10143
10144        with_ = query.args.get("with_")
10145        ctes = with_.pop() if with_ else None
10146
10147        new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False)
10148        if ctes:
10149            new_select.set("with_", ctes)
10150
10151        return new_select.with_(new_cte, as_=query, copy=False)
10152
10153    def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select:
10154        select = self._parse_select(consume_pipe=False)
10155        if not select:
10156            return query
10157
10158        return self._build_pipe_cte(
10159            query=query.select(*select.expressions, append=False), expressions=[exp.Star()]
10160        )
10161
10162    def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select:
10163        limit = self._parse_limit()
10164        offset = self._parse_offset()
10165        if limit:
10166            curr_limit = query.args.get("limit", limit)
10167            if curr_limit.expression.to_py() >= limit.expression.to_py():
10168                query.limit(limit, copy=False)
10169        if offset:
10170            curr_offset = query.args.get("offset")
10171            curr_offset = curr_offset.expression.to_py() if curr_offset else 0
10172            query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False)
10173
10174        return query
10175
10176    def _parse_pipe_syntax_aggregate_fields(self) -> exp.Expr | None:
10177        this = self._parse_disjunction()
10178        if self._match_text_seq("GROUP", "AND", advance=False):
10179            return this
10180
10181        this = self._parse_alias(this)
10182
10183        if self._match_set((TokenType.ASC, TokenType.DESC), advance=False):
10184            return self._parse_ordered(lambda: this)
10185
10186        return this
10187
10188    def _parse_pipe_syntax_aggregate_group_order_by(
10189        self, query: exp.Select, group_by_exists: bool = True
10190    ) -> exp.Select:
10191        expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields)
10192        aggregates_or_groups, orders = [], []
10193        for element in expr:
10194            if isinstance(element, exp.Ordered):
10195                this = element.this
10196                if isinstance(this, exp.Alias):
10197                    element.set("this", this.args["alias"])
10198                orders.append(element)
10199            else:
10200                this = element
10201            aggregates_or_groups.append(this)
10202
10203        if group_by_exists:
10204            query.select(
10205                *aggregates_or_groups, *query.expressions, append=False, copy=False
10206            ).group_by(
10207                *[projection.args.get("alias", projection) for projection in aggregates_or_groups],
10208                copy=False,
10209            )
10210        else:
10211            query.select(*aggregates_or_groups, append=False, copy=False)
10212
10213        if orders:
10214            return query.order_by(*orders, append=False, copy=False)
10215
10216        return query
10217
10218    def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select:
10219        self._match_text_seq("AGGREGATE")
10220        query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False)
10221
10222        if self._match(TokenType.GROUP_BY) or (
10223            self._match_text_seq("GROUP", "AND") and self._match(TokenType.ORDER_BY)
10224        ):
10225            query = self._parse_pipe_syntax_aggregate_group_order_by(query)
10226
10227        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10228
10229    def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> exp.Query | None:
10230        first_setop = self.parse_set_operation(this=query)
10231        if not first_setop:
10232            return None
10233
10234        def _parse_and_unwrap_query() -> exp.Expr | None:
10235            expr = self._parse_paren()
10236            return expr.assert_is(exp.Subquery).unnest() if expr else None
10237
10238        first_setop.this.pop()
10239
10240        setops = [
10241            first_setop.expression.pop().assert_is(exp.Subquery).unnest(),
10242            *self._parse_csv(_parse_and_unwrap_query),
10243        ]
10244
10245        query = self._build_pipe_cte(query=query, expressions=[exp.Star()])
10246        with_ = query.args.get("with_")
10247        ctes = with_.pop() if with_ else None
10248
10249        if isinstance(first_setop, exp.Union):
10250            query = query.union(*setops, copy=False, **first_setop.args)
10251        elif isinstance(first_setop, exp.Except):
10252            query = query.except_(*setops, copy=False, **first_setop.args)
10253        else:
10254            query = query.intersect(*setops, copy=False, **first_setop.args)
10255
10256        query.set("with_", ctes)
10257
10258        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10259
10260    def _parse_pipe_syntax_join(self, query: exp.Query) -> exp.Query | None:
10261        join = self._parse_join()
10262        if not join:
10263            return None
10264
10265        if isinstance(query, exp.Select):
10266            return query.join(join, copy=False)
10267
10268        return query
10269
10270    def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select:
10271        pivots = self._parse_pivots()
10272        if not pivots:
10273            return query
10274
10275        from_ = query.args.get("from_")
10276        if from_:
10277            from_.this.set("pivots", pivots)
10278
10279        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10280
10281    def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select:
10282        self._match_text_seq("EXTEND")
10283        query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False)
10284        return self._build_pipe_cte(query=query, expressions=[exp.Star()])
10285
10286    def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select:
10287        sample = self._parse_table_sample()
10288
10289        with_ = query.args.get("with_")
10290        if with_:
10291            with_.expressions[-1].this.set("sample", sample)
10292        else:
10293            query.set("sample", sample)
10294
10295        return query
10296
10297    def _parse_pipe_syntax_query(self, query: exp.Query) -> exp.Query | None:
10298        if isinstance(query, exp.Subquery):
10299            query = exp.select("*").from_(query, copy=False)
10300
10301        if not query.args.get("from_"):
10302            query = exp.select("*").from_(query.subquery(copy=False), copy=False)
10303
10304        while self._match(TokenType.PIPE_GT):
10305            start_index = self._index
10306            start_text = self._curr.text.upper()
10307            parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text)
10308            if not parser:
10309                # The set operators (UNION, etc) and the JOIN operator have a few common starting
10310                # keywords, making it tricky to disambiguate them without lookahead. The approach
10311                # here is to try and parse a set operation and if that fails, then try to parse a
10312                # join operator. If that fails as well, then the operator is not supported.
10313                parsed_query = self._parse_pipe_syntax_set_operator(query)
10314                parsed_query = parsed_query or self._parse_pipe_syntax_join(query)
10315                if not parsed_query:
10316                    self._retreat(start_index)
10317                    self.raise_error(f"Unsupported pipe syntax operator: '{start_text}'.")
10318                    break
10319                query = parsed_query
10320            else:
10321                query = parser(self, query)
10322
10323        return query
10324
10325    def _parse_declareitem(self) -> exp.DeclareItem | None:
10326        self._match_texts(("VAR", "VARIABLE"))
10327
10328        vars = self._parse_csv(self._parse_id_var)
10329        if not vars:
10330            return None
10331
10332        self._match(TokenType.ALIAS)
10333        kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types()
10334        default = (
10335            self._match(TokenType.DEFAULT) or self._match(TokenType.EQ)
10336        ) and self._parse_bitwise()
10337
10338        return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default))
10339
10340    def _parse_declare(self) -> exp.Declare | exp.Command:
10341        start = self._prev
10342        replace = self._match_text_seq("OR", "REPLACE")
10343        expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem))
10344
10345        if not expressions or self._curr:
10346            return self._parse_as_command(start)
10347
10348        return self.expression(exp.Declare(expressions=expressions, replace=replace))
10349
10350    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10351        exp_class = exp.Cast if strict else exp.TryCast
10352
10353        if exp_class == exp.TryCast:
10354            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10355
10356        return self.expression(exp_class(**kwargs))
10357
10358    def _parse_json_value(self) -> exp.JSONValue:
10359        this = self._parse_bitwise()
10360        self._match(TokenType.COMMA)
10361        path = self._parse_bitwise()
10362
10363        returning = self._match(TokenType.RETURNING) and self._parse_type()
10364
10365        return self.expression(
10366            exp.JSONValue(
10367                this=this,
10368                path=self.dialect.to_json_path(path),
10369                returning=returning,
10370                on_condition=self._parse_on_condition(),
10371            )
10372        )
10373
10374    def _parse_group_concat(self) -> exp.Expr | None:
10375        def concat_exprs(node: exp.Expr | None, exprs: list[exp.Expr]) -> exp.Expr:
10376            if isinstance(node, exp.Distinct) and len(node.expressions) > 1:
10377                concat_exprs = [
10378                    self.expression(
10379                        exp.Concat(
10380                            expressions=node.expressions,
10381                            safe=True,
10382                            coalesce=self.dialect.CONCAT_COALESCE,
10383                        )
10384                    )
10385                ]
10386                node.set("expressions", concat_exprs)
10387                return node
10388            if len(exprs) == 1:
10389                return exprs[0]
10390            return self.expression(
10391                exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE)
10392            )
10393
10394        args = self._parse_csv(self._parse_lambda)
10395
10396        if args:
10397            order = args[-1] if isinstance(args[-1], exp.Order) else None
10398
10399            if order:
10400                # Order By is the last (or only) expression in the list and has consumed the 'expr' before it,
10401                # remove 'expr' from exp.Order and add it back to args
10402                args[-1] = order.this
10403                order.set("this", concat_exprs(order.this, args))
10404
10405            this = order or concat_exprs(args[0], args)
10406        else:
10407            this = None
10408
10409        separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None
10410
10411        return self.expression(exp.GroupConcat(this=this, separator=separator))
10412
10413    def _parse_initcap(self) -> exp.Initcap:
10414        expr = exp.Initcap.from_arg_list(self._parse_function_args())
10415
10416        # attach dialect's default delimiters
10417        if expr.args.get("expression") is None:
10418            expr.set("expression", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS))
10419
10420        return expr
10421
10422    def _parse_operator(self, this: exp.Expr | None) -> exp.Expr | None:
10423        while True:
10424            if not self._match(TokenType.L_PAREN):
10425                break
10426
10427            op = ""
10428            while self._curr and not self._match(TokenType.R_PAREN):
10429                op += self._curr.text
10430                self._advance()
10431
10432            comments = self._prev_comments
10433            this = self.expression(
10434                exp.Operator(this=this, operator=op, expression=self._parse_bitwise()),
10435                comments=comments,
10436            )
10437
10438            if not self._match(TokenType.OPERATOR):
10439                break
10440
10441        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)
1941    def __init__(
1942        self,
1943        error_level: ErrorLevel | None = None,
1944        error_message_context: int = 100,
1945        max_errors: int = 3,
1946        max_nodes: int = -1,
1947        dialect: DialectType = None,
1948    ):
1949        self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE
1950        self.error_message_context: int = error_message_context
1951        self.max_errors: int = max_errors
1952        self.max_nodes: int = max_nodes
1953        self.dialect: t.Any = _resolve_dialect(dialect)
1954        self.sql: str = ""
1955        self.errors: list[ParseError] = []
1956        self._tokens: list[Token] = []
1957        self._tokens_size: i64 = 0
1958        self._index: i64 = 0
1959        self._curr: Token = SENTINEL_NONE
1960        self._next: Token = SENTINEL_NONE
1961        self._prev: Token = SENTINEL_NONE
1962        self._prev_comments: list[str] = []
1963        self._pipe_cte_counter: int = 0
1964        self._chunks: list[list[Token]] = []
1965        self._chunk_index: i64 = 0
1966        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_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 = {'TRAILING', 'LEADING', 'BOTH'}
IDENTIFIER_TOKENS: ClassVar[frozenset] = frozenset({<TokenType.VAR: 89>, <TokenType.IDENTIFIER: 79>})
BRACKETS: ClassVar[frozenset] = frozenset({<TokenType.L_BRACKET: 3>, <TokenType.L_BRACE: 5>})
COLUMN_POSTFIX_TOKENS: ClassVar[frozenset] = frozenset({<TokenType.L_PAREN: 1>, <TokenType.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.MOD: 329>: <class 'sqlglot.expressions.core.Mod'>, <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.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', 'FOREIGN KEY', 'UNIQUE', 'PRIMARY KEY', 'TRUNCATE', 'EXCLUDE', 'PERIOD', 'LIKE', 'BUCKET'}
NO_PAREN_FUNCTION_PARSERS: ClassVar = {'ANY': <function Parser.<lambda>>, 'CASE': <function Parser.<lambda>>, 'CONNECT_BY_ROOT': <function Parser.<lambda>>, 'IF': <function Parser.<lambda>>}
INVALID_FUNC_NAME_TOKENS: ClassVar = {<TokenType.STRING: 77>, <TokenType.IDENTIFIER: 79>}
FUNCTIONS_WITH_ALIASED_ARGS: ClassVar = {'STRUCT'}
KEY_VALUE_DEFINITIONS: ClassVar = (<class 'sqlglot.expressions.core.Alias'>, <class 'sqlglot.expressions.core.EQ'>, <class 'sqlglot.expressions.core.PropertyEQ'>, <class 'sqlglot.expressions.core.Slice'>)
FUNCTION_PARSERS: ClassVar[dict[str, Callable]] = {'ARG_MAX': <function Parser.<dictcomp>.<lambda>>, 'ARGMAX': <function Parser.<dictcomp>.<lambda>>, 'MAX_BY': <function Parser.<dictcomp>.<lambda>>, 'ARG_MIN': <function Parser.<dictcomp>.<lambda>>, 'ARGMIN': <function Parser.<dictcomp>.<lambda>>, 'MIN_BY': <function Parser.<dictcomp>.<lambda>>, 'CAST': <function Parser.<lambda>>, 'CEIL': <function Parser.<lambda>>, 'CONVERT': <function Parser.<lambda>>, 'CHAR': <function Parser.<lambda>>, 'CHR': <function Parser.<lambda>>, 'DECODE': <function Parser.<lambda>>, 'EXTRACT': <function Parser.<lambda>>, 'FLOOR': <function Parser.<lambda>>, 'GAP_FILL': <function Parser.<lambda>>, 'INITCAP': <function Parser.<lambda>>, 'JSON_OBJECT': <function Parser.<lambda>>, 'JSON_OBJECTAGG': <function Parser.<lambda>>, 'JSON_TABLE': <function Parser.<lambda>>, 'MATCH': <function Parser.<lambda>>, 'NORMALIZE': <function Parser.<lambda>>, 'OPENJSON': <function Parser.<lambda>>, 'OVERLAY': <function Parser.<lambda>>, 'POSITION': <function Parser.<lambda>>, 'SAFE_CAST': <function Parser.<lambda>>, 'STRING_AGG': <function Parser.<lambda>>, 'SUBSTRING': <function Parser.<lambda>>, 'TRIM': <function Parser.<lambda>>, 'TRY_CAST': <function Parser.<lambda>>, 'TRY_CONVERT': <function Parser.<lambda>>, 'XMLELEMENT': <function Parser.<lambda>>, 'XMLTABLE': <function Parser.<lambda>>}
QUERY_MODIFIER_PARSERS: ClassVar = {<TokenType.MATCH_RECOGNIZE: 326>: <function Parser.<lambda>>, <TokenType.PREWHERE: 361>: <function Parser.<lambda>>, <TokenType.WHERE: 429>: <function Parser.<lambda>>, <TokenType.GROUP_BY: 290>: <function Parser.<lambda>>, <TokenType.HAVING: 292>: <function Parser.<lambda>>, <TokenType.QUALIFY: 368>: <function Parser.<lambda>>, <TokenType.WINDOW: 430>: <function Parser.<lambda>>, <TokenType.ORDER_BY: 341>: <function Parser.<lambda>>, <TokenType.LIMIT: 319>: <function Parser.<lambda>>, <TokenType.FETCH: 273>: <function Parser.<lambda>>, <TokenType.OFFSET: 337>: <function Parser.<lambda>>, <TokenType.FOR: 279>: <function Parser.<lambda>>, <TokenType.LOCK: 322>: <function Parser.<lambda>>, <TokenType.TABLE_SAMPLE: 405>: <function Parser.<lambda>>, <TokenType.USING: 421>: <function Parser.<lambda>>, <TokenType.CLUSTER_BY: 235>: <function Parser.<lambda>>, <TokenType.DISTRIBUTE_BY: 263>: <function Parser.<lambda>>, <TokenType.SORT_BY: 397>: <function Parser.<lambda>>, <TokenType.CONNECT_BY: 240>: <function Parser.<lambda>>}
QUERY_MODIFIER_TOKENS: ClassVar = {<TokenType.LOCK: 322>, <TokenType.MATCH_RECOGNIZE: 326>, <TokenType.DISTRIBUTE_BY: 263>, <TokenType.SORT_BY: 397>, <TokenType.FETCH: 273>, <TokenType.OFFSET: 337>, <TokenType.ORDER_BY: 341>, <TokenType.TABLE_SAMPLE: 405>, <TokenType.FOR: 279>, <TokenType.GROUP_BY: 290>, <TokenType.HAVING: 292>, <TokenType.USING: 421>, <TokenType.PREWHERE: 361>, <TokenType.CLUSTER_BY: 235>, <TokenType.WHERE: 429>, <TokenType.WINDOW: 430>, <TokenType.QUALIFY: 368>, <TokenType.CONNECT_BY: 240>, <TokenType.LIMIT: 319>}
SET_PARSERS: ClassVar = {'GLOBAL': <function Parser.<lambda>>, 'LOCAL': <function Parser.<lambda>>, 'SESSION': <function Parser.<lambda>>, 'TRANSACTION': <function Parser.<lambda>>}
SHOW_PARSERS: ClassVar[dict[str, Callable]] = {}
TYPE_LITERAL_PARSERS: ClassVar = {<DType.JSON: 'JSON'>: <function Parser.<lambda>>}
DDL_SELECT_TOKENS: ClassVar = {<TokenType.L_PAREN: 1>, <TokenType.SELECT: 387>, <TokenType.WITH: 431>}
PRE_VOLATILE_TOKENS: ClassVar = {<TokenType.UNIQUE: 432>, <TokenType.CREATE: 243>, <TokenType.REPLACE: 375>}
TRANSACTION_KIND: ClassVar = {'EXCLUSIVE', 'DEFERRED', '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 = {'ABORT', 'IGNORE', 'FAIL', 'ROLLBACK', 'REPLACE'}
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 = {'STREAM', 'STATEMENT', 'OFFSET', 'VERSION', 'TIMESTAMP'}
OPCLASS_FOLLOW_KEYWORDS: ClassVar = {'NULLS', 'ASC', 'DESC', 'WITH'}
OPTYPE_FOLLOW_TOKENS: ClassVar = {<TokenType.R_PAREN: 2>, <TokenType.COMMA: 7>}
TABLE_INDEX_HINT_TOKENS: ClassVar = {<TokenType.FORCE: 280>, <TokenType.USE: 420>, <TokenType.IGNORE: 294>}
VIEW_ATTRIBUTES: ClassVar = {'SCHEMABINDING', 'ENCRYPTION', 'VIEW_METADATA'}
WINDOW_ALIAS_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANTI: 221>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ASOF: 226>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FULL: 284>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.IS: 308>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LEFT: 317>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.LOCK: 322>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NATURAL: 331>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PERCENT: 354>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.RIGHT: 379>, <TokenType.ROLLUP: 383>, <TokenType.ROW: 384>, <TokenType.SEMI: 388>, <TokenType.SEQUENCE: 390>, <TokenType.SET: 392>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.WINDOW: 430>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
WINDOW_BEFORE_PAREN_TOKENS: ClassVar = {<TokenType.OVER: 348>}
WINDOW_SIDES: ClassVar = {'FOLLOWING', 'PRECEDING'}
JSON_KEY_VALUE_SEPARATOR_TOKENS: ClassVar = {<TokenType.COLON: 11>, <TokenType.IS: 308>, <TokenType.COMMA: 7>}
FETCH_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANTI: 221>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ASOF: 226>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FULL: 284>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.IS: 308>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LEFT: 317>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.LOCK: 322>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NATURAL: 331>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RANGE: 371>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.RIGHT: 379>, <TokenType.ROLLUP: 383>, <TokenType.SEMI: 388>, <TokenType.SEQUENCE: 390>, <TokenType.SET: 392>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.WINDOW: 430>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
ADD_CONSTRAINT_TOKENS: ClassVar = {<TokenType.FOREIGN_KEY: 281>, <TokenType.INDEX: 297>, <TokenType.PRIMARY_KEY: 362>, <TokenType.UNIQUE: 432>, <TokenType.CONSTRAINT: 241>, <TokenType.KEY: 313>}
DISTINCT_TOKENS: ClassVar = {<TokenType.DISTINCT: 262>}
UNNEST_OFFSET_ALIAS_TOKENS: ClassVar = {<TokenType.SESSION: 59>, <TokenType.SESSION_USER: 61>, <TokenType.IDENTIFIER: 79>, <TokenType.DATABASE: 80>, <TokenType.COLUMN: 81>, <TokenType.SCHEMA: 83>, <TokenType.TABLE: 84>, <TokenType.WAREHOUSE: 85>, <TokenType.STAGE: 86>, <TokenType.STREAM: 87>, <TokenType.STREAMLIT: 88>, <TokenType.VAR: 89>, <TokenType.BIT: 97>, <TokenType.BOOLEAN: 98>, <TokenType.TINYINT: 99>, <TokenType.UTINYINT: 100>, <TokenType.SMALLINT: 101>, <TokenType.USMALLINT: 102>, <TokenType.MEDIUMINT: 103>, <TokenType.UMEDIUMINT: 104>, <TokenType.INT: 105>, <TokenType.UINT: 106>, <TokenType.BIGINT: 107>, <TokenType.UBIGINT: 108>, <TokenType.BIGNUM: 109>, <TokenType.INT128: 110>, <TokenType.UINT128: 111>, <TokenType.INT256: 112>, <TokenType.UINT256: 113>, <TokenType.FLOAT: 114>, <TokenType.DOUBLE: 115>, <TokenType.UDOUBLE: 116>, <TokenType.DECIMAL: 117>, <TokenType.DECIMAL32: 118>, <TokenType.DECIMAL64: 119>, <TokenType.DECIMAL128: 120>, <TokenType.DECIMAL256: 121>, <TokenType.DECFLOAT: 122>, <TokenType.UDECIMAL: 123>, <TokenType.BIGDECIMAL: 124>, <TokenType.CHAR: 125>, <TokenType.NCHAR: 126>, <TokenType.VARCHAR: 127>, <TokenType.NVARCHAR: 128>, <TokenType.BPCHAR: 129>, <TokenType.TEXT: 130>, <TokenType.MEDIUMTEXT: 131>, <TokenType.LONGTEXT: 132>, <TokenType.BLOB: 133>, <TokenType.MEDIUMBLOB: 134>, <TokenType.LONGBLOB: 135>, <TokenType.TINYBLOB: 136>, <TokenType.TINYTEXT: 137>, <TokenType.NAME: 138>, <TokenType.BINARY: 139>, <TokenType.VARBINARY: 140>, <TokenType.JSON: 141>, <TokenType.JSONB: 142>, <TokenType.TIME: 143>, <TokenType.TIMETZ: 144>, <TokenType.TIME_NS: 145>, <TokenType.TIMESTAMP: 146>, <TokenType.TIMESTAMPTZ: 147>, <TokenType.TIMESTAMPLTZ: 148>, <TokenType.TIMESTAMPNTZ: 149>, <TokenType.TIMESTAMP_S: 150>, <TokenType.TIMESTAMP_MS: 151>, <TokenType.TIMESTAMP_NS: 152>, <TokenType.DATETIME: 153>, <TokenType.DATETIME2: 154>, <TokenType.DATETIME64: 155>, <TokenType.SMALLDATETIME: 156>, <TokenType.DATE: 157>, <TokenType.DATE32: 158>, <TokenType.INT4RANGE: 159>, <TokenType.INT4MULTIRANGE: 160>, <TokenType.INT8RANGE: 161>, <TokenType.INT8MULTIRANGE: 162>, <TokenType.NUMRANGE: 163>, <TokenType.NUMMULTIRANGE: 164>, <TokenType.TSRANGE: 165>, <TokenType.TSMULTIRANGE: 166>, <TokenType.TSTZRANGE: 167>, <TokenType.TSTZMULTIRANGE: 168>, <TokenType.DATERANGE: 169>, <TokenType.DATEMULTIRANGE: 170>, <TokenType.UUID: 171>, <TokenType.GEOGRAPHY: 172>, <TokenType.GEOGRAPHYPOINT: 173>, <TokenType.NULLABLE: 174>, <TokenType.GEOMETRY: 175>, <TokenType.POINT: 176>, <TokenType.RING: 177>, <TokenType.LINESTRING: 178>, <TokenType.LOCALTIME: 179>, <TokenType.LOCALTIMESTAMP: 180>, <TokenType.MULTILINESTRING: 182>, <TokenType.POLYGON: 183>, <TokenType.MULTIPOLYGON: 184>, <TokenType.HLLSKETCH: 185>, <TokenType.HSTORE: 186>, <TokenType.SUPER: 187>, <TokenType.SERIAL: 188>, <TokenType.SMALLSERIAL: 189>, <TokenType.BIGSERIAL: 190>, <TokenType.XML: 191>, <TokenType.YEAR: 192>, <TokenType.USERDEFINED: 193>, <TokenType.MONEY: 194>, <TokenType.SMALLMONEY: 195>, <TokenType.ROWVERSION: 196>, <TokenType.IMAGE: 197>, <TokenType.VARIANT: 198>, <TokenType.OBJECT: 199>, <TokenType.INET: 200>, <TokenType.IPADDRESS: 201>, <TokenType.IPPREFIX: 202>, <TokenType.IPV4: 203>, <TokenType.IPV6: 204>, <TokenType.ENUM: 205>, <TokenType.ENUM8: 206>, <TokenType.ENUM16: 207>, <TokenType.FIXEDSTRING: 208>, <TokenType.LOWCARDINALITY: 209>, <TokenType.NESTED: 210>, <TokenType.AGGREGATEFUNCTION: 211>, <TokenType.SIMPLEAGGREGATEFUNCTION: 212>, <TokenType.TDIGEST: 213>, <TokenType.UNKNOWN: 214>, <TokenType.VECTOR: 215>, <TokenType.DYNAMIC: 216>, <TokenType.VOID: 217>, <TokenType.ALL: 220>, <TokenType.ANY: 222>, <TokenType.APPLY: 223>, <TokenType.ARRAY: 224>, <TokenType.ASC: 225>, <TokenType.ATTACH: 227>, <TokenType.AUTO_INCREMENT: 228>, <TokenType.BEGIN: 229>, <TokenType.CACHE: 232>, <TokenType.CASE: 233>, <TokenType.COLLATE: 236>, <TokenType.COMMAND: 237>, <TokenType.COMMENT: 238>, <TokenType.COMMIT: 239>, <TokenType.CONSTRAINT: 241>, <TokenType.COPY: 242>, <TokenType.CUBE: 245>, <TokenType.CURRENT_DATE: 246>, <TokenType.CURRENT_DATETIME: 247>, <TokenType.CURRENT_SCHEMA: 248>, <TokenType.CURRENT_TIME: 249>, <TokenType.CURRENT_TIMESTAMP: 250>, <TokenType.CURRENT_USER: 251>, <TokenType.CURRENT_ROLE: 253>, <TokenType.CURRENT_CATALOG: 254>, <TokenType.DECLARE: 255>, <TokenType.DEFAULT: 256>, <TokenType.DELETE: 257>, <TokenType.DESC: 258>, <TokenType.DESCRIBE: 259>, <TokenType.DETACH: 260>, <TokenType.DICTIONARY: 261>, <TokenType.DIV: 264>, <TokenType.END: 267>, <TokenType.ESCAPE: 268>, <TokenType.EXECUTE: 270>, <TokenType.EXISTS: 271>, <TokenType.FALSE: 272>, <TokenType.FILE: 274>, <TokenType.FILE_FORMAT: 275>, <TokenType.FILTER: 276>, <TokenType.FINAL: 277>, <TokenType.FIRST: 278>, <TokenType.FOREIGN_KEY: 281>, <TokenType.FORMAT: 282>, <TokenType.FUNCTION: 285>, <TokenType.GET: 286>, <TokenType.INDEX: 297>, <TokenType.INTERVAL: 304>, <TokenType.IS: 308>, <TokenType.ISNULL: 309>, <TokenType.KEEP: 312>, <TokenType.KILL: 314>, <TokenType.LIMIT: 319>, <TokenType.LIST: 320>, <TokenType.LOAD: 321>, <TokenType.MAP: 323>, <TokenType.MATCH: 324>, <TokenType.MERGE: 328>, <TokenType.MODEL: 330>, <TokenType.NEXT: 332>, <TokenType.NOTHING: 333>, <TokenType.NULL: 335>, <TokenType.OBJECT_IDENTIFIER: 336>, <TokenType.OFFSET: 337>, <TokenType.OPERATOR: 340>, <TokenType.ORDINALITY: 344>, <TokenType.OUT: 345>, <TokenType.INOUT: 346>, <TokenType.OVER: 348>, <TokenType.OVERLAPS: 349>, <TokenType.OVERWRITE: 350>, <TokenType.PARTITION: 352>, <TokenType.PERCENT: 354>, <TokenType.PIVOT: 355>, <TokenType.PRAGMA: 360>, <TokenType.PROCEDURE: 363>, <TokenType.PROJECTION: 365>, <TokenType.PSEUDO_TYPE: 366>, <TokenType.PUT: 367>, <TokenType.RANGE: 371>, <TokenType.RECURSIVE: 372>, <TokenType.REFRESH: 373>, <TokenType.RENAME: 374>, <TokenType.REPLACE: 375>, <TokenType.REFERENCES: 378>, <TokenType.ROLLUP: 383>, <TokenType.ROW: 384>, <TokenType.ROWS: 385>, <TokenType.SEQUENCE: 390>, <TokenType.SET: 392>, <TokenType.SETTINGS: 393>, <TokenType.SHOW: 394>, <TokenType.SOME: 396>, <TokenType.STORAGE_INTEGRATION: 401>, <TokenType.STRAIGHT_JOIN: 402>, <TokenType.STRUCT: 403>, <TokenType.TAG: 406>, <TokenType.TEMPORARY: 407>, <TokenType.TOP: 408>, <TokenType.TRUE: 410>, <TokenType.TRUNCATE: 411>, <TokenType.TRIGGER: 412>, <TokenType.TYPE: 413>, <TokenType.UNNEST: 417>, <TokenType.UNPIVOT: 418>, <TokenType.UPDATE: 419>, <TokenType.USE: 420>, <TokenType.VIEW: 424>, <TokenType.SEMANTIC_VIEW: 425>, <TokenType.VOLATILE: 426>, <TokenType.UNIQUE: 432>, <TokenType.SINK: 437>, <TokenType.SOURCE: 438>, <TokenType.ANALYZE: 439>, <TokenType.NAMESPACE: 440>, <TokenType.EXPORT: 441>}
SELECT_START_TOKENS: ClassVar = {<TokenType.L_PAREN: 1>, <TokenType.SELECT: 387>, <TokenType.WITH: 431>}
COPY_INTO_VARLEN_OPTIONS: ClassVar = {'FORMAT_OPTIONS', 'FILE_FORMAT', 'CREDENTIAL', 'COPY_OPTIONS'}
IS_JSON_PREDICATE_KIND: ClassVar = {'VALUE', 'OBJECT', 'ARRAY', 'SCALAR'}
ODBC_DATETIME_LITERALS: ClassVar[dict[str, type[sqlglot.expressions.core.Expr]]] = {}
ON_CONDITION_TOKENS: ClassVar = {'TRUE', 'ERROR', 'EMPTY', 'NULL', 'FALSE'}
PRIVILEGE_FOLLOW_TOKENS: ClassVar = {<TokenType.L_PAREN: 1>, <TokenType.ON: 338>, <TokenType.COMMA: 7>}
DESCRIBE_STYLES: ClassVar = {'EXTENDED', 'HISTORY', 'FORMATTED', 'ANALYZE'}
SET_ASSIGNMENT_DELIMITERS: ClassVar = {'TO', ':=', '='}
ANALYZE_STYLES: ClassVar = {'BUFFER_USAGE_LIMIT', 'VERBOSE', 'SAMPLE', 'NO_WRITE_TO_BINLOG', 'FULL', 'SKIP_LOCKED', 'LOCAL'}
ANALYZE_EXPRESSION_PARSERS: ClassVar = {'ALL': <function Parser.<lambda>>, 'COMPUTE': <function Parser.<lambda>>, 'DELETE': <function Parser.<lambda>>, 'DROP': <function Parser.<lambda>>, 'ESTIMATE': <function Parser.<lambda>>, 'LIST': <function Parser.<lambda>>, 'PREDICATE': <function Parser.<lambda>>, 'UPDATE': <function Parser.<lambda>>, 'VALIDATE': <function Parser.<lambda>>}
PARTITION_KEYWORDS: ClassVar = {'PARTITION', 'SUBPARTITION'}
AMBIGUOUS_ALIAS_TOKENS: ClassVar = (<TokenType.LIMIT: 319>, <TokenType.OFFSET: 337>)
OPERATION_MODIFIERS: ClassVar[set[str]] = set()
RECURSIVE_CTE_SEARCH_KIND: ClassVar = {'CYCLE', 'DEPTH', 'BREADTH'}
SECURITY_PROPERTY_KEYWORDS: ClassVar = {'INVOKER', 'NONE', 'DEFINER'}
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
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
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:
1968    def reset(self) -> None:
1969        self.sql = ""
1970        self.errors = []
1971        self._tokens = []
1972        self._tokens_size = 0
1973        self._index = 0
1974        self._curr = SENTINEL_NONE
1975        self._next = SENTINEL_NONE
1976        self._prev = SENTINEL_NONE
1977        self._prev_comments = []
1978        self._pipe_cte_counter = 0
1979        self._chunks = []
1980        self._chunk_index = 0
1981        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:
2074    def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None:
2075        token = token or self._curr or self._prev or Token.string("")
2076        formatted_sql, start_context, highlight, end_context = highlight_sql(
2077            sql=self.sql,
2078            positions=[(token.start, token.end)],
2079            context_length=self.error_message_context,
2080        )
2081        formatted_message = f"{message}. Line {token.line}, Col: {token.col}.\n  {formatted_sql}"
2082
2083        error = ParseError.new(
2084            formatted_message,
2085            description=message,
2086            line=token.line,
2087            col=token.col,
2088            start_context=start_context,
2089            highlight=highlight,
2090            end_context=end_context,
2091        )
2092
2093        if self.error_level == ErrorLevel.IMMEDIATE:
2094            raise error
2095
2096        self.errors.append(error)
def validate_expression(self, expression: ~E, args: list | None = None) -> ~E:
2098    def validate_expression(self, expression: E, args: list | None = None) -> E:
2099        if self.max_nodes > -1:
2100            self._node_count += 1
2101            if self._node_count > self.max_nodes:
2102                self.raise_error(f"Maximum number of AST nodes ({self.max_nodes}) exceeded")
2103        if self.error_level != ErrorLevel.IGNORE:
2104            for error_message in expression.error_messages(args):
2105                self.raise_error(error_message)
2106        return expression
def parse( self, raw_tokens: list[sqlglot.tokenizer_core.Token], sql: str) -> list[sqlglot.expressions.core.Expr | None]:
2125    def parse(self, raw_tokens: list[Token], sql: str) -> list[exp.Expr | None]:
2126        """
2127        Parses a list of tokens and returns a list of syntax trees, one tree
2128        per parsed SQL statement.
2129
2130        Args:
2131            raw_tokens: The list of tokens.
2132            sql: The original SQL string.
2133
2134        Returns:
2135            The list of the produced syntax trees.
2136        """
2137        return self._parse(
2138            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql
2139        )

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

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:
2188    def expression(
2189        self,
2190        instance: E,
2191        token: Token | None = None,
2192        comments: list[str] | None = None,
2193    ) -> E:
2194        if token:
2195            instance.update_positions(token)
2196        instance.add_comments(comments) if comments else self._add_comments(instance)
2197        if not instance.is_primitive:
2198            instance = self.validate_expression(instance)
2199        return instance
def parse_set_operation( self, this: sqlglot.expressions.core.Expr | None, consume_pipe: bool = False) -> sqlglot.expressions.core.Expr | None:
5890    def parse_set_operation(
5891        self, this: exp.Expr | None, consume_pipe: bool = False
5892    ) -> exp.Expr | None:
5893        start = self._index
5894        _, side_token, kind_token = self._parse_join_parts()
5895
5896        side = side_token.text if side_token else None
5897        kind = kind_token.text if kind_token else None
5898
5899        if not self._match_set(self.SET_OPERATIONS):
5900            self._retreat(start)
5901            return None
5902
5903        token_type = self._prev.token_type
5904
5905        if token_type == TokenType.UNION:
5906            operation: type[exp.SetOperation] = exp.Union
5907        elif token_type == TokenType.EXCEPT:
5908            operation = exp.Except
5909        else:
5910            operation = exp.Intersect
5911
5912        comments = self._prev.comments
5913
5914        if self._match(TokenType.DISTINCT):
5915            distinct: bool | None = True
5916        elif self._match(TokenType.ALL):
5917            distinct = False
5918        else:
5919            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]
5920            if distinct is None:
5921                self.raise_error(f"Expected DISTINCT or ALL for {operation.__name__}")
5922
5923        by_name = (
5924            self._match_text_seq("BY", "NAME")
5925            or self._match_text_seq("STRICT", "CORRESPONDING")
5926            or None
5927        )
5928        if self._match_text_seq("CORRESPONDING"):
5929            by_name = True
5930            if not side and not kind:
5931                kind = "INNER"
5932
5933        on_column_list = None
5934        if by_name and self._match_texts(("ON", "BY")):
5935            on_column_list = self._parse_wrapped_csv(self._parse_column)
5936
5937        expression = self._parse_select(
5938            nested=True, parse_set_operation=False, consume_pipe=consume_pipe
5939        )
5940
5941        # Wrap VALUES operands in selects, both for consistency with the CTE canonicalization
5942        # in _parse_cte and so that alias pushdown can reach into set operation branches
5943        if isinstance(this, exp.Values):
5944            this = self._values_to_select(this)
5945        if isinstance(expression, exp.Values):
5946            expression = self._values_to_select(expression)
5947
5948        return self.expression(
5949            operation(
5950                this=this,
5951                distinct=distinct,
5952                by_name=by_name,
5953                expression=expression,
5954                side=side,
5955                kind=kind,
5956                on=on_column_list,
5957            ),
5958            comments=comments,
5959        )
def build_cast(self, strict: bool, **kwargs) -> sqlglot.expressions.core.Expr:
10350    def build_cast(self, strict: bool, **kwargs) -> exp.Expr:
10351        exp_class = exp.Cast if strict else exp.TryCast
10352
10353        if exp_class == exp.TryCast:
10354            kwargs["requires_string"] = self.dialect.TRY_CAST_REQUIRES_STRING
10355
10356        return self.expression(exp_class(**kwargs))