@mikro-orm/knex 6.0.0-dev.14 → 6.0.0-dev.140
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/AbstractSqlConnection.d.ts +8 -4
- package/AbstractSqlConnection.js +30 -18
- package/AbstractSqlDriver.d.ts +21 -23
- package/AbstractSqlDriver.js +394 -225
- package/AbstractSqlPlatform.d.ts +5 -4
- package/AbstractSqlPlatform.js +34 -5
- package/MonkeyPatchable.d.ts +1 -0
- package/MonkeyPatchable.js +3 -0
- package/PivotCollectionPersister.d.ts +17 -0
- package/PivotCollectionPersister.js +128 -0
- package/README.md +64 -107
- package/SqlEntityManager.d.ts +2 -6
- package/SqlEntityManager.js +5 -9
- package/SqlEntityRepository.d.ts +7 -6
- package/SqlEntityRepository.js +11 -8
- package/index.mjs +24 -6
- package/package.json +8 -8
- package/query/ArrayCriteriaNode.d.ts +3 -3
- package/query/CriteriaNode.d.ts +9 -8
- package/query/CriteriaNode.js +17 -9
- package/query/CriteriaNodeFactory.d.ts +6 -6
- package/query/CriteriaNodeFactory.js +17 -13
- package/query/ObjectCriteriaNode.d.ts +3 -3
- package/query/ObjectCriteriaNode.js +14 -10
- package/query/QueryBuilder.d.ts +50 -21
- package/query/QueryBuilder.js +389 -132
- package/query/QueryBuilderHelper.d.ts +11 -16
- package/query/QueryBuilderHelper.js +194 -155
- package/query/ScalarCriteriaNode.d.ts +3 -2
- package/query/ScalarCriteriaNode.js +10 -6
- package/query/enums.d.ts +7 -0
- package/query/enums.js +10 -2
- package/schema/DatabaseSchema.d.ts +5 -2
- package/schema/DatabaseSchema.js +27 -9
- package/schema/DatabaseTable.d.ts +4 -2
- package/schema/DatabaseTable.js +67 -32
- package/schema/SchemaComparator.d.ts +3 -3
- package/schema/SchemaComparator.js +50 -30
- package/schema/SchemaHelper.d.ts +6 -4
- package/schema/SchemaHelper.js +18 -7
- package/schema/SqlSchemaGenerator.d.ts +3 -6
- package/schema/SqlSchemaGenerator.js +69 -34
- package/typings.d.ts +17 -10
package/AbstractSqlDriver.js
CHANGED
|
@@ -4,10 +4,14 @@ exports.AbstractSqlDriver = void 0;
|
|
|
4
4
|
const core_1 = require("@mikro-orm/core");
|
|
5
5
|
const query_1 = require("./query");
|
|
6
6
|
const SqlEntityManager_1 = require("./SqlEntityManager");
|
|
7
|
+
const PivotCollectionPersister_1 = require("./PivotCollectionPersister");
|
|
7
8
|
class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
9
|
+
[core_1.EntityManagerType];
|
|
10
|
+
connection;
|
|
11
|
+
replicas = [];
|
|
12
|
+
platform;
|
|
8
13
|
constructor(config, platform, connection, connector) {
|
|
9
14
|
super(config, connector);
|
|
10
|
-
this.replicas = [];
|
|
11
15
|
this.connection = new connection(this.config);
|
|
12
16
|
this.replicas = this.createReplicas(conf => new connection(this.config, conf, 'read'));
|
|
13
17
|
this.platform = platform;
|
|
@@ -26,19 +30,31 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
26
30
|
}
|
|
27
31
|
const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
|
|
28
32
|
const joinedProps = this.joinedProps(meta, populate);
|
|
29
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false);
|
|
30
|
-
const fields = this.buildFields(meta, populate, joinedProps, qb, options.fields);
|
|
33
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
|
|
34
|
+
const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options.fields);
|
|
31
35
|
const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(entityName, qb, meta, joinedProps);
|
|
36
|
+
const orderBy = [...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy];
|
|
32
37
|
if (core_1.Utils.isPrimaryKey(where, meta.compositePK)) {
|
|
33
38
|
where = { [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
|
|
34
39
|
}
|
|
40
|
+
const { first, last, before, after } = options;
|
|
41
|
+
const isCursorPagination = [first, last, before, after].some(v => v != null);
|
|
35
42
|
qb.select(fields)
|
|
36
43
|
.populate(populate, joinedProps.length > 0 ? options.populateWhere : undefined)
|
|
37
44
|
.where(where)
|
|
38
|
-
.orderBy([...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy])
|
|
39
45
|
.groupBy(options.groupBy)
|
|
40
46
|
.having(options.having)
|
|
47
|
+
.indexHint(options.indexHint)
|
|
48
|
+
.comment(options.comments)
|
|
49
|
+
.hintComment(options.hintComments)
|
|
41
50
|
.withSchema(this.getSchemaName(meta, options));
|
|
51
|
+
if (isCursorPagination) {
|
|
52
|
+
const { orderBy: newOrderBy, where } = this.processCursorOptions(meta, options, orderBy);
|
|
53
|
+
qb.andWhere(where).orderBy(newOrderBy);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
qb.orderBy(orderBy);
|
|
57
|
+
}
|
|
42
58
|
if (options.limit !== undefined) {
|
|
43
59
|
qb.limit(options.limit, options.offset);
|
|
44
60
|
}
|
|
@@ -47,8 +63,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
47
63
|
}
|
|
48
64
|
core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
|
|
49
65
|
const result = await this.rethrow(qb.execute('all'));
|
|
50
|
-
if (
|
|
51
|
-
|
|
66
|
+
if (isCursorPagination && !first && !!last) {
|
|
67
|
+
result.reverse();
|
|
52
68
|
}
|
|
53
69
|
return result;
|
|
54
70
|
}
|
|
@@ -61,58 +77,59 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
61
77
|
opts.limit = 1;
|
|
62
78
|
}
|
|
63
79
|
if (opts.limit > 0 && !opts.flags?.includes(core_1.QueryFlag.DISABLE_PAGINATE)) {
|
|
64
|
-
opts.flags
|
|
80
|
+
opts.flags ??= [];
|
|
65
81
|
opts.flags.push(core_1.QueryFlag.DISABLE_PAGINATE);
|
|
66
82
|
}
|
|
67
83
|
const res = await this.find(entityName, where, opts);
|
|
68
84
|
return res[0] || null;
|
|
69
85
|
}
|
|
70
86
|
async findVirtual(entityName, where, options) {
|
|
71
|
-
|
|
72
|
-
/* istanbul ignore next */
|
|
73
|
-
if (!meta.expression) {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
if (typeof meta.expression === 'string') {
|
|
77
|
-
return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options);
|
|
78
|
-
}
|
|
79
|
-
const em = this.createEntityManager(false);
|
|
80
|
-
em.setTransactionContext(options.ctx);
|
|
81
|
-
const res = meta.expression(em, where, options);
|
|
82
|
-
if (res instanceof (query_1.QueryBuilder)) {
|
|
83
|
-
return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options);
|
|
84
|
-
}
|
|
85
|
-
/* istanbul ignore next */
|
|
86
|
-
return res;
|
|
87
|
+
return this.findFromVirtual(entityName, where, options, query_1.QueryType.SELECT);
|
|
87
88
|
}
|
|
88
89
|
async countVirtual(entityName, where, options) {
|
|
90
|
+
return this.findFromVirtual(entityName, where, options, query_1.QueryType.COUNT);
|
|
91
|
+
}
|
|
92
|
+
async findFromVirtual(entityName, where, options, type) {
|
|
89
93
|
const meta = this.metadata.get(entityName);
|
|
90
94
|
/* istanbul ignore next */
|
|
91
95
|
if (!meta.expression) {
|
|
92
|
-
return 0;
|
|
96
|
+
return type === query_1.QueryType.SELECT ? [] : 0;
|
|
93
97
|
}
|
|
94
98
|
if (typeof meta.expression === 'string') {
|
|
95
|
-
return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options,
|
|
99
|
+
return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, type);
|
|
96
100
|
}
|
|
97
101
|
const em = this.createEntityManager(false);
|
|
98
102
|
em.setTransactionContext(options.ctx);
|
|
99
103
|
const res = meta.expression(em, where, options);
|
|
100
|
-
if (res
|
|
101
|
-
return this.wrapVirtualExpressionInSubquery(meta, res
|
|
104
|
+
if (typeof res === 'string') {
|
|
105
|
+
return this.wrapVirtualExpressionInSubquery(meta, res, where, options, type);
|
|
106
|
+
}
|
|
107
|
+
if (res instanceof query_1.QueryBuilder) {
|
|
108
|
+
return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, type);
|
|
109
|
+
}
|
|
110
|
+
if (core_1.Utils.isObject(res)) {
|
|
111
|
+
const { sql, bindings } = res.toSQL();
|
|
112
|
+
const query = this.platform.formatQuery(sql, bindings);
|
|
113
|
+
return this.wrapVirtualExpressionInSubquery(meta, query, where, options, type);
|
|
102
114
|
}
|
|
103
115
|
/* istanbul ignore next */
|
|
104
116
|
return res;
|
|
105
117
|
}
|
|
106
|
-
async wrapVirtualExpressionInSubquery(meta, expression, where, options, type
|
|
118
|
+
async wrapVirtualExpressionInSubquery(meta, expression, where, options, type) {
|
|
107
119
|
const qb = this.createQueryBuilder(meta.className, options?.ctx, options.connectionType, options.convertCustomTypes)
|
|
108
|
-
.
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
.indexHint(options.indexHint)
|
|
121
|
+
.comment(options.comments)
|
|
122
|
+
.hintComment(options.hintComments);
|
|
123
|
+
if (type !== query_1.QueryType.COUNT) {
|
|
124
|
+
qb.limit(options?.limit, options?.offset);
|
|
125
|
+
if (options.orderBy) {
|
|
126
|
+
qb.orderBy(options.orderBy);
|
|
127
|
+
}
|
|
111
128
|
}
|
|
112
129
|
qb.where(where);
|
|
113
130
|
const kqb = qb.getKnexQuery().clear('select');
|
|
114
131
|
if (type === query_1.QueryType.COUNT) {
|
|
115
|
-
kqb.select(
|
|
132
|
+
kqb.select(this.connection.getKnex().raw('count(*) as count'));
|
|
116
133
|
}
|
|
117
134
|
else { // select
|
|
118
135
|
kqb.select('*');
|
|
@@ -131,6 +148,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
131
148
|
return null;
|
|
132
149
|
}
|
|
133
150
|
if (qb) {
|
|
151
|
+
// here we map the aliased results (cartesian product) to an object graph
|
|
134
152
|
this.mapJoinedProps(ret, meta, populate, qb, ret, map);
|
|
135
153
|
}
|
|
136
154
|
return ret;
|
|
@@ -153,12 +171,26 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
153
171
|
return root[`${relationAlias}__${name}`] != null;
|
|
154
172
|
}));
|
|
155
173
|
if (!hasPK) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
174
|
+
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
|
|
175
|
+
result[relation.name] ??= [];
|
|
176
|
+
}
|
|
177
|
+
if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(relation.kind)) {
|
|
178
|
+
result[relation.name] = null;
|
|
159
179
|
}
|
|
160
180
|
return;
|
|
161
181
|
}
|
|
182
|
+
const tz = this.platform.getTimezone();
|
|
183
|
+
meta2.props
|
|
184
|
+
.filter(prop => prop.persist === false && prop.fieldNames)
|
|
185
|
+
.forEach(prop => {
|
|
186
|
+
if (prop.fieldNames.length > 1) { // composite keys
|
|
187
|
+
relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
191
|
+
relationPojo[prop.name] = root[alias];
|
|
192
|
+
}
|
|
193
|
+
});
|
|
162
194
|
meta2.props
|
|
163
195
|
.filter(prop => this.platform.shouldHaveColumn(prop, p.children || []))
|
|
164
196
|
.forEach(prop => {
|
|
@@ -166,22 +198,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
166
198
|
relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
|
|
167
199
|
prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
|
|
168
200
|
}
|
|
201
|
+
else if (prop.runtimeType === 'Date') {
|
|
202
|
+
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
203
|
+
relationPojo[prop.name] = (typeof root[alias] === 'string' ? new Date(root[alias]) : root[alias]);
|
|
204
|
+
delete root[alias];
|
|
205
|
+
}
|
|
169
206
|
else {
|
|
170
207
|
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
171
208
|
relationPojo[prop.name] = root[alias];
|
|
172
209
|
delete root[alias];
|
|
173
210
|
}
|
|
174
211
|
});
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
result[relation.name]
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
map[key] = result;
|
|
181
|
-
}
|
|
182
|
-
if ([core_1.ReferenceType.MANY_TO_MANY, core_1.ReferenceType.ONE_TO_MANY].includes(relation.reference)) {
|
|
183
|
-
result[relation.name] = result[relation.name] || [];
|
|
184
|
-
this.appendToCollection(meta2, result[relation.name], relationPojo);
|
|
212
|
+
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
|
|
213
|
+
result[relation.name] ??= [];
|
|
214
|
+
result[relation.name].push(relationPojo);
|
|
185
215
|
}
|
|
186
216
|
else {
|
|
187
217
|
result[relation.name] = relationPojo;
|
|
@@ -190,23 +220,15 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
190
220
|
this.mapJoinedProps(relationPojo, meta2, populateChildren, qb, root, map, path);
|
|
191
221
|
});
|
|
192
222
|
}
|
|
193
|
-
appendToCollection(meta, collection, relationPojo) {
|
|
194
|
-
if (collection.length === 0) {
|
|
195
|
-
return void collection.push(relationPojo);
|
|
196
|
-
}
|
|
197
|
-
const last = collection[collection.length - 1];
|
|
198
|
-
const pk1 = core_1.Utils.getCompositeKeyHash(last, meta);
|
|
199
|
-
const pk2 = core_1.Utils.getCompositeKeyHash(relationPojo, meta);
|
|
200
|
-
if (pk1 !== pk2) {
|
|
201
|
-
collection.push(relationPojo);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
223
|
async count(entityName, where, options = {}) {
|
|
205
224
|
const meta = this.metadata.find(entityName);
|
|
206
225
|
if (meta?.virtual) {
|
|
207
226
|
return this.countVirtual(entityName, where, options);
|
|
208
227
|
}
|
|
209
228
|
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
|
|
229
|
+
.indexHint(options.indexHint)
|
|
230
|
+
.comment(options.comments)
|
|
231
|
+
.hintComment(options.hintComments)
|
|
210
232
|
.groupBy(options.groupBy)
|
|
211
233
|
.having(options.having)
|
|
212
234
|
.populate(options.populate ?? [])
|
|
@@ -215,7 +237,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
215
237
|
return this.rethrow(qb.getCount());
|
|
216
238
|
}
|
|
217
239
|
async nativeInsert(entityName, data, options = {}) {
|
|
218
|
-
options.convertCustomTypes
|
|
240
|
+
options.convertCustomTypes ??= true;
|
|
219
241
|
const meta = this.metadata.find(entityName);
|
|
220
242
|
const collections = this.extractManyToMany(entityName, data);
|
|
221
243
|
const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
|
|
@@ -235,16 +257,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
235
257
|
return res;
|
|
236
258
|
}
|
|
237
259
|
async nativeInsertMany(entityName, data, options = {}) {
|
|
238
|
-
options.processCollections
|
|
239
|
-
options.convertCustomTypes
|
|
240
|
-
const meta = this.metadata.find(entityName);
|
|
260
|
+
options.processCollections ??= true;
|
|
261
|
+
options.convertCustomTypes ??= true;
|
|
262
|
+
const meta = this.metadata.find(entityName)?.root;
|
|
241
263
|
const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
|
|
242
264
|
const pks = this.getPrimaryKeyFields(entityName);
|
|
243
265
|
const set = new Set();
|
|
244
|
-
data.forEach(row =>
|
|
266
|
+
data.forEach(row => core_1.Utils.keys(row).forEach(k => set.add(k)));
|
|
245
267
|
const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
|
|
246
|
-
|
|
268
|
+
let fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
|
|
269
|
+
const duplicates = core_1.Utils.findDuplicates(fields);
|
|
247
270
|
const params = [];
|
|
271
|
+
if (duplicates.length) {
|
|
272
|
+
fields = core_1.Utils.unique(fields);
|
|
273
|
+
}
|
|
248
274
|
/* istanbul ignore next */
|
|
249
275
|
const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
|
|
250
276
|
let sql = `insert into ${tableName} `;
|
|
@@ -255,29 +281,62 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
255
281
|
else {
|
|
256
282
|
sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
|
|
257
283
|
}
|
|
284
|
+
const addParams = (prop, row) => {
|
|
285
|
+
let value = row[prop.name];
|
|
286
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
|
|
287
|
+
if (prop.array) {
|
|
288
|
+
for (let i = 0; i < value.length; i++) {
|
|
289
|
+
const item = value[i];
|
|
290
|
+
value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (options.convertCustomTypes && prop.customType) {
|
|
298
|
+
params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
params.push(value);
|
|
302
|
+
};
|
|
258
303
|
if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
|
|
259
304
|
sql += data.map(row => {
|
|
260
305
|
const keys = [];
|
|
306
|
+
const usedDups = [];
|
|
261
307
|
props.forEach(prop => {
|
|
262
308
|
if (prop.fieldNames.length > 1) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
309
|
+
const param = core_1.Utils.flatten([...row[prop.name] ?? prop.fieldNames.map(() => null)]);
|
|
310
|
+
const key = param.map(() => '?');
|
|
311
|
+
prop.fieldNames.forEach((field, idx) => {
|
|
312
|
+
if (!duplicates.includes(field) || !usedDups.includes(field)) {
|
|
313
|
+
params.push(param[idx]);
|
|
314
|
+
keys.push(key[idx]);
|
|
315
|
+
usedDups.push(field);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
269
318
|
}
|
|
270
319
|
else {
|
|
271
|
-
|
|
272
|
-
|
|
320
|
+
const field = prop.fieldNames[0];
|
|
321
|
+
if (!duplicates.includes(field) || !usedDups.includes(field)) {
|
|
322
|
+
if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
|
|
323
|
+
keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
keys.push('?');
|
|
327
|
+
}
|
|
328
|
+
addParams(prop, row);
|
|
329
|
+
usedDups.push(field);
|
|
330
|
+
}
|
|
273
331
|
}
|
|
274
332
|
});
|
|
275
333
|
return '(' + (keys.join(', ') || 'default') + ')';
|
|
276
334
|
}).join(', ');
|
|
277
335
|
}
|
|
278
|
-
if (this.platform.usesReturningStatement()) {
|
|
279
|
-
|
|
280
|
-
|
|
336
|
+
if (meta && this.platform.usesReturningStatement()) {
|
|
337
|
+
const returningProps = meta.props
|
|
338
|
+
.filter(prop => prop.persist !== false && prop.defaultRaw || prop.autoincrement || prop.generated)
|
|
339
|
+
.filter(prop => !(prop.name in data[0]) || core_1.Utils.isRawSql(data[0][prop.name]));
|
|
281
340
|
const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
|
|
282
341
|
/* istanbul ignore next */
|
|
283
342
|
sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
|
|
@@ -289,8 +348,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
289
348
|
pk = data.map(d => core_1.Utils.getPrimaryKeyCond(d, pks));
|
|
290
349
|
}
|
|
291
350
|
else {
|
|
292
|
-
res.row
|
|
293
|
-
res.rows
|
|
351
|
+
res.row ??= {};
|
|
352
|
+
res.rows ??= [];
|
|
294
353
|
pk = data.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]]).map(d => [d]);
|
|
295
354
|
res.insertId = res.insertId || res.row[pks[0]];
|
|
296
355
|
}
|
|
@@ -300,7 +359,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
300
359
|
return res;
|
|
301
360
|
}
|
|
302
361
|
async nativeUpdate(entityName, where, data, options = {}) {
|
|
303
|
-
options.convertCustomTypes
|
|
362
|
+
options.convertCustomTypes ??= true;
|
|
304
363
|
const meta = this.metadata.find(entityName);
|
|
305
364
|
const pks = this.getPrimaryKeyFields(entityName);
|
|
306
365
|
const collections = this.extractManyToMany(entityName, data);
|
|
@@ -314,14 +373,27 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
314
373
|
.withSchema(this.getSchemaName(meta, options));
|
|
315
374
|
if (options.upsert) {
|
|
316
375
|
/* istanbul ignore next */
|
|
317
|
-
const uniqueFields = core_1.Utils.isPlainObject(where) ?
|
|
318
|
-
|
|
376
|
+
const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where) ? core_1.Utils.keys(where) : meta.primaryKeys);
|
|
377
|
+
const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
|
|
319
378
|
qb.insert(data)
|
|
320
|
-
.onConflict(uniqueFields
|
|
321
|
-
.
|
|
379
|
+
.onConflict(uniqueFields)
|
|
380
|
+
.returning(returning);
|
|
381
|
+
if (!options.onConflictAction || options.onConflictAction === 'merge') {
|
|
382
|
+
const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
|
|
383
|
+
qb.merge(fields);
|
|
384
|
+
}
|
|
385
|
+
if (options.onConflictAction === 'ignore') {
|
|
386
|
+
qb.ignore();
|
|
387
|
+
}
|
|
322
388
|
}
|
|
323
389
|
else {
|
|
324
390
|
qb.update(data).where(where);
|
|
391
|
+
// reload generated columns
|
|
392
|
+
const returning = [];
|
|
393
|
+
meta?.props
|
|
394
|
+
.filter(prop => prop.generated && !prop.primary)
|
|
395
|
+
.forEach(prop => returning.push(prop.name));
|
|
396
|
+
qb.returning(returning);
|
|
325
397
|
}
|
|
326
398
|
res = await this.rethrow(qb.execute('run', false));
|
|
327
399
|
}
|
|
@@ -331,23 +403,57 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
331
403
|
return res;
|
|
332
404
|
}
|
|
333
405
|
async nativeUpdateMany(entityName, where, data, options = {}) {
|
|
334
|
-
options.processCollections
|
|
335
|
-
options.convertCustomTypes
|
|
406
|
+
options.processCollections ??= true;
|
|
407
|
+
options.convertCustomTypes ??= true;
|
|
336
408
|
const meta = this.metadata.get(entityName);
|
|
337
409
|
if (options.upsert) {
|
|
338
|
-
const uniqueFields = core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys;
|
|
410
|
+
const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys);
|
|
339
411
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
412
|
+
const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
|
|
340
413
|
qb.insert(data)
|
|
341
|
-
.onConflict(uniqueFields
|
|
342
|
-
.
|
|
343
|
-
|
|
414
|
+
.onConflict(uniqueFields)
|
|
415
|
+
.returning(returning);
|
|
416
|
+
if (!options.onConflictAction || options.onConflictAction === 'merge') {
|
|
417
|
+
const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
|
|
418
|
+
qb.merge(fields);
|
|
419
|
+
}
|
|
420
|
+
if (options.onConflictAction === 'ignore') {
|
|
421
|
+
qb.ignore();
|
|
422
|
+
}
|
|
423
|
+
return this.rethrow(qb.execute('run', false));
|
|
344
424
|
}
|
|
345
425
|
const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
|
|
346
426
|
const keys = new Set();
|
|
347
|
-
|
|
427
|
+
const returning = new Set();
|
|
428
|
+
data.forEach(row => {
|
|
429
|
+
core_1.Utils.keys(row).forEach(k => {
|
|
430
|
+
keys.add(k);
|
|
431
|
+
if (core_1.Utils.isRawSql(row[k])) {
|
|
432
|
+
returning.add(k);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
// reload generated columns
|
|
437
|
+
meta?.props
|
|
438
|
+
.filter(prop => prop.generated && !prop.primary)
|
|
439
|
+
.forEach(prop => returning.add(prop.name));
|
|
348
440
|
const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
|
|
349
441
|
const params = [];
|
|
350
442
|
let sql = `update ${this.getTableName(meta, options)} set `;
|
|
443
|
+
const addParams = (prop, value) => {
|
|
444
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
|
|
445
|
+
if (prop.array) {
|
|
446
|
+
for (let i = 0; i < value.length; i++) {
|
|
447
|
+
const item = value[i];
|
|
448
|
+
value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
params.push(value);
|
|
456
|
+
};
|
|
351
457
|
keys.forEach(key => {
|
|
352
458
|
const prop = meta.properties[key];
|
|
353
459
|
prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
|
|
@@ -362,7 +468,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
362
468
|
else {
|
|
363
469
|
sql += '?';
|
|
364
470
|
}
|
|
365
|
-
params.push(...pks
|
|
471
|
+
params.push(...pks);
|
|
472
|
+
addParams(prop, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
|
|
366
473
|
}
|
|
367
474
|
});
|
|
368
475
|
sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
|
|
@@ -373,7 +480,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
373
480
|
const versionProperty = meta.properties[meta.versionProperty];
|
|
374
481
|
const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
|
|
375
482
|
sql += `${quotedFieldName} = `;
|
|
376
|
-
if (versionProperty.
|
|
483
|
+
if (versionProperty.runtimeType === 'Date') {
|
|
377
484
|
sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
|
|
378
485
|
}
|
|
379
486
|
else {
|
|
@@ -404,6 +511,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
404
511
|
return '?';
|
|
405
512
|
});
|
|
406
513
|
sql += ` in (${conds.join(', ')})`;
|
|
514
|
+
if (this.platform.usesReturningStatement() && returning.size > 0) {
|
|
515
|
+
const returningFields = core_1.Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
|
|
516
|
+
/* istanbul ignore next */
|
|
517
|
+
sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
|
|
518
|
+
}
|
|
407
519
|
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
|
|
408
520
|
for (let i = 0; i < collections.length; i++) {
|
|
409
521
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
@@ -419,49 +531,81 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
419
531
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
|
|
420
532
|
return this.rethrow(qb.execute('run', false));
|
|
421
533
|
}
|
|
422
|
-
async
|
|
423
|
-
const
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
.
|
|
445
|
-
|
|
446
|
-
.
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
534
|
+
async syncCollections(collections, options) {
|
|
535
|
+
const groups = {};
|
|
536
|
+
for (const coll of collections) {
|
|
537
|
+
const wrapped = (0, core_1.helper)(coll.owner);
|
|
538
|
+
const meta = wrapped.__meta;
|
|
539
|
+
const pks = wrapped.getPrimaryKeys(true);
|
|
540
|
+
const snap = coll.getSnapshot();
|
|
541
|
+
const includes = (arr, item) => !!arr.find(i => core_1.Utils.equals(i, item));
|
|
542
|
+
const snapshot = snap ? snap.map(item => (0, core_1.helper)(item).getPrimaryKeys(true)) : [];
|
|
543
|
+
const current = coll.getItems(false).map(item => (0, core_1.helper)(item).getPrimaryKeys(true));
|
|
544
|
+
const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
|
|
545
|
+
const insertDiff = current.filter(item => !includes(snapshot, item));
|
|
546
|
+
const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
|
|
547
|
+
const equals = core_1.Utils.equals(current, target);
|
|
548
|
+
// wrong order if we just delete and insert to the end (only owning sides can have fixed order)
|
|
549
|
+
if (coll.property.owner && coll.property.fixedOrder && !equals && Array.isArray(deleteDiff)) {
|
|
550
|
+
deleteDiff.length = insertDiff.length = 0;
|
|
551
|
+
deleteDiff.push(...snapshot);
|
|
552
|
+
insertDiff.push(...current);
|
|
553
|
+
}
|
|
554
|
+
if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
|
|
555
|
+
const cols = coll.property.referencedColumnNames;
|
|
556
|
+
const qb = this.createQueryBuilder(coll.property.type, options?.ctx, 'write')
|
|
557
|
+
.withSchema(this.getSchemaName(meta, options));
|
|
558
|
+
if (coll.getSnapshot() === undefined) {
|
|
559
|
+
if (coll.property.orphanRemoval) {
|
|
560
|
+
const kqb = qb.delete({ [coll.property.mappedBy]: pks })
|
|
561
|
+
.getKnexQuery()
|
|
562
|
+
.whereNotIn(cols, insertDiff);
|
|
563
|
+
return this.rethrow(this.execute(kqb));
|
|
564
|
+
}
|
|
565
|
+
const kqb = qb.update({ [coll.property.mappedBy]: null })
|
|
566
|
+
.getKnexQuery()
|
|
567
|
+
.whereNotIn(cols, insertDiff);
|
|
568
|
+
return this.rethrow(this.execute(kqb));
|
|
569
|
+
}
|
|
570
|
+
const kqb = qb.update({ [coll.property.mappedBy]: pks })
|
|
571
|
+
.getKnexQuery()
|
|
572
|
+
.whereIn(cols, insertDiff);
|
|
573
|
+
return this.rethrow(this.execute(kqb));
|
|
574
|
+
}
|
|
454
575
|
/* istanbul ignore next */
|
|
455
|
-
|
|
456
|
-
|
|
576
|
+
const pivotMeta = this.metadata.find(coll.property.pivotEntity);
|
|
577
|
+
let schema = pivotMeta.schema;
|
|
578
|
+
if (schema === '*') {
|
|
579
|
+
const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
|
|
580
|
+
schema = coll.property.owner ? ownerSchema : this.config.get('schema');
|
|
581
|
+
}
|
|
582
|
+
else if (schema == null) {
|
|
583
|
+
schema = this.config.get('schema');
|
|
584
|
+
}
|
|
585
|
+
const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
|
|
586
|
+
const persister = groups[tableName] ??= new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, schema);
|
|
587
|
+
persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
|
|
588
|
+
}
|
|
589
|
+
for (const persister of core_1.Utils.values(groups)) {
|
|
590
|
+
await this.rethrow(persister.execute());
|
|
457
591
|
}
|
|
458
|
-
return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
|
|
459
592
|
}
|
|
460
|
-
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
|
|
461
|
-
const pivotProp2 = this.getPivotInverseProperty(prop);
|
|
462
|
-
const ownerMeta = this.metadata.find(pivotProp2.type);
|
|
593
|
+
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
|
|
463
594
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
464
|
-
const
|
|
595
|
+
const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
|
|
596
|
+
const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
|
|
597
|
+
const ownerMeta = this.metadata.find(pivotProp2.type);
|
|
598
|
+
options = { ...options };
|
|
599
|
+
const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType)
|
|
600
|
+
.withSchema(this.getSchemaName(pivotMeta, options))
|
|
601
|
+
.indexHint(options.indexHint)
|
|
602
|
+
.comment(options.comments)
|
|
603
|
+
.hintComment(options.hintComments);
|
|
604
|
+
const pivotAlias = qb.alias;
|
|
605
|
+
const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(core_1.Utils.PK_SEPARATOR);
|
|
606
|
+
const cond = {
|
|
607
|
+
[pivotKey]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
|
|
608
|
+
};
|
|
465
609
|
/* istanbul ignore if */
|
|
466
610
|
if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
|
|
467
611
|
where = cond;
|
|
@@ -469,29 +613,50 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
469
613
|
else {
|
|
470
614
|
where = { ...where, ...cond };
|
|
471
615
|
}
|
|
472
|
-
orderBy = this.getPivotOrderBy(prop, orderBy);
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
616
|
+
orderBy = this.getPivotOrderBy(prop, pivotProp1, pivotAlias, orderBy);
|
|
617
|
+
const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
|
|
618
|
+
const fields = [];
|
|
619
|
+
const k1 = !prop.owner ? 'joinColumns' : 'inverseJoinColumns';
|
|
620
|
+
const k2 = prop.owner ? 'joinColumns' : 'inverseJoinColumns';
|
|
621
|
+
const cols = [
|
|
622
|
+
...prop[k1].map(col => `${pivotAlias}.${col} as fk__${col}`),
|
|
623
|
+
...prop[k2].map(col => `${pivotAlias}.${col} as fk__${col}`),
|
|
624
|
+
];
|
|
625
|
+
fields.push(...cols);
|
|
626
|
+
if (!pivotJoin) {
|
|
627
|
+
const targetAlias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
628
|
+
const targetSchema = this.getSchemaName(prop.targetMeta, options) ?? this.platform.getDefaultSchemaName();
|
|
629
|
+
qb.innerJoin(pivotProp1.name, targetAlias, {}, targetSchema);
|
|
630
|
+
const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options.fields);
|
|
631
|
+
for (const field of targetFields) {
|
|
632
|
+
const f = field.toString();
|
|
633
|
+
fields.unshift(f.includes('.') ? field : `${targetAlias}.${f}`);
|
|
634
|
+
}
|
|
635
|
+
// we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
|
|
636
|
+
populate.forEach(hint => {
|
|
637
|
+
const alias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
638
|
+
qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
|
|
639
|
+
// eslint-disable-next-line dot-notation
|
|
640
|
+
Object.values(qb['_joins']).forEach(join => {
|
|
641
|
+
if (join.alias === alias && join.prop.name === hint.field) {
|
|
642
|
+
fields.push(...qb.helper.mapJoinColumns(qb.type, join));
|
|
643
|
+
}
|
|
644
|
+
});
|
|
488
645
|
});
|
|
489
646
|
}
|
|
490
|
-
|
|
647
|
+
qb.select(fields)
|
|
648
|
+
.where({ [pivotProp1.name]: where })
|
|
649
|
+
.orderBy(orderBy)
|
|
650
|
+
.setLockMode(options.lockMode, options.lockTableAliases);
|
|
651
|
+
if (owners.length === 1 && (options.offset != null || options.limit != null)) {
|
|
652
|
+
qb.limit(options.limit, options.offset);
|
|
653
|
+
}
|
|
654
|
+
const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
|
|
655
|
+
const items = res.map((row) => super.mapResult(row, prop.targetMeta));
|
|
491
656
|
const map = {};
|
|
492
657
|
const pkProps = ownerMeta.getPrimaryProps();
|
|
493
658
|
owners.forEach(owner => {
|
|
494
|
-
const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((
|
|
659
|
+
const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
|
|
495
660
|
const pkProp = pkProps[idx];
|
|
496
661
|
return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
|
|
497
662
|
}));
|
|
@@ -504,10 +669,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
504
669
|
}));
|
|
505
670
|
map[key].push(item);
|
|
506
671
|
prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
|
|
507
|
-
prop.inverseJoinColumns.forEach(col =>
|
|
672
|
+
prop.inverseJoinColumns.forEach((col, idx) => {
|
|
673
|
+
core_1.Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
|
|
674
|
+
});
|
|
508
675
|
});
|
|
509
676
|
return map;
|
|
510
677
|
}
|
|
678
|
+
getPivotOrderBy(prop, pivotProp, pivotAlias, orderBy) {
|
|
679
|
+
// FIXME this is ignoring the rest of the array items
|
|
680
|
+
if (!core_1.Utils.isEmpty(orderBy)) {
|
|
681
|
+
return [{ [pivotProp.name]: core_1.Utils.asArray(orderBy)[0] }];
|
|
682
|
+
}
|
|
683
|
+
if (!core_1.Utils.isEmpty(prop.orderBy)) {
|
|
684
|
+
return [{ [pivotProp.name]: core_1.Utils.asArray(prop.orderBy)[0] }];
|
|
685
|
+
}
|
|
686
|
+
if (prop.fixedOrder) {
|
|
687
|
+
return [{ [`${pivotAlias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC }];
|
|
688
|
+
}
|
|
689
|
+
return [];
|
|
690
|
+
}
|
|
511
691
|
async execute(queryOrKnex, params = [], method = 'all', ctx) {
|
|
512
692
|
return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
|
|
513
693
|
}
|
|
@@ -520,59 +700,80 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
520
700
|
}
|
|
521
701
|
const relationsToPopulate = populate.map(({ field }) => field);
|
|
522
702
|
const toPopulate = meta.relations
|
|
523
|
-
.filter(prop => prop.
|
|
703
|
+
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
|
|
524
704
|
.filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
525
705
|
.map(prop => ({ field: prop.name, strategy: prop.strategy }));
|
|
526
706
|
return [...populate, ...toPopulate];
|
|
527
707
|
}
|
|
708
|
+
/**
|
|
709
|
+
* @internal
|
|
710
|
+
*/
|
|
528
711
|
joinedProps(meta, populate) {
|
|
529
712
|
return populate.filter(p => {
|
|
530
713
|
const prop = meta.properties[p.field] || {};
|
|
531
|
-
return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED &&
|
|
714
|
+
return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && ![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind);
|
|
532
715
|
});
|
|
533
716
|
}
|
|
534
717
|
/**
|
|
535
718
|
* @internal
|
|
536
719
|
*/
|
|
537
|
-
mergeJoinedResult(rawResults, meta) {
|
|
538
|
-
// group by the root entity primary key first
|
|
539
|
-
const map = {};
|
|
720
|
+
mergeJoinedResult(rawResults, meta, joinedProps) {
|
|
540
721
|
const res = [];
|
|
541
|
-
|
|
722
|
+
const map = {};
|
|
723
|
+
for (const item of rawResults) {
|
|
542
724
|
const pk = core_1.Utils.getCompositeKeyHash(item, meta);
|
|
543
725
|
if (map[pk]) {
|
|
544
|
-
|
|
726
|
+
for (const hint of joinedProps) {
|
|
727
|
+
const prop = meta.properties[hint.field];
|
|
728
|
+
if (!item[hint.field]) {
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
switch (prop.kind) {
|
|
732
|
+
case core_1.ReferenceKind.ONE_TO_MANY:
|
|
733
|
+
case core_1.ReferenceKind.MANY_TO_MANY:
|
|
734
|
+
map[pk][hint.field] = this.mergeJoinedResult([...map[pk][hint.field], ...item[hint.field]], prop.targetMeta, hint.children ?? []);
|
|
735
|
+
break;
|
|
736
|
+
case core_1.ReferenceKind.MANY_TO_ONE:
|
|
737
|
+
case core_1.ReferenceKind.ONE_TO_ONE:
|
|
738
|
+
map[pk][hint.field] = this.mergeJoinedResult([map[pk][hint.field], item[hint.field]], prop.targetMeta, hint.children ?? [])[0];
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
545
742
|
}
|
|
546
743
|
else {
|
|
547
|
-
map[pk] =
|
|
744
|
+
map[pk] = item;
|
|
548
745
|
res.push(item);
|
|
549
746
|
}
|
|
550
|
-
}
|
|
747
|
+
}
|
|
551
748
|
return res;
|
|
552
749
|
}
|
|
553
750
|
getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
|
|
554
751
|
const fields = [];
|
|
555
752
|
const joinedProps = this.joinedProps(meta, populate);
|
|
753
|
+
if (explicitFields?.includes('*')) {
|
|
754
|
+
fields.push('*');
|
|
755
|
+
}
|
|
556
756
|
const shouldHaveColumn = (prop, populate, fields) => {
|
|
557
757
|
if (!this.platform.shouldHaveColumn(prop, populate)) {
|
|
558
758
|
return false;
|
|
559
759
|
}
|
|
560
760
|
if (!fields || prop.primary) {
|
|
561
|
-
return
|
|
761
|
+
return !fields?.includes('*');
|
|
562
762
|
}
|
|
563
|
-
return fields.
|
|
763
|
+
return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
|
|
564
764
|
};
|
|
565
765
|
// alias all fields in the primary table
|
|
566
766
|
meta.props
|
|
567
767
|
.filter(prop => shouldHaveColumn(prop, populate, explicitFields))
|
|
568
768
|
.forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, parentTableAlias)));
|
|
569
769
|
joinedProps.forEach(relation => {
|
|
570
|
-
const
|
|
770
|
+
const [propName] = relation.field.split(':', 2);
|
|
771
|
+
const prop = meta.properties[propName];
|
|
571
772
|
const meta2 = this.metadata.find(prop.type);
|
|
572
773
|
const tableAlias = qb.getNextAlias(prop.name);
|
|
573
774
|
const field = parentTableAlias ? `${parentTableAlias}.${prop.name}` : prop.name;
|
|
574
775
|
const path = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
|
|
575
|
-
qb.join(field, tableAlias, {},
|
|
776
|
+
qb.join(field, tableAlias, {}, query_1.JoinType.leftJoin, path);
|
|
576
777
|
const childExplicitFields = explicitFields?.filter(f => core_1.Utils.isPlainObject(f)).map(o => o[prop.name])[0] || [];
|
|
577
778
|
explicitFields?.forEach(f => {
|
|
578
779
|
if (typeof f === 'string' && f.startsWith(`${prop.name}.`)) {
|
|
@@ -587,24 +788,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
587
788
|
* @internal
|
|
588
789
|
*/
|
|
589
790
|
mapPropToFieldNames(qb, prop, tableAlias) {
|
|
590
|
-
const
|
|
791
|
+
const knex = this.connection.getKnex();
|
|
792
|
+
const aliased = knex.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
|
|
591
793
|
if (prop.customType?.convertToJSValueSQL && tableAlias) {
|
|
592
|
-
const prefixed =
|
|
593
|
-
return [
|
|
794
|
+
const prefixed = knex.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
|
|
795
|
+
return [(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
|
|
594
796
|
}
|
|
595
797
|
if (prop.formula) {
|
|
596
|
-
const alias =
|
|
597
|
-
return [`${prop.formula(alias)} as ${aliased}`];
|
|
798
|
+
const alias = knex.ref(tableAlias ?? qb.alias).toString();
|
|
799
|
+
return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
|
|
598
800
|
}
|
|
599
801
|
if (tableAlias) {
|
|
600
|
-
return prop.fieldNames.map(fieldName =>
|
|
802
|
+
return prop.fieldNames.map(fieldName => knex.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
|
|
601
803
|
}
|
|
602
804
|
return prop.fieldNames;
|
|
603
805
|
}
|
|
604
806
|
/** @internal */
|
|
605
|
-
createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
|
|
807
|
+
createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, logging) {
|
|
606
808
|
const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
|
|
607
|
-
const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType);
|
|
809
|
+
const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType, undefined, logging);
|
|
608
810
|
if (!convertCustomTypes) {
|
|
609
811
|
qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
|
|
610
812
|
}
|
|
@@ -628,7 +830,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
628
830
|
}
|
|
629
831
|
const ret = {};
|
|
630
832
|
this.metadata.find(entityName).relations.forEach(prop => {
|
|
631
|
-
if (prop.
|
|
833
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && data[prop.name]) {
|
|
632
834
|
ret[prop.name] = data[prop.name].map((item) => core_1.Utils.asArray(item));
|
|
633
835
|
delete data[prop.name];
|
|
634
836
|
}
|
|
@@ -641,65 +843,31 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
641
843
|
}
|
|
642
844
|
for (const prop of meta.relations) {
|
|
643
845
|
if (collections[prop.name]) {
|
|
644
|
-
|
|
846
|
+
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
847
|
+
const persister = new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
|
|
848
|
+
persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
|
|
849
|
+
await this.rethrow(persister.execute());
|
|
645
850
|
}
|
|
646
851
|
}
|
|
647
852
|
}
|
|
648
|
-
async updateCollectionDiff(meta, prop, pks, deleteDiff, insertDiff, options) {
|
|
649
|
-
if (!deleteDiff) {
|
|
650
|
-
deleteDiff = [];
|
|
651
|
-
}
|
|
652
|
-
if (deleteDiff === true || deleteDiff.length > 0) {
|
|
653
|
-
const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
|
|
654
|
-
.withSchema(this.getSchemaName(meta, options))
|
|
655
|
-
.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
|
|
656
|
-
const knex = qb1.getKnex();
|
|
657
|
-
if (Array.isArray(deleteDiff)) {
|
|
658
|
-
knex.whereIn(prop.inverseJoinColumns, deleteDiff);
|
|
659
|
-
}
|
|
660
|
-
prop.joinColumns.forEach((joinColumn, idx) => knex.andWhere(joinColumn, pks[idx]));
|
|
661
|
-
await this.execute(knex.delete());
|
|
662
|
-
}
|
|
663
|
-
if (insertDiff.length === 0) {
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
const items = insertDiff.map(item => {
|
|
667
|
-
const cond = {};
|
|
668
|
-
prop.joinColumns.forEach((joinColumn, idx) => cond[joinColumn] = pks[idx]);
|
|
669
|
-
prop.inverseJoinColumns.forEach((inverseJoinColumn, idx) => cond[inverseJoinColumn] = item[idx]);
|
|
670
|
-
return cond;
|
|
671
|
-
});
|
|
672
|
-
/* istanbul ignore else */
|
|
673
|
-
if (this.platform.allowsMultiInsert()) {
|
|
674
|
-
await this.nativeInsertMany(prop.pivotEntity, items, { ...options, convertCustomTypes: false, processCollections: false });
|
|
675
|
-
}
|
|
676
|
-
else {
|
|
677
|
-
await core_1.Utils.runSerial(items, item => {
|
|
678
|
-
return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
|
|
679
|
-
.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
|
|
680
|
-
.withSchema(this.getSchemaName(meta, options))
|
|
681
|
-
.insert(item)
|
|
682
|
-
.execute('run', false);
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
853
|
async lockPessimistic(entity, options) {
|
|
687
854
|
const meta = (0, core_1.helper)(entity).__meta;
|
|
688
|
-
const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).
|
|
855
|
+
const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
|
|
689
856
|
const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
|
|
690
|
-
qb.select('1').where(cond).setLockMode(options.lockMode, options.lockTableAliases);
|
|
857
|
+
qb.select((0, core_1.raw)('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
|
|
691
858
|
await this.rethrow(qb.execute());
|
|
692
859
|
}
|
|
693
860
|
buildJoinedPropsOrderBy(entityName, qb, meta, populate, parentPath) {
|
|
694
861
|
const orderBy = [];
|
|
695
862
|
const joinedProps = this.joinedProps(meta, populate);
|
|
696
863
|
joinedProps.forEach(relation => {
|
|
697
|
-
const
|
|
864
|
+
const [propName] = relation.field.split(':', 2);
|
|
865
|
+
const prop = meta.properties[propName];
|
|
698
866
|
const propOrderBy = prop.orderBy;
|
|
699
867
|
const path = `${parentPath ? parentPath : entityName}.${relation.field}`;
|
|
700
868
|
const propAlias = qb.getAliasForJoinPath(path);
|
|
701
869
|
if (propOrderBy) {
|
|
702
|
-
|
|
870
|
+
core_1.Utils.keys(propOrderBy).forEach(field => {
|
|
703
871
|
orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
|
|
704
872
|
});
|
|
705
873
|
}
|
|
@@ -726,12 +894,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
726
894
|
return ret;
|
|
727
895
|
}
|
|
728
896
|
processField(meta, prop, field, ret, populate, joinedProps, qb) {
|
|
729
|
-
if (!prop || (prop.
|
|
897
|
+
if (!prop || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner)) {
|
|
730
898
|
return;
|
|
731
899
|
}
|
|
732
|
-
if (prop.
|
|
900
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
733
901
|
if (prop.object) {
|
|
734
|
-
ret.push(prop.
|
|
902
|
+
ret.push(prop.name);
|
|
735
903
|
return;
|
|
736
904
|
}
|
|
737
905
|
const parts = field.split('.');
|
|
@@ -743,16 +911,15 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
743
911
|
}
|
|
744
912
|
return;
|
|
745
913
|
}
|
|
746
|
-
|
|
747
|
-
ret.push(prop.fieldNames[0]);
|
|
748
|
-
}
|
|
914
|
+
ret.push(prop.name);
|
|
749
915
|
}
|
|
750
|
-
buildFields(meta, populate, joinedProps, qb, fields) {
|
|
916
|
+
buildFields(meta, populate, joinedProps, qb, alias, fields) {
|
|
751
917
|
const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
|
|
752
918
|
const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
|
|
753
|
-
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
|
|
919
|
+
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
|
|
754
920
|
const hasExplicitFields = !!fields;
|
|
755
921
|
const ret = [];
|
|
922
|
+
let addFormulas = false;
|
|
756
923
|
if (joinedProps.length > 0) {
|
|
757
924
|
ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate));
|
|
758
925
|
}
|
|
@@ -778,20 +945,22 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
778
945
|
else if (lazyProps.filter(p => !p.formula).length > 0) {
|
|
779
946
|
const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
|
|
780
947
|
ret.push(...core_1.Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
|
|
948
|
+
addFormulas = true;
|
|
781
949
|
}
|
|
782
950
|
else if (hasLazyFormulas || requiresSQLConversion) {
|
|
783
951
|
ret.push('*');
|
|
952
|
+
addFormulas = true;
|
|
784
953
|
}
|
|
785
|
-
if (ret.length > 0 && !hasExplicitFields) {
|
|
954
|
+
if (ret.length > 0 && !hasExplicitFields && addFormulas) {
|
|
786
955
|
meta.props
|
|
787
956
|
.filter(prop => prop.formula && !lazyProps.includes(prop))
|
|
788
957
|
.forEach(prop => {
|
|
789
|
-
const
|
|
790
|
-
const aliased =
|
|
791
|
-
ret.push(`${prop.formula(
|
|
958
|
+
const a = this.connection.getKnex().ref(alias).toString();
|
|
959
|
+
const aliased = this.connection.getKnex().ref(prop.fieldNames[0]).toString();
|
|
960
|
+
ret.push((0, core_1.raw)(`${prop.formula(a)} as ${aliased}`));
|
|
792
961
|
});
|
|
793
962
|
meta.props
|
|
794
|
-
.filter(prop => prop.
|
|
963
|
+
.filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
|
|
795
964
|
.forEach(prop => ret.push(prop.name));
|
|
796
965
|
}
|
|
797
966
|
return ret.length > 0 ? core_1.Utils.unique(ret) : ['*'];
|