feat(transactions): add single-node transaction support with session-aware reads, commits, aborts, and transaction metrics

This commit is contained in:
2026-04-29 22:14:46 +00:00
parent e79fe339aa
commit b72e8ed5e7
19 changed files with 913 additions and 77 deletions
+60 -15
View File
@@ -44,7 +44,7 @@ tap.test('transactions: should still support explicit sessions', async () => {
expect(end.ok).toEqual(1);
});
tap.test('transactions: should reject raw transaction-scoped writes before mutation', async () => {
tap.test('transactions: should reject transaction-scoped writes without txnNumber before mutation', async () => {
const db = client.db('txntest');
const coll = db.collection('docs');
await coll.insertOne({ key: 'outside', value: 1 });
@@ -59,8 +59,8 @@ tap.test('transactions: should reject raw transaction-scoped writes before mutat
});
} catch (err: any) {
threw = true;
expect(err.code).toEqual(20);
expect(err.codeName).toEqual('IllegalOperation');
expect(err.code).toEqual(14);
expect(err.codeName).toEqual('TypeMismatch');
}
expect(threw).toBeTrue();
@@ -68,44 +68,89 @@ tap.test('transactions: should reject raw transaction-scoped writes before mutat
expect(await coll.countDocuments({ key: 'outside' })).toEqual(1);
});
tap.test('transactions: official driver transaction should fail without committing writes', async () => {
tap.test('transactions: official driver transaction should commit buffered writes', async () => {
const coll = client.db('txntest').collection('driverdocs');
await coll.insertOne({ key: 'outside-driver', value: 0 });
const session = client.startSession();
let threw = false;
try {
session.startTransaction();
await coll.insertOne({ key: 'inside-driver', value: 1 }, { session });
const inTxn = await coll.findOne({ key: 'inside-driver' }, { session });
expect(inTxn).toBeTruthy();
expect(await coll.countDocuments({ key: 'inside-driver' })).toEqual(0);
await session.commitTransaction();
} catch (err: any) {
threw = true;
expect(err.code).toEqual(20);
expect(err.codeName).toEqual('IllegalOperation');
await session.abortTransaction().catch(() => undefined);
} finally {
await session.endSession();
}
expect(threw).toBeTrue();
expect(await coll.countDocuments({ key: 'inside-driver' })).toEqual(0);
expect(await coll.countDocuments({ key: 'inside-driver' })).toEqual(1);
expect(await coll.countDocuments({ key: 'outside-driver' })).toEqual(1);
});
tap.test('transactions: commit and abort commands should be explicit unsupported errors', async () => {
tap.test('transactions: abort should discard buffered writes', async () => {
const coll = client.db('txntest').collection('abortdocs');
const session = client.startSession();
try {
session.startTransaction();
await coll.insertOne({ key: 'abort-me', value: 1 }, { session });
expect(await coll.findOne({ key: 'abort-me' }, { session })).toBeTruthy();
await session.abortTransaction();
} finally {
await session.endSession();
}
expect(await coll.findOne({ key: 'abort-me' })).toBeNull();
});
tap.test('transactions: update and delete should commit atomically', async () => {
const coll = client.db('txntest').collection('mutations');
await coll.insertMany([
{ key: 'update-me', value: 1 },
{ key: 'delete-me', value: 2 },
]);
const session = client.startSession();
try {
session.startTransaction();
await coll.updateOne({ key: 'update-me' }, { $set: { value: 10 } }, { session });
await coll.deleteOne({ key: 'delete-me' }, { session });
expect((await coll.findOne({ key: 'update-me' }, { session }))!.value).toEqual(10);
expect(await coll.findOne({ key: 'delete-me' }, { session })).toBeNull();
expect((await coll.findOne({ key: 'update-me' }))!.value).toEqual(1);
expect(await coll.findOne({ key: 'delete-me' })).toBeTruthy();
await session.commitTransaction();
} finally {
await session.endSession();
}
expect((await coll.findOne({ key: 'update-me' }))!.value).toEqual(10);
expect(await coll.findOne({ key: 'delete-me' })).toBeNull();
});
tap.test('transactions: commit and abort without active transaction should be explicit errors', async () => {
for (const command of [{ commitTransaction: 1 }, { abortTransaction: 1 }]) {
let threw = false;
try {
await client.db('admin').command(command);
} catch (err: any) {
threw = true;
expect(err.code).toEqual(20);
expect(err.codeName).toEqual('IllegalOperation');
expect(err.code).toEqual(251);
expect(err.codeName).toEqual('NoSuchTransaction');
}
expect(threw).toBeTrue();
}
});
tap.test('transactions: serverStatus should expose transaction and oplog metrics', async () => {
const status = await client.db('admin').command({ serverStatus: 1 });
expect(status.ok).toEqual(1);
expect(status.transactions.currentActive).toEqual(0);
expect(status.logicalSessionRecordCache.activeSessionsCount).toBeGreaterThanOrEqual(0);
expect(status.oplog.totalEntries).toBeGreaterThan(0);
});
tap.test('transactions: cleanup', async () => {
await client.close();
await server.stop();