diff --git a/sqlglot/dialects/singlestore.py b/sqlglot/dialects/singlestore.py index 9abd6e3efa..1126f84848 100644 --- a/sqlglot/dialects/singlestore.py +++ b/sqlglot/dialects/singlestore.py @@ -21,7 +21,7 @@ from sqlglot.dialects.mysql import _str_to_date from sqlglot.expressions import DataType from sqlglot.generator import ESCAPED_UNICODE_RE, unsupported_args -from sqlglot.helper import csv, seq_get, is_int +from sqlglot.helper import csv, seq_get, is_int, ensure_list from sqlglot.parser import build_coalesce from sqlglot.time import format_time from sqlglot.trie import new_trie @@ -457,6 +457,56 @@ def _parse_match_against(self) -> exp.MatchAgainst: return self.expression(exp.MatchAgainst, this=this, expressions=expressions) + def _parse_alter(self) -> t.Union[exp.Alter, exp.Command]: + start = self._prev + + if self._match_text_seq("DEFINER", advance=False): + definer = self._parse_assignment() + else: + definer = None + + if self._match_text_seq("SCHEMA_BINDING", "=", "ON"): + schema_binding = True + elif self._match_text_seq("SCHEMA_BINDING", "=", "OFF"): + schema_binding = False + else: + schema_binding = None + + alter_token = self._match_set(self.ALTERABLES) and self._prev + if not alter_token: + return self._parse_as_command(start) + + 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() + + parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None + if parser: + actions = ensure_list(parser(self)) + not_valid = self._match_text_seq("NOT", "VALID") + options = self._parse_csv(self._parse_property) + + if not self._curr and actions: + return self.expression( + exp.Alter, + this=this, + kind=alter_token.text.upper(), + exists=exists, + actions=actions, + only=only, + options=options, + cluster=cluster, + not_valid=not_valid, + definer=definer, + schema_binding=schema_binding, + ) + + return self._parse_as_command(start) + def _parse_alter_table_set(self) -> exp.AlterSet: alter_set = self.expression(exp.AlterSet) @@ -3596,8 +3646,17 @@ def alter_sql(self, expression: exp.Alter) -> str: actions_sql = self.format_args(*actions_list).lstrip("\n") kind = self.sql(expression, "kind") + definer = self.sql(expression, "definer") + definer = f" {definer}" if definer else "" + schema_binding = expression.args.get("schema_binding") + if schema_binding is True: + schema_binding = " SCHEMA_BINDING = ON" + elif schema_binding is False: + schema_binding = " SCHEMA_BINDING = OFF" + else: + schema_binding = "" - return f"ALTER {kind} {self.sql(expression, 'this')} {actions_sql}" + return f"ALTER{definer}{schema_binding} {kind} {self.sql(expression, 'this')} {actions_sql}" def add_column_sql(self, expression: exp.Expression) -> str: sql = self.sql(expression) diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py index 57efb2c34e..3f04798b90 100644 --- a/sqlglot/expressions.py +++ b/sqlglot/expressions.py @@ -4856,6 +4856,8 @@ class Alter(Expression): "options": False, "cluster": False, "not_valid": False, + "definer": False, + "schema_binding": False, } @property diff --git a/tests/dialects/test_singlestore.py b/tests/dialects/test_singlestore.py index f2ef759487..6699673bed 100644 --- a/tests/dialects/test_singlestore.py +++ b/tests/dialects/test_singlestore.py @@ -114,6 +114,8 @@ def setUp(self): execute_query( """CREATE OR REPLACE PROCEDURE proc() RETURNS void AS BEGIN ECHO SELECT 1; END""" ) + execute_query("""DROP VIEW IF EXISTS users_view""") + execute_query("""CREATE VIEW users_view AS SELECT * FROM users""") def validate_generation( self, @@ -5894,3 +5896,83 @@ def test_alter_database_parsing(self): actions=[exp.AlterSet(expressions=[exp.Var(this="ASYNC REPLICATION")])], ), ) + + def test_alter_view_parsing(self): + self.validate_parsing( + "ALTER VIEW users_view AS SELECT * FROM users", + exp.Alter( + this=exp.Table(this=exp.Identifier(this="users_view", quoted=False)), + kind="VIEW", + actions=[ + exp.Select( + **{ + "expressions": [exp.Star()], + "from": exp.From( + this=exp.Table(this=exp.Identifier(this="users", quoted=False)) + ), + } + ) + ], + ), + ) + self.validate_parsing( + "ALTER DEFINER = CURRENT_USER() VIEW users_view AS SELECT * FROM users", + exp.Alter( + this=exp.Table(this=exp.Identifier(this="users_view", quoted=False)), + kind="VIEW", + actions=[ + exp.Select( + **{ + "expressions": [exp.Star()], + "from": exp.From( + this=exp.Table(this=exp.Identifier(this="users", quoted=False)) + ), + } + ) + ], + definer=exp.EQ( + this=exp.Column(this=exp.Identifier(this="DEFINER", quoted=False)), + expression=exp.CurrentUser(), + ), + ), + ) + self.validate_parsing( + "ALTER SCHEMA_BINDING = ON VIEW users_view AS SELECT * FROM users", + exp.Alter( + this=exp.Table(this=exp.Identifier(this="users_view", quoted=False)), + kind="VIEW", + actions=[ + exp.Select( + **{ + "expressions": [exp.Star()], + "from": exp.From( + this=exp.Table(this=exp.Identifier(this="users", quoted=False)) + ), + } + ) + ], + schema_binding=True, + ), + ) + self.validate_parsing( + "ALTER DEFINER = CURRENT_USER() SCHEMA_BINDING = ON VIEW users_view AS SELECT * FROM users", + exp.Alter( + this=exp.Table(this=exp.Identifier(this="users_view", quoted=False)), + kind="VIEW", + actions=[ + exp.Select( + **{ + "expressions": [exp.Star()], + "from": exp.From( + this=exp.Table(this=exp.Identifier(this="users", quoted=False)) + ), + } + ) + ], + definer=exp.EQ( + this=exp.Column(this=exp.Identifier(this="DEFINER", quoted=False)), + expression=exp.CurrentUser(), + ), + schema_binding=True, + ), + )