Repository: edgedb/edgedb

Issue:
Rewrite in abstract types possible?
- EdgeDB Version: 4.0-dev.7696+eae6d5a
- EdgeDB CLI Version: 3.5.0-dev.1025+fec3413
- OS Version: Linux hp 6.1.39-1-lts #1 SMP PREEMPT_DYNAMIC Wed, 19 Jul 2023 17:58:31 +0000 x86_64 GNU/Linux

Steps to Reproduce:

1. `insert Foo { name := "f1" };`
2. `update Foo set { name := "f1" };`
```
InternalServerError: could not resolve trailing pointer class for (__derived__::__old__)
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
```

Schema:

```
abstract type Revisable {
  required rev: int32 {
    default := 1;
    # increase revision iff object _really_ changes
    rewrite update using (
      (__old__.rev + 1) if __subject__ != __old__ else __old__.rev
      # NB: also tried the following
      #(__old__.rev + 1) if __subject__{*} != __old__{*} else __old__.rev
      #(__old__.rev + 1) if <json>__subject__{*} != <json>__old__{*} else __old__.rev
      #(__old__.rev + 1) if exists std::assert_distinct({__subject__, __old__}) else __old__.rev
    );
  }
}

type Foo extending Revisable {
  name: str {}
}
```



Discussion:
A few things:
1. There's definitely a bug here, since ISE's are always a bug
2. If the code worked (which it should), it wouldn't do what you want: objects are compared by their `id`s
3. I don't think there is currently a way to do this in the generic way that you want. There is `__specified__`, but it only shows the properties on the actual type the rewrite is defined on, not on the subtype it's operating on. It's a feature we could consider, though. Do you have a lot of no-op updates that make it a bad idea to always update the rev?
@msullivan thank you!

1. I see
2. Still I would expect `<json>__subject__{*} != <json>__old__{*}` to work, `__subject__` and `__old__` being the properties of subtype it's operating on. I _personally_ believe it's the least surprise
3. I can stand with always updating rev. The question still how (if somehow) to mimic sql `create or replace trigger _100_on_before_update before update on Bebebe for each row when (new is distinct from old) execute procedure Bububu;`

Shall I expect some update on the subject or just punt on that?


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
@@ -1823,6 +1823,9 @@ def process_update_rewrites(
     old_path_id = ir_stmt.rewrites.old_path_id
     assert old_path_id
 
+    table_rel = table_relation.relation
+    assert isinstance(table_rel, pgast.Relation)
+
     # Need to set up an iterator for any internal DML.
     iterator = pgast.IteratorCTE(
         path_id=subject_path_id,
@@ -1865,6 +1868,9 @@ def process_update_rewrites(
         ] = contents_select.path_rvar_map[(object_path_id, "value")]
 
         # pull in table_relation for __old__
+        table_rel.path_outputs[
+            (old_path_id, "value")
+        ] = table_rel.path_outputs[(object_path_id, "value")]
         relctx.include_rvar(
             rewrites_stmt, table_relation, old_path_id, ctx=ctx
         )
@@ -1884,8 +1890,6 @@ def process_update_rewrites(
         relctx.pull_path_namespace(
             target=rewrites_stmt, source=table_relation, ctx=ctx
         )
-        table_rel = table_relation.relation
-        assert isinstance(table_rel, pgast.Relation)
         table_rel.path_outputs[
             (subject_path_id, "value")
         ] = table_rel.path_outputs[(object_path_id, "value")]


Test patch:
diff --git a/tests/test_edgeql_rewrites.py b/tests/test_edgeql_rewrites.py
--- a/tests/test_edgeql_rewrites.py
+++ b/tests/test_edgeql_rewrites.py
@@ -1026,3 +1026,24 @@ async def test_edgeql_rewrites_26(self):
                 };
                 '''
             )
+
+    async def test_edgeql_rewrites_27(self):
+        await self.con.execute(
+            '''
+            create type Foo {
+                create property will_be_true: bool {
+                    create rewrite update using (__subject__ = __old__);
+                };
+            };
+            insert Foo { will_be_true := false };
+            '''
+        )
+        await self.assert_query_result(
+            'select Foo { will_be_true }',
+            [{'will_be_true': False}]
+        )
+        await self.con.execute('update Foo set { };')
+        await self.assert_query_result(
+            'select Foo { will_be_true }',
+            [{'will_be_true': True}]
+        )