Commit Graph
141 Commits
Author SHA1 Message Date
Guilherme Araújo f2b698f1cf sqlite: rename DatabaseSync and StatementSync
Rename the DatabaseSync and StatementSync classes to Database and
Statement, and the internal DatabaseSyncLimits helper to
DatabaseLimits. The old names are kept as aliases of the new classes
and are Documentation-only deprecated (DEP0210, DEP0211).

Assisted-by: Claude Code
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65988
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
2026-09-26 00:03:45 +00:00
Kamat, Trivikram 8bf65d4beb sqlite: run generator return() on cursor refilter
SQLite re-invokes xFilter on a cursor it already used, as it does
for the inner table of a correlated subquery or join, abandoning the
previous iterator mid-loop. Call its return() method so generator
`finally` blocks still run.

Refactor the xClose cleanup intoCloseIterator() so both paths share it;
on refilter, a throwing cleanup is surfaced as the query error.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: opencode
PR-URL: https://github.com/nodejs/node/pull/66195
Fixes: https://github.com/nodejs/node/issues/66193
Reviewed-By: Guilherme Araújo <arauujogui@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
2026-09-25 20:43:00 +02:00
Trevor Burnhamandbyteforge38 dd9f4dac32 sqlite: add virtual table support via createModule()
Expose SQLite's virtual table API through a new
`database.createModule(name, options)` method, wrapping
`sqlite3_create_module_v2()`. This enables read-only virtual tables
backed by JavaScript data sources, usable either as an eponymous table
(`SELECT * FROM module_name`) or via `CREATE VIRTUAL TABLE t USING
module_name`. Hidden columns pass parameters using table-valued
function syntax (`SELECT * FROM module_name(param1, param2)`).

`options` accepts `columns`, `rows`, `directOnly`, and
`useBigIntArguments`. Column types are validated against INTEGER,
TEXT, REAL, BLOB, and ANY, and column names are quoted when building
the `sqlite3_declare_vtab()` schema.

Rebased from https://github.com/nodejs/node/pull/61544, which was
opened by byteforge38 and became inactive. Changes on top of that
work:

- xColumn reports the value each hidden column was constrained to,
  rather than NULL. SQLite treats xBestIndex's `omit` as a hint, so it
  may recheck a constraint it already handed to xFilter; against NULL
  that recheck rejected every row, and `gs(1, 3) WHERE start = 1`
  returned no rows.
- xBestIndex lowers estimatedCost as it consumes constraints. With a
  constant cost the planner was free to pick the unconstrained plan and
  recheck afterwards, so a correlated parameter such as
  `FROM t, gs(t.a, t.a + 1)` also returned no rows.
- Violations of the iteration protocol report a SQLite error instead of
  calling PropagateJSError with no JavaScript exception pending. That
  left `.all()` returning undefined and `exec()` reporting success.
- xBestIndex passes the constrained hidden-column indices to xFilter
  through idxStr rather than an int bitmask, which previously aliased
  for parameter indices at or above the width of an int.
- xFilter, xNext, and xColumn take a CallbackDepthGuard. Without it
  close() from inside rows(), an iterator's next(), or a row getter
  finalized the statement that SQLite was still stepping, crashing the
  process.
- xClose calls the iterator's return() method so generator `finally`
  blocks run when SQLite stops stepping early, as it does for LIMIT or
  a `break` out of a for...of loop. It is skipped while tearing down
  from ~StatementSync or ~DatabaseSync, which run from garbage
  collection callbacks where JavaScript cannot be executed; an
  abandoned generator does not run `finally` in JavaScript either. It
  is also skipped when an error is already pending, so that error still
  reaches the caller.
- VirtualTableModule holds a BaseObjectWeakPtr<DatabaseSync> to match
  UserDefinedFunction instead of a raw pointer.
- createModule() rejects being called from an authorizer callback.
- Documents that values yielded by rows() follow the usual conversion
  rules, so a number is stored as REAL and a BigInt as INTEGER even
  when a column declares INTEGER, since virtual tables do not apply
  column affinity to the values they return.

