sqlite: cleanup ERM support and export Session class

Update sqlite Session to support Symbol.dispose and
move the definition of the dispose methods to c++ to
close the open TODO

PR-URL: https://github.com/nodejs/node/pull/58378
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
James M Snell 2025-05-18 13:52:12 -07:00
parent dd205c9831
commit 62ba6196d4
6 changed files with 74 additions and 14 deletions

View File

@ -1,19 +1,6 @@
'use strict';
const {
SymbolDispose,
} = primordials;
const { emitExperimentalWarning } = require('internal/util');
const binding = internalBinding('sqlite');
emitExperimentalWarning('SQLite');
// TODO(cjihrig): Move this to C++ once Symbol.dispose reaches Stage 4.
binding.DatabaseSync.prototype[SymbolDispose] = function() {
try {
this.close();
} catch {
// Ignore errors.
}
};
module.exports = binding;
module.exports = internalBinding('sqlite');

View File

@ -1005,6 +1005,14 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
db->connection_ = nullptr;
}
void DatabaseSync::Dispose(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::TryCatch try_catch(args.GetIsolate());
Close(args);
if (try_catch.HasCaught()) {
CHECK(try_catch.CanContinue());
}
}
void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
DatabaseSync* db;
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
@ -2577,6 +2585,7 @@ Local<FunctionTemplate> Session::GetConstructorTemplate(Environment* env) {
SetProtoMethod(
isolate, tmpl, "patchset", Session::Changeset<sqlite3session_patchset>);
SetProtoMethod(isolate, tmpl, "close", Session::Close);
SetProtoDispose(isolate, tmpl, Session::Dispose);
env->set_sqlite_session_constructor_template(tmpl);
}
return tmpl;
@ -2621,6 +2630,14 @@ void Session::Close(const FunctionCallbackInfo<Value>& args) {
session->Delete();
}
void Session::Dispose(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::TryCatch try_catch(args.GetIsolate());
Close(args);
if (try_catch.HasCaught()) {
CHECK(try_catch.CanContinue());
}
}
void Session::Delete() {
if (!database_ || !database_->connection_ || session_ == nullptr) return;
sqlite3session_delete(session_);
@ -2656,6 +2673,7 @@ static void Initialize(Local<Object> target,
SetProtoMethod(isolate, db_tmpl, "open", DatabaseSync::Open);
SetProtoMethod(isolate, db_tmpl, "close", DatabaseSync::Close);
SetProtoDispose(isolate, db_tmpl, DatabaseSync::Dispose);
SetProtoMethod(isolate, db_tmpl, "prepare", DatabaseSync::Prepare);
SetProtoMethod(isolate, db_tmpl, "exec", DatabaseSync::Exec);
SetProtoMethod(isolate, db_tmpl, "function", DatabaseSync::CustomFunction);
@ -2686,6 +2704,8 @@ static void Initialize(Local<Object> target,
target,
"StatementSync",
StatementSync::GetConstructorTemplate(env));
SetConstructorFunction(
context, target, "Session", Session::GetConstructorTemplate(env));
target->Set(context, env->constants_string(), constants).Check();

View File

@ -64,6 +64,7 @@ class DatabaseSync : public BaseObject {
static void IsTransactionGetter(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Dispose(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Prepare(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Exec(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Location(const v8::FunctionCallbackInfo<v8::Value>& args);
@ -194,6 +195,7 @@ class Session : public BaseObject {
template <Sqlite3ChangesetGenFunc sqliteChangesetFunc>
static void Changeset(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Dispose(const v8::FunctionCallbackInfo<v8::Value>& args);
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static BaseObjectPtr<Session> Create(Environment* env,

View File

@ -598,6 +598,32 @@ void SetMethodNoSideEffect(Isolate* isolate,
that->Set(name_string, t);
}
void SetProtoDispose(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> that,
v8::FunctionCallback callback) {
Local<v8::Signature> signature = v8::Signature::New(isolate, that);
Local<v8::FunctionTemplate> t =
NewFunctionTemplate(isolate,
callback,
signature,
v8::ConstructorBehavior::kThrow,
v8::SideEffectType::kHasSideEffect);
that->PrototypeTemplate()->Set(v8::Symbol::GetDispose(isolate), t);
}
void SetProtoAsyncDispose(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> that,
v8::FunctionCallback callback) {
Local<v8::Signature> signature = v8::Signature::New(isolate, that);
Local<v8::FunctionTemplate> t =
NewFunctionTemplate(isolate,
callback,
signature,
v8::ConstructorBehavior::kThrow,
v8::SideEffectType::kHasSideEffect);
that->PrototypeTemplate()->Set(v8::Symbol::GetAsyncDispose(isolate), t);
}
void SetProtoMethod(v8::Isolate* isolate,
Local<v8::FunctionTemplate> that,
const std::string_view name,

View File

@ -926,6 +926,16 @@ void SetMethodNoSideEffect(v8::Isolate* isolate,
const std::string_view name,
v8::FunctionCallback callback);
// Set the Symbol.dispose method on the prototype of the class.
void SetProtoDispose(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> that,
v8::FunctionCallback callback);
// Set the Symbol.asyncDispose method on the prototype of the class.
void SetProtoAsyncDispose(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> that,
v8::FunctionCallback callback);
enum class SetConstructorFunctionFlag {
NONE,
SET_CLASS_NAME,

View File

@ -539,3 +539,18 @@ test('session.close() - closing twice', (t) => {
message: 'session is not open'
});
});
test('session supports ERM', (t) => {
const database = new DatabaseSync(':memory:');
let afterDisposeSession;
{
using session = database.createSession();
afterDisposeSession = session;
const changeset = session.changeset();
t.assert.ok(changeset instanceof Uint8Array);
t.assert.strictEqual(changeset.length, 0);
}
t.assert.throws(() => afterDisposeSession.changeset(), {
message: /session is not open/,
});
});