mirror of
https://github.com/strapi/strapi.git
synced 2025-12-29 08:04:51 +00:00
Merge branch 'chore/transactions' into deits/rollbacks
This commit is contained in:
commit
9fecc309e7
@ -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) {
|
||||
|
||||
5
packages/core/database/lib/index.d.ts
vendored
5
packages/core/database/lib/index.d.ts
vendored
@ -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[];
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
17
packages/core/database/lib/transaction-context.js
Normal file
17
packages/core/database/lib/transaction-context.js
Normal 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;
|
||||
@ -23,6 +23,7 @@ describe('Entity service triggers webhooks', () => {
|
||||
getModel: () => model,
|
||||
},
|
||||
db: {
|
||||
transaction: (cb) => cb(),
|
||||
query: () => ({
|
||||
count: () => 0,
|
||||
create: ({ data }) => data,
|
||||
|
||||
@ -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)),
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user