Repository: edgedb/edgedb

Issue:
Cardinality inference of backlinks ignores `delegated` on exclusive constraints
```
edgedb> CREATE ABSTRACT TYPE ExtensionConfig {
.......     CREATE REQUIRED SINGLE LINK cfg -> AbstractConfig {
.......         CREATE DELEGATED CONSTRAINT std::exclusive;
.......     };
....... };
OK: CREATE TYPE
edgedb> ALTER TYPE AbstractConfig {
.......     CREATE LINK extensions := .<cfg[IS ExtensionConfig];
....... };
OK: ALTER TYPE
```

`extensions` ought to be a `multi` link, because although `cfg` has an exclusive constraint, it is delegated to child types, and so is *not* exclusive when looking at `ExtensionConfig` itself.
It is getting inferred as `single`, though.

This should fail:
```
edgedb> select AbstractConfig { single x := .extensions };
[]
```



Discussion:

Resolving patch:
diff --git a/edb/edgeql/compiler/inference/cardinality.py b/edb/edgeql/compiler/inference/cardinality.py
--- a/edb/edgeql/compiler/inference/cardinality.py
+++ b/edb/edgeql/compiler/inference/cardinality.py
@@ -1055,6 +1055,8 @@ def get_object_exclusive_constraints(
             # We ignore constraints with except expressions, because
             # they can't actually ensure cardinality
             and not constr.get_except_expr(schema)
+            # And delegated constraints can't either
+            and not constr.get_delegated(schema)
         ):
             if subjectexpr.refs is None:
                 continue
diff --git a/edb/schema/pointers.py b/edb/schema/pointers.py
--- a/edb/schema/pointers.py
+++ b/edb/schema/pointers.py
@@ -786,6 +786,7 @@ def get_exclusive_constraints(
             if (
                 constr.issubclass(schema, exclusive)
                 and not constr.get_subjectexpr(schema)
+                and not constr.get_delegated(schema)
             ):
                 assert not constr.get_except_expr(schema)
                 constrs.append(constr)


Test patch:
diff --git a/tests/schemas/cards_ir_inference.esdl b/tests/schemas/cards_ir_inference.esdl
--- a/tests/schemas/cards_ir_inference.esdl
+++ b/tests/schemas/cards_ir_inference.esdl
@@ -143,3 +143,17 @@ function taking_non_opt_returning_opt(a: str) -> optional str {
         a
     );
 };
+
+
+type Tgt;
+abstract type Src {
+    required lnk: Tgt { delegated constraint exclusive }
+};
+type SrcSub1 extending Src;
+type SrcSub2 extending Src;
+
+abstract type Named2 {
+    required property name -> str;
+    delegated constraint exclusive on (.name);
+}
+type Named2Sub extending Named2;
diff --git a/tests/test_edgeql_ir_card_inference.py b/tests/test_edgeql_ir_card_inference.py
--- a/tests/test_edgeql_ir_card_inference.py
+++ b/tests/test_edgeql_ir_card_inference.py
@@ -1269,3 +1269,42 @@ def test_edgeql_ir_card_inference_150(self):
         """
         select User { [is schema::Object].name }
         """
+
+    def test_edgeql_ir_card_inference_151(self):
+        # lnk has a *delegated* constraint
+        """
+        select Tgt { back := .<lnk[is Src] }
+% OK %
+        back: MANY
+        """
+
+    def test_edgeql_ir_card_inference_152(self):
+        """
+        select Tgt { back := .<lnk[is SrcSub1] }
+% OK %
+        back: AT_MOST_ONE
+        """
+
+    def test_edgeql_ir_card_inference_153(self):
+        # Constraint is delegated, shouldn't apply here
+        """
+        select Named filter .name = ''
+% OK %
+        MANY
+        """
+
+    def test_edgeql_ir_card_inference_154(self):
+        # Constraint is delegated, shouldn't apply here
+        """
+        select Named2 filter .name = ''
+% OK %
+        MANY
+        """
+
+    def test_edgeql_ir_card_inference_155(self):
+        # But should apply to this subtype
+        """
+        select Named2Sub filter .name = ''
+% OK %
+        AT_MOST_ONE
+        """