Repository: edgedb/edgedb

Issue:
Computables of shapes inside arrays have problems
Computables inside shapes in arrays tend to behave pretty weirdly.
* If the computable needs materialization, we will not properly extract things
* `SELECT array_agg((SELECT User {m := 10}))[1000].m` returns `10` instead of failing

One thing I considered is to discard views when using array elimination forms (indexing, array_unpack, array_get), which would solve these problems by disallowing them, but among other things that would break casting an array with a shape to json (since we compile that using an array_unpack).


Discussion:
This still seems to be a bug, @msullivan please take a look.

Resolving patch:
diff --git a/edb/pgsql/compiler/relgen.py b/edb/pgsql/compiler/relgen.py
--- a/edb/pgsql/compiler/relgen.py
+++ b/edb/pgsql/compiler/relgen.py
@@ -1118,7 +1118,12 @@ def process_set_as_subquery(
             source_is_visible = outer_fence.is_visible(ir_source.path_id)
 
         if source_is_visible:
-            get_set_rvar(ir_set.rptr.source, ctx=ctx)
+            get_set_rvar(ir_source, ctx=ctx)
+            # Force a source rvar so that trivial computed pointers
+            # on erroneous objects (like a bad array deref) fail.
+            # (Most sensible computables will end up requiring the
+            # source rvar anyway.)
+            ensure_source_rvar(ir_source, ctx.rel, ctx=ctx)
     else:
         ir_source = None
         source_is_visible = False
@@ -1158,6 +1163,8 @@ def process_set_as_subquery(
                 with newctx.subrel() as _, _.newscope() as subctx:
                     get_set_rvar(ir_source, ctx=subctx)
                     subrvar = relctx.rvar_for_rel(subctx.rel, ctx=subctx)
+                    # Force a source rvar. See above.
+                    ensure_source_rvar(ir_source, subctx.rel, ctx=subctx)
 
                 relctx.include_rvar(
                     stmt, subrvar, ir_source.path_id, 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
@@ -6548,3 +6548,46 @@ async def test_edgeql_collection_shape_07(self):
             ''',
             [[{"id": {}}], [{"id": {}}]]
         )
+
+    async def test_edgeql_assert_fail_object_computed_01(self):
+        # check that accessing a trivial computable on an object
+        # that will fail to evaluate still fails
+
+        async with self.assertRaisesRegexTx(
+            edgedb.CardinalityViolationError,
+            "assert_exists violation",
+        ):
+            await self.con.query("""
+                SELECT assert_exists((SELECT User {m := 10} FILTER false)).m;
+            """)
+
+        async with self.assertRaisesRegexTx(
+            edgedb.InvalidValueError,
+            "array index 1000 is out of bounds",
+        ):
+            await self.con.query("""
+                SELECT array_agg((SELECT User {m := Issue}))[{1000}].m;
+            """)
+
+        async with self.assertRaisesRegexTx(
+            edgedb.InvalidValueError,
+            "array index 1000 is out of bounds",
+        ):
+            await self.con.query("""
+                SELECT array_agg((SELECT User {m := 10}))[{1000}].m;
+            """)
+
+    @test.xfail('''
+        Publication is empty, and so even if we join in User to the result
+        of the array dereference, that all gets optimized out on the pg
+        side. I'm not really sure what we can reasonably do about this.
+    ''')
+    async def test_edgeql_assert_fail_object_computed_02(self):
+        # Publication is empty, and so
+        async with self.assertRaisesRegexTx(
+            edgedb.InvalidValueError,
+            "array index 1000 is out of bounds",
+        ):
+            await self.con.query("""
+                SELECT array_agg((SELECT User {m := Publication}))[{1000}].m;
+            """)