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