Refs: https://github.com/nodejs/node/pull/61544
Refs: https://github.com/nodejs/node/issues/63826
Fixes: https://github.com/nodejs/node/issues/61539

Co-authored-by: byteforge38 <stormcraft318@gmail.com>
Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
Assisted-by: Claude Opus 5
PR-URL: https://github.com/nodejs/node/pull/65787
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-09-22 03:57:34 +00:00
Guilherme Araújo 25b205bd90 sqlite: throw on invalid URL path instead of abort
ValidateDatabasePath() treats any object exposing a string `href` as a
URL, then asserts the parse result with CHECK(ada::can_parse(location)).
Whether that string parses depends on user input rather than on an
invariant the code guarantees, so a value such as { href: 'not a url' }
aborted the process instead of throwing.

Replace the CHECK with an ERR_INVALID_URL exception, matching how
node_file.cc reports an unparsable URL. Both DatabaseSync() and backup()
validate their path through this function, so both paths are covered.

Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
Assisted-by: Claude Code
PR-URL: https://github.com/nodejs/node/pull/66026
Reviewed-By: Xuguang Mei <meixuguang@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
2026-09-18 05:06:10 +00:00
Shelley Vohr 1105aec934 src: fix abort when two Environments share an IsolateData
A second Environment created from the same `IsolateData`, which
embedding.md allows and the cctest fixture does, aborted as soon as it
loaded `stream_wrap`, `tcp_wrap`, `pipe_wrap`, `tty_wrap`, `http2` or
the crypto key classes, i.e. on `require('net')`: those bindings
created their per-isolate templates from the per-Environment binding
initializer and stored them with a setter that CHECKs the slot is
empty. Templates that were already cached per isolate (ffi, sqlite,
dtls) instead hit "FunctionTemplate already instantiated" when
`SetConstructorFunction()` renamed them.

Build these templates once per isolate and reuse them, set class names
when the template is created, and store the `ShutdownWrap`, `WriteWrap`
and `Http2Stream` function templates rather than their instance
templates so the second Environment can expose the same constructors.

Refs: https://github.com/nodejs/node/pull/43802
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65978
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Tim Perry <pimterry@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
2026-09-13 09:50:21 +00:00
Trivikram Kamat bd9d82d171 sqlite: track registered user-defined functions
Track scalar and aggregate/window functions on each DatabaseSync
instance. Remove registrations through SQLite destruction callbacks
and clear tracking when the database closes.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex
PR-URL: https://github.com/nodejs/node/pull/65896
Fixes: https://github.com/nodejs/node/issues/65880
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Xuguang Mei <meixuguang@gmail.com>
2026-09-10 00:03:34 +00:00
Trivikram Kamat f9072f37ed sqlite: always copy changeset before applying
SQLite can invoke JavaScript through user-defined SQL functions while
applying a changeset, even when no filter or conflict callback is set.
Copy non-empty changesets unconditionally so JavaScript cannot detach
or modify the input while SQLite is still reading it.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex
PR-URL: https://github.com/nodejs/node/pull/65870
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-09-09 01:34:15 +00:00
Trevor Burnhamandmike-git374 41cb09f8a5 sqlite: bind undefined to NULL
Omitting a named parameter binds NULL, but passing `undefined` for that
same parameter threw ERR_INVALID_ARG_TYPE. Bind `undefined` to NULL so
the two forms agree. This matches the conversion already applied to a
user-defined function's `undefined` return value, as well as SQLite's
own WASM oo1 API.

