@strapi/database 4.5.2 → 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
|
|
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
|
-
|
|
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)
|
|
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
|
|
297
|
-
try {
|
|
298
|
-
const dataToUpdate = processData(metadata, data);
|
|
297
|
+
const dataToUpdate = processData(metadata, data);
|
|
299
298
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
|
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
|
-
|
|
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 = `
|
|
286
|
+
const tempOrderTableName = `orderTable_${now}_${randomHex}`;
|
|
298
287
|
try {
|
|
299
288
|
await db.connection
|
|
300
289
|
.raw(
|
|
301
290
|
`
|
|
302
|
-
CREATE
|
|
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 = `
|
|
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' &&
|
|
414
|
+
if (attribute.relation === 'manyToMany' && orderColumnName === inverseOrderColumnName) {
|
|
415
415
|
inverseOrderColumnName = `inv_${inverseOrderColumnName}`;
|
|
416
416
|
}
|
|
417
417
|
|
|
@@ -446,6 +446,11 @@ const morphToMany = async (input, ctx) => {
|
|
|
446
446
|
.where({
|
|
447
447
|
[joinColumn.name]: referencedValues,
|
|
448
448
|
...(joinTable.on || {}),
|
|
449
|
+
// If the populateValue contains an "on" property,
|
|
450
|
+
// only populate the types defined in it
|
|
451
|
+
...('on' in populateValue
|
|
452
|
+
? { [morphColumn.typeColumn.name]: Object.keys(populateValue.on) }
|
|
453
|
+
: {}),
|
|
449
454
|
})
|
|
450
455
|
.orderBy([joinColumn.name, 'order'])
|
|
451
456
|
.execute({ mapResults: false });
|
|
@@ -470,6 +475,8 @@ const morphToMany = async (input, ctx) => {
|
|
|
470
475
|
}, {});
|
|
471
476
|
|
|
472
477
|
const map = {};
|
|
478
|
+
const { on, ...typePopulate } = populateValue;
|
|
479
|
+
|
|
473
480
|
for (const type of Object.keys(idsByType)) {
|
|
474
481
|
const ids = idsByType[type];
|
|
475
482
|
|
|
@@ -483,7 +490,7 @@ const morphToMany = async (input, ctx) => {
|
|
|
483
490
|
const qb = db.entityManager.createQueryBuilder(type);
|
|
484
491
|
|
|
485
492
|
const rows = await qb
|
|
486
|
-
.init(
|
|
493
|
+
.init(on?.[type] ?? typePopulate)
|
|
487
494
|
.addSelect(`${qb.alias}.${idColumn.referencedColumn}`)
|
|
488
495
|
.where({ [idColumn.referencedColumn]: ids })
|
|
489
496
|
.execute({ mapResults: false });
|
|
@@ -540,6 +547,8 @@ const morphToOne = async (input, ctx) => {
|
|
|
540
547
|
}, {});
|
|
541
548
|
|
|
542
549
|
const map = {};
|
|
550
|
+
const { on, ...typePopulate } = populateValue;
|
|
551
|
+
|
|
543
552
|
for (const type of Object.keys(idsByType)) {
|
|
544
553
|
const ids = idsByType[type];
|
|
545
554
|
|
|
@@ -552,7 +561,7 @@ const morphToOne = async (input, ctx) => {
|
|
|
552
561
|
const qb = db.entityManager.createQueryBuilder(type);
|
|
553
562
|
|
|
554
563
|
const rows = await qb
|
|
555
|
-
.init(
|
|
564
|
+
.init(on?.[type] ?? typePopulate)
|
|
556
565
|
.addSelect(`${qb.alias}.${idColumn.referencedColumn}`)
|
|
557
566
|
.where({ [idColumn.referencedColumn]: ids })
|
|
558
567
|
.execute({ mapResults: false });
|
|
@@ -579,7 +588,16 @@ const morphToOne = async (input, ctx) => {
|
|
|
579
588
|
|
|
580
589
|
// TODO: Omit limit & offset to avoid needing a query per result to avoid making too many queries
|
|
581
590
|
const pickPopulateParams = (populate) => {
|
|
582
|
-
const fieldsToPick = [
|
|
591
|
+
const fieldsToPick = [
|
|
592
|
+
'select',
|
|
593
|
+
'count',
|
|
594
|
+
'where',
|
|
595
|
+
'populate',
|
|
596
|
+
'orderBy',
|
|
597
|
+
'filters',
|
|
598
|
+
'ordering',
|
|
599
|
+
'on',
|
|
600
|
+
];
|
|
583
601
|
|
|
584
602
|
if (populate.count !== true) {
|
|
585
603
|
fieldsToPick.push('limit', 'offset');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/database",
|
|
3
|
-
"version": "4.5.
|
|
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.
|
|
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": "
|
|
46
|
+
"gitHead": "8716ecc920130db5341b0904cf868c6e6b581a5d"
|
|
47
47
|
}
|