Repository: edgedb/edgedb

Issue:
Upserts failing in `For` block
when I query:
```
with
  raw_data := <json>$data
for item in json_array_unpack(raw_data) union (insert Person {slug := <str>json_get(item, 'slug')} unless conflict on .slug else (select Person)) {slug, id, name}
```

it fails with `edgedb.errors.ConstraintViolationError: slug violates exclusivity constraint`

schema:
```
  type Person {
      required property slug -> str {
          constraint exclusive;
      }
      property name -> str;
  }
```


Discussion:
Are there duplicates in input?
I've tested with and without duplicates in the input and the response is the same.
Yeah, just running
```
with
  raw_data := to_json('[{"slug": "foo"}]')
for item in json_array_unpack(raw_data) union (
    insert Person {slug := <str>json_get(item, 'slug')}
    unless conflict on .slug else (select Person)
);
```
twice fails.
@jerber As a workaround, it looks like if you lift the json_get call into a variable outside of the `insert`, it works. 

Also, doing it by casting to a tuple work, and is fairly nice:
```
with
  raw_data := to_json('[{"slug": "foo"}]')
for item in <tuple<slug: str>>json_array_unpack(raw_data) union (
    insert Person {slug := item.slug}
    unless conflict on .slug else (select Person)
) {slug, id, name};
```
Seems to be some sort of subquery/scope related issue; just sticking the `json_get` call into a subquery makes it work...
@msullivan ah great that did work. Here is the updated version I'm using, with an upsert.
```
with
    raw_data := <json>$data,
for item in json_array_unpack(raw_data) union (
    with
        slug := <str>json_get(item, 'slug'),
        name := <str>json_get(item, 'name')
    insert Person {slug := slug, name := name}
    unless conflict on .slug else (update Person set {slug := slug, name := name})
) { slug, name, id }
```
Happy that the workaround works for you! (That said, this is of course a pretty unpleasant bug!)

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
@@ -133,10 +133,13 @@ def _compile_conflict_select(
                     error, context=parser_context
                 )
 
+            # We want to use the same path_scope_id as the original
+            elem_set = setgen.ensure_set(elem.expr, ctx=ctx)
+            elem_set.path_scope_id = elem.path_scope_id
+
             # FIXME: The wrong thing will definitely happen if there are
             # volatile entries here
-            ptr_anchors[name] = ctx.create_anchor(
-                setgen.ensure_set(elem.expr, ctx=ctx), name)
+            ptr_anchors[name] = ctx.create_anchor(elem_set, name)
 
     if for_inheritance and not ptrs_in_shape:
         return None


Test patch:
diff --git a/tests/test_edgeql_insert.py b/tests/test_edgeql_insert.py
--- a/tests/test_edgeql_insert.py
+++ b/tests/test_edgeql_insert.py
@@ -3065,6 +3065,35 @@ async def test_edgeql_insert_unless_conflict_22(self):
             [],
         )
 
+    async def test_edgeql_insert_unless_conflict_23(self):
+        # Test that scoping of default parameters doesn't get messed up
+        obj1 = await self.con.query_single('''
+            insert DerivedPerson { sub_key := "foo" };
+        ''')
+        obj2 = await self.con.query_single('''
+            insert DerivedPerson {
+                name := "new",
+                sub_key := <str>json_get(
+                    to_json('{ "sub_key": "foo"}'), 'sub_key')
+            }
+            unless conflict on .sub_key else (select DerivedPerson);
+        ''')
+        self.assertEqual(obj1.id, obj2.id)
+
+        obj3 = await self.con.query('''
+            with
+              raw_data := to_json('[{"sub_key": "foo"}]')
+            for item in json_array_unpack(raw_data) union (
+                insert DerivedPerson {
+                    name := "new",
+                    sub_key := <str>json_get(item, 'sub_key')
+                }
+                unless conflict on .sub_key else (select DerivedPerson)
+            );
+        ''')
+        self.assertEqual(len(obj3), 1)
+        self.assertEqual(obj1.id, tuple(obj3)[0].id)
+
     async def test_edgeql_insert_dependent_01(self):
         query = r'''
             SELECT (