From c6b9ae52bdcadc171d117753b27b66d475663c77 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Sun, 9 Aug 2026 20:02:53 +0500 Subject: [PATCH] sqlite: improve error for excess bound parameters Signed-off-by: Lazizbek Ergashev --- doc/api/sqlite.md | 3 ++ src/node_sqlite.cc | 14 ++++++++- test/parallel/test-sqlite-statement-sync.js | 33 +++++++++++++++++---- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/doc/api/sqlite.md b/doc/api/sqlite.md index 8a875035f8cb..d1e0591d9202 100644 --- a/doc/api/sqlite.md +++ b/doc/api/sqlite.md @@ -1066,6 +1066,9 @@ db.prepare('SELECT ?2 AS a, ?1 AS b').get('first', 'second'); // { a: 'second', b: 'first' } ``` +Passing more anonymous parameters than the statement accepts throws an +`ERR_INVALID_STATE` error. The `?NNN` form raises the number accepted to `NNN`. + Named parameters begin with one of the prefix characters `$`, `:`, or `@` in SQL. They are bound from an object passed as the first argument. Repeating a name in the SQL binds the same value to every occurrence. diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 9ae07441917c..3b558910ad78 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -3077,14 +3077,26 @@ bool StatementSync::BindParams(const FunctionCallbackInfo& args) { anon_start++; } + int param_count = sqlite3_bind_parameter_count(statement_.get()); + for (int i = anon_start; i < args.Length(); ++i) { - while (1) { + while (anon_idx <= param_count) { const char* param = sqlite3_bind_parameter_name(statement_.get(), anon_idx); if (param == nullptr || param[0] == '?') break; anon_idx++; } + if (anon_idx > param_count) { + THROW_ERR_INVALID_STATE( + env(), + "Too many anonymous parameter values were provided. " + "The statement accepts %d, but received %d", + i - anon_start, + args.Length() - anon_start); + return false; + } + if (!BindValue(args[i], anon_idx)) { return false; } diff --git a/test/parallel/test-sqlite-statement-sync.js b/test/parallel/test-sqlite-statement-sync.js index 44eb482f9bdb..0cc07dbab312 100644 --- a/test/parallel/test-sqlite-statement-sync.js +++ b/test/parallel/test-sqlite-statement-sync.js @@ -405,7 +405,7 @@ suite('StatementSync.prototype.run()', () => { t.assert.deepStrictEqual(stmt.run(), { changes: 1, lastInsertRowid: 1 }); }); - test('SQLite throws when trying to bind too many parameters', (t) => { + test('throws when trying to bind too many parameters', (t) => { using db = new DatabaseSync(':memory:'); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' @@ -415,10 +415,33 @@ suite('StatementSync.prototype.run()', () => { t.assert.throws(() => { stmt.run(1, 2, 3); }, { - code: 'ERR_SQLITE_ERROR', - message: 'column index out of range', - errcode: 25, - errstr: 'column index out of range', + code: 'ERR_INVALID_STATE', + message: 'Too many anonymous parameter values were provided. ' + + 'The statement accepts 2, but received 3', + }); + + t.assert.throws(() => { + db.prepare('SELECT 1').run(5); + }, { + code: 'ERR_INVALID_STATE', + message: 'Too many anonymous parameter values were provided. ' + + 'The statement accepts 0, but received 1', + }); + + t.assert.throws(() => { + db.prepare('SELECT $a AS a, ? AS b').run({ $a: 1 }, 2, 3); + }, { + code: 'ERR_INVALID_STATE', + message: 'Too many anonymous parameter values were provided. ' + + 'The statement accepts 1, but received 2', + }); + + t.assert.throws(() => { + db.prepare('SELECT $a AS a').run(1); + }, { + code: 'ERR_INVALID_STATE', + message: 'Too many anonymous parameter values were provided. ' + + 'The statement accepts 0, but received 1', }); });