Repository: edgedb/edgedb

Issue:
`assert_single` causes nested `select`s to return wrong results
- EdgeDB Version: 1.3+5d0197d

Investigating this issue: https://github.com/edgedb/edgedb-js/issues/330

The query the querybuilder generates looks correct:
```edgeql
SELECT std::assert_single((WITH
  __scope_0_teams := DETACHED default::teams
SELECT __scope_0_teams {
  id,
  name,
  slug,
  team_users := (
    WITH
      __scope_1_team_users := __scope_0_teams.team_users
    SELECT __scope_1_team_users {
      id,
      role,
      user := (
        WITH
          __scope_2_users := __scope_1_team_users.user
        SELECT __scope_2_users {
          id,
          name,
          email
        }
        LIMIT 1
      )
    }
  )
}))
```
but the result indeed has the wrong user for the second `team_users`:
```json
{
  "id": "4580ad20c4aa11ec8df6970388680a9f",
  "name": "The A Team",
  "slug": "team_a",
  "team_users": [
    {
      "id": "0b42d696c4ab11ec991c1f2f1fdc585f",
      "role": "Owner",
      "user": {
        "id": "9eb3c06cc4aa11ec8df6af1e7a7a74b3",
        "name": "User a",
        "email": "usera@example.com"
      }
    },
    {
      "id": "12876b42c4ab11ec991c632dd3ae1049",
      "role": "Editor",
      "user": {
        "id": "9eb3c06cc4aa11ec8df6af1e7a7a74b3",
        "name": "User a",
        "email": "usera@example.com"
      }
    }
  ]
}
```

Weirdly running the same query in repl returns the correct result:
```
edgedb> SELECT std::assert_single((WITH
.......   __scope_0_teams := DETACHED default::teams
....... SELECT __scope_0_teams {
.......   id,
.......   name,
.......   slug,
.......   team_users := (
.......     WITH
.......       __scope_1_team_users := __scope_0_teams.team_users
.......     SELECT __scope_1_team_users {
.......       id,
.......       role,
.......       user := (
.......         WITH
.......           __scope_2_users := __scope_1_team_users.user
.......         SELECT __scope_2_users {
.......           id,
.......           name,
.......           email
.......         }
.......         LIMIT 1
.......       )
.......     }
.......   )
....... }));
{
  default::teams {
    id: 4580ad20-c4aa-11ec-8df6-970388680a9f,
    name: 'The A Team',
    slug: 'team_a',
    team_users: {
      default::team_users {
        id: 0b42d696-c4ab-11ec-991c-1f2f1fdc585f,
        role: Owner,
        user: default::users {id: 9eb3c06c-c4aa-11ec-8df6-af1e7a7a74b3, name: 'User a', email: 'usera@example.com'},
      },
      default::team_users {
        id: 12876b42-c4ab-11ec-991c-632dd3ae1049,
        role: Editor,
        user: default::users {id: 9eb3cb7a-c4aa-11ec-8df6-d3bbffd81bce, name: 'User b', email: 'userb@example.com'},
      },
    },
  },
}
```

Both the js client/querybuilder and repl return the expected result if the wrapping `assert_single` is removed:
```edgeql
SELECT (WITH
  __scope_0_teams := DETACHED default::teams
SELECT __scope_0_teams {
  id,
  name,
  slug,
  team_users := (
    WITH
      __scope_1_team_users := __scope_0_teams.team_users
    SELECT __scope_1_team_users {
      id,
      role,
      user := (
        WITH
          __scope_2_users := __scope_1_team_users.user
        SELECT __scope_2_users {
          id,
          name,
          email
        }
        LIMIT 1
      )
    }
  )
})
```
```json
[
  {
    "id": "4580ad20c4aa11ec8df6970388680a9f",
    "name": "The A Team",
    "slug": "team_a",
    "team_users": [
      {
        "id": "0b42d696c4ab11ec991c1f2f1fdc585f",
        "role": "Owner",
        "user": {
          "id": "9eb3c06cc4aa11ec8df6af1e7a7a74b3",
          "name": "User a",
          "email": "usera@example.com"
        }
      },
      {
        "id": "12876b42c4ab11ec991c632dd3ae1049",
        "role": "Editor",
        "user": {
          "id": "9eb3cb7ac4aa11ec8df6d3bbffd81bce",
          "name": "User b",
          "email": "userb@example.com"
        }
      }
    ]
  }
]
```

