@strapi/database 4.4.0-rc.0 → 4.5.0-alpha.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/index.js +545 -33
- package/lib/metadata/index.js +1 -0
- package/lib/metadata/relations.js +62 -3
- package/lib/query/helpers/join.js +9 -8
- package/lib/query/helpers/where.js +3 -2
- package/lib/query/query-builder.js +73 -23
- package/lib/tests/knex-utils.test.e2e.js +33 -0
- package/lib/utils/knex.js +12 -0
- package/package.json +2 -2
|
@@ -12,19 +12,35 @@ const {
|
|
|
12
12
|
isEmpty,
|
|
13
13
|
isArray,
|
|
14
14
|
isNull,
|
|
15
|
+
uniqBy,
|
|
16
|
+
differenceBy,
|
|
17
|
+
groupBy,
|
|
15
18
|
} = require('lodash/fp');
|
|
16
19
|
const types = require('../types');
|
|
17
20
|
const { createField } = require('../fields');
|
|
18
21
|
const { createQueryBuilder } = require('../query');
|
|
19
22
|
const { createRepository } = require('./entity-repository');
|
|
20
|
-
const { isBidirectional, isOneToAny } = require('../metadata/relations');
|
|
21
23
|
const { deleteRelatedMorphOneRelationsAfterMorphToManyUpdate } = require('./morph-relations');
|
|
24
|
+
const {
|
|
25
|
+
isBidirectional,
|
|
26
|
+
isOneToAny,
|
|
27
|
+
isManyToAny,
|
|
28
|
+
isAnyToOne,
|
|
29
|
+
isAnyToMany,
|
|
30
|
+
} = require('../metadata/relations');
|
|
22
31
|
|
|
23
32
|
const toId = (value) => value.id || value;
|
|
24
33
|
const toIds = (value) => castArray(value || []).map(toId);
|
|
25
34
|
|
|
26
35
|
const isValidId = (value) => isString(value) || isInteger(value);
|
|
27
36
|
const toAssocs = (data) => {
|
|
37
|
+
if (data?.connect || data?.disconnect || (isPlainObject(data) && !data.id)) {
|
|
38
|
+
return {
|
|
39
|
+
connect: toAssocs(data.connect),
|
|
40
|
+
disconnect: toAssocs(data.disconnect),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
28
44
|
return castArray(data)
|
|
29
45
|
.filter((datum) => !isNil(datum))
|
|
30
46
|
.map((datum) => {
|
|
@@ -466,17 +482,56 @@ const createEntityManager = (db) => {
|
|
|
466
482
|
// need to set the column on the target
|
|
467
483
|
|
|
468
484
|
const { joinTable } = attribute;
|
|
469
|
-
const { joinColumn, inverseJoinColumn } =
|
|
485
|
+
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } =
|
|
486
|
+
joinTable;
|
|
487
|
+
const select = [joinColumn.name];
|
|
488
|
+
if (isAnyToMany(attribute)) {
|
|
489
|
+
select.push(orderColumnName);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const cleanRelationData = toAssocs(data[attributeName]);
|
|
493
|
+
const relsToAdd = uniqBy('id', cleanRelationData.connect || cleanRelationData);
|
|
494
|
+
const relIdsToadd = toIds(relsToAdd);
|
|
495
|
+
|
|
496
|
+
// need to delete the previous relations for oneToAny relations
|
|
497
|
+
if (isBidirectional(attribute) && isOneToAny(attribute)) {
|
|
498
|
+
// update orders for previous oneToAny relations that will be deleted if it has order (oneToMany)
|
|
499
|
+
if (isAnyToMany(attribute)) {
|
|
500
|
+
const currentRelsToDelete = await this.createQueryBuilder(joinTable.name)
|
|
501
|
+
.select(select)
|
|
502
|
+
.where({
|
|
503
|
+
[inverseJoinColumn.name]: relIdsToadd,
|
|
504
|
+
[joinColumn.name]: { $ne: id },
|
|
505
|
+
})
|
|
506
|
+
.where(joinTable.on || {})
|
|
507
|
+
.execute();
|
|
470
508
|
|
|
471
|
-
|
|
509
|
+
currentRelsToDelete.sort((a, b) => b[orderColumnName] - a[orderColumnName]);
|
|
510
|
+
|
|
511
|
+
for (const relToDelete of currentRelsToDelete) {
|
|
512
|
+
if (relToDelete[orderColumnName] !== null) {
|
|
513
|
+
await this.createQueryBuilder(joinTable.name)
|
|
514
|
+
.decrement(orderColumnName, 1)
|
|
515
|
+
.where({
|
|
516
|
+
[joinColumn.name]: relToDelete[joinColumn.name],
|
|
517
|
+
[orderColumnName]: { $gt: relToDelete[orderColumnName] },
|
|
518
|
+
})
|
|
519
|
+
.where(joinTable.on || {})
|
|
520
|
+
.execute();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// delete previous oneToAny relations
|
|
472
526
|
await this.createQueryBuilder(joinTable.name)
|
|
473
527
|
.delete()
|
|
474
|
-
.where({ [inverseJoinColumn.name]:
|
|
528
|
+
.where({ [inverseJoinColumn.name]: relIdsToadd })
|
|
475
529
|
.where(joinTable.on || {})
|
|
476
530
|
.execute();
|
|
477
531
|
}
|
|
478
532
|
|
|
479
|
-
|
|
533
|
+
// prepare new relations to insert
|
|
534
|
+
const insert = relsToAdd.map((data) => {
|
|
480
535
|
return {
|
|
481
536
|
[joinColumn.name]: id,
|
|
482
537
|
[inverseJoinColumn.name]: data.id,
|
|
@@ -485,11 +540,38 @@ const createEntityManager = (db) => {
|
|
|
485
540
|
};
|
|
486
541
|
});
|
|
487
542
|
|
|
488
|
-
//
|
|
543
|
+
// add order value when relevant
|
|
544
|
+
if (isAnyToMany(attribute)) {
|
|
545
|
+
insert.forEach((rel, idx) => {
|
|
546
|
+
rel[orderColumnName] = idx + 1;
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
// add inv_order value when relevant
|
|
550
|
+
if (isBidirectional(attribute) && isManyToAny(attribute)) {
|
|
551
|
+
const maxMap = {};
|
|
552
|
+
await Promise.all(
|
|
553
|
+
relIdsToadd.map(async (relId) => {
|
|
554
|
+
const { max } = await this.createQueryBuilder(joinTable.name)
|
|
555
|
+
.max(inverseOrderColumnName)
|
|
556
|
+
.where({ [inverseJoinColumn.name]: relId })
|
|
557
|
+
.where(joinTable.on || {})
|
|
558
|
+
.first()
|
|
559
|
+
.execute();
|
|
560
|
+
|
|
561
|
+
maxMap[relId] = max;
|
|
562
|
+
})
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
insert.forEach((rel) => {
|
|
566
|
+
rel[inverseOrderColumnName] = maxMap[rel[inverseJoinColumn.name]] + 1;
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
489
570
|
if (insert.length === 0) {
|
|
490
571
|
continue;
|
|
491
572
|
}
|
|
492
573
|
|
|
574
|
+
// insert new relations
|
|
493
575
|
await this.createQueryBuilder(joinTable.name).insert(insert).execute();
|
|
494
576
|
}
|
|
495
577
|
}
|
|
@@ -644,42 +726,434 @@ const createEntityManager = (db) => {
|
|
|
644
726
|
|
|
645
727
|
if (attribute.joinTable) {
|
|
646
728
|
const { joinTable } = attribute;
|
|
647
|
-
const { joinColumn, inverseJoinColumn } =
|
|
729
|
+
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } =
|
|
730
|
+
joinTable;
|
|
731
|
+
const select = [joinColumn.name, inverseJoinColumn.name];
|
|
732
|
+
if (isAnyToMany(attribute)) {
|
|
733
|
+
select.push(orderColumnName);
|
|
734
|
+
}
|
|
735
|
+
if (isBidirectional(attribute) && isManyToAny(attribute)) {
|
|
736
|
+
select.push(inverseOrderColumnName);
|
|
737
|
+
}
|
|
648
738
|
|
|
649
|
-
//
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
739
|
+
// only delete relations
|
|
740
|
+
if (isNull(data[attributeName])) {
|
|
741
|
+
// INVERSE ORDER UPDATE
|
|
742
|
+
if (isBidirectional(attribute) && isManyToAny(attribute)) {
|
|
743
|
+
let lastId = 0;
|
|
744
|
+
let done = false;
|
|
745
|
+
const batchSize = 100;
|
|
746
|
+
while (!done) {
|
|
747
|
+
const relsToDelete = await this.createQueryBuilder(joinTable.name)
|
|
748
|
+
.select(select)
|
|
749
|
+
.where({
|
|
750
|
+
[joinColumn.name]: id,
|
|
751
|
+
id: { $gt: lastId },
|
|
752
|
+
})
|
|
753
|
+
.where(joinTable.on || {})
|
|
754
|
+
.orderBy('id')
|
|
755
|
+
.limit(batchSize)
|
|
756
|
+
.execute();
|
|
757
|
+
// TODO: cannot put pivot here...
|
|
758
|
+
done = relsToDelete.length < batchSize;
|
|
759
|
+
lastId = relsToDelete[relsToDelete.length - 1]?.id;
|
|
760
|
+
|
|
761
|
+
const updateInverseOrderPromises = [];
|
|
762
|
+
for (const relToDelete of relsToDelete) {
|
|
763
|
+
if (relToDelete[inverseOrderColumnName] !== null) {
|
|
764
|
+
const updatePromise = this.createQueryBuilder(joinTable.name)
|
|
765
|
+
.decrement(inverseOrderColumnName, 1)
|
|
766
|
+
.where({
|
|
767
|
+
[inverseJoinColumn.name]: relToDelete[inverseJoinColumn.name],
|
|
768
|
+
[inverseOrderColumnName]: { $gt: relToDelete[inverseOrderColumnName] },
|
|
769
|
+
})
|
|
770
|
+
.where(joinTable.on || {})
|
|
771
|
+
.execute();
|
|
772
|
+
updateInverseOrderPromises.push(updatePromise);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
await Promise.all(updateInverseOrderPromises);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
655
779
|
|
|
656
|
-
if (
|
|
657
|
-
isBidirectional(attribute) &&
|
|
658
|
-
['oneToOne', 'oneToMany'].includes(attribute.relation)
|
|
659
|
-
) {
|
|
660
780
|
await this.createQueryBuilder(joinTable.name)
|
|
661
781
|
.delete()
|
|
662
|
-
.where({ [
|
|
782
|
+
.where({ [joinColumn.name]: id })
|
|
663
783
|
.where(joinTable.on || {})
|
|
664
784
|
.execute();
|
|
665
|
-
}
|
|
785
|
+
} else {
|
|
786
|
+
const cleanRelationData = toAssocs(data[attributeName]);
|
|
787
|
+
const isPartialUpdate =
|
|
788
|
+
has('connect', cleanRelationData) || has('disconnect', cleanRelationData);
|
|
789
|
+
let relIdsToaddOrMove;
|
|
790
|
+
|
|
791
|
+
if (isPartialUpdate) {
|
|
792
|
+
// does not support pivot
|
|
793
|
+
let connect = uniqBy('id', cleanRelationData.connect || []);
|
|
794
|
+
let disconnect = uniqBy('id', cleanRelationData.disconnect || []);
|
|
795
|
+
if (isAnyToOne(attribute)) {
|
|
796
|
+
connect = connect.slice(-1);
|
|
797
|
+
disconnect = [];
|
|
798
|
+
}
|
|
799
|
+
relIdsToaddOrMove = toIds(connect);
|
|
800
|
+
// DELETE relations in disconnect
|
|
801
|
+
const relIdsToDelete = toIds(differenceBy('id', disconnect, connect));
|
|
802
|
+
|
|
803
|
+
// UPDATE RELEVANT ORDERS
|
|
804
|
+
if (
|
|
805
|
+
isAnyToMany(attribute) ||
|
|
806
|
+
(isBidirectional(attribute) && isManyToAny(attribute))
|
|
807
|
+
) {
|
|
808
|
+
const relsToDelete = await this.createQueryBuilder(joinTable.name)
|
|
809
|
+
.select(select)
|
|
810
|
+
.where({
|
|
811
|
+
[joinColumn.name]: id,
|
|
812
|
+
[inverseJoinColumn.name]: { $in: relIdsToDelete },
|
|
813
|
+
})
|
|
814
|
+
.where(joinTable.on || {})
|
|
815
|
+
.execute();
|
|
816
|
+
|
|
817
|
+
// ORDER UPDATE
|
|
818
|
+
if (isAnyToMany(attribute)) {
|
|
819
|
+
// sort by order DESC so that the order updates are done in the correct order
|
|
820
|
+
// avoiding one to interfere with the others
|
|
821
|
+
relsToDelete.sort((a, b) => b[orderColumnName] - a[orderColumnName]);
|
|
822
|
+
|
|
823
|
+
for (const relToDelete of relsToDelete) {
|
|
824
|
+
if (relToDelete[orderColumnName] !== null) {
|
|
825
|
+
await this.createQueryBuilder(joinTable.name)
|
|
826
|
+
.decrement(orderColumnName, 1)
|
|
827
|
+
.where({
|
|
828
|
+
[joinColumn.name]: id,
|
|
829
|
+
[orderColumnName]: { $gt: relToDelete[orderColumnName] },
|
|
830
|
+
})
|
|
831
|
+
.where(joinTable.on || {})
|
|
832
|
+
.execute();
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// INVERSE ORDER UPDATE
|
|
838
|
+
if (isBidirectional(attribute) && isManyToAny(attribute)) {
|
|
839
|
+
const updateInverseOrderPromises = [];
|
|
840
|
+
for (const relToDelete of relsToDelete) {
|
|
841
|
+
if (relToDelete[inverseOrderColumnName] !== null) {
|
|
842
|
+
const updatePromise = this.createQueryBuilder(joinTable.name)
|
|
843
|
+
.decrement(inverseOrderColumnName, 1)
|
|
844
|
+
.where({
|
|
845
|
+
[inverseJoinColumn.name]: relToDelete[inverseJoinColumn.name],
|
|
846
|
+
[inverseOrderColumnName]: { $gt: relToDelete[inverseOrderColumnName] },
|
|
847
|
+
})
|
|
848
|
+
.where(joinTable.on || {})
|
|
849
|
+
.execute();
|
|
850
|
+
updateInverseOrderPromises.push(updatePromise);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
await Promise.all(updateInverseOrderPromises);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// DELETE
|
|
859
|
+
await this.createQueryBuilder(joinTable.name)
|
|
860
|
+
.delete()
|
|
861
|
+
.where({
|
|
862
|
+
[joinColumn.name]: id,
|
|
863
|
+
[inverseJoinColumn.name]: { $in: relIdsToDelete },
|
|
864
|
+
})
|
|
865
|
+
.where(joinTable.on || {})
|
|
866
|
+
.execute();
|
|
666
867
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
868
|
+
// add/move
|
|
869
|
+
let max;
|
|
870
|
+
const currentMovingRels = await this.createQueryBuilder(joinTable.name)
|
|
871
|
+
.select(select)
|
|
872
|
+
.where({
|
|
873
|
+
[joinColumn.name]: id,
|
|
874
|
+
[inverseJoinColumn.name]: { $in: relIdsToaddOrMove },
|
|
875
|
+
})
|
|
876
|
+
.where(joinTable.on || {})
|
|
877
|
+
.execute();
|
|
878
|
+
const currentMovingRelsMap = groupBy(inverseJoinColumn.name, currentMovingRels);
|
|
879
|
+
|
|
880
|
+
if (isAnyToMany(attribute)) {
|
|
881
|
+
max = (
|
|
882
|
+
await this.createQueryBuilder(joinTable.name)
|
|
883
|
+
.max(orderColumnName)
|
|
884
|
+
.where({ [joinColumn.name]: id })
|
|
885
|
+
.where(joinTable.on || {})
|
|
886
|
+
.first()
|
|
887
|
+
.execute()
|
|
888
|
+
).max;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
for (const relToAddOrMove of connect) {
|
|
892
|
+
// const currentRel = currentMovingRelsMap[relToAddOrMove.id]?.[0];
|
|
893
|
+
const currentRel = currentMovingRelsMap[relToAddOrMove.id]?.[0];
|
|
894
|
+
if (currentRel && isAnyToMany(attribute)) {
|
|
895
|
+
const currentOrderIsNull = currentRel[orderColumnName] === null;
|
|
896
|
+
if (!currentOrderIsNull) {
|
|
897
|
+
await this.createQueryBuilder(joinTable.name)
|
|
898
|
+
.decrement(orderColumnName, 1)
|
|
899
|
+
.where({
|
|
900
|
+
[joinColumn.name]: id,
|
|
901
|
+
[orderColumnName]: { $gt: currentRel[orderColumnName] },
|
|
902
|
+
})
|
|
903
|
+
.where(joinTable.on || {})
|
|
904
|
+
.execute();
|
|
905
|
+
|
|
906
|
+
currentMovingRels.forEach((rel) => {
|
|
907
|
+
if (rel[orderColumnName] > currentRel[orderColumnName]) {
|
|
908
|
+
rel[orderColumnName] -= 1;
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
await this.createQueryBuilder(joinTable.name)
|
|
914
|
+
.update({
|
|
915
|
+
[orderColumnName]: currentOrderIsNull ? max + 1 : max,
|
|
916
|
+
})
|
|
917
|
+
.where({
|
|
918
|
+
[joinColumn.name]: id,
|
|
919
|
+
[inverseJoinColumn.name]: relToAddOrMove.id,
|
|
920
|
+
})
|
|
921
|
+
.where(joinTable.on || {})
|
|
922
|
+
.execute();
|
|
923
|
+
} else if (!currentRel) {
|
|
924
|
+
const insert = {
|
|
925
|
+
[joinColumn.name]: id,
|
|
926
|
+
[inverseJoinColumn.name]: relToAddOrMove.id,
|
|
927
|
+
...(relToAddOrMove.__pivot || {}),
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
if (isAnyToMany(attribute)) {
|
|
931
|
+
insert[orderColumnName] = max + 1;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (isBidirectional(attribute) && isManyToAny(attribute)) {
|
|
935
|
+
const { max: reverseMax } = await this.createQueryBuilder(joinTable.name)
|
|
936
|
+
.max(inverseOrderColumnName)
|
|
937
|
+
.where({ [inverseJoinColumn.name]: relToAddOrMove.id })
|
|
938
|
+
.where(joinTable.on || {})
|
|
939
|
+
.first()
|
|
940
|
+
.execute();
|
|
941
|
+
|
|
942
|
+
insert[inverseOrderColumnName] = reverseMax + 1;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
await this.createQueryBuilder(joinTable.name).insert(insert).execute();
|
|
946
|
+
max += 1;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
} else {
|
|
950
|
+
// overwrite all relations
|
|
951
|
+
const relsToAdd = uniqBy('id', cleanRelationData);
|
|
952
|
+
relIdsToaddOrMove = toIds(relsToAdd);
|
|
953
|
+
|
|
954
|
+
// UPDATE RELEVANT ORDERS BEFORE DELETE
|
|
955
|
+
if (isAnyToMany(attribute) || isManyToAny(attribute)) {
|
|
956
|
+
let lastId = 0;
|
|
957
|
+
let done = false;
|
|
958
|
+
const batchSize = 100;
|
|
959
|
+
while (!done) {
|
|
960
|
+
const relsToDelete = await this.createQueryBuilder(joinTable.name)
|
|
961
|
+
.select(select)
|
|
962
|
+
.where({
|
|
963
|
+
[joinColumn.name]: id,
|
|
964
|
+
[inverseJoinColumn.name]: { $notIn: relIdsToaddOrMove },
|
|
965
|
+
id: { $gt: lastId },
|
|
966
|
+
})
|
|
967
|
+
.where(joinTable.on || {})
|
|
968
|
+
.orderBy('id')
|
|
969
|
+
.limit(batchSize)
|
|
970
|
+
.execute();
|
|
971
|
+
|
|
972
|
+
done = relsToDelete.length < batchSize;
|
|
973
|
+
lastId = relsToDelete[relsToDelete.length - 1]?.id;
|
|
974
|
+
|
|
975
|
+
// ORDER UPDATE
|
|
976
|
+
if (isAnyToMany(attribute)) {
|
|
977
|
+
// sort by order DESC so that the order updates are done in the correct order
|
|
978
|
+
// avoiding one to interfere with the others
|
|
979
|
+
relsToDelete.sort((a, b) => b[orderColumnName] - a[orderColumnName]);
|
|
980
|
+
|
|
981
|
+
for (const relToDelete of relsToDelete) {
|
|
982
|
+
if (relToDelete[orderColumnName] !== null) {
|
|
983
|
+
await this.createQueryBuilder(joinTable.name)
|
|
984
|
+
.decrement(orderColumnName, 1)
|
|
985
|
+
.where({
|
|
986
|
+
[joinColumn.name]: id,
|
|
987
|
+
[orderColumnName]: { $gt: relToDelete[orderColumnName] },
|
|
988
|
+
})
|
|
989
|
+
.where(joinTable.on || {})
|
|
990
|
+
// manque le pivot ici
|
|
991
|
+
.execute();
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// INVERSE ORDER UPDATE
|
|
997
|
+
if (isBidirectional(attribute) && isManyToAny(attribute)) {
|
|
998
|
+
const updateInverseOrderPromises = [];
|
|
999
|
+
for (const relToDelete of relsToDelete) {
|
|
1000
|
+
if (relToDelete[inverseOrderColumnName] !== null) {
|
|
1001
|
+
const updatePromise = this.createQueryBuilder(joinTable.name)
|
|
1002
|
+
.decrement(inverseOrderColumnName, 1)
|
|
1003
|
+
.where({
|
|
1004
|
+
[inverseJoinColumn.name]: relToDelete[inverseJoinColumn.name],
|
|
1005
|
+
[inverseOrderColumnName]: { $gt: relToDelete[inverseOrderColumnName] },
|
|
1006
|
+
})
|
|
1007
|
+
.where(joinTable.on || {})
|
|
1008
|
+
.execute();
|
|
1009
|
+
updateInverseOrderPromises.push(updatePromise);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
await Promise.all(updateInverseOrderPromises);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// DELETE
|
|
1019
|
+
await this.createQueryBuilder(joinTable.name)
|
|
1020
|
+
.delete()
|
|
1021
|
+
.where({
|
|
1022
|
+
[joinColumn.name]: id,
|
|
1023
|
+
[inverseJoinColumn.name]: { $notIn: relIdsToaddOrMove },
|
|
1024
|
+
})
|
|
1025
|
+
.where(joinTable.on || {})
|
|
1026
|
+
.execute();
|
|
676
1027
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1028
|
+
const currentMovingRels = await this.createQueryBuilder(joinTable.name)
|
|
1029
|
+
.select(select)
|
|
1030
|
+
.where({
|
|
1031
|
+
[joinColumn.name]: id,
|
|
1032
|
+
[inverseJoinColumn.name]: { $in: relIdsToaddOrMove },
|
|
1033
|
+
})
|
|
1034
|
+
.where(joinTable.on || {})
|
|
1035
|
+
.execute();
|
|
1036
|
+
const currentMovingRelsMap = groupBy(inverseJoinColumn.name, currentMovingRels);
|
|
1037
|
+
|
|
1038
|
+
let max = 0;
|
|
1039
|
+
for (const relToAdd of relsToAdd) {
|
|
1040
|
+
const currentRel = currentMovingRelsMap[relToAdd.id]?.[0];
|
|
1041
|
+
|
|
1042
|
+
if (currentRel && isAnyToMany(attribute)) {
|
|
1043
|
+
const update = { [orderColumnName]: max + 1 };
|
|
1044
|
+
await this.createQueryBuilder(joinTable.name)
|
|
1045
|
+
.update(update)
|
|
1046
|
+
.where({
|
|
1047
|
+
[joinColumn.name]: id,
|
|
1048
|
+
[inverseJoinColumn.name]: relToAdd.id,
|
|
1049
|
+
})
|
|
1050
|
+
.where(joinTable.on || {})
|
|
1051
|
+
.execute();
|
|
1052
|
+
} else if (!currentRel) {
|
|
1053
|
+
const insert = {
|
|
1054
|
+
[joinColumn.name]: id,
|
|
1055
|
+
[inverseJoinColumn.name]: relToAdd.id,
|
|
1056
|
+
...(relToAdd.__pivot || {}),
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
if (isAnyToMany(attribute)) {
|
|
1060
|
+
insert[orderColumnName] = max + 1;
|
|
1061
|
+
}
|
|
1062
|
+
// can be optimized in one query
|
|
1063
|
+
if (isBidirectional(attribute) && isManyToAny(attribute)) {
|
|
1064
|
+
const { max: reverseMax } = await this.createQueryBuilder(joinTable.name)
|
|
1065
|
+
.max(inverseOrderColumnName)
|
|
1066
|
+
.where({ [inverseJoinColumn.name]: id })
|
|
1067
|
+
.where(joinTable.on || {})
|
|
1068
|
+
.first()
|
|
1069
|
+
.execute();
|
|
1070
|
+
|
|
1071
|
+
insert[inverseOrderColumnName] = reverseMax + 1;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
await this.createQueryBuilder(joinTable.name).insert(insert).execute();
|
|
1075
|
+
}
|
|
1076
|
+
max += 1;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// Delete the previous relations for oneToAny relations
|
|
1081
|
+
if (isBidirectional(attribute) && isOneToAny(attribute)) {
|
|
1082
|
+
// update orders for previous oneToAny relations that will be deleted if it has order (oneToMany)
|
|
1083
|
+
if (isAnyToMany(attribute)) {
|
|
1084
|
+
const currentRelsToDelete = await this.createQueryBuilder(joinTable.name)
|
|
1085
|
+
.select(select)
|
|
1086
|
+
.where({
|
|
1087
|
+
[inverseJoinColumn.name]: relIdsToaddOrMove,
|
|
1088
|
+
[joinColumn.name]: { $ne: id },
|
|
1089
|
+
})
|
|
1090
|
+
.where(joinTable.on || {})
|
|
1091
|
+
.execute();
|
|
1092
|
+
|
|
1093
|
+
currentRelsToDelete.sort((a, b) => b[orderColumnName] - a[orderColumnName]);
|
|
1094
|
+
|
|
1095
|
+
for (const relToDelete of currentRelsToDelete) {
|
|
1096
|
+
if (relToDelete[orderColumnName] !== null) {
|
|
1097
|
+
await this.createQueryBuilder(joinTable.name)
|
|
1098
|
+
.decrement(orderColumnName, 1)
|
|
1099
|
+
.where({
|
|
1100
|
+
[joinColumn.name]: relToDelete[joinColumn.name],
|
|
1101
|
+
[orderColumnName]: { $gt: relToDelete[orderColumnName] },
|
|
1102
|
+
})
|
|
1103
|
+
.where(joinTable.on || {})
|
|
1104
|
+
.execute();
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// delete previous oneToAny relations
|
|
1110
|
+
await this.createQueryBuilder(joinTable.name)
|
|
1111
|
+
.delete()
|
|
1112
|
+
.where({
|
|
1113
|
+
[inverseJoinColumn.name]: relIdsToaddOrMove,
|
|
1114
|
+
[joinColumn.name]: { $ne: id },
|
|
1115
|
+
})
|
|
1116
|
+
.where(joinTable.on || {})
|
|
1117
|
+
.execute();
|
|
680
1118
|
}
|
|
681
1119
|
|
|
682
|
-
|
|
1120
|
+
// Delete the previous relations for anyToOne relations
|
|
1121
|
+
if (isBidirectional(attribute) && isAnyToOne(attribute)) {
|
|
1122
|
+
// update orders for previous anyToOne relations that will be deleted if it has order (manyToOne)
|
|
1123
|
+
if (isManyToAny(attribute)) {
|
|
1124
|
+
const currentRelsToDelete = await this.createQueryBuilder(joinTable.name)
|
|
1125
|
+
.select(select)
|
|
1126
|
+
.where({
|
|
1127
|
+
[joinColumn.name]: id,
|
|
1128
|
+
[inverseJoinColumn.name]: { $notIn: relIdsToaddOrMove },
|
|
1129
|
+
})
|
|
1130
|
+
.where(joinTable.on || {})
|
|
1131
|
+
.execute();
|
|
1132
|
+
|
|
1133
|
+
for (const relToDelete of currentRelsToDelete) {
|
|
1134
|
+
if (relToDelete[inverseOrderColumnName] !== null) {
|
|
1135
|
+
await this.createQueryBuilder(joinTable.name)
|
|
1136
|
+
.decrement(inverseOrderColumnName, 1)
|
|
1137
|
+
.where({
|
|
1138
|
+
[inverseJoinColumn.name]: relToDelete[inverseJoinColumn.name],
|
|
1139
|
+
[inverseOrderColumnName]: { $gt: relToDelete[inverseOrderColumnName] },
|
|
1140
|
+
})
|
|
1141
|
+
.where(joinTable.on || {})
|
|
1142
|
+
.execute();
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// delete previous oneToAny relations
|
|
1148
|
+
await this.createQueryBuilder(joinTable.name)
|
|
1149
|
+
.delete()
|
|
1150
|
+
.where({
|
|
1151
|
+
[joinColumn.name]: id,
|
|
1152
|
+
[inverseJoinColumn.name]: { $notIn: relIdsToaddOrMove },
|
|
1153
|
+
})
|
|
1154
|
+
.where(joinTable.on || {})
|
|
1155
|
+
.execute();
|
|
1156
|
+
}
|
|
683
1157
|
}
|
|
684
1158
|
}
|
|
685
1159
|
}
|
|
@@ -796,7 +1270,45 @@ const createEntityManager = (db) => {
|
|
|
796
1270
|
|
|
797
1271
|
if (attribute.joinTable) {
|
|
798
1272
|
const { joinTable } = attribute;
|
|
799
|
-
const { joinColumn } = joinTable;
|
|
1273
|
+
const { joinColumn, inverseJoinColumn, inverseOrderColumnName } = joinTable;
|
|
1274
|
+
|
|
1275
|
+
// INVERSE ORDER UPDATE
|
|
1276
|
+
if (isBidirectional(attribute) && isManyToAny(attribute)) {
|
|
1277
|
+
let lastId = 0;
|
|
1278
|
+
let done = false;
|
|
1279
|
+
const batchSize = 100;
|
|
1280
|
+
while (!done) {
|
|
1281
|
+
const relsToDelete = await this.createQueryBuilder(joinTable.name)
|
|
1282
|
+
.select(inverseJoinColumn.name, inverseOrderColumnName)
|
|
1283
|
+
.where({
|
|
1284
|
+
[joinColumn.name]: id,
|
|
1285
|
+
id: { $gt: lastId },
|
|
1286
|
+
})
|
|
1287
|
+
.where(joinTable.on || {})
|
|
1288
|
+
.orderBy('id')
|
|
1289
|
+
.limit(batchSize)
|
|
1290
|
+
.execute();
|
|
1291
|
+
done = relsToDelete.length < batchSize;
|
|
1292
|
+
lastId = relsToDelete[relsToDelete.length - 1]?.id;
|
|
1293
|
+
|
|
1294
|
+
const updateInverseOrderPromises = [];
|
|
1295
|
+
for (const relToDelete of relsToDelete) {
|
|
1296
|
+
if (relToDelete[inverseOrderColumnName] !== null) {
|
|
1297
|
+
const updatePromise = this.createQueryBuilder(joinTable.name)
|
|
1298
|
+
.decrement(inverseOrderColumnName, 1)
|
|
1299
|
+
.where({
|
|
1300
|
+
[inverseJoinColumn.name]: relToDelete[inverseJoinColumn.name],
|
|
1301
|
+
[inverseOrderColumnName]: { $gt: relToDelete[inverseOrderColumnName] },
|
|
1302
|
+
})
|
|
1303
|
+
.where(joinTable.on || {})
|
|
1304
|
+
.execute();
|
|
1305
|
+
updateInverseOrderPromises.push(updatePromise);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
await Promise.all(updateInverseOrderPromises);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
800
1312
|
|
|
801
1313
|
await this.createQueryBuilder(joinTable.name)
|
|
802
1314
|
.delete()
|
package/lib/metadata/index.js
CHANGED
|
@@ -10,6 +10,9 @@ const hasInversedBy = _.has('inversedBy');
|
|
|
10
10
|
const hasMappedBy = _.has('mappedBy');
|
|
11
11
|
|
|
12
12
|
const isOneToAny = (attribute) => ['oneToOne', 'oneToMany'].includes(attribute.relation);
|
|
13
|
+
const isManyToAny = (attribute) => ['manyToMany', 'manyToOne'].includes(attribute.relation);
|
|
14
|
+
const isAnyToOne = (attribute) => ['oneToOne', 'manyToOne'].includes(attribute.relation);
|
|
15
|
+
const isAnyToMany = (attribute) => ['oneToMany', 'manyToMany'].includes(attribute.relation);
|
|
13
16
|
const isBidirectional = (attribute) => hasInversedBy(attribute) || hasMappedBy(attribute);
|
|
14
17
|
const isOwner = (attribute) => !isBidirectional(attribute) || hasInversedBy(attribute);
|
|
15
18
|
const shouldUseJoinTable = (attribute) => attribute.useJoinTable !== false;
|
|
@@ -398,12 +401,20 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
398
401
|
const joinColumnName = _.snakeCase(`${meta.singularName}_id`);
|
|
399
402
|
let inverseJoinColumnName = _.snakeCase(`${targetMeta.singularName}_id`);
|
|
400
403
|
|
|
401
|
-
// if relation is
|
|
404
|
+
// if relation is self referencing
|
|
402
405
|
if (joinColumnName === inverseJoinColumnName) {
|
|
403
406
|
inverseJoinColumnName = `inv_${inverseJoinColumnName}`;
|
|
404
407
|
}
|
|
405
408
|
|
|
406
|
-
|
|
409
|
+
const orderColumnName = _.snakeCase(`${meta.singularName}_order`);
|
|
410
|
+
let inverseOrderColumnName = _.snakeCase(`${targetMeta.singularName}_order`);
|
|
411
|
+
|
|
412
|
+
// if relation is self referencing
|
|
413
|
+
if (attribute.relation === 'manyToMany' && joinColumnName === inverseJoinColumnName) {
|
|
414
|
+
inverseOrderColumnName = `inv_${inverseOrderColumnName}`;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const metadataSchema = {
|
|
407
418
|
uid: joinTableName,
|
|
408
419
|
tableName: joinTableName,
|
|
409
420
|
attributes: {
|
|
@@ -450,7 +461,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
450
461
|
onDelete: 'CASCADE',
|
|
451
462
|
},
|
|
452
463
|
],
|
|
453
|
-
}
|
|
464
|
+
};
|
|
454
465
|
|
|
455
466
|
const joinTable = {
|
|
456
467
|
name: joinTableName,
|
|
@@ -464,6 +475,43 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
464
475
|
},
|
|
465
476
|
};
|
|
466
477
|
|
|
478
|
+
// order
|
|
479
|
+
if (isAnyToMany(attribute)) {
|
|
480
|
+
metadataSchema.attributes[orderColumnName] = {
|
|
481
|
+
type: 'integer',
|
|
482
|
+
column: {
|
|
483
|
+
unsigned: true,
|
|
484
|
+
defaultTo: null,
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
metadataSchema.indexes.push({
|
|
488
|
+
name: `${joinTableName}_order_fk`,
|
|
489
|
+
columns: [orderColumnName],
|
|
490
|
+
});
|
|
491
|
+
joinTable.orderColumnName = orderColumnName;
|
|
492
|
+
joinTable.orderBy = { [orderColumnName]: 'asc' };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// inv order
|
|
496
|
+
if (isBidirectional(attribute) && isManyToAny(attribute)) {
|
|
497
|
+
metadataSchema.attributes[inverseOrderColumnName] = {
|
|
498
|
+
type: 'integer',
|
|
499
|
+
column: {
|
|
500
|
+
unsigned: true,
|
|
501
|
+
defaultTo: null,
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
metadataSchema.indexes.push({
|
|
506
|
+
name: `${joinTableName}_order_inv_fk`,
|
|
507
|
+
columns: [inverseOrderColumnName],
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
joinTable.inverseOrderColumnName = inverseOrderColumnName;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
metadata.add(metadataSchema);
|
|
514
|
+
|
|
467
515
|
attribute.joinTable = joinTable;
|
|
468
516
|
|
|
469
517
|
if (isBidirectional(attribute)) {
|
|
@@ -480,6 +528,14 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
480
528
|
joinColumn: joinTable.inverseJoinColumn,
|
|
481
529
|
inverseJoinColumn: joinTable.joinColumn,
|
|
482
530
|
};
|
|
531
|
+
|
|
532
|
+
if (isManyToAny(attribute)) {
|
|
533
|
+
inverseAttribute.joinTable.orderColumnName = inverseOrderColumnName;
|
|
534
|
+
inverseAttribute.joinTable.orderBy = { [inverseOrderColumnName]: 'asc' };
|
|
535
|
+
}
|
|
536
|
+
if (isAnyToMany(attribute)) {
|
|
537
|
+
inverseAttribute.joinTable.inverseOrderColumnName = orderColumnName;
|
|
538
|
+
}
|
|
483
539
|
}
|
|
484
540
|
};
|
|
485
541
|
|
|
@@ -488,4 +544,7 @@ module.exports = {
|
|
|
488
544
|
|
|
489
545
|
isBidirectional,
|
|
490
546
|
isOneToAny,
|
|
547
|
+
isManyToAny,
|
|
548
|
+
isAnyToOne,
|
|
549
|
+
isAnyToMany,
|
|
491
550
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const createPivotJoin = (
|
|
3
|
+
const createPivotJoin = (ctx, { alias, refAlias, joinTable, targetMeta }) => {
|
|
4
|
+
const { qb } = ctx;
|
|
4
5
|
const joinAlias = qb.getAlias();
|
|
5
6
|
qb.join({
|
|
6
7
|
alias: joinAlias,
|
|
@@ -11,10 +12,10 @@ const createPivotJoin = (qb, joinTable, alias, tragetMeta) => {
|
|
|
11
12
|
on: joinTable.on,
|
|
12
13
|
});
|
|
13
14
|
|
|
14
|
-
const subAlias = qb.getAlias();
|
|
15
|
+
const subAlias = refAlias || qb.getAlias();
|
|
15
16
|
qb.join({
|
|
16
17
|
alias: subAlias,
|
|
17
|
-
referencedTable:
|
|
18
|
+
referencedTable: targetMeta.tableName,
|
|
18
19
|
referencedColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
19
20
|
rootColumn: joinTable.inverseJoinColumn.name,
|
|
20
21
|
rootTable: joinAlias,
|
|
@@ -23,22 +24,22 @@ const createPivotJoin = (qb, joinTable, alias, tragetMeta) => {
|
|
|
23
24
|
return subAlias;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
const createJoin = (ctx, { alias, attributeName, attribute }) => {
|
|
27
|
+
const createJoin = (ctx, { alias, refAlias, attributeName, attribute }) => {
|
|
27
28
|
const { db, qb } = ctx;
|
|
28
29
|
|
|
29
30
|
if (attribute.type !== 'relation') {
|
|
30
31
|
throw new Error(`Cannot join on non relational field ${attributeName}`);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
const
|
|
34
|
+
const targetMeta = db.metadata.get(attribute.target);
|
|
34
35
|
|
|
35
36
|
const { joinColumn } = attribute;
|
|
36
37
|
|
|
37
38
|
if (joinColumn) {
|
|
38
|
-
const subAlias = qb.getAlias();
|
|
39
|
+
const subAlias = refAlias || qb.getAlias();
|
|
39
40
|
qb.join({
|
|
40
41
|
alias: subAlias,
|
|
41
|
-
referencedTable:
|
|
42
|
+
referencedTable: targetMeta.tableName,
|
|
42
43
|
referencedColumn: joinColumn.referencedColumn,
|
|
43
44
|
rootColumn: joinColumn.name,
|
|
44
45
|
rootTable: alias,
|
|
@@ -48,7 +49,7 @@ const createJoin = (ctx, { alias, attributeName, attribute }) => {
|
|
|
48
49
|
|
|
49
50
|
const { joinTable } = attribute;
|
|
50
51
|
if (joinTable) {
|
|
51
|
-
return createPivotJoin(
|
|
52
|
+
return createPivotJoin(ctx, { alias, refAlias, joinTable, targetMeta });
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
return alias;
|
|
@@ -6,6 +6,7 @@ const types = require('../../types');
|
|
|
6
6
|
const { createField } = require('../../fields');
|
|
7
7
|
const { createJoin } = require('./join');
|
|
8
8
|
const { toColumnName } = require('./transform');
|
|
9
|
+
const { isKnexQuery } = require('../../utils/knex');
|
|
9
10
|
|
|
10
11
|
const GROUP_OPERATORS = ['$and', '$or'];
|
|
11
12
|
const OPERATORS = [
|
|
@@ -206,12 +207,12 @@ const applyOperator = (qb, column, operator, value) => {
|
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
case '$in': {
|
|
209
|
-
qb.whereIn(column, _.castArray(value));
|
|
210
|
+
qb.whereIn(column, isKnexQuery(value) ? value : _.castArray(value));
|
|
210
211
|
break;
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
case '$notIn': {
|
|
214
|
-
qb.whereNotIn(column, _.castArray(value));
|
|
215
|
+
qb.whereNotIn(column, isKnexQuery(value) ? value : _.castArray(value));
|
|
215
216
|
break;
|
|
216
217
|
}
|
|
217
218
|
|
|
@@ -4,33 +4,38 @@ const _ = require('lodash/fp');
|
|
|
4
4
|
|
|
5
5
|
const helpers = require('./helpers');
|
|
6
6
|
|
|
7
|
-
const createQueryBuilder = (uid, db) => {
|
|
7
|
+
const createQueryBuilder = (uid, db, initialState = {}) => {
|
|
8
8
|
const meta = db.metadata.get(uid);
|
|
9
9
|
const { tableName } = meta;
|
|
10
10
|
|
|
11
|
-
const state =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
11
|
+
const state = _.defaults(
|
|
12
|
+
{
|
|
13
|
+
type: 'select',
|
|
14
|
+
select: [],
|
|
15
|
+
count: null,
|
|
16
|
+
max: null,
|
|
17
|
+
first: false,
|
|
18
|
+
data: null,
|
|
19
|
+
where: [],
|
|
20
|
+
joins: [],
|
|
21
|
+
populate: null,
|
|
22
|
+
limit: null,
|
|
23
|
+
offset: null,
|
|
24
|
+
transaction: null,
|
|
25
|
+
forUpdate: false,
|
|
26
|
+
orderBy: [],
|
|
27
|
+
groupBy: [],
|
|
28
|
+
increments: [],
|
|
29
|
+
decrements: [],
|
|
30
|
+
aliasCounter: 0,
|
|
31
|
+
},
|
|
32
|
+
initialState
|
|
33
|
+
);
|
|
28
34
|
|
|
29
|
-
let counter = 0;
|
|
30
35
|
const getAlias = () => {
|
|
31
|
-
const alias = `t${
|
|
36
|
+
const alias = `t${state.aliasCounter}`;
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
state.aliasCounter += 1;
|
|
34
39
|
|
|
35
40
|
return alias;
|
|
36
41
|
};
|
|
@@ -40,6 +45,10 @@ const createQueryBuilder = (uid, db) => {
|
|
|
40
45
|
getAlias,
|
|
41
46
|
state,
|
|
42
47
|
|
|
48
|
+
clone() {
|
|
49
|
+
return createQueryBuilder(uid, db, state);
|
|
50
|
+
},
|
|
51
|
+
|
|
43
52
|
select(args) {
|
|
44
53
|
state.type = 'select';
|
|
45
54
|
state.select = _.uniq(_.castArray(args));
|
|
@@ -77,6 +86,20 @@ const createQueryBuilder = (uid, db) => {
|
|
|
77
86
|
return this;
|
|
78
87
|
},
|
|
79
88
|
|
|
89
|
+
increment(column, amount = 1) {
|
|
90
|
+
state.type = 'update';
|
|
91
|
+
state.increments.push({ column, amount });
|
|
92
|
+
|
|
93
|
+
return this;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
decrement(column, amount = 1) {
|
|
97
|
+
state.type = 'update';
|
|
98
|
+
state.decrements.push({ column, amount });
|
|
99
|
+
|
|
100
|
+
return this;
|
|
101
|
+
},
|
|
102
|
+
|
|
80
103
|
count(count = 'id') {
|
|
81
104
|
state.type = 'count';
|
|
82
105
|
state.count = count;
|
|
@@ -195,7 +218,24 @@ const createQueryBuilder = (uid, db) => {
|
|
|
195
218
|
},
|
|
196
219
|
|
|
197
220
|
join(join) {
|
|
198
|
-
|
|
221
|
+
if (!join.targetField) {
|
|
222
|
+
state.joins.push(join);
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const model = db.metadata.get(uid);
|
|
227
|
+
const attribute = model.attributes[join.targetField];
|
|
228
|
+
|
|
229
|
+
helpers.createJoin(
|
|
230
|
+
{ db, qb: this },
|
|
231
|
+
{
|
|
232
|
+
alias: this.alias,
|
|
233
|
+
refAlias: join.alias,
|
|
234
|
+
attributeName: join.targetField,
|
|
235
|
+
attribute,
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
|
|
199
239
|
return this;
|
|
200
240
|
},
|
|
201
241
|
|
|
@@ -325,7 +365,9 @@ const createQueryBuilder = (uid, db) => {
|
|
|
325
365
|
break;
|
|
326
366
|
}
|
|
327
367
|
case 'update': {
|
|
328
|
-
|
|
368
|
+
if (state.data) {
|
|
369
|
+
qb.update(state.data);
|
|
370
|
+
}
|
|
329
371
|
break;
|
|
330
372
|
}
|
|
331
373
|
case 'delete': {
|
|
@@ -350,6 +392,14 @@ const createQueryBuilder = (uid, db) => {
|
|
|
350
392
|
qb.forUpdate();
|
|
351
393
|
}
|
|
352
394
|
|
|
395
|
+
if (!_.isEmpty(state.increments)) {
|
|
396
|
+
state.increments.forEach((incr) => qb.increment(incr.column, incr.amount));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!_.isEmpty(state.decrements)) {
|
|
400
|
+
state.decrements.forEach((decr) => qb.decrement(decr.column, decr.amount));
|
|
401
|
+
}
|
|
402
|
+
|
|
353
403
|
if (state.limit) {
|
|
354
404
|
qb.limit(state.limit);
|
|
355
405
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
|
4
|
+
const { isKnexQuery } = require('../utils/knex');
|
|
5
|
+
|
|
6
|
+
let strapi;
|
|
7
|
+
|
|
8
|
+
describe('knex', () => {
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
strapi = await createStrapiInstance();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterAll(async () => {
|
|
14
|
+
await strapi.destroy();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('isKnexQuery', () => {
|
|
18
|
+
test('knex query: true', () => {
|
|
19
|
+
const res = isKnexQuery(strapi.db.connection('strapi_core_store_settings'));
|
|
20
|
+
expect(res).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('knex raw: true', () => {
|
|
24
|
+
const res = isKnexQuery(strapi.db.connection.raw('SELECT * FROM strapi_core_store_settings'));
|
|
25
|
+
expect(res).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test.each([[''], [{}], [[]], [2], [new Date()]])('%s: false', (value) => {
|
|
29
|
+
const res = isKnexQuery(value);
|
|
30
|
+
expect(res).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const KnexBuilder = require('knex/lib/query/querybuilder');
|
|
4
|
+
const KnexRaw = require('knex/lib/raw');
|
|
5
|
+
|
|
6
|
+
const isKnexQuery = (value) => {
|
|
7
|
+
return value instanceof KnexBuilder || value instanceof KnexRaw;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
isKnexQuery,
|
|
12
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/database",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0-alpha.0",
|
|
4
4
|
"description": "Strapi's database layer",
|
|
5
5
|
"homepage": "https://strapi.io",
|
|
6
6
|
"bugs": {
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"node": ">=14.19.1 <=18.x.x",
|
|
43
43
|
"npm": ">=6.0.0"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "c9a98c4dbcf3c4f2a449f8d96e7cbe4cd9b1e0f5"
|
|
46
46
|
}
|