diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 8e2f13cd6334..ade13640dce5 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -2702,6 +2702,8 @@ bool StatementSync::BindParams(const FunctionCallbackInfo& args) { anon_start++; } + int param_count = sqlite3_bind_parameter_count(statement_); + for (int i = anon_start; i < args.Length(); ++i) { while (1) { const char* param = sqlite3_bind_parameter_name(statement_, anon_idx); @@ -2709,6 +2711,19 @@ bool StatementSync::BindParams(const FunctionCallbackInfo& args) { anon_idx++; } + // More values were supplied than the statement has parameters to bind + // them to. Report that directly instead of letting sqlite3_bind_*() + // surface its "column index out of range" error, which reads as a + // problem with the table rather than with the call. + if (anon_idx > param_count) { + THROW_ERR_INVALID_ARG_VALUE( + env(), + "Too many parameter values were provided. The statement accepts %d " + "parameter(s), which are already bound.", + param_count); + return false; + } + if (!BindValue(args[i], anon_idx)) { return false; } diff --git a/test/parallel/test-sqlite-data-types.js b/test/parallel/test-sqlite-data-types.js index 26af15a777d2..718a299266a2 100644 --- a/test/parallel/test-sqlite-data-types.js +++ b/test/parallel/test-sqlite-data-types.js @@ -151,6 +151,41 @@ suite('data binding and mapping', () => { }); }); + test('throws when more values are provided than the statement accepts', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY) STRICT;'); + + t.assert.throws(() => { + db.prepare('INSERT INTO data (key) VALUES (?)').run(1, 2); + }, { + code: 'ERR_INVALID_ARG_VALUE', + message: /Too many parameter values were provided.+accepts 1 parameter/, + }); + + // A statement with no parameters at all. + t.assert.throws(() => { + db.prepare('SELECT 1').get(5); + }, { + code: 'ERR_INVALID_ARG_VALUE', + message: /Too many parameter values were provided.+accepts 0 parameter/, + }); + + // Excess anonymous values alongside named parameters. + t.assert.throws(() => { + db.prepare('INSERT INTO data (key) VALUES ($k)').run({ $k: 1 }, 2); + }, { + code: 'ERR_INVALID_ARG_VALUE', + message: /Too many parameter values were provided.+accepts 1 parameter/, + }); + + // The correct number of values still binds. + t.assert.deepStrictEqual( + db.prepare('INSERT INTO data (key) VALUES (?)').run(10), + { changes: 1, lastInsertRowid: 10 }, + ); + }); + test('throws when binding a BigInt that is too large', (t) => { const max = 9223372036854775807n; // Largest 64-bit signed integer value. const db = new DatabaseSync(nextDb()); diff --git a/test/parallel/test-sqlite-statement-sync.js b/test/parallel/test-sqlite-statement-sync.js index b3a1dc434537..de32a2c42c73 100644 --- a/test/parallel/test-sqlite-statement-sync.js +++ b/test/parallel/test-sqlite-statement-sync.js @@ -310,7 +310,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) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec( @@ -321,10 +321,8 @@ 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_ARG_VALUE', + message: /Too many parameter values were provided.+accepts 2 parameter/, }); });