Repository: edgedb/edgedb

Issue:
InternalServerError (relation does not exist) when going from single link to multi link in abstract type
- EdgeDB Version: 1.0-rc.5
- OS Version: wsl2 under windows 10

Steps to Reproduce:

```
create abstract type Person { create link lover -> Person; };
create type NPC extending Person; # there's no issue when there is no subtype
alter type Person { alter link lover { set multi; }; };
```

Ran into this in chapter 9 of edgedb easy. Possibly related to #3309?


Discussion:

Resolving patch:
diff --git a/edb/pgsql/delta.py b/edb/pgsql/delta.py
--- a/edb/pgsql/delta.py
+++ b/edb/pgsql/delta.py
@@ -3186,7 +3186,9 @@ def _alter_pointer_cardinality(
                 dt = dbops.DropTable(name=otabname, conditions=[condition])
                 self.pgops.add(dt)
 
-            self.alter_inhview(schema, context, source_op.scls)
+            self.schedule_inhview_update(
+                schema, context, source_op.scls,
+                s_objtypes.ObjectTypeCommandContext,)
         else:
             # Moving from source table to pointer table.
             self.create_table(ptr, schema, context)
@@ -4132,15 +4134,17 @@ class AlterLinkUpperCardinality(
     LinkMetaCommand,
     adapts=s_links.AlterLinkUpperCardinality,
 ):
-    def apply(
+    def _alter_innards(
         self,
         schema: s_schema.Schema,
         context: sd.CommandContext,
     ) -> s_schema.Schema:
+        orig_schema = context.current().original_schema
         pop = self.get_parent_op(context)
-        orig_schema = schema
-        schema = super().apply(schema, context)
 
+        # We need to run the parent change *before* the children,
+        # or else the view update in the child might fail if a
+        # link table isn't created in the parent yet.
         if (
             not self.scls.generic(schema)
             and not self.scls.is_pure_computable(schema)
@@ -4151,7 +4155,7 @@ def apply(
             if orig_card != new_card:
                 self._alter_pointer_cardinality(schema, orig_schema, context)
 
-        return schema
+        return super()._alter_innards(schema, context)
 
 
 class AlterLinkLowerCardinality(
@@ -4462,6 +4466,8 @@ def _delete_property(
 
         if has_table(prop, orig_schema):
             self.drop_inhview(orig_schema, context, prop)
+            self.alter_ancestor_inhviews(
+                schema, context, prop, exclude_children={prop})
             old_table_name = common.get_backend_name(
                 orig_schema, prop, catenate=False)
             self.pgops.add(dbops.DropTable(name=old_table_name))
@@ -4535,15 +4541,17 @@ class AlterPropertyUpperCardinality(
     PropertyMetaCommand,
     adapts=s_props.AlterPropertyUpperCardinality,
 ):
-    def apply(
+    def _alter_innards(
         self,
         schema: s_schema.Schema,
         context: sd.CommandContext,
     ) -> s_schema.Schema:
         pop = self.get_parent_op(context)
-        orig_schema = schema
-        schema = super().apply(schema, context)
+        orig_schema = context.current().original_schema
 
+        # We need to run the parent change *before* the children,
+        # or else the view update in the child might fail if a
+        # link table isn't created in the parent yet.
         if (
             not self.scls.generic(schema)
             and not self.scls.is_pure_computable(schema)
@@ -4555,7 +4563,7 @@ def apply(
             if orig_card != new_card:
                 self._alter_pointer_cardinality(schema, orig_schema, context)
 
-        return schema
+        return super()._alter_innards(schema, context)
 
 
 class AlterPropertyLowerCardinality(


Test patch:
diff --git a/tests/test_edgeql_ddl.py b/tests/test_edgeql_ddl.py
--- a/tests/test_edgeql_ddl.py
+++ b/tests/test_edgeql_ddl.py
@@ -12381,6 +12381,104 @@ async def test_edgeql_ddl_drop_incoming_link(self):
             delete Foo;
         """)
 
+    async def test_edgeql_ddl_set_multi_with_children_01(self):
+        await self.con.execute(r"""
+            create type Person { create link lover -> Person; };
+            create type NPC extending Person;
+            alter type Person { alter link lover { set multi; }; };
+        """)
+
+        await self.con.execute(r"""
+            drop type NPC;
+            drop type Person;
+        """)
+
+    async def test_edgeql_ddl_set_multi_with_children_02(self):
+        await self.con.execute(r"""
+            create abstract type Person { create link lover -> Person; };
+            create type NPC extending Person;
+            alter type Person { alter link lover { set multi; }; };
+        """)
+
+        await self.con.execute(r"""
+            drop type NPC;
+            drop type Person;
+        """)
+
+    async def test_edgeql_ddl_set_multi_with_children_03(self):
+        await self.con.execute(r"""
+            create type Person { create property foo -> str; };
+            create type NPC extending Person;
+            alter type Person { alter property foo { set multi; }; };
+        """)
+
+        await self.con.execute(r"""
+            drop type NPC;
+            drop type Person;
+        """)
+
+    async def test_edgeql_ddl_set_multi_with_children_04(self):
+        await self.con.execute(r"""
+            create abstract type Person { create property foo -> str; };
+            create type NPC extending Person;
+            alter type Person { alter property foo { set multi; }; };
+        """)
+
+        await self.con.execute(r"""
+            drop type NPC;
+            drop type Person;
+        """)
+
+    async def test_edgeql_ddl_set_single_with_children_01(self):
+        await self.con.execute(r"""
+            create abstract type Person { create multi link foo -> Person; };
+            create type NPC extending Person;
+            alter type Person alter link foo {
+                set single USING (SELECT .foo LIMIT 1);
+            };
+        """)
+
+        await self.con.execute(r"""
+            drop type NPC;
+            drop type Person;
+        """)
+
+    async def test_edgeql_ddl_set_single_with_children_02(self):
+        await self.con.execute(r"""
+            create abstract type Person { create multi property foo -> str; };
+            create type NPC extending Person;
+            alter type Person alter property foo {
+                set single USING (SELECT .foo LIMIT 1);
+            };
+        """)
+
+        await self.con.execute(r"""
+            drop type NPC;
+            drop type Person;
+        """)
+
+    async def test_edgeql_ddl_drop_multi_child_01(self):
+        await self.con.execute(r"""
+            create abstract type Person { create multi property foo -> str; };
+            create type NPC extending Person;
+        """)
+
+        await self.con.execute(r"""
+            drop type NPC;
+            drop type Person;
+        """)
+
+    async def test_edgeql_ddl_drop_multi_child_02(self):
+        await self.con.execute(r"""
+            create abstract type Person { create multi link foo -> Person; };
+            create type NPC extending Person;
+        """)
+
+        await self.con.execute(r"""
+            drop type NPC;
+            drop type Person;
+        """)
+
 
 class TestConsecutiveMigrations(tb.DDLTestCase):
     TRANSACTION_ISOLATION = False