Repository: edgedb/edgedb

Issue:
Finish up last parts of triggers
- [x] Make triggers chain and implement a recursion check
- [x] There is an XXX in the implementation regarding overlays for the DML done inside the trigger. Investigate and fix if needed


Discussion:

Resolving patch:
diff --git a/edb/pgsql/compiler/dml.py b/edb/pgsql/compiler/dml.py
--- a/edb/pgsql/compiler/dml.py
+++ b/edb/pgsql/compiler/dml.py
@@ -3220,8 +3220,6 @@ def compile_trigger(
         # up everywhere...  but __old__ has a TriggerAnchor set up in
         # it, which acts like a dml statement, and *diverts* __old__
         # away from the new data!
-        # XXX: TODO: what about the overlays induced by *new* DML...
-        # Those need to be rooted at `None`
 
         # We grab the list of DML out of dml_stmts instead of just
         # from the overlays for determinism reasons; it effects the
@@ -3245,6 +3243,8 @@ def compile_trigger(
         tctx.rel_overlays.ptr = tctx.rel_overlays.ptr.set(
             trigger.new_set.expr, tctx.rel_overlays.ptr[None])
 
+        # N.B: Any DML in the trigger will have the "global" overlay (None)
+        # as its starting point.
         dispatch.compile(trigger.expr, ctx=tctx)
         # Force the value to get output so that if it might error
         # it will be forced up by check_ctes
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
@@ -2042,8 +2042,12 @@ def _add_type_rel_overlay(
         ctx: context.CompilerContextLevel) -> None:
     entry = (op, rel, path_id)
     dml_stmts2 = dml_stmts if dml_stmts else (None,)
+    # If there is a "global" overlay, and there is none for the
+    # current statements, use it as the base. This is important for
+    # not losing track of the global environment in triggers.
+    root = ctx.rel_overlays.type.get(None, immu.Map())
     for dml_stmt in dml_stmts2:
-        ds_overlays = ctx.rel_overlays.type.get(dml_stmt, immu.Map())
+        ds_overlays = ctx.rel_overlays.type.get(dml_stmt, root)
         overlays = ds_overlays.get(typeid, ())
         if entry not in overlays:
             ds_overlays = ds_overlays.set(typeid, overlays + (entry,))
@@ -2129,8 +2133,12 @@ def _add_ptr_rel_overlay(
     entry = (op, rel, path_id)
     dml_stmts2 = dml_stmts if dml_stmts else (None,)
     key = typeid, ptrref_name
+    # If there is a "global" overlay, and there is none for the
+    # current statements, use it as the base. This is important for
+    # not losing track of the global environment in triggers.
+    root = ctx.rel_overlays.ptr.get(None, immu.Map())
     for dml_stmt in dml_stmts2:
-        ds_overlays = ctx.rel_overlays.ptr.get(dml_stmt, immu.Map())
+        ds_overlays = ctx.rel_overlays.ptr.get(dml_stmt, root)
         overlays = ds_overlays.get(key, ())
         if entry not in overlays:
             ds_overlays = ds_overlays.set(key, overlays + (entry,))


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
@@ -1030,3 +1030,29 @@ async def test_edgeql_triggers_chain_03(self):
                     (insert Note { name := "foo" }),
                 }
             ''')
+
+    async def test_edgeql_triggers_tricky_01(self):
+        await self.con.execute('''
+            alter type InsertTest {
+              create trigger log after insert for each do (
+                with X := (insert Note{ name := "x", subject := __new__.sub }),
+                insert Note { name := "y", note := <str>count(X.subject) }
+              );
+            };
+        ''')
+
+        await self.con.execute('''
+            insert InsertTest {
+                name := "test", sub := (insert Subordinate { name := "!" })
+            }
+        ''')
+
+        await self.assert_query_result(
+            '''
+            select Note { name, note, subject } order by .name
+            ''',
+            [
+                {'name': 'x', 'note': None, 'subject': {'id': str}},
+                {'name': 'y', 'note': "1", 'subject': None},
+            ]
+        )