Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/modules/ROOT/pages/how-to/databases-and-queries.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,15 @@ Bind values as parameters instead of pasting them into the statement text. Ident
* Named: `:name` in the statement and a dict as the one extra argument. A named parameter can hold a collection, as in `age IN :ages`.
* `None` binds as null.

A list or tuple on its own is the positional list, so it never binds as one parameter. `db.query("sql", "SELECT FROM V WHERE @rid IN ?", [rid_a, rid_b])` has two values for one `?`: the engine would bind `rid_a`, drop `rid_b` without a word and return one row where two were meant. The bindings raise `TypeError` before the statement runs when a SQL or SQLScript call has more positional values than the statement can bind, because the engine does not count them (https://github.com/ArcadeData/arcadedb/issues/10721[ArcadeData/arcadedb#10721]). To bind a list as one parameter, bind it by name or wrap it so it is the one positional value:
A list or tuple on its own is the positional list, so it never binds as one parameter. `db.query("sql", "SELECT FROM V WHERE @rid IN ?", [rid_a, rid_b])` has two values for one `?`. The bindings raise `TypeError` before the statement runs when a SQL or SQLScript call has more positional values than the statement can bind, and since 26.11.1 the engine refuses a mismatched count too (https://github.com/ArcadeData/arcadedb/issues/10721[ArcadeData/arcadedb#10721]). Older engines bound `rid_a`, dropped `rid_b` without a word and returned one row where two were meant. To bind a list as one parameter, bind it by name or wrap it so it is the one positional value:

[source,python]
----
db.query("sql", "SELECT FROM V WHERE @rid IN :ids", {"ids": [rid_a, rid_b]})
db.query("sql", "SELECT FROM V WHERE @rid IN ?", [[rid_a, rid_b]])
----

The check counts the `?`, `$` and `:` characters of the SQL text, so the count is an upper bound: a `?` inside a string literal counts as a placeholder, and a call that binds is never refused. Fewer values than placeholders are not checked, and the engine binds null for the missing ones. `db.query` and `db.command` run the check; the async executor does not.
The bindings' check counts the `?`, `$` and `:` characters of the SQL text, so its count is an upper bound: a `?` inside a string literal counts as a placeholder, and a call that binds is never refused. The engine counts exactly: from 26.11.1 it also refuses fewer values than placeholders, with an error that names both counts (an `ArcadeDBError` wrapping the engine's `CommandSQLParsingException`), where older engines bound null for the missing ones. Pass `None` for a placeholder you mean as null. `db.query` and `db.command` run the bindings' check; the async executor does not, so there the engine's error reaches the error callback.

=== Results

Expand Down
8 changes: 5 additions & 3 deletions src/arcadedb/_params.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""More positional values than a statement has placeholders is a wrong call, refused before it runs.

``query("sql", "... WHERE @rid IN ?", [rid_a, rid_b])`` reads the lone list as the positional-parameter list (the documented form,
one element per ``?``). The statement holds one ``?``, the engine binds the first element, ignores the second without a word, and
the query returns fewer rows than the caller meant. The engine does not count (it binds null for a missing value too;
ArcadeData/arcadedb#10721), and the bindings cannot ask it: the parsed statement's placeholder count is no public engine call.
one element per ``?``). The statement holds one ``?``. Engines before 26.11.1 bound the first element, ignored the second without a
word, and returned fewer rows than the caller meant (and bound null for a missing value). Since 26.11.1 the engine refuses a
mismatched count itself (ArcadeData/arcadedb#10721); this check still runs because it fails earlier, without an engine round trip,
with a TypeError that names the fix, and because on a build linked to an older engine nothing else stops it. The bindings cannot ask
the engine for the count: the parsed statement's placeholder count is no public engine call.
So the check is the one that never refuses a call that binds: an UPPER BOUND on the placeholders, the characters ``?``, ``$`` and
``:`` anywhere in the SQL text (a ``?`` in a string literal counts, so such a statement is checked more loosely, never refused
wrongly). A call with more values than that bound has a value no placeholder can take.
Expand Down
8 changes: 4 additions & 4 deletions tests/test_positional_count.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""More positional values than the statement has placeholders is refused, not bound silently.

``query("sql", "... WHERE @rid IN ?", [rid_a, rid_b])`` reads the lone list as the positional-parameter list (the documented
form: one element per ``?``). The statement has one ``?``, so the engine bound the first element, dropped the second without a
word, and returned fewer rows than the caller meant. The engine does not check the count (it also lets a call with fewer values
than placeholders bind null; ArcadeData/arcadedb#10721), so the bindings refuse the form that cannot be right: more values than
the statement can hold.
form: one element per ``?``). The statement has one ``?``, so engines before 26.11.1 bound the first element, dropped the second
without a word, and returned fewer rows than the caller meant (and bound null for a missing value). Since 26.11.1 the engine
refuses a mismatched count itself (ArcadeData/arcadedb#10721); the bindings still refuse the form that cannot be right, more values
than the statement can hold, before the call, with a TypeError that names the fix.

The check is an upper bound (placeholder characters counted anywhere in the text), so it never refuses a call that binds.
"""
Expand Down
Loading