diff --git a/doc/api/sqlite.md b/doc/api/sqlite.md index 95d1143a242..7de0f2d88ce 100644 --- a/doc/api/sqlite.md +++ b/doc/api/sqlite.md @@ -1013,7 +1013,8 @@ wrapper around [`sqlite3session_patchset()`][]. ### `session.close()` -Closes the session. An exception is thrown if the database or the session is not open. This method is a +Closes the session. An exception is thrown if the database or the session is not open, +or if the session is currently generating a changeset or patchset. This method is a wrapper around [`sqlite3session_delete()`][]. ### `session[Symbol.dispose]()` diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index d0da887062b..cc8d52dcf61 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -4326,6 +4326,10 @@ void Session::Changeset(const FunctionCallbackInfo& args) { env, session->session_ == nullptr, "session is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get()); + session->is_generating_changeset_ = true; + auto changeset_guard = + OnScopeLeave([&] { session->is_generating_changeset_ = false; }); + int nChangeset; void* pChangeset; int r = sqliteChangesetFunc(session->session_, &nChangeset, &pChangeset); @@ -4351,6 +4355,8 @@ void Session::Close(const FunctionCallbackInfo& args) { env, !session->database_->IsOpen(), "database is not open"); THROW_AND_RETURN_ON_BAD_STATE( env, session->session_ == nullptr, "session is not open"); + THROW_AND_RETURN_ON_BAD_STATE( + env, session->is_generating_changeset_, "session is currently in use"); session->Delete(); } diff --git a/src/node_sqlite.h b/src/node_sqlite.h index 475a759e75e..557d907fd73 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -466,6 +466,7 @@ class Session : public BaseObject { void Delete(); sqlite3_session* session_; BaseObjectPtr database_; // The Parent Database + bool is_generating_changeset_ = false; friend class DatabaseSync; }; diff --git a/test/parallel/test-sqlite-session.js b/test/parallel/test-sqlite-session.js index 2a72740bf89..a8bbaa77d06 100644 --- a/test/parallel/test-sqlite-session.js +++ b/test/parallel/test-sqlite-session.js @@ -612,6 +612,28 @@ test('session.close() - closing twice', (t) => { }); }); +test('session.close() - while generating changes throws exception', (t) => { + for (const method of ['changeset', 'patchset']) { + const database = new DatabaseSync(':memory:'); + database.exec('CREATE TABLE data(key INTEGER PRIMARY KEY, value TEXT)'); + + const session = database.createSession({ table: 'data' }); + database.exec("INSERT INTO data VALUES (1, 'a'), (2, 'b'), (3, 'c')"); + database.setAuthorizer(() => { + session.close(); + return constants.SQLITE_OK; + }); + + t.assert.throws(() => session[method](), { + code: 'ERR_INVALID_STATE', + message: 'session is currently in use', + }); + + database.setAuthorizer(null); + t.assert.notStrictEqual(session[method]().length, 0); + } +}); + test('session - keeps its database alive after the db handle is dropped', async (t) => { const { gcUntil, onGC } = require('../common/gc');