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