Fixes: https://github.com/nodejs/node/issues/61824
Refs: https://github.com/nodejs/node/pull/61472
Refs: https://github.com/nodejs/node/pull/62008
Co-authored-by: mike-git374 <217764531+mike-git374@users.noreply.github.com>
Assisted-by: claude:opus-5
Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65709
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-09-08 15:50:32 +00:00
Shelley Vohr 48c90fffb7 src: support building with the V8 sandbox
With V8_ENABLE_SANDBOX every ArrayBuffer backing store has to be
allocated inside the sandbox, so memory that Node.js or a library
allocated itself cannot be wrapped and has to be copied in. Several
places already special-cased this, each with its own #ifdef, but a
build with the sandbox enabled still failed to compile
(`kMaxSafeBufferSizeForSandbox`), aborted in `crypto.randomUUID()`
(`SecureBuffer` was UNREACHABLE) and threw from every caller of the
malloc-owning `Buffer::New()` (v8.serialize, string transcoding, IPC,
the public `Buffer::New(isolate, data, length)`), and trace_events,
SEA assets and FFI still wrapped outside memory unconditionally.

Add `AdoptIntoBackingStore()`, which wraps the memory as before in
regular builds and copies it into an isolate-allocated backing store,
running the deleter right away, when the sandbox is enabled, and use it
at all of those sites so the #ifdef lives in one place. The trace
category flag cannot be copied since JS polls it, so under the sandbox
`getCategoryEnabledBuffer()` returns nothing and lib falls back to
`isTraceCategoryEnabled()`; FFI zero-copy views throw
ERR_OPERATION_FAILED there for the same reason.

Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/62237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
2026-09-07 05:10:02 +00:00
Trevor Burnham c8e5c965e3 sqlite: re-validate database state after reading options
prepare(), function(), aggregate(), deserialize(), applyChangeset() and
backup() validated the connection, then read their options bag with
Object::Get(). A property getter runs arbitrary JavaScript at that
point, so a getter calling close() invalidates what was just checked.
Five of the six then passed a null sqlite3* to SQLite and crashed;
prepare() reported a spurious "out of memory".

Re-check IsOpen() after option parsing, immediately before the SQLite
call, keeping the early check so invalid calls still fail before any
user code runs. IsOpen() is the only condition a getter can change:
authorizer and callback depths are RAII-managed. createSession()
already parsed options first, so it only gains the early check.

deserialize() also latched the buffer length before reading
options.dbName. A getter that shrank the backing store left the length
too large; CopyContents() then handed the uninitialized remainder to
SQLite, from where serialize() returned it to JavaScript. Check the
CopyContents() result instead of discarding it.

function() and aggregate() cast the callback's length property with
As<Int32>() and no IsInt32() guard. length is configurable, so any type
reached the cast and produced a silently wrong arity.

Fixes: https://github.com/nodejs/node/issues/65586
Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65595
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-09-03 15:36:30 +00:00
Filip Skokan 6e7818e4f6 sqlite: run backup completion in callback scope
Run SQLite backup after-work callbacks inside an internal callback
scope. This drains promise reactions and next ticks before the event
loop can become idle.

Add a child-process regression test where backup is the final active
request.

Assisted-by: Codex
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65666
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Richard Lau <richard.lau@ibm.com>
2026-09-02 23:33:37 +00:00
Matteo Collina 5954e52aa6 sqlite: copy changeset before applying it
SQLite can invoke JavaScript conflict and filter callbacks while
applying a changeset. Copy the input first so detaching or modifying
its backing buffer during a callback does not affect the active
operation.

Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/65286
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-31 15:47:32 +00:00
Trevor Burnham 01c1300dfc sqlite: reject closing a session from a callback
SQLite runs "PRAGMA table_xinfo" from inside its pre-update hook, while
it is still walking the connection's session list. Deleting a session
from a callback that the PRAGMA triggers frees memory that walk is still
using. Both an authorizer callback and a 'sqlite.db.query' subscriber
reach that window, and either one segfaults.

Reject session.close() and Symbol.dispose when the connection is inside
any callback. An already-closed session stays a no-op so that disposal
remains idempotent.

Fixes: https://github.com/nodejs/node/issues/65428
Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65454
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-30 10:15:09 -07:00
Trevor Burnham 76aeacf687 sqlite: keep sessions alive across SQLite callbacks
Session objects are weak and nothing else holds a strong reference to
them, so a garbage collection can free one while SQLite is still using
it. SQLite runs "PRAGMA table_xinfo" from inside its pre-update hook,
which reaches JavaScript, so a GC during a callback invoked from there
can collect a session the hook is still walking.

