sqlite: add getter to detect transactions

This commit adds an isTransaction getter to the DatabaseSync
class for determining if the database is currently within a
transaction.

Fixes: https://github.com/nodejs/node/issues/57922
PR-URL: https://github.com/nodejs/node/pull/57925
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Zeyu "Alex" Yang <himself65@outlook.com>
This commit is contained in:
Colin Ihrig 2025-04-20 11:57:42 -04:00 committed by GitHub
parent 7102ea1559
commit 2e0ec72f54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 62 additions and 0 deletions

View File

@ -291,6 +291,15 @@ added: v23.11.0
* {boolean} Whether the database is currently open or not.
### `database.isTransaction`
<!-- YAML
added: REPLACEME
-->
* {boolean} Whether the database is currently within a transaction. This method
is a wrapper around [`sqlite3_get_autocommit()`][].
### `database.open()`
<!-- YAML
@ -839,6 +848,7 @@ resolution handler passed to [`database.applyChangeset()`][]. See also
[`sqlite3_create_window_function()`]: https://www.sqlite.org/c3ref/create_function.html
[`sqlite3_exec()`]: https://www.sqlite.org/c3ref/exec.html
[`sqlite3_expanded_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html
[`sqlite3_get_autocommit()`]: https://sqlite.org/c3ref/get_autocommit.html
[`sqlite3_last_insert_rowid()`]: https://www.sqlite.org/c3ref/last_insert_rowid.html
[`sqlite3_load_extension()`]: https://www.sqlite.org/c3ref/load_extension.html
[`sqlite3_prepare_v2()`]: https://www.sqlite.org/c3ref/prepare.html

View File

@ -979,6 +979,15 @@ void DatabaseSync::IsOpenGetter(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(db->IsOpen());
}
void DatabaseSync::IsTransactionGetter(
const FunctionCallbackInfo<Value>& args) {
DatabaseSync* db;
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
Environment* env = Environment::GetCurrent(args);
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
args.GetReturnValue().Set(sqlite3_get_autocommit(db->connection_) == 0);
}
void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
DatabaseSync* db;
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
@ -2623,6 +2632,10 @@ static void Initialize(Local<Object> target,
db_tmpl,
FIXED_ONE_BYTE_STRING(isolate, "isOpen"),
DatabaseSync::IsOpenGetter);
SetSideEffectFreeGetter(isolate,
db_tmpl,
FIXED_ONE_BYTE_STRING(isolate, "isTransaction"),
DatabaseSync::IsTransactionGetter);
SetConstructorFunction(context, target, "DatabaseSync", db_tmpl);
SetConstructorFunction(context,
target,

View File

@ -61,6 +61,8 @@ class DatabaseSync : public BaseObject {
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Open(const v8::FunctionCallbackInfo<v8::Value>& args);
static void IsOpenGetter(const v8::FunctionCallbackInfo<v8::Value>& args);
static void IsTransactionGetter(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Prepare(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Exec(const v8::FunctionCallbackInfo<v8::Value>& args);

View File

@ -324,3 +324,40 @@ suite('DatabaseSync.prototype.exec()', () => {
});
});
});
suite('DatabaseSync.prototype.isTransaction', () => {
test('correctly detects a committed transaction', (t) => {
const db = new DatabaseSync(':memory:');
t.assert.strictEqual(db.isTransaction, false);
db.exec('BEGIN');
t.assert.strictEqual(db.isTransaction, true);
db.exec('CREATE TABLE foo (id INTEGER PRIMARY KEY)');
t.assert.strictEqual(db.isTransaction, true);
db.exec('COMMIT');
t.assert.strictEqual(db.isTransaction, false);
});
test('correctly detects a rolled back transaction', (t) => {
const db = new DatabaseSync(':memory:');
t.assert.strictEqual(db.isTransaction, false);
db.exec('BEGIN');
t.assert.strictEqual(db.isTransaction, true);
db.exec('CREATE TABLE foo (id INTEGER PRIMARY KEY)');
t.assert.strictEqual(db.isTransaction, true);
db.exec('ROLLBACK');
t.assert.strictEqual(db.isTransaction, false);
});
test('throws if database is not open', (t) => {
const db = new DatabaseSync(nextDb(), { open: false });
t.assert.throws(() => {
return db.isTransaction;
}, {
code: 'ERR_INVALID_STATE',
message: /database is not open/,
});
});
});