Repository: edgedb/edgedb

Issue:
Queries fail on abstract types
<!-- 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: 4.0+d568f08
- EdgeDB CLI Version: EdgeDB CLI 4.1.0-dev
- OS Version: x86_64 GNU/Linux 6.5.9-arch2-1

Even simple query fails: 
```
update ChatBase
filter (.id = <uuid>$chat_id) and (<uuid>$my_id in .members.id)
set { 
  messages += (
    insert Message { 
      from := (select User filter .id = <uuid>$my_id), 
      text := <str>$text 
    }
  ) 
}
```
with error:
```
InternalServerError: <ir.TypeRef 'default::ChatBase' at 0x7fd7d64c4dd0> 
```
And it works if I repalce ChatBase with Dialog

<!-- If the issue is about a query error, please also provide your schema -->

Schema:
messaging.esdl
```edgeql
module default {
   abstract type ChatBase {
      multi members: User {
         on target delete allow;
      };
      multi messages: Message {
         on target delete allow;
         on source delete delete target;
         constraint exclusive;
      }
   }
   type Dialog extending ChatBase {
      overloaded required multi members: User;
      trigger require_two_members after insert, update for each do (
         assert(
            count(__new__.members) <= 2,
            message := "Dialog can have only two members, now are: " ++ to_str(count(__new__.members))
         )
      );
      trigger prohibit_same_dialogs after insert for each do (
         assert_single(
            (select Dialog filter (count(__new__.members intersect .members) = 2)),
            message := "Can't create more than one dialog with same members"
         )
      );
   }
   type Monolog extending ChatBase {
      required single link owner: User {
         constraint exclusive;
      };
   }
   type GroupChat extending ChatBase {
      pictureId: str;
   }
   scalar type MessageText extending str {
      constraint min_len_value(1);
      constraint max_len_value(1000);
   }
   type Message {
      from: User {
         on target delete delete source;
      };
      required single link chat := assert_exists(.<messages[is ChatBase]);
      required text: MessageText;
      required createdAt: datetime {
         readonly := true;
         default := datetime_of_statement();
      }
   }
   type DraftMessage {
      required from: User {
         on source delete delete target;
      }; 
      chat: ChatBase;
      recipient: User;
      constraint expression on (
         (exists .chat and not exists .recipient) 
         or 
         (exists .recipient and not exists .chat)
      );
      required text: MessageText;
   }
}
```
default.esdl:
```edgeql
module default {
   scalar type Activity extending enum<Investor, Entrepreneur, Freelancer>;
   function is_unique_array(arr: array<Activity>) -> bool
      using (count(distinct (array_unpack(arr))) = count(array_unpack(arr)));
   type User {
      required phone: str {
         constraint exclusive;
         constraint expression on (len(__subject__) = 12);
      };
      name: str;
      nickname: str {
         constraint exclusive;
      };
      pictureId: str;
      required activities: array<Activity> {
         constraint expression on (is_unique_array(__subject__));
         default := <array<Activity>>[];
      };
      required interests: array<str> {
         default := <array<str>>[];
      };
      statusMessage: str;
      property canBeVisible := exists(.name) and exists(.nickname);
      link chats := .<members[is ChatBase];
   }
   type OtpPhoneRequest {
      required phone: str;
      required otp: int32;
      required sentAt: datetime;
      confirmedAt: datetime;
   }
}
```


Discussion:
Note: Probably the issue is more about the trigger than the abstract type
> Note: Probably the issue is more about the trigger than the abstract type

I removed triggers in dev mode, but error persisted
This is minimal reproducible example:

```
module default {
    abstract type ChatBase {
        multi messages: str {
            constraint exclusive;
        }
    }

    type Dialog extending ChatBase { }
    type Monolog extending ChatBase { }
}
```

```
_localdev:edgedb> update ChatBase set { messages += 'hello world' };
...
        File "/home/aljaz/EdgeDB/edgedb/edb/pgsql/compiler/__init__.py", line 157, in compile_ir_to_sql_tree
          raise errors.InternalServerError(*args) from e
      edb.errors.InternalServerError: <ir.TypeRef '__derived__::expr~23' at 0x7f16356e8790>
```

