Skip to content

Commit 18a4c93

Browse files
committed
Ensured that default transaction options are passed to transactions
1 parent 45b5b72 commit 18a4c93

3 files changed

Lines changed: 100 additions & 3 deletions

File tree

src/packages/dumbo/src/storage/sqlite/core/pool/dualPool.int.spec.ts

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import assert from 'node:assert';
22
import * as fs from 'node:fs';
33
import { afterAll, beforeAll, describe, it } from 'vitest';
4-
import { SQL } from '../../../../core';
4+
import { InvalidOperationError, SQL } from '../../../../core';
55
import { sqlite3Pool } from '../../sqlite3';
66

77
const withDeadline = { timeout: 30000 };
@@ -316,4 +316,87 @@ describe('SQLite Dual Connection Pool', () => {
316316
assert.equal(results.filter((r) => r instanceof Error).length, 0);
317317
},
318318
);
319+
320+
it(
321+
'respects allowNestedTransactions from connectionOptions in dual pool writer',
322+
withDeadline,
323+
async () => {
324+
const nestedTxFileName = 'nested-tx-test.db';
325+
326+
const pool = sqlite3Pool({
327+
fileName: nestedTxFileName,
328+
transactionOptions: { allowNestedTransactions: true },
329+
});
330+
331+
try {
332+
await pool.execute.command(
333+
SQL`CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)`,
334+
);
335+
336+
await pool.withTransaction(async (outerTx) => {
337+
await outerTx.execute.command(
338+
SQL`INSERT INTO test (value) VALUES ('outer')`,
339+
);
340+
341+
await pool.withTransaction(async (innerTx) => {
342+
await innerTx.execute.command(
343+
SQL`INSERT INTO test (value) VALUES ('inner')`,
344+
);
345+
});
346+
});
347+
348+
const result = await pool.execute.query(
349+
SQL`SELECT COUNT(*) as count FROM test`,
350+
);
351+
assert.equal(result.rows[0]?.count, 2, 'Both rows should be present');
352+
} finally {
353+
await pool.close();
354+
cleanupDb(nestedTxFileName);
355+
}
356+
},
357+
);
358+
359+
it(
360+
'dual pool writer: throws actionable error on nested withTransaction when allowNestedTransactions is false',
361+
withDeadline,
362+
async () => {
363+
const nestedTxFileName = 'nested-tx-no-opts-test.db';
364+
365+
const pool = sqlite3Pool({ fileName: nestedTxFileName });
366+
367+
try {
368+
await pool.execute.command(
369+
SQL`CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)`,
370+
);
371+
372+
await assert.rejects(
373+
() =>
374+
pool.withTransaction(async (outerTx) => {
375+
await outerTx.execute.command(
376+
SQL`INSERT INTO test (value) VALUES ('outer')`,
377+
);
378+
await pool.withTransaction(async (innerTx) => {
379+
await innerTx.execute.command(
380+
SQL`INSERT INTO test (value) VALUES ('inner')`,
381+
);
382+
});
383+
}),
384+
(err: unknown) => {
385+
assert.ok(
386+
err instanceof InvalidOperationError,
387+
`Expected InvalidOperationError, got ${String(err)}`,
388+
);
389+
assert.ok(
390+
err.message.includes('allowNestedTransactions'),
391+
`Error message should mention allowNestedTransactions, got: ${err.message}`,
392+
);
393+
return true;
394+
},
395+
);
396+
} finally {
397+
await pool.close();
398+
cleanupDb(nestedTxFileName);
399+
}
400+
},
401+
);
319402
});

src/packages/dumbo/src/storage/sqlite/core/transactions/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
JSONSerializer,
44
} from '../../../../core';
55
import {
6+
InvalidOperationError,
67
SQL,
78
sqlExecutor,
89
type DatabaseTransaction,
@@ -50,6 +51,8 @@ export const sqliteTransaction =
5051
allowNestedTransactions =
5152
options?.allowNestedTransactions ?? allowNestedTransactions;
5253

54+
let hasBegun = false;
55+
5356
const transaction: DatabaseTransaction<ConnectionType> = {
5457
connection: connection(),
5558
driverType,
@@ -68,8 +71,14 @@ export const sqliteTransaction =
6871
}
6972

7073
transactionCounter.increment();
74+
} else if (hasBegun) {
75+
throw new InvalidOperationError(
76+
'Cannot start a nested transaction: allowNestedTransactions is false. ' +
77+
'Set transactionOptions: { allowNestedTransactions: true } on your pool or connection.',
78+
);
7179
}
7280

81+
hasBegun = true;
7382
const mode = options?.mode ?? defaultTransactionMode ?? 'IMMEDIATE';
7483
await client.command(SQL`BEGIN ${SQL.plain(mode)} TRANSACTION`);
7584
},
@@ -91,6 +100,7 @@ export const sqliteTransaction =
91100

92101
transactionCounter.reset();
93102
}
103+
hasBegun = false;
94104
await client.command(SQL`COMMIT`);
95105
} finally {
96106
if (options?.close)
@@ -109,6 +119,7 @@ export const sqliteTransaction =
109119
}
110120
}
111121

122+
hasBegun = false;
112123
await client.command(SQL`ROLLBACK`);
113124
} finally {
114125
if (options?.close)
@@ -121,7 +132,10 @@ export const sqliteTransaction =
121132
execute: sqlExecutor(sqliteSQLExecutor(driverType, serializer), {
122133
connect: () => getClient,
123134
}),
124-
_transactionOptions: options ?? {},
135+
_transactionOptions: {
136+
...options,
137+
allowNestedTransactions,
138+
},
125139
};
126140

127141
return transaction as InferTransactionFromConnection<ConnectionType>;

src/packages/dumbo/src/storage/sqlite/sqlite3/transactions/transactions.int.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe('SQLite3 Transactions', () => {
107107
} catch (error) {
108108
assert.strictEqual(
109109
(error as Error).message,
110-
'SQLITE_ERROR: cannot start a transaction within a transaction',
110+
'Cannot start a nested transaction: allowNestedTransactions is false. Set transactionOptions: { allowNestedTransactions: true } on your pool or connection.',
111111
);
112112
} finally {
113113
await connection.close();

0 commit comments

Comments
 (0)