Repository: edgedb/edgedb

Issue:
Removing `required` constraint on link fails to create migration
<!-- Please search existing issues to avoid creating duplicates. -->
- EdgeDB Version: EdgeDB CLI 2.0.5+53f9c9b, EdgeDB 2.1+9c4bc9f
- OS Version: macOS 12.6

(Follow up from [discord](https://discord.com/channels/841451783728529451/841451783728529454/1020400707406405783))

Steps to Reproduce:

1. Drop a `required` constraint on a link, making it optional.

```
-        required link subject -> SignalSubject;
+        link subject -> SignalSubject;
```

```
$  edgedb migration create
did you make link 'subject' of object type 'default::Signal' optional? [y,n,l,c,b,s,q,?]
> l
The following DDL statements will be applied:
    ALTER TYPE default::Signal {
        ALTER LINK subject {
            RESET OPTIONALITY;
        };
    };
did you make link 'subject' of object type 'default::Signal' optional? [y,n,l,c,b,s,q,?]
> y
edgedb error: EdgeDB could not resolve migration with the provided answers. Please retry with different answers.
```

<!-- If the issue is about a query error, please also provide your schema -->
Schema:



Discussion:
This appears more complicated than the above; just made the same change on another `required link` successfully:

```
  ALTER TYPE default::StrategyNode {
      ALTER LINK documentVersion {
          RESET OPTIONALITY;
      };
  };
 ```
 
 Both required links were originally created the same way. From the original migration,

```
CREATE TYPE default::StrategyNode EXTENDING default::SignalSubject {
        CREATE REQUIRED LINK documentVersion -> default::DocumentVersion;
...
CREATE TYPE default::Signal {
        CREATE REQUIRED LINK subject -> default::SignalSubject;
```


The only difference I see is that the target of the failing link is abstract.
I was unable to reproduce this issue while doing the obvious thing, so there must be something more subtle going on that is triggering this for you.
Do you have a more complete example of a schema where it is failing? Are there a lot of migrations that have been run already?
Attempted to reproduce more cleanly [here](https://github.com/jackfischer/edgedb-4371).

Unfortunately my theory it was due to `SignalSubject` being abstract is wrong
https://github.com/jackfischer/edgedb-4371/commit/4bee1e78aa04edb46e1caa56ca9605ca5d5b7481
Found it!!

Making the following change to remove the computed backlink for this relationship allowed the removal of `required`. 

```
   abstract type SignalSubject {
-        multi link signals := .<subject[is Signal];
+        # multi link signals := .<subject[is Signal];
     }
```
Great! Do you have a small reproducing case you can share, then?
Yup live here - https://github.com/jackfischer/edgedb-4371, `default.esdl` is edited so it's ready for the failing `edgedb migration create`. 

Resolving patch:
diff --git a/edb/schema/delta.py b/edb/schema/delta.py
--- a/edb/schema/delta.py
+++ b/edb/schema/delta.py
@@ -1990,6 +1990,7 @@ def _propagate_if_expr_refs(
                     schema, context, cmdtype=AlterObject)
                 delta_create, cmd_create, _ = ref.init_delta_branch(
                     schema, context, cmdtype=AlterObject)
+
                 # Mark it metadata_only so that if it actually gets
                 # applied, only the metadata is changed but not
                 # the real underlying schema.
@@ -1997,6 +1998,12 @@ def _propagate_if_expr_refs(
                     cmd_drop.metadata_only = True
                     cmd_create.metadata_only = True
 
+                    # Treat the drop as canonical, since we only need
+                    # to eliminate the reference, not get to a fully
+                    # consistent state, and the canonicalization can
+                    # mess up "associated" attributes.
+                    cmd_drop.canonical = True
+
                 try:
                     # Compute a dummy value
                     dummy = cmd_create.get_dummy_expr_field_value(
diff --git a/edb/schema/pointers.py b/edb/schema/pointers.py
--- a/edb/schema/pointers.py
+++ b/edb/schema/pointers.py
@@ -1941,6 +1941,7 @@ def canonicalize_attributes(
                 # If the expression was explicitly set to None,
                 # that means that `RESET EXPRESSION` was executed
                 # and this is no longer a computable.
+
                 self.set_attribute_value('computable', False)
                 computed_fields = pointer.get_computed_fields(schema)
                 if (


Test patch:
diff --git a/tests/test_edgeql_data_migration.py b/tests/test_edgeql_data_migration.py
--- a/tests/test_edgeql_data_migration.py
+++ b/tests/test_edgeql_data_migration.py
@@ -11078,6 +11078,31 @@ async def test_edgeql_migration_policies_and_collections(self):
             alias Foo := 20;
         """)
 
+    async def test_edgeql_migration_drop_required_01(self):
+        await self.migrate(r"""
+            abstract type AbstractLinkTarget {
+                multi link linkSources := .<abstractTarget[is LinkSource];
+            }
+
+            type ImplementationType extending AbstractLinkTarget {}
+
+            type LinkSource {
+                required link abstractTarget -> AbstractLinkTarget;
+            }
+        """)
+
+        await self.migrate(r"""
+            abstract type AbstractLinkTarget {
+                multi link linkSources := .<abstractTarget[is LinkSource];
+            }
+
+            type ImplementationType extending AbstractLinkTarget {}
+
+            type LinkSource {
+                link abstractTarget -> AbstractLinkTarget;
+            }
+        """)
+
 
 class TestEdgeQLDataMigrationNonisolated(EdgeQLDataMigrationTestCase):
     TRANSACTION_ISOLATION = False