Repository: edgedb/edgedb

Issue:
Overloaded index in SDL can be broken if processed in certain orders
```
module default {
  type Tag extending Named {
    index on (.name);
  }

  abstract type Named  {
    required property name -> str;
    index on (.name);
  }
}
```
can't populate a migration, failing with a diff like:
```
================================================================================
DESCRIBE CURRENT MIGRATION AS JSON mismatch
================================================================================
<DeltaRoot at 0x7f85ecc0b290> (
    <AlterObjectType canonical=True, classname=default::Tag, annotations={'confidence': 1.0}, orig_cmd_type=<class 'edb.schema.objtypes.AlterObjectType'> at 0x7f85ed40d910> (
        <AlterIndex canonical=True, classname=default::__|idx@default|Tag@3db4ce30d3905bf22576d6242da5a82c39092cbc, ddl_identity={'kwargs': edb.schema.expr.ExpressionDict({}), 'expr': <Expression text='.name', refs={a01f8090-5c1a-11ee-8010-db04e0348227}, origin='a0203ddf-5c1a-11ee-9fd4-7546bd34c5e0 expr' at 0x7f85ed46ff90>}, annotations={'confidence': 1.0} at 0x7f85ed46f6d0> (
            expr             = <ExpressionShell .name refs=(<ObjectShell Property(<QualName default::__|name@default|Tag>) at 0x7f85ed40e6d0>)> | <ExpressionShell .name refs=(<ObjectShell Property(<QualName default::__|name@default|Tag>) at 0x7f85ed46df90>)>,
            inherited_fields = edb.common.checked.FrozenCheckedSet[str]({'expr'}) | None
        )
    )
)
```

It works if `Named` comes first.


Discussion:

Resolving patch:
diff --git a/edb/schema/constraints.py b/edb/schema/constraints.py
--- a/edb/schema/constraints.py
+++ b/edb/schema/constraints.py
@@ -301,6 +301,12 @@ def get_ddl_identity(
         if (
             ddl_identity is not None
             and self.field_is_inherited(schema, 'subjectexpr')
+            and (bases := self.get_bases(schema).objects(schema))
+            and (
+                bases[0].generic(schema)
+                or 'subjectexpr' not in (
+                    bases[0].get_ddl_identity(schema) or ())
+            )
         ):
             ddl_identity.pop('subjectexpr', None)
 
@@ -1553,6 +1559,10 @@ class RebaseConstraint(
     ConstraintCommand,
     referencing.RebaseReferencedInheritingObject[Constraint],
 ):
+    # finalexpr is fully determined by a bunch of ddl_identity fields,
+    # so it should be inherited by compute_inherited_fields if they are
+    EXTRA_INHERITED_FIELDS = {'finalexpr'}
+
     def _get_bases_for_ast(
         self,
         schema: s_schema.Schema,
diff --git a/edb/schema/inheriting.py b/edb/schema/inheriting.py
--- a/edb/schema/inheriting.py
+++ b/edb/schema/inheriting.py
@@ -1084,6 +1084,8 @@ class RebaseInheritingObject(
     removed_bases = struct.Field(tuple)  # type: ignore
     added_bases = struct.Field(tuple)  # type: ignore
 
+    EXTRA_INHERITED_FIELDS: set[str] = set()
+
     def __repr__(self) -> str:
         return '<%s.%s "%s">' % (self.__class__.__module__,
                                  self.__class__.__name__,
@@ -1122,6 +1124,39 @@ def _alter_finalize(
 
         return schema
 
+    def compute_inherited_fields(
+        self,
+        schema: s_schema.Schema,
+        context: sd.CommandContext,
+    ) -> Dict[str, bool]:
+        result = super().compute_inherited_fields(schema, context)
+
+        # When things like indexes and constraints that use
+        # ddl_identity to define their identity are inherited, the
+        # child should inherit all of those fields, even if the object
+        # is owned in the child.
+        # Make this happen when rebasing.
+        mcls = self.get_schema_metaclass()
+        new_bases = self.get_attribute_value('bases').objects(schema)
+
+        inherit = new_bases and not new_bases[0].get_abstract(schema)
+
+        fields = {
+            field.name for field in mcls.get_fields().values()
+            if field.ddl_identity and field.inheritable
+        }
+        fields.update(self.EXTRA_INHERITED_FIELDS)
+
+        for field in fields:
+            if field not in result:
+                result[field] = (
+                    inherit
+                    and bool(new_bases[0].get_explicit_field_value(
+                        schema, field, None))
+                )
+
+        return result
+
     def canonicalize_attributes(
         self,
         schema: s_schema.Schema,


Test patch:
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -8247,6 +8247,40 @@ def test_schema_migrations_implicit_type_02(self):
             """,
         ])
 
+    def test_schema_migrations_inh_ordering_01(self):
+        self._assert_migration_equivalence([
+            r"""
+            type Tag extending Named {
+              index on (.name);
+              constraint expression on (.name != "");
+              constraint max_value(10) on (.cnt);
+            }
+
+            abstract type Named  {
+              required property name -> str;
+              required property cnt -> int64;
+              index on (.name);
+              constraint expression on (.name != "");
+              constraint max_value(10) on (.cnt);
+            }
+            """,
+            r"""
+            abstract type Named  {
+              required property name -> str;
+              required property cnt -> int64;
+              index on (.name);
+              constraint expression on (.name != "");
+              constraint max_value(10) on (.cnt);
+            }
+
+            type Tag extending Named {
+              index on (.name);
+              constraint expression on (.name != "");
+              constraint max_value(10) on (.cnt);
+            }
+            """,
+        ])
+
 
 class TestDescribe(tb.BaseSchemaLoadTest):
     """Test the DESCRIBE command."""