@strapi/database 4.5.0-alpha.0 → 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 +337 -542
- package/lib/entity-manager/morph-relations.js +6 -2
- package/lib/entity-manager/regular-relations.js +283 -0
- package/lib/metadata/index.js +9 -2
- package/lib/metadata/relations.js +15 -2
- 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/query-builder.js +29 -0
- package/package.json +2 -2
- package/lib/query/helpers/populate.js +0 -649
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
const { fromRow } = require('../transform');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Populate oneToOne and manyToOne relation
|
|
9
|
+
* @param {*} input
|
|
10
|
+
* @param {*} ctx
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
const XtoOne = async (input, ctx) => {
|
|
14
|
+
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
|
|
15
|
+
const { db, qb } = ctx;
|
|
16
|
+
|
|
17
|
+
const fromTargetRow = (rowOrRows) => fromRow(targetMeta, rowOrRows);
|
|
18
|
+
|
|
19
|
+
if (attribute.joinColumn) {
|
|
20
|
+
const { name: joinColumnName, referencedColumn: referencedColumnName } = attribute.joinColumn;
|
|
21
|
+
|
|
22
|
+
const referencedValues = _.uniq(
|
|
23
|
+
results.map((r) => r[joinColumnName]).filter((value) => !_.isNil(value))
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (_.isEmpty(referencedValues)) {
|
|
27
|
+
results.forEach((result) => {
|
|
28
|
+
result[attributeName] = null;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const rows = await db.entityManager
|
|
35
|
+
.createQueryBuilder(targetMeta.uid)
|
|
36
|
+
.init(populateValue)
|
|
37
|
+
.addSelect(`${qb.alias}.${referencedColumnName}`)
|
|
38
|
+
.where({ [referencedColumnName]: referencedValues })
|
|
39
|
+
.execute({ mapResults: false });
|
|
40
|
+
|
|
41
|
+
const map = _.groupBy(referencedColumnName, rows);
|
|
42
|
+
|
|
43
|
+
results.forEach((result) => {
|
|
44
|
+
result[attributeName] = fromTargetRow(_.first(map[result[joinColumnName]]));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (attribute.joinTable) {
|
|
51
|
+
const { joinTable } = attribute;
|
|
52
|
+
|
|
53
|
+
const qb = db.entityManager.createQueryBuilder(targetMeta.uid);
|
|
54
|
+
|
|
55
|
+
const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
|
|
56
|
+
|
|
57
|
+
const alias = qb.getAlias();
|
|
58
|
+
const joinColAlias = `${alias}.${joinColumnName}`;
|
|
59
|
+
|
|
60
|
+
const referencedValues = _.uniq(
|
|
61
|
+
results.map((r) => r[referencedColumnName]).filter((value) => !_.isNil(value))
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (isCount) {
|
|
65
|
+
if (_.isEmpty(referencedValues)) {
|
|
66
|
+
results.forEach((result) => {
|
|
67
|
+
result[attributeName] = { count: 0 };
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const rows = await qb
|
|
73
|
+
.init(populateValue)
|
|
74
|
+
.join({
|
|
75
|
+
alias,
|
|
76
|
+
referencedTable: joinTable.name,
|
|
77
|
+
referencedColumn: joinTable.inverseJoinColumn.name,
|
|
78
|
+
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
79
|
+
rootTable: qb.alias,
|
|
80
|
+
on: joinTable.on,
|
|
81
|
+
})
|
|
82
|
+
.select([joinColAlias, qb.raw('count(*) AS count')])
|
|
83
|
+
.where({ [joinColAlias]: referencedValues })
|
|
84
|
+
.groupBy(joinColAlias)
|
|
85
|
+
.execute({ mapResults: false });
|
|
86
|
+
|
|
87
|
+
const map = rows.reduce((map, row) => {
|
|
88
|
+
map[row[joinColumnName]] = { count: Number(row.count) };
|
|
89
|
+
return map;
|
|
90
|
+
}, {});
|
|
91
|
+
|
|
92
|
+
results.forEach((result) => {
|
|
93
|
+
result[attributeName] = map[result[referencedColumnName]] || { count: 0 };
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (_.isEmpty(referencedValues)) {
|
|
100
|
+
results.forEach((result) => {
|
|
101
|
+
result[attributeName] = null;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const rows = await qb
|
|
108
|
+
.init(populateValue)
|
|
109
|
+
.join({
|
|
110
|
+
alias,
|
|
111
|
+
referencedTable: joinTable.name,
|
|
112
|
+
referencedColumn: joinTable.inverseJoinColumn.name,
|
|
113
|
+
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
114
|
+
rootTable: qb.alias,
|
|
115
|
+
on: joinTable.on,
|
|
116
|
+
orderBy: joinTable.orderBy,
|
|
117
|
+
})
|
|
118
|
+
.addSelect(joinColAlias)
|
|
119
|
+
.where({ [joinColAlias]: referencedValues })
|
|
120
|
+
.execute({ mapResults: false });
|
|
121
|
+
|
|
122
|
+
const map = _.groupBy(joinColumnName, rows);
|
|
123
|
+
|
|
124
|
+
results.forEach((result) => {
|
|
125
|
+
result[attributeName] = fromTargetRow(_.first(map[result[referencedColumnName]]));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const oneToMany = async (input, ctx) => {
|
|
131
|
+
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
|
|
132
|
+
const { db, qb } = ctx;
|
|
133
|
+
|
|
134
|
+
const fromTargetRow = (rowOrRows) => fromRow(targetMeta, rowOrRows);
|
|
135
|
+
|
|
136
|
+
if (attribute.joinColumn) {
|
|
137
|
+
const { name: joinColumnName, referencedColumn: referencedColumnName } = attribute.joinColumn;
|
|
138
|
+
|
|
139
|
+
const referencedValues = _.uniq(
|
|
140
|
+
results.map((r) => r[joinColumnName]).filter((value) => !_.isNil(value))
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (_.isEmpty(referencedValues)) {
|
|
144
|
+
results.forEach((result) => {
|
|
145
|
+
result[attributeName] = null;
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const rows = await db.entityManager
|
|
151
|
+
.createQueryBuilder(targetMeta.uid)
|
|
152
|
+
.init(populateValue)
|
|
153
|
+
.addSelect(`${qb.alias}.${referencedColumnName}`)
|
|
154
|
+
.where({ [referencedColumnName]: referencedValues })
|
|
155
|
+
.execute({ mapResults: false });
|
|
156
|
+
|
|
157
|
+
const map = _.groupBy(referencedColumnName, rows);
|
|
158
|
+
|
|
159
|
+
results.forEach((result) => {
|
|
160
|
+
result[attributeName] = fromTargetRow(map[result[joinColumnName]] || []);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (attribute.joinTable) {
|
|
167
|
+
const { joinTable } = attribute;
|
|
168
|
+
|
|
169
|
+
const qb = db.entityManager.createQueryBuilder(targetMeta.uid);
|
|
170
|
+
|
|
171
|
+
const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
|
|
172
|
+
|
|
173
|
+
const alias = qb.getAlias();
|
|
174
|
+
const joinColAlias = `${alias}.${joinColumnName}`;
|
|
175
|
+
|
|
176
|
+
const referencedValues = _.uniq(
|
|
177
|
+
results.map((r) => r[referencedColumnName]).filter((value) => !_.isNil(value))
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
if (isCount) {
|
|
181
|
+
if (_.isEmpty(referencedValues)) {
|
|
182
|
+
results.forEach((result) => {
|
|
183
|
+
result[attributeName] = { count: 0 };
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const rows = await qb
|
|
189
|
+
.init(populateValue)
|
|
190
|
+
.join({
|
|
191
|
+
alias,
|
|
192
|
+
referencedTable: joinTable.name,
|
|
193
|
+
referencedColumn: joinTable.inverseJoinColumn.name,
|
|
194
|
+
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
195
|
+
rootTable: qb.alias,
|
|
196
|
+
on: joinTable.on,
|
|
197
|
+
})
|
|
198
|
+
.select([joinColAlias, qb.raw('count(*) AS count')])
|
|
199
|
+
.where({ [joinColAlias]: referencedValues })
|
|
200
|
+
.groupBy(joinColAlias)
|
|
201
|
+
.execute({ mapResults: false });
|
|
202
|
+
|
|
203
|
+
const map = rows.reduce((map, row) => {
|
|
204
|
+
map[row[joinColumnName]] = { count: Number(row.count) };
|
|
205
|
+
return map;
|
|
206
|
+
}, {});
|
|
207
|
+
|
|
208
|
+
results.forEach((result) => {
|
|
209
|
+
result[attributeName] = map[result[referencedColumnName]] || { count: 0 };
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (_.isEmpty(referencedValues)) {
|
|
216
|
+
results.forEach((result) => {
|
|
217
|
+
result[attributeName] = [];
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const rows = await qb
|
|
223
|
+
.init(populateValue)
|
|
224
|
+
.join({
|
|
225
|
+
alias,
|
|
226
|
+
referencedTable: joinTable.name,
|
|
227
|
+
referencedColumn: joinTable.inverseJoinColumn.name,
|
|
228
|
+
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
229
|
+
rootTable: qb.alias,
|
|
230
|
+
on: joinTable.on,
|
|
231
|
+
orderBy: _.mapValues((v) => populateValue.ordering || v, joinTable.orderBy),
|
|
232
|
+
})
|
|
233
|
+
.addSelect(joinColAlias)
|
|
234
|
+
.where({ [joinColAlias]: referencedValues })
|
|
235
|
+
.execute({ mapResults: false });
|
|
236
|
+
|
|
237
|
+
const map = _.groupBy(joinColumnName, rows);
|
|
238
|
+
|
|
239
|
+
results.forEach((r) => {
|
|
240
|
+
r[attributeName] = fromTargetRow(map[r[referencedColumnName]] || []);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const manyToMany = async (input, ctx) => {
|
|
246
|
+
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
|
|
247
|
+
const { db } = ctx;
|
|
248
|
+
|
|
249
|
+
const fromTargetRow = (rowOrRows) => fromRow(targetMeta, rowOrRows);
|
|
250
|
+
|
|
251
|
+
const { joinTable } = attribute;
|
|
252
|
+
|
|
253
|
+
const populateQb = db.entityManager.createQueryBuilder(targetMeta.uid);
|
|
254
|
+
|
|
255
|
+
const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
|
|
256
|
+
|
|
257
|
+
const alias = populateQb.getAlias();
|
|
258
|
+
const joinColAlias = `${alias}.${joinColumnName}`;
|
|
259
|
+
const referencedValues = _.uniq(
|
|
260
|
+
results.map((r) => r[referencedColumnName]).filter((value) => !_.isNil(value))
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
if (isCount) {
|
|
264
|
+
if (_.isEmpty(referencedValues)) {
|
|
265
|
+
results.forEach((result) => {
|
|
266
|
+
result[attributeName] = { count: 0 };
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const rows = await populateQb
|
|
272
|
+
.init(populateValue)
|
|
273
|
+
.join({
|
|
274
|
+
alias,
|
|
275
|
+
referencedTable: joinTable.name,
|
|
276
|
+
referencedColumn: joinTable.inverseJoinColumn.name,
|
|
277
|
+
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
278
|
+
rootTable: populateQb.alias,
|
|
279
|
+
on: joinTable.on,
|
|
280
|
+
})
|
|
281
|
+
.select([joinColAlias, populateQb.raw('count(*) AS count')])
|
|
282
|
+
.where({ [joinColAlias]: referencedValues })
|
|
283
|
+
.groupBy(joinColAlias)
|
|
284
|
+
.execute({ mapResults: false });
|
|
285
|
+
|
|
286
|
+
const map = rows.reduce((map, row) => {
|
|
287
|
+
map[row[joinColumnName]] = { count: Number(row.count) };
|
|
288
|
+
return map;
|
|
289
|
+
}, {});
|
|
290
|
+
|
|
291
|
+
results.forEach((result) => {
|
|
292
|
+
result[attributeName] = map[result[referencedColumnName]] || { count: 0 };
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (_.isEmpty(referencedValues)) {
|
|
299
|
+
results.forEach((result) => {
|
|
300
|
+
result[attributeName] = [];
|
|
301
|
+
});
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const rows = await populateQb
|
|
306
|
+
.init(populateValue)
|
|
307
|
+
.join({
|
|
308
|
+
alias,
|
|
309
|
+
referencedTable: joinTable.name,
|
|
310
|
+
referencedColumn: joinTable.inverseJoinColumn.name,
|
|
311
|
+
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
|
|
312
|
+
rootTable: populateQb.alias,
|
|
313
|
+
on: joinTable.on,
|
|
314
|
+
orderBy: _.mapValues((v) => populateValue.ordering || v, joinTable.orderBy),
|
|
315
|
+
})
|
|
316
|
+
.addSelect(joinColAlias)
|
|
317
|
+
.where({ [joinColAlias]: referencedValues })
|
|
318
|
+
.execute({ mapResults: false });
|
|
319
|
+
|
|
320
|
+
const map = _.groupBy(joinColumnName, rows);
|
|
321
|
+
|
|
322
|
+
results.forEach((result) => {
|
|
323
|
+
result[attributeName] = fromTargetRow(map[result[referencedColumnName]] || []);
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const morphX = async (input, ctx) => {
|
|
328
|
+
const { attribute, attributeName, results, populateValue, targetMeta } = input;
|
|
329
|
+
const { db, uid } = ctx;
|
|
330
|
+
|
|
331
|
+
const fromTargetRow = (rowOrRows) => fromRow(targetMeta, rowOrRows);
|
|
332
|
+
|
|
333
|
+
const { target, morphBy } = attribute;
|
|
334
|
+
|
|
335
|
+
const targetAttribute = db.metadata.get(target).attributes[morphBy];
|
|
336
|
+
|
|
337
|
+
if (targetAttribute.relation === 'morphToOne') {
|
|
338
|
+
const { idColumn, typeColumn } = targetAttribute.morphColumn;
|
|
339
|
+
|
|
340
|
+
const referencedValues = _.uniq(
|
|
341
|
+
results.map((r) => r[idColumn.referencedColumn]).filter((value) => !_.isNil(value))
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
if (_.isEmpty(referencedValues)) {
|
|
345
|
+
results.forEach((result) => {
|
|
346
|
+
result[attributeName] = null;
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const rows = await db.entityManager
|
|
353
|
+
.createQueryBuilder(target)
|
|
354
|
+
.init(populateValue)
|
|
355
|
+
// .addSelect(`${qb.alias}.${idColumn.referencedColumn}`)
|
|
356
|
+
.where({ [idColumn.name]: referencedValues, [typeColumn.name]: uid })
|
|
357
|
+
.execute({ mapResults: false });
|
|
358
|
+
|
|
359
|
+
const map = _.groupBy(idColumn.name, rows);
|
|
360
|
+
|
|
361
|
+
results.forEach((result) => {
|
|
362
|
+
const matchingRows = map[result[idColumn.referencedColumn]];
|
|
363
|
+
|
|
364
|
+
const matchingValue =
|
|
365
|
+
attribute.relation === 'morphOne' ? _.first(matchingRows) : matchingRows;
|
|
366
|
+
|
|
367
|
+
result[attributeName] = fromTargetRow(matchingValue);
|
|
368
|
+
});
|
|
369
|
+
} else if (targetAttribute.relation === 'morphToMany') {
|
|
370
|
+
const { joinTable } = targetAttribute;
|
|
371
|
+
|
|
372
|
+
const { joinColumn, morphColumn } = joinTable;
|
|
373
|
+
|
|
374
|
+
const { idColumn, typeColumn } = morphColumn;
|
|
375
|
+
|
|
376
|
+
const referencedValues = _.uniq(
|
|
377
|
+
results.map((r) => r[idColumn.referencedColumn]).filter((value) => !_.isNil(value))
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
if (_.isEmpty(referencedValues)) {
|
|
381
|
+
results.forEach((result) => {
|
|
382
|
+
result[attributeName] = attribute.relation === 'morphOne' ? null : [];
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// find with join table
|
|
389
|
+
const qb = db.entityManager.createQueryBuilder(target);
|
|
390
|
+
|
|
391
|
+
const alias = qb.getAlias();
|
|
392
|
+
|
|
393
|
+
const rows = await qb
|
|
394
|
+
.init(populateValue)
|
|
395
|
+
.join({
|
|
396
|
+
alias,
|
|
397
|
+
referencedTable: joinTable.name,
|
|
398
|
+
referencedColumn: joinColumn.name,
|
|
399
|
+
rootColumn: joinColumn.referencedColumn,
|
|
400
|
+
rootTable: qb.alias,
|
|
401
|
+
on: {
|
|
402
|
+
...(joinTable.on || {}),
|
|
403
|
+
field: attributeName,
|
|
404
|
+
},
|
|
405
|
+
orderBy: _.mapValues((v) => populateValue.ordering || v, joinTable.orderBy),
|
|
406
|
+
})
|
|
407
|
+
.addSelect([`${alias}.${idColumn.name}`, `${alias}.${typeColumn.name}`])
|
|
408
|
+
.where({
|
|
409
|
+
[`${alias}.${idColumn.name}`]: referencedValues,
|
|
410
|
+
[`${alias}.${typeColumn.name}`]: uid,
|
|
411
|
+
})
|
|
412
|
+
.execute({ mapResults: false });
|
|
413
|
+
|
|
414
|
+
const map = _.groupBy(idColumn.name, rows);
|
|
415
|
+
|
|
416
|
+
results.forEach((result) => {
|
|
417
|
+
const matchingRows = map[result[idColumn.referencedColumn]];
|
|
418
|
+
|
|
419
|
+
const matchingValue =
|
|
420
|
+
attribute.relation === 'morphOne' ? _.first(matchingRows) : matchingRows;
|
|
421
|
+
|
|
422
|
+
result[attributeName] = fromTargetRow(matchingValue);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const morphToMany = async (input, ctx) => {
|
|
428
|
+
const { attribute, attributeName, results, populateValue } = input;
|
|
429
|
+
const { db } = ctx;
|
|
430
|
+
|
|
431
|
+
// find with join table
|
|
432
|
+
const { joinTable } = attribute;
|
|
433
|
+
|
|
434
|
+
const { joinColumn, morphColumn } = joinTable;
|
|
435
|
+
const { idColumn, typeColumn, typeField = '__type' } = morphColumn;
|
|
436
|
+
|
|
437
|
+
// fetch join table to create the ids map then do the same as morphToOne without the first
|
|
438
|
+
|
|
439
|
+
const referencedValues = _.uniq(
|
|
440
|
+
results.map((r) => r[joinColumn.referencedColumn]).filter((value) => !_.isNil(value))
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const qb = db.entityManager.createQueryBuilder(joinTable.name);
|
|
444
|
+
|
|
445
|
+
const joinRows = await qb
|
|
446
|
+
.where({
|
|
447
|
+
[joinColumn.name]: referencedValues,
|
|
448
|
+
...(joinTable.on || {}),
|
|
449
|
+
})
|
|
450
|
+
.orderBy([joinColumn.name, 'order'])
|
|
451
|
+
.execute({ mapResults: false });
|
|
452
|
+
|
|
453
|
+
const joinMap = _.groupBy(joinColumn.name, joinRows);
|
|
454
|
+
|
|
455
|
+
const idsByType = joinRows.reduce((acc, result) => {
|
|
456
|
+
const idValue = result[morphColumn.idColumn.name];
|
|
457
|
+
const typeValue = result[morphColumn.typeColumn.name];
|
|
458
|
+
|
|
459
|
+
if (!idValue || !typeValue) {
|
|
460
|
+
return acc;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (!_.has(typeValue, acc)) {
|
|
464
|
+
acc[typeValue] = [];
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
acc[typeValue].push(idValue);
|
|
468
|
+
|
|
469
|
+
return acc;
|
|
470
|
+
}, {});
|
|
471
|
+
|
|
472
|
+
const map = {};
|
|
473
|
+
for (const type of Object.keys(idsByType)) {
|
|
474
|
+
const ids = idsByType[type];
|
|
475
|
+
|
|
476
|
+
// type was removed but still in morph relation
|
|
477
|
+
if (!db.metadata.get(type)) {
|
|
478
|
+
map[type] = {};
|
|
479
|
+
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const qb = db.entityManager.createQueryBuilder(type);
|
|
484
|
+
|
|
485
|
+
const rows = await qb
|
|
486
|
+
.init(populateValue)
|
|
487
|
+
.addSelect(`${qb.alias}.${idColumn.referencedColumn}`)
|
|
488
|
+
.where({ [idColumn.referencedColumn]: ids })
|
|
489
|
+
.execute({ mapResults: false });
|
|
490
|
+
|
|
491
|
+
map[type] = _.groupBy(idColumn.referencedColumn, rows);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
results.forEach((result) => {
|
|
495
|
+
const joinResults = joinMap[result[joinColumn.referencedColumn]] || [];
|
|
496
|
+
|
|
497
|
+
const matchingRows = joinResults.flatMap((joinResult) => {
|
|
498
|
+
const id = joinResult[idColumn.name];
|
|
499
|
+
const type = joinResult[typeColumn.name];
|
|
500
|
+
|
|
501
|
+
const fromTargetRow = (rowOrRows) => fromRow(db.metadata.get(type), rowOrRows);
|
|
502
|
+
|
|
503
|
+
return (map[type][id] || []).map((row) => {
|
|
504
|
+
return {
|
|
505
|
+
[typeField]: type,
|
|
506
|
+
...fromTargetRow(row),
|
|
507
|
+
};
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
result[attributeName] = matchingRows;
|
|
512
|
+
});
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const morphToOne = async (input, ctx) => {
|
|
516
|
+
const { attribute, attributeName, results, populateValue } = input;
|
|
517
|
+
const { db } = ctx;
|
|
518
|
+
|
|
519
|
+
const { morphColumn } = attribute;
|
|
520
|
+
const { idColumn, typeColumn } = morphColumn;
|
|
521
|
+
|
|
522
|
+
// make a map for each type what ids to return
|
|
523
|
+
// make a nested map per id
|
|
524
|
+
|
|
525
|
+
const idsByType = results.reduce((acc, result) => {
|
|
526
|
+
const idValue = result[morphColumn.idColumn.name];
|
|
527
|
+
const typeValue = result[morphColumn.typeColumn.name];
|
|
528
|
+
|
|
529
|
+
if (!idValue || !typeValue) {
|
|
530
|
+
return acc;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (!_.has(typeValue, acc)) {
|
|
534
|
+
acc[typeValue] = [];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
acc[typeValue].push(idValue);
|
|
538
|
+
|
|
539
|
+
return acc;
|
|
540
|
+
}, {});
|
|
541
|
+
|
|
542
|
+
const map = {};
|
|
543
|
+
for (const type of Object.keys(idsByType)) {
|
|
544
|
+
const ids = idsByType[type];
|
|
545
|
+
|
|
546
|
+
// type was removed but still in morph relation
|
|
547
|
+
if (!db.metadata.get(type)) {
|
|
548
|
+
map[type] = {};
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const qb = db.entityManager.createQueryBuilder(type);
|
|
553
|
+
|
|
554
|
+
const rows = await qb
|
|
555
|
+
.init(populateValue)
|
|
556
|
+
.addSelect(`${qb.alias}.${idColumn.referencedColumn}`)
|
|
557
|
+
.where({ [idColumn.referencedColumn]: ids })
|
|
558
|
+
.execute({ mapResults: false });
|
|
559
|
+
|
|
560
|
+
map[type] = _.groupBy(idColumn.referencedColumn, rows);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
results.forEach((result) => {
|
|
564
|
+
const id = result[idColumn.name];
|
|
565
|
+
const type = result[typeColumn.name];
|
|
566
|
+
|
|
567
|
+
if (!type || !id) {
|
|
568
|
+
result[attributeName] = null;
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const matchingRows = map[type][id];
|
|
573
|
+
|
|
574
|
+
const fromTargetRow = (rowOrRows) => fromRow(db.metadata.get(type), rowOrRows);
|
|
575
|
+
|
|
576
|
+
result[attributeName] = fromTargetRow(_.first(matchingRows));
|
|
577
|
+
});
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// TODO: Omit limit & offset to avoid needing a query per result to avoid making too many queries
|
|
581
|
+
const pickPopulateParams = (populate) => {
|
|
582
|
+
const fieldsToPick = ['select', 'count', 'where', 'populate', 'orderBy', 'filters', 'ordering'];
|
|
583
|
+
|
|
584
|
+
if (populate.count !== true) {
|
|
585
|
+
fieldsToPick.push('limit', 'offset');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return _.pick(fieldsToPick, populate);
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
const applyPopulate = async (results, populate, ctx) => {
|
|
592
|
+
const { db, uid, qb } = ctx;
|
|
593
|
+
const meta = db.metadata.get(uid);
|
|
594
|
+
|
|
595
|
+
if (_.isEmpty(results)) {
|
|
596
|
+
return results;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
for (const attributeName of Object.keys(populate)) {
|
|
600
|
+
const attribute = meta.attributes[attributeName];
|
|
601
|
+
const targetMeta = db.metadata.get(attribute.target);
|
|
602
|
+
|
|
603
|
+
const populateValue = {
|
|
604
|
+
filters: qb.state.filters,
|
|
605
|
+
...pickPopulateParams(populate[attributeName]),
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
const isCount = populateValue.count === true;
|
|
609
|
+
|
|
610
|
+
const input = { attribute, attributeName, results, populateValue, targetMeta, isCount };
|
|
611
|
+
|
|
612
|
+
switch (attribute.relation) {
|
|
613
|
+
case 'oneToOne':
|
|
614
|
+
case 'manyToOne': {
|
|
615
|
+
await XtoOne(input, ctx);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
case 'oneToMany': {
|
|
619
|
+
await oneToMany(input, ctx);
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
case 'manyToMany': {
|
|
623
|
+
await manyToMany(input, ctx);
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
case 'morphOne':
|
|
627
|
+
case 'morphMany': {
|
|
628
|
+
await morphX(input, ctx);
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
case 'morphToMany': {
|
|
632
|
+
await morphToMany(input, ctx);
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
case 'morphToOne': {
|
|
636
|
+
await morphToOne(input, ctx);
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
default: {
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
module.exports = applyPopulate;
|