Repository: edgedb/edgedb

Issue:
ISE when adding EXTENDING to a link and making it non-computed at the same time
- EdgeDB Version: 2.14+7aec755
- EdgeDB CLI Version: 2.3.1+ef99779
- OS Version: Ubuntu 20.04.5 LTS

I tried to migrate my database to the newest schema update, but the migration has failed with an InternalServerError:

```text
edgedb error: InternalServerError: column 7474fe5b-9c4d-11ed-9d45-a9010fbd7a5e.a2f4b18f-e5e7-11ed-b37a-d3fa41f211f2 does not exist
  Server traceback:
      edb.errors.InternalServerError: column 7474fe5b-9c4d-11ed-9d45-a9010fbd7a5e.a2f4b18f-e5e7-11ed-b37a-d3fa41f211f2 does not exist
```

Migration:

```edgeql
CREATE MIGRATION m1oneswk7chg5cyhzgv6muoniyjsdc32jkqs6cdztuogd5nve2zdyq
    ONTO m1h3hqfkputkuh4mqb7mzg6jelupg3x6lxoa4owixd7ihsm6idrpxa
{
  CREATE ABSTRACT LINK default::with_linked_at {
      CREATE PROPERTY linked_at -> std::datetime {
          SET default := (std::datetime_of_statement());
          SET readonly := true;
      };
  };
  ALTER TYPE default::User {
      ALTER LINK albums {
          EXTENDING default::with_linked_at LAST;
      };
      ALTER LINK artists {
          EXTENDING default::with_linked_at LAST;
      };
      ALTER LINK following {
          EXTENDING default::with_linked_at LAST;
      };
      ALTER LINK tracks {
          EXTENDING default::with_linked_at LAST;
      };
  };
  ALTER TYPE default::Playlist {
      CREATE MULTI LINK followers EXTENDING default::with_linked_at -> default::User;
  };
  CREATE SCALAR TYPE default::position EXTENDING std::int64 {
      CREATE CONSTRAINT std::min_value(0);
  };
  CREATE ABSTRACT LINK default::with_position {
      CREATE PROPERTY position -> default::position {
          SET default := 0;
      };
  };
  ALTER TYPE default::Playlist {
      ALTER LINK tracks {
          EXTENDING default::with_linked_at,
          default::with_position LAST;
      };
  };
  ALTER TYPE default::User {
      ALTER LINK following {
          RESET EXPRESSION;
          RESET EXPRESSION;
          RESET OPTIONALITY;
          SET TYPE default::User;
      };
  };
  ALTER TYPE default::Album {
      ALTER LINK tracks {
          EXTENDING default::with_position LAST;
      };
  };
  ALTER TYPE default::User {
      CREATE MULTI LINK following_playlists := (.<followers[IS default::Playlist]);
  };
  ALTER TYPE default::Playlist {
      CREATE PROPERTY follower_count := (std::count(.followers));
  };
  ALTER TYPE default::User {
      ALTER LINK followers {
          USING (.<following[IS default::User]);
      };
  };
  ALTER TYPE default::User {
      ALTER LINK friends {
          USING (SELECT
              .following
          FILTER
              (.following IN .followers)
          );
      };
  };
};
```

Schema:

```esdl
module default {
    scalar type AlbumType extending enum<`album`, `single`, `compilation`>;
    scalar type PrivacyType extending enum<`public`, `friends`, `private`>;

    abstract type CreatedAt {
        required property created_at -> datetime {
            default := datetime_of_statement();
            readonly := true;
        };
    }

    scalar type duration_ms extending int64 {
        constraint min_value(0);
    }

    scalar type position extending int64 {
        constraint min_value(0);
    }

    abstract link with_position {
        property position -> position {
            default := 0;
        };
    }

    abstract link with_linked_at {
        property linked_at -> datetime {
            default := datetime_of_statement();
            readonly := true;
        };
    }

    abstract type Entity extending CreatedAt {
        required property name -> str;

        property spotify_id -> str;
        property apple_music_id -> str;
        property yandex_music_id -> str;
    }

    abstract type Genres {
        required property genres -> array<str> {
            default := <array<str>>[];
        };
    }

    type Track extending Entity, Genres {
        required multi link artists -> Artist;

        required property explicit -> bool {
            default := false;
        };

        required property duration_ms -> duration_ms;

        multi link streams := .<track[is Stream];

        property stream_count := count(.streams);
        property stream_duration_ms := sum(.streams.duration_ms);

        link album := assert_single(.<tracks[is Album]);
    }

    type Artist extending Entity, Genres {
        multi link followers := .<artists[is User];

        property follower_count := count(.followers);

        multi link streams := .<artists[is Track].<track[is Stream];

        property stream_count := count(.streams);
        property stream_duration_ms := sum(.streams.duration_ms);

        multi link tracks := .<artists[is Track];
        multi link albums := .<artists[is Album];
    }

    type Album extending Entity, Genres {
        required multi link artists -> Artist;
        required multi link tracks extending with_position -> Track;

        required property album_type -> AlbumType {
            default := AlbumType.album;
        };

        required property release_date -> cal::local_date;

        property label -> str;

        property duration_ms := sum(.tracks.duration_ms);

        property track_count := count(.tracks);
    }

    type Playlist extending Entity {
        required link user -> User {
            on target delete delete source;
        };

        multi link followers extending with_linked_at -> User;

        property follower_count := count(.followers);

        multi link tracks extending with_linked_at, with_position -> Track;

        required property description -> str {
            default := "";
        };

        required property privacy_type -> PrivacyType {
            default := PrivacyType.public;
        };

        property duration_ms := sum(.tracks.duration_ms);

        property track_count := count(.tracks);
    }

    type Stream extending CreatedAt {
        required link user -> User {
            on target delete delete source;
        };

        required link track -> Track {
            on target delete delete source;
        };

        required property duration_ms -> duration_ms;
    }

    type User extending Entity {
        multi link tracks extending with_linked_at -> Track;
        multi link albums extending with_linked_at -> Album;
        multi link artists extending with_linked_at -> Artist;

        multi link playlists := .<user[is Playlist];

        multi link following extending with_linked_at -> User;

        multi link followers := .<following[is User];

        multi link friends := (select .following filter .following in .followers);

        property follower_count := count(.followers);

        multi link following_playlists := .<followers[is Playlist];

        multi link streams := .<user[is Stream];

        property stream_count := count(.streams);
        property stream_duration_ms := sum(.streams.duration_ms);

        required property verified -> bool {
            default := false;
        };

        required property premium -> bool {
            default := false;
        }

        required property privacy_type -> PrivacyType {
            default := PrivacyType.public;
        };

        required property email -> str {
            constraint exclusive;
        };
        required property password_hash -> str;
    }
}
```



