@strapi/database 4.15.0-alpha.0 → 4.15.1
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/README.md +3 -0
- package/dist/connection.d.ts +3 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/dialects/dialect.d.ts +27 -0
- package/dist/dialects/dialect.d.ts.map +1 -0
- package/dist/dialects/index.d.ts +5 -0
- package/dist/dialects/index.d.ts.map +1 -0
- package/dist/dialects/mysql/constants.d.ts +3 -0
- package/dist/dialects/mysql/constants.d.ts.map +1 -0
- package/dist/dialects/mysql/database-inspector.d.ts +12 -0
- package/dist/dialects/mysql/database-inspector.d.ts.map +1 -0
- package/dist/dialects/mysql/index.d.ts +20 -0
- package/dist/dialects/mysql/index.d.ts.map +1 -0
- package/dist/dialects/mysql/schema-inspector.d.ts +13 -0
- package/dist/dialects/mysql/schema-inspector.d.ts.map +1 -0
- package/dist/dialects/postgresql/index.d.ts +14 -0
- package/dist/dialects/postgresql/index.d.ts.map +1 -0
- package/dist/dialects/postgresql/schema-inspector.d.ts +14 -0
- package/dist/dialects/postgresql/schema-inspector.d.ts.map +1 -0
- package/dist/dialects/sqlite/index.d.ts +19 -0
- package/dist/dialects/sqlite/index.d.ts.map +1 -0
- package/dist/dialects/sqlite/schema-inspector.d.ts +13 -0
- package/dist/dialects/sqlite/schema-inspector.d.ts.map +1 -0
- package/dist/entity-manager/entity-repository.d.ts +4 -0
- package/dist/entity-manager/entity-repository.d.ts.map +1 -0
- package/dist/entity-manager/index.d.ts +5 -0
- package/dist/entity-manager/index.d.ts.map +1 -0
- package/dist/entity-manager/morph-relations.d.ts +13 -0
- package/dist/entity-manager/morph-relations.d.ts.map +1 -0
- package/dist/entity-manager/regular-relations.d.ts +83 -0
- package/dist/entity-manager/regular-relations.d.ts.map +1 -0
- package/dist/entity-manager/relations/cloning/regular-relations.d.ts +17 -0
- package/dist/entity-manager/relations/cloning/regular-relations.d.ts.map +1 -0
- package/dist/entity-manager/relations-orderer.d.ts +73 -0
- package/dist/entity-manager/relations-orderer.d.ts.map +1 -0
- package/dist/entity-manager/types.d.ts +97 -0
- package/dist/entity-manager/types.d.ts.map +1 -0
- package/dist/errors/database.d.ts +5 -0
- package/dist/errors/database.d.ts.map +1 -0
- package/dist/errors/index.d.ts +8 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/invalid-date.d.ts +5 -0
- package/dist/errors/invalid-date.d.ts.map +1 -0
- package/dist/errors/invalid-datetime.d.ts +5 -0
- package/dist/errors/invalid-datetime.d.ts.map +1 -0
- package/dist/errors/invalid-relation.d.ts +5 -0
- package/dist/errors/invalid-relation.d.ts.map +1 -0
- package/dist/errors/invalid-time.d.ts +5 -0
- package/dist/errors/invalid-time.d.ts.map +1 -0
- package/dist/errors/not-null.d.ts +7 -0
- package/dist/errors/not-null.d.ts.map +1 -0
- package/dist/fields/biginteger.d.ts +4 -0
- package/dist/fields/biginteger.d.ts.map +1 -0
- package/dist/fields/boolean.d.ts +6 -0
- package/dist/fields/boolean.d.ts.map +1 -0
- package/dist/fields/date.d.ts +6 -0
- package/dist/fields/date.d.ts.map +1 -0
- package/dist/fields/datetime.d.ts +6 -0
- package/dist/fields/datetime.d.ts.map +1 -0
- package/dist/fields/field.d.ts +7 -0
- package/dist/fields/field.d.ts.map +1 -0
- package/dist/fields/index.d.ts +4 -0
- package/dist/fields/index.d.ts.map +1 -0
- package/dist/fields/json.d.ts +6 -0
- package/dist/fields/json.d.ts.map +1 -0
- package/dist/fields/number.d.ts +6 -0
- package/dist/fields/number.d.ts.map +1 -0
- package/dist/fields/shared/parsers.d.ts +4 -0
- package/dist/fields/shared/parsers.d.ts.map +1 -0
- package/dist/fields/string.d.ts +6 -0
- package/dist/fields/string.d.ts.map +1 -0
- package/dist/fields/time.d.ts +6 -0
- package/dist/fields/time.d.ts.map +1 -0
- package/dist/fields/timestamp.d.ts +6 -0
- package/dist/fields/timestamp.d.ts.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6189 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +6157 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lifecycles/index.d.ts +17 -0
- package/dist/lifecycles/index.d.ts.map +1 -0
- package/dist/lifecycles/subscribers/index.d.ts +5 -0
- package/dist/lifecycles/subscribers/index.d.ts.map +1 -0
- package/dist/lifecycles/subscribers/models-lifecycles.d.ts +6 -0
- package/dist/lifecycles/subscribers/models-lifecycles.d.ts.map +1 -0
- package/dist/lifecycles/subscribers/timestamps.d.ts +3 -0
- package/dist/lifecycles/subscribers/timestamps.d.ts.map +1 -0
- package/dist/lifecycles/types.d.ts +25 -0
- package/dist/lifecycles/types.d.ts.map +1 -0
- package/dist/metadata/index.d.ts +10 -0
- package/dist/metadata/index.d.ts.map +1 -0
- package/dist/metadata/metadata.d.ts +22 -0
- package/dist/metadata/metadata.d.ts.map +1 -0
- package/dist/metadata/relations.d.ts +16 -0
- package/dist/metadata/relations.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +12 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/storage.d.ts +15 -0
- package/dist/migrations/storage.d.ts.map +1 -0
- package/dist/query/helpers/index.d.ts +8 -0
- package/dist/query/helpers/index.d.ts.map +1 -0
- package/dist/query/helpers/join.d.ts +30 -0
- package/dist/query/helpers/join.d.ts.map +1 -0
- package/dist/query/helpers/order-by.d.ts +14 -0
- package/dist/query/helpers/order-by.d.ts.map +1 -0
- package/dist/query/helpers/populate/apply.d.ts +11 -0
- package/dist/query/helpers/populate/apply.d.ts.map +1 -0
- package/dist/query/helpers/populate/index.d.ts +4 -0
- package/dist/query/helpers/populate/index.d.ts.map +1 -0
- package/dist/query/helpers/populate/process.d.ts +24 -0
- package/dist/query/helpers/populate/process.d.ts.map +1 -0
- package/dist/query/helpers/search.d.ts +4 -0
- package/dist/query/helpers/search.d.ts.map +1 -0
- package/dist/query/helpers/streams/index.d.ts +2 -0
- package/dist/query/helpers/streams/index.d.ts.map +1 -0
- package/dist/query/helpers/streams/readable.d.ts +39 -0
- package/dist/query/helpers/streams/readable.d.ts.map +1 -0
- package/dist/query/helpers/transform.d.ts +8 -0
- package/dist/query/helpers/transform.d.ts.map +1 -0
- package/dist/query/helpers/where.d.ts +19 -0
- package/dist/query/helpers/where.d.ts.map +1 -0
- package/dist/query/index.d.ts +3 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/query-builder.d.ts +79 -0
- package/dist/query/query-builder.d.ts.map +1 -0
- package/dist/query/types.d.ts +8 -0
- package/dist/query/types.d.ts.map +1 -0
- package/dist/schema/builder.d.ts +33 -0
- package/dist/schema/builder.d.ts.map +1 -0
- package/dist/schema/diff.d.ts +7 -0
- package/dist/schema/diff.d.ts.map +1 -0
- package/dist/schema/index.d.ts +20 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/schema.d.ts +4 -0
- package/dist/schema/schema.d.ts.map +1 -0
- package/dist/schema/storage.d.ts +10 -0
- package/dist/schema/storage.d.ts.map +1 -0
- package/dist/schema/types.d.ts +103 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/transaction-context.d.ts +22 -0
- package/dist/transaction-context.d.ts.map +1 -0
- package/dist/types/index.d.ts +169 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/content-types.d.ts +13 -0
- package/dist/utils/content-types.d.ts.map +1 -0
- package/dist/utils/knex.d.ts +12 -0
- package/dist/utils/knex.d.ts.map +1 -0
- package/dist/utils/types.d.ts +10 -0
- package/dist/utils/types.d.ts.map +1 -0
- package/dist/validations/index.d.ts +6 -0
- package/dist/validations/index.d.ts.map +1 -0
- package/dist/validations/relations/bidirectional.d.ts +12 -0
- package/dist/validations/relations/bidirectional.d.ts.map +1 -0
- package/dist/validations/relations/index.d.ts +7 -0
- package/dist/validations/relations/index.d.ts.map +1 -0
- package/package.json +21 -9
- package/.eslintignore +0 -2
- package/.eslintrc.js +0 -4
- package/jest.config.js +0 -6
- package/lib/__tests__/index.test.js +0 -93
- package/lib/__tests__/lifecycles.test.js +0 -55
- package/lib/connection.js +0 -64
- package/lib/dialects/dialect.js +0 -63
- package/lib/dialects/index.js +0 -53
- package/lib/dialects/mysql/constants.js +0 -6
- package/lib/dialects/mysql/database-inspector.js +0 -37
- package/lib/dialects/mysql/index.js +0 -92
- package/lib/dialects/mysql/schema-inspector.js +0 -234
- package/lib/dialects/postgresql/index.js +0 -65
- package/lib/dialects/postgresql/schema-inspector.js +0 -283
- package/lib/dialects/sqlite/index.js +0 -87
- package/lib/dialects/sqlite/schema-inspector.js +0 -151
- package/lib/entity-manager/__tests__/relations-orderer.test.js +0 -186
- package/lib/entity-manager/__tests__/sort-connect-array.test.js +0 -79
- package/lib/entity-manager/entity-repository.js +0 -164
- package/lib/entity-manager/index.js +0 -1385
- package/lib/entity-manager/morph-relations.js +0 -63
- package/lib/entity-manager/regular-relations.js +0 -506
- package/lib/entity-manager/relations/cloning/regular-relations.js +0 -76
- package/lib/entity-manager/relations-orderer.js +0 -225
- package/lib/errors/database.js +0 -12
- package/lib/errors/index.js +0 -17
- package/lib/errors/invalid-date.js +0 -14
- package/lib/errors/invalid-datetime.js +0 -14
- package/lib/errors/invalid-relation.js +0 -14
- package/lib/errors/invalid-time.js +0 -14
- package/lib/errors/not-null.js +0 -15
- package/lib/fields/biginteger.js +0 -17
- package/lib/fields/boolean.js +0 -39
- package/lib/fields/date.js +0 -16
- package/lib/fields/datetime.js +0 -19
- package/lib/fields/field.js +0 -17
- package/lib/fields/index.d.ts +0 -9
- package/lib/fields/index.js +0 -50
- package/lib/fields/json.js +0 -21
- package/lib/fields/number.js +0 -23
- package/lib/fields/shared/parsers.js +0 -71
- package/lib/fields/string.js +0 -17
- package/lib/fields/time.js +0 -17
- package/lib/fields/timestamp.js +0 -19
- package/lib/index.d.ts +0 -198
- package/lib/index.js +0 -129
- package/lib/lifecycles/index.d.ts +0 -51
- package/lib/lifecycles/index.js +0 -90
- package/lib/lifecycles/subscribers/index.d.ts +0 -11
- package/lib/lifecycles/subscribers/models-lifecycles.js +0 -19
- package/lib/lifecycles/subscribers/timestamps.js +0 -65
- package/lib/metadata/index.js +0 -244
- package/lib/metadata/relations.js +0 -578
- package/lib/migrations/index.d.ts +0 -9
- package/lib/migrations/index.js +0 -75
- package/lib/migrations/storage.js +0 -44
- package/lib/query/helpers/index.js +0 -11
- package/lib/query/helpers/join.js +0 -96
- package/lib/query/helpers/order-by.js +0 -70
- package/lib/query/helpers/populate/apply.js +0 -664
- package/lib/query/helpers/populate/index.js +0 -9
- package/lib/query/helpers/populate/process.js +0 -102
- package/lib/query/helpers/search.js +0 -84
- package/lib/query/helpers/streams/index.js +0 -5
- package/lib/query/helpers/streams/readable.js +0 -174
- package/lib/query/helpers/transform.js +0 -84
- package/lib/query/helpers/where.js +0 -365
- package/lib/query/index.js +0 -7
- package/lib/query/query-builder.js +0 -514
- package/lib/schema/__tests__/schema-diff.test.js +0 -231
- package/lib/schema/builder.js +0 -386
- package/lib/schema/diff.js +0 -399
- package/lib/schema/index.d.ts +0 -49
- package/lib/schema/index.js +0 -94
- package/lib/schema/schema.js +0 -202
- package/lib/schema/storage.js +0 -76
- package/lib/transaction-context.js +0 -68
- package/lib/types/index.d.ts +0 -6
- package/lib/types/index.js +0 -35
- package/lib/utils/content-types.js +0 -40
- package/lib/utils/knex.js +0 -22
- package/lib/validations/index.js +0 -20
- package/lib/validations/relations/bidirectional.js +0 -89
- package/lib/validations/relations/index.js +0 -14
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { groupBy, pipe, mapValues, map, isEmpty } = require('lodash/fp');
|
|
4
|
-
const { createQueryBuilder } = require('../query');
|
|
5
|
-
|
|
6
|
-
const getMorphToManyRowsLinkedToMorphOne = (rows, { uid, attributeName, typeColumn, db }) =>
|
|
7
|
-
rows.filter((row) => {
|
|
8
|
-
const relatedType = row[typeColumn.name];
|
|
9
|
-
const field = row.field;
|
|
10
|
-
|
|
11
|
-
const targetAttribute = db.metadata.get(relatedType).attributes[field];
|
|
12
|
-
|
|
13
|
-
// ensure targeted field is the right one + check if it is a morphOne
|
|
14
|
-
return (
|
|
15
|
-
targetAttribute?.target === uid &&
|
|
16
|
-
targetAttribute?.morphBy === attributeName &&
|
|
17
|
-
targetAttribute?.relation === 'morphOne'
|
|
18
|
-
);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (
|
|
22
|
-
rows,
|
|
23
|
-
{ uid, attributeName, joinTable, db, transaction: trx }
|
|
24
|
-
) => {
|
|
25
|
-
const { morphColumn } = joinTable;
|
|
26
|
-
const { idColumn, typeColumn } = morphColumn;
|
|
27
|
-
|
|
28
|
-
const morphOneRows = getMorphToManyRowsLinkedToMorphOne(rows, {
|
|
29
|
-
uid,
|
|
30
|
-
attributeName,
|
|
31
|
-
typeColumn,
|
|
32
|
-
db,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const groupByType = groupBy(typeColumn.name);
|
|
36
|
-
const groupByField = groupBy('field');
|
|
37
|
-
|
|
38
|
-
const typeAndFieldIdsGrouped = pipe(groupByType, mapValues(groupByField))(morphOneRows);
|
|
39
|
-
|
|
40
|
-
const orWhere = [];
|
|
41
|
-
|
|
42
|
-
for (const [type, v] of Object.entries(typeAndFieldIdsGrouped)) {
|
|
43
|
-
for (const [field, arr] of Object.entries(v)) {
|
|
44
|
-
orWhere.push({
|
|
45
|
-
[typeColumn.name]: type,
|
|
46
|
-
field,
|
|
47
|
-
[idColumn.name]: { $in: map(idColumn.name, arr) },
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!isEmpty(orWhere)) {
|
|
53
|
-
await createQueryBuilder(joinTable.name, db)
|
|
54
|
-
.delete()
|
|
55
|
-
.where({ $or: orWhere })
|
|
56
|
-
.transacting(trx)
|
|
57
|
-
.execute();
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
module.exports = {
|
|
62
|
-
deleteRelatedMorphOneRelationsAfterMorphToManyUpdate,
|
|
63
|
-
};
|
|
@@ -1,506 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { randomBytes } = require('crypto');
|
|
4
|
-
const { map, isEmpty } = require('lodash/fp');
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
isBidirectional,
|
|
8
|
-
isOneToAny,
|
|
9
|
-
isManyToAny,
|
|
10
|
-
isAnyToOne,
|
|
11
|
-
hasOrderColumn,
|
|
12
|
-
hasInverseOrderColumn,
|
|
13
|
-
} = require('../metadata/relations');
|
|
14
|
-
const { createQueryBuilder } = require('../query');
|
|
15
|
-
const { addSchema } = require('../utils/knex');
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* If some relations currently exist for this oneToX relation, on the one side, this function removes them and update the inverse order if needed.
|
|
19
|
-
* @param {Object} params
|
|
20
|
-
* @param {string} params.id - entity id on which the relations for entities relIdsToadd are created
|
|
21
|
-
* @param {string} params.attribute - attribute of the relation
|
|
22
|
-
* @param {string} params.inverseRelIds - entity ids of the inverse side for which the current relations will be deleted
|
|
23
|
-
* @param {string} params.db - database instance
|
|
24
|
-
*/
|
|
25
|
-
const deletePreviousOneToAnyRelations = async ({
|
|
26
|
-
id,
|
|
27
|
-
attribute,
|
|
28
|
-
relIdsToadd,
|
|
29
|
-
db,
|
|
30
|
-
transaction: trx,
|
|
31
|
-
}) => {
|
|
32
|
-
if (!(isBidirectional(attribute) && isOneToAny(attribute))) {
|
|
33
|
-
throw new Error(
|
|
34
|
-
'deletePreviousOneToAnyRelations can only be called for bidirectional oneToAny relations'
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
const { joinTable } = attribute;
|
|
38
|
-
const { joinColumn, inverseJoinColumn } = joinTable;
|
|
39
|
-
|
|
40
|
-
await createQueryBuilder(joinTable.name, db)
|
|
41
|
-
.delete()
|
|
42
|
-
.where({
|
|
43
|
-
[inverseJoinColumn.name]: relIdsToadd,
|
|
44
|
-
[joinColumn.name]: { $ne: id },
|
|
45
|
-
})
|
|
46
|
-
.where(joinTable.on || {})
|
|
47
|
-
.transacting(trx)
|
|
48
|
-
.execute();
|
|
49
|
-
|
|
50
|
-
await cleanOrderColumns({ attribute, db, inverseRelIds: relIdsToadd, transaction: trx });
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* If a relation currently exists for this xToOne relations, this function removes it and update the inverse order if needed.
|
|
55
|
-
* @param {Object} params
|
|
56
|
-
* @param {string} params.id - entity id on which the relation for entity relIdToadd is created
|
|
57
|
-
* @param {string} params.attribute - attribute of the relation
|
|
58
|
-
* @param {string} params.relIdToadd - entity id of the new relation
|
|
59
|
-
* @param {string} params.db - database instance
|
|
60
|
-
*/
|
|
61
|
-
const deletePreviousAnyToOneRelations = async ({
|
|
62
|
-
id,
|
|
63
|
-
attribute,
|
|
64
|
-
relIdToadd,
|
|
65
|
-
db,
|
|
66
|
-
transaction: trx,
|
|
67
|
-
}) => {
|
|
68
|
-
const { joinTable } = attribute;
|
|
69
|
-
const { joinColumn, inverseJoinColumn } = joinTable;
|
|
70
|
-
|
|
71
|
-
if (!isAnyToOne(attribute)) {
|
|
72
|
-
throw new Error('deletePreviousAnyToOneRelations can only be called for anyToOne relations');
|
|
73
|
-
}
|
|
74
|
-
// handling manyToOne
|
|
75
|
-
if (isManyToAny(attribute)) {
|
|
76
|
-
// if the database integrity was not broken relsToDelete is supposed to be of length 1
|
|
77
|
-
const relsToDelete = await createQueryBuilder(joinTable.name, db)
|
|
78
|
-
.select(inverseJoinColumn.name)
|
|
79
|
-
.where({
|
|
80
|
-
[joinColumn.name]: id,
|
|
81
|
-
[inverseJoinColumn.name]: { $ne: relIdToadd },
|
|
82
|
-
})
|
|
83
|
-
.where(joinTable.on || {})
|
|
84
|
-
.transacting(trx)
|
|
85
|
-
.execute();
|
|
86
|
-
|
|
87
|
-
const relIdsToDelete = map(inverseJoinColumn.name, relsToDelete);
|
|
88
|
-
|
|
89
|
-
await createQueryBuilder(joinTable.name, db)
|
|
90
|
-
.delete()
|
|
91
|
-
.where({
|
|
92
|
-
[joinColumn.name]: id,
|
|
93
|
-
[inverseJoinColumn.name]: { $in: relIdsToDelete },
|
|
94
|
-
})
|
|
95
|
-
.where(joinTable.on || {})
|
|
96
|
-
.transacting(trx)
|
|
97
|
-
.execute();
|
|
98
|
-
|
|
99
|
-
await cleanOrderColumns({ attribute, db, inverseRelIds: relIdsToDelete, transaction: trx });
|
|
100
|
-
|
|
101
|
-
// handling oneToOne
|
|
102
|
-
} else {
|
|
103
|
-
await createQueryBuilder(joinTable.name, db)
|
|
104
|
-
.delete()
|
|
105
|
-
.where({
|
|
106
|
-
[joinColumn.name]: id,
|
|
107
|
-
[inverseJoinColumn.name]: { $ne: relIdToadd },
|
|
108
|
-
})
|
|
109
|
-
.where(joinTable.on || {})
|
|
110
|
-
.transacting(trx)
|
|
111
|
-
.execute();
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Delete all or some relations of entity field
|
|
117
|
-
* @param {Object} params
|
|
118
|
-
* @param {string} params.id - entity id for which the relations will be deleted
|
|
119
|
-
* @param {string} params.attribute - attribute of the relation
|
|
120
|
-
* @param {string} params.db - database instance
|
|
121
|
-
* @param {string} params.relIdsToDelete - ids of entities to remove from the relations. Also accepts 'all'
|
|
122
|
-
* @param {string} params.relIdsToNotDelete - ids of entities to not remove from the relation when relIdsToDelete equals 'all'
|
|
123
|
-
*/
|
|
124
|
-
const deleteRelations = async ({
|
|
125
|
-
id,
|
|
126
|
-
attribute,
|
|
127
|
-
db,
|
|
128
|
-
relIdsToNotDelete = [],
|
|
129
|
-
relIdsToDelete = [],
|
|
130
|
-
transaction: trx,
|
|
131
|
-
}) => {
|
|
132
|
-
const { joinTable } = attribute;
|
|
133
|
-
const { joinColumn, inverseJoinColumn } = joinTable;
|
|
134
|
-
const all = relIdsToDelete === 'all';
|
|
135
|
-
|
|
136
|
-
if (hasOrderColumn(attribute) || hasInverseOrderColumn(attribute)) {
|
|
137
|
-
let lastId = 0;
|
|
138
|
-
let done = false;
|
|
139
|
-
const batchSize = 100;
|
|
140
|
-
while (!done) {
|
|
141
|
-
const batchToDelete = await createQueryBuilder(joinTable.name, db)
|
|
142
|
-
.select(inverseJoinColumn.name)
|
|
143
|
-
.where({
|
|
144
|
-
[joinColumn.name]: id,
|
|
145
|
-
id: { $gt: lastId },
|
|
146
|
-
[inverseJoinColumn.name]: { $notIn: relIdsToNotDelete },
|
|
147
|
-
...(all ? {} : { [inverseJoinColumn.name]: { $in: relIdsToDelete } }),
|
|
148
|
-
})
|
|
149
|
-
.where(joinTable.on || {})
|
|
150
|
-
.orderBy('id')
|
|
151
|
-
.limit(batchSize)
|
|
152
|
-
.transacting(trx)
|
|
153
|
-
.execute();
|
|
154
|
-
done = batchToDelete.length < batchSize;
|
|
155
|
-
lastId = batchToDelete[batchToDelete.length - 1]?.id || 0;
|
|
156
|
-
|
|
157
|
-
const batchIds = map(inverseJoinColumn.name, batchToDelete);
|
|
158
|
-
|
|
159
|
-
await createQueryBuilder(joinTable.name, db)
|
|
160
|
-
.delete()
|
|
161
|
-
.where({
|
|
162
|
-
[joinColumn.name]: id,
|
|
163
|
-
[inverseJoinColumn.name]: { $in: batchIds },
|
|
164
|
-
})
|
|
165
|
-
.where(joinTable.on || {})
|
|
166
|
-
.transacting(trx)
|
|
167
|
-
.execute();
|
|
168
|
-
|
|
169
|
-
await cleanOrderColumns({ attribute, db, id, inverseRelIds: batchIds, transaction: trx });
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
await createQueryBuilder(joinTable.name, db)
|
|
173
|
-
.delete()
|
|
174
|
-
.where({
|
|
175
|
-
[joinColumn.name]: id,
|
|
176
|
-
[inverseJoinColumn.name]: { $notIn: relIdsToNotDelete },
|
|
177
|
-
...(all ? {} : { [inverseJoinColumn.name]: { $in: relIdsToDelete } }),
|
|
178
|
-
})
|
|
179
|
-
.where(joinTable.on || {})
|
|
180
|
-
.transacting(trx)
|
|
181
|
-
.execute();
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Clean the order columns by ensuring the order value are continuous (ex: 1, 2, 3 and not 1, 5, 10)
|
|
187
|
-
* @param {Object} params
|
|
188
|
-
* @param {string} params.id - entity id for which the clean will be done
|
|
189
|
-
* @param {string} params.attribute - attribute of the relation
|
|
190
|
-
* @param {string} params.db - database instance
|
|
191
|
-
* @param {string} params.inverseRelIds - entity ids of the inverse side for which the clean will be done
|
|
192
|
-
*/
|
|
193
|
-
const cleanOrderColumns = async ({ id, attribute, db, inverseRelIds, transaction: trx }) => {
|
|
194
|
-
if (
|
|
195
|
-
!(hasOrderColumn(attribute) && id) &&
|
|
196
|
-
!(hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds))
|
|
197
|
-
) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Handle databases that don't support window function ROW_NUMBER (here it's MySQL 5)
|
|
202
|
-
if (!strapi.db.dialect.supportsWindowFunctions()) {
|
|
203
|
-
await cleanOrderColumnsForOldDatabases({ id, attribute, db, inverseRelIds, transaction: trx });
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const { joinTable } = attribute;
|
|
208
|
-
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
UPDATE :joinTable: as a,
|
|
212
|
-
(
|
|
213
|
-
SELECT
|
|
214
|
-
id,
|
|
215
|
-
ROW_NUMBER() OVER ( PARTITION BY :joinColumn: ORDER BY :orderColumn:) AS src_order,
|
|
216
|
-
FROM :joinTable:
|
|
217
|
-
WHERE :joinColumn: = :id
|
|
218
|
-
) AS b
|
|
219
|
-
SET :orderColumn: = b.src_order
|
|
220
|
-
WHERE b.id = a.id;
|
|
221
|
-
*/
|
|
222
|
-
const updateOrderColumn = async () => {
|
|
223
|
-
if (!hasOrderColumn(attribute) || !id) return;
|
|
224
|
-
|
|
225
|
-
const selectRowsToOrder = (joinTableName) =>
|
|
226
|
-
db
|
|
227
|
-
.connection(joinTableName)
|
|
228
|
-
.select('id')
|
|
229
|
-
.rowNumber('src_order', orderColumnName, joinColumn.name)
|
|
230
|
-
.where(joinColumn.name, id)
|
|
231
|
-
.toSQL();
|
|
232
|
-
|
|
233
|
-
switch (strapi.db.dialect.client) {
|
|
234
|
-
case 'mysql': {
|
|
235
|
-
// Here it's MariaDB and MySQL 8
|
|
236
|
-
const select = selectRowsToOrder(joinTable.name);
|
|
237
|
-
|
|
238
|
-
await db
|
|
239
|
-
.getConnection()
|
|
240
|
-
.raw(
|
|
241
|
-
`UPDATE ?? as a, ( ${select.sql} ) AS b
|
|
242
|
-
SET ?? = b.src_order
|
|
243
|
-
WHERE b.id = a.id`,
|
|
244
|
-
[joinTable.name, ...select.bindings, orderColumnName]
|
|
245
|
-
)
|
|
246
|
-
.transacting(trx);
|
|
247
|
-
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
default: {
|
|
251
|
-
const joinTableName = addSchema(joinTable.name);
|
|
252
|
-
const select = selectRowsToOrder(joinTableName);
|
|
253
|
-
|
|
254
|
-
// raw query as knex doesn't allow updating from a subquery
|
|
255
|
-
await db.connection
|
|
256
|
-
.raw(
|
|
257
|
-
`UPDATE ?? as a
|
|
258
|
-
SET ?? = b.src_order
|
|
259
|
-
FROM ( ${select.sql} ) AS b
|
|
260
|
-
WHERE b.id = a.id`,
|
|
261
|
-
[joinTableName, orderColumnName, ...select.bindings]
|
|
262
|
-
)
|
|
263
|
-
.transacting(trx);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
UPDATE :joinTable: as a,
|
|
270
|
-
(
|
|
271
|
-
SELECT
|
|
272
|
-
id,
|
|
273
|
-
ROW_NUMBER() OVER ( PARTITION BY :inverseJoinColumn: ORDER BY :inverseOrderColumn:) AS inv_order
|
|
274
|
-
FROM :joinTable:
|
|
275
|
-
WHERE :inverseJoinColumn: IN (:inverseRelIds)
|
|
276
|
-
) AS b
|
|
277
|
-
SET :inverseOrderColumn: = b.inv_order
|
|
278
|
-
WHERE b.id = a.id;
|
|
279
|
-
*/
|
|
280
|
-
const updateInverseOrderColumn = async () => {
|
|
281
|
-
if (!hasInverseOrderColumn(attribute) || isEmpty(inverseRelIds)) return;
|
|
282
|
-
|
|
283
|
-
const selectRowsToOrder = (joinTableName) =>
|
|
284
|
-
db
|
|
285
|
-
.connection(joinTableName)
|
|
286
|
-
.select('id')
|
|
287
|
-
.rowNumber('inv_order', inverseOrderColumnName, inverseJoinColumn.name)
|
|
288
|
-
.where(inverseJoinColumn.name, 'in', inverseRelIds)
|
|
289
|
-
.toSQL();
|
|
290
|
-
|
|
291
|
-
switch (strapi.db.dialect.client) {
|
|
292
|
-
case 'mysql': {
|
|
293
|
-
// Here it's MariaDB and MySQL 8
|
|
294
|
-
const select = selectRowsToOrder(joinTable.name);
|
|
295
|
-
|
|
296
|
-
await db
|
|
297
|
-
.getConnection()
|
|
298
|
-
.raw(
|
|
299
|
-
`UPDATE ?? as a, ( ${select.sql} ) AS b
|
|
300
|
-
SET ?? = b.inv_order
|
|
301
|
-
WHERE b.id = a.id`,
|
|
302
|
-
[joinTable.name, ...select.bindings, inverseOrderColumnName]
|
|
303
|
-
)
|
|
304
|
-
.transacting(trx);
|
|
305
|
-
break;
|
|
306
|
-
}
|
|
307
|
-
default: {
|
|
308
|
-
const joinTableName = addSchema(joinTable.name);
|
|
309
|
-
const select = selectRowsToOrder(joinTableName);
|
|
310
|
-
|
|
311
|
-
// raw query as knex doesn't allow updating from a subquery
|
|
312
|
-
await db.connection
|
|
313
|
-
.raw(
|
|
314
|
-
`UPDATE ?? as a
|
|
315
|
-
SET ?? = b.inv_order
|
|
316
|
-
FROM ( ${select.sql} ) AS b
|
|
317
|
-
WHERE b.id = a.id`,
|
|
318
|
-
[joinTableName, inverseOrderColumnName, ...select.bindings]
|
|
319
|
-
)
|
|
320
|
-
.transacting(trx);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
return Promise.all([updateOrderColumn(), updateInverseOrderColumn()]);
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
/*
|
|
329
|
-
* Ensure that orders are following a 1, 2, 3 sequence, without gap.
|
|
330
|
-
* The use of a session variable instead of a window function makes the query compatible with MySQL 5
|
|
331
|
-
*/
|
|
332
|
-
const cleanOrderColumnsForOldDatabases = async ({
|
|
333
|
-
id,
|
|
334
|
-
attribute,
|
|
335
|
-
db,
|
|
336
|
-
inverseRelIds,
|
|
337
|
-
transaction: trx,
|
|
338
|
-
}) => {
|
|
339
|
-
const { joinTable } = attribute;
|
|
340
|
-
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
|
|
341
|
-
|
|
342
|
-
const randomSuffix = `${new Date().valueOf()}_${randomBytes(16).toString('hex')}`;
|
|
343
|
-
|
|
344
|
-
if (hasOrderColumn(attribute) && id) {
|
|
345
|
-
// raw query as knex doesn't allow updating from a subquery
|
|
346
|
-
// https://github.com/knex/knex/issues/2504
|
|
347
|
-
const orderVar = `order_${randomSuffix}`;
|
|
348
|
-
await db.connection.raw(`SET @${orderVar} = 0;`).transacting(trx);
|
|
349
|
-
await db.connection
|
|
350
|
-
.raw(
|
|
351
|
-
`UPDATE :joinTableName: as a, (
|
|
352
|
-
SELECT id, (@${orderVar}:=@${orderVar} + 1) AS src_order
|
|
353
|
-
FROM :joinTableName:
|
|
354
|
-
WHERE :joinColumnName: = :id
|
|
355
|
-
ORDER BY :orderColumnName:
|
|
356
|
-
) AS b
|
|
357
|
-
SET :orderColumnName: = b.src_order
|
|
358
|
-
WHERE a.id = b.id
|
|
359
|
-
AND a.:joinColumnName: = :id`,
|
|
360
|
-
{
|
|
361
|
-
joinTableName: joinTable.name,
|
|
362
|
-
orderColumnName,
|
|
363
|
-
joinColumnName: joinColumn.name,
|
|
364
|
-
id,
|
|
365
|
-
}
|
|
366
|
-
)
|
|
367
|
-
.transacting(trx);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds)) {
|
|
371
|
-
const orderVar = `order_${randomSuffix}`;
|
|
372
|
-
const columnVar = `col_${randomSuffix}`;
|
|
373
|
-
await db.connection.raw(`SET @${orderVar} = 0;`).transacting(trx);
|
|
374
|
-
await db.connection
|
|
375
|
-
.raw(
|
|
376
|
-
`UPDATE ?? as a, (
|
|
377
|
-
SELECT
|
|
378
|
-
id,
|
|
379
|
-
@${orderVar}:=CASE WHEN @${columnVar} = ?? THEN @${orderVar} + 1 ELSE 1 END AS inv_order,
|
|
380
|
-
@${columnVar}:=?? ??
|
|
381
|
-
FROM ?? a
|
|
382
|
-
WHERE ?? IN(${inverseRelIds.map(() => '?').join(', ')})
|
|
383
|
-
ORDER BY ??, ??
|
|
384
|
-
) AS b
|
|
385
|
-
SET ?? = b.inv_order
|
|
386
|
-
WHERE a.id = b.id
|
|
387
|
-
AND a.?? IN(${inverseRelIds.map(() => '?').join(', ')})`,
|
|
388
|
-
[
|
|
389
|
-
joinTable.name,
|
|
390
|
-
inverseJoinColumn.name,
|
|
391
|
-
inverseJoinColumn.name,
|
|
392
|
-
inverseJoinColumn.name,
|
|
393
|
-
joinTable.name,
|
|
394
|
-
inverseJoinColumn.name,
|
|
395
|
-
...inverseRelIds,
|
|
396
|
-
inverseJoinColumn.name,
|
|
397
|
-
joinColumn.name,
|
|
398
|
-
inverseOrderColumnName,
|
|
399
|
-
inverseJoinColumn.name,
|
|
400
|
-
...inverseRelIds,
|
|
401
|
-
]
|
|
402
|
-
)
|
|
403
|
-
.transacting(trx);
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Use this when a relation is added or removed and its inverse order column
|
|
409
|
-
* needs to be re-calculated
|
|
410
|
-
*
|
|
411
|
-
* Example: In this following table
|
|
412
|
-
*
|
|
413
|
-
* | joinColumn | inverseJoinColumn | order | inverseOrder |
|
|
414
|
-
* | --------------- | -------- | ----------- | ------------------ |
|
|
415
|
-
* | 1 | 1 | 1 | 1 |
|
|
416
|
-
* | 2 | 1 | 3 | 2 |
|
|
417
|
-
* | 2 | 2 | 3 | 1 |
|
|
418
|
-
*
|
|
419
|
-
* You add a new relation { joinColumn: 1, inverseJoinColumn: 2 }
|
|
420
|
-
*
|
|
421
|
-
* | joinColumn | inverseJoinColumn | order | inverseOrder |
|
|
422
|
-
* | --------------- | -------- | ----------- | ------------------ |
|
|
423
|
-
* | 1 | 1 | 1 | 1 |
|
|
424
|
-
* | 1 | 2 | 2 | 1 | <- inverseOrder should be 2
|
|
425
|
-
* | 2 | 1 | 3 | 2 |
|
|
426
|
-
* | 2 | 2 | 3 | 1 |
|
|
427
|
-
*
|
|
428
|
-
* This function would make such update, so all inverse order columns related
|
|
429
|
-
* to the given id (1 in this example) are following a 1, 2, 3 sequence, without gap.
|
|
430
|
-
*
|
|
431
|
-
* @param {Object} params
|
|
432
|
-
* @param {string} params.id - entity id to find which inverse order column to clean
|
|
433
|
-
* @param {Object} params.attribute - attribute of the relation
|
|
434
|
-
* @param {Object} params.trx - knex transaction
|
|
435
|
-
*
|
|
436
|
-
*/
|
|
437
|
-
|
|
438
|
-
const cleanInverseOrderColumn = async ({ id, attribute, trx }) => {
|
|
439
|
-
const con = strapi.db.connection;
|
|
440
|
-
const { joinTable } = attribute;
|
|
441
|
-
const { joinColumn, inverseJoinColumn, inverseOrderColumnName } = joinTable;
|
|
442
|
-
|
|
443
|
-
switch (strapi.db.dialect.client) {
|
|
444
|
-
/*
|
|
445
|
-
UPDATE `:joinTableName` AS `t1`
|
|
446
|
-
JOIN (
|
|
447
|
-
SELECT
|
|
448
|
-
`inverseJoinColumn`,
|
|
449
|
-
MAX(`:inverseOrderColumnName`) AS `max_inv_order`
|
|
450
|
-
FROM `:joinTableName`
|
|
451
|
-
GROUP BY `:inverseJoinColumn`
|
|
452
|
-
) AS `t2`
|
|
453
|
-
ON `t1`.`:inverseJoinColumn` = `t2`.`:inverseJoinColumn`
|
|
454
|
-
SET `t1`.`:inverseOrderColumnNAme` = `t2`.`max_inv_order` + 1
|
|
455
|
-
WHERE `t1`.`:joinColumnName` = :id;
|
|
456
|
-
*/
|
|
457
|
-
case 'mysql': {
|
|
458
|
-
// Group by the inverse join column and get the max value of the inverse order column
|
|
459
|
-
const subQuery = con(joinTable.name)
|
|
460
|
-
.select(inverseJoinColumn.name)
|
|
461
|
-
.max(inverseOrderColumnName, { as: 'max_inv_order' })
|
|
462
|
-
.groupBy(inverseJoinColumn.name)
|
|
463
|
-
.as('t2');
|
|
464
|
-
|
|
465
|
-
// Update ids with the new inverse order
|
|
466
|
-
await con(`${joinTable.name} as t1`)
|
|
467
|
-
.join(subQuery, `t1.${inverseJoinColumn.name}`, '=', `t2.${inverseJoinColumn.name}`)
|
|
468
|
-
.where(joinColumn.name, id)
|
|
469
|
-
.update({
|
|
470
|
-
[inverseOrderColumnName]: con.raw('t2.max_inv_order + 1'),
|
|
471
|
-
})
|
|
472
|
-
.transacting(trx);
|
|
473
|
-
break;
|
|
474
|
-
}
|
|
475
|
-
default: {
|
|
476
|
-
/*
|
|
477
|
-
UPDATE `:joinTableName` as `t1`
|
|
478
|
-
SET `:inverseOrderColumnName` = (
|
|
479
|
-
SELECT max(`:inverseOrderColumnName`) + 1
|
|
480
|
-
FROM `:joinTableName` as `t2`
|
|
481
|
-
WHERE t2.:inverseJoinColumn = t1.:inverseJoinColumn
|
|
482
|
-
)
|
|
483
|
-
WHERE `t1`.`:joinColumnName` = :id
|
|
484
|
-
*/
|
|
485
|
-
// New inverse order will be the max value + 1
|
|
486
|
-
const selectMaxInverseOrder = con.raw(`max(${inverseOrderColumnName}) + 1`);
|
|
487
|
-
|
|
488
|
-
const subQuery = con(`${joinTable.name} as t2`)
|
|
489
|
-
.select(selectMaxInverseOrder)
|
|
490
|
-
.whereRaw(`t2.${inverseJoinColumn.name} = t1.${inverseJoinColumn.name}`);
|
|
491
|
-
|
|
492
|
-
await con(`${joinTable.name} as t1`)
|
|
493
|
-
.where(`t1.${joinColumn.name}`, id)
|
|
494
|
-
.update({ [inverseOrderColumnName]: subQuery })
|
|
495
|
-
.transacting(trx);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
module.exports = {
|
|
501
|
-
deletePreviousOneToAnyRelations,
|
|
502
|
-
deletePreviousAnyToOneRelations,
|
|
503
|
-
deleteRelations,
|
|
504
|
-
cleanOrderColumns,
|
|
505
|
-
cleanInverseOrderColumn,
|
|
506
|
-
};
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { cleanInverseOrderColumn } = require('../../regular-relations');
|
|
4
|
-
|
|
5
|
-
const replaceRegularRelations = async ({
|
|
6
|
-
targetId,
|
|
7
|
-
sourceId,
|
|
8
|
-
attribute,
|
|
9
|
-
omitIds,
|
|
10
|
-
transaction: trx,
|
|
11
|
-
}) => {
|
|
12
|
-
const { joinTable } = attribute;
|
|
13
|
-
const { joinColumn, inverseJoinColumn } = joinTable;
|
|
14
|
-
|
|
15
|
-
// We are effectively stealing the relation from the cloned entity
|
|
16
|
-
await strapi.db.entityManager
|
|
17
|
-
.createQueryBuilder(joinTable.name)
|
|
18
|
-
.update({ [joinColumn.name]: targetId })
|
|
19
|
-
.where({ [joinColumn.name]: sourceId })
|
|
20
|
-
.where({ $not: { [inverseJoinColumn.name]: omitIds } })
|
|
21
|
-
.onConflict([joinColumn.name, inverseJoinColumn.name])
|
|
22
|
-
.ignore()
|
|
23
|
-
.transacting(trx)
|
|
24
|
-
.execute();
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const cloneRegularRelations = async ({ targetId, sourceId, attribute, transaction: trx }) => {
|
|
28
|
-
const { joinTable } = attribute;
|
|
29
|
-
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
|
|
30
|
-
const connection = strapi.db.getConnection();
|
|
31
|
-
|
|
32
|
-
// Get the columns to select
|
|
33
|
-
const columns = [joinColumn.name, inverseJoinColumn.name];
|
|
34
|
-
if (orderColumnName) columns.push(orderColumnName);
|
|
35
|
-
if (inverseOrderColumnName) columns.push(inverseOrderColumnName);
|
|
36
|
-
if (joinTable.on) columns.push(...Object.keys(joinTable.on));
|
|
37
|
-
|
|
38
|
-
const selectStatement = connection
|
|
39
|
-
.select(
|
|
40
|
-
// Override joinColumn with the new id
|
|
41
|
-
{ [joinColumn.name]: targetId },
|
|
42
|
-
// The rest of columns will be the same
|
|
43
|
-
...columns.slice(1)
|
|
44
|
-
)
|
|
45
|
-
.where(joinColumn.name, sourceId)
|
|
46
|
-
.from(joinTable.name)
|
|
47
|
-
.toSQL();
|
|
48
|
-
|
|
49
|
-
// Insert the clone relations
|
|
50
|
-
await strapi.db.entityManager
|
|
51
|
-
.createQueryBuilder(joinTable.name)
|
|
52
|
-
.insert(
|
|
53
|
-
strapi.db.connection.raw(
|
|
54
|
-
`(${columns.join(',')}) ${selectStatement.sql}`,
|
|
55
|
-
selectStatement.bindings
|
|
56
|
-
)
|
|
57
|
-
)
|
|
58
|
-
.onConflict([joinColumn.name, inverseJoinColumn.name])
|
|
59
|
-
.ignore()
|
|
60
|
-
.transacting(trx)
|
|
61
|
-
.execute();
|
|
62
|
-
|
|
63
|
-
// Clean the inverse order column
|
|
64
|
-
if (inverseOrderColumnName) {
|
|
65
|
-
await cleanInverseOrderColumn({
|
|
66
|
-
id: targetId,
|
|
67
|
-
attribute,
|
|
68
|
-
trx,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
module.exports = {
|
|
74
|
-
replaceRegularRelations,
|
|
75
|
-
cloneRegularRelations,
|
|
76
|
-
};
|