Repository: edgedb/edgedb

Issue:
Support shorthand form for view declarations in eschema.
Currently the declaration of views is as follows:

```
        view FooBaz:
            expr := SELECT Foo FILTER Foo.bar = 'baz'
```

To be made consistent with another expression-based object, functions, this should be changed to:


```
        view FooBaz:
            from edgeql := SELECT Foo FILTER Foo.bar = 'baz'
```

At this point, only "edgeql" is a valid expression language for views.


Discussion:
Do we need the short form, and if so, what's the syntax?
```
view FooBaz from edgeql := SELECT Foo FILTER Foo.bar = 'baz'
```
Views are not functions, and will only support EdgeQL expressions as the basis of the definition, so let's continue to use the turnstile.  The short form should be `view Foo := <expr>`

Resolving patch:
diff --git a/edgedb/lang/edgeql/codegen.py b/edgedb/lang/edgeql/codegen.py
--- a/edgedb/lang/edgeql/codegen.py
+++ b/edgedb/lang/edgeql/codegen.py
@@ -42,6 +42,12 @@ def visit(self, node, **kwargs):
             visitor = getattr(self, method, self.generic_visit)
             return visitor(node, **kwargs)
 
+    def _needs_parentheses(self, node):
+        return (
+            isinstance(node.parent, edgeql_ast.Base) and
+            not isinstance(node.parent, edgeql_ast.DDL)
+        )
+
     def generic_visit(self, node, *args, **kwargs):
         raise EdgeQLSourceGeneratorError(
             'No method to generate code for %s' % node.__class__.__name__)
@@ -105,8 +111,7 @@ def visit_Coalesce(self, node):
 
     def visit_InsertQuery(self, node):
         # need to parenthesise when INSERT appears as an expression
-        parenthesise = (isinstance(node.parent, edgeql_ast.Base) and
-                        not isinstance(node.parent, edgeql_ast.DDL))
+        parenthesise = self._needs_parentheses(node)
 
         if parenthesise:
             self.write('(')
@@ -126,8 +131,7 @@ def visit_InsertQuery(self, node):
 
     def visit_UpdateQuery(self, node):
         # need to parenthesise when UPDATE appears as an expression
-        parenthesise = (isinstance(node.parent, edgeql_ast.Base) and
-                        not isinstance(node.parent, edgeql_ast.DDL))
+        parenthesise = self._needs_parentheses(node)
 
         if parenthesise:
             self.write('(')
@@ -153,8 +157,7 @@ def visit_UpdateExpr(self, node):
 
     def visit_DeleteQuery(self, node):
         # need to parenthesise when DELETE appears as an expression
-        parenthesise = (isinstance(node.parent, edgeql_ast.Base) and
-                        not isinstance(node.parent, edgeql_ast.DDL))
+        parenthesise = self._needs_parentheses(node)
 
         if parenthesise:
             self.write('(')
@@ -168,8 +171,7 @@ def visit_DeleteQuery(self, node):
     def visit_SelectQuery(self, node):
         # XXX: need to parenthesise when SELECT appears as an expression,
         # the actual passed value is ignored.
-        parenthesise = (isinstance(node.parent, edgeql_ast.Base) and
-                        not isinstance(node.parent, edgeql_ast.DDL))
+        parenthesise = self._needs_parentheses(node)
 
         if parenthesise:
             self.write('(')
@@ -190,8 +192,7 @@ def visit_SelectQuery(self, node):
 
     def visit_ForQuery(self, node):
         # need to parenthesise when GROUP appears as an expression
-        parenthesise = (isinstance(node.parent, edgeql_ast.Base) and
-                        not isinstance(node.parent, edgeql_ast.DDL))
+        parenthesise = self._needs_parentheses(node)
 
         if parenthesise:
             self.write('(')
@@ -220,8 +221,7 @@ def visit_ForQuery(self, node):
 
     def visit_GroupQuery(self, node):
         # need to parenthesise when GROUP appears as an expression
-        parenthesise = (isinstance(node.parent, edgeql_ast.Base) and
-                        not isinstance(node.parent, edgeql_ast.DDL))
+        parenthesise = self._needs_parentheses(node)
 
         if parenthesise:
             self.write('(')
