@strapi/database 4.4.3 → 4.5.0-beta.0
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.
- package/lib/entity-manager/entity-repository.js +51 -12
- package/lib/entity-manager/index.js +407 -100
- package/lib/entity-manager/morph-relations.js +6 -2
- package/lib/entity-manager/regular-relations.js +283 -0
- package/lib/metadata/index.js +9 -1
- package/lib/metadata/relations.js +75 -3
- package/lib/query/helpers/join.js +9 -8
- package/lib/query/helpers/populate/apply.js +646 -0
- package/lib/query/helpers/populate/index.js +9 -0
- package/lib/query/helpers/populate/process.js +96 -0
- package/lib/query/helpers/where.js +3 -2
- package/lib/query/query-builder.js +102 -23
- package/lib/tests/knex-utils.test.e2e.js +33 -0
- package/lib/utils/knex.js +12 -0
- package/package.json +2 -2
- package/lib/query/helpers/populate.js +0 -649
|
@@ -12,20 +12,38 @@ const {
|
|
|
12
12
|
isEmpty,
|
|
13
13
|
isArray,
|
|
14
14
|
isNull,
|
|
15
|
+
uniqWith,
|
|
16
|
+
isEqual,
|
|
17
|
+
differenceWith,
|
|
18
|
+
isNumber,
|
|
19
|
+
map,
|
|
20
|
+
difference,
|
|
15
21
|
} = require('lodash/fp');
|
|
16
22
|
const types = require('../types');
|
|
17
23
|
const { createField } = require('../fields');
|
|
18
24
|
const { createQueryBuilder } = require('../query');
|
|
19
25
|
const { createRepository } = require('./entity-repository');
|
|
20
|
-
const { isBidirectional, isOneToAny } = require('../metadata/relations');
|
|
21
26
|
const { deleteRelatedMorphOneRelationsAfterMorphToManyUpdate } = require('./morph-relations');
|
|
27
|
+
const {
|
|
28
|
+
isBidirectional,
|
|
29
|
+
isAnyToOne,
|
|
30
|
+
isOneToAny,
|
|
31
|
+
hasOrderColumn,
|
|
32
|
+
hasInverseOrderColumn,
|
|
33
|
+
} = require('../metadata/relations');
|
|
34
|
+
const {
|
|
35
|
+
deletePreviousOneToAnyRelations,
|
|
36
|
+
deletePreviousAnyToOneRelations,
|
|
37
|
+
deleteRelations,
|
|
38
|
+
cleanOrderColumns,
|
|
39
|
+
} = require('./regular-relations');
|
|
22
40
|
|
|
23
41
|
const toId = (value) => value.id || value;
|
|
24
42
|
const toIds = (value) => castArray(value || []).map(toId);
|
|
25
43
|
|
|
26
44
|
const isValidId = (value) => isString(value) || isInteger(value);
|
|
27
|
-
const
|
|
28
|
-
|
|
45
|
+
const toIdArray = (data) => {
|
|
46
|
+
const array = castArray(data)
|
|
29
47
|
.filter((datum) => !isNil(datum))
|
|
30
48
|
.map((datum) => {
|
|
31
49
|
// if it is a string or an integer return an obj with id = to datum
|
|
@@ -40,6 +58,26 @@ const toAssocs = (data) => {
|
|
|
40
58
|
|
|
41
59
|
return datum;
|
|
42
60
|
});
|
|
61
|
+
return uniqWith(isEqual, array);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const toAssocs = (data) => {
|
|
65
|
+
if (isArray(data) || isString(data) || isNumber(data) || isNull(data) || data?.id) {
|
|
66
|
+
return {
|
|
67
|
+
set: isNull(data) ? data : toIdArray(data),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (data?.set) {
|
|
72
|
+
return {
|
|
73
|
+
set: isNull(data.set) ? data.set : toIdArray(data.set),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
connect: toIdArray(data?.connect),
|
|
79
|
+
disconnect: toIdArray(data?.disconnect),
|
|
80
|
+
};
|
|
43
81
|
};
|
|
44
82
|
|
|
45
83
|
const processData = (metadata, data = {}, { withDefaults = false } = {}) => {
|
|
@@ -173,12 +211,24 @@ const createEntityManager = (db) => {
|
|
|
173
211
|
}
|
|
174
212
|
|
|
175
213
|
const dataToInsert = processData(metadata, data, { withDefaults: true });
|
|
214
|
+
let id;
|
|
176
215
|
|
|
177
|
-
const
|
|
216
|
+
const trx = await strapi.db.transaction();
|
|
217
|
+
try {
|
|
218
|
+
const res = await this.createQueryBuilder(uid)
|
|
219
|
+
.insert(dataToInsert)
|
|
220
|
+
.transacting(trx)
|
|
221
|
+
.execute();
|
|
178
222
|
|
|
179
|
-
|
|
223
|
+
id = res[0].id || res[0];
|
|
180
224
|
|
|
181
|
-
|
|
225
|
+
await this.attachRelations(uid, id, data, { transaction: trx });
|
|
226
|
+
|
|
227
|
+
await trx.commit();
|
|
228
|
+
} catch (e) {
|
|
229
|
+
await trx.rollback();
|
|
230
|
+
throw e;
|
|
231
|
+
}
|
|
182
232
|
|
|
183
233
|
// TODO: in case there is no select or populate specified return the inserted data ?
|
|
184
234
|
// TODO: do not trigger the findOne lifecycles ?
|
|
@@ -243,13 +293,25 @@ const createEntityManager = (db) => {
|
|
|
243
293
|
|
|
244
294
|
const { id } = entity;
|
|
245
295
|
|
|
246
|
-
const
|
|
296
|
+
const trx = await strapi.db.transaction();
|
|
297
|
+
try {
|
|
298
|
+
const dataToUpdate = processData(metadata, data);
|
|
247
299
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
300
|
+
if (!isEmpty(dataToUpdate)) {
|
|
301
|
+
await this.createQueryBuilder(uid)
|
|
302
|
+
.where({ id })
|
|
303
|
+
.update(dataToUpdate)
|
|
304
|
+
.transacting(trx)
|
|
305
|
+
.execute();
|
|
306
|
+
}
|
|
251
307
|
|
|
252
|
-
|
|
308
|
+
await this.updateRelations(uid, id, data, { transaction: trx });
|
|
309
|
+
|
|
310
|
+
await trx.commit();
|
|
311
|
+
} catch (e) {
|
|
312
|
+
await trx.rollback();
|
|
313
|
+
throw e;
|
|
314
|
+
}
|
|
253
315
|
|
|
254
316
|
// TODO: do not trigger the findOne lifecycles ?
|
|
255
317
|
const result = await this.findOne(uid, {
|
|
@@ -310,9 +372,17 @@ const createEntityManager = (db) => {
|
|
|
310
372
|
|
|
311
373
|
const { id } = entity;
|
|
312
374
|
|
|
313
|
-
|
|
375
|
+
const trx = await strapi.db.transaction();
|
|
376
|
+
try {
|
|
377
|
+
await this.createQueryBuilder(uid).where({ id }).delete().transacting(trx).execute();
|
|
314
378
|
|
|
315
|
-
|
|
379
|
+
await this.deleteRelations(uid, id, { transaction: trx });
|
|
380
|
+
|
|
381
|
+
await trx.commit();
|
|
382
|
+
} catch (e) {
|
|
383
|
+
await trx.rollback();
|
|
384
|
+
throw e;
|
|
385
|
+
}
|
|
316
386
|
|
|
317
387
|
await db.lifecycles.run('afterDelete', uid, { params, result: entity }, states);
|
|
318
388
|
|
|
@@ -342,8 +412,7 @@ const createEntityManager = (db) => {
|
|
|
342
412
|
* @param {ID} id - entity ID
|
|
343
413
|
* @param {object} data - data received for creation
|
|
344
414
|
*/
|
|
345
|
-
|
|
346
|
-
async attachRelations(uid, id, data) {
|
|
415
|
+
async attachRelations(uid, id, data, { transaction: trx }) {
|
|
347
416
|
const { attributes } = db.metadata.get(uid);
|
|
348
417
|
|
|
349
418
|
for (const attributeName of Object.keys(attributes)) {
|
|
@@ -355,6 +424,8 @@ const createEntityManager = (db) => {
|
|
|
355
424
|
continue;
|
|
356
425
|
}
|
|
357
426
|
|
|
427
|
+
const cleanRelationData = toAssocs(data[attributeName]);
|
|
428
|
+
|
|
358
429
|
if (attribute.relation === 'morphOne' || attribute.relation === 'morphMany') {
|
|
359
430
|
const { target, morphBy } = attribute;
|
|
360
431
|
|
|
@@ -364,9 +435,12 @@ const createEntityManager = (db) => {
|
|
|
364
435
|
// set columns
|
|
365
436
|
const { idColumn, typeColumn } = targetAttribute.morphColumn;
|
|
366
437
|
|
|
438
|
+
const relId = toId(cleanRelationData.set[0]);
|
|
439
|
+
|
|
367
440
|
await this.createQueryBuilder(target)
|
|
368
441
|
.update({ [idColumn.name]: id, [typeColumn.name]: uid })
|
|
369
|
-
.where({ id:
|
|
442
|
+
.where({ id: relId })
|
|
443
|
+
.transacting(trx)
|
|
370
444
|
.execute();
|
|
371
445
|
} else if (targetAttribute.relation === 'morphToMany') {
|
|
372
446
|
const { joinTable } = targetAttribute;
|
|
@@ -374,7 +448,11 @@ const createEntityManager = (db) => {
|
|
|
374
448
|
|
|
375
449
|
const { idColumn, typeColumn } = morphColumn;
|
|
376
450
|
|
|
377
|
-
|
|
451
|
+
if (isEmpty(cleanRelationData.set)) {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const rows = cleanRelationData.set.map((data, idx) => {
|
|
378
456
|
return {
|
|
379
457
|
[joinColumn.name]: data.id,
|
|
380
458
|
[idColumn.name]: id,
|
|
@@ -386,11 +464,7 @@ const createEntityManager = (db) => {
|
|
|
386
464
|
};
|
|
387
465
|
});
|
|
388
466
|
|
|
389
|
-
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
await this.createQueryBuilder(joinTable.name).insert(rows).execute();
|
|
467
|
+
await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
|
|
394
468
|
}
|
|
395
469
|
|
|
396
470
|
continue;
|
|
@@ -403,40 +477,44 @@ const createEntityManager = (db) => {
|
|
|
403
477
|
|
|
404
478
|
const { idColumn, typeColumn, typeField = '__type' } = morphColumn;
|
|
405
479
|
|
|
406
|
-
|
|
480
|
+
if (isEmpty(cleanRelationData.set)) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const rows = cleanRelationData.set.map((data, idx) => ({
|
|
407
485
|
[joinColumn.name]: id,
|
|
408
486
|
[idColumn.name]: data.id,
|
|
409
487
|
[typeColumn.name]: data[typeField],
|
|
410
488
|
...(joinTable.on || {}),
|
|
411
489
|
...(data.__pivot || {}),
|
|
490
|
+
order: idx + 1,
|
|
412
491
|
}));
|
|
413
492
|
|
|
414
|
-
if (isEmpty(rows)) {
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
493
|
// delete previous relations
|
|
419
494
|
await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rows, {
|
|
420
495
|
uid,
|
|
421
496
|
attributeName,
|
|
422
497
|
joinTable,
|
|
423
498
|
db,
|
|
499
|
+
transaction: trx,
|
|
424
500
|
});
|
|
425
501
|
|
|
426
|
-
await this.createQueryBuilder(joinTable.name).insert(rows).execute();
|
|
502
|
+
await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
|
|
427
503
|
|
|
428
504
|
continue;
|
|
429
505
|
}
|
|
430
506
|
|
|
431
507
|
if (attribute.joinColumn && attribute.owner) {
|
|
508
|
+
const relIdsToAdd = toIds(cleanRelationData.set);
|
|
432
509
|
if (
|
|
433
510
|
attribute.relation === 'oneToOne' &&
|
|
434
511
|
isBidirectional(attribute) &&
|
|
435
|
-
|
|
512
|
+
relIdsToAdd.length
|
|
436
513
|
) {
|
|
437
514
|
await this.createQueryBuilder(uid)
|
|
438
|
-
.where({ [attribute.joinColumn.name]:
|
|
515
|
+
.where({ [attribute.joinColumn.name]: relIdsToAdd, id: { $ne: id } })
|
|
439
516
|
.update({ [attribute.joinColumn.name]: null })
|
|
517
|
+
.transacting(trx)
|
|
440
518
|
.execute();
|
|
441
519
|
}
|
|
442
520
|
|
|
@@ -449,16 +527,19 @@ const createEntityManager = (db) => {
|
|
|
449
527
|
const { target } = attribute;
|
|
450
528
|
|
|
451
529
|
// TODO: check it is an id & the entity exists (will throw due to FKs otherwise so not a big pbl in SQL)
|
|
530
|
+
const relIdsToAdd = toIds(cleanRelationData.set);
|
|
452
531
|
|
|
453
532
|
await this.createQueryBuilder(target)
|
|
454
533
|
.where({ [attribute.joinColumn.referencedColumn]: id })
|
|
455
534
|
.update({ [attribute.joinColumn.referencedColumn]: null })
|
|
535
|
+
.transacting(trx)
|
|
456
536
|
.execute();
|
|
457
537
|
|
|
458
538
|
await this.createQueryBuilder(target)
|
|
459
539
|
.update({ [attribute.joinColumn.referencedColumn]: id })
|
|
460
540
|
// NOTE: works if it is an array or a single id
|
|
461
|
-
.where({ id:
|
|
541
|
+
.where({ id: relIdsToAdd })
|
|
542
|
+
.transacting(trx)
|
|
462
543
|
.execute();
|
|
463
544
|
}
|
|
464
545
|
|
|
@@ -466,17 +547,24 @@ const createEntityManager = (db) => {
|
|
|
466
547
|
// need to set the column on the target
|
|
467
548
|
|
|
468
549
|
const { joinTable } = attribute;
|
|
469
|
-
const { joinColumn, inverseJoinColumn } =
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
550
|
+
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } =
|
|
551
|
+
joinTable;
|
|
552
|
+
|
|
553
|
+
const relsToAdd = cleanRelationData.set || cleanRelationData.connect;
|
|
554
|
+
const relIdsToadd = toIds(relsToAdd);
|
|
555
|
+
|
|
556
|
+
if (isBidirectional(attribute) && isOneToAny(attribute)) {
|
|
557
|
+
await deletePreviousOneToAnyRelations({
|
|
558
|
+
id,
|
|
559
|
+
attribute,
|
|
560
|
+
relIdsToadd,
|
|
561
|
+
db,
|
|
562
|
+
transaction: trx,
|
|
563
|
+
});
|
|
477
564
|
}
|
|
478
565
|
|
|
479
|
-
|
|
566
|
+
// prepare new relations to insert
|
|
567
|
+
const insert = relsToAdd.map((data) => {
|
|
480
568
|
return {
|
|
481
569
|
[joinColumn.name]: id,
|
|
482
570
|
[inverseJoinColumn.name]: data.id,
|
|
@@ -485,12 +573,40 @@ const createEntityManager = (db) => {
|
|
|
485
573
|
};
|
|
486
574
|
});
|
|
487
575
|
|
|
488
|
-
//
|
|
576
|
+
// add order value
|
|
577
|
+
if (hasOrderColumn(attribute)) {
|
|
578
|
+
insert.forEach((rel, idx) => {
|
|
579
|
+
rel[orderColumnName] = idx + 1;
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
// add inv_order value
|
|
583
|
+
if (hasInverseOrderColumn(attribute)) {
|
|
584
|
+
const maxResults = await db
|
|
585
|
+
.getConnection()
|
|
586
|
+
.select(inverseJoinColumn.name)
|
|
587
|
+
.max(inverseOrderColumnName, { as: 'max' })
|
|
588
|
+
.whereIn(inverseJoinColumn.name, relIdsToadd)
|
|
589
|
+
.where(joinTable.on || {})
|
|
590
|
+
.groupBy(inverseJoinColumn.name)
|
|
591
|
+
.from(joinTable.name)
|
|
592
|
+
.transacting(trx);
|
|
593
|
+
|
|
594
|
+
const maxMap = maxResults.reduce(
|
|
595
|
+
(acc, res) => Object.assign(acc, { [res[inverseJoinColumn.name]]: res.max }),
|
|
596
|
+
{}
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
insert.forEach((rel) => {
|
|
600
|
+
rel[inverseOrderColumnName] = (maxMap[rel[inverseJoinColumn.name]] || 0) + 1;
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
489
604
|
if (insert.length === 0) {
|
|
490
605
|
continue;
|
|
491
606
|
}
|
|
492
607
|
|
|
493
|
-
|
|
608
|
+
// insert new relations
|
|
609
|
+
await this.createQueryBuilder(joinTable.name).insert(insert).transacting(trx).execute();
|
|
494
610
|
}
|
|
495
611
|
}
|
|
496
612
|
},
|
|
@@ -504,8 +620,7 @@ const createEntityManager = (db) => {
|
|
|
504
620
|
* @param {object} data - data received for creation
|
|
505
621
|
*/
|
|
506
622
|
// TODO: check relation exists (handled by FKs except for polymorphics)
|
|
507
|
-
|
|
508
|
-
async updateRelations(uid, id, data) {
|
|
623
|
+
async updateRelations(uid, id, data, { transaction: trx }) {
|
|
509
624
|
const { attributes } = db.metadata.get(uid);
|
|
510
625
|
|
|
511
626
|
for (const attributeName of Object.keys(attributes)) {
|
|
@@ -514,6 +629,7 @@ const createEntityManager = (db) => {
|
|
|
514
629
|
if (attribute.type !== 'relation' || !has(attributeName, data)) {
|
|
515
630
|
continue;
|
|
516
631
|
}
|
|
632
|
+
const cleanRelationData = toAssocs(data[attributeName]);
|
|
517
633
|
|
|
518
634
|
if (attribute.relation === 'morphOne' || attribute.relation === 'morphMany') {
|
|
519
635
|
const { target, morphBy } = attribute;
|
|
@@ -529,12 +645,15 @@ const createEntityManager = (db) => {
|
|
|
529
645
|
await this.createQueryBuilder(target)
|
|
530
646
|
.update({ [idColumn.name]: null, [typeColumn.name]: null })
|
|
531
647
|
.where({ [idColumn.name]: id, [typeColumn.name]: uid })
|
|
648
|
+
.transacting(trx)
|
|
532
649
|
.execute();
|
|
533
650
|
|
|
534
|
-
if (!isNull(
|
|
651
|
+
if (!isNull(cleanRelationData.set)) {
|
|
652
|
+
const relId = toIds(cleanRelationData.set[0]);
|
|
535
653
|
await this.createQueryBuilder(target)
|
|
536
654
|
.update({ [idColumn.name]: id, [typeColumn.name]: uid })
|
|
537
|
-
.where({ id:
|
|
655
|
+
.where({ id: relId })
|
|
656
|
+
.transacting(trx)
|
|
538
657
|
.execute();
|
|
539
658
|
}
|
|
540
659
|
} else if (targetAttribute.relation === 'morphToMany') {
|
|
@@ -551,9 +670,14 @@ const createEntityManager = (db) => {
|
|
|
551
670
|
...(joinTable.on || {}),
|
|
552
671
|
field: attributeName,
|
|
553
672
|
})
|
|
673
|
+
.transacting(trx)
|
|
554
674
|
.execute();
|
|
555
675
|
|
|
556
|
-
|
|
676
|
+
if (isEmpty(cleanRelationData.set)) {
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const rows = cleanRelationData.set.map((data, idx) => ({
|
|
557
681
|
[joinColumn.name]: data.id,
|
|
558
682
|
[idColumn.name]: id,
|
|
559
683
|
[typeColumn.name]: uid,
|
|
@@ -563,11 +687,7 @@ const createEntityManager = (db) => {
|
|
|
563
687
|
field: attributeName,
|
|
564
688
|
}));
|
|
565
689
|
|
|
566
|
-
|
|
567
|
-
continue;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
await this.createQueryBuilder(joinTable.name).insert(rows).execute();
|
|
690
|
+
await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
|
|
571
691
|
}
|
|
572
692
|
|
|
573
693
|
continue;
|
|
@@ -590,29 +710,32 @@ const createEntityManager = (db) => {
|
|
|
590
710
|
[joinColumn.name]: id,
|
|
591
711
|
...(joinTable.on || {}),
|
|
592
712
|
})
|
|
713
|
+
.transacting(trx)
|
|
593
714
|
.execute();
|
|
594
715
|
|
|
595
|
-
|
|
716
|
+
if (isEmpty(cleanRelationData.set)) {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const rows = cleanRelationData.set.map((data, idx) => ({
|
|
596
721
|
[joinColumn.name]: id,
|
|
597
722
|
[idColumn.name]: data.id,
|
|
598
723
|
[typeColumn.name]: data[typeField],
|
|
599
724
|
...(joinTable.on || {}),
|
|
600
725
|
...(data.__pivot || {}),
|
|
726
|
+
order: idx + 1,
|
|
601
727
|
}));
|
|
602
728
|
|
|
603
|
-
if (isEmpty(rows)) {
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
729
|
// delete previous relations
|
|
608
730
|
await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rows, {
|
|
609
731
|
uid,
|
|
610
732
|
attributeName,
|
|
611
733
|
joinTable,
|
|
612
734
|
db,
|
|
735
|
+
transaction: trx,
|
|
613
736
|
});
|
|
614
737
|
|
|
615
|
-
await this.createQueryBuilder(joinTable.name).insert(rows).execute();
|
|
738
|
+
await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
|
|
616
739
|
|
|
617
740
|
continue;
|
|
618
741
|
}
|
|
@@ -631,55 +754,243 @@ const createEntityManager = (db) => {
|
|
|
631
754
|
await this.createQueryBuilder(target)
|
|
632
755
|
.where({ [attribute.joinColumn.referencedColumn]: id })
|
|
633
756
|
.update({ [attribute.joinColumn.referencedColumn]: null })
|
|
757
|
+
.transacting(trx)
|
|
634
758
|
.execute();
|
|
635
759
|
|
|
636
|
-
if (!isNull(
|
|
760
|
+
if (!isNull(cleanRelationData.set)) {
|
|
761
|
+
const relIdsToAdd = toIds(cleanRelationData.set);
|
|
637
762
|
await this.createQueryBuilder(target)
|
|
638
|
-
|
|
639
|
-
.where({ id: data[attributeName] })
|
|
763
|
+
.where({ id: relIdsToAdd })
|
|
640
764
|
.update({ [attribute.joinColumn.referencedColumn]: id })
|
|
765
|
+
.transacting(trx)
|
|
641
766
|
.execute();
|
|
642
767
|
}
|
|
643
768
|
}
|
|
644
769
|
|
|
645
770
|
if (attribute.joinTable) {
|
|
646
771
|
const { joinTable } = attribute;
|
|
647
|
-
const { joinColumn, inverseJoinColumn } =
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
.
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
.
|
|
655
|
-
|
|
656
|
-
if (
|
|
657
|
-
isBidirectional(attribute) &&
|
|
658
|
-
['oneToOne', 'oneToMany'].includes(attribute.relation)
|
|
659
|
-
) {
|
|
660
|
-
await this.createQueryBuilder(joinTable.name)
|
|
661
|
-
.delete()
|
|
662
|
-
.where({ [inverseJoinColumn.name]: toIds(data[attributeName]) })
|
|
663
|
-
.where(joinTable.on || {})
|
|
664
|
-
.execute();
|
|
772
|
+
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } =
|
|
773
|
+
joinTable;
|
|
774
|
+
const select = [joinColumn.name, inverseJoinColumn.name];
|
|
775
|
+
if (hasOrderColumn(attribute)) {
|
|
776
|
+
select.push(orderColumnName);
|
|
777
|
+
}
|
|
778
|
+
if (hasInverseOrderColumn(attribute)) {
|
|
779
|
+
select.push(inverseOrderColumnName);
|
|
665
780
|
}
|
|
666
781
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
782
|
+
// only delete relations
|
|
783
|
+
if (isNull(cleanRelationData.set)) {
|
|
784
|
+
await deleteRelations({ id, attribute, db, relIdsToDelete: 'all', transaction: trx });
|
|
785
|
+
} else {
|
|
786
|
+
const isPartialUpdate = !has('set', cleanRelationData);
|
|
787
|
+
let relIdsToaddOrMove;
|
|
788
|
+
|
|
789
|
+
if (isPartialUpdate) {
|
|
790
|
+
if (isAnyToOne(attribute)) {
|
|
791
|
+
cleanRelationData.connect = cleanRelationData.connect.slice(-1);
|
|
792
|
+
}
|
|
793
|
+
relIdsToaddOrMove = toIds(cleanRelationData.connect);
|
|
794
|
+
const relIdsToDelete = toIds(
|
|
795
|
+
differenceWith(isEqual, cleanRelationData.disconnect, cleanRelationData.connect)
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
if (!isEmpty(relIdsToDelete)) {
|
|
799
|
+
await deleteRelations({ id, attribute, db, relIdsToDelete, transaction: trx });
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (isEmpty(cleanRelationData.connect)) {
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Fetch current relations to handle ordering
|
|
807
|
+
let currentMovingRels;
|
|
808
|
+
if (hasOrderColumn(attribute) || hasInverseOrderColumn(attribute)) {
|
|
809
|
+
currentMovingRels = await this.createQueryBuilder(joinTable.name)
|
|
810
|
+
.select(select)
|
|
811
|
+
.where({
|
|
812
|
+
[joinColumn.name]: id,
|
|
813
|
+
[inverseJoinColumn.name]: { $in: relIdsToaddOrMove },
|
|
814
|
+
})
|
|
815
|
+
.where(joinTable.on || {})
|
|
816
|
+
.transacting(trx)
|
|
817
|
+
.execute();
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// prepare relations to insert
|
|
821
|
+
const insert = cleanRelationData.connect.map((relToAdd) => ({
|
|
670
822
|
[joinColumn.name]: id,
|
|
671
|
-
[inverseJoinColumn.name]:
|
|
823
|
+
[inverseJoinColumn.name]: relToAdd.id,
|
|
672
824
|
...(joinTable.on || {}),
|
|
673
|
-
...(
|
|
674
|
-
};
|
|
675
|
-
|
|
825
|
+
...(relToAdd.__pivot || {}),
|
|
826
|
+
}));
|
|
827
|
+
|
|
828
|
+
// add order value
|
|
829
|
+
if (hasOrderColumn(attribute)) {
|
|
830
|
+
const orderMax = (
|
|
831
|
+
await this.createQueryBuilder(joinTable.name)
|
|
832
|
+
.max(orderColumnName)
|
|
833
|
+
.where({ [joinColumn.name]: id })
|
|
834
|
+
.where(joinTable.on || {})
|
|
835
|
+
.first()
|
|
836
|
+
.transacting(trx)
|
|
837
|
+
.execute()
|
|
838
|
+
).max;
|
|
839
|
+
|
|
840
|
+
insert.forEach((row, idx) => {
|
|
841
|
+
row[orderColumnName] = orderMax + idx + 1;
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// add inv order value
|
|
846
|
+
if (hasInverseOrderColumn(attribute)) {
|
|
847
|
+
const nonExistingRelsIds = difference(
|
|
848
|
+
relIdsToaddOrMove,
|
|
849
|
+
map(inverseJoinColumn.name, currentMovingRels)
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
const maxResults = await db
|
|
853
|
+
.getConnection()
|
|
854
|
+
.select(inverseJoinColumn.name)
|
|
855
|
+
.max(inverseOrderColumnName, { as: 'max' })
|
|
856
|
+
.whereIn(inverseJoinColumn.name, nonExistingRelsIds)
|
|
857
|
+
.where(joinTable.on || {})
|
|
858
|
+
.groupBy(inverseJoinColumn.name)
|
|
859
|
+
.from(joinTable.name)
|
|
860
|
+
.transacting(trx);
|
|
861
|
+
|
|
862
|
+
const maxMap = maxResults.reduce(
|
|
863
|
+
(acc, res) => Object.assign(acc, { [res[inverseJoinColumn.name]]: res.max }),
|
|
864
|
+
{}
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
insert.forEach((row) => {
|
|
868
|
+
row[inverseOrderColumnName] = (maxMap[row[inverseJoinColumn.name]] || 0) + 1;
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// insert rows
|
|
873
|
+
const query = this.createQueryBuilder(joinTable.name)
|
|
874
|
+
.insert(insert)
|
|
875
|
+
.onConflict(joinTable.pivotColumns)
|
|
876
|
+
.transacting(trx);
|
|
877
|
+
|
|
878
|
+
if (hasOrderColumn(attribute)) {
|
|
879
|
+
query.merge([orderColumnName]);
|
|
880
|
+
} else {
|
|
881
|
+
query.ignore();
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
await query.execute();
|
|
885
|
+
|
|
886
|
+
// remove gap between orders
|
|
887
|
+
await cleanOrderColumns({ attribute, db, id, transaction: trx });
|
|
888
|
+
} else {
|
|
889
|
+
if (isAnyToOne(attribute)) {
|
|
890
|
+
cleanRelationData.set = cleanRelationData.set.slice(-1);
|
|
891
|
+
}
|
|
892
|
+
// overwrite all relations
|
|
893
|
+
relIdsToaddOrMove = toIds(cleanRelationData.set);
|
|
894
|
+
await deleteRelations({
|
|
895
|
+
id,
|
|
896
|
+
attribute,
|
|
897
|
+
db,
|
|
898
|
+
relIdsToDelete: 'all',
|
|
899
|
+
relIdsToNotDelete: relIdsToaddOrMove,
|
|
900
|
+
transaction: trx,
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
if (isEmpty(cleanRelationData.set)) {
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const insert = cleanRelationData.set.map((relToAdd) => ({
|
|
908
|
+
[joinColumn.name]: id,
|
|
909
|
+
[inverseJoinColumn.name]: relToAdd.id,
|
|
910
|
+
...(joinTable.on || {}),
|
|
911
|
+
...(relToAdd.__pivot || {}),
|
|
912
|
+
}));
|
|
913
|
+
|
|
914
|
+
// add order value
|
|
915
|
+
if (hasOrderColumn(attribute)) {
|
|
916
|
+
insert.forEach((row, idx) => {
|
|
917
|
+
row[orderColumnName] = idx + 1;
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// add inv order value
|
|
922
|
+
if (hasInverseOrderColumn(attribute)) {
|
|
923
|
+
const existingRels = await this.createQueryBuilder(joinTable.name)
|
|
924
|
+
.select(inverseJoinColumn.name)
|
|
925
|
+
.where({
|
|
926
|
+
[joinColumn.name]: id,
|
|
927
|
+
[inverseJoinColumn.name]: { $in: relIdsToaddOrMove },
|
|
928
|
+
})
|
|
929
|
+
.where(joinTable.on || {})
|
|
930
|
+
.transacting(trx)
|
|
931
|
+
.execute();
|
|
932
|
+
|
|
933
|
+
const nonExistingRelsIds = difference(
|
|
934
|
+
relIdsToaddOrMove,
|
|
935
|
+
map(inverseJoinColumn.name, existingRels)
|
|
936
|
+
);
|
|
937
|
+
|
|
938
|
+
const maxResults = await db
|
|
939
|
+
.getConnection()
|
|
940
|
+
.select(inverseJoinColumn.name)
|
|
941
|
+
.max(inverseOrderColumnName, { as: 'max' })
|
|
942
|
+
.whereIn(inverseJoinColumn.name, nonExistingRelsIds)
|
|
943
|
+
.where(joinTable.on || {})
|
|
944
|
+
.groupBy(inverseJoinColumn.name)
|
|
945
|
+
.from(joinTable.name)
|
|
946
|
+
.transacting(trx);
|
|
947
|
+
|
|
948
|
+
const maxMap = maxResults.reduce(
|
|
949
|
+
(acc, res) => Object.assign(acc, { [res[inverseJoinColumn.name]]: res.max }),
|
|
950
|
+
{}
|
|
951
|
+
);
|
|
952
|
+
|
|
953
|
+
insert.forEach((row) => {
|
|
954
|
+
row[inverseOrderColumnName] = (maxMap[row[inverseJoinColumn.name]] || 0) + 1;
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// insert rows
|
|
959
|
+
const query = this.createQueryBuilder(joinTable.name)
|
|
960
|
+
.insert(insert)
|
|
961
|
+
.onConflict(joinTable.pivotColumns)
|
|
962
|
+
.transacting(trx);
|
|
963
|
+
|
|
964
|
+
if (hasOrderColumn(attribute)) {
|
|
965
|
+
query.merge([orderColumnName]);
|
|
966
|
+
} else {
|
|
967
|
+
query.ignore();
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
await query.execute();
|
|
971
|
+
}
|
|
676
972
|
|
|
677
|
-
//
|
|
678
|
-
if (
|
|
679
|
-
|
|
973
|
+
// Delete the previous relations for oneToAny relations
|
|
974
|
+
if (isBidirectional(attribute) && isOneToAny(attribute)) {
|
|
975
|
+
await deletePreviousOneToAnyRelations({
|
|
976
|
+
id,
|
|
977
|
+
attribute,
|
|
978
|
+
relIdsToadd: relIdsToaddOrMove,
|
|
979
|
+
db,
|
|
980
|
+
transaction: trx,
|
|
981
|
+
});
|
|
680
982
|
}
|
|
681
983
|
|
|
682
|
-
|
|
984
|
+
// Delete the previous relations for anyToOne relations
|
|
985
|
+
if (isBidirectional(attribute) && isAnyToOne(attribute)) {
|
|
986
|
+
await deletePreviousAnyToOneRelations({
|
|
987
|
+
id,
|
|
988
|
+
attribute,
|
|
989
|
+
relIdToadd: relIdsToaddOrMove[0],
|
|
990
|
+
db,
|
|
991
|
+
transaction: trx,
|
|
992
|
+
});
|
|
993
|
+
}
|
|
683
994
|
}
|
|
684
995
|
}
|
|
685
996
|
}
|
|
@@ -694,8 +1005,7 @@ const createEntityManager = (db) => {
|
|
|
694
1005
|
* @param {Metadata} metadata - model metadta
|
|
695
1006
|
* @param {ID} id - entity ID
|
|
696
1007
|
*/
|
|
697
|
-
|
|
698
|
-
async deleteRelations(uid, id) {
|
|
1008
|
+
async deleteRelations(uid, id, { transaction: trx }) {
|
|
699
1009
|
const { attributes } = db.metadata.get(uid);
|
|
700
1010
|
|
|
701
1011
|
for (const attributeName of Object.keys(attributes)) {
|
|
@@ -724,6 +1034,7 @@ const createEntityManager = (db) => {
|
|
|
724
1034
|
await this.createQueryBuilder(target)
|
|
725
1035
|
.update({ [idColumn.name]: null, [typeColumn.name]: null })
|
|
726
1036
|
.where({ [idColumn.name]: id, [typeColumn.name]: uid })
|
|
1037
|
+
.transacting(trx)
|
|
727
1038
|
.execute();
|
|
728
1039
|
} else if (targetAttribute.relation === 'morphToMany') {
|
|
729
1040
|
const { joinTable } = targetAttribute;
|
|
@@ -739,6 +1050,7 @@ const createEntityManager = (db) => {
|
|
|
739
1050
|
...(joinTable.on || {}),
|
|
740
1051
|
field: attributeName,
|
|
741
1052
|
})
|
|
1053
|
+
.transacting(trx)
|
|
742
1054
|
.execute();
|
|
743
1055
|
}
|
|
744
1056
|
|
|
@@ -767,6 +1079,7 @@ const createEntityManager = (db) => {
|
|
|
767
1079
|
[joinColumn.name]: id,
|
|
768
1080
|
...(joinTable.on || {}),
|
|
769
1081
|
})
|
|
1082
|
+
.transacting(trx)
|
|
770
1083
|
.execute();
|
|
771
1084
|
|
|
772
1085
|
continue;
|
|
@@ -791,18 +1104,12 @@ const createEntityManager = (db) => {
|
|
|
791
1104
|
await this.createQueryBuilder(target)
|
|
792
1105
|
.where({ [attribute.joinColumn.referencedColumn]: id })
|
|
793
1106
|
.update({ [attribute.joinColumn.referencedColumn]: null })
|
|
1107
|
+
.transacting(trx)
|
|
794
1108
|
.execute();
|
|
795
1109
|
}
|
|
796
1110
|
|
|
797
1111
|
if (attribute.joinTable) {
|
|
798
|
-
|
|
799
|
-
const { joinColumn } = joinTable;
|
|
800
|
-
|
|
801
|
-
await this.createQueryBuilder(joinTable.name)
|
|
802
|
-
.delete()
|
|
803
|
-
.where({ [joinColumn.name]: id })
|
|
804
|
-
.where(joinTable.on || {})
|
|
805
|
-
.execute();
|
|
1112
|
+
await deleteRelations({ id, attribute, db, relIdsToDelete: 'all', transaction: trx });
|
|
806
1113
|
}
|
|
807
1114
|
}
|
|
808
1115
|
},
|