Repository: edgedb/edgedb

Issue:
Can't use backlinks if a link with the same name is computed
<!-- Please search existing issues to avoid creating duplicates. -->
- EdgeDB Version: 1.0b2+ga7130d5c7.cv202104290000  
- OS Version: macOS Big Sur / MacBook Air (M1, 2020) /  Memory: 8 GB 

Steps to Reproduce:

Simplified Schema;

```
using extension graphql;

module default {
    type User {
        required property email -> str {
            constraint exclusive;
        }
        multi link entries := .<author[IS Entry]; 
    }

    type Entry {
        link author -> User; 
        link topic := .<entries[IS Topic];
    }

    type Topic {
        multi link entries -> Entry; 
    }
};
```
GraphQL Query;
```
User {
      email
      entries {
        topic {
          id
        }
      }
}
```

When trying to use this query I am getting **"QueryError: relation \"edgedbpub.718fcee6-e765-11eb-9572-8bb96a65cb68_t\" does not exist"** error

With the following schema change it's working well. I think this is a name collision issue. I wonder if this is a bug or am I doing something wrong?

```
using extension graphql;

module default {
    type User {
        required property email -> str {
            constraint exclusive;
        }
        multi link entries := .<author[IS Entry]; 
    }

    type Entry {
        link author -> User; 
        link topic := .<topic_entries[IS Topic];
    }

    type Topic {
        multi link topic_entries -> Entry; 
    }
};
```



Discussion:
It is definitely a bug that you get *that* error!

Whether it should be accepted is a more complicated question, but I think the answer is "probably yes"? The tricky thing here is that we don't allow backlinks of computable links. (If you ask for `.<entries` in the second schema, you'll get an error). So then the questions are:
 * What should we do about `.<entries` if one of the links named `entries` is computable?
 * Should we do something different when there's a type intersection after, like `.<author[IS Topic]`?
@msullivan Will you add a bug tag to this issue? Otherwise this restriction should be documented. 
@msullivan Sully, has this one been fixed?
I know you are very busy with many issues and I really appreciate your efforts. Just wondering, is this issue will be fixed in short term? I've just started a pet project and this issue blocking me. Good luck!
@kafein yeah, this will either be fixed or made to produce an error (though I think probably fixed). It shouldn't be blocking you though, since you can work around it with a name change?
@msullivan Thanks for information. Yes I can do it, but generally I don't like workarounds much :) Just wanted to check to see if you have a plan to fix this. 

Great project, great team. Good luck!
It looks like we do now produce a proper error when trying to create the schema:
```cannot follow backlink 'entries' because link 'entries' of object type 'default::User' is computed```

Resolving patch:
diff --git a/edb/edgeql/compiler/setgen.py b/edb/edgeql/compiler/setgen.py
--- a/edb/edgeql/compiler/setgen.py
+++ b/edb/edgeql/compiler/setgen.py
@@ -658,31 +658,42 @@ def resolve_ptr(
         ptrs = near_endpoint.getrptrs(ctx.env.schema, pointer_name,
                                       sources=far_endpoints)
         if ptrs:
-            if track_ref is not False:
-                # If this reverse pointer access is followed by
-                # intersections, we filter out any pointers that
-                # couldn't be picked up by the intersections. This avoids
-                # creating spurious dependencies when reverse
-                # links are used in schemas.
-                dep_ptrs = {
-                    ptr for ptr in ptrs
-                    if (src := ptr.get_source(ctx.env.schema))
-                    and all(
-                        src.issubclass(ctx.env.schema, typ)
-                        or any(
-                            dsrc.issubclass(ctx.env.schema, typ)
-                            for dsrc in src.descendants(ctx.env.schema)
-                        )
-                        for typ in upcoming_intersections
+            # If this reverse pointer access is followed by
+            # intersections, we filter out any pointers that
+            # couldn't be picked up by the intersections. This avoids
+            # creating spurious dependencies when reverse
+            # links are used in schemas.
+            dep_ptrs = {
+                ptr for ptr in ptrs
+                if (src := ptr.get_source(ctx.env.schema))
+                and all(
+                    src.issubclass(ctx.env.schema, typ)
+                    or any(
+                        dsrc.issubclass(ctx.env.schema, typ)
+                        for dsrc in src.descendants(ctx.env.schema)
                     )
-                }
+                    for typ in upcoming_intersections
+                )
+            }
 
+            if track_ref is not False:
                 for p in dep_ptrs:
                     ctx.env.add_schema_ref(
                         p.get_nearest_non_derived_parent(ctx.env.schema),
                         track_ref)
+
+            # We can only compute backlinks for non-computed pointers,
+            # but we need to make sure that a computed pointer doesn't
+            # break properly-filtered backlinks.
+            concrete_ptrs = [
+                ptr for ptr in ptrs
+                if not ptr.is_pure_computable(ctx.env.schema)]
+
             for ptr in ptrs:
-                if ptr.is_pure_computable(ctx.env.schema):
+                if (
+                    ptr.is_pure_computable(ctx.env.schema)
+                    and (ptr in dep_ptrs or not concrete_ptrs)
+                ):
                     vname = ptr.get_verbosename(ctx.env.schema,
                                                 with_parent=True)
                     raise errors.InvalidReferenceError(
@@ -697,7 +708,7 @@ def resolve_ptr(
                 ptrname=s_name.UnqualName(pointer_name),
                 source=near_endpoint,
                 direction=direction,
-                components=ptrs,
+                components=concrete_ptrs,
                 opaque=opaque,
                 modname=ctx.derived_target_module,
             )


Test patch:
diff --git a/tests/test_edgeql_select.py b/tests/test_edgeql_select.py
--- a/tests/test_edgeql_select.py
+++ b/tests/test_edgeql_select.py
@@ -6182,3 +6182,33 @@ async def test_edgeql_select_bare_backlink_01(self):
             ''',
             [],
         )
+
+    async def test_edgeql_select_reverse_overload_03(self):
+        await self.con.execute('''
+            CREATE TYPE Dummy1 {
+                CREATE LINK whatever -> User;
+            };
+            CREATE TYPE Dummy2 {
+                CREATE LINK whatever := (SELECT User FILTER .name = 'Elvis');
+            };
+            INSERT Dummy1 { whatever := (SELECT User FILTER .name = 'Yury') };
+        ''')
+
+        # We should be able to query the whatever backlink as long as we
+        # filter it properly
+        await self.assert_query_result(
+            r'''
+                SELECT User.<whatever[IS Dummy1];
+            ''',
+            [{}],
+        )
+
+        with self.assertRaisesRegex(
+                edgedb.QueryError,
+                r"cannot follow backlink 'whatever' because link 'whatever' "
+                r"of object type 'default::Dummy2' is computed"):
+            await self.con.query(
+                r'''
+                    SELECT User.<whatever
+                ''',
+            )