@strapi/database 4.5.4 → 4.6.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/__tests__/relations-orderer.test.js +85 -0
- package/lib/entity-manager/index.js +63 -19
- package/lib/entity-manager/regular-relations.js +63 -53
- package/lib/entity-manager/relations-orderer.js +126 -0
- package/lib/metadata/index.js +1 -1
- package/lib/metadata/relations.js +3 -3
- package/lib/query/helpers/where.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const relationsOrderer = require('../relations-orderer');
|
|
4
|
+
|
|
5
|
+
describe('relations orderer', () => {
|
|
6
|
+
test('connect at the end', () => {
|
|
7
|
+
const orderer = relationsOrderer(
|
|
8
|
+
[
|
|
9
|
+
{ id: 2, order: 4 },
|
|
10
|
+
{ id: 3, order: 10 },
|
|
11
|
+
],
|
|
12
|
+
'id',
|
|
13
|
+
'order'
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
orderer.connect([{ id: 4, position: { end: true } }, { id: 5 }]);
|
|
17
|
+
|
|
18
|
+
expect(orderer.get()).toMatchObject([
|
|
19
|
+
{ id: 2, order: 4 },
|
|
20
|
+
{ id: 3, order: 10 },
|
|
21
|
+
{ id: 4, order: 10.5 },
|
|
22
|
+
{ id: 5, order: 10.5 },
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('connect at the start', () => {
|
|
27
|
+
const orderer = relationsOrderer(
|
|
28
|
+
[
|
|
29
|
+
{ id: 2, order: 4 },
|
|
30
|
+
{ id: 3, order: 10 },
|
|
31
|
+
],
|
|
32
|
+
'id',
|
|
33
|
+
'order'
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
orderer.connect([{ id: 4, position: { start: true } }]);
|
|
37
|
+
|
|
38
|
+
expect(orderer.get()).toMatchObject([
|
|
39
|
+
{ id: 4, order: 0.5 },
|
|
40
|
+
{ id: 2, order: 4 },
|
|
41
|
+
{ id: 3, order: 10 },
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('connect multiple relations', () => {
|
|
46
|
+
const orderer = relationsOrderer(
|
|
47
|
+
[
|
|
48
|
+
{ id: 2, order: 4 },
|
|
49
|
+
{ id: 3, order: 10 },
|
|
50
|
+
],
|
|
51
|
+
'id',
|
|
52
|
+
'order'
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
orderer.connect([
|
|
56
|
+
{ id: 4, position: { before: 2 } },
|
|
57
|
+
{ id: 4, position: { before: 3 } },
|
|
58
|
+
{ id: 5, position: { before: 4 } },
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
expect(orderer.get()).toMatchObject([
|
|
62
|
+
{ id: 2, order: 4 },
|
|
63
|
+
{ id: 5, order: 9.5 },
|
|
64
|
+
{ id: 4, order: 9.5 },
|
|
65
|
+
{ id: 3, order: 10 },
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('connect with no initial relations', () => {
|
|
70
|
+
const orderer = relationsOrderer([], 'id', 'order');
|
|
71
|
+
|
|
72
|
+
orderer.connect([
|
|
73
|
+
{ id: 1, position: { start: true } },
|
|
74
|
+
{ id: 2, position: { start: true } },
|
|
75
|
+
{ id: 3, position: { after: 1 } },
|
|
76
|
+
{ id: 1, position: { after: 2 } },
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
expect(orderer.get()).toMatchObject([
|
|
80
|
+
{ id: 2, order: 0.5 },
|
|
81
|
+
{ id: 1, order: 0.5 },
|
|
82
|
+
{ id: 3, order: 0.5 },
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const {
|
|
4
4
|
isUndefined,
|
|
5
5
|
castArray,
|
|
6
|
+
compact,
|
|
6
7
|
isNil,
|
|
7
8
|
has,
|
|
8
9
|
isString,
|
|
@@ -18,6 +19,7 @@ const {
|
|
|
18
19
|
isNumber,
|
|
19
20
|
map,
|
|
20
21
|
difference,
|
|
22
|
+
uniqBy,
|
|
21
23
|
} = require('lodash/fp');
|
|
22
24
|
const types = require('../types');
|
|
23
25
|
const { createField } = require('../fields');
|
|
@@ -37,6 +39,7 @@ const {
|
|
|
37
39
|
deleteRelations,
|
|
38
40
|
cleanOrderColumns,
|
|
39
41
|
} = require('./regular-relations');
|
|
42
|
+
const relationsOrderer = require('./relations-orderer');
|
|
40
43
|
|
|
41
44
|
const toId = (value) => value.id || value;
|
|
42
45
|
const toIds = (value) => castArray(value || []).map(toId);
|
|
@@ -75,7 +78,10 @@ const toAssocs = (data) => {
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
return {
|
|
78
|
-
connect: toIdArray(data?.connect)
|
|
81
|
+
connect: toIdArray(data?.connect).map((elm) => ({
|
|
82
|
+
id: elm.id,
|
|
83
|
+
position: elm.position ? elm.position : { end: true },
|
|
84
|
+
})),
|
|
79
85
|
disconnect: toIdArray(data?.disconnect),
|
|
80
86
|
};
|
|
81
87
|
};
|
|
@@ -561,7 +567,7 @@ const createEntityManager = (db) => {
|
|
|
561
567
|
}
|
|
562
568
|
|
|
563
569
|
// prepare new relations to insert
|
|
564
|
-
const insert = relsToAdd.map((data) => {
|
|
570
|
+
const insert = uniqBy('id', relsToAdd).map((data) => {
|
|
565
571
|
return {
|
|
566
572
|
[joinColumn.name]: id,
|
|
567
573
|
[inverseJoinColumn.name]: data.id,
|
|
@@ -571,11 +577,23 @@ const createEntityManager = (db) => {
|
|
|
571
577
|
});
|
|
572
578
|
|
|
573
579
|
// add order value
|
|
574
|
-
if (hasOrderColumn(attribute)) {
|
|
575
|
-
insert.forEach((
|
|
576
|
-
|
|
580
|
+
if (cleanRelationData.set && hasOrderColumn(attribute)) {
|
|
581
|
+
insert.forEach((data, idx) => {
|
|
582
|
+
data[orderColumnName] = idx + 1;
|
|
583
|
+
});
|
|
584
|
+
} else if (cleanRelationData.connect && hasOrderColumn(attribute)) {
|
|
585
|
+
// use position attributes to calculate order
|
|
586
|
+
const orderMap = relationsOrderer([], inverseJoinColumn.name, joinTable.orderColumnName)
|
|
587
|
+
.connect(relsToAdd)
|
|
588
|
+
.get()
|
|
589
|
+
// set the order based on the order of the ids
|
|
590
|
+
.reduce((acc, rel, idx) => ({ ...acc, [rel.id]: idx }), {});
|
|
591
|
+
|
|
592
|
+
insert.forEach((row) => {
|
|
593
|
+
row[orderColumnName] = orderMap[row[inverseJoinColumn.name]];
|
|
577
594
|
});
|
|
578
595
|
}
|
|
596
|
+
|
|
579
597
|
// add inv_order value
|
|
580
598
|
if (hasInverseOrderColumn(attribute)) {
|
|
581
599
|
const maxResults = await db
|
|
@@ -815,27 +833,53 @@ const createEntityManager = (db) => {
|
|
|
815
833
|
}
|
|
816
834
|
|
|
817
835
|
// prepare relations to insert
|
|
818
|
-
const insert = cleanRelationData.connect.map((relToAdd) => ({
|
|
836
|
+
const insert = uniqBy('id', cleanRelationData.connect).map((relToAdd) => ({
|
|
819
837
|
[joinColumn.name]: id,
|
|
820
838
|
[inverseJoinColumn.name]: relToAdd.id,
|
|
821
839
|
...(joinTable.on || {}),
|
|
822
840
|
...(relToAdd.__pivot || {}),
|
|
823
841
|
}));
|
|
824
842
|
|
|
825
|
-
// add order value
|
|
826
843
|
if (hasOrderColumn(attribute)) {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
844
|
+
// Get all adjacent relations and the one with the highest order
|
|
845
|
+
const adjacentRelations = await this.createQueryBuilder(joinTable.name)
|
|
846
|
+
.where({
|
|
847
|
+
$or: [
|
|
848
|
+
{
|
|
849
|
+
[joinColumn.name]: id,
|
|
850
|
+
[inverseJoinColumn.name]: {
|
|
851
|
+
$in: compact(
|
|
852
|
+
cleanRelationData.connect.map(
|
|
853
|
+
(r) => r.position?.after || r.position?.before
|
|
854
|
+
)
|
|
855
|
+
),
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
[joinColumn.name]: id,
|
|
860
|
+
[orderColumnName]: this.createQueryBuilder(joinTable.name)
|
|
861
|
+
.max(orderColumnName)
|
|
862
|
+
.where({ [joinColumn.name]: id })
|
|
863
|
+
.where(joinTable.on || {})
|
|
864
|
+
.transacting(trx)
|
|
865
|
+
.getKnexQuery(),
|
|
866
|
+
},
|
|
867
|
+
],
|
|
868
|
+
})
|
|
869
|
+
.where(joinTable.on || {})
|
|
870
|
+
.transacting(trx)
|
|
871
|
+
.execute();
|
|
836
872
|
|
|
837
|
-
|
|
838
|
-
|
|
873
|
+
const orderMap = relationsOrderer(
|
|
874
|
+
adjacentRelations,
|
|
875
|
+
inverseJoinColumn.name,
|
|
876
|
+
joinTable.orderColumnName
|
|
877
|
+
)
|
|
878
|
+
.connect(cleanRelationData.connect)
|
|
879
|
+
.getOrderMap();
|
|
880
|
+
|
|
881
|
+
insert.forEach((row) => {
|
|
882
|
+
row[orderColumnName] = orderMap[row[inverseJoinColumn.name]];
|
|
839
883
|
});
|
|
840
884
|
}
|
|
841
885
|
|
|
@@ -901,7 +945,7 @@ const createEntityManager = (db) => {
|
|
|
901
945
|
continue;
|
|
902
946
|
}
|
|
903
947
|
|
|
904
|
-
const insert = cleanRelationData.set.map((relToAdd) => ({
|
|
948
|
+
const insert = uniqBy('id', cleanRelationData.set).map((relToAdd) => ({
|
|
905
949
|
[joinColumn.name]: id,
|
|
906
950
|
[inverseJoinColumn.name]: relToAdd.id,
|
|
907
951
|
...(joinTable.on || {}),
|
|
@@ -198,42 +198,60 @@ const cleanOrderColumns = async ({ id, attribute, db, inverseRelIds, transaction
|
|
|
198
198
|
return;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
// Handle databases that don't support window function ROW_NUMBER
|
|
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
|
+
const update = [];
|
|
210
|
+
const updateBinding = [];
|
|
211
|
+
const select = ['??'];
|
|
212
|
+
const selectBinding = ['id'];
|
|
213
|
+
const where = [];
|
|
214
|
+
const whereBinding = [];
|
|
215
|
+
|
|
216
|
+
if (hasOrderColumn(attribute) && id) {
|
|
217
|
+
update.push('?? = b.src_order');
|
|
218
|
+
updateBinding.push(orderColumnName);
|
|
219
|
+
select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS src_order');
|
|
220
|
+
selectBinding.push(joinColumn.name, orderColumnName);
|
|
221
|
+
where.push('?? = ?');
|
|
222
|
+
whereBinding.push(joinColumn.name, id);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds)) {
|
|
226
|
+
update.push('?? = b.inv_order');
|
|
227
|
+
updateBinding.push(inverseOrderColumnName);
|
|
228
|
+
select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS inv_order');
|
|
229
|
+
selectBinding.push(inverseJoinColumn.name, inverseOrderColumnName);
|
|
230
|
+
where.push(`?? IN (${inverseRelIds.map(() => '?').join(', ')})`);
|
|
231
|
+
whereBinding.push(inverseJoinColumn.name, ...inverseRelIds);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// raw query as knex doesn't allow updating from a subquery
|
|
235
|
+
// https://github.com/knex/knex/issues/2504
|
|
201
236
|
switch (strapi.db.dialect.client) {
|
|
202
237
|
case 'mysql':
|
|
203
|
-
await
|
|
238
|
+
await db.connection
|
|
239
|
+
.raw(
|
|
240
|
+
`UPDATE
|
|
241
|
+
?? as a,
|
|
242
|
+
(
|
|
243
|
+
SELECT ${select.join(', ')}
|
|
244
|
+
FROM ??
|
|
245
|
+
WHERE ${where.join(' OR ')}
|
|
246
|
+
) AS b
|
|
247
|
+
SET ${update.join(', ')}
|
|
248
|
+
WHERE b.id = a.id`,
|
|
249
|
+
[joinTable.name, ...selectBinding, joinTable.name, ...whereBinding, ...updateBinding]
|
|
250
|
+
)
|
|
251
|
+
.transacting(trx);
|
|
204
252
|
break;
|
|
205
253
|
default: {
|
|
206
|
-
const { joinTable } = attribute;
|
|
207
|
-
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
|
|
208
|
-
const update = [];
|
|
209
|
-
const updateBinding = [];
|
|
210
|
-
const select = ['??'];
|
|
211
|
-
const selectBinding = ['id'];
|
|
212
|
-
const where = [];
|
|
213
|
-
const whereBinding = [];
|
|
214
|
-
|
|
215
|
-
if (hasOrderColumn(attribute) && id) {
|
|
216
|
-
update.push('?? = b.src_order');
|
|
217
|
-
updateBinding.push(orderColumnName);
|
|
218
|
-
select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS src_order');
|
|
219
|
-
selectBinding.push(joinColumn.name, orderColumnName);
|
|
220
|
-
where.push('?? = ?');
|
|
221
|
-
whereBinding.push(joinColumn.name, id);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds)) {
|
|
225
|
-
update.push('?? = b.inv_order');
|
|
226
|
-
updateBinding.push(inverseOrderColumnName);
|
|
227
|
-
select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS inv_order');
|
|
228
|
-
selectBinding.push(inverseJoinColumn.name, inverseOrderColumnName);
|
|
229
|
-
where.push(`?? IN (${inverseRelIds.map(() => '?').join(', ')})`);
|
|
230
|
-
whereBinding.push(inverseJoinColumn.name, ...inverseRelIds);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
254
|
const joinTableName = addSchema(joinTable.name);
|
|
234
|
-
|
|
235
|
-
// raw query as knex doesn't allow updating from a subquery
|
|
236
|
-
// https://github.com/knex/knex/issues/2504
|
|
237
255
|
await db.connection
|
|
238
256
|
.raw(
|
|
239
257
|
`UPDATE ?? as a
|
|
@@ -247,29 +265,24 @@ const cleanOrderColumns = async ({ id, attribute, db, inverseRelIds, transaction
|
|
|
247
265
|
[joinTableName, ...updateBinding, ...selectBinding, joinTableName, ...whereBinding]
|
|
248
266
|
)
|
|
249
267
|
.transacting(trx);
|
|
250
|
-
|
|
251
|
-
/*
|
|
252
|
-
`UPDATE :joinTable: as a
|
|
253
|
-
SET :orderColumn: = b.src_order, :inverseOrderColumn: = b.inv_order
|
|
254
|
-
FROM (
|
|
255
|
-
SELECT
|
|
256
|
-
id,
|
|
257
|
-
ROW_NUMBER() OVER ( PARTITION BY :joinColumn: ORDER BY :orderColumn:) AS src_order,
|
|
258
|
-
ROW_NUMBER() OVER ( PARTITION BY :inverseJoinColumn: ORDER BY :inverseOrderColumn:) AS inv_order
|
|
259
|
-
FROM :joinTable:
|
|
260
|
-
WHERE :joinColumn: = :id OR :inverseJoinColumn: IN (:inverseRelIds)
|
|
261
|
-
) AS b
|
|
262
|
-
WHERE b.id = a.id`,
|
|
263
|
-
*/
|
|
264
268
|
}
|
|
269
|
+
/*
|
|
270
|
+
`UPDATE :joinTable: as a
|
|
271
|
+
SET :orderColumn: = b.src_order, :inverseOrderColumn: = b.inv_order
|
|
272
|
+
FROM (
|
|
273
|
+
SELECT
|
|
274
|
+
id,
|
|
275
|
+
ROW_NUMBER() OVER ( PARTITION BY :joinColumn: ORDER BY :orderColumn:) AS src_order,
|
|
276
|
+
ROW_NUMBER() OVER ( PARTITION BY :inverseJoinColumn: ORDER BY :inverseOrderColumn:) AS inv_order
|
|
277
|
+
FROM :joinTable:
|
|
278
|
+
WHERE :joinColumn: = :id OR :inverseJoinColumn: IN (:inverseRelIds)
|
|
279
|
+
) AS b
|
|
280
|
+
WHERE b.id = a.id`,
|
|
281
|
+
*/
|
|
265
282
|
}
|
|
266
283
|
};
|
|
267
284
|
|
|
268
|
-
|
|
269
|
-
* Ensure that orders are following a 1, 2, 3 sequence, without gap.
|
|
270
|
-
* The use of a temporary table instead of a window function makes the query compatible with MySQL 5 and prevents some deadlocks to happen in innoDB databases
|
|
271
|
-
*/
|
|
272
|
-
const cleanOrderColumnsForInnoDB = async ({
|
|
285
|
+
const cleanOrderColumnsForOldDatabases = async ({
|
|
273
286
|
id,
|
|
274
287
|
attribute,
|
|
275
288
|
db,
|
|
@@ -306,9 +319,6 @@ const cleanOrderColumnsForInnoDB = async ({
|
|
|
306
319
|
}
|
|
307
320
|
)
|
|
308
321
|
.transacting(trx);
|
|
309
|
-
|
|
310
|
-
// raw query as knex doesn't allow updating from a subquery
|
|
311
|
-
// https://github.com/knex/knex/issues/2504
|
|
312
322
|
await db.connection
|
|
313
323
|
.raw(
|
|
314
324
|
`UPDATE ?? as a, (SELECT * FROM ??) AS b
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Responsible for calculating the relations order when connecting them.
|
|
7
|
+
*
|
|
8
|
+
* The connect method takes an array of relations with positional attributes:
|
|
9
|
+
* - before: the id of the relation to connect before
|
|
10
|
+
* - after: the id of the relation to connect after
|
|
11
|
+
* - end: it should be at the end
|
|
12
|
+
* - start: it should be at the start
|
|
13
|
+
*
|
|
14
|
+
* Example:
|
|
15
|
+
* - Having a connect array like:
|
|
16
|
+
* [ { id: 4, before: 2 }, { id: 4, before: 3}, {id: 5, before: 4} ]
|
|
17
|
+
* - With the initial relations:
|
|
18
|
+
* [ { id: 2, order: 4 }, { id: 3, order: 10 } ]
|
|
19
|
+
* - Step by step, going through the connect array, the array of relations would be:
|
|
20
|
+
* [ { id: 4, order: 3.5 }, { id: 2, order: 4 }, { id: 3, order: 10 } ]
|
|
21
|
+
* [ { id: 2, order: 4 }, { id: 4, order: 3.5 }, { id: 3, order: 10 } ]
|
|
22
|
+
* [ { id: 2, order: 4 }, { id: 5, order: 3.5 }, { id: 4, order: 3.5 }, { id: 3, order: 10 } ]
|
|
23
|
+
* - The final step would be to recalculate fractional order values.
|
|
24
|
+
* [ { id: 2, order: 4 }, { id: 5, order: 3.33 }, { id: 4, order: 3.66 }, { id: 3, order: 10 } ]
|
|
25
|
+
*
|
|
26
|
+
* Constraints:
|
|
27
|
+
* - Expects you will never connect a relation before / after one that does not exist
|
|
28
|
+
* - Expect initArr to have all relations referenced in the positional attributes
|
|
29
|
+
*
|
|
30
|
+
* @param {Array<*>} initArr - array of relations to initialize the class with
|
|
31
|
+
* @param {string} idColumn - the column name of the id
|
|
32
|
+
* @param {string} orderColumn - the column name of the order
|
|
33
|
+
* @return {*}
|
|
34
|
+
*/
|
|
35
|
+
const relationsOrderer = (initArr, idColumn, orderColumn) => {
|
|
36
|
+
const arr = _.castArray(initArr || []).map((r) => ({
|
|
37
|
+
init: true,
|
|
38
|
+
id: r[idColumn],
|
|
39
|
+
order: r[orderColumn],
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const maxOrder = _.maxBy('order', arr)?.order || 0;
|
|
43
|
+
|
|
44
|
+
// TODO: Improve performance by using a map
|
|
45
|
+
const findRelation = (id) => {
|
|
46
|
+
const idx = arr.findIndex((r) => r.id === id);
|
|
47
|
+
return { idx, relation: arr[idx] };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const removeRelation = (r) => {
|
|
51
|
+
const { idx } = findRelation(r.id);
|
|
52
|
+
if (idx >= 0) {
|
|
53
|
+
arr.splice(idx, 1);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const insertRelation = (r) => {
|
|
58
|
+
let idx;
|
|
59
|
+
|
|
60
|
+
if (r.position?.before) {
|
|
61
|
+
const { idx: _idx, relation } = findRelation(r.position.before);
|
|
62
|
+
if (relation.init) r.order = relation.order - 0.5;
|
|
63
|
+
else r.order = relation.order;
|
|
64
|
+
idx = _idx;
|
|
65
|
+
} else if (r.position?.after) {
|
|
66
|
+
const { idx: _idx, relation } = findRelation(r.position.after);
|
|
67
|
+
if (relation.init) r.order = relation.order + 0.5;
|
|
68
|
+
else r.order = relation.order;
|
|
69
|
+
idx = _idx + 1;
|
|
70
|
+
} else if (r.position?.start) {
|
|
71
|
+
r.order = 0.5;
|
|
72
|
+
idx = 0;
|
|
73
|
+
} else {
|
|
74
|
+
r.order = maxOrder + 0.5;
|
|
75
|
+
idx = arr.length;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Insert the relation in the array
|
|
79
|
+
arr.splice(idx, 0, r);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
disconnect(relations) {
|
|
84
|
+
_.castArray(relations).forEach((relation) => {
|
|
85
|
+
removeRelation(relation);
|
|
86
|
+
});
|
|
87
|
+
return this;
|
|
88
|
+
},
|
|
89
|
+
connect(relations) {
|
|
90
|
+
_.castArray(relations).forEach((relation) => {
|
|
91
|
+
this.disconnect(relation);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
insertRelation(relation);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
strapi.log.error(err);
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Could not connect ${relation.id}, position ${JSON.stringify(
|
|
99
|
+
relation.position
|
|
100
|
+
)} is invalid`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return this;
|
|
105
|
+
},
|
|
106
|
+
get() {
|
|
107
|
+
return arr;
|
|
108
|
+
},
|
|
109
|
+
/**
|
|
110
|
+
* Get a map between the relation id and its order
|
|
111
|
+
*/
|
|
112
|
+
getOrderMap() {
|
|
113
|
+
return _(arr)
|
|
114
|
+
.groupBy('order')
|
|
115
|
+
.reduce((acc, relations) => {
|
|
116
|
+
if (relations[0]?.init) return acc;
|
|
117
|
+
relations.forEach((relation, idx) => {
|
|
118
|
+
acc[relation.id] = Math.floor(relation.order) + (idx + 1) / (relations.length + 1);
|
|
119
|
+
});
|
|
120
|
+
return acc;
|
|
121
|
+
}, {});
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
module.exports = relationsOrderer;
|
package/lib/metadata/index.js
CHANGED
|
@@ -231,7 +231,7 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
|
|
|
231
231
|
type: 'string',
|
|
232
232
|
},
|
|
233
233
|
order: {
|
|
234
|
-
type: '
|
|
234
|
+
type: 'float',
|
|
235
235
|
column: {
|
|
236
236
|
unsigned: true,
|
|
237
237
|
},
|
|
@@ -485,7 +485,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
485
485
|
// order
|
|
486
486
|
if (isAnyToMany(attribute)) {
|
|
487
487
|
metadataSchema.attributes[orderColumnName] = {
|
|
488
|
-
type: '
|
|
488
|
+
type: 'float',
|
|
489
489
|
column: {
|
|
490
490
|
unsigned: true,
|
|
491
491
|
defaultTo: null,
|
|
@@ -502,7 +502,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
502
502
|
// inv order
|
|
503
503
|
if (isBidirectional(attribute) && isManyToAny(attribute)) {
|
|
504
504
|
metadataSchema.attributes[inverseOrderColumnName] = {
|
|
505
|
-
type: '
|
|
505
|
+
type: 'float',
|
|
506
506
|
column: {
|
|
507
507
|
unsigned: true,
|
|
508
508
|
defaultTo: null,
|
|
@@ -53,7 +53,7 @@ const castValue = (value, attribute) => {
|
|
|
53
53
|
return value;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
if (types.isScalar(attribute.type)) {
|
|
56
|
+
if (types.isScalar(attribute.type) && !isKnexQuery(value)) {
|
|
57
57
|
const field = createField(attribute);
|
|
58
58
|
|
|
59
59
|
return value === null ? null : field.toDB(value);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/database",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0-beta.0",
|
|
4
4
|
"description": "Strapi's database layer",
|
|
5
5
|
"homepage": "https://strapi.io",
|
|
6
6
|
"bugs": {
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"node": ">=14.19.1 <=18.x.x",
|
|
44
44
|
"npm": ">=6.0.0"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "c0c3365ad801d088a6ab6c4eb95a014078429747"
|
|
47
47
|
}
|