Repository: geldata/gel
Pull request: Implement basic select-or-insert via coalesce
Implement a select-or-insert idiom using
`(SELECT ...) ?? (INSERT ...)`.

We only allow it under restricted circumstances (when the select and
insert both use the same value for an exclusive property) and it is
compiled using ON CONFLICT.

This doesn't work when the values are constants yet, because the
normalizer treats each constant separately. I'll change that in a
follow-up.

Mostly fixes #684, modulo follow-up.

Problem statement:
SELECT Object IF EXISTS Object ELSE INSERT 
Hi, I tried to use the IF...ELSE and EXISTS clauses in order to construct a query that either selects an item if it exists or else inserts it. So since I can do the following:
```
SELECT 1 IF EXISTS (SELECT Person FILTER .name = 'Ryan Gosling') 
....... ELSE (SELECT 0);
```
...I tried to do
```
SELECT Person FILTER .name = 'Ryan Gosling' 
....... IF EXISTS (SELECT Person FILTER .name = 'Ryan Gosling') 
....... ELSE (SELECT <int64>{});
```
However I get the error: **QueryError: operator 'std::IF' cannot be applied to operands of type 'std::bool', 'std::bool', 'std::int64'**

Am I making some syntax error or is this kind of selection not allowed? I assumed it would work because the documentation on IF states `anytype` as the left assignment, but I have not been able to figure out how to make it work with my schema types. 

Thank you very much for your help.

Hints from issue discussion:
Soon after opening this and trying various permutations, I realized that the left and right assignments need to be identical in type and be a singleton on both ends if I want to make an insert statement.

Closing this. Please feel free to delete.
@vpetrovykh should we enhance our error message here?
Another related question / error message confusion: 