Hold a strong reference to every attached session for the duration of
each callback SQLite invokes. The trace callback now enters that guard
before building its payload, since the allocation can itself trigger a
garbage collection.

Fixes: https://github.com/nodejs/node/issues/65460
Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65465
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-28 21:19:18 +00:00
Guilherme Araújo 2743045dc4 sqlite: throw on disposal of an in-use session
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65449
Fixes: https://github.com/nodejs/node/issues/65447
Refs: https://github.com/nodejs/node/pull/65349
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
2026-08-28 09:26:48 +00:00
Trevor Burnham 5fc9f1f2ff sqlite: reject reentry while binding parameters
c4b755ee00 tracks the statements currently being stepped and rejects
reentry into them, but the guard is established inside the execution
helpers, after the entry point has already reset the statement and
bound its parameters. Binding reads properties off the supplied
object, so a named-parameter getter runs JavaScript in that window.
Reentering the same statement there resets it a second time and, for
iterate(), hands out a second iterator; both iterators record the
same reset generation, so neither is invalidated and they interleave
rows from one virtual machine.

Establish the guard at the four StatementSync entry points instead,
before the reset, so it spans binding as well as stepping. The
existing guards inside the helpers are left in place; the stepping
set is a stack, so the nested acquisition is balanced.

Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
Assisted-by: claude:opus-5
PR-URL: https://github.com/nodejs/node/pull/65294
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-21 22:54:43 +00:00
Trivikram Kamat 37e66c41a4 sqlite: remove unused column name helper
Remove the unused column name helper from StatementExecutionHelper.
It became unused after all() and get() switched to cached names.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
PR-URL: https://github.com/nodejs/node/pull/65401
Refs: https://github.com/nodejs/node/pull/65276
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
2026-08-21 14:19:45 +00:00
Trivikram Kamat c2543d9301 sqlite: reject busy statement finalization in authorizer
An iterator can leave a statement active between sqlite3_step() calls.
Finalizing that statement from an authorizer callback can release its
locks and change the outcome of the statement being authorized.

