fix(turso): unbound params executed as NULL — mysql CLI 'commands out of sync'#13
Merged
Conversation
… out of sync' The Turso engine executes statements with unbound parameters as NULL instead of erroring. Oracle's mysql >= 8.1 CLI probes dollar-quoting support at startup with `select $$` and requires an ERR reply; against the turso backend the probe instead returned a one-column result set (NULL row) that the CLI never reads, wedging libmysqlclient's state machine so every subsequent statement failed client-side with CR 2014 "Commands out of sync". The rusqlite backend was immune because rusqlite rejects a parameter-count mismatch before executing. Add an SQLite-tokenizer-style placeholder counter to litewire-turso and reject bind-count mismatches in query()/execute() after prepare, with the same "Wrong number of parameters passed to query. Got X, needed Y" message shape rusqlite produces. Regression coverage: unit tests for the scanner and both backend paths, plus a wire-level e2e test (feature `turso`) asserting `select $$` yields a server ERR packet and the connection stays usable. Verified with mysql:8.4 CLI via podman: before the fix every statement returned ERROR 2014; after, SELECT/CRUD/SHOW all pass against the turso backend and rusqlite behavior is unchanged.
…canner Review differential-testing found $name::type and $name::type(...) were over-counted (the :: suffix re-entered the :name arm as a phantom parameter). sqlite3 tokenize.c treats ::seg repetitions and one trailing (...) as part of the $-variable name; the scanner now does too. Adds the review's mismatch cases as regression tests plus a retirement TODO for when turso exposes a public parameter count.
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.
Problem
Oracle's
mysql8.4 CLI fails withERROR 2014 (HY000): Commands out of syncon every statement (evenSELECT 1) when the MySQL frontend is backed bylitewire-turso. The same CLI works against the rusqlite backend, and PHP mysqli works against both.Root cause
The mysql >= 8.1 CLI probes dollar-quoting support at startup by sending
select $$and expecting an ERR reply. Byte-level capture of both sessions showed the divergence:$$parses as one SQLite parameter, zero provided) → ERR packetWrong number of parameters passed to query. Got 0, needed 1→ CLI continues normally.SELECT $$returned a 1-column result set with a NULL row. The CLI only checks the probe's return code and never reads the result set, so libmysqlclient's state machine sticks atMYSQL_STATUS_GET_RESULTand every followingmysql_real_queryfails client-side with CR 2014. mysqli tolerated it because mysqlnd never sends this probe.Fix
crates/litewire-turso/src/lib.rs: add an SQLite-tokenizer-style placeholder counter (?,?NNN,:name,@name,$name; skips string literals, quoted identifiers,--and/* */comments) and reject bind-count mismatches inquery()/execute()after prepare, with the same message shape rusqlite produces. turso 0.7.0 does not exposeparameters_count()on its publicStatement(it exists only on the privateturso_sdk_kitinner), hence the scanner.Tests
param_count_scanner,unbound_parameter_rejected_like_rusqlite,execute_bind_count_mismatch_rejected(litewire-turso unit tests)crates/litewire/tests/mysql_turso_e2e.rs(featureturso): assertsselect $$yields a server ERR packet and the same connection stays usable, plus wire CRUD sanitycargo test --workspace: 25/25 suites pass;cargo clippy --workspace --all-targets -- -D warningsandcargo clippy -p litewire --features turso --all-targets -- -D warningsclean;cargo fmt --checkcleanRepro evidence (mysql:8.4 CLI via podman)
Before:
ERROR 2014 (HY000) at line 1: Commands out of sync; you can't run this command nowAfter:
rusqlite backend behavior unchanged.