Repository navigation
Bindings: more positional values than placeholders is a TypeError, not a silently dropped value - #52
Merged
Merged
Conversation
query("sql", "... WHERE @Rid IN ?", [rid_a, rid_b]) reads the lone list as the positional-parameter list, one element per ?. The statement has one ?, so the engine binds rid_a, drops rid_b without a word, and returns one row where two were meant. The engine does not count positional values (ArcadeData/arcadedb#10721), so Database.query() and command() now raise TypeError before the statement runs when a SQL or SQLScript call has more positional values than the text can bind. The count is an upper bound taken from the ?, $ and : characters of the text, so a call that binds is never refused.
The message names both ways out: wrap the list, [[a, b]], or bind it by name, IN :ids. test_parameter_binding.py passed values to SELECT 1 (no placeholder) and now uses SELECT ?, ?. The how-to page documents the behaviour.
Port of humemai/arcadedb-embedded-python#536.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
tae898
added a commit
that referenced
this pull request
Oct 25, 2026
…on never replays a partly committed block Three Python-only ports from humemai/arcadedb-embedded-python native-preview (#537, #531, #527): - NativeDatabase._exec calls refuse_surplus_positionals (already shared with the JVM build, PR #52): more positional values than the SQL text can hold is a TypeError, not a silently dropped value. - The native RID class offers the Java RID surface (ordering, getBucketId, getPosition, toString, isValid, equals, compareTo) and Result.get_rid() reads a projected @Rid. - run_in_transaction: the guard that refuses to replay a block whose BATCH boundary or commit() already made part of its work durable sat inside a try whose own except swallowed the raise. The raise moves outside it. New test tests/test_run_in_transaction_partial_commit.py runs on both builds. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
db.query("sql", "SELECT FROM V 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 bindsrid_a, dropsrid_bwithout a word, and the query returns one row where two were meant. The engine does not count positional values: it also ignores a second value for a?and binds null for a missing one (ArcadeData/arcadedb#10721). This is the client-side guard until the engine checks it.Port of humemai/arcadedb-embedded-python#536.
Behaviour change
A
db.query()ordb.command()call with more positional values than the SQL statement can bind raisesTypeErrorbefore the statement runs (not wrapped asArcadeDBError, because it is a wrong call and not a failed statement). The message names both ways out: wrap the list,[[rid_a, rid_b]], or bind it by name,IN :idswith{"ids": [rid_a, rid_b]}. The documented API is unchanged: a lone list or tuple is still the positional list, and a call with one value per placeholder binds as before. The only calls refused are those that dropped a value.The count is an upper bound taken from the
?,$and:characters of the text, withsqlandsqlscriptchecked and other languages left alone. It is not an exact count: the parsed statement's placeholder count is no public engine call, and named parameters carry positional numbers in the parser, so an exact count from the text would be a SQL tokenizer. The bound never refuses a call that binds (a?inside a string literal counts as a placeholder, so such a statement is checked more loosely). Fewer values than placeholders are not checked. The async executor is not checked either; this change touchesquery()andcommand()only. The check is a new module,src/arcadedb/_params.py, called once from each of the two methods.Tests
tests/test_positional_count.py(20 tests): list, tuple and several arguments for one?are refused forquery()andcommand(), a refusedcommand()changes nothing, the error is aTypeErrorand notQuery failed, and the forms that bind keep binding (one value per?, a wrapped list, a named list,$n, SQLScript, a?in a literal, openCypher).origin/main(none raised) and all 20 pass with the change.test_each_parameter_shape_reaches_one_java_overloadpassed values toSELECT 1(no placeholder); it now usesSELECT ?, ?, since it only checks overload resolution.Docs
This repository has one page on parameters,
how-to/databases-and-queries.adoc, and it gets the new paragraph and the two forms that bind a list. #536 also edited an API reference page and a test table in the old docs site; neither exists here.Checked locally
tests/test_positional_count.pyandtests/test_parameter_binding.py: 48 passed (red run before the change: 9 failed, 39 passed).arcadedb_embedded26.11.1.dev0, engine 26.11.1-SNAPSHOT): 1529 passed, 21 failed, 4 skipped (neo4jis not in my environment). 20 of the failures are the same 20 that fail with the source fromorigin/main(checked by running those six files on it): this wheel's bridge jar predatesGraphCalls.ridSet,FloatVectorsand the vertex-typeinsert_many, and its jars fail the jar-closure and server-packaging checks. The 21st,test_python_function_interrupt.py::test_sigint_during_a_query_that_calls_python_is_a_keyboard_interrupt, counted no interrupt during the full run (a timing test, with another job running on the machine) and passes 3 of 3 alone on this branch. None of the 21 mention positional values.pre-commiton the touched files: black and isort pass, the YAML, shfmt and actionlint hooks have no files, andscripts/check_no_embedded_name.pypasses.-c pyproject.toml --severity-level low --confidence-level low) on the touched Python files: no issues.isinstanceand a length compare on the one-value paths and a cachedstr.counton calls with two or more values; no timing claim is made.🤖 Generated with Claude Code