@@ -760,6 +760,15 @@ def visit_AlterAction(self, node):
     def visit_DropAction(self, node):
         self._visit_DropObject(node, 'ACTION')
 
+    def visit_CreateView(self, node):
+        self._visit_CreateObject(node, 'VIEW')
+
+    def visit_AlterView(self, node):
+        self._visit_AlterObject(node, 'VIEW')
+
+    def visit_DropView(self, node):
+        self._visit_DropObject(node, 'VIEW')
+
     def visit_CreateEvent(self, node):
         after_name = lambda: self._ddl_visit_bases(node)
         self._visit_CreateObject(node, 'EVENT', after_name=after_name)
diff --git a/edgedb/lang/schema/parser/grammar/declarations.py b/edgedb/lang/schema/parser/grammar/declarations.py
--- a/edgedb/lang/schema/parser/grammar/declarations.py
+++ b/edgedb/lang/schema/parser/grammar/declarations.py
@@ -577,6 +577,14 @@ def reduce_VIEW_Identifier_COLON_NL_INDENT_Attributes_DEDENT(self, *kids):
         self.val = esast.ViewDeclaration(name=kids[1].val,
                                          attributes=kids[5].val)
 
+    def reduce_VIEW_Identifier_TurnstileBlob(self, *kids):
+        self.val = esast.ViewDeclaration(
+            name=kids[1].val,
+            attributes=[esast.Attribute(
+                name=qlast.ObjectRef(name='expr'),
+                value=kids[2].val)
+            ])
+
 
 class FunctionDeclaration(Nonterm):
     def reduce_FUNCTION_FunctionDeclCore(self, *kids):


Test patch:
diff --git a/tests/test_edgeql_syntax.py b/tests/test_edgeql_syntax.py
--- a/tests/test_edgeql_syntax.py
+++ b/tests/test_edgeql_syntax.py
@@ -2828,26 +2828,48 @@ def test_edgeql_syntax_ddl_type_03(self):
     def test_edgeql_syntax_set_command_01(self):
         """
         SET MODULE default;
-
-% OK %
-
-        SET MODULE default;
         """
 
     def test_edgeql_syntax_set_command_02(self):
         """
         SET foo := MODULE default;
+        """
+
+    def test_edgeql_syntax_set_command_03(self):
+        """
+        SET MODULE default, foo := (SELECT User);
+        """
+
+    def test_edgeql_syntax_ddl_view_01(self):
+        """
+        CREATE VIEW Foo := (SELECT User);
 
 % OK %
 
-        SET foo := MODULE default;
+        CREATE VIEW Foo {
+            SET expr := SELECT User;
+        };
         """
 
-    def test_edgeql_syntax_set_command_03(self):
+    def test_edgeql_syntax_ddl_view_02(self):
         """
-        SET MODULE default, foo := (SELECT User);
+        CREATE VIEW Foo {
+            SET expr := (SELECT User);
+        };
+
+        ALTER VIEW Foo
+            SET expr := (SELECT Person);
+
+        DROP VIEW Foo;
 
 % OK %
 
-        SET MODULE default, foo := (SELECT User);
+        CREATE VIEW Foo {
+            SET expr := SELECT User;
+        };
+
+        ALTER VIEW Foo
+            SET expr := SELECT Person;
+
+        DROP VIEW Foo;
         """
diff --git a/tests/test_schema_syntax.py b/tests/test_schema_syntax.py
--- a/tests/test_schema_syntax.py
+++ b/tests/test_schema_syntax.py
@@ -1030,6 +1030,20 @@ def test_eschema_syntax_view_02(self):
                 FILTER Foo.bar = 'baz'
         """
 
+    def test_eschema_syntax_view_03(self):
+        """
+        view FooBaz :=
+                SELECT Foo
+                FILTER Foo.bar = 'baz'
+
+% OK %
+
+        view FooBaz:
+            expr :=
+                SELECT Foo
+                FILTER Foo.bar = 'baz'
+        """
+
     def test_eschema_syntax_attribute_01(self):
         """
         abstract attribute foobar std::str