@strapi/database 4.5.3 → 4.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -211,22 +211,19 @@ const createEntityManager = (db) => {
211
211
  }
212
212
 
213
213
  const dataToInsert = processData(metadata, data, { withDefaults: true });
214
- let id;
215
214
 
216
- const trx = await strapi.db.transaction();
217
- try {
218
- const res = await this.createQueryBuilder(uid)
219
- .insert(dataToInsert)
220
- .transacting(trx)
221
- .execute();
215
+ const res = await this.createQueryBuilder(uid).insert(dataToInsert).execute();
222
216
 
223
- id = res[0].id || res[0];
217
+ const id = res[0].id || res[0];
224
218
 
219
+ const trx = await strapi.db.transaction();
220
+ try {
225
221
  await this.attachRelations(uid, id, data, { transaction: trx });
226
222
 
227
223
  await trx.commit();
228
224
  } catch (e) {
229
225
  await trx.rollback();
226
+ await this.createQueryBuilder(uid).where({ id }).delete().execute();
230
227
  throw e;
231
228
  }
232
229
 
@@ -285,7 +282,11 @@ const createEntityManager = (db) => {
285
282
  throw new Error('Update requires a where parameter');
286
283
  }
287
284
 
288
- const entity = await this.createQueryBuilder(uid).select('id').where(where).first().execute();
285
+ const entity = await this.createQueryBuilder(uid)
286
+ .select('*')
287
+ .where(where)
288
+ .first()
289
+ .execute({ mapResults: false });
289
290
 
290
291
  if (!entity) {
291
292
  return null;
@@ -293,23 +294,19 @@ const createEntityManager = (db) => {
293
294
 
294
295
  const { id } = entity;
295
296
 
296
- const trx = await strapi.db.transaction();
297
- try {
298
- const dataToUpdate = processData(metadata, data);
297
+ const dataToUpdate = processData(metadata, data);
299
298
 
300
- if (!isEmpty(dataToUpdate)) {
301
- await this.createQueryBuilder(uid)
302
- .where({ id })
303
- .update(dataToUpdate)
304
- .transacting(trx)
305
- .execute();
306
- }
299
+ if (!isEmpty(dataToUpdate)) {
300
+ await this.createQueryBuilder(uid).where({ id }).update(dataToUpdate).execute();
301
+ }
307
302
 
303
+ const trx = await strapi.db.transaction();
304
+ try {
308
305
  await this.updateRelations(uid, id, data, { transaction: trx });
309
-
310
306
  await trx.commit();
311
307
  } catch (e) {
312
308
  await trx.rollback();
309
+ await this.createQueryBuilder(uid).where({ id }).update(entity).execute();
313
310
  throw e;
314
311
  }
315
312
 
@@ -372,10 +369,10 @@ const createEntityManager = (db) => {
372
369
 
373
370
  const { id } = entity;
374
371
 
372
+ await this.createQueryBuilder(uid).where({ id }).delete().execute();
373
+
375
374
  const trx = await strapi.db.transaction();
376
375
  try {
377
- await this.createQueryBuilder(uid).where({ id }).delete().transacting(trx).execute();
378
-
379
376
  await this.deleteRelations(uid, id, { transaction: trx });
380
377
 
381
378
  await trx.commit();
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { map, isEmpty } = require('lodash/fp');
4
+ const { randomBytes } = require('crypto');
4
5
 
5
6
  const {
6
7
  isBidirectional,
@@ -197,60 +198,42 @@ const cleanOrderColumns = async ({ id, attribute, db, inverseRelIds, transaction
197
198
  return;
198
199
  }
199
200
 
200
- // Handle databases that don't support window function ROW_NUMBER
201
- if (!strapi.db.dialect.supportsWindowFunctions()) {
202
- await cleanOrderColumnsForOldDatabases({ id, attribute, db, inverseRelIds, transaction: trx });
203
- return;
204
- }
205
-
206
- const { joinTable } = attribute;
207
- const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
208
- const update = [];
209
- const updateBinding = [];
210
- const select = ['??'];
211
- const selectBinding = ['id'];
212
- const where = [];
213
- const whereBinding = [];
214
-
215
- if (hasOrderColumn(attribute) && id) {
216
- update.push('?? = b.src_order');
217
- updateBinding.push(orderColumnName);
218
- select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS src_order');
219
- selectBinding.push(joinColumn.name, orderColumnName);
220
- where.push('?? = ?');
221
- whereBinding.push(joinColumn.name, id);
222
- }
223
-
224
- if (hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds)) {
225
- update.push('?? = b.inv_order');
226
- updateBinding.push(inverseOrderColumnName);
227
- select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS inv_order');
228
- selectBinding.push(inverseJoinColumn.name, inverseOrderColumnName);
229
- where.push(`?? IN (${inverseRelIds.map(() => '?').join(', ')})`);
230
- whereBinding.push(inverseJoinColumn.name, ...inverseRelIds);
231
- }
232
-
233
- // raw query as knex doesn't allow updating from a subquery
234
- // https://github.com/knex/knex/issues/2504
235
201
  switch (strapi.db.dialect.client) {
236
202
  case 'mysql':
237
- await db.connection
238
- .raw(
239
- `UPDATE
240
- ?? as a,
241
- (
242
- SELECT ${select.join(', ')}
243
- FROM ??
244
- WHERE ${where.join(' OR ')}
245
- ) AS b
246
- SET ${update.join(', ')}
247
- WHERE b.id = a.id`,
248
- [joinTable.name, ...selectBinding, joinTable.name, ...whereBinding, ...updateBinding]
249
- )
250
- .transacting(trx);
203
+ await cleanOrderColumnsForInnoDB({ id, attribute, db, inverseRelIds, transaction: trx });
251
204
  break;
252
205
  default: {
206
+ const { joinTable } = attribute;
207
+ const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
208
+ const update = [];
209
+ const updateBinding = [];
210
+ const select = ['??'];
211
+ const selectBinding = ['id'];
212
+ const where = [];
213
+ const whereBinding = [];
214
+
215
+ if (hasOrderColumn(attribute) && id) {
216
+ update.push('?? = b.src_order');
217
+ updateBinding.push(orderColumnName);
218
+ select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS src_order');
219
+ selectBinding.push(joinColumn.name, orderColumnName);
220
+ where.push('?? = ?');
221
+ whereBinding.push(joinColumn.name, id);
222
+ }
223
+
224
+ if (hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds)) {
225
+ update.push('?? = b.inv_order');
226
+ updateBinding.push(inverseOrderColumnName);
227
+ select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS inv_order');
228
+ selectBinding.push(inverseJoinColumn.name, inverseOrderColumnName);
229
+ where.push(`?? IN (${inverseRelIds.map(() => '?').join(', ')})`);
230
+ whereBinding.push(inverseJoinColumn.name, ...inverseRelIds);
231
+ }
232
+
253
233
  const joinTableName = addSchema(joinTable.name);
234
+
235
+ // raw query as knex doesn't allow updating from a subquery
236
+ // https://github.com/knex/knex/issues/2504
254
237
  await db.connection
255
238
  .raw(
256
239
  `UPDATE ?? as a
@@ -264,24 +247,29 @@ const cleanOrderColumns = async ({ id, attribute, db, inverseRelIds, transaction
264
247
  [joinTableName, ...updateBinding, ...selectBinding, joinTableName, ...whereBinding]
265
248
  )
266
249
  .transacting(trx);
250
+
251
+ /*
252
+ `UPDATE :joinTable: as a
253
+ SET :orderColumn: = b.src_order, :inverseOrderColumn: = b.inv_order
254
+ FROM (
255
+ SELECT
256
+ id,
257
+ ROW_NUMBER() OVER ( PARTITION BY :joinColumn: ORDER BY :orderColumn:) AS src_order,
258
+ ROW_NUMBER() OVER ( PARTITION BY :inverseJoinColumn: ORDER BY :inverseOrderColumn:) AS inv_order
259
+ FROM :joinTable:
260
+ WHERE :joinColumn: = :id OR :inverseJoinColumn: IN (:inverseRelIds)
261
+ ) AS b
262
+ WHERE b.id = a.id`,
263
+ */
267
264
  }
268
- /*
269
- `UPDATE :joinTable: as a
270
- SET :orderColumn: = b.src_order, :inverseOrderColumn: = b.inv_order
271
- FROM (
272
- SELECT
273
- id,
274
- ROW_NUMBER() OVER ( PARTITION BY :joinColumn: ORDER BY :orderColumn:) AS src_order,
275
- ROW_NUMBER() OVER ( PARTITION BY :inverseJoinColumn: ORDER BY :inverseOrderColumn:) AS inv_order
276
- FROM :joinTable:
277
- WHERE :joinColumn: = :id OR :inverseJoinColumn: IN (:inverseRelIds)
278
- ) AS b
279
- WHERE b.id = a.id`,
280
- */
281
265
  }
282
266
  };
283
267
 
284
- const cleanOrderColumnsForOldDatabases = async ({
268
+ /*
269
+ * Ensure that orders are following a 1, 2, 3 sequence, without gap.
270
+ * The use of a temporary table instead of a window function makes the query compatible with MySQL 5 and prevents some deadlocks to happen in innoDB databases
271
+ */
272
+ const cleanOrderColumnsForInnoDB = async ({
285
273
  id,
286
274
  attribute,
287
275
  db,
@@ -292,14 +280,15 @@ const cleanOrderColumnsForOldDatabases = async ({
292
280
  const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
293
281
 
294
282
  const now = new Date().valueOf();
283
+ const randomHex = randomBytes(16).toString('hex');
295
284
 
296
285
  if (hasOrderColumn(attribute) && id) {
297
- const tempOrderTableName = `tempOrderTableName_${now}`;
286
+ const tempOrderTableName = `orderTable_${now}_${randomHex}`;
298
287
  try {
299
288
  await db.connection
300
289
  .raw(
301
290
  `
302
- CREATE TEMPORARY TABLE :tempOrderTableName:
291
+ CREATE TABLE :tempOrderTableName:
303
292
  SELECT
304
293
  id,
305
294
  (
@@ -317,6 +306,9 @@ const cleanOrderColumnsForOldDatabases = async ({
317
306
  }
318
307
  )
319
308
  .transacting(trx);
309
+
310
+ // raw query as knex doesn't allow updating from a subquery
311
+ // https://github.com/knex/knex/issues/2504
320
312
  await db.connection
321
313
  .raw(
322
314
  `UPDATE ?? as a, (SELECT * FROM ??) AS b
@@ -326,14 +318,12 @@ const cleanOrderColumnsForOldDatabases = async ({
326
318
  )
327
319
  .transacting(trx);
328
320
  } finally {
329
- await db.connection
330
- .raw(`DROP TEMPORARY TABLE IF EXISTS ??`, [tempOrderTableName])
331
- .transacting(trx);
321
+ await db.connection.raw(`DROP TABLE IF EXISTS ??`, [tempOrderTableName]).transacting(trx);
332
322
  }
333
323
  }
334
324
 
335
325
  if (hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds)) {
336
- const tempInvOrderTableName = `tempInvOrderTableName_${now}`;
326
+ const tempInvOrderTableName = `invOrderTable_${now}_${randomHex}`;
337
327
  try {
338
328
  await db.connection
339
329
  .raw(
@@ -411,7 +411,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
411
411
  let inverseOrderColumnName = _.snakeCase(`${meta.singularName}_order`);
412
412
 
413
413
  // if relation is self referencing
414
- if (attribute.relation === 'manyToMany' && joinColumnName === inverseJoinColumnName) {
414
+ if (attribute.relation === 'manyToMany' && orderColumnName === inverseOrderColumnName) {
415
415
  inverseOrderColumnName = `inv_${inverseOrderColumnName}`;
416
416
  }
417
417
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/database",
3
- "version": "4.5.3",
3
+ "version": "4.5.4",
4
4
  "description": "Strapi's database layer",
5
5
  "homepage": "https://strapi.io",
6
6
  "bugs": {
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "date-fns": "2.29.2",
35
- "debug": "4.3.1",
35
+ "debug": "4.3.4",
36
36
  "fs-extra": "10.0.0",
37
37
  "knex": "1.0.7",
38
38
  "lodash": "4.17.21",
@@ -43,5 +43,5 @@
43
43
  "node": ">=14.19.1 <=18.x.x",
44
44
  "npm": ">=6.0.0"
45
45
  },
46
- "gitHead": "5453885f7aa329f98d047a6030a8e98ba3cbdb41"
46
+ "gitHead": "8716ecc920130db5341b0904cf868c6e6b581a5d"
47
47
  }