Schema:
```edgeql
CREATE MIGRATION m1twuydatkvh42y4fyomu6szyonvhcdtclydxvbzpzbv32ttkrvh3a
    ONTO initial
{
  CREATE SCALAR TYPE default::TeamRole EXTENDING enum<Owner, Editor>;
  CREATE TYPE default::team_users {
      CREATE REQUIRED PROPERTY role -> default::TeamRole;
  };
  CREATE TYPE default::teams {
      CREATE REQUIRED PROPERTY name -> std::str;
      CREATE REQUIRED PROPERTY slug -> std::str {
          CREATE CONSTRAINT std::exclusive;
      };
  };
  ALTER TYPE default::team_users {
      CREATE REQUIRED LINK team -> default::teams {
          ON TARGET DELETE  DELETE SOURCE;
      };
  };
  ALTER TYPE default::teams {
      CREATE MULTI LINK team_users := (.<team[IS default::team_users]);
  };
  CREATE TYPE default::users {
      CREATE REQUIRED PROPERTY email -> std::str {
          CREATE CONSTRAINT std::exclusive;
      };
      CREATE REQUIRED PROPERTY name -> std::str;
  };
  ALTER TYPE default::team_users {
      CREATE LINK user -> default::users {
          ON TARGET DELETE  DELETE SOURCE;
      };
  };
  ALTER TYPE default::users {
      CREATE MULTI LINK team_memberships := (.<user[IS default::team_users]);
  };

  insert teams {
    name := 'The A Team',
    slug := 'team_a'
  };

  for x in {'a', 'b'} union (
    insert users {
      email := 'user' ++ x ++ '@example.com',
      name := 'User ' ++ x
    }
  );

  insert team_users {
    user := (select users filter .email = 'usera@example.com'),
    team := (select teams limit 1),
    role := TeamRole.Owner
  };
  
  insert team_users {
    user := (select users filter .email = 'userb@example.com'),
    team := (select teams limit 1),
    role := TeamRole.Editor
  };
};
```



Discussion:
Looks like the repl injecting typenames 'fixes' the bug, it also shows the wrong result if you disable `display-typenames`:
```
edgedb> \set display-typenames off
edgedb> SELECT std::assert_single((WITH
.......   __scope_0_teams := DETACHED default::teams
....... SELECT __scope_0_teams {
.......   id,
.......   name,
.......   slug,
.......   team_users := (
.......     WITH
.......       __scope_1_team_users := __scope_0_teams.team_users
.......     SELECT __scope_1_team_users {
.......       id,
.......       role,
.......       user := (
.......         WITH
.......           __scope_2_users := __scope_1_team_users.user
.......         SELECT __scope_2_users {
.......           id,
.......           name,
.......           email
.......         }
.......         LIMIT 1
.......       )
.......     }
.......   )
....... }));
{
  Object {
    id: f961d8c6-c4b1-11ec-9232-6bf15f88e13d,
    name: 'The A Team',
    slug: 'team_a',
    team_users: {
      Object {
        id: f96237b2-c4b1-11ec-9232-6f9027fdaad9,
        role: Owner,
        user: Object {id: f962063e-c4b1-11ec-9232-bb553c0f874d, name: 'User a', email: 'usera@example.com'},
      },
      Object {
        id: f96272ae-c4b1-11ec-9232-5f1bb08ae708,
        role: Editor,
        user: Object {id: f962063e-c4b1-11ec-9232-bb553c0f874d, name: 'User a', email: 'usera@example.com'},
      },
    },
  },
}
```
A more reduced version is:
```
SELECT assert_single((                                                   
  SELECT teams {                                                         
  team_users := (                                                        
    WITH                                                                 
      __scope_1_team_users := teams.team_users                           
    SELECT __scope_1_team_users {                                        
      multi _user := (                                                   
        __scope_1_team_users.user { name }                               
      )                                                                  
    }                                                                    
  )                                                                      
})); 
```
If I drop the `multi`, it produces an ISE. 

