Repository: edgedb/edgedb

Issue:
The `after delete` trigger causes an ISE when referring to `__type__`
<!-- Please search existing issues to avoid creating duplicates. -->

<!--
For the EdgeDB Version: run `edgedb query 'select sys::get_version_as_str()'` from your project directory (or run `select sys::get_version_as_str();` in the EdgeDB interactive shell).
For the EdgeDB CLI Version: Run `edgedb --version` from anywhere
-->

- EdgeDB Version: 3.0-dev.7435+bb3b78c
- EdgeDB CLI Version: 3.0.0-dev.968+8eef530
- OS Version: Ubuntu

Steps to Reproduce:

```
edgedb> insert Person{name := 'Alice'};
{default::Person {id: c6ee77c0-e53d-11ed-b114-0345f652c87f}}
edgedb> delete Person;
edgedb error: InternalServerError: missing FROM-clause entry for table "Person~5"
  Hint: This is most likely a bug in EdgeDB. Please consider opening an issue ticket at https://github.com/edgedb/edgedb/issues/new?template=bug_report.md
  Server traceback:
      edb.errors.InternalServerError: missing FROM-clause entry for table "Person~5"
```

<!-- If the issue is about a query error, please also provide your schema -->

Schema:

```
module default {
    type Log {
        body: str;
    }

    type Person {
        required name: str;

        trigger log_delete after delete for each do (
            insert Log {
                body := __old__.__type__.name,
            }
        );
    }
}
```

The trigger works fine if the expression doesn't refer to `__type__`. 

As far as I can tell other triggers work fine even when they refer to `__type__`.


Discussion:

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
@@ -1126,6 +1126,14 @@ def process_set_as_path(
             # If this is a link that is stored inline, make sure
             # the source aspect is actually accessible (not just value).
             src_rvar = ensure_source_rvar(ir_source, stmt, ctx=ctx)
+            # In case the source is visible (so the codepath below
+            # that uses the current statement doesn't trigger) but the
+            # source aspect wasn't available, make sure we include it
+            # in our return. This can come up with __old__ in triggers.
+            if source_is_visible:
+                rvars.append(SetRVar(
+                    src_rvar, path_id=ir_source.path_id, aspects=['source']
+                ))
         else:
             aspects = ['value', 'source']
             src_rvar = get_set_rvar(ir_source, ctx=ctx)


Test patch:
diff --git a/tests/test_edgeql_triggers.py b/tests/test_edgeql_triggers.py
--- a/tests/test_edgeql_triggers.py
+++ b/tests/test_edgeql_triggers.py
@@ -1056,3 +1056,66 @@ async def test_edgeql_triggers_tricky_01(self):
                 {'name': 'y', 'note': "1", 'subject': None},
             ]
         )
+
+    async def test_edgeql_triggers_old_link_01(self):
+        await self.con.execute('''
+            alter type InsertTest {
+              create trigger log_upd after update for each do (
+                insert Note { name := "upd", note := __old__.__type__.name }
+              );
+              create trigger log_del after delete for each do (
+                insert Note { name := "del", note := __old__.__type__.name }
+              );
+            };
+        ''')
+
+        await self.con.execute('''
+            insert InsertTest {
+                name := "test",
+            };
+            update InsertTest set {};
+            delete InsertTest;
+        ''')
+
+        await self.assert_query_result(
+            '''
+            select Note { name, note } order by .name
+            ''',
+            [
+                {'name': 'del', 'note': "default::InsertTest"},
+                {'name': 'upd', 'note': "default::InsertTest"},
+            ]
+        )
+
+    async def test_edgeql_triggers_old_link_02(self):
+        await self.con.execute('''
+            alter type InsertTest {
+              create trigger log_upd after update for each do (
+                insert Note {
+                    name := "upd", note := <str>count(__old__.subordinates) }
+              );
+              create trigger log_del after delete for each do (
+                insert Note {
+                    name := "del", note := <str>count(__old__.subordinates) }
+              );
+            };
+        ''')
+
+        await self.con.execute('''
+            insert InsertTest {
+                name := "test",
+                subordinates := (insert Subordinate { name := "foo" }),
+            };
+            update InsertTest set {};
+            delete InsertTest;
+        ''')
+
+        await self.assert_query_result(
+            '''
+            select Note { name, note } order by .name
+            ''',
+            [
+                {'name': 'del', 'note': "1"},
+                {'name': 'upd', 'note': "1"},
+            ]
+        )