Repository: edgedb/edgedb

Issue:
cannot alter type of a column used in a trigger definition
```
edgedb error: ERROR: InternalServerError: cannot alter type of a column used in a trigger definition
  Hint: This is most likely a bug in EdgeDB. Please consider opening an issue ticket at https://github.com/edgedb/edgedb/issues/new?template=bug_report.md
  Server traceback:
      edb.errors.InternalServerError: cannot alter type of a column used in a trigger definition
```
I discovered this while trying to reproduce https://github.com/edgedb/edgedb/issues/2298 not sure if they are related.

<!-- Please search existing issues to avoid creating duplicates. -->
- EdgeDB Version: 1.0b2.dev5500+g2f67186c8-2021030200~focal.nightly
- OS Version: Linux 5.8.0-44-generic #50~20.04.1-Ubuntu SMP Wed Feb 10 21:07:30 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

Steps to Reproduce:
```bash
#!/usr/bin/env bash

set -ex

cd `mktemp --directory`
mkdir -p dbschema/migrations

edgedb server init --nightly bug
echo 'module default {
	type MyObject {
		required property number -> int16 {
			constraint exclusive;
		}
	}
};' > dbschema/schema.esdl
yes | edgedb -I bug create-migration
edgedb -I bug migrate
echo 'module default {
	scalar type MySequence extending sequence;

	type MyObject {
		required property number -> MySequence {
			constraint exclusive;
		}
	}
};' > dbschema/schema.esdl
yes | edgedb -I bug create-migration
edgedb -I bug migrate
```


Discussion:
@msullivan, we need to cycle constraint triggers when compiling `SET TYPE`.

Resolving patch:
diff --git a/edb/pgsql/delta.py b/edb/pgsql/delta.py
--- a/edb/pgsql/delta.py
+++ b/edb/pgsql/delta.py
@@ -1303,7 +1303,8 @@ class ConstraintCommand(sd.ObjectCommand,
                         metaclass=CommandMeta):
     op_priority = 3
 
-    def constraint_is_effective(self, schema, constraint):
+    @classmethod
+    def constraint_is_effective(cls, schema, constraint):
         subject = constraint.get_subject(schema)
         if subject is None:
             return False
@@ -1329,6 +1330,44 @@ def constraint_is_effective(self, schema, constraint):
         else:
             return True
 
