@strapi/database 4.4.3 → 4.5.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/entity-manager/entity-repository.js +51 -12
- package/lib/entity-manager/index.js +407 -100
- package/lib/entity-manager/morph-relations.js +6 -2
- package/lib/entity-manager/regular-relations.js +283 -0
- package/lib/metadata/index.js +9 -1
- package/lib/metadata/relations.js +75 -3
- package/lib/query/helpers/join.js +9 -8
- package/lib/query/helpers/populate/apply.js +646 -0
- package/lib/query/helpers/populate/index.js +9 -0
- package/lib/query/helpers/populate/process.js +96 -0
- package/lib/query/helpers/where.js +3 -2
- package/lib/query/query-builder.js +102 -23
- package/lib/tests/knex-utils.test.e2e.js +33 -0
- package/lib/utils/knex.js +12 -0
- package/package.json +2 -2
- package/lib/query/helpers/populate.js +0 -649
|
@@ -20,7 +20,7 @@ const getMorphToManyRowsLinkedToMorphOne = (rows, { uid, attributeName, typeColu
|
|
|
20
20
|
|
|
21
21
|
const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (
|
|
22
22
|
rows,
|
|
23
|
-
{ uid, attributeName, joinTable, db }
|
|
23
|
+
{ uid, attributeName, joinTable, db, transaction: trx }
|
|
24
24
|
) => {
|
|
25
25
|
const { morphColumn } = joinTable;
|
|
26
26
|
const { idColumn, typeColumn } = morphColumn;
|
|
@@ -50,7 +50,11 @@ const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
if (!isEmpty(orWhere)) {
|
|
53
|
-
await createQueryBuilder(joinTable.name, db)
|
|
53
|
+
await createQueryBuilder(joinTable.name, db)
|
|
54
|
+
.delete()
|
|
55
|
+
.where({ $or: orWhere })
|
|
56
|
+
.transacting(trx)
|
|
57
|
+
.execute();
|
|
54
58
|
}
|
|
55
59
|
};
|
|
56
60
|
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { map, isEmpty } = require('lodash/fp');
|
|
4
|
+
const {
|
|
5
|
+
isBidirectional,
|
|
6
|
+
isOneToAny,
|
|
7
|
+
isManyToAny,
|
|
8
|
+
isAnyToOne,
|
|
9
|
+
hasOrderColumn,
|
|
10
|
+
hasInverseOrderColumn,
|
|
11
|
+
} = require('../metadata/relations');
|
|
12
|
+
const { createQueryBuilder } = require('../query');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* If some relations currently exist for this oneToX relation, on the one side, this function removes them and update the inverse order if needed.
|
|
16
|
+
* @param {Object} params
|
|
17
|
+
* @param {string} params.id - entity id on which the relations for entities relIdsToadd are created
|
|
18
|
+
* @param {string} params.attribute - attribute of the relation
|
|
19
|
+
* @param {string} params.inverseRelIds - entity ids of the inverse side for which the current relations will be deleted
|
|
20
|
+
* @param {string} params.db - database instance
|
|
21
|
+
*/
|
|
22
|
+
const deletePreviousOneToAnyRelations = async ({
|
|
23
|
+
id,
|
|
24
|
+
attribute,
|
|
25
|
+
relIdsToadd,
|
|
26
|
+
db,
|
|
27
|
+
transaction: trx,
|
|
28
|
+
}) => {
|
|
29
|
+
if (!(isBidirectional(attribute) && isOneToAny(attribute))) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
'deletePreviousOneToAnyRelations can only be called for bidirectional oneToAny relations'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const { joinTable } = attribute;
|
|
35
|
+
const { joinColumn, inverseJoinColumn } = joinTable;
|
|
36
|
+
|
|
37
|
+
await createQueryBuilder(joinTable.name, db)
|
|
38
|
+
.delete()
|
|
39
|
+
.where({
|
|
40
|
+
[inverseJoinColumn.name]: relIdsToadd,
|
|
41
|
+
[joinColumn.name]: { $ne: id },
|
|
42
|
+
})
|
|
43
|
+
.where(joinTable.on || {})
|
|
44
|
+
.transacting(trx)
|
|
45
|
+
.execute();
|
|
46
|
+
|
|
47
|
+
await cleanOrderColumns({ attribute, db, inverseRelIds: relIdsToadd, transaction: trx });
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* If a relation currently exists for this xToOne relations, this function removes it and update the inverse order if needed.
|
|
52
|
+
* @param {Object} params
|
|
53
|
+
* @param {string} params.id - entity id on which the relation for entity relIdToadd is created
|
|
54
|
+
* @param {string} params.attribute - attribute of the relation
|
|
55
|
+
* @param {string} params.relIdToadd - entity id of the new relation
|
|
56
|
+
* @param {string} params.db - database instance
|
|
57
|
+
*/
|
|
58
|
+
const deletePreviousAnyToOneRelations = async ({
|
|
59
|
+
id,
|
|
60
|
+
attribute,
|
|
61
|
+
relIdToadd,
|
|
62
|
+
db,
|
|
63
|
+
transaction: trx,
|
|
64
|
+
}) => {
|
|
65
|
+
const { joinTable } = attribute;
|
|
66
|
+
const { joinColumn, inverseJoinColumn } = joinTable;
|
|
67
|
+
|
|
68
|
+
if (!(isBidirectional(attribute) && isAnyToOne(attribute))) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'deletePreviousAnyToOneRelations can only be called for bidirectional anyToOne relations'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
// handling manyToOne
|
|
74
|
+
if (isManyToAny(attribute)) {
|
|
75
|
+
// if the database integrity was not broken relsToDelete is supposed to be of length 1
|
|
76
|
+
const relsToDelete = await createQueryBuilder(joinTable.name, db)
|
|
77
|
+
.select(inverseJoinColumn.name)
|
|
78
|
+
.where({
|
|
79
|
+
[joinColumn.name]: id,
|
|
80
|
+
[inverseJoinColumn.name]: { $ne: relIdToadd },
|
|
81
|
+
})
|
|
82
|
+
.where(joinTable.on || {})
|
|
83
|
+
.transacting(trx)
|
|
84
|
+
.execute();
|
|
85
|
+
|
|
86
|
+
const relIdsToDelete = map(inverseJoinColumn.name, relsToDelete);
|
|
87
|
+
|
|
88
|
+
await createQueryBuilder(joinTable.name, db)
|
|
89
|
+
.delete()
|
|
90
|
+
.where({
|
|
91
|
+
[joinColumn.name]: id,
|
|
92
|
+
[inverseJoinColumn.name]: { $in: relIdsToDelete },
|
|
93
|
+
})
|
|
94
|
+
.where(joinTable.on || {})
|
|
95
|
+
.transacting(trx)
|
|
96
|
+
.execute();
|
|
97
|
+
|
|
98
|
+
await cleanOrderColumns({ attribute, db, inverseRelIds: relIdsToDelete, transaction: trx });
|
|
99
|
+
|
|
100
|
+
// handling oneToOne
|
|
101
|
+
} else {
|
|
102
|
+
await createQueryBuilder(joinTable.name, db)
|
|
103
|
+
.delete()
|
|
104
|
+
.where({
|
|
105
|
+
[joinColumn.name]: id,
|
|
106
|
+
[inverseJoinColumn.name]: { $ne: relIdToadd },
|
|
107
|
+
})
|
|
108
|
+
.where(joinTable.on || {})
|
|
109
|
+
.transacting(trx)
|
|
110
|
+
.execute();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Delete all or some relations of entity field
|
|
116
|
+
* @param {Object} params
|
|
117
|
+
* @param {string} params.id - entity id for which the relations will be deleted
|
|
118
|
+
* @param {string} params.attribute - attribute of the relation
|
|
119
|
+
* @param {string} params.db - database instance
|
|
120
|
+
* @param {string} params.relIdsToDelete - ids of entities to remove from the relations. Also accepts 'all'
|
|
121
|
+
* @param {string} params.relIdsToNotDelete - ids of entities to not remove from the relation when relIdsToDelete equals 'all'
|
|
122
|
+
*/
|
|
123
|
+
const deleteRelations = async ({
|
|
124
|
+
id,
|
|
125
|
+
attribute,
|
|
126
|
+
db,
|
|
127
|
+
relIdsToNotDelete = [],
|
|
128
|
+
relIdsToDelete = [],
|
|
129
|
+
transaction: trx,
|
|
130
|
+
}) => {
|
|
131
|
+
const { joinTable } = attribute;
|
|
132
|
+
const { joinColumn, inverseJoinColumn } = joinTable;
|
|
133
|
+
const all = relIdsToDelete === 'all';
|
|
134
|
+
|
|
135
|
+
if (hasOrderColumn(attribute) || hasInverseOrderColumn(attribute)) {
|
|
136
|
+
let lastId = 0;
|
|
137
|
+
let done = false;
|
|
138
|
+
const batchSize = 100;
|
|
139
|
+
while (!done) {
|
|
140
|
+
const batchToDelete = await createQueryBuilder(joinTable.name, db)
|
|
141
|
+
.select(inverseJoinColumn.name)
|
|
142
|
+
.where({
|
|
143
|
+
[joinColumn.name]: id,
|
|
144
|
+
id: { $gt: lastId },
|
|
145
|
+
[inverseJoinColumn.name]: { $notIn: relIdsToNotDelete },
|
|
146
|
+
...(all ? {} : { [inverseJoinColumn.name]: { $in: relIdsToDelete } }),
|
|
147
|
+
})
|
|
148
|
+
.where(joinTable.on || {})
|
|
149
|
+
.orderBy('id')
|
|
150
|
+
.limit(batchSize)
|
|
151
|
+
.transacting(trx)
|
|
152
|
+
.execute();
|
|
153
|
+
done = batchToDelete.length < batchSize;
|
|
154
|
+
lastId = batchToDelete[batchToDelete.length - 1]?.id;
|
|
155
|
+
|
|
156
|
+
const batchIds = map(inverseJoinColumn.name, batchToDelete);
|
|
157
|
+
|
|
158
|
+
await createQueryBuilder(joinTable.name, db)
|
|
159
|
+
.delete()
|
|
160
|
+
.where({
|
|
161
|
+
[joinColumn.name]: id,
|
|
162
|
+
[inverseJoinColumn.name]: { $in: batchIds },
|
|
163
|
+
})
|
|
164
|
+
.where(joinTable.on || {})
|
|
165
|
+
.transacting(trx)
|
|
166
|
+
.execute();
|
|
167
|
+
|
|
168
|
+
await cleanOrderColumns({ attribute, db, id, inverseRelIds: batchIds, transaction: trx });
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
await createQueryBuilder(joinTable.name, db)
|
|
172
|
+
.delete()
|
|
173
|
+
.where({
|
|
174
|
+
[joinColumn.name]: id,
|
|
175
|
+
[inverseJoinColumn.name]: { $notIn: relIdsToNotDelete },
|
|
176
|
+
...(all ? {} : { [inverseJoinColumn.name]: { $in: relIdsToDelete } }),
|
|
177
|
+
})
|
|
178
|
+
.where(joinTable.on || {})
|
|
179
|
+
.transacting(trx)
|
|
180
|
+
.execute();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Clean the order columns by ensuring the order value are continuous (ex: 1, 2, 3 and not 1, 5, 10)
|
|
186
|
+
* @param {Object} params
|
|
187
|
+
* @param {string} params.id - entity id for which the clean will be done
|
|
188
|
+
* @param {string} params.attribute - attribute of the relation
|
|
189
|
+
* @param {string} params.db - database instance
|
|
190
|
+
* @param {string} params.inverseRelIds - entity ids of the inverse side for which the clean will be done
|
|
191
|
+
*/
|
|
192
|
+
const cleanOrderColumns = async ({ id, attribute, db, inverseRelIds, transaction: trx }) => {
|
|
193
|
+
if (
|
|
194
|
+
!(hasOrderColumn(attribute) && id) &&
|
|
195
|
+
!(hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds))
|
|
196
|
+
) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const { joinTable } = attribute;
|
|
201
|
+
const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
|
|
202
|
+
const update = [];
|
|
203
|
+
const updateBinding = [];
|
|
204
|
+
const select = ['??'];
|
|
205
|
+
const selectBinding = ['id'];
|
|
206
|
+
const where = [];
|
|
207
|
+
const whereBinding = [];
|
|
208
|
+
|
|
209
|
+
if (hasOrderColumn(attribute) && id) {
|
|
210
|
+
update.push('?? = b.src_order');
|
|
211
|
+
updateBinding.push(orderColumnName);
|
|
212
|
+
select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS src_order');
|
|
213
|
+
selectBinding.push(joinColumn.name, orderColumnName);
|
|
214
|
+
where.push('?? = ?');
|
|
215
|
+
whereBinding.push(joinColumn.name, id);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds)) {
|
|
219
|
+
update.push('?? = b.inv_order');
|
|
220
|
+
updateBinding.push(inverseOrderColumnName);
|
|
221
|
+
select.push('ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ??) AS inv_order');
|
|
222
|
+
selectBinding.push(inverseJoinColumn.name, inverseOrderColumnName);
|
|
223
|
+
where.push(`?? IN (${inverseRelIds.map(() => '?').join(', ')})`);
|
|
224
|
+
whereBinding.push(inverseJoinColumn.name, ...inverseRelIds);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// raw query as knex doesn't allow updating from a subquery
|
|
228
|
+
// https://github.com/knex/knex/issues/2504
|
|
229
|
+
switch (strapi.db.dialect.client) {
|
|
230
|
+
case 'mysql':
|
|
231
|
+
await db
|
|
232
|
+
.getConnection()
|
|
233
|
+
.raw(
|
|
234
|
+
`UPDATE
|
|
235
|
+
?? as a,
|
|
236
|
+
(
|
|
237
|
+
SELECT ${select.join(', ')}
|
|
238
|
+
FROM ??
|
|
239
|
+
WHERE ${where.join(' OR ')}
|
|
240
|
+
) AS b
|
|
241
|
+
SET ${update.join(', ')}
|
|
242
|
+
WHERE b.id = a.id`,
|
|
243
|
+
[joinTable.name, ...selectBinding, joinTable.name, ...whereBinding, ...updateBinding]
|
|
244
|
+
)
|
|
245
|
+
.transacting(trx);
|
|
246
|
+
break;
|
|
247
|
+
default:
|
|
248
|
+
await db
|
|
249
|
+
.getConnection()
|
|
250
|
+
.raw(
|
|
251
|
+
`UPDATE ?? as a
|
|
252
|
+
SET ${update.join(', ')}
|
|
253
|
+
FROM (
|
|
254
|
+
SELECT ${select.join(', ')}
|
|
255
|
+
FROM ??
|
|
256
|
+
WHERE ${where.join(' OR ')}
|
|
257
|
+
) AS b
|
|
258
|
+
WHERE b.id = a.id`,
|
|
259
|
+
[joinTable.name, ...updateBinding, ...selectBinding, joinTable.name, ...whereBinding]
|
|
260
|
+
)
|
|
261
|
+
.transacting(trx);
|
|
262
|
+
/*
|
|
263
|
+
`UPDATE :joinTable: as a
|
|
264
|
+
SET :orderColumn: = b.src_order, :inverseOrderColumn: = b.inv_order
|
|
265
|
+
FROM (
|
|
266
|
+
SELECT
|
|
267
|
+
id,
|
|
268
|
+
ROW_NUMBER() OVER ( PARTITION BY :joinColumn: ORDER BY :orderColumn:) AS src_order,
|
|
269
|
+
ROW_NUMBER() OVER ( PARTITION BY :inverseJoinColumn: ORDER BY :inverseOrderColumn:) AS inv_order
|
|
270
|
+
FROM :joinTable:
|
|
271
|
+
WHERE :joinColumn: = :id OR :inverseJoinColumn: IN (:inverseRelIds)
|
|
272
|
+
) AS b
|
|
273
|
+
WHERE b.id = a.id`,
|
|
274
|
+
*/
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
module.exports = {
|
|
279
|
+
deletePreviousOneToAnyRelations,
|
|
280
|
+
deletePreviousAnyToOneRelations,
|
|
281
|
+
deleteRelations,
|
|
282
|
+
cleanOrderColumns,
|
|
283
|
+
};
|
package/lib/metadata/index.js
CHANGED
|
@@ -123,7 +123,7 @@ const createCompoLinkModelMeta = (baseModelMeta) => {
|
|
|
123
123
|
type: 'integer',
|
|
124
124
|
column: {
|
|
125
125
|
unsigned: true,
|
|
126
|
-
defaultTo:
|
|
126
|
+
defaultTo: null,
|
|
127
127
|
},
|
|
128
128
|
},
|
|
129
129
|
},
|
|
@@ -142,6 +142,11 @@ const createCompoLinkModelMeta = (baseModelMeta) => {
|
|
|
142
142
|
name: `${baseModelMeta.tableName}_entity_fk`,
|
|
143
143
|
columns: ['entity_id'],
|
|
144
144
|
},
|
|
145
|
+
{
|
|
146
|
+
name: `${baseModelMeta.tableName}_unique`,
|
|
147
|
+
columns: ['entity_id', 'component_id', 'field', 'component_type'],
|
|
148
|
+
type: 'unique',
|
|
149
|
+
},
|
|
145
150
|
],
|
|
146
151
|
foreignKeys: [
|
|
147
152
|
{
|
|
@@ -183,6 +188,7 @@ const createDynamicZone = (attributeName, attribute, meta) => {
|
|
|
183
188
|
orderBy: {
|
|
184
189
|
order: 'asc',
|
|
185
190
|
},
|
|
191
|
+
pivotColumns: ['entity_id', 'component_id', 'field', 'component_type'],
|
|
186
192
|
},
|
|
187
193
|
});
|
|
188
194
|
};
|
|
@@ -205,9 +211,11 @@ const createComponent = (attributeName, attribute, meta) => {
|
|
|
205
211
|
on: {
|
|
206
212
|
field: attributeName,
|
|
207
213
|
},
|
|
214
|
+
orderColumnName: 'order',
|
|
208
215
|
orderBy: {
|
|
209
216
|
order: 'asc',
|
|
210
217
|
},
|
|
218
|
+
pivotColumns: ['entity_id', 'component_id', 'field', 'component_type'],
|
|
211
219
|
},
|
|
212
220
|
});
|
|
213
221
|
};
|
|
@@ -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;
|
|
@@ -269,6 +272,7 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
|
|
|
269
272
|
orderBy: {
|
|
270
273
|
order: 'asc',
|
|
271
274
|
},
|
|
275
|
+
pivotColumns: [joinColumnName, typeColumnName, idColumnName],
|
|
272
276
|
};
|
|
273
277
|
|
|
274
278
|
attribute.joinTable = joinTable;
|
|
@@ -398,12 +402,20 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
398
402
|
const joinColumnName = _.snakeCase(`${meta.singularName}_id`);
|
|
399
403
|
let inverseJoinColumnName = _.snakeCase(`${targetMeta.singularName}_id`);
|
|
400
404
|
|
|
401
|
-
// if relation is
|
|
405
|
+
// if relation is self referencing
|
|
402
406
|
if (joinColumnName === inverseJoinColumnName) {
|
|
403
407
|
inverseJoinColumnName = `inv_${inverseJoinColumnName}`;
|
|
404
408
|
}
|
|
405
409
|
|
|
406
|
-
|
|
410
|
+
const orderColumnName = _.snakeCase(`${targetMeta.singularName}_order`);
|
|
411
|
+
let inverseOrderColumnName = _.snakeCase(`${meta.singularName}_order`);
|
|
412
|
+
|
|
413
|
+
// if relation is self referencing
|
|
414
|
+
if (attribute.relation === 'manyToMany' && joinColumnName === inverseJoinColumnName) {
|
|
415
|
+
inverseOrderColumnName = `inv_${inverseOrderColumnName}`;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const metadataSchema = {
|
|
407
419
|
uid: joinTableName,
|
|
408
420
|
tableName: joinTableName,
|
|
409
421
|
attributes: {
|
|
@@ -433,6 +445,11 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
433
445
|
name: `${joinTableName}_inv_fk`,
|
|
434
446
|
columns: [inverseJoinColumnName],
|
|
435
447
|
},
|
|
448
|
+
{
|
|
449
|
+
name: `${joinTableName}_unique`,
|
|
450
|
+
columns: [joinColumnName, inverseJoinColumnName],
|
|
451
|
+
type: 'unique',
|
|
452
|
+
},
|
|
436
453
|
],
|
|
437
454
|
foreignKeys: [
|
|
438
455
|
{
|
|
@@ -450,7 +467,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
450
467
|
onDelete: 'CASCADE',
|
|
451
468
|
},
|
|
452
469
|
],
|
|
453
|
-
}
|
|
470
|
+
};
|
|
454
471
|
|
|
455
472
|
const joinTable = {
|
|
456
473
|
name: joinTableName,
|
|
@@ -462,8 +479,46 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
462
479
|
name: inverseJoinColumnName,
|
|
463
480
|
referencedColumn: 'id',
|
|
464
481
|
},
|
|
482
|
+
pivotColumns: [joinColumnName, inverseJoinColumnName],
|
|
465
483
|
};
|
|
466
484
|
|
|
485
|
+
// order
|
|
486
|
+
if (isAnyToMany(attribute)) {
|
|
487
|
+
metadataSchema.attributes[orderColumnName] = {
|
|
488
|
+
type: 'integer',
|
|
489
|
+
column: {
|
|
490
|
+
unsigned: true,
|
|
491
|
+
defaultTo: null,
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
metadataSchema.indexes.push({
|
|
495
|
+
name: `${joinTableName}_order_fk`,
|
|
496
|
+
columns: [orderColumnName],
|
|
497
|
+
});
|
|
498
|
+
joinTable.orderColumnName = orderColumnName;
|
|
499
|
+
joinTable.orderBy = { [orderColumnName]: 'asc' };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// inv order
|
|
503
|
+
if (isBidirectional(attribute) && isManyToAny(attribute)) {
|
|
504
|
+
metadataSchema.attributes[inverseOrderColumnName] = {
|
|
505
|
+
type: 'integer',
|
|
506
|
+
column: {
|
|
507
|
+
unsigned: true,
|
|
508
|
+
defaultTo: null,
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
metadataSchema.indexes.push({
|
|
513
|
+
name: `${joinTableName}_order_inv_fk`,
|
|
514
|
+
columns: [inverseOrderColumnName],
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
joinTable.inverseOrderColumnName = inverseOrderColumnName;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
metadata.add(metadataSchema);
|
|
521
|
+
|
|
467
522
|
attribute.joinTable = joinTable;
|
|
468
523
|
|
|
469
524
|
if (isBidirectional(attribute)) {
|
|
@@ -479,13 +534,30 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
|
|
479
534
|
name: joinTableName,
|
|
480
535
|
joinColumn: joinTable.inverseJoinColumn,
|
|
481
536
|
inverseJoinColumn: joinTable.joinColumn,
|
|
537
|
+
pivotColumns: joinTable.pivotColumns,
|
|
482
538
|
};
|
|
539
|
+
|
|
540
|
+
if (isManyToAny(attribute)) {
|
|
541
|
+
inverseAttribute.joinTable.orderColumnName = inverseOrderColumnName;
|
|
542
|
+
inverseAttribute.joinTable.orderBy = { [inverseOrderColumnName]: 'asc' };
|
|
543
|
+
}
|
|
544
|
+
if (isAnyToMany(attribute)) {
|
|
545
|
+
inverseAttribute.joinTable.inverseOrderColumnName = orderColumnName;
|
|
546
|
+
}
|
|
483
547
|
}
|
|
484
548
|
};
|
|
485
549
|
|
|
550
|
+
const hasOrderColumn = (attribute) => isAnyToMany(attribute);
|
|
551
|
+
const hasInverseOrderColumn = (attribute) => isBidirectional(attribute) && isManyToAny(attribute);
|
|
552
|
+
|
|
486
553
|
module.exports = {
|
|
487
554
|
createRelation,
|
|
488
555
|
|
|
489
556
|
isBidirectional,
|
|
490
557
|
isOneToAny,
|
|
558
|
+
isManyToAny,
|
|
559
|
+
isAnyToOne,
|
|
560
|
+
isAnyToMany,
|
|
561
|
+
hasOrderColumn,
|
|
562
|
+
hasInverseOrderColumn,
|
|
491
563
|
};
|
|
@@ -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;
|