Reject close() and Symbol.dispose when the statement is busy and the
connection is in an authorizer callback. Continue allowing idle
statements to be finalized.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: https://github.com/nodejs/node/pull/65369
Fixes: https://github.com/nodejs/node/issues/65368
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
2026-08-21 03:30:10 +00:00
James M Snell 9f0ce45fe4 src: fixup manual new/delete usages
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode/Opus
PR-URL: https://github.com/nodejs/node/pull/65348
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
2026-08-20 13:03:25 -07:00
Trivikram Kamat d6bbf5799f sqlite: prevent reentrant session.close()
Reject session.close() while generating a changeset or patchset.
Closing the session at that point frees it while SQLite is still
using it, causing a use-after-free.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: https://github.com/nodejs/node/pull/65349
Fixes: https://github.com/nodejs/node/issues/65347
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
2026-08-19 06:42:46 +00:00
Edy Silva cf30b2e2c7 sqlite: reuse cached column names in statement all() and get()
Signed-off-by: geeksilva97 <edigleyssonsilva@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65276
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-18 06:21:03 +00:00
Trivikram Kamat 3abf65fc3e sqlite: validate StatementSync.run() integers
Use the standard SQLite integer conversion for changes and
lastInsertRowid. Throw ERR_OUT_OF_RANGE when a value cannot be
represented safely as a Number, or return it as a BigInt when
BigInt reads are enabled.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: https://github.com/nodejs/node/pull/65178
Fixes: https://github.com/nodejs/node/issues/65177
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
2026-08-17 04:01:47 +00:00
Guilherme Araújo c7b962c222 sqlite: add diagnostic channel
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/62241
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-15 19:42:37 +00:00
Guilherme Araújo c8996eaec9 sqlite: add stmt persistent flag
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/62757
Reviewed-By: Xuguang Mei <meixuguang@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
2026-08-14 21:57:31 +00:00
Trevor Burnham c4b755ee00 sqlite: reject connection access from authorizer callbacks
SQLite requires that an authorizer callback not modify the connection
that invoked it, and counts sqlite3_prepare_v2() and sqlite3_step() as
modifications. node:sqlite let the callback call prepare(), exec(), the
statement execution methods, and other connection-mutating APIs on the
same DatabaseSync. Track authorizer depth on DatabaseSync with an RAII
guard around the callback and throw ERR_INVALID_STATE from the affected
entry points while it is on the stack. Covering every authorizer
invocation, including the re-prepare that SQLite can run during
sqlite3_step(), exposed a second and distinct hazard: reentering a
statement that is currently being stepped is a use-after-free rather
than a contract violation, since finalizing it frees the virtual machine
under sqlite3_step() and re-running it resets that machine
mid-execution. Any callback SQLite invokes during execution can reach
it, so a user-defined function is enough. Track the statements currently
being stepped and reject reentry into only those, which leaves a
user-defined function free to prepare, run, and finalize its own helper
statements.

Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
Fixes: https://github.com/nodejs/node/issues/63207
Assisted-by: claude:opus-5
PR-URL: https://github.com/nodejs/node/pull/65156
Reviewed-By: Xuguang Mei <meixuguang@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-14 21:43:35 +00:00
Edy Silva f46e96fe54 sqlite: expose prepared statement statistics
Signed-off-by: geeksilva97 <edigleyssonsilva@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64541
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
2026-08-13 13:59:56 +00:00
Guilherme Araújo b0edc373a6 sqlite: manage sqlite3_stmt lifetime with RAII
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/62419
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-11 23:47:37 +00:00
semimikoh 673cdef7d9 sqlite: check sqlite3_step() and sqlite3_reset() results
Signed-off-by: semimikoh <ejffjeosms@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/63319
Fixes: https://github.com/nodejs/node/issues/63311
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-11 15:30:37 +00:00
Trevor Burnham cb9bb66780 sqlite: reject statement-less SQL in prepare()
Apply the same check to DatabaseSync::Prepare() so that statement-less
SQL is rejected at preparation instead of on first use. This matches
SQLite's own oo1 JavaScript API, which throws when the SQL contains no
statements rather than exposing the C API's null statement pointer.

Previously db.prepare('-- comment') returned a StatementSync whose
statement_ was null. Every method on it threw "statement has been
finalized", which was misleading because nothing had been finalized, and
the object was still inserted into statements_. Since IsFinalized() is
true for a null statement, its destructor skipped UntrackStatement() and
left a dangling pointer in the set that a later close() would finalize.

Refs: https://github.com/nodejs/node/pull/65157#discussion_r3742903347
Refs: https://sqlite.org/wasm/doc/trunk/api-oo1.md

Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65157
Fixes: https://github.com/nodejs/node/issues/65149
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-11 03:14:51 +00:00
Trevor Burnham 71b3676332 sqlite: reject statement-less SQL in SQLTagStore
sqlite3_prepare_v2() returns SQLITE_OK without producing a statement
when its input holds no SQL, such as a comment. PrepareStatement() only
checked the return code, so it cached a StatementSync wrapping a null
sqlite3_stmt. Executing it reached sqlite3_clear_bindings(), which only
guards against a null statement under SQLITE_ENABLE_API_ARMOR, and
segfaulted.

Reject such input instead of caching it. The StatementSync methods
already avoid the crash because their IsFinalized() guard treats a null
statement as finalized.

