Repository: edgedb/edgedb

Issue:
edb.errors.InternalServerError: bind message supplies 2 parameters, but prepared statement "" requires 1
```
START MIGRATION TO {
    module default {
        type Movie {
            required property title -> str;
            multi link actors -> Person {
                property index -> int64;
            };
        }
        type Person {
            required property first_name -> str;
            required property last_name -> str;
        }
    }
};
```
```
tutorial> INSERT Movie {
.........     title := "demo",
.........     actors := {
.........         (SELECT Person LIMIT 1){index := 0}
.........     }
......... };
ERROR: InternalServerError: bind message supplies 2 parameters, but prepared statement "" requires 1
  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
  Server traceback:
      edb.errors.InternalServerError: bind message supplies 2 parameters, but prepared statement "" requires 1
```


Discussion:
This appears to be #1442.

You must refer to link properties with `@`:

```
tutorial> INSERT Movie {
.........     title := "demo",
.........     actors := {
.........         (SELECT Person LIMIT 1){@index := 0}
.........     }
......... };
```
This specific query is OK as is from the syntax standpoint, even though we *know* that the user probably meant setting a link property, but didn't use the correct syntax. Should we send a warning in cases like this? Perhaps we need to add a special header to the response of the `Prepare` message for cases like this.
I tried that in the first place, but couldn't figure out what I'm doing wrong (I also tried `@Movie.actors.index` etc.)

```
tutorial> INSERT Movie {
.........     title := "demo",
.........     actors := {
.........         (SELECT Person LIMIT 1){@index := 0}
.........     }
......... };
error: invalid reference to link property in top level shape
  ┌─ query:4:34
  │
4 │         (SELECT Person LIMIT 1){@index := 0}
  │                                  ^^^^^^ error

```
Try removing the extra `{` around `SELECT`:

```
tutorial> INSERT Movie {
.........     title := "demo",
.........     actors :=
.........         (SELECT Person LIMIT 1){@index := 0}
......... 
......... };
```
> Try removing the extra { around SELECT:

Isn't that what I will need when I am inserting multiple entries? (to make them a set)
> Isn't that what I will need when I am inserting multiple entries? (to make them a set)

Everything is always a set in EdgeQL, and set cardinality semantics is inferred from the context. Syntactically, `Foo` and `{Foo}` are completely equivalent (except when there's a bug like the above).
Oh, I see, thanks so much! 
I can successfully insert a single item, but when it comes to multiple sets I couldn't find a way to do it
```
tutorial> INSERT Movie {
.........     title := "demo",
.........     actors :=
.........         (SELECT Person LIMIT 1){@index := 0}
......... };
{Object {id: e6f087a2-fc38-11ea-ba60-a73feaa4e1dc}}
tutorial> INSERT Movie {
.........     title := "demo",
.........     actors :=
.........         (SELECT Person LIMIT 1){@index := 0},
.........         (SELECT Person LIMIT 1){@index := 1}
......... };
error: Unexpected '('
  ┌─ query:5:9
  │
5 │         (SELECT Person LIMIT 1){@index := 1}
  │         ^^ error

tutorial> INSERT Movie {
.........     title := "demo",
.........     actors :=
.........         ((SELECT Person LIMIT 1){@index := 0}),
.........         ((SELECT Person LIMIT 1){@index := 1})
......... };
error: Unexpected '('
  ┌─ query:5:9
  │
5 │         ((SELECT Person LIMIT 1){@index := 1})
  │         ^^ error

tutorial> INSERT Movie {
.........     title := "demo",
.........     actors := {
.........         (SELECT Person LIMIT 1){@index := 0},
.........         (SELECT Person LIMIT 1){@index := 1}
.........     }
......... };
error: invalid reference to link property in top level shape
  ┌─ query:4:34
  │
4 │         (SELECT Person LIMIT 1){@index := 0},
  │                                  ^^^^^^ error

tutorial> 
```
Yep, this is a bug. Here's a workaround for now:

```
INSERT Movie {
    actors := (
        WITH
            persons := {
                (SELECT Person LIMIT 1),
                (SELECT Person LIMIT 1)
            },
        FOR actor IN {enumerate(persons)}
        UNION (SELECT actor.1 { @index := actor.0 })
    )
};
```
> Yep, this is a bug. 

Okay, guess I don't have to submit another issue? 

> Here's a workaround for now:

Awesome! Will try to draft something out of it for my system
I'm leaving this open to track the *original* issue of an ISE for the buggy query. The more general issue is subsumed by #4436.

The root of the issue here is that the `0` in the shape gets converted into a param by the query normalizer, but then is not actually used in our compiled code. We *have* a mechanism for detecting unused variables and inserting them into an unused CTE, but we are spuriously considering the param as being used.

Resolving patch:
diff --git a/edb/pgsql/ast.py b/edb/pgsql/ast.py
--- a/edb/pgsql/ast.py
+++ b/edb/pgsql/ast.py
@@ -459,8 +459,6 @@ class Param:
     #: whether parameter is required
     required: bool
 
-    used: bool = False
-
 
 class Query(ReturningQuery):
     """Generic superclass representing a query."""
diff --git a/edb/pgsql/compiler/clauses.py b/edb/pgsql/compiler/clauses.py
--- a/edb/pgsql/compiler/clauses.py
+++ b/edb/pgsql/compiler/clauses.py
@@ -23,6 +23,8 @@
 
 import random
 
+from edb.common import ast as ast_visitor
+
 from edb.edgeql import qltypes
 from edb.ir import ast as irast
 from edb.pgsql import ast as pgast
@@ -375,10 +377,19 @@ def fini_toplevel(
 
     if not ctx.env.use_named_params:
         # Adding unused parameters into a CTE
+
+        # Find the used parameters by searching the query, so we don't
+        # get confused if something has been compiled but then omitted
+        # from the output for some reason.
+        flt = lambda n: isinstance(n, pgast.ParamRef)
+        param_refs: List[pgast.ParamRef] = ast_visitor.find_children(stmt, flt)
+
+        used = {param_ref.number for param_ref in param_refs}
+
         targets = []
         for param in ctx.env.query_params:
             pgparam = argmap[param.name]
-            if pgparam.used:
+            if pgparam.index in used:
                 continue
             targets.append(pgast.ResTarget(val=pgast.TypeCast(
                 arg=pgast.ParamRef(number=pgparam.index),
diff --git a/edb/pgsql/compiler/expr.py b/edb/pgsql/compiler/expr.py
--- a/edb/pgsql/compiler/expr.py
+++ b/edb/pgsql/compiler/expr.py
@@ -129,8 +129,6 @@ def compile_Parameter(
         )
     else:
         index = ctx.argmap[expr.name].index
-        ctx.argmap[expr.name].used = True
-
         result = pgast.ParamRef(number=index, nullable=not expr.required)
 
     return pgast.TypeCast(


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
@@ -5643,3 +5643,11 @@ async def test_edgeql_insert_rebind_with_typenames_01(self):
             [{'id': str}],
             always_typenames=True,
         )
+
+    async def test_edgeql_insert_pointless_shape_elements_01(self):
+        await self.con.execute('''
+            insert Person {
+                name := "test",
+                notes := (select Note { foo := 0 })
+            };
+        ''')