Repository: edgedb/edgedb

Issue:
Some sort of scoping error with a non-object binding
This query works properly:
```
with
  options := {'aneuploid', 'balkanize', 'defenestrate', 'sanguine'},
  pairs := (select Pair filter .word1 = 'hatch' and .similarity = <float64>$sim),
select options filter (
    with opt_pair := (
        select Pair filter .word1 = options and .word2 in pairs.word2),
    select count(opt_pair) = count(distinct opt_pair.similarity)
) limit 1;
```
but this should-be-equivalent query runs for a long time and then returns no data
```
with
  options := {'aneuploid', 'balkanize', 'defenestrate', 'sanguine'},
  word2 := (select Pair filter .word1 = 'hatch' and .similarity = <float64>$sim).word2,
select options filter (
    with opt_pair := (
        select Pair filter .word1 = options and .word2 in word2),
    select count(opt_pair) = count(distinct opt_pair.similarity)
) limit 1;
```

wrapping the reference to `word2` in the filter clause with a select fixes it.

Schema is 
```
module default {
    type Pair {
        required property word1 -> str;
        required property word2 -> str;
        required property similarity -> float64;
        constraint exclusive on ((.word1, .word2));
        index on ((.word1, .word2));
        index on ((.word1, .similarity));
    }
}
```


Discussion:

Resolving patch:
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
@@ -411,7 +411,26 @@ def fixup(subrel: pgast.Query) -> None:
         else:
             all_null = False
 
-    if all_null:
+    # Fail if no subquery had the path or, for scalar identity paths,
+    # if any did not have it.
+    #
+    # We need to do this for scalar identity because scalar identity
+    # is only used for volatility refs, and it is OK if looking it up
+    # fails, because we create a backup volatility ref---but it is
+    # *not* OK for it to succeed and produce NULL in some cases.
+    if all_null or (
+        aspect == 'identity' and optional and not path_id.is_objtype_path()
+    ):
+        # If *none* of the subqueries had it, we have to remove them all
+        # before erroring, lest a future call see them and decide
+        # they really exist.
+        def _clear(subrel: pgast.Query) -> None:
+            assert flavor == 'normal'
+            new_path_id = map_path_id(path_id, subrel.view_path_id_map)
+            del subrel.path_outputs[new_path_id, aspect]
+            subrel.target_list.pop()
+
+        astutils.for_each_query_in_set(rel, _clear)
         raise LookupError(
             f'cannot find refs for '
             f'path {path_id} {aspect} in {rel}')
@@ -1086,7 +1105,7 @@ def _get_path_output(
                                     ptr_info=ptr_info, env=env)
 
     assert isinstance(rel, pgast.Query)
-    if is_values_relation(rel):
+    if is_values_relation(rel) and aspect != 'identity':
         # The VALUES() construct seems to always expose its
         # value as "column1".
         alias = 'column1'
diff --git a/edb/pgsql/compiler/relctx.py b/edb/pgsql/compiler/relctx.py
--- a/edb/pgsql/compiler/relctx.py
+++ b/edb/pgsql/compiler/relctx.py
@@ -580,9 +580,6 @@ def new_rel_rvar(
         ir_set: irast.Set, stmt: pgast.Query, *,
         lateral: bool=True,
         ctx: context.CompilerContextLevel) -> pgast.PathRangeVar:
-    if irutils.is_scalar_view_set(ir_set):
-        ensure_bond_for_expr(ir_set, stmt, ctx=ctx)
-
     return rvar_for_rel(stmt, typeref=ir_set.typeref, lateral=lateral, ctx=ctx)
 
 


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
@@ -7074,3 +7074,74 @@ async def test_edgeql_select_nested_order_02(self):
                 {"key": {"name": "Regression.", "number": "4"}},
             ]
         )
+
+    async def test_edgeql_select_scalar_views_01(self):
+        # Test the fix for #3525. I did not have a lot of luck
+        # minimizing this one.
+        await self.con.execute('''
+            CREATE TYPE default::Pair {
+                CREATE REQUIRED PROPERTY similarity -> std::float64;
+                CREATE REQUIRED PROPERTY word1 -> std::str;
+                CREATE REQUIRED PROPERTY word2 -> std::str;
+            };
+
+            for tup in {
+                ('hatch', 'foo', 0.5),
+                ('hatch', 'bar', 0.5),
+                ('hatch', 'baz', 0.5),
+
+                ('balkanize', 'foo', 0.1),
+                ('balkanize', 'bar', 0.2),
+                ('balkanize', 'baz', 0.3),
+
+                ('defenestrate', 'foo', 0.1),
+                ('defenestrate', 'bar', 0.2),
+                ('defenestrate', 'baz', 0.2),
+
+            } union {
+                (insert Pair { word1 := tup.0, word2 := tup.1,
+                               similarity := tup.2 }),
+                (insert Pair { word1 := tup.1, word2 := tup.0,
+                               similarity := tup.2 }),
+            };
+        ''')
+
+        await self.assert_query_result(
+            '''
+            with
+              options := {'balkanize', 'defenestrate'},
+              word2 := (select Pair
+                        filter .word1 = 'hatch' and .similarity = 0.5).word2,
+            select options filter (
+                with opt_pair := (
+                    select Pair filter .word1 = options and .word2 in (word2)),
+                select count(opt_pair) = count(distinct opt_pair.similarity)
+            );
+            ''',
+            ['balkanize'],
+        )
+
+    async def test_edgeql_select_scalar_views_02(self):
+        await self.assert_query_result(
+            '''
+            select (select {1,2} filter random() > 0) filter random() > 0
+            ''',
+            {1, 2},
+        )
+
+    async def test_edgeql_select_scalar_views_03(self):
+        # The thing this is testing for
+        await self.assert_query_result(
+            '''
+            select {1,2,3,4+0} filter random() > 0
+            ''',
+            {1, 2, 3, 4},
+        )
+
+    async def test_edgeql_select_scalar_views_04(self):
+        await self.assert_query_result(
+            '''
+            for x in 2 union (select {1,x} filter random() > 0)
+            ''',
+            {1, 2}
+        )