Fixes: https://github.com/nodejs/node/issues/65149

Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65157
Fixes: https://github.com/nodejs/node/issues/65149
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-11 03:14:50 +00:00
Kamat, Trivikram ab95309457 sqlite: isolate applyChangeset filter errors
Track filter callback failures within each applyChangeset() invocation.
Returning false from xFilter is not a SQLite error. This previously left
the database-wide suppression flag set, which could hide the next
unrelated SQLite error.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: https://github.com/nodejs/node/pull/64823
Fixes: https://github.com/nodejs/node/issues/64822
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
2026-08-09 23:03:08 -07:00
Kamat, Trivikram b2b7405d26 sqlite: reject deserialize() while in a callback
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: https://github.com/nodejs/node/pull/64796
Refs: https://github.com/nodejs/node/issues/64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
2026-08-09 16:26:58 -07:00
Nora Dossche 330f7b5503 sqlite: check null returns from sqlite value functions
sqlite3_column_text() can return nullptr on failure which
was not handled.
sqlite3_column_blob() can return nullptr for zero-length BLOBs,
which is then passed to memcpy() which is UB.
Avoid this by checking for a nullptr.

Signed-off-by: ndossche <nora.dossche@ugent.be>
PR-URL: https://github.com/nodejs/node/pull/63288
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-09 07:44:02 +00:00
Anshika Jain 98fd6cb1c0 sqlite: validate maxSize argument in createTagStore()
Signed-off-by: anshikakalpana <anshikajain196872@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/63792
Fixes: https://github.com/nodejs/node/issues/63791
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-08-08 15:54:54 +00:00
Ali Hassan 347e266187 sqlite: refactor error helpers and user function pointers
Signed-off-by: Ali Hassan <ali-hassan27@outlook.com>
PR-URL: https://github.com/nodejs/node/pull/62794
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-08-08 13:54:01 +00:00
Trivikram Kamat f538f11faf sqlite: reject non-positive backup rates
Passing a rate of 0 to backup() causes sqlite3_backup_step() to copy
no pages. The backup job then continually reschedules itself and the
returned promise never settles.

Require backup rates to be positive integers to prevent zero-work
backup jobs.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: https://github.com/nodejs/node/pull/64893
Fixes: https://github.com/nodejs/node/issues/64892
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-08-08 07:29:36 +00:00
Matteo Collina 8fc73417fb sqlite: clear SQLTagStore bindings
SQLTagStore reused cached statements without clearing values bound by a
previous execution. Clear bindings before binding new values and reject
parameters that do not correspond to template substitutions.

Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/65041
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
2026-08-07 09:16:50 +00:00
mike-git374 de333e856d sqlite: bind Boolean
PR-URL: https://github.com/nodejs/node/pull/62001
Fixes: https://github.com/nodejs/node/issues/57862
Reviewed-By: Zeyu "Alex" Yang <himself65@outlook.com>
2026-08-05 22:50:13 -07:00
Nora Dossche c18fd9060b sqlite: fix undefined behaviour in Session::Changeset()
If `nChangeset == 0`, the pointer `pChangeset` may be nullptr.
Passing a nullptr to `memcpy()` is undefined behaviour.
This can be triggered by running the test suite under UBSAN.

Signed-off-by: ndossche <nora.dossche@ugent.be>
PR-URL: https://github.com/nodejs/node/pull/63637
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
2026-08-06 03:18:25 +00:00
Guilherme Araújo 51c0d94072 sqlite: add StatementSync.prototype.close()
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64232
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
2026-08-05 18:10:35 -07:00
Guilherme Araújo 04c104a725 sqlite: add StatementSync.prototype[Symbol.dispose]()
This extends explicit resource management support to prepared
statements, allowing a StatementSync to be deterministically
finalized via a `using` declaration, mirroring the existing
DatabaseSync and Session dispose methods.

Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64232
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
2026-08-05 18:10:31 -07:00
mike-git374 62acd4888c sqlite: bind ArrayBuffer
PR-URL: https://github.com/nodejs/node/pull/62061
Fixes: https://github.com/nodejs/node/issues/61396
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
2026-08-05 22:43:40 +02:00
Matteo CollinaandAsroy Cristian Sitorus 5cef7673ae sqlite: prevent database close during callbacks
Co-authored-by: Asroy Cristian Sitorus <asroycristiansitorus@gmail.com>
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/64743
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-08-04 11:05:47 +00:00
Trivikram Kamat f00fb75051 sqlite: invalidate sessions when closing database
DatabaseSync::DeleteSessions() deleted native sqlite3_session handles
without clearing the pointers held by their Session wrappers. Reopening
the database allowed stale session handles to be used, crashing the
process.