Resolving patch:
diff --git a/edb/edgeql/compiler/conflicts.py b/edb/edgeql/compiler/conflicts.py
--- a/edb/edgeql/compiler/conflicts.py
+++ b/edb/edgeql/compiler/conflicts.py
@@ -800,6 +800,11 @@ def compile_inheritance_conflict_checks(
 
     conflicters = []
     for subject_stype, anc_type, ir in modified_ancestors:
+
+        # don't enforce any constraints for abstract object type
+        if subject_stype.get_abstract(schema=ctx.env.schema):
+            continue
+
         conflicters.extend(
             _compile_inheritance_conflict_selects(
                 stmt, ir, anc_type, subject_stype, ctx=ctx
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
@@ -431,6 +431,12 @@ def fini_dml_stmt(
             else:
                 stop_ref = base_typeref
 
+            # When the base type is abstract, there will be no CTE for it,
+            # so the overlays of children types have to apply to the whole
+            # ancestry tree.
+            if base_typeref.is_abstract:
+                stop_ref = None
+
             # The overlay for update is in two parts:
             # First, filter out objects that have been updated, then union them
             # back in. (If we just did union, we'd see the old values also.)


Test patch:
diff --git a/tests/test_constraints.py b/tests/test_constraints.py
--- a/tests/test_constraints.py
+++ b/tests/test_constraints.py
@@ -1773,3 +1773,85 @@ async def test_constraints_abstract_scalar(self):
             edgedb.ConstraintViolationError, "Minimum allowed value for"
         ):
             await self.con.execute("insert X { y := -1 }")
+
+    async def test_constraints_abstract_object_01(self):
+        await self.con.execute(
+            """
+                create abstract type ChatBase {
+                    create multi property messages: str {
+                        create constraint exclusive;
+                    };
+                };
+
+                create type Dialog extending ChatBase;
+                create type Monolog extending ChatBase;
+                insert Dialog;
+                insert Monolog;
+            """
+        )
+        async with self.assertRaisesRegexTx(
+            edgedb.ConstraintViolationError,
+            "messages violates exclusivity constraint"
+        ):
+            await self.con.execute("""
+                update ChatBase set { messages += 'hello world' };
+            """)
+        async with self.assertRaisesRegexTx(
+            edgedb.ConstraintViolationError,
+            "messages violates exclusivity constraint"
+        ):
+            await self.con.execute("""
+                update ChatBase set { messages := 'hello world' };
+            """)
+
+    async def test_constraints_abstract_object_02(self):
+        await self.con.execute(
+            """
+                create abstract type ChatBase {
+                    create single property messages: str {
+                        create constraint exclusive;
+                    };
+                };
+
+                create type Dialog extending ChatBase;
+                create type Monolog extending ChatBase;
+                insert Dialog;
+                insert Monolog;
+            """
+        )
+        async with self.assertRaisesRegexTx(
+            edgedb.ConstraintViolationError,
+            "messages violates exclusivity constraint"
+        ):
+            await self.con.execute("""
+                update ChatBase set { messages := 'hello world' };
+            """)
+
+    async def test_constraints_abstract_object_03(self):
+        # Add one where the constraint comes from a different type
+        # than the update
+        await self.con.execute(
+            """
+                create abstract type ChatBase {
+                    create single property messages: str {
+                    };
+                };
+                create abstract type ChatBase2 {
+                    create single property messages: str {
+                        create constraint exclusive;
+                    };
+                };
+
+                create type Dialog extending ChatBase, ChatBase2;
+                create type Monolog extending ChatBase, ChatBase2;
+                insert Dialog;
+                insert Monolog;
+            """
+        )
+        async with self.assertRaisesRegexTx(
+            edgedb.ConstraintViolationError,
+            "messages violates exclusivity constraint"
+        ):
+            await self.con.execute("""
+                update ChatBase set { messages := 'hello world' };
+            """)