Repository: edgedb/edgedb

Issue:
Migration error: unable to create type with index
<!-- Please search existing issues to avoid creating duplicates. -->
- EdgeDB Version: `1.0-alpha.2+dev.505.g5b68ec24`

Steps to Reproduce:

Running this migration
```
START TRANSACTION;
CREATE MIGRATION test TO {
    module default {
        type Author {
            required property name -> str;
        }

        type Comment {
            required property body -> str;
        }

        type CommentRating {
            required link author -> Author;
            required link comment -> Comment;

            index on ((__subject__.author, __subject__.comment));
        }
    }
};
COMMIT MIGRATION test;
COMMIT;
```
results in crypric error: `InvalidReferenceError: schema item 'default::__|author@@default|CommentRating' does not exist`

It looks like there is a problem with index creation: creating type before migration works. Working code:
```
START TRANSACTION;

CREATE TYPE CommentRating;  # <----- added

CREATE MIGRATION test TO {
    module default {
        type Author {
            required property name -> str;
        }

        type Comment {
            required property body -> str;
        }

        type CommentRating {
            required link author -> Author;
            required link comment -> Comment;

            index on ((__subject__.author, __subject__.comment));
        }
    }
};
COMMIT MIGRATION test;
COMMIT;
```


Discussion:
Confirmed.  This is a DDL dependency sorting bug.

Resolving patch:
diff --git a/edb/schema/ordering.py b/edb/schema/ordering.py
--- a/edb/schema/ordering.py
+++ b/edb/schema/ordering.py
@@ -87,9 +87,7 @@ def linearize_delta(
             dep_stack = opmap[dep_op]
             dep_parent = _get_parent_op(dep_stack)
             assert isinstance(dep_parent, sd.ObjectCommand)
-            if ((dep_item['tag'], dep_parent.classname)
-                    != (info['tag'], parent.classname)):
-                dependencies[op].add(dep_op)
+            dependencies[op].add(dep_op)
 
     for _key, info in ordered:
         op = info['op']
@@ -101,58 +99,48 @@ def linearize_delta(
         opstack = opmap[op]
         parent = _get_parent_op(opstack)
 
-        reattachment_offset = 1
+        reattachment_depth = 1
 
-        for offset, ancestor_op in enumerate(reversed(opstack[1:-1])):
+        for depth, ancestor_op in enumerate(reversed(opstack[1:-1])):
             assert isinstance(ancestor_op, sd.ObjectCommand)
             mcls = ancestor_op.get_schema_metaclass()
             create_cmd_cls = sd.ObjectCommandMeta.get_command_class_or_die(
                 sd.CreateObject, mcls)
-            ancestor_key: Tuple[Type[sd.ObjectCommand], sn.Name] = (
-                create_cmd_cls, ancestor_op.classname)
-
-            attached_parent = parents.get(ancestor_key)
-            if attached_parent is not None:
-                parent_offset = offsets[attached_parent]
-                deps = dependencies.get(op)
-
-                ok_to_reattach = (
-                    not deps
-                    or all(offsets.get(dep, max_offset) < parent_offset
-                           for dep in deps)
-                )
-
-                if ok_to_reattach:
-                    reattachment_offset = -(offset + 1)
-                    attached_parent.add(opstack[reattachment_offset])
-                    offset = parent_offset
-
-                break
-
-            ancestor_key = (type(ancestor_op), ancestor_op.classname)
-            attached_parent = parents.get(ancestor_key)
-            if attached_parent is not None:
-                parent_offset = offsets[attached_parent]
-                deps = dependencies.get(op)
-                ok_to_reattach = (
-                    not deps
-                    or all(offsets.get(dep, max_offset) < parent_offset
-                           for dep in deps)
-                )
-
-                if ok_to_reattach:
-                    reattachment_offset = -(offset + 1)
-                    attached_parent.add(opstack[reattachment_offset])
-                    offset = parent_offset
-
+            attached_root: Optional[sd.ObjectCommand] = None
+
+            # Try attaching to a "Create" op, if that doesn't work
+            # attach to whatever the ancestor is right now.
+            for ancestor_cls in [create_cmd_cls, type(ancestor_op)]:
+                ancestor_key: Tuple[Type[sd.ObjectCommand], sn.Name] = (
+                    ancestor_cls, ancestor_op.classname)
+                # The root operation is the top-level operation in the delta.
+                attached_root = parents.get(ancestor_key)
+                if attached_root is not None:
+                    root_offset = offsets[attached_root]
+                    deps = dependencies.get(op, set())
+
+                    # It's OK to reattach if no dependency has offset
+                    # higher than our root operation.
+                    if all(offsets.get(dep, max_offset) <= root_offset
+                           for dep in deps):
+                        reattachment_depth = -(depth + 1)
+                        attached_root.add(opstack[reattachment_depth])
+                        # record where this branch was reattached
+                        offset = root_offset
+
+                    break
+
+            if attached_root is not None:
+                # As long as we found our root operation, we consider
+                # the potential reattachment process complete.
                 break
 
-        if reattachment_offset == 1:
+        if reattachment_depth == 1:
             # Haven't seen this op branch yet
             ops.append(parent)
             offset = len(ops) - 1
 
-        for op in opstack[reattachment_offset:]:
+        for op in opstack[reattachment_depth:]:
             if isinstance(op, sd.ObjectCommand):
                 ancestor_key = (type(op), op.classname)
                 parents[ancestor_key] = op


Test patch:
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -1552,6 +1552,28 @@ def test_get_migration_31(self):
 
         self._assert_migration_consistency(schema)
 
+    def test_get_migration_32(self):
+        # Test migration of index dependent on two links.
+        # Issue #1181
+        schema = r'''
+        type Author {
+            required property name -> str;
+        }
+
+        type Comment {
+            required property body -> str;
+        }
+
+        type CommentRating {
+            required link author -> Author;
+            required link comment -> Comment;
+
+            index on ((__subject__.author, __subject__.comment));
+        }
+        '''
+
+        self._assert_migration_consistency(schema)
+
     def test_get_migration_multi_module_01(self):
         schema = r'''
             # The two declared types declared are from different