Track Session wrappers and delete sessions through Session::Delete() so
the wrappers are marked closed.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: https://github.com/nodejs/node/pull/64783
Fixes: https://github.com/nodejs/node/issues/64782
Reviewed-By: Aviv Keller <me@aviv.sh>
2026-08-03 19:34:01 +00:00
Trivikram Kamat a58aad785b sqlite: check database state before calling SQLite
Check whether the database is open in enableLoadExtension() and
setAuthorizer() before passing the connection to SQLite. This prevents
calls after close() from terminating the process and makes them throw
ERR_INVALID_STATE instead.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: https://github.com/nodejs/node/pull/64812
Fixes: https://github.com/nodejs/node/issues/64811
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
2026-07-31 04:11:16 +00:00
Matteo Collina d0387cf67c sqlite: invalidate tag store iterators on statement reset
SQLTagStore Run/Iterate/Get/All reset the shared cached statement via
raw sqlite3_reset(), bypassing ResetStatement() and its reset_generation_
bump, so live iterators were never invalidated. Also mark the iterator
done on SQLITE_DONE so it cannot restart the statement.

PR-URL: https://github.com/nodejs-private/node-private/pull/896
Refs: https://hackerone.com/reports/3564941
Reviewed-By: Robert Nagy <ronagy@icloud.com>
CVE-ID: CVE-2026-58041
2026-07-29 10:41:25 -03:00
Mohamed Sayed d4d35c6363 sqlite: fix crash when a session outlives its database
Keep the database alive while a session is open so that closing the
database before the session does not leave the session pointing at
freed memory.

Signed-off-by: Mohamed Sayed <k@3zrv.com>
PR-URL: https://github.com/nodejs/node/pull/63797
Fixes: https://github.com/nodejs/node/issues/63796
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
2026-07-26 01:43:30 +00:00
Matteo Collina fd41198dff sqlite: fix use-after-free in Exec() and ApplyChangeset()
When sqlite3_exec() or sqlite3changeset_apply() call JavaScript
callbacks (user-defined functions, conflict handlers, or filter
callbacks), the DatabaseSync object could be garbage-collected
if the JavaScript code drops all references to it. Both methods
only held a raw DatabaseSync* pointer on the C++ stack, which
V8 GC does not track.

Add a BaseObjectPtr<DatabaseSync> guard that keeps the database
alive for the duration of these SQLite API calls, preventing a
use-after-free when the JavaScript callback triggers GC.

Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/64535
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
2026-07-23 14:37:21 +00:00
Guilherme Araújo 47e332d227 sqlite: read column count after step in StatementSync.all()
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64219
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-07-07 12:40:43 +00:00
Yagiz Nizipli cf89b61932 sqlite: do not leave database open after failed open
When sqlite3_open_v2() fails, SQLite still assigns a database handle
in a "sick" state. Such a handle may only be used to retrieve the
error and must then be released with sqlite3_close(). Keeping it in
connection_ meant that after a failed open():

- isOpen incorrectly reported true,
- every method passed the "database is not open" check and called
  SQLite APIs on the sick handle, which is an API misuse,
- registering a user-defined function leaked its user data in builds
  with SQLITE_ENABLE_API_ARMOR, because SQLite rejects the call
  before taking ownership of the user data,
- open() could not be retried since the database appeared open.

Close and reset the connection handle when open() fails at any point
so that the database remains closed and open() can be retried.

Fixes: https://github.com/nodejs/node/issues/63831
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
PR-URL: https://github.com/nodejs/node/pull/63854
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
2026-06-16 16:29:30 +00:00