Discussion:
Could you please share the schema from before trying to apply this migration?
(I probably *could* reverse engineer it from the migration, but it would be best if you could share it.)
I'm not exactly sure what the schema looked like before the update, but the entire commit history on the file is [here](https://github.com/MelodyKit/melody.web/commits/main/schema/default.esdl).
Thanks! I have a minimization:
From this schema:
```
type T { multi link following := T; }
```
Try to migrate to
```
abstract link abs { property foo: str };
type T { multi link following extending abs -> T; }
```
which gives the migration
```
CREATE ABSTRACT LINK default::abs {
    CREATE PROPERTY foo: std::str;
};
ALTER TYPE default::T {
    ALTER LINK following {
        EXTENDING default::abs LAST;
    };
    ALTER LINK following {
        RESET EXPRESSION;
        RESET EXPRESSION;
        RESET OPTIONALITY;
        SET TYPE default::T;
    };
};
```
I think the problem then is that we are updating inhviews when doing the rebase in a way that fails to realize that the link table for following doesn't actually exist yet.

The solution here is trickier than just fixing that, though, because I don't think that computed pointers should have abstract bases anyway. Computed links won't have the appropriate link properties.
This is trouble because the generated migration shouldn't be valid or generated.

Resolving patch:
diff --git a/edb/schema/ordering.py b/edb/schema/ordering.py
--- a/edb/schema/ordering.py
+++ b/edb/schema/ordering.py
@@ -224,7 +224,7 @@ def maybe_replace_preceding(
             isinstance(op, sd.DeleteObject)
             or (
                 isinstance(op, sd.AlterObject)
-                and op.get_nonattr_special_subcommand_count() == 0
+                and op.get_nonattr_subcommand_count() == 0
             )
         ):
             return False
@@ -248,6 +248,7 @@ def maybe_replace_preceding(
                 isinstance(parents[op], sd.DeltaRoot)
                 != isinstance(parents[alter_op], sd.DeltaRoot)
             )
+            or bool(alter_op.get_subcommands(type=sd.RenameObject))
         ):
             return False
 
diff --git a/edb/schema/pointers.py b/edb/schema/pointers.py
--- a/edb/schema/pointers.py
+++ b/edb/schema/pointers.py
@@ -1584,6 +1584,22 @@ def _validate_computables(
 
         is_computable = scls.is_pure_computable(schema)
         is_owned = scls.get_owned(schema)
+
+        if is_computable:
+            if any(
+                b.generic(schema)
+                and not str(b.get_name(schema)) in (
+                    'std::link', 'std::property')
+                for b in scls.get_bases(schema).objects(schema)
+            ):
+                raise errors.SchemaDefinitionError(
+                    f'it is illegal for the computed '
+                    f'{scls.get_verbosename(schema, with_parent=True)} '
+                    f'to extend an abstract '
+                    f'{scls.get_schema_class_displayname()}',
+                    context=self.source_context,
+                )
+
         # Get the non-generic, explicitly declared ancestors as the
         # limitations on computables apply to explicitly declared
         # pointers, not just a long chain of inherited ones.


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
@@ -2209,6 +2209,15 @@ async def test_edgeql_migration_describe_abs_ptr_01(self):
             },
         })
 
+    async def test_edgeql_migration_abs_ptr_01(self):
+        await self.migrate(r"""
+            type T { multi link following := T; }
+        """)
+        await self.migrate(r"""
+            abstract link abs { property foo: str };
+            type T { multi link following extending abs -> T; }
+        """)
+
     async def test_edgeql_migration_describe_function_01(self):
         await self.migrate('''
             function foo(x: str) -> str using (SELECT <str>random());
@@ -5922,7 +5931,7 @@ async def test_edgeql_migration_rebase_02(self):
         await self.interact([
             "did you create object type 'test::HasContent'?",
             "did you alter object type 'test::Post'?",
-            "did you alter object type 'test::Post'?",
+            "did you alter property 'content' of object type 'test::Post'?",
             "did you create object type 'test::Reply'?",
         ])
 
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -341,6 +341,16 @@ def test_schema_bad_link_05(self):
             };
         """
 
+    @tb.must_fail(
+        errors.SchemaDefinitionError,
+        "illegal for the computed link.*to extend an abstract link",
+    )
+    def test_schema_bad_link_06(self):
+        """
+            abstract link abs { property foo: str };
+            type T { multi link following extending abs -> T {using (T)} }
+        """
+
     @tb.must_fail(errors.InvalidPropertyTargetError,
                   "invalid property type: expected a scalar type, "
                   "or a scalar collection, got object type 'test::Object'",