summaryrefslogtreecommitdiffstats
path: root/sqlglot/parser.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-03-03 14:11:07 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-03-03 14:11:07 +0000
commit42a1548cecf48d18233f56e3385cf9c89abcb9c2 (patch)
tree5e0fff4ecbd1fd7dd1022a7580139038df2a824c /sqlglot/parser.py
parentReleasing debian version 21.1.2-1. (diff)
downloadsqlglot-42a1548cecf48d18233f56e3385cf9c89abcb9c2.tar.xz
sqlglot-42a1548cecf48d18233f56e3385cf9c89abcb9c2.zip
Merging upstream version 22.2.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sqlglot/parser.py')
-rw-r--r--sqlglot/parser.py340
1 files changed, 251 insertions, 89 deletions
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 4e7f870..49dac2e 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -17,6 +17,8 @@ if t.TYPE_CHECKING:
logger = logging.getLogger("sqlglot")
+OPTIONS_TYPE = t.Dict[str, t.Sequence[t.Union[t.Sequence[str], str]]]
+
def build_var_map(args: t.List) -> exp.StarMap | exp.VarMap:
if len(args) == 1 and args[0].is_star:
@@ -367,6 +369,7 @@ class Parser(metaclass=_Parser):
TokenType.TEMPORARY,
TokenType.TOP,
TokenType.TRUE,
+ TokenType.TRUNCATE,
TokenType.UNIQUE,
TokenType.UNPIVOT,
TokenType.UPDATE,
@@ -435,6 +438,7 @@ class Parser(metaclass=_Parser):
TokenType.TABLE,
TokenType.TIMESTAMP,
TokenType.TIMESTAMPTZ,
+ TokenType.TRUNCATE,
TokenType.WINDOW,
TokenType.XOR,
*TYPE_TOKENS,
@@ -578,7 +582,7 @@ class Parser(metaclass=_Parser):
exp.Column: lambda self: self._parse_column(),
exp.Condition: lambda self: self._parse_conjunction(),
exp.DataType: lambda self: self._parse_types(allow_identifiers=False),
- exp.Expression: lambda self: self._parse_statement(),
+ exp.Expression: lambda self: self._parse_expression(),
exp.From: lambda self: self._parse_from(),
exp.Group: lambda self: self._parse_group(),
exp.Having: lambda self: self._parse_having(),
@@ -625,10 +629,10 @@ class Parser(metaclass=_Parser):
TokenType.SET: lambda self: self._parse_set(),
TokenType.UNCACHE: lambda self: self._parse_uncache(),
TokenType.UPDATE: lambda self: self._parse_update(),
+ TokenType.TRUNCATE: lambda self: self._parse_truncate_table(),
TokenType.USE: lambda self: self.expression(
exp.Use,
- kind=self._match_texts(("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA"))
- and exp.var(self._prev.text),
+ kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False),
this=self._parse_table(schema=False),
),
}
@@ -642,36 +646,44 @@ class Parser(metaclass=_Parser):
TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt, this=self._parse_unary()),
}
- PRIMARY_PARSERS = {
- TokenType.STRING: lambda self, token: self.expression(
- exp.Literal, this=token.text, is_string=True
- ),
- TokenType.NUMBER: lambda self, token: self.expression(
- exp.Literal, this=token.text, is_string=False
- ),
- TokenType.STAR: lambda self, _: self.expression(
- exp.Star, **{"except": self._parse_except(), "replace": self._parse_replace()}
+ STRING_PARSERS = {
+ TokenType.HEREDOC_STRING: lambda self, token: self.expression(
+ exp.RawString, this=token.text
),
- TokenType.NULL: lambda self, _: self.expression(exp.Null),
- TokenType.TRUE: lambda self, _: self.expression(exp.Boolean, this=True),
- TokenType.FALSE: lambda self, _: self.expression(exp.Boolean, this=False),
- TokenType.BIT_STRING: lambda self, token: self.expression(exp.BitString, this=token.text),
- TokenType.HEX_STRING: lambda self, token: self.expression(exp.HexString, this=token.text),
- TokenType.BYTE_STRING: lambda self, token: self.expression(exp.ByteString, this=token.text),
- TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token),
TokenType.NATIONAL_STRING: lambda self, token: self.expression(
exp.National, this=token.text
),
TokenType.RAW_STRING: lambda self, token: self.expression(exp.RawString, this=token.text),
- TokenType.HEREDOC_STRING: lambda self, token: self.expression(
- exp.RawString, this=token.text
+ TokenType.STRING: lambda self, token: self.expression(
+ exp.Literal, this=token.text, is_string=True
),
TokenType.UNICODE_STRING: lambda self, token: self.expression(
exp.UnicodeString,
this=token.text,
escape=self._match_text_seq("UESCAPE") and self._parse_string(),
),
+ }
+
+ NUMERIC_PARSERS = {
+ TokenType.BIT_STRING: lambda self, token: self.expression(exp.BitString, this=token.text),
+ TokenType.BYTE_STRING: lambda self, token: self.expression(exp.ByteString, this=token.text),
+ TokenType.HEX_STRING: lambda self, token: self.expression(exp.HexString, this=token.text),
+ TokenType.NUMBER: lambda self, token: self.expression(
+ exp.Literal, this=token.text, is_string=False
+ ),
+ }
+
+ PRIMARY_PARSERS = {
+ **STRING_PARSERS,
+ **NUMERIC_PARSERS,
+ TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token),
+ TokenType.NULL: lambda self, _: self.expression(exp.Null),
+ TokenType.TRUE: lambda self, _: self.expression(exp.Boolean, this=True),
+ TokenType.FALSE: lambda self, _: self.expression(exp.Boolean, this=False),
TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(),
+ TokenType.STAR: lambda self, _: self.expression(
+ exp.Star, **{"except": self._parse_except(), "replace": self._parse_replace()}
+ ),
}
PLACEHOLDER_PARSERS = {
@@ -799,7 +811,9 @@ class Parser(metaclass=_Parser):
exp.CharacterSetColumnConstraint, this=self._parse_var_or_string()
),
"CHECK": lambda self: self.expression(
- exp.CheckColumnConstraint, this=self._parse_wrapped(self._parse_conjunction)
+ exp.CheckColumnConstraint,
+ this=self._parse_wrapped(self._parse_conjunction),
+ enforced=self._match_text_seq("ENFORCED"),
),
"COLLATE": lambda self: self.expression(
exp.CollateColumnConstraint, this=self._parse_var()
@@ -873,6 +887,8 @@ class Parser(metaclass=_Parser):
FUNCTIONS_WITH_ALIASED_ARGS = {"STRUCT"}
+ KEY_VALUE_DEFINITIONS = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice)
+
FUNCTION_PARSERS = {
"CAST": lambda self: self._parse_cast(self.STRICT_CAST),
"CONVERT": lambda self: self._parse_convert(self.STRICT_CAST),
@@ -895,6 +911,7 @@ class Parser(metaclass=_Parser):
QUERY_MODIFIER_PARSERS = {
TokenType.MATCH_RECOGNIZE: lambda self: ("match", self._parse_match_recognize()),
+ TokenType.PREWHERE: lambda self: ("prewhere", self._parse_prewhere()),
TokenType.WHERE: lambda self: ("where", self._parse_where()),
TokenType.GROUP_BY: lambda self: ("group", self._parse_group()),
TokenType.HAVING: lambda self: ("having", self._parse_having()),
@@ -934,22 +951,23 @@ class Parser(metaclass=_Parser):
exp.DataType.Type.JSON: lambda self, this, _: self.expression(exp.ParseJSON, this=this),
}
- MODIFIABLES = (exp.Subquery, exp.Subqueryable, exp.Table)
-
DDL_SELECT_TOKENS = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN}
PRE_VOLATILE_TOKENS = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE}
TRANSACTION_KIND = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"}
- TRANSACTION_CHARACTERISTICS = {
- "ISOLATION LEVEL REPEATABLE READ",
- "ISOLATION LEVEL READ COMMITTED",
- "ISOLATION LEVEL READ UNCOMMITTED",
- "ISOLATION LEVEL SERIALIZABLE",
- "READ WRITE",
- "READ ONLY",
+ TRANSACTION_CHARACTERISTICS: OPTIONS_TYPE = {
+ "ISOLATION": (
+ ("LEVEL", "REPEATABLE", "READ"),
+ ("LEVEL", "READ", "COMMITTED"),
+ ("LEVEL", "READ", "UNCOMITTED"),
+ ("LEVEL", "SERIALIZABLE"),
+ ),
+ "READ": ("WRITE", "ONLY"),
}
+ USABLES: OPTIONS_TYPE = dict.fromkeys(("ROLE", "WAREHOUSE", "DATABASE", "SCHEMA"), tuple())
+
INSERT_ALTERNATIVES = {"ABORT", "FAIL", "IGNORE", "REPLACE", "ROLLBACK"}
CLONE_KEYWORDS = {"CLONE", "COPY"}
@@ -1012,6 +1030,9 @@ class Parser(metaclass=_Parser):
# If this is True and '(' is not found, the keyword will be treated as an identifier
VALUES_FOLLOWED_BY_PAREN = True
+ # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift)
+ SUPPORTS_IMPLICIT_UNNEST = False
+
__slots__ = (
"error_level",
"error_message_context",
@@ -2450,10 +2471,37 @@ class Parser(metaclass=_Parser):
alias=self._parse_table_alias() if parse_alias else None,
)
+ def _implicit_unnests_to_explicit(self, this: E) -> E:
+ from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm
+
+ refs = {_norm(this.args["from"].this.copy(), dialect=self.dialect).alias_or_name}
+ for i, join in enumerate(this.args.get("joins") or []):
+ table = join.this
+ normalized_table = table.copy()
+ normalized_table.meta["maybe_column"] = True
+ normalized_table = _norm(normalized_table, dialect=self.dialect)
+
+ if isinstance(table, exp.Table) and not join.args.get("on"):
+ if normalized_table.parts[0].name in refs:
+ table_as_column = table.to_column()
+ unnest = exp.Unnest(expressions=[table_as_column])
+
+ # Table.to_column creates a parent Alias node that we want to convert to
+ # a TableAlias and attach to the Unnest, so it matches the parser's output
+ if isinstance(table.args.get("alias"), exp.TableAlias):
+ table_as_column.replace(table_as_column.this)
+ exp.alias_(unnest, None, table=[table.args["alias"].this], copy=False)
+
+ table.replace(unnest)
+
+ refs.add(normalized_table.alias_or_name)
+
+ return this
+
def _parse_query_modifiers(
self, this: t.Optional[exp.Expression]
) -> t.Optional[exp.Expression]:
- if isinstance(this, self.MODIFIABLES):
+ if isinstance(this, (exp.Query, exp.Table)):
for join in iter(self._parse_join, None):
this.append("joins", join)
for lateral in iter(self._parse_lateral, None):
@@ -2478,6 +2526,10 @@ class Parser(metaclass=_Parser):
offset.set("expressions", limit_by_expressions)
continue
break
+
+ if self.SUPPORTS_IMPLICIT_UNNEST and this and "from" in this.args:
+ this = self._implicit_unnests_to_explicit(this)
+
return this
def _parse_hint(self) -> t.Optional[exp.Hint]:
@@ -2803,7 +2855,9 @@ class Parser(metaclass=_Parser):
or self._parse_placeholder()
)
- def _parse_table_parts(self, schema: bool = False, is_db_reference: bool = False) -> exp.Table:
+ def _parse_table_parts(
+ self, schema: bool = False, is_db_reference: bool = False, wildcard: bool = False
+ ) -> exp.Table:
catalog = None
db = None
table: t.Optional[exp.Expression | str] = self._parse_table_part(schema=schema)
@@ -2817,8 +2871,20 @@ class Parser(metaclass=_Parser):
else:
catalog = db
db = table
+ # "" used for tsql FROM a..b case
table = self._parse_table_part(schema=schema) or ""
+ if (
+ wildcard
+ and self._is_connected()
+ and (isinstance(table, exp.Identifier) or not table)
+ and self._match(TokenType.STAR)
+ ):
+ if isinstance(table, exp.Identifier):
+ table.args["this"] += "*"
+ else:
+ table = exp.Identifier(this="*")
+
if is_db_reference:
catalog = db
db = table
@@ -2861,6 +2927,9 @@ class Parser(metaclass=_Parser):
bracket = parse_bracket and self._parse_bracket(None)
bracket = self.expression(exp.Table, this=bracket) if bracket else None
+
+ only = self._match(TokenType.ONLY)
+
this = t.cast(
exp.Expression,
bracket
@@ -2869,6 +2938,12 @@ class Parser(metaclass=_Parser):
),
)
+ if only:
+ this.set("only", only)
+
+ # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context
+ self._match_text_seq("*")
+
if schema:
return self._parse_schema(this=this)
@@ -3161,6 +3236,14 @@ class Parser(metaclass=_Parser):
def _pivot_column_names(self, aggregations: t.List[exp.Expression]) -> t.List[str]:
return [agg.alias for agg in aggregations]
+ def _parse_prewhere(self, skip_where_token: bool = False) -> t.Optional[exp.PreWhere]:
+ if not skip_where_token and not self._match(TokenType.PREWHERE):
+ return None
+
+ return self.expression(
+ exp.PreWhere, comments=self._prev_comments, this=self._parse_conjunction()
+ )
+
def _parse_where(self, skip_where_token: bool = False) -> t.Optional[exp.Where]:
if not skip_where_token and not self._match(TokenType.WHERE):
return None
@@ -3291,8 +3374,12 @@ class Parser(metaclass=_Parser):
return None
return self.expression(exp_class, expressions=self._parse_csv(self._parse_ordered))
- def _parse_ordered(self, parse_method: t.Optional[t.Callable] = None) -> exp.Ordered:
+ def _parse_ordered(
+ self, parse_method: t.Optional[t.Callable] = None
+ ) -> t.Optional[exp.Ordered]:
this = parse_method() if parse_method else self._parse_conjunction()
+ if not this:
+ return None
asc = self._match(TokenType.ASC)
desc = self._match(TokenType.DESC) or (asc and False)
@@ -3510,7 +3597,7 @@ class Parser(metaclass=_Parser):
if self._match_text_seq("DISTINCT", "FROM"):
klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ
- return self.expression(klass, this=this, expression=self._parse_conjunction())
+ return self.expression(klass, this=this, expression=self._parse_bitwise())
expression = self._parse_null() or self._parse_boolean()
if not expression:
@@ -3528,7 +3615,7 @@ class Parser(metaclass=_Parser):
matched_l_paren = self._prev.token_type == TokenType.L_PAREN
expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias))
- if len(expressions) == 1 and isinstance(expressions[0], exp.Subqueryable):
+ if len(expressions) == 1 and isinstance(expressions[0], exp.Query):
this = self.expression(exp.In, this=this, query=expressions[0])
else:
this = self.expression(exp.In, this=this, expressions=expressions)
@@ -3959,7 +4046,7 @@ class Parser(metaclass=_Parser):
this = self._parse_query_modifiers(seq_get(expressions, 0))
- if isinstance(this, exp.Subqueryable):
+ if isinstance(this, exp.UNWRAPPED_QUERIES):
this = self._parse_set_operations(
self._parse_subquery(this=this, parse_alias=False)
)
@@ -4064,6 +4151,9 @@ class Parser(metaclass=_Parser):
alias = upper in self.FUNCTIONS_WITH_ALIASED_ARGS
args = self._parse_csv(lambda: self._parse_lambda(alias=alias))
+ if alias:
+ args = self._kv_to_prop_eq(args)
+
if function and not anonymous:
if "dialect" in function.__code__.co_varnames:
func = function(args, dialect=self.dialect)
@@ -4076,6 +4166,8 @@ class Parser(metaclass=_Parser):
this = func
else:
+ if token_type == TokenType.IDENTIFIER:
+ this = exp.Identifier(this=this, quoted=True)
this = self.expression(exp.Anonymous, this=this, expressions=args)
if isinstance(this, exp.Expression):
@@ -4084,6 +4176,26 @@ class Parser(metaclass=_Parser):
self._match_r_paren(this)
return self._parse_window(this)
+ def _kv_to_prop_eq(self, expressions: t.List[exp.Expression]) -> t.List[exp.Expression]:
+ transformed = []
+
+ for e in expressions:
+ if isinstance(e, self.KEY_VALUE_DEFINITIONS):
+ if isinstance(e, exp.Alias):
+ e = self.expression(exp.PropertyEQ, this=e.args.get("alias"), expression=e.this)
+
+ if not isinstance(e, exp.PropertyEQ):
+ e = self.expression(
+ exp.PropertyEQ, this=exp.to_identifier(e.name), expression=e.expression
+ )
+
+ if isinstance(e.this, exp.Column):
+ e.this.replace(e.this.this)
+
+ transformed.append(e)
+
+ return transformed
+
def _parse_function_parameter(self) -> t.Optional[exp.Expression]:
return self._parse_column_def(self._parse_id_var())
@@ -4496,7 +4608,7 @@ class Parser(metaclass=_Parser):
# https://duckdb.org/docs/sql/data_types/struct.html#creating-structs
if bracket_kind == TokenType.L_BRACE:
- this = self.expression(exp.Struct, expressions=expressions)
+ this = self.expression(exp.Struct, expressions=self._kv_to_prop_eq(expressions))
elif not this or this.name.upper() == "ARRAY":
this = self.expression(exp.Array, expressions=expressions)
else:
@@ -4747,12 +4859,10 @@ class Parser(metaclass=_Parser):
return None
@t.overload
- def _parse_json_object(self, agg: Lit[False]) -> exp.JSONObject:
- ...
+ def _parse_json_object(self, agg: Lit[False]) -> exp.JSONObject: ...
@t.overload
- def _parse_json_object(self, agg: Lit[True]) -> exp.JSONObjectAgg:
- ...
+ def _parse_json_object(self, agg: Lit[True]) -> exp.JSONObjectAgg: ...
def _parse_json_object(self, agg=False):
star = self._parse_star()
@@ -5140,16 +5250,16 @@ class Parser(metaclass=_Parser):
return None
def _parse_string(self) -> t.Optional[exp.Expression]:
- if self._match_set((TokenType.STRING, TokenType.RAW_STRING)):
- return self.PRIMARY_PARSERS[self._prev.token_type](self, self._prev)
+ if self._match_set(self.STRING_PARSERS):
+ return self.STRING_PARSERS[self._prev.token_type](self, self._prev)
return self._parse_placeholder()
def _parse_string_as_identifier(self) -> t.Optional[exp.Identifier]:
return exp.to_identifier(self._match(TokenType.STRING) and self._prev.text, quoted=True)
def _parse_number(self) -> t.Optional[exp.Expression]:
- if self._match(TokenType.NUMBER):
- return self.PRIMARY_PARSERS[TokenType.NUMBER](self, self._prev)
+ if self._match_set(self.NUMERIC_PARSERS):
+ return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev)
return self._parse_placeholder()
def _parse_identifier(self) -> t.Optional[exp.Expression]:
@@ -5182,6 +5292,9 @@ class Parser(metaclass=_Parser):
def _parse_var_or_string(self) -> t.Optional[exp.Expression]:
return self._parse_var() or self._parse_string()
+ def _parse_primary_or_var(self) -> t.Optional[exp.Expression]:
+ return self._parse_primary() or self._parse_var(any_token=True)
+
def _parse_null(self) -> t.Optional[exp.Expression]:
if self._match_set(self.NULL_TOKENS):
return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev)
@@ -5200,16 +5313,12 @@ class Parser(metaclass=_Parser):
return self._parse_placeholder()
def _parse_parameter(self) -> exp.Parameter:
- def _parse_parameter_part() -> t.Optional[exp.Expression]:
- return (
- self._parse_identifier() or self._parse_primary() or self._parse_var(any_token=True)
- )
-
self._match(TokenType.L_BRACE)
- this = _parse_parameter_part()
- expression = self._match(TokenType.COLON) and _parse_parameter_part()
+ this = self._parse_identifier() or self._parse_primary_or_var()
+ expression = self._match(TokenType.COLON) and (
+ self._parse_identifier() or self._parse_primary_or_var()
+ )
self._match(TokenType.R_BRACE)
-
return self.expression(exp.Parameter, this=this, expression=expression)
def _parse_placeholder(self) -> t.Optional[exp.Expression]:
@@ -5376,35 +5485,15 @@ class Parser(metaclass=_Parser):
exp.DropPartition, expressions=self._parse_csv(self._parse_partition), exists=exists
)
- def _parse_add_constraint(self) -> exp.AddConstraint:
- this = None
- kind = self._prev.token_type
-
- if kind == TokenType.CONSTRAINT:
- this = self._parse_id_var()
-
- if self._match_text_seq("CHECK"):
- expression = self._parse_wrapped(self._parse_conjunction)
- enforced = self._match_text_seq("ENFORCED") or False
-
- return self.expression(
- exp.AddConstraint, this=this, expression=expression, enforced=enforced
- )
-
- if kind == TokenType.FOREIGN_KEY or self._match(TokenType.FOREIGN_KEY):
- expression = self._parse_foreign_key()
- elif kind == TokenType.PRIMARY_KEY or self._match(TokenType.PRIMARY_KEY):
- expression = self._parse_primary_key()
- else:
- expression = None
-
- return self.expression(exp.AddConstraint, this=this, expression=expression)
-
def _parse_alter_table_add(self) -> t.List[exp.Expression]:
index = self._index - 1
- if self._match_set(self.ADD_CONSTRAINT_TOKENS):
- return self._parse_csv(self._parse_add_constraint)
+ if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False):
+ return self._parse_csv(
+ lambda: self.expression(
+ exp.AddConstraint, expressions=self._parse_csv(self._parse_constraint)
+ )
+ )
self._retreat(index)
if not self.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and self._match_text_seq("ADD"):
@@ -5472,6 +5561,7 @@ class Parser(metaclass=_Parser):
parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None
if parser:
actions = ensure_list(parser(self))
+ options = self._parse_csv(self._parse_property)
if not self._curr and actions:
return self.expression(
@@ -5480,6 +5570,7 @@ class Parser(metaclass=_Parser):
exists=exists,
actions=actions,
only=only,
+ options=options,
)
return self._parse_as_command(start)
@@ -5610,11 +5701,34 @@ class Parser(metaclass=_Parser):
return set_
- def _parse_var_from_options(self, options: t.Collection[str]) -> t.Optional[exp.Var]:
- for option in options:
- if self._match_text_seq(*option.split(" ")):
- return exp.var(option)
- return None
+ def _parse_var_from_options(
+ self, options: OPTIONS_TYPE, raise_unmatched: bool = True
+ ) -> t.Optional[exp.Var]:
+ start = self._curr
+ if not start:
+ return None
+
+ option = start.text.upper()
+ continuations = options.get(option)
+
+ index = self._index
+ self._advance()
+ for keywords in continuations or []:
+ if isinstance(keywords, str):
+ keywords = (keywords,)
+
+ if self._match_text_seq(*keywords):
+ option = f"{option} {' '.join(keywords)}"
+ break
+ else:
+ if continuations or continuations is None:
+ if raise_unmatched:
+ self.raise_error(f"Unknown option {option}")
+
+ self._retreat(index)
+ return None
+
+ return exp.var(option)
def _parse_as_command(self, start: Token) -> exp.Command:
while self._curr:
@@ -5806,14 +5920,12 @@ class Parser(metaclass=_Parser):
return True
@t.overload
- def _replace_columns_with_dots(self, this: exp.Expression) -> exp.Expression:
- ...
+ def _replace_columns_with_dots(self, this: exp.Expression) -> exp.Expression: ...
@t.overload
def _replace_columns_with_dots(
self, this: t.Optional[exp.Expression]
- ) -> t.Optional[exp.Expression]:
- ...
+ ) -> t.Optional[exp.Expression]: ...
def _replace_columns_with_dots(self, this):
if isinstance(this, exp.Dot):
@@ -5849,3 +5961,53 @@ class Parser(metaclass=_Parser):
else:
column.replace(dot_or_id)
return node
+
+ def _parse_truncate_table(self) -> t.Optional[exp.TruncateTable] | exp.Expression:
+ start = self._prev
+
+ # Not to be confused with TRUNCATE(number, decimals) function call
+ if self._match(TokenType.L_PAREN):
+ self._retreat(self._index - 2)
+ return self._parse_function()
+
+ # Clickhouse supports TRUNCATE DATABASE as well
+ is_database = self._match(TokenType.DATABASE)
+
+ self._match(TokenType.TABLE)
+
+ exists = self._parse_exists(not_=False)
+
+ expressions = self._parse_csv(
+ lambda: self._parse_table(schema=True, is_db_reference=is_database)
+ )
+
+ cluster = self._parse_on_property() if self._match(TokenType.ON) else None
+
+ if self._match_text_seq("RESTART", "IDENTITY"):
+ identity = "RESTART"
+ elif self._match_text_seq("CONTINUE", "IDENTITY"):
+ identity = "CONTINUE"
+ else:
+ identity = None
+
+ if self._match_text_seq("CASCADE") or self._match_text_seq("RESTRICT"):
+ option = self._prev.text
+ else:
+ option = None
+
+ partition = self._parse_partition()
+
+ # Fallback case
+ if self._curr:
+ return self._parse_as_command(start)
+
+ return self.expression(
+ exp.TruncateTable,
+ expressions=expressions,
+ is_database=is_database,
+ exists=exists,
+ cluster=cluster,
+ identity=identity,
+ option=option,
+ partition=partition,
+ )