Skip to content

Commit cc48aad

Browse files
arnaudbosArnaud Bos
andauthored
fix(transactional-adapter-kysely) isolation level not percolating through to transaction execution (#244)
* test(kysely): add test showing isolationLevel not percolating * fix(kysely): fix isolationLevel option not percolating through transaction execution --------- Co-authored-by: Arnaud Bos <[email protected]>
1 parent 9b304c4 commit cc48aad

File tree

2 files changed

+72
-5
lines changed

2 files changed

+72
-5
lines changed

packages/transactional-adapters/transactional-adapter-kysely/src/lib/transactional-adapter-kysely.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ export class TransactionalAdapterKysely<DB = any>
3939
fn: (...args: any[]) => Promise<any>,
4040
setClient: (client?: Kysely<DB>) => void,
4141
) => {
42-
const transaction = kyselyDb.transaction();
42+
let transaction = kyselyDb.transaction();
4343
if (options?.isolationLevel) {
44-
transaction.setIsolationLevel(options.isolationLevel);
44+
transaction = transaction.setIsolationLevel(options.isolationLevel);
4545
}
4646
return transaction.execute(async (trx) => {
4747
setClient(trx);

packages/transactional-adapters/transactional-adapter-kysely/test/transactional-adapter-kysely.spec.ts

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
ClsPluginTransactional,
33
InjectTransaction,
4+
Propagation,
45
Transaction,
56
Transactional,
67
TransactionHost,
@@ -123,6 +124,7 @@ const kyselyDb = new Kysely<Database>({
123124
max: 2,
124125
}),
125126
}),
127+
log: ['query', 'error'],
126128
});
127129

128130
@Module({
@@ -134,15 +136,15 @@ const kyselyDb = new Kysely<Database>({
134136
],
135137
exports: [KYSELY],
136138
})
137-
class KnexModule {}
139+
class KyselyModule {}
138140

139141
@Module({
140142
imports: [
141-
KnexModule,
143+
KyselyModule,
142144
ClsModule.forRoot({
143145
plugins: [
144146
new ClsPluginTransactional({
145-
imports: [KnexModule],
147+
imports: [KyselyModule],
146148
adapter: new TransactionalAdapterKysely({
147149
kyselyInstanceToken: KYSELY,
148150
}),
@@ -248,6 +250,71 @@ describe('Transactional', () => {
248250
expect.not.arrayContaining([{ name: 'Nobody' }]),
249251
);
250252
});
253+
254+
describe('Isolation Level', () => {
255+
let userA;
256+
let userB;
257+
let txHost: TransactionHost<TransactionalAdapterKysely<Database>>;
258+
259+
beforeEach(async () => {
260+
userA = await kyselyDb
261+
.insertInto('user')
262+
.values({
263+
name: 'User~A',
264+
265+
})
266+
.returningAll()
267+
.executeTakeFirstOrThrow();
268+
269+
userB = await kyselyDb
270+
.insertInto('user')
271+
.values({
272+
name: 'User~B',
273+
274+
})
275+
.returningAll()
276+
.executeTakeFirstOrThrow();
277+
txHost = module.get(TransactionHost);
278+
});
279+
280+
it('should abort a transaction on serialization anomaly', async () => {
281+
await expect(
282+
txHost.withTransaction(
283+
{ isolationLevel: 'serializable' },
284+
async () => {
285+
const { name: userAname } = await txHost.tx
286+
.selectFrom('user')
287+
.where('id', '=', userA.id)
288+
.select('name')
289+
.executeTakeFirstOrThrow();
290+
291+
await txHost.withTransaction(
292+
Propagation.RequiresNew,
293+
{ isolationLevel: 'serializable' },
294+
async () => {
295+
const { name: userBname } = await txHost.tx
296+
.selectFrom('user')
297+
.where('id', '=', userB.id)
298+
.select('name')
299+
.executeTakeFirstOrThrow();
300+
301+
await txHost.tx
302+
.updateTable('user')
303+
.set('name', userBname)
304+
.where('id', '=', userA.id)
305+
.execute();
306+
},
307+
);
308+
309+
await txHost.tx
310+
.updateTable('user')
311+
.set('name', userAname)
312+
.where('id', '=', userB.id)
313+
.execute();
314+
},
315+
)).rejects.toThrow(new Error('could not serialize access due to read/write dependencies among transactions'));
316+
});
317+
});
251318
});
252319
});
253320

0 commit comments

Comments
 (0)