+    @classmethod
+    def create_constraint(
+            cls, constraint, schema, context, source_context=None):
+        op = dbops.CommandGroup(priority=1)
+        if cls.constraint_is_effective(schema, constraint):
+            subject = constraint.get_subject(schema)
+
+            if subject is not None:
+                schemac_to_backendc = \
+                    schemamech.ConstraintMech.\
+                    schema_constraint_to_backend_constraint
+                bconstr = schemac_to_backendc(
+                    subject, constraint, schema, context,
+                    source_context)
+
+                op.add_command(bconstr.create_ops())
+
+        return op
+
+    @classmethod
+    def delete_constraint(
+            cls, constraint, schema, context, source_context=None):
+        op = dbops.CommandGroup()
+        if cls.constraint_is_effective(schema, constraint):
+            subject = constraint.get_subject(schema)
+
+            if subject is not None:
+                schemac_to_backendc = \
+                    schemamech.ConstraintMech.\
+                    schema_constraint_to_backend_constraint
+                bconstr = schemac_to_backendc(
+                    subject, constraint, schema, context,
+                    source_context)
+
+                op.add_command(bconstr.delete_ops())
+
+        return op
+
 
 class CreateConstraint(
         ConstraintCommand, CreateObject,
@@ -1340,21 +1379,10 @@ def apply(
     ) -> s_schema.Schema:
         schema = super().apply(schema, context)
         constraint = self.scls
-        if not self.constraint_is_effective(schema, constraint):
-            return schema
-
-        subject = constraint.get_subject(schema)
 
-        if subject is not None:
-            schemac_to_backendc = \
-                schemamech.ConstraintMech.\
-                schema_constraint_to_backend_constraint
-            bconstr = schemac_to_backendc(
-                subject, constraint, schema, context, self.source_context)
-
-            op = dbops.CommandGroup(priority=1)
-            op.add_command(bconstr.create_ops())
-            self.pgops.add(op)
+        op = self.create_constraint(
+            constraint, schema, context, self.source_context)
+        self.pgops.add(op)
 
         return schema
 
@@ -1497,20 +1525,9 @@ def apply(
         delta_root_ctx = context.top()
         orig_schema = delta_root_ctx.original_schema
         constraint = schema.get(self.classname)
-        if self.constraint_is_effective(orig_schema, constraint):
-            subject = constraint.get_subject(orig_schema)
-
-            if subject is not None:
-                schemac_to_backendc = \
-                    schemamech.ConstraintMech.\
-                    schema_constraint_to_backend_constraint
-                bconstr = schemac_to_backendc(
-                    subject, constraint, orig_schema, context,
-                    self.source_context)
-
-                op = dbops.CommandGroup()
-                op.add_command(bconstr.delete_ops())
-                self.pgops.add(op)
+        op = self.delete_constraint(
+            constraint, orig_schema, context, self.source_context)
+        self.pgops.add(op)
 
         schema = super().apply(schema, context)
         return schema
@@ -2756,6 +2773,24 @@ def _alter_pointer_optionality(
             )
             self.pgops.add(alter_table)
 
+    def _drop_constraints(self, pointer, schema, context):
+        # We need to be able to drop all the constraints referencing a
+        # pointer before modifying its type, and then recreate them
+        # once the change is done.
+        # We look at all referrers to the pointer (and not just the
+        # constraints directly on the pointer) because we want to
+        # pick up object constraints that reference it as well.
+        for cnstr in schema.get_referrers(
+                pointer, scls_type=s_constr.Constraint):
+            self.pgops.add(
+                ConstraintCommand.delete_constraint(cnstr, schema, context))
+
+    def _recreate_constraints(self, pointer, schema, context):
+        for cnstr in schema.get_referrers(
+                pointer, scls_type=s_constr.Constraint):
+            self.pgops.add(
+                ConstraintCommand.create_constraint(cnstr, schema, context))
+
     def _alter_pointer_type(self, pointer, schema, orig_schema, context):
         old_ptr_stor_info = types.get_pointer_storage_info(
             pointer, schema=orig_schema)
@@ -2800,6 +2835,9 @@ def _alter_pointer_type(self, pointer, schema, orig_schema, context):
         ):
             return
 
+        # We actually have work to do, so drop any constraints we have
+        self._drop_constraints(pointer, schema, context)
+
         if using_eql_expr is None and not is_link:
             # A lack of an explicit EdgeQL conversion expression means
             # that the new type is assignment-castable from the old type
@@ -2998,6 +3036,8 @@ def _alter_pointer_type(self, pointer, schema, orig_schema, context):
         if changing_col_type or need_temp_col:
             self.pgops.add(alter_table)
 
+        self._recreate_constraints(pointer, schema, context)
+
         if changing_col_type:
             self.schedule_inhviews_update(
                 schema,


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
@@ -2210,7 +2210,10 @@ async def test_edgeql_ddl_ptr_set_type_using_01(self):
             CREATE TYPE SubBar EXTENDING Bar;
 
             CREATE TYPE Foo {
-                CREATE PROPERTY p -> str;
+                CREATE PROPERTY p -> str {
+                    CREATE CONSTRAINT exclusive;
+                };
+                CREATE CONSTRAINT exclusive ON (.p);
                 CREATE REQUIRED PROPERTY r_p -> str;
                 CREATE MULTI PROPERTY m_p -> str;
                 CREATE REQUIRED MULTI PROPERTY rm_p -> str;