Skip to content

Commit 9c6fb01

Browse files
zerone0xclaude
andcommitted
sqlite: report zero changes for read-only statements
Read-only statements (e.g. SELECT) should always report 0 for both changes and lastInsertRowid. Previously, running a SELECT after an INSERT would leak the change count from the INSERT because sqlite3_changes64() returns the count from the most recent write statement on the connection. Use sqlite3_stmt_readonly() to detect read-only statements and return 0 instead of querying the connection-level counters. Fixes: #59764 Co-Authored-By: Claude <noreply@anthropic.com>
1 parent a06e789 commit 9c6fb01

2 files changed

Lines changed: 40 additions & 2 deletions

File tree

src/node_sqlite.cc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2698,8 +2698,13 @@ MaybeLocal<Object> StatementExecutionHelper::Run(Environment* env,
26982698
int r = sqlite3_reset(stmt);
26992699
CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_OK, MaybeLocal<Object>());
27002700

2701-
sqlite3_int64 last_insert_rowid = sqlite3_last_insert_rowid(db->Connection());
2702-
sqlite3_int64 changes = sqlite3_changes64(db->Connection());
2701+
// Read-only statements (e.g. SELECT) never modify rows, so report 0
2702+
// instead of leaking the count from the most recent write statement.
2703+
bool is_readonly = sqlite3_stmt_readonly(stmt);
2704+
sqlite3_int64 last_insert_rowid =
2705+
is_readonly ? 0 : sqlite3_last_insert_rowid(db->Connection());
2706+
sqlite3_int64 changes =
2707+
is_readonly ? 0 : sqlite3_changes64(db->Connection());
27032708
Local<Value> last_insert_rowid_val;
27042709
Local<Value> changes_val;
27052710

test/parallel/test-sqlite-statement-sync.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,3 +909,36 @@ suite('options.allowBareNamedParameters', () => {
909909
);
910910
});
911911
});
912+
913+
suite('read-only statements report zero changes', () => {
914+
test('SELECT after INSERT reports zero changes', (t) => {
915+
const db = new DatabaseSync(':memory:');
916+
t.after(() => { db.close(); });
917+
db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)');
918+
const insert = db.prepare('INSERT INTO test (name) VALUES (?)');
919+
t.assert.deepStrictEqual(
920+
insert.run('foo'),
921+
{ changes: 1, lastInsertRowid: 1 },
922+
);
923+
const select = db.prepare('SELECT * FROM test');
924+
t.assert.deepStrictEqual(
925+
select.run(),
926+
{ changes: 0, lastInsertRowid: 0 },
927+
);
928+
});
929+
930+
test('SELECT after multiple INSERTs reports zero changes', (t) => {
931+
const db = new DatabaseSync(':memory:');
932+
t.after(() => { db.close(); });
933+
db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)');
934+
const insert = db.prepare('INSERT INTO test (name) VALUES (?)');
935+
insert.run('a');
936+
insert.run('b');
937+
insert.run('c');
938+
const select = db.prepare('SELECT * FROM test');
939+
t.assert.deepStrictEqual(
940+
select.run(),
941+
{ changes: 0, lastInsertRowid: 0 },
942+
);
943+
});
944+
});

0 commit comments

Comments
 (0)