@mikro-orm/knex 6.0.0-dev.10 → 6.0.0-dev.101
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 +22 -22
- package/AbstractSqlDriver.js +233 -88
- package/AbstractSqlPlatform.d.ts +5 -4
- package/AbstractSqlPlatform.js +34 -5
- package/MonkeyPatchable.d.ts +1 -0
- package/MonkeyPatchable.js +3 -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 +22 -6
- package/package.json +8 -8
- package/query/ArrayCriteriaNode.d.ts +3 -3
- package/query/CriteriaNode.d.ts +8 -8
- package/query/CriteriaNode.js +16 -9
- package/query/CriteriaNodeFactory.d.ts +6 -6
- package/query/CriteriaNodeFactory.js +10 -13
- package/query/ObjectCriteriaNode.d.ts +3 -3
- package/query/ObjectCriteriaNode.js +12 -8
- package/query/QueryBuilder.d.ts +41 -19
- package/query/QueryBuilder.js +319 -110
- package/query/QueryBuilderHelper.d.ts +9 -13
- package/query/QueryBuilderHelper.js +123 -97
- package/query/ScalarCriteriaNode.d.ts +3 -2
- package/query/ScalarCriteriaNode.js +9 -6
- package/query/enums.js +1 -1
- package/schema/DatabaseSchema.d.ts +5 -2
- package/schema/DatabaseSchema.js +26 -8
- package/schema/DatabaseTable.d.ts +4 -2
- package/schema/DatabaseTable.js +53 -28
- package/schema/SchemaComparator.js +12 -2
- package/schema/SchemaHelper.d.ts +6 -4
- package/schema/SchemaHelper.js +11 -4
- package/schema/SqlSchemaGenerator.d.ts +3 -6
- package/schema/SqlSchemaGenerator.js +61 -28
- package/typings.d.ts +12 -7
package/AbstractSqlDriver.js
CHANGED
|
@@ -5,9 +5,12 @@ const core_1 = require("@mikro-orm/core");
|
|
|
5
5
|
const query_1 = require("./query");
|
|
6
6
|
const SqlEntityManager_1 = require("./SqlEntityManager");
|
|
7
7
|
class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
8
|
+
[core_1.EntityManagerType];
|
|
9
|
+
connection;
|
|
10
|
+
replicas = [];
|
|
11
|
+
platform;
|
|
8
12
|
constructor(config, platform, connection, connector) {
|
|
9
13
|
super(config, connector);
|
|
10
|
-
this.replicas = [];
|
|
11
14
|
this.connection = new connection(this.config);
|
|
12
15
|
this.replicas = this.createReplicas(conf => new connection(this.config, conf, 'read'));
|
|
13
16
|
this.platform = platform;
|
|
@@ -26,19 +29,31 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
26
29
|
}
|
|
27
30
|
const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
|
|
28
31
|
const joinedProps = this.joinedProps(meta, populate);
|
|
29
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false);
|
|
32
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
|
|
30
33
|
const fields = this.buildFields(meta, populate, joinedProps, qb, options.fields);
|
|
31
34
|
const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(entityName, qb, meta, joinedProps);
|
|
35
|
+
const orderBy = [...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy];
|
|
32
36
|
if (core_1.Utils.isPrimaryKey(where, meta.compositePK)) {
|
|
33
37
|
where = { [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
|
|
34
38
|
}
|
|
39
|
+
const { first, last, before, after } = options;
|
|
40
|
+
const isCursorPagination = [first, last, before, after].some(v => v != null);
|
|
35
41
|
qb.select(fields)
|
|
36
42
|
.populate(populate, joinedProps.length > 0 ? options.populateWhere : undefined)
|
|
37
43
|
.where(where)
|
|
38
|
-
.orderBy([...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy])
|
|
39
44
|
.groupBy(options.groupBy)
|
|
40
45
|
.having(options.having)
|
|
46
|
+
.indexHint(options.indexHint)
|
|
47
|
+
.comment(options.comments)
|
|
48
|
+
.hintComment(options.hintComments)
|
|
41
49
|
.withSchema(this.getSchemaName(meta, options));
|
|
50
|
+
if (isCursorPagination) {
|
|
51
|
+
const { orderBy: newOrderBy, where } = this.processCursorOptions(meta, options, orderBy);
|
|
52
|
+
qb.andWhere(where).orderBy(newOrderBy);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
qb.orderBy(orderBy);
|
|
56
|
+
}
|
|
42
57
|
if (options.limit !== undefined) {
|
|
43
58
|
qb.limit(options.limit, options.offset);
|
|
44
59
|
}
|
|
@@ -47,8 +62,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
47
62
|
}
|
|
48
63
|
core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
|
|
49
64
|
const result = await this.rethrow(qb.execute('all'));
|
|
65
|
+
if (isCursorPagination && !first && !!last) {
|
|
66
|
+
result.reverse();
|
|
67
|
+
}
|
|
50
68
|
if (joinedProps.length > 0) {
|
|
51
|
-
return this.mergeJoinedResult(result, meta);
|
|
69
|
+
return this.mergeJoinedResult(result, meta, joinedProps);
|
|
52
70
|
}
|
|
53
71
|
return result;
|
|
54
72
|
}
|
|
@@ -61,7 +79,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
61
79
|
opts.limit = 1;
|
|
62
80
|
}
|
|
63
81
|
if (opts.limit > 0 && !opts.flags?.includes(core_1.QueryFlag.DISABLE_PAGINATE)) {
|
|
64
|
-
opts.flags
|
|
82
|
+
opts.flags ??= [];
|
|
65
83
|
opts.flags.push(core_1.QueryFlag.DISABLE_PAGINATE);
|
|
66
84
|
}
|
|
67
85
|
const res = await this.find(entityName, where, opts);
|
|
@@ -79,7 +97,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
79
97
|
const em = this.createEntityManager(false);
|
|
80
98
|
em.setTransactionContext(options.ctx);
|
|
81
99
|
const res = meta.expression(em, where, options);
|
|
82
|
-
if (res instanceof
|
|
100
|
+
if (res instanceof query_1.QueryBuilder) {
|
|
83
101
|
return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options);
|
|
84
102
|
}
|
|
85
103
|
/* istanbul ignore next */
|
|
@@ -97,7 +115,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
97
115
|
const em = this.createEntityManager(false);
|
|
98
116
|
em.setTransactionContext(options.ctx);
|
|
99
117
|
const res = meta.expression(em, where, options);
|
|
100
|
-
if (res instanceof
|
|
118
|
+
if (res instanceof query_1.QueryBuilder) {
|
|
101
119
|
return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, query_1.QueryType.COUNT);
|
|
102
120
|
}
|
|
103
121
|
/* istanbul ignore next */
|
|
@@ -105,14 +123,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
105
123
|
}
|
|
106
124
|
async wrapVirtualExpressionInSubquery(meta, expression, where, options, type = query_1.QueryType.SELECT) {
|
|
107
125
|
const qb = this.createQueryBuilder(meta.className, options?.ctx, options.connectionType, options.convertCustomTypes)
|
|
108
|
-
.limit(options?.limit, options?.offset)
|
|
126
|
+
.limit(options?.limit, options?.offset)
|
|
127
|
+
.indexHint(options.indexHint)
|
|
128
|
+
.comment(options.comments)
|
|
129
|
+
.hintComment(options.hintComments);
|
|
109
130
|
if (options.orderBy) {
|
|
110
131
|
qb.orderBy(options.orderBy);
|
|
111
132
|
}
|
|
112
133
|
qb.where(where);
|
|
113
134
|
const kqb = qb.getKnexQuery().clear('select');
|
|
114
135
|
if (type === query_1.QueryType.COUNT) {
|
|
115
|
-
kqb.select(
|
|
136
|
+
kqb.select(this.connection.getKnex().raw('count(*) as count'));
|
|
116
137
|
}
|
|
117
138
|
else { // select
|
|
118
139
|
kqb.select('*');
|
|
@@ -154,11 +175,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
154
175
|
}));
|
|
155
176
|
if (!hasPK) {
|
|
156
177
|
// initialize empty collections
|
|
157
|
-
if ([core_1.
|
|
158
|
-
result[relation.name]
|
|
178
|
+
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
|
|
179
|
+
result[relation.name] ??= [];
|
|
159
180
|
}
|
|
160
181
|
return;
|
|
161
182
|
}
|
|
183
|
+
const tz = this.platform.getTimezone();
|
|
162
184
|
meta2.props
|
|
163
185
|
.filter(prop => this.platform.shouldHaveColumn(prop, p.children || []))
|
|
164
186
|
.forEach(prop => {
|
|
@@ -166,6 +188,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
166
188
|
relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
|
|
167
189
|
prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
|
|
168
190
|
}
|
|
191
|
+
else if (prop.runtimeType === 'Date') {
|
|
192
|
+
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
193
|
+
relationPojo[prop.name] = (typeof root[alias] === 'string' ? new Date(root[alias]) : root[alias]);
|
|
194
|
+
delete root[alias];
|
|
195
|
+
}
|
|
169
196
|
else {
|
|
170
197
|
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
171
198
|
relationPojo[prop.name] = root[alias];
|
|
@@ -179,8 +206,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
179
206
|
else {
|
|
180
207
|
map[key] = result;
|
|
181
208
|
}
|
|
182
|
-
if ([core_1.
|
|
183
|
-
result[relation.name]
|
|
209
|
+
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
|
|
210
|
+
result[relation.name] ??= [];
|
|
184
211
|
this.appendToCollection(meta2, result[relation.name], relationPojo);
|
|
185
212
|
}
|
|
186
213
|
else {
|
|
@@ -207,6 +234,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
207
234
|
return this.countVirtual(entityName, where, options);
|
|
208
235
|
}
|
|
209
236
|
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
|
|
237
|
+
.indexHint(options.indexHint)
|
|
238
|
+
.comment(options.comments)
|
|
239
|
+
.hintComment(options.hintComments)
|
|
210
240
|
.groupBy(options.groupBy)
|
|
211
241
|
.having(options.having)
|
|
212
242
|
.populate(options.populate ?? [])
|
|
@@ -215,7 +245,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
215
245
|
return this.rethrow(qb.getCount());
|
|
216
246
|
}
|
|
217
247
|
async nativeInsert(entityName, data, options = {}) {
|
|
218
|
-
options.convertCustomTypes
|
|
248
|
+
options.convertCustomTypes ??= true;
|
|
219
249
|
const meta = this.metadata.find(entityName);
|
|
220
250
|
const collections = this.extractManyToMany(entityName, data);
|
|
221
251
|
const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
|
|
@@ -235,16 +265,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
235
265
|
return res;
|
|
236
266
|
}
|
|
237
267
|
async nativeInsertMany(entityName, data, options = {}) {
|
|
238
|
-
options.processCollections
|
|
239
|
-
options.convertCustomTypes
|
|
268
|
+
options.processCollections ??= true;
|
|
269
|
+
options.convertCustomTypes ??= true;
|
|
240
270
|
const meta = this.metadata.find(entityName);
|
|
241
271
|
const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
|
|
242
272
|
const pks = this.getPrimaryKeyFields(entityName);
|
|
243
273
|
const set = new Set();
|
|
244
|
-
data.forEach(row =>
|
|
274
|
+
data.forEach(row => core_1.Utils.keys(row).forEach(k => set.add(k)));
|
|
245
275
|
const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
|
|
246
|
-
|
|
276
|
+
let fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
|
|
277
|
+
const duplicates = core_1.Utils.findDuplicates(fields);
|
|
247
278
|
const params = [];
|
|
279
|
+
if (duplicates.length) {
|
|
280
|
+
fields = core_1.Utils.unique(fields);
|
|
281
|
+
}
|
|
248
282
|
/* istanbul ignore next */
|
|
249
283
|
const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
|
|
250
284
|
let sql = `insert into ${tableName} `;
|
|
@@ -255,29 +289,45 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
255
289
|
else {
|
|
256
290
|
sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
|
|
257
291
|
}
|
|
292
|
+
const addParams = (prop, row) => {
|
|
293
|
+
if (options.convertCustomTypes && prop.customType) {
|
|
294
|
+
params.push(prop.customType.convertToDatabaseValue(row[prop.name], this.platform, { key: prop.name, mode: 'query-data' }));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
params.push(row[prop.name]);
|
|
298
|
+
};
|
|
258
299
|
if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
|
|
259
300
|
sql += data.map(row => {
|
|
260
301
|
const keys = [];
|
|
261
302
|
props.forEach(prop => {
|
|
262
303
|
if (prop.fieldNames.length > 1) {
|
|
263
|
-
|
|
264
|
-
|
|
304
|
+
const param = [...row[prop.name] ?? prop.fieldNames.map(() => null)];
|
|
305
|
+
const key = (row[prop.name] ?? prop.fieldNames).map(() => '?');
|
|
306
|
+
prop.fieldNames.forEach((field, idx) => {
|
|
307
|
+
if (duplicates.includes(field)) {
|
|
308
|
+
param.splice(idx, 1);
|
|
309
|
+
key.splice(idx, 1);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
params.push(...param);
|
|
313
|
+
keys.push(...key);
|
|
265
314
|
}
|
|
266
315
|
else if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
|
|
267
316
|
keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
|
|
268
|
-
|
|
317
|
+
addParams(prop, row);
|
|
269
318
|
}
|
|
270
319
|
else {
|
|
271
|
-
|
|
320
|
+
addParams(prop, row);
|
|
272
321
|
keys.push('?');
|
|
273
322
|
}
|
|
274
323
|
});
|
|
275
324
|
return '(' + (keys.join(', ') || 'default') + ')';
|
|
276
325
|
}).join(', ');
|
|
277
326
|
}
|
|
278
|
-
if (this.platform.usesReturningStatement()) {
|
|
279
|
-
|
|
280
|
-
|
|
327
|
+
if (meta && this.platform.usesReturningStatement()) {
|
|
328
|
+
const returningProps = meta.props
|
|
329
|
+
.filter(prop => prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw))
|
|
330
|
+
.filter(prop => !(prop.name in data[0]) || core_1.Utils.isRawSql(data[0][prop.name]));
|
|
281
331
|
const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
|
|
282
332
|
/* istanbul ignore next */
|
|
283
333
|
sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
|
|
@@ -289,8 +339,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
289
339
|
pk = data.map(d => core_1.Utils.getPrimaryKeyCond(d, pks));
|
|
290
340
|
}
|
|
291
341
|
else {
|
|
292
|
-
res.row
|
|
293
|
-
res.rows
|
|
342
|
+
res.row ??= {};
|
|
343
|
+
res.rows ??= [];
|
|
294
344
|
pk = data.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]]).map(d => [d]);
|
|
295
345
|
res.insertId = res.insertId || res.row[pks[0]];
|
|
296
346
|
}
|
|
@@ -300,7 +350,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
300
350
|
return res;
|
|
301
351
|
}
|
|
302
352
|
async nativeUpdate(entityName, where, data, options = {}) {
|
|
303
|
-
options.convertCustomTypes
|
|
353
|
+
options.convertCustomTypes ??= true;
|
|
304
354
|
const meta = this.metadata.find(entityName);
|
|
305
355
|
const pks = this.getPrimaryKeyFields(entityName);
|
|
306
356
|
const collections = this.extractManyToMany(entityName, data);
|
|
@@ -314,11 +364,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
314
364
|
.withSchema(this.getSchemaName(meta, options));
|
|
315
365
|
if (options.upsert) {
|
|
316
366
|
/* istanbul ignore next */
|
|
317
|
-
const uniqueFields = core_1.Utils.isPlainObject(where) ?
|
|
318
|
-
|
|
367
|
+
const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where) ? core_1.Utils.keys(where) : meta.primaryKeys);
|
|
368
|
+
const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
|
|
319
369
|
qb.insert(data)
|
|
320
|
-
.onConflict(uniqueFields
|
|
321
|
-
.
|
|
370
|
+
.onConflict(uniqueFields)
|
|
371
|
+
.returning(returning);
|
|
372
|
+
if (!options.onConflictAction || options.onConflictAction === 'merge') {
|
|
373
|
+
const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
|
|
374
|
+
qb.merge(fields);
|
|
375
|
+
}
|
|
376
|
+
if (options.onConflictAction === 'ignore') {
|
|
377
|
+
qb.ignore();
|
|
378
|
+
}
|
|
322
379
|
}
|
|
323
380
|
else {
|
|
324
381
|
qb.update(data).where(where);
|
|
@@ -331,12 +388,36 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
331
388
|
return res;
|
|
332
389
|
}
|
|
333
390
|
async nativeUpdateMany(entityName, where, data, options = {}) {
|
|
334
|
-
options.processCollections
|
|
335
|
-
options.convertCustomTypes
|
|
391
|
+
options.processCollections ??= true;
|
|
392
|
+
options.convertCustomTypes ??= true;
|
|
336
393
|
const meta = this.metadata.get(entityName);
|
|
394
|
+
if (options.upsert) {
|
|
395
|
+
const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys);
|
|
396
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
397
|
+
const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
|
|
398
|
+
qb.insert(data)
|
|
399
|
+
.onConflict(uniqueFields)
|
|
400
|
+
.returning(returning);
|
|
401
|
+
if (!options.onConflictAction || options.onConflictAction === 'merge') {
|
|
402
|
+
const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
|
|
403
|
+
qb.merge(fields);
|
|
404
|
+
}
|
|
405
|
+
if (options.onConflictAction === 'ignore') {
|
|
406
|
+
qb.ignore();
|
|
407
|
+
}
|
|
408
|
+
return qb.execute('run', false);
|
|
409
|
+
}
|
|
337
410
|
const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
|
|
338
411
|
const keys = new Set();
|
|
339
|
-
|
|
412
|
+
const returning = new Set();
|
|
413
|
+
data.forEach(row => {
|
|
414
|
+
core_1.Utils.keys(row).forEach(k => {
|
|
415
|
+
keys.add(k);
|
|
416
|
+
if (core_1.Utils.isRawSql(row[k])) {
|
|
417
|
+
returning.add(k);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
});
|
|
340
421
|
const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
|
|
341
422
|
const params = [];
|
|
342
423
|
let sql = `update ${this.getTableName(meta, options)} set `;
|
|
@@ -365,7 +446,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
365
446
|
const versionProperty = meta.properties[meta.versionProperty];
|
|
366
447
|
const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
|
|
367
448
|
sql += `${quotedFieldName} = `;
|
|
368
|
-
if (versionProperty.
|
|
449
|
+
if (versionProperty.runtimeType === 'Date') {
|
|
369
450
|
sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
|
|
370
451
|
}
|
|
371
452
|
else {
|
|
@@ -396,6 +477,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
396
477
|
return '?';
|
|
397
478
|
});
|
|
398
479
|
sql += ` in (${conds.join(', ')})`;
|
|
480
|
+
if (this.platform.usesReturningStatement() && returning.size > 0) {
|
|
481
|
+
const returningFields = core_1.Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
|
|
482
|
+
/* istanbul ignore next */
|
|
483
|
+
sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
|
|
484
|
+
}
|
|
399
485
|
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
|
|
400
486
|
for (let i = 0; i < collections.length; i++) {
|
|
401
487
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
@@ -430,30 +516,53 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
430
516
|
deleteDiff.push(...snapshot);
|
|
431
517
|
insertDiff.push(...current);
|
|
432
518
|
}
|
|
433
|
-
if (coll.property.
|
|
519
|
+
if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
|
|
434
520
|
const cols = coll.property.referencedColumnNames;
|
|
435
521
|
const qb = this.createQueryBuilder(coll.property.type, ctx, 'write')
|
|
436
|
-
.withSchema(this.getSchemaName(meta, options))
|
|
437
|
-
|
|
522
|
+
.withSchema(this.getSchemaName(meta, options));
|
|
523
|
+
if (coll.getSnapshot() === undefined) {
|
|
524
|
+
if (coll.property.orphanRemoval) {
|
|
525
|
+
const kqb = qb.delete({ [coll.property.mappedBy]: pks })
|
|
526
|
+
.getKnexQuery()
|
|
527
|
+
.whereNotIn(cols, insertDiff);
|
|
528
|
+
return this.rethrow(this.execute(kqb));
|
|
529
|
+
}
|
|
530
|
+
const kqb = qb.update({ [coll.property.mappedBy]: null })
|
|
531
|
+
.getKnexQuery()
|
|
532
|
+
.whereNotIn(cols, insertDiff);
|
|
533
|
+
return this.rethrow(this.execute(kqb));
|
|
534
|
+
}
|
|
535
|
+
const kqb = qb.update({ [coll.property.mappedBy]: pks })
|
|
438
536
|
.getKnexQuery()
|
|
439
537
|
.whereIn(cols, insertDiff);
|
|
440
|
-
return this.rethrow(this.execute(
|
|
538
|
+
return this.rethrow(this.execute(kqb));
|
|
441
539
|
}
|
|
442
540
|
/* istanbul ignore next */
|
|
443
541
|
const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
|
|
444
542
|
const pivotMeta = this.metadata.find(coll.property.pivotEntity);
|
|
445
543
|
if (pivotMeta.schema === '*') {
|
|
446
544
|
/* istanbul ignore next */
|
|
447
|
-
options
|
|
545
|
+
options ??= {};
|
|
448
546
|
options.schema = ownerSchema;
|
|
449
547
|
}
|
|
450
548
|
return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
|
|
451
549
|
}
|
|
452
550
|
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
|
|
453
551
|
const pivotProp2 = this.getPivotInverseProperty(prop);
|
|
552
|
+
const prop2 = prop.targetMeta.properties[prop.mappedBy ?? prop.inversedBy];
|
|
454
553
|
const ownerMeta = this.metadata.find(pivotProp2.type);
|
|
455
554
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
456
|
-
|
|
555
|
+
options = { ...options };
|
|
556
|
+
const qb = this.createQueryBuilder(prop.type, ctx, options.connectionType)
|
|
557
|
+
.withSchema(this.getSchemaName(prop.targetMeta, options))
|
|
558
|
+
.indexHint(options.indexHint)
|
|
559
|
+
.comment(options.comments)
|
|
560
|
+
.hintComment(options.hintComments);
|
|
561
|
+
const pivotAlias = qb.getNextAlias(pivotMeta.tableName);
|
|
562
|
+
const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(core_1.Utils.PK_SEPARATOR);
|
|
563
|
+
const cond = {
|
|
564
|
+
[pivotKey]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
|
|
565
|
+
};
|
|
457
566
|
/* istanbul ignore if */
|
|
458
567
|
if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
|
|
459
568
|
where = cond;
|
|
@@ -461,22 +570,32 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
461
570
|
else {
|
|
462
571
|
where = { ...where, ...cond };
|
|
463
572
|
}
|
|
464
|
-
orderBy = this.getPivotOrderBy(prop, orderBy);
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
573
|
+
orderBy = this.getPivotOrderBy(prop, pivotAlias, orderBy);
|
|
574
|
+
const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
|
|
575
|
+
const fields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, options.fields);
|
|
576
|
+
const k1 = !prop.owner ? 'joinColumns' : 'inverseJoinColumns';
|
|
577
|
+
const k2 = prop.owner ? 'joinColumns' : 'inverseJoinColumns';
|
|
578
|
+
const cols = [
|
|
579
|
+
...prop[k1].map(col => `${pivotAlias}.${col} as fk__${col}`),
|
|
580
|
+
...prop[k2].map(col => `${pivotAlias}.${col} as fk__${col}`),
|
|
581
|
+
];
|
|
582
|
+
fields.push(...cols);
|
|
583
|
+
qb.select(fields)
|
|
584
|
+
.join(prop2.name, pivotAlias, {}, 'pivotJoin', undefined, options.schema)
|
|
585
|
+
.populate(populate)
|
|
586
|
+
.where(where)
|
|
587
|
+
.orderBy(orderBy)
|
|
588
|
+
.setLockMode(options.lockMode, options.lockTableAliases);
|
|
589
|
+
if (owners.length === 1 && (options.offset != null || options.limit != null)) {
|
|
472
590
|
qb.limit(options.limit, options.offset);
|
|
473
591
|
}
|
|
474
|
-
|
|
592
|
+
const schema = this.config.get('schema');
|
|
593
|
+
if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options.schema) {
|
|
475
594
|
// eslint-disable-next-line dot-notation
|
|
476
595
|
qb['finalize']();
|
|
477
596
|
// eslint-disable-next-line dot-notation
|
|
478
597
|
Object.values(qb['_joins']).forEach(join => {
|
|
479
|
-
join.schema =
|
|
598
|
+
join.schema = schema;
|
|
480
599
|
});
|
|
481
600
|
}
|
|
482
601
|
const items = owners.length ? await this.rethrow(qb.execute('all')) : [];
|
|
@@ -500,6 +619,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
500
619
|
});
|
|
501
620
|
return map;
|
|
502
621
|
}
|
|
622
|
+
getPivotOrderBy(prop, pivotAlias, orderBy) {
|
|
623
|
+
if (!core_1.Utils.isEmpty(orderBy)) {
|
|
624
|
+
return orderBy;
|
|
625
|
+
}
|
|
626
|
+
if (!core_1.Utils.isEmpty(prop.orderBy)) {
|
|
627
|
+
return core_1.Utils.asArray(prop.orderBy);
|
|
628
|
+
}
|
|
629
|
+
if (prop.fixedOrder) {
|
|
630
|
+
return [{ [`${pivotAlias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC }];
|
|
631
|
+
}
|
|
632
|
+
return [];
|
|
633
|
+
}
|
|
503
634
|
async execute(queryOrKnex, params = [], method = 'all', ctx) {
|
|
504
635
|
return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
|
|
505
636
|
}
|
|
@@ -512,7 +643,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
512
643
|
}
|
|
513
644
|
const relationsToPopulate = populate.map(({ field }) => field);
|
|
514
645
|
const toPopulate = meta.relations
|
|
515
|
-
.filter(prop => prop.
|
|
646
|
+
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
|
|
516
647
|
.filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
517
648
|
.map(prop => ({ field: prop.name, strategy: prop.strategy }));
|
|
518
649
|
return [...populate, ...toPopulate];
|
|
@@ -520,23 +651,30 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
520
651
|
joinedProps(meta, populate) {
|
|
521
652
|
return populate.filter(p => {
|
|
522
653
|
const prop = meta.properties[p.field] || {};
|
|
523
|
-
return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && prop.
|
|
654
|
+
return (p.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && prop.kind !== core_1.ReferenceKind.SCALAR;
|
|
524
655
|
});
|
|
525
656
|
}
|
|
526
657
|
/**
|
|
527
658
|
* @internal
|
|
528
659
|
*/
|
|
529
|
-
mergeJoinedResult(rawResults, meta) {
|
|
660
|
+
mergeJoinedResult(rawResults, meta, joinedProps) {
|
|
530
661
|
// group by the root entity primary key first
|
|
531
662
|
const map = {};
|
|
532
663
|
const res = [];
|
|
533
664
|
rawResults.forEach(item => {
|
|
534
665
|
const pk = core_1.Utils.getCompositeKeyHash(item, meta);
|
|
535
666
|
if (map[pk]) {
|
|
536
|
-
|
|
667
|
+
joinedProps.forEach(hint => {
|
|
668
|
+
// Sometimes we might join a M:N relation with additional filter on the target entity, and as a result, we get
|
|
669
|
+
// the first result with `null` for all target values, which is mapped as empty array. When we see that happen,
|
|
670
|
+
// we need to merge the results of the next item.
|
|
671
|
+
if (Array.isArray(map[pk][hint.field]) && Array.isArray(item[hint.field]) && map[pk][hint.field].length === 0) {
|
|
672
|
+
item[hint.field].forEach((el) => map[pk][hint.field].push(el));
|
|
673
|
+
}
|
|
674
|
+
});
|
|
537
675
|
}
|
|
538
676
|
else {
|
|
539
|
-
map[pk] =
|
|
677
|
+
map[pk] = item;
|
|
540
678
|
res.push(item);
|
|
541
679
|
}
|
|
542
680
|
});
|
|
@@ -545,14 +683,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
545
683
|
getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
|
|
546
684
|
const fields = [];
|
|
547
685
|
const joinedProps = this.joinedProps(meta, populate);
|
|
686
|
+
if (explicitFields?.includes('*')) {
|
|
687
|
+
fields.push('*');
|
|
688
|
+
}
|
|
548
689
|
const shouldHaveColumn = (prop, populate, fields) => {
|
|
549
690
|
if (!this.platform.shouldHaveColumn(prop, populate)) {
|
|
550
691
|
return false;
|
|
551
692
|
}
|
|
552
693
|
if (!fields || prop.primary) {
|
|
553
|
-
return
|
|
694
|
+
return !fields?.includes('*');
|
|
554
695
|
}
|
|
555
|
-
return fields.
|
|
696
|
+
return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
|
|
556
697
|
};
|
|
557
698
|
// alias all fields in the primary table
|
|
558
699
|
meta.props
|
|
@@ -579,24 +720,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
579
720
|
* @internal
|
|
580
721
|
*/
|
|
581
722
|
mapPropToFieldNames(qb, prop, tableAlias) {
|
|
582
|
-
const
|
|
723
|
+
const knex = this.connection.getKnex();
|
|
724
|
+
const aliased = knex.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
|
|
583
725
|
if (prop.customType?.convertToJSValueSQL && tableAlias) {
|
|
584
|
-
const prefixed =
|
|
585
|
-
return [
|
|
726
|
+
const prefixed = knex.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
|
|
727
|
+
return [(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
|
|
586
728
|
}
|
|
587
729
|
if (prop.formula) {
|
|
588
|
-
const alias =
|
|
589
|
-
return [`${prop.formula(alias)} as ${aliased}`];
|
|
730
|
+
const alias = knex.ref(tableAlias ?? qb.alias).toString();
|
|
731
|
+
return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
|
|
590
732
|
}
|
|
591
733
|
if (tableAlias) {
|
|
592
|
-
return prop.fieldNames.map(fieldName =>
|
|
734
|
+
return prop.fieldNames.map(fieldName => knex.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
|
|
593
735
|
}
|
|
594
736
|
return prop.fieldNames;
|
|
595
737
|
}
|
|
596
738
|
/** @internal */
|
|
597
|
-
createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
|
|
739
|
+
createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, logging) {
|
|
598
740
|
const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
|
|
599
|
-
const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType);
|
|
741
|
+
const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType, undefined, logging);
|
|
600
742
|
if (!convertCustomTypes) {
|
|
601
743
|
qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
|
|
602
744
|
}
|
|
@@ -620,7 +762,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
620
762
|
}
|
|
621
763
|
const ret = {};
|
|
622
764
|
this.metadata.find(entityName).relations.forEach(prop => {
|
|
623
|
-
if (prop.
|
|
765
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && data[prop.name]) {
|
|
624
766
|
ret[prop.name] = data[prop.name].map((item) => core_1.Utils.asArray(item));
|
|
625
767
|
delete data[prop.name];
|
|
626
768
|
}
|
|
@@ -641,10 +783,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
641
783
|
if (!deleteDiff) {
|
|
642
784
|
deleteDiff = [];
|
|
643
785
|
}
|
|
786
|
+
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
644
787
|
if (deleteDiff === true || deleteDiff.length > 0) {
|
|
645
|
-
const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
|
|
646
|
-
.withSchema(this.getSchemaName(meta, options))
|
|
647
|
-
.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
|
|
788
|
+
const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write').withSchema(this.getSchemaName(pivotMeta, options));
|
|
648
789
|
const knex = qb1.getKnex();
|
|
649
790
|
if (Array.isArray(deleteDiff)) {
|
|
650
791
|
knex.whereIn(prop.inverseJoinColumns, deleteDiff);
|
|
@@ -663,13 +804,16 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
663
804
|
});
|
|
664
805
|
/* istanbul ignore else */
|
|
665
806
|
if (this.platform.allowsMultiInsert()) {
|
|
666
|
-
await this.nativeInsertMany(prop.pivotEntity, items, {
|
|
807
|
+
await this.nativeInsertMany(prop.pivotEntity, items, {
|
|
808
|
+
...options,
|
|
809
|
+
convertCustomTypes: false,
|
|
810
|
+
processCollections: false,
|
|
811
|
+
});
|
|
667
812
|
}
|
|
668
813
|
else {
|
|
669
814
|
await core_1.Utils.runSerial(items, item => {
|
|
670
815
|
return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
|
|
671
|
-
.
|
|
672
|
-
.withSchema(this.getSchemaName(meta, options))
|
|
816
|
+
.withSchema(this.getSchemaName(pivotMeta, options))
|
|
673
817
|
.insert(item)
|
|
674
818
|
.execute('run', false);
|
|
675
819
|
});
|
|
@@ -677,9 +821,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
677
821
|
}
|
|
678
822
|
async lockPessimistic(entity, options) {
|
|
679
823
|
const meta = (0, core_1.helper)(entity).__meta;
|
|
680
|
-
const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).
|
|
824
|
+
const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
|
|
681
825
|
const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
|
|
682
|
-
qb.select('1').where(cond).setLockMode(options.lockMode, options.lockTableAliases);
|
|
826
|
+
qb.select((0, core_1.raw)('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
|
|
683
827
|
await this.rethrow(qb.execute());
|
|
684
828
|
}
|
|
685
829
|
buildJoinedPropsOrderBy(entityName, qb, meta, populate, parentPath) {
|
|
@@ -691,7 +835,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
691
835
|
const path = `${parentPath ? parentPath : entityName}.${relation.field}`;
|
|
692
836
|
const propAlias = qb.getAliasForJoinPath(path);
|
|
693
837
|
if (propOrderBy) {
|
|
694
|
-
|
|
838
|
+
core_1.Utils.keys(propOrderBy).forEach(field => {
|
|
695
839
|
orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
|
|
696
840
|
});
|
|
697
841
|
}
|
|
@@ -718,12 +862,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
718
862
|
return ret;
|
|
719
863
|
}
|
|
720
864
|
processField(meta, prop, field, ret, populate, joinedProps, qb) {
|
|
721
|
-
if (!prop || (prop.
|
|
865
|
+
if (!prop || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner)) {
|
|
722
866
|
return;
|
|
723
867
|
}
|
|
724
|
-
if (prop.
|
|
868
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
725
869
|
if (prop.object) {
|
|
726
|
-
ret.push(prop.
|
|
870
|
+
ret.push(prop.name);
|
|
727
871
|
return;
|
|
728
872
|
}
|
|
729
873
|
const parts = field.split('.');
|
|
@@ -735,9 +879,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
735
879
|
}
|
|
736
880
|
return;
|
|
737
881
|
}
|
|
738
|
-
|
|
739
|
-
ret.push(prop.fieldNames[0]);
|
|
740
|
-
}
|
|
882
|
+
ret.push(prop.name);
|
|
741
883
|
}
|
|
742
884
|
buildFields(meta, populate, joinedProps, qb, fields) {
|
|
743
885
|
const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
|
|
@@ -745,6 +887,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
745
887
|
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
|
|
746
888
|
const hasExplicitFields = !!fields;
|
|
747
889
|
const ret = [];
|
|
890
|
+
let addFormulas = false;
|
|
748
891
|
if (joinedProps.length > 0) {
|
|
749
892
|
ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate));
|
|
750
893
|
}
|
|
@@ -770,20 +913,22 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
770
913
|
else if (lazyProps.filter(p => !p.formula).length > 0) {
|
|
771
914
|
const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
|
|
772
915
|
ret.push(...core_1.Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
|
|
916
|
+
addFormulas = true;
|
|
773
917
|
}
|
|
774
918
|
else if (hasLazyFormulas || requiresSQLConversion) {
|
|
775
919
|
ret.push('*');
|
|
920
|
+
addFormulas = true;
|
|
776
921
|
}
|
|
777
|
-
if (ret.length > 0 && !hasExplicitFields) {
|
|
922
|
+
if (ret.length > 0 && !hasExplicitFields && addFormulas) {
|
|
778
923
|
meta.props
|
|
779
924
|
.filter(prop => prop.formula && !lazyProps.includes(prop))
|
|
780
925
|
.forEach(prop => {
|
|
781
|
-
const alias =
|
|
782
|
-
const aliased =
|
|
783
|
-
ret.push(`${prop.formula(alias)} as ${aliased}`);
|
|
926
|
+
const alias = this.connection.getKnex().ref(qb.alias).toString();
|
|
927
|
+
const aliased = this.connection.getKnex().ref(prop.fieldNames[0]).toString();
|
|
928
|
+
ret.push((0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`));
|
|
784
929
|
});
|
|
785
930
|
meta.props
|
|
786
|
-
.filter(prop => prop.
|
|
931
|
+
.filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
|
|
787
932
|
.forEach(prop => ret.push(prop.name));
|
|
788
933
|
}
|
|
789
934
|
return ret.length > 0 ? core_1.Utils.unique(ret) : ['*'];
|