Merge branch 'chore/transactions' into deits/rollbacks

This commit is contained in:
Bassel 2023-01-18 14:27:17 +02:00
commit 9fecc309e7
9 changed files with 92 additions and 24 deletions

View File

@ -227,7 +227,7 @@ const createEntityManager = (db) => {
const trx = await strapi.db.transaction();
try {
await this.attachRelations(uid, id, data, { transaction: trx });
await this.attachRelations(uid, id, data, { transaction: trx.get() });
await trx.commit();
} catch (e) {
@ -311,7 +311,7 @@ const createEntityManager = (db) => {
const trx = await strapi.db.transaction();
try {
await this.updateRelations(uid, id, data, { transaction: trx });
await this.updateRelations(uid, id, data, { transaction: trx.get() });
await trx.commit();
} catch (e) {
await trx.rollback();
@ -382,7 +382,7 @@ const createEntityManager = (db) => {
const trx = await strapi.db.transaction();
try {
await this.deleteRelations(uid, id, { transaction: trx });
await this.deleteRelations(uid, id, { transaction: trx.get() });
await trx.commit();
} catch (e) {

View File

@ -163,6 +163,11 @@ export interface Database {
connection: Knex;
query<T extends keyof AllTypes>(uid: T): QueryFromContentType<T>;
transaction(
cb?: (trx: Knex.Transaction) => Promise<any>
):
| Promise<void>
| { get: () => Knex.Transaction; rollback: () => Promise<void>; commit: () => Promise<void> };
}
export class Database implements Database {
static transformContentTypes(contentTypes: any[]): ModelConfig[];

View File

@ -8,6 +8,7 @@ const { createMigrationsProvider } = require('./migrations');
const { createLifecyclesProvider } = require('./lifecycles');
const createConnection = require('./connection');
const errors = require('./errors');
const transactionCtx = require('./transaction-context');
// TODO: move back into strapi
const { transformContentTypes } = require('./utils/content-types');
@ -49,6 +50,43 @@ class Database {
return this.entityManager.getRepository(uid);
}
async transaction(cb) {
const notNestedTransaction = !transactionCtx.get();
const trx = notNestedTransaction ? await this.connection.transaction() : transactionCtx.get();
if (!cb) {
return {
async commit() {
if (notNestedTransaction) {
await trx.commit();
}
},
async rollback() {
if (notNestedTransaction) {
await trx.rollback();
}
},
get() {
return trx;
},
};
}
return transactionCtx.run(trx, async () => {
try {
const res = await cb(trx);
if (notNestedTransaction) {
await trx.commit();
}
return res;
} catch (error) {
if (notNestedTransaction) {
await trx.rollback();
}
throw error;
}
});
}
getConnection(tableName) {
const schema = this.connection.getSchemaName();
const connection = tableName ? this.connection(tableName) : this.connection;
@ -60,10 +98,6 @@ class Database {
return schema ? trx.schema.withSchema(schema) : trx.schema;
}
transaction() {
return this.connection.transaction();
}
queryBuilder(uid) {
return this.entityManager.createQueryBuilder(uid);
}

View File

@ -4,6 +4,7 @@ const _ = require('lodash/fp');
const { DatabaseError } = require('../errors');
const helpers = require('./helpers');
const transactionCtx = require('../transaction-context');
const createQueryBuilder = (uid, db, initialState = {}) => {
const meta = db.metadata.get(uid);
@ -473,10 +474,18 @@ const createQueryBuilder = (uid, db, initialState = {}) => {
try {
const qb = this.getKnexQuery();
if (transactionCtx.get()) {
qb.transacting(transactionCtx.get());
}
const rows = await qb;
if (state.populate && !_.isNil(rows)) {
await helpers.applyPopulate(_.castArray(rows), state.populate, { qb: this, uid, db });
await helpers.applyPopulate(_.castArray(rows), state.populate, {
qb: this,
uid,
db,
});
}
let results = rows;

View File

@ -0,0 +1,17 @@
'use strict';
const { AsyncLocalStorage } = require('async_hooks');
const storage = new AsyncLocalStorage();
const transactionCtx = {
async run(store, cb) {
return storage.run(store, cb);
},
get() {
return storage.getStore();
},
};
module.exports = transactionCtx;

View File

@ -23,6 +23,7 @@ describe('Entity service triggers webhooks', () => {
getModel: () => model,
},
db: {
transaction: (cb) => cb(),
query: () => ({
count: () => 0,
create: ({ data }) => data,

View File

@ -57,6 +57,7 @@ describe('Entity service', () => {
const fakeDB = {
query: jest.fn(() => fakeQuery),
transaction: (cb) => cb(),
};
const fakeStrapi = {
@ -185,6 +186,7 @@ describe('Entity service', () => {
});
const fakeDB = {
transaction: (cb) => cb(),
query: jest.fn((uid) => fakeQuery(uid)),
};

View File

@ -86,7 +86,7 @@ module.exports = {
.queryBuilder(FOLDER_MODEL_UID)
.select(['id', 'pathId', 'path'])
.where({ id: { $in: folderIds } })
.transacting(trx)
.transacting(trx.get())
.forUpdate()
.execute();
@ -95,7 +95,7 @@ module.exports = {
.queryBuilder(FILE_MODEL_UID)
.select(['id'])
.where({ id: { $in: fileIds } })
.transacting(trx)
.transacting(trx.get())
.forUpdate()
.execute();
@ -106,7 +106,7 @@ module.exports = {
.queryBuilder(FOLDER_MODEL_UID)
.select('path')
.where({ id: destinationFolderId })
.transacting(trx)
.transacting(trx.get())
.first()
.execute();
destinationFolderPath = destinationFolder.path;
@ -123,7 +123,7 @@ module.exports = {
const { joinTable } = strapi.db.metadata.get(FOLDER_MODEL_UID).attributes.parent;
await strapi.db
.queryBuilder(joinTable.name)
.transacting(trx)
.transacting(trx.get())
.delete()
.where({ [joinTable.joinColumn.name]: { $in: folderIds } })
.execute();
@ -131,7 +131,7 @@ module.exports = {
if (destinationFolderId !== null) {
await strapi.db
.queryBuilder(joinTable.name)
.transacting(trx)
.transacting(trx.get())
.insert(
existingFolders.map((folder) => ({
[joinTable.inverseJoinColumn.name]: destinationFolderId,
@ -157,7 +157,7 @@ module.exports = {
// update path for folders themselves & folders below
totalFolderNumber = await strapi.db
.getConnection(folderTable)
.transacting(trx)
.transacting(trx.get())
.where(pathColName, existingFolder.path)
.orWhere(pathColName, 'like', `${existingFolder.path}/%`)
.update(
@ -172,7 +172,7 @@ module.exports = {
// update path of files below
totalFileNumber = await strapi.db
.getConnection(fileTable)
.transacting(trx)
.transacting(trx.get())
.where(folderPathColName, existingFolder.path)
.orWhere(folderPathColName, 'like', `${existingFolder.path}/%`)
.update(
@ -191,7 +191,7 @@ module.exports = {
const fileJoinTable = strapi.db.metadata.get(FILE_MODEL_UID).attributes.folder.joinTable;
await strapi.db
.queryBuilder(fileJoinTable.name)
.transacting(trx)
.transacting(trx.get())
.delete()
.where({ [fileJoinTable.joinColumn.name]: { $in: fileIds } })
.execute();
@ -199,7 +199,7 @@ module.exports = {
if (destinationFolderId !== null) {
await strapi.db
.queryBuilder(fileJoinTable.name)
.transacting(trx)
.transacting(trx.get())
.insert(
existingFiles.map((file) => ({
[fileJoinTable.inverseJoinColumn.name]: destinationFolderId,
@ -212,7 +212,7 @@ module.exports = {
// update files main fields (path + updatedBy)
await strapi.db
.getConnection(fileTable)
.transacting(trx)
.transacting(trx.get())
.whereIn('id', fileIds)
.update(folderPathColName, destinationFolderPath);
}

View File

@ -103,7 +103,7 @@ const update = async (id, { name, parent }, { user }) => {
.queryBuilder(FOLDER_MODEL_UID)
.select(['pathId', 'path'])
.where({ id })
.transacting(trx)
.transacting(trx.get())
.forUpdate()
.first()
.execute();
@ -112,7 +112,7 @@ const update = async (id, { name, parent }, { user }) => {
const { joinTable } = strapi.db.metadata.get(FOLDER_MODEL_UID).attributes.parent;
await strapi.db
.queryBuilder(joinTable.name)
.transacting(trx)
.transacting(trx.get())
.delete()
.where({ [joinTable.joinColumn.name]: id })
.execute();
@ -120,7 +120,7 @@ const update = async (id, { name, parent }, { user }) => {
if (parent !== null) {
await strapi.db
.queryBuilder(joinTable.name)
.transacting(trx)
.transacting(trx.get())
.insert({ [joinTable.inverseJoinColumn.name]: parent, [joinTable.joinColumn.name]: id })
.where({ [joinTable.joinColumn.name]: id })
.execute();
@ -133,7 +133,7 @@ const update = async (id, { name, parent }, { user }) => {
.queryBuilder(FOLDER_MODEL_UID)
.select('path')
.where({ id: parent })
.transacting(trx)
.transacting(trx.get())
.first()
.execute();
destinationFolderPath = destinationFolder.path;
@ -148,7 +148,7 @@ const update = async (id, { name, parent }, { user }) => {
// update folders below
await strapi.db
.getConnection(folderTable)
.transacting(trx)
.transacting(trx.get())
.where(pathColumnName, existingFolder.path)
.orWhere(pathColumnName, 'like', `${existingFolder.path}/%`)
.update(
@ -163,7 +163,7 @@ const update = async (id, { name, parent }, { user }) => {
// update files below
await strapi.db
.getConnection(fileTable)
.transacting(trx)
.transacting(trx.get())
.where(folderPathColumnName, existingFolder.path)
.orWhere(folderPathColumnName, 'like', `${existingFolder.path}/%`)
.update(