Type name injection seems to fix it because with type names injected, it materializes the shape. But that seems like it's own bug; we shouldn't need to materialize here.

Just referring to `.user` instead of `__scope_1_team_users.user` fixes the issue.

Probably this is some `_get_computable_ctx` namespacing stuff. I bet maybe it is confusion between using `__scope_1_team_users` and `teams.team_users` as the path id...

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
@@ -1519,6 +1519,7 @@ def newctx() -> Iterator[context.ContextLevel]:
 
             subctx.view_nodes = qlctx.view_nodes.copy()
             subctx.view_map = ctx.view_map.new_child()
+            subctx.view_sets = ctx.view_sets.copy()
 
             source_scope = pathctx.get_set_scope(rptr.source, ctx=ctx)
             if source_scope and source_scope.namespaces:
diff --git a/edb/pgsql/compiler/pathctx.py b/edb/pgsql/compiler/pathctx.py
--- a/edb/pgsql/compiler/pathctx.py
+++ b/edb/pgsql/compiler/pathctx.py
@@ -346,10 +346,9 @@ def _find_rel_rvar(
             rel_rvar = maybe_get_path_rvar(
                 rel, src_path_id, aspect='source', env=env)
 
-    if rel_rvar is None and alt_aspect is not None:
+    if rel_rvar is None and alt_aspect is not None and flavor == 'normal':
         # There is no source range var for the requested aspect,
         # check if there is a cached var with less specificity.
-        assert flavor == 'normal'
         var = rel.path_namespace.get((path_id, alt_aspect))
         if var is not None:
             put_path_var(


Test patch:
diff --git a/tests/test_edgeql_scope.py b/tests/test_edgeql_scope.py
--- a/tests/test_edgeql_scope.py
+++ b/tests/test_edgeql_scope.py
@@ -3719,6 +3719,62 @@ async def test_edgeql_select_outer_rebind_06(self):
             [{"name": "Dragon", "nameLen": 6, "nameLen2": 6}]
         )
 
+    async def test_edgeql_select_outer_rebind_07a(self):
+        await self.assert_query_result(
+            r'''
+                SELECT assert_single((
+                  SELECT User {
+                  deck := (
+                    WITH
+                      User_deck := User.deck
+                    SELECT User_deck {
+                      awards := (
+                        User_deck.awards { name }
+                      )
+                    }
+                  )
+                }) filter .name ILIKE 'Alice%');
+            ''',
+            tb.bag([
+                {
+                    "deck": tb.bag([
+                        {"awards": [{"name": "2nd"}]},
+                        {"awards": tb.bag([{"name": "1st"}, {"name": "3rd"}])},
+                        {"awards": []},
+                        {"awards": []}
+                    ])
+                }
+            ])
+        )
+
+    async def test_edgeql_select_outer_rebind_07b(self):
+        await self.assert_query_result(
+            r'''
+                SELECT assert_exists((
+                  SELECT User {
+                  deck := (
+                    WITH
+                      User_deck := User.deck
+                    SELECT User_deck {
+                      awards := (
+                        User_deck.awards { name }
+                      )
+                    }
+                  )
+                }) filter .name ILIKE 'Alice%');
+            ''',
+            tb.bag([
+                {
+                    "deck": tb.bag([
+                        {"awards": [{"name": "2nd"}]},
+                        {"awards": tb.bag([{"name": "1st"}, {"name": "3rd"}])},
+                        {"awards": []},
+                        {"awards": []}
+                    ])
+                }
+            ])
+        )
+
     async def test_edgeql_scope_for_with_computable_01(self):
         await self.assert_query_result(
             r'''