Skip to content

Commit

Permalink
feat(sql): add readOnly option to em.begin() and `em.transactiona…
Browse files Browse the repository at this point in the history
…l()`
  • Loading branch information
B4nan committed Aug 28, 2023
1 parent af74491 commit 86bb7d4
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 5 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/connections/Connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ export abstract class Connection {
*/
abstract getDefaultClientUrl(): string;

async transactional<T>(cb: (trx: Transaction) => Promise<T>, options?: { isolationLevel?: IsolationLevel; ctx?: Transaction; eventBroadcaster?: TransactionEventBroadcaster }): Promise<T> {
async transactional<T>(cb: (trx: Transaction) => Promise<T>, options?: { isolationLevel?: IsolationLevel; readOnly?: boolean; ctx?: Transaction; eventBroadcaster?: TransactionEventBroadcaster }): Promise<T> {
throw new Error(`Transactions are not supported by current driver`);
}

async begin(options?: { isolationLevel?: IsolationLevel; ctx?: Transaction; eventBroadcaster?: TransactionEventBroadcaster }): Promise<Transaction> {
async begin(options?: { isolationLevel?: IsolationLevel; readOnly?: boolean; ctx?: Transaction; eventBroadcaster?: TransactionEventBroadcaster }): Promise<Transaction> {
throw new Error(`Transactions are not supported by current driver`);
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export type TransactionEventType = EventType.beforeTransactionStart | EventType.
export interface TransactionOptions {
ctx?: Transaction;
isolationLevel?: IsolationLevel;
readOnly?: boolean;
flushMode?: FlushMode;
ignoreNestedTransactions?: boolean;
}
Expand Down
9 changes: 6 additions & 3 deletions packages/knex/src/AbstractSqlConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export abstract class AbstractSqlConnection extends Connection {
}
}

async transactional<T>(cb: (trx: Transaction<Knex.Transaction>) => Promise<T>, options: { isolationLevel?: IsolationLevel; ctx?: Knex.Transaction; eventBroadcaster?: TransactionEventBroadcaster } = {}): Promise<T> {
async transactional<T>(cb: (trx: Transaction<Knex.Transaction>) => Promise<T>, options: { isolationLevel?: IsolationLevel; readOnly?: boolean; ctx?: Knex.Transaction; eventBroadcaster?: TransactionEventBroadcaster } = {}): Promise<T> {
const trx = await this.begin(options);

try {
Expand All @@ -58,12 +58,15 @@ export abstract class AbstractSqlConnection extends Connection {
}
}

async begin(options: { isolationLevel?: IsolationLevel; ctx?: Knex.Transaction; eventBroadcaster?: TransactionEventBroadcaster } = {}): Promise<Knex.Transaction> {
async begin(options: { isolationLevel?: IsolationLevel; readOnly?: boolean; ctx?: Knex.Transaction; eventBroadcaster?: TransactionEventBroadcaster } = {}): Promise<Knex.Transaction> {
if (!options.ctx) {
await options.eventBroadcaster?.dispatchEvent(EventType.beforeTransactionStart);
}

const trx = await (options.ctx || this.client).transaction(null, { isolationLevel: options.isolationLevel });
const trx = await (options.ctx || this.client).transaction(null, {
isolationLevel: options.isolationLevel,
readOnly: options.readOnly,
});

if (!options.ctx) {
await options.eventBroadcaster?.dispatchEvent(EventType.afterTransactionStart, trx);
Expand Down
13 changes: 13 additions & 0 deletions tests/EntityManager.postgre.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,19 @@ describe('EntityManagerPostgre', () => {
expect(mock.mock.calls[2][0]).toMatch('rollback');
});

test('read-only transactions', async () => {
const mock = mockLogger(orm, ['query']);

const god1 = new Author2('God1', 'hello@heaven1.god');
await expect(orm.em.transactional(async em => {
await em.persistAndFlush(god1);
}, { readOnly: true, isolationLevel: IsolationLevel.READ_COMMITTED })).rejects.toThrowError(/cannot execute INSERT in a read-only transaction/);

expect(mock.mock.calls[0][0]).toMatch('begin transaction isolation level read committed read only');
expect(mock.mock.calls[1][0]).toMatch('insert into "author2" ("created_at", "updated_at", "name", "email", "terms_accepted") values ($1, $2, $3, $4, $5) returning "id", "created_at", "updated_at", "age", "terms_accepted"');
expect(mock.mock.calls[2][0]).toMatch('rollback');
});

test('nested transactions with save-points', async () => {
await orm.em.transactional(async em => {
const god1 = new Author2('God1', 'hello1@heaven.god');
Expand Down

0 comments on commit 86bb7d4

Please sign in to comment.