This is how I am inserting a Person if they don't exist:
```
SELECT ( SELECT Person FILTER .name = 'Zendaya' LIMIT 1 ) 
....... IF 'Zendaya' IN Person.name 
....... ELSE (INSERT Person { last_name := 'Zendaya'  }  );
```
I am also able to use `FOR x IN ...` to separately _either_ insert or select multiple persons. However, if I try to use the above statement to try to do both at once in my `UNION` clause (i.e, select x if it exists or insert it if it doesn't), like this:
```
FOR x IN { 
....... ( name := 'Zendaya' ), 
....... ( name := 'Madonna') 
....... } UNION ( 
....... SELECT ( SELECT Person FILTER .name = x.name LIMIT 1 ) 
....... IF x.name IN Person.name 
....... ELSE (INSERT Person { last_name := x.name  }  ) 
....... );
```
I get the following error message (even if both Persons exist): **InternalServerError: missing FROM-clause entry for table "__derived__|x@@w-1~2"**

It's clear to me from the message that I shouldn't be trying this, but it would be a common use case for me and others so I want to make sure there isn't a right way to attempt this using `FOR`. Are my nested SELECTs an issue? Hope my question is clear.

Thank you again.
The `InternalServerError` is a bug, but the real issue here is that you cannot really do conditional `INSERT` like that, and that has to do with how Postgres handles DML CTEs internally -- it always executes the statement regardless of whether the logical flow reaches it or not.  EdgeDB currently allows nesting `INSERT` arbitrarily, but we will have to tighten the rules, and only allow it in the top-level `WITH` block and as a value expression in another `INSERT` statement.

Unfortunately, there's currently no good way to do conditional `INSERT` in a single statement other than via the `ON CONFLICT` clause in SQL, but we currently lack the syntax to do that.  One possible way is to specialize the `??` operator,  so:

```
INSERT Movie {
    cast := (
        FOR actor_name IN {'Zednaya', 'Madonna'}
        UNION (
            (SELECT Person FILTER .name = actor_name) 
            ?? (INSERT Person {name := actor_name})
        )
    )
}
```
Thank you for taking the time to explain that. Your suggested syntax looks very clear and I'll be hoping that makes its way into the spec.

For now I understand that I cannot use either of the queries I constructred in my example, because `SELECT...IF EXISTS...ELSE INSERT` will always insert the item anyway. Gotcha. So my approach would be to run all the SELECT queries separately as a `FOR x IN...` and construct the `INSERT Movie` statement in my Python/Node script based on the result of that. 
To be clear `SELECT Foo ?? Bar` already works as a concise spelling of `SELECT Foo IF EXISTS Foo ELSE Bar`, it's just that `INSERT` specifically doesn't respect the flow and is always executed.
I do like this syntax sugar because it seems to be quite a natural use of `??`. I'm all for this if we could do it reliably and have clear errors for when this construct cannot be cleanly compiled.
Hi,

Is there no way to insert a record if not exist as of today?
Maybe an "exclusive" mode? (silent, etc...)

Thanks!
There is a (slightly verbose) way using the `FOR` statement:

```
FOR _ IN {
    (SELECT true FILTER (NOT EXISTS (<your-condition>)))
}
UNION (
    INSERT <your-insert>
)
```
Looks more like a hack ^^.
I'll use it for now then ;-). Thanks @elprans again

Commit message: Implement basic select-or-insert via coalesce

Implement a select-or-insert idiom using
`(SELECT ...) ?? (INSERT ...)`.

We only allow it under restricted circumstances (when the select and
insert both use the same value for an exclusive property) and it is
compiled using ON CONFLICT.

This doesn't work when the values are constants yet, because the
normalizer treats each constant separately. I'll change that in a
follow-up.

Mostly fixes #684, modulo follow-up.

Patch to review:
diff --git a/edb/edgeql/compiler/expr.py b/edb/edgeql/compiler/expr.py
index 9cddc49892f..63fb5e830d7 100644
--- a/edb/edgeql/compiler/expr.py
+++ b/edb/edgeql/compiler/expr.py
@@ -47,6 +47,7 @@
 from . import pathctx
 from . import setgen
 from . import typegen
+from . import stmt
 
 from . import func  # NOQA
 
@@ -71,10 +72,54 @@ def compile_Path(
     return setgen.compile_path(expr, ctx=ctx)
 
 
+def compile_coalesce_insert(
+        left: qlast.Expr, right: qlast.InsertQuery,
+        ctx: context.ContextLevel) -> irast.Set:
+
+    # This maybe could have been implemented with less code by
+    # modifying compile_operator directly, but all of those approaches
+    # seemed to require some spaghetti code or adding dead-drop state
+    # in context.
+    with ctx.newscope() as lctx:
+        lir = setgen.ensure_set(dispatch.compile(left, ctx=lctx), ctx=lctx)
+        lir = setgen.scoped_set(setgen.ensure_stmt(lir, ctx=lctx), ctx=lctx)
+    with ctx.newscope() as rctx:
+        rir = setgen.ensure_set(
+            stmt.compile_InsertQuery(right, ctx=rctx, conditioned_on=lir),
+            ctx=rctx
+        )
+        rir = setgen.scoped_set(setgen.ensure_stmt(rir, ctx=rctx), ctx=rctx)
+
+    # Now we flip-flop the coalesce so that the SELECT gets executed
+    # if the INSERT is empty.
+    with ctx.new() as subctx:
+        subctx.anchors = subctx.anchors.copy()
+        l_alias = subctx.aliases.get('l')
+        subctx.anchors[l_alias] = lir
+        r_alias = subctx.aliases.get('r')
+        subctx.anchors[r_alias] = rir
+
+        rpath = qlast.Path(steps=[qlast.ObjectRef(name=r_alias)])
+        lpath = qlast.Path(steps=[qlast.ObjectRef(name=l_alias)])
+
+        # Only one side of the the ?? will possibly be non-empty, so
+        # we just union them together.
+        e = qlast.BinOp(op='UNION', left=lpath, right=rpath)
+
+        return setgen.ensure_set(dispatch.compile(e, ctx=subctx), ctx=subctx)
+
+
 @dispatch.compile.register(qlast.BinOp)
 def compile_BinOp(
         expr: qlast.BinOp, *, ctx: context.ContextLevel) -> irast.Set:
 
+    if (
+        expr.op == '??'
+        and not ctx.in_conditional
+        and isinstance(expr.right, qlast.InsertQuery)
+    ):
+        return compile_coalesce_insert(expr.left, expr.right, ctx=ctx)
+
     op_node = func.compile_operator(
         expr, op_name=expr.op, qlargs=[expr.left, expr.right], ctx=ctx)
 
diff --git a/edb/edgeql/compiler/inference/cardinality.py b/edb/edgeql/compiler/inference/cardinality.py
index 4c8e6f5b472..c9948e5ae4d 100644
--- a/edb/edgeql/compiler/inference/cardinality.py
+++ b/edb/edgeql/compiler/inference/cardinality.py
@@ -573,14 +573,17 @@ def extract_filters(
     scope_tree: irast.ScopeTreeNode,
     singletons: Collection[irast.PathId],
     env: context.Environment,
-) -> Sequence[Tuple[s_pointers.Pointer, irast.Set]]:
+    *,
+    # When strict=False, ignore any clauses in the filter_set we don't
+    # care about. If True, return None if any exist.
+    strict: bool = False,
+) -> Optional[Sequence[Tuple[s_pointers.Pointer, irast.Set]]]:
 
     schema = env.schema
     scope_tree = _get_set_scope(filter_set, scope_tree)
 
     ptr: s_pointers.Pointer
 
-    ptr_filters = []
     expr = filter_set.expr
     if isinstance(expr, irast.OperatorCall):
         if expr.func_shortname == 'std::=':
@@ -614,7 +617,7 @@ def extract_filters(
                     assert isinstance(_ptr, s_pointers.Pointer)
                     ptr = _ptr
 
-                    ptr_filters.append((ptr, right))
+                    return [(ptr, right)]
 
             elif _is_ptr_or_self_ref(right, result_set, env):
                 if infer_cardinality(
@@ -633,23 +636,31 @@ def extract_filters(
                     assert isinstance(_ptr, s_pointers.Pointer)
                     ptr = _ptr
 
-                    ptr_filters.append((ptr, left))
+                    return [(ptr, right)]
 
         elif expr.func_shortname == 'std::AND':
             left, right = (a.expr for a in expr.args)
 
-            ptr_filters.extend(
-                extract_filters(
-                    result_set, left, scope_tree, singletons, env
-                )
+            left_filters = extract_filters(
+                result_set, left, scope_tree, singletons, env
             )
-            ptr_filters.extend(
-                extract_filters(
-                    result_set, right, scope_tree, singletons, env
-                )
+            right_filters = extract_filters(
+                result_set, right, scope_tree, singletons, env
             )
 
-    return ptr_filters
+            ptr_filters: List[Tuple[s_pointers.Pointer, irast.Set]] = []
+            if left_filters is not None:
+                ptr_filters.extend(left_filters)
+            elif strict:
+                return None
+            if right_filters is not None:
+                ptr_filters.extend(right_filters)
+            elif strict:
+                return None
+
+            return ptr_filters
+
+    return None
 
 
 def _analyse_filter_clause(
diff --git a/edb/edgeql/compiler/stmt.py b/edb/edgeql/compiler/stmt.py
index 855b8a99b2a..2a188343f48 100644
--- a/edb/edgeql/compiler/stmt.py
+++ b/edb/edgeql/compiler/stmt.py
@@ -29,6 +29,7 @@
 from edb import errors
 
 from edb.ir import ast as irast
+from edb.ir import utils as irutils
 
 from edb.schema import ddl as s_ddl
 from edb.schema import functions as s_func
@@ -56,6 +57,9 @@
 from . import stmtctx
 from . import typegen
 
+if TYPE_CHECKING:
+    from edb.schema import constraints as s_constr
+
 
 @dispatch.compile.register(qlast.SelectQuery)
 def compile_SelectQuery(
@@ -254,16 +258,105 @@ def compile_GroupQuery(
     return result
 
 
-@dispatch.compile.register(qlast.InsertQuery)
-def compile_InsertQuery(
-        expr: qlast.InsertQuery, *, ctx: context.ContextLevel) -> irast.Set:
+def simple_stmt_eq(lhs: irast.Base, rhs: irast.Base) -> bool:
+    if (
+        isinstance(lhs, irast.BaseConstant)
+        and isinstance(rhs, irast.BaseConstant)
+        and lhs.typeref == rhs.typeref  # XXX?
+        and lhs.value == rhs.value
+    ):
+        return True
+    elif (
+        isinstance(lhs, irast.Parameter)
+        and isinstance(rhs, irast.Parameter)
+        and lhs.name == rhs.name
+    ):
+        return True
+    else:
+        return False
 
-    if ctx.in_conditional is not None:
+
+def handle_conditional_insert(
+        expr: qlast.InsertQuery,
+        rhs: irast.InsertStmt,
+        lhs_set: Union[irast.Expr, irast.Set],
+        *, ctx: context.ContextLevel,
+) -> List[irast.PointerRef]:
+    def error(s: str) -> NoReturn:
         raise errors.QueryError(
-            'INSERT statements cannot be used inside conditional expressions',
+            f'Invalid conditional INSERT statement: {s}',
             context=expr.context,
         )
 
+    schema = ctx.env.schema
+
+    if not isinstance(lhs_set, irast.Set):
+        error("left hand side is not SELECT")
+    lhs_set = irutils.unwrap_set(lhs_set)
+    if not isinstance(lhs_set.expr, irast.SelectStmt):
+        error("left hand side is not SELECT")
+    lhs = lhs_set.expr
+
+    if lhs.result.path_id != rhs.subject.path_id:
+        error("types do not match")
+
+    if lhs.where:
+        filtered_ptrs = inference.cardinality.extract_filters(
+            lhs.result, lhs.where,
+            scope_tree=ctx.path_scope, singletons=(), env=ctx.env, strict=True)
+    else:
+        filtered_ptrs = None
+
+    # TODO: Can we support some >1 cases?
+    if not filtered_ptrs or len(filtered_ptrs) > 1:
+        error("does not contain exactly one FILTER clause")
+        return None
+
+    exclusive_constr: s_constr.Constraint = schema.get('std::exclusive')
+
+    # Every filter must be on a unique pointer
+    # Adapted from _analyze_filter_caluse
+    for ptr, _ in filtered_ptrs:
+        ptr = ptr.get_nearest_non_derived_parent(schema)
+        is_unique = (
+            ptr.is_id_pointer(schema) or
+            any(c.issubclass(schema, exclusive_constr)
+                for c in ptr.get_constraints(schema).objects(schema))
+        )
+        if not is_unique or not ptr.is_property(schema):
+            error("FILTER is not on an exclusive property")
+
+    shape_props = {}
+    for shape_set, _ in rhs.subject.shape:
+        # what's the base_ptr deal here???
+        base_ptr = shape_set.rptr.ptrref.base_ptr
+        if (not isinstance(base_ptr, irast.PointerRef)
+                or not isinstance(shape_set.expr, irast.SelectStmt)):
+            continue
+        prop_id = base_ptr.name
+        shape_props[prop_id] = shape_set.expr.result, base_ptr
+
+    ret = []
+    for ptr, ptr_set in filtered_ptrs:
+        ptr = ptr.get_nearest_non_derived_parent(schema)
+        name = ptr.get_name(schema)
+        if name not in shape_props:
+            error("property in FILTER clause does not match INSERT")
+        result, rptr = shape_props[name]
+
+        if not simple_stmt_eq(ptr_set.expr, result.expr):
+            error("value in FILTER clause does not match INSERT")
+        ret.append(rptr)
+
+    return ret
+
+
+@dispatch.compile.register(qlast.InsertQuery)
+def compile_InsertQuery(
+        expr: qlast.InsertQuery, *,
+        ctx: context.ContextLevel,
+        conditioned_on: Optional[irast.Set] = None) -> irast.Set:
+
     with ctx.subquery() as ictx:
         stmt = irast.InsertStmt()
         init_stmt(stmt, expr, ctx=ictx, parent_ctx=ctx)
@@ -324,6 +417,17 @@ def compile_InsertQuery(
 
         result = fini_stmt(stmt, expr, ctx=ictx, parent_ctx=ctx)
 
+    if conditioned_on:
+        stmt.on_conflict = handle_conditional_insert(
+            expr, stmt, conditioned_on, ctx=ctx)
+
+    if ctx.in_conditional is not None:
+        raise errors.QueryError(
+            'INSERT statements cannot be used inside conditional '
+            'expressions',
+            context=expr.context,
+        )
+
     return result
 
 
diff --git a/edb/ir/ast.py b/edb/ir/ast.py
index aee4161a5ca..15043ba8b04 100644
--- a/edb/ir/ast.py
+++ b/edb/ir/ast.py
@@ -715,7 +715,7 @@ class MutatingStmt(Stmt):
 
 
 class InsertStmt(MutatingStmt):
-    pass
+    on_conflict: typing.Optional[typing.List[PointerRef]] = None
 
 
 class UpdateStmt(MutatingStmt, FilteredStmt):
diff --git a/edb/pgsql/compiler/dml.py b/edb/pgsql/compiler/dml.py
index 07317831f3b..4425a6df26a 100644
--- a/edb/pgsql/compiler/dml.py
+++ b/edb/pgsql/compiler/dml.py
@@ -535,6 +535,20 @@ def process_insert_body(
                 env=subctx.env,
             )
 
+    if isinstance(ir_stmt, irast.InsertStmt) and ir_stmt.on_conflict:
+        assert not insert_stmt.on_conflict
+
+        fields = []
+        for ptrref in ir_stmt.on_conflict:
+            ptr_info = pg_types.get_ptrref_storage_info(
+                ptrref, resolve_type=True, link_bias=False)
+            fields.append(pgast.ColumnRef(name=[ptr_info.column_name]))
+
+        insert_stmt.on_conflict = pgast.OnConflictClause(
+            action='nothing',
+            infer=pgast.InferClause(index_elems=fields),
+        )
+
     toplevel = ctx.toplevel_stmt
     toplevel.ctes.append(insert_cte)
 
diff --git a/tests/test_edgeql_insert.py b/tests/test_edgeql_insert.py
index 50d80c65e93..9609fafb022 100644
--- a/tests/test_edgeql_insert.py
+++ b/tests/test_edgeql_insert.py
@@ -1715,3 +1715,121 @@ async def test_edgeql_insert_correlated_bad_02(self):
                     Subordinate,
                 );
             ''')
+
+
+class TestInsertCoalesce(tb.QueryTestCase):
+    SCHEMA = os.path.join(os.path.dirname(__file__), 'schemas',
+                          'constraints.esdl')
+
+    async def test_select_coalesce_insert_01(self):
+        # We use parameters here because normalization prevents
+        # constants from appearing the same.
+        # TODO: Fix normalization and update this test.
+        query = r'''
+        SELECT
+         ((SELECT test::UniqueName_2 FILTER .name = <str>$0)
+          ?? (INSERT test::UniqueName_2 {name := <str>$0})) {name};
+        '''
+
+        await self.assert_query_result(
+            query,
+            [{"name": "test"}],
+            variables=("test",)
+        )
+
+        await self.assert_query_result(
+            query,
+            [{"name": "test"}],
+            variables=("test",)
+        )
+
+        query2 = r'''
+        SELECT
+         ((SELECT test::UniqueName_2 FILTER .name = <str>$0)
+          ?? (INSERT test::UniqueName_2 {name := <str>$0}));
+        '''
+
+        res = await self.con.query(query2, "test2")
+        res2 = await self.con.query(query2, "test2")
+        assert res == res2
+
+        res3 = await self.con.query(query2, "test3")
+        assert res != res3
+
+    async def test_select_coalesce_insert_02(self):
+        base = 'Invalid conditional INSERT statement'
+
+        async with self._run_and_rollback():
+            with self.assertRaisesRegex(
+                    edgedb.QueryError,
+                    f"{base}: left hand side is not SELECT"):
+                await self.con.query(r'''
+                    SELECT
+                     ((INSERT test::UniqueName_2 {name := "status"})
+                      ?? (INSERT test::UniqueName_2 {name := <str>$0}));
+                ''')
+
+        async with self._run_and_rollback():
+            with self.assertRaisesRegex(
+                    edgedb.QueryError,
+                    f"{base}: types do not match"):
+                await self.con.query(r'''
+                    SELECT
+                     ((SELECT test::ParentUniqueName FILTER .name = <str>$0)
+                      ?? (INSERT test::UniqueName_2 {name := <str>$0}));
+                ''')
+
+        async with self._run_and_rollback():
+            with self.assertRaisesRegex(
+                    edgedb.QueryError,
+                    f"{base}: does not contain exactly one FILTER clause"):
+                await self.con.query(r'''
+                    SELECT
+                     ((SELECT test::UniqueName_2)
+                      ?? (INSERT test::UniqueName_2 {name := <str>$0}));
+                ''', "test")
+
+        async with self._run_and_rollback():
+            with self.assertRaisesRegex(
+                    edgedb.QueryError,
+                    f"{base}: does not contain exactly one FILTER clause"):
+                await self.con.query(r'''
+                    SELECT
+                     ((SELECT test::UniqueName_2
+                       FILTER .name = <str>$0 OR .name = <str>$0 ++ "!")
+                      ?? (INSERT test::UniqueName_2 {name := <str>$0}));
+                ''')
+
+        async with self._run_and_rollback():
+            with self.assertRaisesRegex(
+                    edgedb.QueryError,
+                    f"{base}: does not contain exactly one FILTER clause"):
+                await self.con.query(r'''
+                    SELECT
+                     ((SELECT test::LosingParent
+                       FILTER .name = <str>$0 AND .lp = <str>$1)
+                      ?? (INSERT test::LosingParent {
+                             name := <str>$0, lp := <str>$1}));
+                ''')
+
+        async with self._run_and_rollback():
+            with self.assertRaisesRegex(
+                    edgedb.QueryError,
+                    f"{base}: FILTER is not on an exclusive property"):
+                await self.con.query(r'''
+                    SELECT
+                     ((SELECT test::LosingParent
+                       FILTER .lp = <str>$0)
+                      ?? (INSERT test::LosingParent {
+                             name := "hello", lp := <str>$1}));
+                ''')
+
+        async with self._run_and_rollback():
+            with self.assertRaisesRegex(
+                    edgedb.QueryError,
+                    f"{base}: value in FILTER clause does not match INSERT"):
+                await self.con.query(r'''
+                    SELECT
+                     ((SELECT test::UniqueName_2 FILTER .name = "foo")
+                      ?? (INSERT test::UniqueName_2 {name := "bar"}));
+                ''')


Code review comments:
diff_hunk: @@ -715,7 +715,7 @@ class MutatingStmt(Stmt):
 
 
 class InsertStmt(MutatingStmt):
-    pass
+    on_conflict: typing.Optional[typing.List[PointerRef]] = None
path: edb/ir/ast.py
text: @user1:
SQL `ON CONFLICT` accepts constraint names as conflict targets, let's use a constraint object directly.  I suggest adding a `ConstraintRef` to IR:

```python
class ConstraintRef(ImmutableBase):
    # The id of the constraint
    id: uuid.UUID
    # The module id of the constraint
    module_id: uuid.UUID
```

You can obtain the name of the SQL constraint with `common.get_constraint_backend_name()`

@author:
That turns out to not get the right constraint name, but I think I figured it out.

diff_hunk: @@ -254,16 +258,105 @@ def compile_GroupQuery(
     return result
 
 
-@dispatch.compile.register(qlast.InsertQuery)
-def compile_InsertQuery(
-        expr: qlast.InsertQuery, *, ctx: context.ContextLevel) -> irast.Set:
+def simple_stmt_eq(lhs: irast.Base, rhs: irast.Base) -> bool:
+    if (
+        isinstance(lhs, irast.BaseConstant)
+        and isinstance(rhs, irast.BaseConstant)
+        and lhs.typeref == rhs.typeref  # XXX?
+        and lhs.value == rhs.value
+    ):
+        return True
+    elif (
+        isinstance(lhs, irast.Parameter)
+        and isinstance(rhs, irast.Parameter)
+        and lhs.name == rhs.name
+    ):
+        return True
+    else:
+        return False
 
-    if ctx.in_conditional is not None:
+
+def handle_conditional_insert(
+        expr: qlast.InsertQuery,
+        rhs: irast.InsertStmt,
+        lhs_set: Union[irast.Expr, irast.Set],
+        *, ctx: context.ContextLevel,
+) -> List[irast.PointerRef]:
+    def error(s: str) -> NoReturn:
         raise errors.QueryError(
-            'INSERT statements cannot be used inside conditional expressions',
+            f'Invalid conditional INSERT statement: {s}',
             context=expr.context,
         )
 
+    schema = ctx.env.schema
+
+    if not isinstance(lhs_set, irast.Set):
+        error("left hand side is not SELECT")
+    lhs_set = irutils.unwrap_set(lhs_set)
+    if not isinstance(lhs_set.expr, irast.SelectStmt):
+        error("left hand side is not SELECT")
+    lhs = lhs_set.expr
+
+    if lhs.result.path_id != rhs.subject.path_id:
+        error("types do not match")
+
+    if lhs.where:
+        filtered_ptrs = inference.cardinality.extract_filters(
+            lhs.result, lhs.where,
+            scope_tree=ctx.path_scope, singletons=(), env=ctx.env, strict=True)
+    else:
+        filtered_ptrs = None
+
+    # TODO: Can we support some >1 cases?
+    if not filtered_ptrs or len(filtered_ptrs) > 1:
+        error("does not contain exactly one FILTER clause")
+        return None
+
+    exclusive_constr: s_constr.Constraint = schema.get('std::exclusive')
+
+    # Every filter must be on a unique pointer
+    # Adapted from _analyze_filter_caluse
+    for ptr, _ in filtered_ptrs:
+        ptr = ptr.get_nearest_non_derived_parent(schema)
+        is_unique = (
+            ptr.is_id_pointer(schema) or
+            any(c.issubclass(schema, exclusive_constr)
+                for c in ptr.get_constraints(schema).objects(schema))
+        )
+        if not is_unique or not ptr.is_property(schema):
+            error("FILTER is not on an exclusive property")
+
+    shape_props = {}
+    for shape_set, _ in rhs.subject.shape:
+        # what's the base_ptr deal here???
+        base_ptr = shape_set.rptr.ptrref.base_ptr
path: edb/edgeql/compiler/stmt.py
text: @user1:
You can use `ptrcls_from_ptrref` to obtain the pointer schema object from the descriptor and then compare it directly with `filtered_ptrs` (well, `get_nearest_non_derived_parent` thereof).

diff_hunk: @@ -254,16 +258,105 @@ def compile_GroupQuery(
     return result
 
 
-@dispatch.compile.register(qlast.InsertQuery)
-def compile_InsertQuery(
-        expr: qlast.InsertQuery, *, ctx: context.ContextLevel) -> irast.Set:
+def simple_stmt_eq(lhs: irast.Base, rhs: irast.Base) -> bool:
+    if (
+        isinstance(lhs, irast.BaseConstant)
+        and isinstance(rhs, irast.BaseConstant)
+        and lhs.typeref == rhs.typeref  # XXX?
path: edb/edgeql/compiler/stmt.py
text: @user1:
How about using `evaluate_to_python_val` here to compute the value of the constant for comparison?

@author:
Oh, yeah. I looked at doing that for the whole comparison and didn't because it wouldn't work for parameters but it should work here.

Does the typeref `==` comparison actually work, by the way?

@user1:
It does so by accident because we maintain a `type -> typeref` cache.  If you want to compare them reliably, you have to compare `typeref.id`, but it'd be much better to do `ir_typeref_to_type` and compare the type object instead.