Skip to content

Commit 3f967fc

Browse files
committed
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>
1 parent 75aec8e commit 3f967fc

4 files changed

Lines changed: 70 additions & 4 deletions

File tree

doc/api/sqlite.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,15 @@ added: v22.5.0
12021202
The source SQL text of the prepared statement. This property is a
12031203
wrapper around [`sqlite3_sql()`][].
12041204

1205+
### `statement[Symbol.dispose]()`
1206+
1207+
<!-- YAML
1208+
added: REPLACEME
1209+
-->
1210+
1211+
Finalizes the prepared statement. If the prepared statement is already
1212+
finalized, then this is a no-op.
1213+
12051214
## Class: `SQLTagStore`
12061215

12071216
<!-- YAML

src/node_sqlite.cc

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,10 +1037,7 @@ void DatabaseSync::FinalizeStatements() {
10371037
}
10381038

10391039
void DatabaseSync::UntrackStatement(StatementSync* statement) {
1040-
auto it = statements_.find(statement);
1041-
if (it != statements_.end()) {
1042-
statements_.erase(it);
1043-
}
1040+
statements_.erase(statement);
10441041
}
10451042

10461043
inline bool DatabaseSync::IsOpen() {
@@ -2578,6 +2575,10 @@ StatementSync::StatementSync(Environment* env,
25782575
}
25792576

25802577
StatementSync::~StatementSync() {
2578+
Close();
2579+
}
2580+
2581+
void StatementSync::Close() {
25812582
if (!IsFinalized()) {
25822583
db_->UntrackStatement(this);
25832584
Finalize();
@@ -2598,6 +2599,16 @@ inline bool StatementSync::IsFinalized() {
25982599
return statement_ == nullptr;
25992600
}
26002601

2602+
void StatementSync::Dispose(const FunctionCallbackInfo<Value>& args) {
2603+
StatementSync* stmt;
2604+
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
2605+
v8::TryCatch try_catch(args.GetIsolate());
2606+
stmt->Close();
2607+
if (try_catch.HasCaught()) {
2608+
CHECK(try_catch.CanContinue());
2609+
}
2610+
}
2611+
26012612
inline int StatementSync::ResetStatement() {
26022613
reset_generation_++;
26032614
return sqlite3_reset(statement_);
@@ -3625,6 +3636,7 @@ Local<FunctionTemplate> StatementSync::GetConstructorTemplate(
36253636
isolate, tmpl, "setReadBigInts", StatementSync::SetReadBigInts);
36263637
SetProtoMethod(
36273638
isolate, tmpl, "setReturnArrays", StatementSync::SetReturnArrays);
3639+
SetProtoDispose(isolate, tmpl, StatementSync::Dispose);
36283640
env->set_sqlite_statement_sync_constructor_template(tmpl);
36293641
}
36303642
return tmpl;

src/node_sqlite.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ class StatementSync : public BaseObject {
278278
const v8::FunctionCallbackInfo<v8::Value>& args);
279279
static void SetReadBigInts(const v8::FunctionCallbackInfo<v8::Value>& args);
280280
static void SetReturnArrays(const v8::FunctionCallbackInfo<v8::Value>& args);
281+
static void Dispose(const v8::FunctionCallbackInfo<v8::Value>& args);
281282
v8::MaybeLocal<v8::Value> ColumnToValue(const int column);
282283
v8::MaybeLocal<v8::Name> ColumnNameToName(const int column);
283284
bool GetCachedColumnNames(v8::LocalVector<v8::Name>* keys);
@@ -289,6 +290,7 @@ class StatementSync : public BaseObject {
289290

290291
private:
291292
~StatementSync() override;
293+
void Close();
292294
BaseObjectPtr<DatabaseSync> db_;
293295
sqlite3_stmt* statement_;
294296
bool return_arrays_ = false;

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,3 +909,46 @@ suite('options.allowBareNamedParameters', () => {
909909
);
910910
});
911911
});
912+
913+
914+
suite('StatementSync.prototype[Symbol.dispose]()', () => {
915+
test('finalizes an open statement', (t) => {
916+
using db = new DatabaseSync(':memory:');
917+
db.exec('CREATE TABLE storage(key TEXT, val TEXT)');
918+
const stmt = db.prepare('SELECT * FROM storage');
919+
stmt[Symbol.dispose]();
920+
t.assert.throws(() => stmt.get(), {
921+
code: 'ERR_INVALID_STATE',
922+
message: /statement has been finalized/,
923+
});
924+
});
925+
926+
test('does not throw on an already-finalized statement', () => {
927+
using db = new DatabaseSync(':memory:');
928+
const stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)');
929+
stmt[Symbol.dispose]();
930+
stmt[Symbol.dispose]();
931+
});
932+
933+
test('works with a using declaration', (t) => {
934+
using db = new DatabaseSync(':memory:');
935+
db.exec('CREATE TABLE storage(key TEXT, val TEXT)');
936+
let captured;
937+
{
938+
using stmt = db.prepare('SELECT * FROM storage');
939+
captured = stmt;
940+
t.assert.deepStrictEqual(stmt.all(), []);
941+
}
942+
t.assert.throws(() => captured.get(), {
943+
code: 'ERR_INVALID_STATE',
944+
message: /statement has been finalized/,
945+
});
946+
});
947+
948+
test('closing the database after dispose does not double-finalize', () => {
949+
using db = new DatabaseSync(':memory:');
950+
const stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)');
951+
stmt[Symbol.dispose]();
952+
db.close();
953+
});
954+
});

0 commit comments

Comments
 (0)