diff --git a/doc/api/sqlite.md b/doc/api/sqlite.md index 57a0007baab..da4db1b34d3 100644 --- a/doc/api/sqlite.md +++ b/doc/api/sqlite.md @@ -1011,6 +1011,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 027372e42da..70f033dcdb5 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -2777,13 +2777,25 @@ 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) { + while (anon_idx <= param_count) { const char* param = sqlite3_bind_parameter_name(statement_, 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 cf0e4daa45c..686879271b5 100644 --- a/test/parallel/test-sqlite-statement-sync.js +++ b/test/parallel/test-sqlite-statement-sync.js @@ -337,7 +337,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(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( @@ -348,10 +348,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', }); });