Skip to content

sqlite: improve error for excess bound parameters - #65164

Open
lazerg wants to merge 1 commit into
nodejs:mainfrom
lazerg:fix/issue-65163-excess-bound-params
Open

sqlite: improve error for excess bound parameters#65164
lazerg wants to merge 1 commit into
nodejs:mainfrom
lazerg:fix/issue-65163-excess-bound-params

Conversation

@lazerg

@lazerg lazerg commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Fixes: #65163

BindParams() walked the anonymous arguments without checking how many parameters the statement actually has, so an extra argument failed inside sqlite3_bind_* and came back as ERR_SQLITE_ERROR: column index out of range. To a JavaScript caller "column" reads as a table column, which points at the schema rather than at the extra argument.

Binding now stops when it runs out of parameter slots and throws ERR_INVALID_STATE with both counts, the same way an unknown named parameter is already reported from that function.

Callers matching on ERR_SQLITE_ERROR or errcode 25 for this case will see the new error instead.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/sqlite

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem. labels Aug 9, 2026
@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.31%. Comparing base (ee5a070) to head (f4ad7ab).

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #65164      +/-   ##
==========================================
+ Coverage   90.30%   90.31%   +0.01%     
==========================================
  Files         759      759              
  Lines      248328   248336       +8     
  Branches    46853    46858       +5     
==========================================
+ Hits       224243   224283      +40     
+ Misses      15514    15465      -49     
- Partials     8571     8588      +17     
Files with missing lines Coverage Δ
src/node_sqlite.cc 81.25% <100.00%> (+0.01%) ⬆️

... and 28 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Signed-off-by: Lazizbek Ergashev <lazerg2@gmail.com>
@lazerg
lazerg force-pushed the fix/issue-65163-excess-bound-params branch from 2fc910b to f4ad7ab Compare August 9, 2026 17:07

@pacocartones pacocartones left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent verification — logic checks out; one doc/test gap to consider

I verified the fix independently, both against the C++ and against live SQLite semantics (Node 24, node:sqlite). The core change is correct and the error message counts are accurate.

Root cause confirmed: the old loop kept binding anonymous values past the statement's parameter count; sqlite3_bind_* then failed with SQLITE_RANGE (errcode 25), which surfaced as the opaque ERR_SQLITE_ERROR: column index out of range. The explicit capacity check before binding fixes exactly that.

Loop verification (PR head):

  • anon_idx++ after BindValue is retained, so each arg binds to a distinct slot.
  • The while (anon_idx <= param_count) skip over named parameters is correct for every interleaving I checked (named-only, anon-only, $a, ?, ?, $a, $a, ?, $b, ?).
  • The counts are right: at failure, i - anon_start equals the number of anonymous values bound, which — since the throw only fires once every slot up to param_count is taken — is the statement's actual anonymous capacity; args.Length() - anon_start is the number of anonymous values received (the named-params object excluded). I hand-checked all four new tests against the code and the expected messages match.

?NNN semantics (relevant to the new doc sentence): SQLite numbers ?NNN by explicit index and silently allows binding to intermediate "phantom" indices, so SELECT ?2 genuinely accepts up to two values (get('x', 'y'){ a: 'y' }) and SELECT ?2, ? numbers the bare ? at index 3 (bind count 3). The new loop handles these correctly — the doc claim "The ?NNN form raises the number accepted to NNN" holds.

One gap (non-blocking): that doc sentence is the only place the ?NNN form is covered, and there is no test for it. The four added tests exercise ?1,?2, no params, mixed named+anonymous, and named-only. Suggest adding a ?NNN case (e.g. db.prepare('SELECT ?2 AS a').run('x', 'y') succeeds; .run('x', 'y', 'z') throws the new ERR_INVALID_STATE with accepts 2, received 3) — it locks in the documented behavior, including the surprising phantom-index binding, and guards the new loop against regressions.

LGTM otherwise.

message: 'column index out of range',
errcode: 25,
errstr: 'column index out of range',
code: 'ERR_INVALID_STATE',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess ERR_INVALID_ARG_TYPE is more appropriate

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at both codes before I picked this one.

doc/api/errors.md defines ERR_INVALID_ARG_TYPE as "An argument of the wrong type was passed to a Node.js API". Here the values have valid types. There are simply more of them than the statement has anonymous slots.

The closest case is the unknown named parameter failure in the same function. It throws ERR_INVALID_STATE, and sqlite.md documents it that way. In node_sqlite.cc every ERR_INVALID_ARG_TYPE is a real type check, and the docs already draw that line: a value of an unsupported type gives ERR_INVALID_ARG_TYPE, a parameter that the statement does not accept gives ERR_INVALID_STATE.

So I kept ERR_INVALID_STATE for consistency. If you still prefer ERR_INVALID_ARG_TYPE, say so and I will change it.

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

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sqlite: excess bound parameters produce an opaque "column index out of range" error

4 participants