Repository: edgedb/edgedb

Issue:
Duplicated item on select if two select policies exists
<!-- Please search existing issues to avoid creating duplicates. -->
- EdgeDB Version: 
    EdgeDB CLI 2.0.4+c7a8cb4
    EgdeDB 2.1+c73716f
- OS Version: linux lts

Steps to Reproduce:
1. Create two users, and 1 post with one of the user as .author;
2. set global current_user_id with the author user id;
3. update the post .reviewer with the second user;
4. select the Post.

The select will return the same object twice.
It doesn't occurs if the select policy expressions are unified or only the `.author` is defined;

<!-- If the issue is about a query error, please also provide your schema -->
Schema:
[example schema](https://github.com/lu-zen/edgedb-duplicated-select)


Discussion:
Fascinatingly, it seems that access policies are actually a red herring here, and it is a bug in computed globals. If we have two `User`s, and do:
```
select (detached global current_user, global current_user);
```
we get back two results! The second global reference is somehow becoming just a `User` reference.

Resolving patch:
diff --git a/edb/edgeql/compiler/context.py b/edb/edgeql/compiler/context.py
--- a/edb/edgeql/compiler/context.py
+++ b/edb/edgeql/compiler/context.py
@@ -132,7 +132,7 @@ class Environment:
     path_scope: irast.ScopeTreeNode
     """Overrall expression path scope tree."""
 
-    schema_view_cache: Dict[s_types.Type, s_types.Type]
+    schema_view_cache: Dict[s_types.Type, tuple[s_types.Type, irast.Set]]
     """Type cache used by schema-level views."""
 
     query_parameters: Dict[str, irast.Param]
diff --git a/edb/edgeql/compiler/stmtctx.py b/edb/edgeql/compiler/stmtctx.py
--- a/edb/edgeql/compiler/stmtctx.py
+++ b/edb/edgeql/compiler/stmtctx.py
@@ -661,13 +661,16 @@ def declare_view(
     return view_set
 
 
-def declare_view_from_schema(
+def _declare_view_from_schema(
         viewcls: s_types.Type, *,
-        ctx: context.ContextLevel) -> s_types.Type:
-    vc = ctx.env.schema_view_cache.get(viewcls)
-    if vc is not None:
-        return vc
-
+        ctx: context.ContextLevel) -> tuple[s_types.Type, irast.Set]:
+    e = ctx.env.schema_view_cache.get(viewcls)
+    if e is not None:
+        return e
+
+    # N.B: This takes a context, which we need to use to create a
+    # subcontext to compile in, but it should avoid depending on the
+    # context, because of the cache.
     with ctx.detached() as subctx:
         subctx.expr_exposed = context.Exposure.UNEXPOSED
         view_expr = viewcls.get_expr(ctx.env.schema)
@@ -679,16 +682,25 @@ def declare_view_from_schema(
                                 binding_kind=irast.BindingKind.With,
                                 fully_detached=True, ctx=subctx)
         # The view path id _itself_ should not be in the nested namespace.
-        view_set.path_id = view_set.path_id.replace_namespace(
-            ctx.path_id_namespace)
+        view_set.path_id = view_set.path_id.replace_namespace(frozenset())
 
         vc = subctx.aliased_views[viewcls_name]
         assert vc is not None
-        ctx.env.schema_view_cache[viewcls] = vc
-        ctx.source_map.update(subctx.source_map)
-        ctx.aliased_views[viewcls_name] = subctx.aliased_views[viewcls_name]
-        ctx.view_nodes[vc.get_name(ctx.env.schema)] = vc
-        ctx.view_sets[vc] = subctx.view_sets[vc]
+        ctx.env.schema_view_cache[viewcls] = vc, view_set
+
+    return vc, view_set
+
+
+def declare_view_from_schema(
+        viewcls: s_types.Type, *,
+        ctx: context.ContextLevel) -> s_types.Type:
+    vc, view_set = _declare_view_from_schema(viewcls, ctx=ctx)
+
+    viewcls_name = viewcls.get_name(ctx.env.schema)
+
+    ctx.aliased_views[viewcls_name] = vc
+    ctx.view_nodes[vc.get_name(ctx.env.schema)] = vc
+    ctx.view_sets[vc] = view_set
 
     return vc
 


Test patch:
diff --git a/tests/test_edgeql_expr_aliases.py b/tests/test_edgeql_expr_aliases.py
--- a/tests/test_edgeql_expr_aliases.py
+++ b/tests/test_edgeql_expr_aliases.py
@@ -1106,3 +1106,11 @@ async def test_edgeql_aliases_helper_01(self):
             await self.con.execute("""
                 SELECT __AwardAlias2__winner
             """)
+
+    async def test_edgeql_aliases_detached_01(self):
+        await self.assert_query_result(
+            r"""
+                select count((detached FireCard, detached FireCard))
+            """,
+            [4]
+        )