summaryrefslogtreecommitdiffstats
path: root/sqlglot/parser.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 05:35:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 05:35:50 +0000
commit8f1b330983bddb35e2ec61a5667a84318bad88ef (patch)
tree32b74e964839c34014d31ec828fc6521323b9edc /sqlglot/parser.py
parentAdding upstream version 23.13.7. (diff)
downloadsqlglot-8f1b330983bddb35e2ec61a5667a84318bad88ef.tar.xz
sqlglot-8f1b330983bddb35e2ec61a5667a84318bad88ef.zip
Adding upstream version 23.16.0.upstream/23.16.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sqlglot/parser.py')
-rw-r--r--sqlglot/parser.py314
1 files changed, 257 insertions, 57 deletions
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index b0a2516..67ffd8f 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -61,6 +61,23 @@ def build_logarithm(args: t.List, dialect: Dialect) -> exp.Func:
return (exp.Ln if dialect.parser_class.LOG_DEFAULTS_TO_LN else exp.Log)(this=this)
+def build_hex(args: t.List, dialect: Dialect) -> exp.Hex | exp.LowerHex:
+ arg = seq_get(args, 0)
+ return exp.LowerHex(this=arg) if dialect.HEX_LOWERCASE else exp.Hex(this=arg)
+
+
+def build_lower(args: t.List) -> exp.Lower | exp.Hex:
+ # LOWER(HEX(..)) can be simplified to LowerHex to simplify its transpilation
+ arg = seq_get(args, 0)
+ return exp.LowerHex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Lower(this=arg)
+
+
+def build_upper(args: t.List) -> exp.Upper | exp.Hex:
+ # UPPER(HEX(..)) can be simplified to Hex to simplify its transpilation
+ arg = seq_get(args, 0)
+ return exp.Hex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Upper(this=arg)
+
+
def build_extract_json_with_path(expr_type: t.Type[E]) -> t.Callable[[t.List, Dialect], E]:
def _builder(args: t.List, dialect: Dialect) -> E:
expression = expr_type(
@@ -74,6 +91,17 @@ def build_extract_json_with_path(expr_type: t.Type[E]) -> t.Callable[[t.List, Di
return _builder
+def build_mod(args: t.List) -> exp.Mod:
+ this = seq_get(args, 0)
+ expression = seq_get(args, 1)
+
+ # Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7
+ this = exp.Paren(this=this) if isinstance(this, exp.Binary) else this
+ expression = exp.Paren(this=expression) if isinstance(expression, exp.Binary) else expression
+
+ return exp.Mod(this=this, expression=expression)
+
+
class _Parser(type):
def __new__(cls, clsname, bases, attrs):
klass = super().__new__(cls, clsname, bases, attrs)
@@ -123,7 +151,7 @@ class Parser(metaclass=_Parser):
"LOG": build_logarithm,
"LOG2": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)),
"LOG10": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)),
- "MOD": lambda args: exp.Mod(this=seq_get(args, 0), expression=seq_get(args, 1)),
+ "MOD": build_mod,
"TIME_TO_TIME_STR": lambda args: exp.Cast(
this=seq_get(args, 0),
to=exp.DataType(this=exp.DataType.Type.TEXT),
@@ -137,6 +165,10 @@ class Parser(metaclass=_Parser):
length=exp.Literal.number(10),
),
"VAR_MAP": build_var_map,
+ "LOWER": build_lower,
+ "UPPER": build_upper,
+ "HEX": build_hex,
+ "TO_HEX": build_hex,
}
NO_PAREN_FUNCTIONS = {
@@ -295,22 +327,23 @@ class Parser(metaclass=_Parser):
DB_CREATABLES = {
TokenType.DATABASE,
- TokenType.SCHEMA,
- TokenType.TABLE,
- TokenType.VIEW,
- TokenType.MODEL,
TokenType.DICTIONARY,
+ TokenType.MODEL,
+ TokenType.SCHEMA,
TokenType.SEQUENCE,
TokenType.STORAGE_INTEGRATION,
+ TokenType.TABLE,
+ TokenType.TAG,
+ TokenType.VIEW,
}
CREATABLES = {
TokenType.COLUMN,
TokenType.CONSTRAINT,
+ TokenType.FOREIGN_KEY,
TokenType.FUNCTION,
TokenType.INDEX,
TokenType.PROCEDURE,
- TokenType.FOREIGN_KEY,
*DB_CREATABLES,
}
@@ -373,6 +406,7 @@ class Parser(metaclass=_Parser):
TokenType.REFRESH,
TokenType.REPLACE,
TokenType.RIGHT,
+ TokenType.ROLLUP,
TokenType.ROW,
TokenType.ROWS,
TokenType.SEMI,
@@ -467,7 +501,6 @@ class Parser(metaclass=_Parser):
}
EQUALITY = {
- TokenType.COLON_EQ: exp.PropertyEQ,
TokenType.EQ: exp.EQ,
TokenType.NEQ: exp.NEQ,
TokenType.NULLSAFE_EQ: exp.NullSafeEQ,
@@ -653,6 +686,7 @@ class Parser(metaclass=_Parser):
kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False),
this=self._parse_table(schema=False),
),
+ TokenType.SEMICOLON: lambda self: self.expression(exp.Semicolon),
}
UNARY_PARSERS = {
@@ -700,7 +734,12 @@ class Parser(metaclass=_Parser):
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()}
+ exp.Star,
+ **{
+ "except": self._parse_star_op("EXCEPT", "EXCLUDE"),
+ "replace": self._parse_star_op("REPLACE"),
+ "rename": self._parse_star_op("RENAME"),
+ },
),
}
@@ -729,6 +768,9 @@ class Parser(metaclass=_Parser):
}
PROPERTY_PARSERS: t.Dict[str, t.Callable] = {
+ "ALLOWED_VALUES": lambda self: self.expression(
+ exp.AllowedValuesProperty, expressions=self._parse_csv(self._parse_primary)
+ ),
"ALGORITHM": lambda self: self._parse_property_assignment(exp.AlgorithmProperty),
"AUTO": lambda self: self._parse_auto_property(),
"AUTO_INCREMENT": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty),
@@ -748,6 +790,7 @@ class Parser(metaclass=_Parser):
"CONTAINS": lambda self: self._parse_contains_property(),
"COPY": lambda self: self._parse_copy_property(),
"DATABLOCKSIZE": lambda self, **kwargs: self._parse_datablocksize(**kwargs),
+ "DATA_DELETION": lambda self: self._parse_data_deletion_property(),
"DEFINER": lambda self: self._parse_definer(),
"DETERMINISTIC": lambda self: self.expression(
exp.StabilityProperty, this=exp.Literal.string("IMMUTABLE")
@@ -796,6 +839,7 @@ class Parser(metaclass=_Parser):
"READS": lambda self: self._parse_reads_property(),
"REMOTE": lambda self: self._parse_remote_with_connection(),
"RETURNS": lambda self: self._parse_returns(),
+ "STRICT": lambda self: self.expression(exp.StrictProperty),
"ROW": lambda self: self._parse_row(),
"ROW_FORMAT": lambda self: self._parse_property_assignment(exp.RowFormatProperty),
"SAMPLE": lambda self: self.expression(
@@ -900,6 +944,14 @@ class Parser(metaclass=_Parser):
"DELETE": lambda self: self.expression(exp.Delete, where=self._parse_where()),
"DROP": lambda self: self._parse_alter_table_drop(),
"RENAME": lambda self: self._parse_alter_table_rename(),
+ "SET": lambda self: self._parse_alter_table_set(),
+ }
+
+ ALTER_ALTER_PARSERS = {
+ "DISTKEY": lambda self: self._parse_alter_diststyle(),
+ "DISTSTYLE": lambda self: self._parse_alter_diststyle(),
+ "SORTKEY": lambda self: self._parse_alter_sortkey(),
+ "COMPOUND": lambda self: self._parse_alter_sortkey(compound=True),
}
SCHEMA_UNNAMED_CONSTRAINTS = {
@@ -990,6 +1042,8 @@ class Parser(metaclass=_Parser):
exp.DataType.Type.JSON: lambda self, this, _: self.expression(exp.ParseJSON, this=this),
}
+ TYPE_CONVERTER: t.Dict[exp.DataType.Type, t.Callable[[exp.DataType], exp.DataType]] = {}
+
DDL_SELECT_TOKENS = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN}
PRE_VOLATILE_TOKENS = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE}
@@ -1084,6 +1138,9 @@ class Parser(metaclass=_Parser):
# Whether the table sample clause expects CSV syntax
TABLESAMPLE_CSV = False
+ # The default method used for table sampling
+ DEFAULT_SAMPLING_METHOD: t.Optional[str] = None
+
# Whether the SET command needs a delimiter (e.g. "=") for assignments
SET_REQUIRES_ASSIGNMENT_DELIMITER = True
@@ -1228,6 +1285,9 @@ class Parser(metaclass=_Parser):
for i, token in enumerate(raw_tokens):
if token.token_type == TokenType.SEMICOLON:
+ if token.comments:
+ chunks.append([token])
+
if i < total - 1:
chunks.append([])
else:
@@ -1471,7 +1531,7 @@ class Parser(metaclass=_Parser):
if self._match_set(self.STATEMENT_PARSERS):
return self.STATEMENT_PARSERS[self._prev.token_type](self)
- if self._match_set(Tokenizer.COMMANDS):
+ if self._match_set(self.dialect.tokenizer.COMMANDS):
return self._parse_command()
expression = self._parse_expression()
@@ -1492,6 +1552,8 @@ class Parser(metaclass=_Parser):
schema=True, is_db_reference=self._prev.token_type == TokenType.SCHEMA
)
+ cluster = self._parse_on_property() if self._match(TokenType.ON) else None
+
if self._match(TokenType.L_PAREN, advance=False):
expressions = self._parse_wrapped_csv(self._parse_types)
else:
@@ -1503,12 +1565,13 @@ class Parser(metaclass=_Parser):
exists=if_exists,
this=table,
expressions=expressions,
- kind=kind,
+ kind=kind.upper(),
temporary=temporary,
materialized=materialized,
cascade=self._match_text_seq("CASCADE"),
constraints=self._match_text_seq("CONSTRAINTS"),
purge=self._match_text_seq("PURGE"),
+ cluster=cluster,
)
def _parse_exists(self, not_: bool = False) -> t.Optional[bool]:
@@ -1651,7 +1714,7 @@ class Parser(metaclass=_Parser):
exp.Clone, this=self._parse_table(schema=True), shallow=shallow, copy=copy
)
- if self._curr:
+ if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False):
return self._parse_as_command(start)
return self.expression(
@@ -1678,6 +1741,7 @@ class Parser(metaclass=_Parser):
index = self._index
while self._curr:
+ self._match(TokenType.COMMA)
if self._match_text_seq("INCREMENT"):
self._match_text_seq("BY")
self._match_text_seq("=")
@@ -1822,23 +1886,65 @@ class Parser(metaclass=_Parser):
return self.expression(exp.StabilityProperty, this=exp.Literal.string("VOLATILE"))
- def _parse_system_versioning_property(self) -> exp.WithSystemVersioningProperty:
- self._match_pair(TokenType.EQ, TokenType.ON)
+ def _parse_retention_period(self) -> exp.Var:
+ # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...}
+ number = self._parse_number()
+ number_str = f"{number} " if number else ""
+ unit = self._parse_var(any_token=True)
+ return exp.var(f"{number_str}{unit}")
+
+ def _parse_system_versioning_property(
+ self, with_: bool = False
+ ) -> exp.WithSystemVersioningProperty:
+ self._match(TokenType.EQ)
+ prop = self.expression(
+ exp.WithSystemVersioningProperty,
+ **{ # type: ignore
+ "on": True,
+ "with": with_,
+ },
+ )
+
+ if self._match_text_seq("OFF"):
+ prop.set("on", False)
+ return prop
- prop = self.expression(exp.WithSystemVersioningProperty)
+ self._match(TokenType.ON)
if self._match(TokenType.L_PAREN):
- self._match_text_seq("HISTORY_TABLE", "=")
- prop.set("this", self._parse_table_parts())
+ while self._curr and not self._match(TokenType.R_PAREN):
+ if self._match_text_seq("HISTORY_TABLE", "="):
+ prop.set("this", self._parse_table_parts())
+ elif self._match_text_seq("DATA_CONSISTENCY_CHECK", "="):
+ prop.set("data_consistency", self._advance_any() and self._prev.text.upper())
+ elif self._match_text_seq("HISTORY_RETENTION_PERIOD", "="):
+ prop.set("retention_period", self._parse_retention_period())
- if self._match(TokenType.COMMA):
- self._match_text_seq("DATA_CONSISTENCY_CHECK", "=")
- prop.set("expression", self._advance_any() and self._prev.text.upper())
+ self._match(TokenType.COMMA)
- self._match_r_paren()
+ return prop
+
+ def _parse_data_deletion_property(self) -> exp.DataDeletionProperty:
+ self._match(TokenType.EQ)
+ on = self._match_text_seq("ON") or not self._match_text_seq("OFF")
+ prop = self.expression(exp.DataDeletionProperty, on=on)
+
+ if self._match(TokenType.L_PAREN):
+ while self._curr and not self._match(TokenType.R_PAREN):
+ if self._match_text_seq("FILTER_COLUMN", "="):
+ prop.set("filter_column", self._parse_column())
+ elif self._match_text_seq("RETENTION_PERIOD", "="):
+ prop.set("retention_period", self._parse_retention_period())
+
+ self._match(TokenType.COMMA)
return prop
def _parse_with_property(self) -> t.Optional[exp.Expression] | t.List[exp.Expression]:
+ if self._match_text_seq("(", "SYSTEM_VERSIONING"):
+ prop = self._parse_system_versioning_property(with_=True)
+ self._match_r_paren()
+ return prop
+
if self._match(TokenType.L_PAREN, advance=False):
return self._parse_wrapped_properties()
@@ -1853,6 +1959,9 @@ class Parser(metaclass=_Parser):
elif self._match_text_seq("NO", "DATA"):
return self._parse_withdata(no=True)
+ if self._match(TokenType.SERDE_PROPERTIES, advance=False):
+ return self._parse_serde_properties(with_=True)
+
if not self._next:
return None
@@ -2201,6 +2310,7 @@ class Parser(metaclass=_Parser):
def _parse_returns(self) -> exp.ReturnsProperty:
value: t.Optional[exp.Expression]
+ null = None
is_table = self._match(TokenType.TABLE)
if is_table:
@@ -2214,10 +2324,13 @@ class Parser(metaclass=_Parser):
self.raise_error("Expecting >")
else:
value = self._parse_schema(exp.var("TABLE"))
+ elif self._match_text_seq("NULL", "ON", "NULL", "INPUT"):
+ null = True
+ value = None
else:
value = self._parse_types()
- return self.expression(exp.ReturnsProperty, this=value, is_table=is_table)
+ return self.expression(exp.ReturnsProperty, this=value, is_table=is_table, null=null)
def _parse_describe(self) -> exp.Describe:
kind = self._match_set(self.CREATABLES) and self._prev.text
@@ -2340,6 +2453,21 @@ class Parser(metaclass=_Parser):
return None
return self._parse_row_format()
+ def _parse_serde_properties(self, with_: bool = False) -> t.Optional[exp.SerdeProperties]:
+ index = self._index
+ with_ = with_ or self._match_text_seq("WITH")
+
+ if not self._match(TokenType.SERDE_PROPERTIES):
+ self._retreat(index)
+ return None
+ return self.expression(
+ exp.SerdeProperties,
+ **{ # type: ignore
+ "expressions": self._parse_wrapped_properties(),
+ "with": with_,
+ },
+ )
+
def _parse_row_format(
self, match_row: bool = False
) -> t.Optional[exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty]:
@@ -2349,11 +2477,7 @@ class Parser(metaclass=_Parser):
if self._match_text_seq("SERDE"):
this = self._parse_string()
- serde_properties = None
- if self._match(TokenType.SERDE_PROPERTIES):
- serde_properties = self.expression(
- exp.SerdeProperties, expressions=self._parse_wrapped_properties()
- )
+ serde_properties = self._parse_serde_properties()
return self.expression(
exp.RowFormatSerdeProperty, this=this, serde_properties=serde_properties
@@ -2672,9 +2796,7 @@ class Parser(metaclass=_Parser):
)
def _implicit_unnests_to_explicit(self, this: E) -> E:
- from sqlglot.optimizer.normalize_identifiers import (
- normalize_identifiers as _norm,
- )
+ 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 []):
@@ -3366,6 +3488,9 @@ class Parser(metaclass=_Parser):
elif self._match_texts(("SEED", "REPEATABLE")):
seed = self._parse_wrapped(self._parse_number)
+ if not method and self.DEFAULT_SAMPLING_METHOD:
+ method = exp.var(self.DEFAULT_SAMPLING_METHOD)
+
return self.expression(
exp.TableSample,
expressions=expressions,
@@ -3519,7 +3644,11 @@ class Parser(metaclass=_Parser):
elements["all"] = False
while True:
- expressions = self._parse_csv(self._parse_conjunction)
+ expressions = self._parse_csv(
+ lambda: None
+ if self._match(TokenType.ROLLUP, advance=False)
+ else self._parse_conjunction()
+ )
if expressions:
elements["expressions"].extend(expressions)
@@ -3817,7 +3946,24 @@ class Parser(metaclass=_Parser):
return self._parse_alias(self._parse_conjunction())
def _parse_conjunction(self) -> t.Optional[exp.Expression]:
- return self._parse_tokens(self._parse_equality, self.CONJUNCTION)
+ this = self._parse_equality()
+
+ if self._match(TokenType.COLON_EQ):
+ this = self.expression(
+ exp.PropertyEQ,
+ this=this,
+ comments=self._prev_comments,
+ expression=self._parse_conjunction(),
+ )
+
+ while self._match_set(self.CONJUNCTION):
+ this = self.expression(
+ self.CONJUNCTION[self._prev.token_type],
+ this=this,
+ comments=self._prev_comments,
+ expression=self._parse_equality(),
+ )
+ return this
def _parse_equality(self) -> t.Optional[exp.Expression]:
return self._parse_tokens(self._parse_comparison, self.EQUALITY)
@@ -4061,6 +4207,7 @@ class Parser(metaclass=_Parser):
) -> t.Optional[exp.Expression]:
index = self._index
+ this: t.Optional[exp.Expression] = None
prefix = self._match_text_seq("SYSUDTLIB", ".")
if not self._match_set(self.TYPE_TOKENS):
@@ -4081,7 +4228,7 @@ class Parser(metaclass=_Parser):
while self._match(TokenType.DOT):
type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
- return exp.DataType.build(type_name, udt=True)
+ this = exp.DataType.build(type_name, udt=True)
else:
self._retreat(self._index - 1)
return None
@@ -4134,7 +4281,6 @@ class Parser(metaclass=_Parser):
maybe_func = True
- this: t.Optional[exp.Expression] = None
values: t.Optional[t.List[exp.Expression]] = None
if nested and self._match(TokenType.LT):
@@ -4203,10 +4349,17 @@ class Parser(metaclass=_Parser):
values=values,
prefix=prefix,
)
+ elif expressions:
+ this.set("expressions", expressions)
while self._match_pair(TokenType.L_BRACKET, TokenType.R_BRACKET):
this = exp.DataType(this=exp.DataType.Type.ARRAY, expressions=[this], nested=True)
+ if self.TYPE_CONVERTER and isinstance(this.this, exp.DataType.Type):
+ converter = self.TYPE_CONVERTER.get(this.this)
+ if converter:
+ this = converter(t.cast(exp.DataType, this))
+
return this
def _parse_struct_types(self, type_required: bool = False) -> t.Optional[exp.Expression]:
@@ -4326,9 +4479,7 @@ class Parser(metaclass=_Parser):
if not this and self._match(TokenType.R_PAREN, advance=False):
this = self.expression(exp.Tuple)
elif isinstance(this, exp.UNWRAPPED_QUERIES):
- this = self._parse_set_operations(
- self._parse_subquery(this=this, parse_alias=False)
- )
+ this = self._parse_subquery(this=this, parse_alias=False)
elif isinstance(this, exp.Subquery):
this = self._parse_subquery(
this=self._parse_set_operations(this), parse_alias=False
@@ -5625,13 +5776,8 @@ class Parser(metaclass=_Parser):
return self._parse_placeholder()
def _parse_parameter(self) -> exp.Parameter:
- self._match(TokenType.L_BRACE)
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)
+ return self.expression(exp.Parameter, this=this)
def _parse_placeholder(self) -> t.Optional[exp.Expression]:
if self._match_set(self.PLACEHOLDER_PARSERS):
@@ -5641,23 +5787,14 @@ class Parser(metaclass=_Parser):
self._advance(-1)
return None
- def _parse_except(self) -> t.Optional[t.List[exp.Expression]]:
- if not self._match(TokenType.EXCEPT):
- return None
- if self._match(TokenType.L_PAREN, advance=False):
- return self._parse_wrapped_csv(self._parse_column)
-
- except_column = self._parse_column()
- return [except_column] if except_column else None
-
- def _parse_replace(self) -> t.Optional[t.List[exp.Expression]]:
- if not self._match(TokenType.REPLACE):
+ def _parse_star_op(self, *keywords: str) -> t.Optional[t.List[exp.Expression]]:
+ if not self._match_texts(keywords):
return None
if self._match(TokenType.L_PAREN, advance=False):
return self._parse_wrapped_csv(self._parse_expression)
- replace_expression = self._parse_expression()
- return [replace_expression] if replace_expression else None
+ expression = self._parse_expression()
+ return [expression] if expression else None
def _parse_csv(
self, parse_method: t.Callable, sep: TokenType = TokenType.COMMA
@@ -5812,7 +5949,12 @@ class Parser(metaclass=_Parser):
return self._parse_wrapped_csv(self._parse_field_def, optional=True)
return self._parse_wrapped_csv(self._parse_add_column, optional=True)
- def _parse_alter_table_alter(self) -> exp.AlterColumn:
+ def _parse_alter_table_alter(self) -> t.Optional[exp.Expression]:
+ if self._match_texts(self.ALTER_ALTER_PARSERS):
+ return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self)
+
+ # Many dialects support the ALTER [COLUMN] syntax, so if there is no
+ # keyword after ALTER we default to parsing this statement
self._match(TokenType.COLUMN)
column = self._parse_field(any_token=True)
@@ -5833,6 +5975,27 @@ class Parser(metaclass=_Parser):
using=self._match(TokenType.USING) and self._parse_conjunction(),
)
+ def _parse_alter_diststyle(self) -> exp.AlterDistStyle:
+ if self._match_texts(("ALL", "EVEN", "AUTO")):
+ return self.expression(exp.AlterDistStyle, this=exp.var(self._prev.text.upper()))
+
+ self._match_text_seq("KEY", "DISTKEY")
+ return self.expression(exp.AlterDistStyle, this=self._parse_column())
+
+ def _parse_alter_sortkey(self, compound: t.Optional[bool] = None) -> exp.AlterSortKey:
+ if compound:
+ self._match_text_seq("SORTKEY")
+
+ if self._match(TokenType.L_PAREN, advance=False):
+ return self.expression(
+ exp.AlterSortKey, expressions=self._parse_wrapped_id_vars(), compound=compound
+ )
+
+ self._match_texts(("AUTO", "NONE"))
+ return self.expression(
+ exp.AlterSortKey, this=exp.var(self._prev.text.upper()), compound=compound
+ )
+
def _parse_alter_table_drop(self) -> t.List[exp.Expression]:
index = self._index - 1
@@ -5858,6 +6021,41 @@ class Parser(metaclass=_Parser):
self._match_text_seq("TO")
return self.expression(exp.RenameTable, this=self._parse_table(schema=True))
+ def _parse_alter_table_set(self) -> exp.AlterSet:
+ alter_set = self.expression(exp.AlterSet)
+
+ if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq(
+ "TABLE", "PROPERTIES"
+ ):
+ alter_set.set("expressions", self._parse_wrapped_csv(self._parse_conjunction))
+ elif self._match_text_seq("FILESTREAM_ON", advance=False):
+ alter_set.set("expressions", [self._parse_conjunction()])
+ elif self._match_texts(("LOGGED", "UNLOGGED")):
+ alter_set.set("option", exp.var(self._prev.text.upper()))
+ elif self._match_text_seq("WITHOUT") and self._match_texts(("CLUSTER", "OIDS")):
+ alter_set.set("option", exp.var(f"WITHOUT {self._prev.text.upper()}"))
+ elif self._match_text_seq("LOCATION"):
+ alter_set.set("location", self._parse_field())
+ elif self._match_text_seq("ACCESS", "METHOD"):
+ alter_set.set("access_method", self._parse_field())
+ elif self._match_text_seq("TABLESPACE"):
+ alter_set.set("tablespace", self._parse_field())
+ elif self._match_text_seq("FILE", "FORMAT") or self._match_text_seq("FILEFORMAT"):
+ alter_set.set("file_format", [self._parse_field()])
+ elif self._match_text_seq("STAGE_FILE_FORMAT"):
+ alter_set.set("file_format", self._parse_wrapped_options())
+ elif self._match_text_seq("STAGE_COPY_OPTIONS"):
+ alter_set.set("copy_options", self._parse_wrapped_options())
+ elif self._match_text_seq("TAG") or self._match_text_seq("TAGS"):
+ alter_set.set("tag", self._parse_csv(self._parse_conjunction))
+ else:
+ if self._match_text_seq("SERDE"):
+ alter_set.set("serde", self._parse_field())
+
+ alter_set.set("expressions", [self._parse_properties()])
+
+ return alter_set
+
def _parse_alter(self) -> exp.AlterTable | exp.Command:
start = self._prev
@@ -5867,6 +6065,7 @@ class Parser(metaclass=_Parser):
exists = self._parse_exists()
only = self._match_text_seq("ONLY")
this = self._parse_table(schema=True)
+ cluster = self._parse_on_property() if self._match(TokenType.ON) else None
if self._next:
self._advance()
@@ -5884,6 +6083,7 @@ class Parser(metaclass=_Parser):
actions=actions,
only=only,
options=options,
+ cluster=cluster,
)
return self._parse_as_command(start)
@@ -5974,7 +6174,7 @@ class Parser(metaclass=_Parser):
if kind in ("GLOBAL", "SESSION") and self._match_text_seq("TRANSACTION"):
return self._parse_set_transaction(global_=kind == "GLOBAL")
- left = self._parse_primary() or self._parse_id_var()
+ left = self._parse_primary() or self._parse_column()
assignment_delimiter = self._match_texts(("=", "TO"))
if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter):