Skip to content

Bind a lone buffer positionally instead of as named parameters - #234

Open
ivanjeremic wants to merge 1 commit into
tursodatabase:mainfrom
ivanjeremic:fix/buffer-bind-params
Open

Bind a lone buffer positionally instead of as named parameters#234
ivanjeremic wants to merge 1 commit into
tursodatabase:mainfrom
ivanjeremic:fix/buffer-bind-params

Conversation

@ivanjeremic

Copy link
Copy Markdown

The bug

Binding a single Buffer to a statement panics out of the runtime:

const db = new Database(":memory:");
db.exec("CREATE TABLE kv (key BLOB PRIMARY KEY, value BLOB NOT NULL)");
const key = Buffer.from([1, 2, 3]);

db.prepare("INSERT INTO kv (key, value) VALUES (?, ?)").run(key, Buffer.from([9])); // fine
db.prepare("SELECT value FROM kv WHERE key = ?").get(key);                          // panics
thread '<unnamed>' panicked at src/lib.rs:1354:62:
called `Option::unwrap()` on a `None` value

On 0.6.0-pre.41 it aborts the process outright (fatal runtime error: failed to initiate panic).

Why

map_params decides positional vs named by JS type, and a Buffer is an object:

ValueType::Object => {
    let object = params.coerce_to_object()?;
    if object.is_array()? { map_params_array(object) } else { map_params_object(stmt, object) }
}

So a lone buffer goes to map_params_object, which asks for each parameter's name. For an anonymous ? that is None, and the .unwrap() panics. Two arguments take the positional path, which is why the insert above survives and the select does not — and why the existing blob tests pass: they bind inside an array (insertStmt.run([array])), never alone.

The fix

  • Buffers and typed arrays take the positional path, as they already do inside an array. map_value has the is_buffer / is_typedarray checks; this reuses them one level up.
  • map_params_object reports an anonymous-parameter statement instead of unwrapping None, so genuine misuse — a named-parameter object against ? placeholders — throws a JS error rather than killing the process.

Tests

Added to integration-tests/tests/sync.test.js:

  • a lone Buffer, a Buffer inside an array, and a Uint8Array all bind and read back the same blob;
  • a named-parameter object against anonymous ? throws rather than aborting.

I could not build or run the suite locally — no Rust toolchain on the machine I wrote this on — so the change is source-reviewed only and needs your CI to confirm it. Reproduced the bug itself on both 0.5.29 and 0.6.0-pre.41.

Related

Found while using libSQL as a key-value engine, where every key is a Buffer; the workaround until now was encoding keys as hex text.

A buffer is a JS object, so `map_params` sent it down the named-parameter
path, where `stmt.parameter_name()` returns None for an anonymous '?' and the
unwrap panicked out of the runtime — taking the process with it on recent
builds. Passing the same buffer inside an array worked, which is why the
existing blob tests did not catch it.

Buffers and typed arrays now take the positional path, as they already do
inside an array, and a named-parameter object offered to a statement with
anonymous parameters is reported rather than unwrapped.

Reproduces on 0.5.29 and 0.6.0-pre.41.
ivanjeremic added a commit to zelavis/zelavis that referenced this pull request Sep 12, 2026
#382)

Binding a lone Buffer routed it down the named-parameter path, where an
anonymous '?' has no name and the unwrap panicked out of the runtime, aborting
the process on 0.6.0-pre.41. Fixed in tursodatabase/libsql-js#234 from a fork
under repos/libsql-js: buffers and typed arrays bind positionally, and a
named-parameter object against '?' throws instead.

The item is no longer "report it" but "take the keys back off hex once a
release carries it".

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@ivanjeremic

Copy link
Copy Markdown
Author

Update: I've now built this locally (Rust 1.98.1, npm run build:debug) and verified it against the package entry point, so the "source-reviewed only" caveat in the description no longer applies.

Before the patch, the second line below aborted the process. After it:

insert with a Buffer: ok
select by a lone Buffer: { value: <Buffer 09> }
select by a lone Uint8Array: { value: <Buffer 09> }
range scan by Buffers: 1 row(s)
named object against a ? statement: SQLite3 cannot bind named parameters to a statement using anonymous '?' parameters; pass them positionally instead

So both halves hold: buffers and typed arrays bind positionally, and a named-parameter object offered to a statement with anonymous ? throws a JS error rather than panicking out of the runtime.

I have not run the full integration-tests suite locally — it expects a provider setup I don't have — so the two tests added here still need your CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant