@mikro-orm/knex 6.0.0-dev.32 → 6.0.0-dev.320
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 +22 -4
- package/AbstractSqlConnection.js +45 -20
- package/AbstractSqlDriver.d.ts +30 -24
- package/AbstractSqlDriver.js +606 -268
- package/AbstractSqlPlatform.d.ts +9 -5
- package/AbstractSqlPlatform.js +25 -29
- 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 +4 -8
- package/SqlEntityManager.js +8 -12
- package/SqlEntityRepository.d.ts +7 -5
- package/SqlEntityRepository.js +11 -7
- package/index.mjs +24 -3
- package/package.json +8 -8
- package/query/ArrayCriteriaNode.d.ts +3 -2
- package/query/ArrayCriteriaNode.js +7 -2
- package/query/CriteriaNode.d.ts +6 -4
- package/query/CriteriaNode.js +22 -9
- package/query/CriteriaNodeFactory.d.ts +1 -1
- package/query/CriteriaNodeFactory.js +27 -11
- package/query/ObjectCriteriaNode.d.ts +3 -2
- package/query/ObjectCriteriaNode.js +72 -28
- package/query/QueryBuilder.d.ts +69 -36
- package/query/QueryBuilder.js +469 -146
- package/query/QueryBuilderHelper.d.ts +13 -18
- package/query/QueryBuilderHelper.js +199 -171
- package/query/ScalarCriteriaNode.d.ts +3 -2
- package/query/ScalarCriteriaNode.js +14 -4
- package/query/enums.d.ts +7 -0
- package/query/enums.js +10 -2
- package/schema/DatabaseSchema.d.ts +5 -2
- package/schema/DatabaseSchema.js +26 -8
- package/schema/DatabaseTable.d.ts +12 -4
- package/schema/DatabaseTable.js +370 -42
- package/schema/SchemaComparator.d.ts +7 -5
- package/schema/SchemaComparator.js +69 -35
- package/schema/SchemaHelper.d.ts +7 -5
- package/schema/SchemaHelper.js +30 -11
- package/schema/SqlSchemaGenerator.d.ts +11 -48
- package/schema/SqlSchemaGenerator.js +84 -30
- package/typings.d.ts +27 -8
package/query/QueryBuilder.js
CHANGED
|
@@ -26,6 +26,12 @@ const CriteriaNodeFactory_1 = require("./CriteriaNodeFactory");
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
class QueryBuilder {
|
|
29
|
+
metadata;
|
|
30
|
+
driver;
|
|
31
|
+
context;
|
|
32
|
+
connectionType;
|
|
33
|
+
em;
|
|
34
|
+
loggerContext;
|
|
29
35
|
get mainAlias() {
|
|
30
36
|
this.ensureFromClause();
|
|
31
37
|
return this._mainAlias;
|
|
@@ -37,31 +43,58 @@ class QueryBuilder {
|
|
|
37
43
|
this.ensureFromClause();
|
|
38
44
|
return this._helper;
|
|
39
45
|
}
|
|
46
|
+
/** @internal */
|
|
47
|
+
type;
|
|
48
|
+
/** @internal */
|
|
49
|
+
_fields;
|
|
50
|
+
/** @internal */
|
|
51
|
+
_populate = [];
|
|
52
|
+
/** @internal */
|
|
53
|
+
_populateWhere;
|
|
54
|
+
/** @internal */
|
|
55
|
+
__populateWhere;
|
|
56
|
+
/** @internal */
|
|
57
|
+
_populateMap = {};
|
|
58
|
+
aliasCounter = 0;
|
|
59
|
+
flags = new Set([core_1.QueryFlag.CONVERT_CUSTOM_TYPES]);
|
|
60
|
+
finalized = false;
|
|
61
|
+
_joins = {};
|
|
62
|
+
_explicitAlias = false;
|
|
63
|
+
_schema;
|
|
64
|
+
_cond = {};
|
|
65
|
+
_data;
|
|
66
|
+
_orderBy = [];
|
|
67
|
+
_groupBy = [];
|
|
68
|
+
_having = {};
|
|
69
|
+
_returning;
|
|
70
|
+
_onConflict;
|
|
71
|
+
_limit;
|
|
72
|
+
_offset;
|
|
73
|
+
_distinctOn;
|
|
74
|
+
_joinedProps = new Map();
|
|
75
|
+
_cache;
|
|
76
|
+
_indexHint;
|
|
77
|
+
_comments = [];
|
|
78
|
+
_hintComments = [];
|
|
79
|
+
flushMode;
|
|
80
|
+
lockMode;
|
|
81
|
+
lockTables;
|
|
82
|
+
subQueries = {};
|
|
83
|
+
_mainAlias;
|
|
84
|
+
_aliases = {};
|
|
85
|
+
_helper;
|
|
86
|
+
platform;
|
|
87
|
+
knex;
|
|
40
88
|
/**
|
|
41
89
|
* @internal
|
|
42
90
|
*/
|
|
43
|
-
constructor(entityName, metadata, driver, context, alias, connectionType, em) {
|
|
91
|
+
constructor(entityName, metadata, driver, context, alias, connectionType, em, loggerContext) {
|
|
44
92
|
this.metadata = metadata;
|
|
45
93
|
this.driver = driver;
|
|
46
94
|
this.context = context;
|
|
47
95
|
this.connectionType = connectionType;
|
|
48
96
|
this.em = em;
|
|
49
|
-
|
|
50
|
-
this._populate = [];
|
|
51
|
-
/** @internal */
|
|
52
|
-
this._populateMap = {};
|
|
53
|
-
this.aliasCounter = 0;
|
|
54
|
-
this.flags = new Set([core_1.QueryFlag.CONVERT_CUSTOM_TYPES]);
|
|
55
|
-
this.finalized = false;
|
|
56
|
-
this._joins = {};
|
|
57
|
-
this._explicitAlias = false;
|
|
58
|
-
this._cond = {};
|
|
59
|
-
this._orderBy = [];
|
|
60
|
-
this._groupBy = [];
|
|
61
|
-
this._having = {};
|
|
62
|
-
this._joinedProps = new Map();
|
|
63
|
-
this.subQueries = {};
|
|
64
|
-
this._aliases = {};
|
|
97
|
+
this.loggerContext = loggerContext;
|
|
65
98
|
this.platform = this.driver.getPlatform();
|
|
66
99
|
this.knex = this.driver.getConnection(this.connectionType).getKnex();
|
|
67
100
|
if (alias) {
|
|
@@ -116,27 +149,45 @@ class QueryBuilder {
|
|
|
116
149
|
this._fields = this.mainAlias.metadata.primaryKeys;
|
|
117
150
|
}
|
|
118
151
|
else {
|
|
119
|
-
this._fields = [
|
|
152
|
+
this._fields = [(0, core_1.raw)('*')];
|
|
120
153
|
}
|
|
121
154
|
if (distinct) {
|
|
122
155
|
this.flags.add(core_1.QueryFlag.DISTINCT);
|
|
123
156
|
}
|
|
124
157
|
return this.init(enums_1.QueryType.COUNT);
|
|
125
158
|
}
|
|
126
|
-
join(field, alias, cond = {}, type =
|
|
127
|
-
this.joinReference(field, alias, cond, type, path);
|
|
159
|
+
join(field, alias, cond = {}, type = enums_1.JoinType.innerJoin, path, schema) {
|
|
160
|
+
this.joinReference(field, alias, cond, type, path, schema);
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
innerJoin(field, alias, cond = {}, schema) {
|
|
164
|
+
this.join(field, alias, cond, enums_1.JoinType.innerJoin, undefined, schema);
|
|
128
165
|
return this;
|
|
129
166
|
}
|
|
130
|
-
|
|
131
|
-
|
|
167
|
+
innerJoinLateral(field, alias, cond = {}, schema) {
|
|
168
|
+
this.join(field, alias, cond, enums_1.JoinType.innerJoinLateral, undefined, schema);
|
|
169
|
+
return this;
|
|
132
170
|
}
|
|
133
|
-
|
|
171
|
+
leftJoin(field, alias, cond = {}, schema) {
|
|
172
|
+
return this.join(field, alias, cond, enums_1.JoinType.leftJoin, undefined, schema);
|
|
173
|
+
}
|
|
174
|
+
leftJoinLateral(field, alias, cond = {}, schema) {
|
|
175
|
+
return this.join(field, alias, cond, enums_1.JoinType.leftJoinLateral, undefined, schema);
|
|
176
|
+
}
|
|
177
|
+
joinAndSelect(field, alias, cond = {}, type = enums_1.JoinType.innerJoin, path, fields, schema) {
|
|
134
178
|
if (!this.type) {
|
|
135
179
|
this.select('*');
|
|
136
180
|
}
|
|
137
|
-
|
|
138
|
-
|
|
181
|
+
let subquery;
|
|
182
|
+
if (Array.isArray(field)) {
|
|
183
|
+
subquery = field[1] instanceof QueryBuilder ? field[1].getFormattedQuery() : field[1].toString();
|
|
184
|
+
field = field[0];
|
|
185
|
+
}
|
|
186
|
+
const prop = this.joinReference(field, alias, cond, type, path, schema, subquery);
|
|
139
187
|
const [fromAlias] = this.helper.splitField(field);
|
|
188
|
+
if (subquery) {
|
|
189
|
+
this._joins[`${fromAlias}.${prop.name}#${alias}`].subquery = subquery;
|
|
190
|
+
}
|
|
140
191
|
const populate = this._joinedProps.get(fromAlias);
|
|
141
192
|
const item = { field: prop.name, strategy: core_1.LoadStrategy.JOINED, children: [] };
|
|
142
193
|
if (populate) {
|
|
@@ -146,15 +197,39 @@ class QueryBuilder {
|
|
|
146
197
|
this._populate.push(item);
|
|
147
198
|
}
|
|
148
199
|
this._joinedProps.set(alias, item);
|
|
200
|
+
this.addSelect(this.getFieldsForJoinedLoad(prop, alias, fields));
|
|
149
201
|
return this;
|
|
150
202
|
}
|
|
151
|
-
leftJoinAndSelect(field, alias, cond = {}) {
|
|
152
|
-
return this.joinAndSelect(field, alias, cond,
|
|
203
|
+
leftJoinAndSelect(field, alias, cond = {}, fields, schema) {
|
|
204
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.leftJoin, undefined, fields, schema);
|
|
205
|
+
}
|
|
206
|
+
leftJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
|
|
207
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.leftJoinLateral, undefined, fields, schema);
|
|
208
|
+
}
|
|
209
|
+
innerJoinAndSelect(field, alias, cond = {}, fields, schema) {
|
|
210
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.innerJoin, undefined, fields, schema);
|
|
211
|
+
}
|
|
212
|
+
innerJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
|
|
213
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.innerJoinLateral, undefined, fields, schema);
|
|
153
214
|
}
|
|
154
|
-
getFieldsForJoinedLoad(prop, alias) {
|
|
215
|
+
getFieldsForJoinedLoad(prop, alias, explicitFields) {
|
|
155
216
|
const fields = [];
|
|
217
|
+
const populate = [];
|
|
218
|
+
const joinKey = Object.keys(this._joins).find(join => join.endsWith(`#${alias}`));
|
|
219
|
+
if (joinKey) {
|
|
220
|
+
const path = this._joins[joinKey].path.split('.').slice(1);
|
|
221
|
+
let children = this._populate;
|
|
222
|
+
for (let i = 0; i < path.length; i++) {
|
|
223
|
+
const child = children.filter(hint => {
|
|
224
|
+
const [propName] = hint.field.split(':', 2);
|
|
225
|
+
return propName === path[i];
|
|
226
|
+
});
|
|
227
|
+
children = child.flatMap(c => c.children);
|
|
228
|
+
}
|
|
229
|
+
populate.push(...children);
|
|
230
|
+
}
|
|
156
231
|
prop.targetMeta.props
|
|
157
|
-
.filter(prop => this.platform.shouldHaveColumn(prop,
|
|
232
|
+
.filter(prop => explicitFields ? explicitFields.includes(prop.name) || prop.primary : this.platform.shouldHaveColumn(prop, populate))
|
|
158
233
|
.forEach(prop => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias)));
|
|
159
234
|
return fields;
|
|
160
235
|
}
|
|
@@ -165,9 +240,15 @@ class QueryBuilder {
|
|
|
165
240
|
}
|
|
166
241
|
where(cond, params, operator) {
|
|
167
242
|
this.ensureNotFinalized();
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
243
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(cond);
|
|
244
|
+
if (rawField) {
|
|
245
|
+
const sql = this.platform.formatQuery(rawField.sql, rawField.params);
|
|
246
|
+
cond = { [(0, core_1.raw)(`(${sql})`)]: core_1.Utils.asArray(params) };
|
|
247
|
+
operator ??= '$and';
|
|
248
|
+
}
|
|
249
|
+
else if (core_1.Utils.isString(cond)) {
|
|
250
|
+
cond = { [(0, core_1.raw)(`(${cond})`, core_1.Utils.asArray(params))]: [] };
|
|
251
|
+
operator ??= '$and';
|
|
171
252
|
}
|
|
172
253
|
else {
|
|
173
254
|
cond = core_1.QueryHelper.processWhere({
|
|
@@ -183,19 +264,20 @@ class QueryBuilder {
|
|
|
183
264
|
const op = operator || params;
|
|
184
265
|
const topLevel = !op || !core_1.Utils.hasObjectKeys(this._cond);
|
|
185
266
|
const criteriaNode = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond);
|
|
267
|
+
const ignoreBranching = this.__populateWhere === 'infer';
|
|
186
268
|
if ([enums_1.QueryType.UPDATE, enums_1.QueryType.DELETE].includes(this.type) && criteriaNode.willAutoJoin(this)) {
|
|
187
269
|
// use sub-query to support joining
|
|
188
270
|
this.setFlag(this.type === enums_1.QueryType.UPDATE ? core_1.QueryFlag.UPDATE_SUB_QUERY : core_1.QueryFlag.DELETE_SUB_QUERY);
|
|
189
271
|
this.select(this.mainAlias.metadata.primaryKeys, true);
|
|
190
272
|
}
|
|
191
273
|
if (topLevel) {
|
|
192
|
-
this._cond = criteriaNode.process(this);
|
|
274
|
+
this._cond = criteriaNode.process(this, { ignoreBranching });
|
|
193
275
|
}
|
|
194
276
|
else if (Array.isArray(this._cond[op])) {
|
|
195
|
-
this._cond[op].push(criteriaNode.process(this));
|
|
277
|
+
this._cond[op].push(criteriaNode.process(this, { ignoreBranching }));
|
|
196
278
|
}
|
|
197
279
|
else {
|
|
198
|
-
const cond1 = [this._cond, criteriaNode.process(this)];
|
|
280
|
+
const cond1 = [this._cond, criteriaNode.process(this, { ignoreBranching })];
|
|
199
281
|
this._cond = { [op]: cond1 };
|
|
200
282
|
}
|
|
201
283
|
if (this._onConflict) {
|
|
@@ -222,8 +304,9 @@ class QueryBuilder {
|
|
|
222
304
|
aliasMap: this.getAliasMap(),
|
|
223
305
|
aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
|
|
224
306
|
convertCustomTypes: false,
|
|
307
|
+
type: 'orderBy',
|
|
225
308
|
});
|
|
226
|
-
this._orderBy.push(CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this));
|
|
309
|
+
this._orderBy.push(CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, { matchPopulateJoins: true }));
|
|
227
310
|
});
|
|
228
311
|
return this;
|
|
229
312
|
}
|
|
@@ -235,15 +318,22 @@ class QueryBuilder {
|
|
|
235
318
|
having(cond = {}, params) {
|
|
236
319
|
this.ensureNotFinalized();
|
|
237
320
|
if (core_1.Utils.isString(cond)) {
|
|
238
|
-
cond = { [`(${cond})
|
|
321
|
+
cond = { [(0, core_1.raw)(`(${cond})`, params)]: [] };
|
|
239
322
|
}
|
|
240
323
|
this._having = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond).process(this);
|
|
241
324
|
return this;
|
|
242
325
|
}
|
|
243
326
|
onConflict(fields = []) {
|
|
327
|
+
const meta = this.mainAlias.metadata;
|
|
244
328
|
this.ensureNotFinalized();
|
|
245
|
-
this._onConflict
|
|
246
|
-
this._onConflict.push({
|
|
329
|
+
this._onConflict ??= [];
|
|
330
|
+
this._onConflict.push({
|
|
331
|
+
fields: core_1.Utils.asArray(fields).flatMap(f => {
|
|
332
|
+
const key = f.toString();
|
|
333
|
+
/* istanbul ignore next */
|
|
334
|
+
return meta.properties[key]?.fieldNames ?? [key];
|
|
335
|
+
}),
|
|
336
|
+
});
|
|
247
337
|
return this;
|
|
248
338
|
}
|
|
249
339
|
ignore() {
|
|
@@ -263,6 +353,10 @@ class QueryBuilder {
|
|
|
263
353
|
this._onConflict[this._onConflict.length - 1].merge = data;
|
|
264
354
|
return this;
|
|
265
355
|
}
|
|
356
|
+
returning(fields) {
|
|
357
|
+
this._returning = core_1.Utils.asArray(fields);
|
|
358
|
+
return this;
|
|
359
|
+
}
|
|
266
360
|
/**
|
|
267
361
|
* @internal
|
|
268
362
|
*/
|
|
@@ -272,17 +366,6 @@ class QueryBuilder {
|
|
|
272
366
|
this._populateWhere = populateWhere;
|
|
273
367
|
return this;
|
|
274
368
|
}
|
|
275
|
-
/**
|
|
276
|
-
* @internal
|
|
277
|
-
*/
|
|
278
|
-
ref(field) {
|
|
279
|
-
return this.knex.ref(field);
|
|
280
|
-
}
|
|
281
|
-
raw(sql, bindings = []) {
|
|
282
|
-
const raw = this.knex.raw(sql, bindings);
|
|
283
|
-
raw.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
284
|
-
return raw;
|
|
285
|
-
}
|
|
286
369
|
limit(limit, offset = 0) {
|
|
287
370
|
this.ensureNotFinalized();
|
|
288
371
|
this._limit = limit;
|
|
@@ -325,6 +408,9 @@ class QueryBuilder {
|
|
|
325
408
|
this.flags.delete(flag);
|
|
326
409
|
return this;
|
|
327
410
|
}
|
|
411
|
+
hasFlag(flag) {
|
|
412
|
+
return this.flags.has(flag);
|
|
413
|
+
}
|
|
328
414
|
cache(config = true) {
|
|
329
415
|
this.ensureNotFinalized();
|
|
330
416
|
this._cache = config;
|
|
@@ -338,6 +424,24 @@ class QueryBuilder {
|
|
|
338
424
|
this._indexHint = sql;
|
|
339
425
|
return this;
|
|
340
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Prepend comment to the sql query using the syntax `/* ... *‍/`. Some characters are forbidden such as `/*, *‍/` and `?`.
|
|
429
|
+
*/
|
|
430
|
+
comment(comment) {
|
|
431
|
+
this.ensureNotFinalized();
|
|
432
|
+
this._comments.push(...core_1.Utils.asArray(comment));
|
|
433
|
+
return this;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Add hints to the query using comment-like syntax `/*+ ... *‍/`. MySQL and Oracle use this syntax for optimizer hints.
|
|
437
|
+
* Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints
|
|
438
|
+
* are ignored as simple comments.
|
|
439
|
+
*/
|
|
440
|
+
hintComment(comment) {
|
|
441
|
+
this.ensureNotFinalized();
|
|
442
|
+
this._hintComments.push(...core_1.Utils.asArray(comment));
|
|
443
|
+
return this;
|
|
444
|
+
}
|
|
341
445
|
from(target, aliasName) {
|
|
342
446
|
this.ensureNotFinalized();
|
|
343
447
|
if (target instanceof QueryBuilder) {
|
|
@@ -352,63 +456,108 @@ class QueryBuilder {
|
|
|
352
456
|
}
|
|
353
457
|
return this;
|
|
354
458
|
}
|
|
355
|
-
getKnexQuery() {
|
|
459
|
+
getKnexQuery(processVirtualEntity = true) {
|
|
356
460
|
this.finalize();
|
|
357
|
-
const qb = this.getQueryBase();
|
|
358
|
-
|
|
461
|
+
const qb = this.getQueryBase(processVirtualEntity);
|
|
462
|
+
const type = this.type ?? enums_1.QueryType.SELECT;
|
|
463
|
+
qb.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
464
|
+
core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(type, this._cond, qb), this._cond && !this._onConflict);
|
|
359
465
|
core_1.Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this._groupBy, 'groupBy')), this._groupBy);
|
|
360
|
-
core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(
|
|
466
|
+
core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(type, this._having, qb, undefined, 'having'), this._having);
|
|
361
467
|
core_1.Utils.runIfNotEmpty(() => {
|
|
362
|
-
const queryOrder = this.helper.getQueryOrder(
|
|
363
|
-
if (queryOrder) {
|
|
364
|
-
|
|
468
|
+
const queryOrder = this.helper.getQueryOrder(type, this._orderBy, this._populateMap);
|
|
469
|
+
if (queryOrder.length > 0) {
|
|
470
|
+
const sql = core_1.Utils.unique(queryOrder).join(', ');
|
|
471
|
+
qb.orderByRaw(sql);
|
|
365
472
|
return;
|
|
366
473
|
}
|
|
367
474
|
}, this._orderBy);
|
|
368
475
|
core_1.Utils.runIfNotEmpty(() => qb.limit(this._limit), this._limit != null);
|
|
369
476
|
core_1.Utils.runIfNotEmpty(() => qb.offset(this._offset), this._offset);
|
|
370
|
-
core_1.Utils.runIfNotEmpty(() => this.
|
|
477
|
+
core_1.Utils.runIfNotEmpty(() => this._comments.forEach(comment => qb.comment(comment)), this._comments);
|
|
478
|
+
core_1.Utils.runIfNotEmpty(() => this._hintComments.forEach(comment => qb.hintComment(comment)), this._hintComments);
|
|
479
|
+
core_1.Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(type, this._onConflict, qb), this._onConflict);
|
|
371
480
|
if (this.type === enums_1.QueryType.TRUNCATE && this.platform.usesCascadeStatement()) {
|
|
372
481
|
return this.knex.raw(qb.toSQL().toNative().sql + ' cascade');
|
|
373
482
|
}
|
|
374
483
|
if (this.lockMode) {
|
|
375
484
|
this.helper.getLockSQL(qb, this.lockMode, this.lockTables);
|
|
376
485
|
}
|
|
377
|
-
this.helper.finalize(
|
|
486
|
+
this.helper.finalize(type, qb, this.mainAlias.metadata, this._data, this._returning);
|
|
378
487
|
return qb;
|
|
379
488
|
}
|
|
380
489
|
/**
|
|
381
490
|
* Returns the query with parameters as wildcards.
|
|
382
491
|
*/
|
|
383
492
|
getQuery() {
|
|
384
|
-
return this.
|
|
493
|
+
return this.toQuery().sql;
|
|
494
|
+
}
|
|
495
|
+
#query;
|
|
496
|
+
toQuery() {
|
|
497
|
+
if (this.#query) {
|
|
498
|
+
return this.#query;
|
|
499
|
+
}
|
|
500
|
+
const sql = this.getKnexQuery().toSQL();
|
|
501
|
+
const query = sql.toNative();
|
|
502
|
+
return this.#query = { sql: query.sql, params: query.bindings ?? [], _sql: sql };
|
|
385
503
|
}
|
|
386
504
|
/**
|
|
387
505
|
* Returns the list of all parameters for this query.
|
|
388
506
|
*/
|
|
389
507
|
getParams() {
|
|
390
|
-
return this.
|
|
508
|
+
return this.toQuery().params;
|
|
391
509
|
}
|
|
392
510
|
/**
|
|
393
511
|
* Returns raw interpolated query string with all the parameters inlined.
|
|
394
512
|
*/
|
|
395
513
|
getFormattedQuery() {
|
|
396
|
-
const query = this.
|
|
514
|
+
const query = this.toQuery()._sql;
|
|
397
515
|
return this.platform.formatQuery(query.sql, query.bindings);
|
|
398
516
|
}
|
|
399
517
|
/**
|
|
400
518
|
* @internal
|
|
401
519
|
*/
|
|
402
|
-
getAliasForJoinPath(path) {
|
|
520
|
+
getAliasForJoinPath(path, options) {
|
|
403
521
|
if (!path || path === this.mainAlias.entityName) {
|
|
404
522
|
return this.mainAlias.aliasName;
|
|
405
523
|
}
|
|
406
|
-
const join =
|
|
407
|
-
if (path
|
|
524
|
+
const join = typeof path === 'string' ? this.getJoinForPath(path, options) : path;
|
|
525
|
+
if (join?.path?.endsWith('[pivot]')) {
|
|
408
526
|
return join.alias;
|
|
409
527
|
}
|
|
410
528
|
return join?.inverseAlias || join?.alias;
|
|
411
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* @internal
|
|
532
|
+
*/
|
|
533
|
+
getJoinForPath(path, options) {
|
|
534
|
+
const joins = Object.values(this._joins);
|
|
535
|
+
if (joins.length === 0) {
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
let join = joins.find(j => j.path === path);
|
|
539
|
+
if (options?.preferNoBranch) {
|
|
540
|
+
join = joins.find(j => {
|
|
541
|
+
return j.path?.replace(/\[\d+]|\[populate]/g, '') === path.replace(/\[\d+]|\[populate]/g, '');
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
if (!join && options?.ignoreBranching) {
|
|
545
|
+
join = joins.find(j => {
|
|
546
|
+
return j.path?.replace(/\[\d+]/g, '') === path.replace(/\[\d+]/g, '');
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
if (!join && options?.matchPopulateJoins && options?.ignoreBranching) {
|
|
550
|
+
join = joins.find(j => {
|
|
551
|
+
return j.path?.replace(/\[\d+]|\[populate]/g, '') === path.replace(/\[\d+]|\[populate]/g, '');
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
if (!join && options?.matchPopulateJoins) {
|
|
555
|
+
join = joins.find(j => {
|
|
556
|
+
return j.path?.replace(/\[populate]/g, '') === path.replace(/\[populate]/g, '');
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
return join;
|
|
560
|
+
}
|
|
412
561
|
/**
|
|
413
562
|
* @internal
|
|
414
563
|
*/
|
|
@@ -425,7 +574,10 @@ class QueryBuilder {
|
|
|
425
574
|
* Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
|
|
426
575
|
* Use `method` to specify what kind of result you want to get (array/single/meta).
|
|
427
576
|
*/
|
|
428
|
-
async execute(method = 'all',
|
|
577
|
+
async execute(method = 'all', options) {
|
|
578
|
+
options = typeof options === 'boolean' ? { mapResults: options } : (options ?? {});
|
|
579
|
+
options.mergeResults ??= true;
|
|
580
|
+
options.mapResults ??= true;
|
|
429
581
|
if (!this.connectionType && method !== 'run' && [enums_1.QueryType.INSERT, enums_1.QueryType.UPDATE, enums_1.QueryType.DELETE, enums_1.QueryType.TRUNCATE].includes(this.type ?? enums_1.QueryType.SELECT)) {
|
|
430
582
|
this.connectionType = 'write';
|
|
431
583
|
}
|
|
@@ -434,21 +586,33 @@ class QueryBuilder {
|
|
|
434
586
|
if (cached?.data) {
|
|
435
587
|
return cached.data;
|
|
436
588
|
}
|
|
437
|
-
const
|
|
438
|
-
const
|
|
589
|
+
const write = method === 'run' || !this.platform.getConfig().get('preferReadReplicas');
|
|
590
|
+
const type = this.connectionType || (write ? 'write' : 'read');
|
|
591
|
+
const res = await this.driver.getConnection(type).execute(query.sql, query.bindings, method, this.context, this.loggerContext);
|
|
439
592
|
const meta = this.mainAlias.metadata;
|
|
440
|
-
if (!mapResults || !meta) {
|
|
593
|
+
if (!options.mapResults || !meta) {
|
|
441
594
|
await this.em?.storeCache(this._cache, cached, res);
|
|
442
595
|
return res;
|
|
443
596
|
}
|
|
444
|
-
if (method === '
|
|
597
|
+
if (method === 'run') {
|
|
598
|
+
return res;
|
|
599
|
+
}
|
|
600
|
+
const joinedProps = this.driver.joinedProps(meta, this._populate);
|
|
601
|
+
let mapped;
|
|
602
|
+
if (Array.isArray(res)) {
|
|
445
603
|
const map = {};
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
604
|
+
mapped = res.map(r => this.driver.mapResult(r, meta, this._populate, this, map));
|
|
605
|
+
if (options.mergeResults && joinedProps.length > 0) {
|
|
606
|
+
mapped = this.driver.mergeJoinedResult(mapped, this.mainAlias.metadata, joinedProps);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
mapped = [this.driver.mapResult(res, meta, joinedProps, this)];
|
|
449
611
|
}
|
|
450
|
-
const mapped = this.driver.mapResult(res, meta, this._populate, this);
|
|
451
612
|
await this.em?.storeCache(this._cache, cached, mapped);
|
|
613
|
+
if (method === 'get') {
|
|
614
|
+
return mapped[0];
|
|
615
|
+
}
|
|
452
616
|
return mapped;
|
|
453
617
|
}
|
|
454
618
|
/**
|
|
@@ -462,11 +626,29 @@ class QueryBuilder {
|
|
|
462
626
|
*/
|
|
463
627
|
async getResultList() {
|
|
464
628
|
await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
629
|
+
const res = await this.execute('all', true);
|
|
630
|
+
const entities = [];
|
|
631
|
+
function propagatePopulateHint(entity, hint) {
|
|
632
|
+
(0, core_1.helper)(entity).__serializationContext.populate ??= hint;
|
|
633
|
+
hint.forEach(hint => {
|
|
634
|
+
const [propName] = hint.field.split(':', 2);
|
|
635
|
+
const value = entity[propName];
|
|
636
|
+
if (core_1.Utils.isEntity(value, true)) {
|
|
637
|
+
(0, core_1.helper)(value).populated();
|
|
638
|
+
propagatePopulateHint(value, hint.children ?? []);
|
|
639
|
+
}
|
|
640
|
+
else if (core_1.Utils.isCollection(value)) {
|
|
641
|
+
value.populated();
|
|
642
|
+
value.getItems(false).forEach(item => propagatePopulateHint(item, hint.children ?? []));
|
|
643
|
+
}
|
|
644
|
+
});
|
|
468
645
|
}
|
|
469
|
-
|
|
646
|
+
for (const r of res) {
|
|
647
|
+
const entity = this.em.map(this.mainAlias.entityName, r, { schema: this._schema });
|
|
648
|
+
propagatePopulateHint(entity, this._populate);
|
|
649
|
+
entities.push(entity);
|
|
650
|
+
}
|
|
651
|
+
return entities;
|
|
470
652
|
}
|
|
471
653
|
/**
|
|
472
654
|
* Executes the query, returning the first result or null
|
|
@@ -484,7 +666,7 @@ class QueryBuilder {
|
|
|
484
666
|
res = await this.execute('get', false);
|
|
485
667
|
}
|
|
486
668
|
else {
|
|
487
|
-
const qb = this.clone();
|
|
669
|
+
const qb = this.type === undefined ? this : this.clone();
|
|
488
670
|
qb.count(field, distinct ?? qb.hasToManyJoins()).limit(undefined).offset(undefined).orderBy([]);
|
|
489
671
|
res = await qb.execute('get', false);
|
|
490
672
|
}
|
|
@@ -503,7 +685,11 @@ class QueryBuilder {
|
|
|
503
685
|
* Provides promise-like interface so we can await the QB instance.
|
|
504
686
|
*/
|
|
505
687
|
then(onfulfilled, onrejected) {
|
|
506
|
-
|
|
688
|
+
let type = this.type ?? enums_1.QueryType.SELECT;
|
|
689
|
+
if (this.flags.has(core_1.QueryFlag.UPDATE_SUB_QUERY) || this.flags.has(core_1.QueryFlag.DELETE_SUB_QUERY)) {
|
|
690
|
+
type = enums_1.QueryType.UPDATE;
|
|
691
|
+
}
|
|
692
|
+
switch (type) {
|
|
507
693
|
case enums_1.QueryType.INSERT:
|
|
508
694
|
case enums_1.QueryType.UPDATE:
|
|
509
695
|
case enums_1.QueryType.DELETE:
|
|
@@ -526,15 +712,22 @@ class QueryBuilder {
|
|
|
526
712
|
/* istanbul ignore next */
|
|
527
713
|
alias = meta?.properties[f]?.fieldNames[0] ?? alias;
|
|
528
714
|
}
|
|
529
|
-
|
|
715
|
+
const ret = qb.as(alias);
|
|
716
|
+
// tag the instance, so it is possible to detect it easily
|
|
717
|
+
Object.defineProperty(ret, '__as', { enumerable: false, value: alias });
|
|
718
|
+
return ret;
|
|
530
719
|
}
|
|
531
|
-
clone() {
|
|
720
|
+
clone(reset) {
|
|
532
721
|
const qb = new QueryBuilder(this.mainAlias.entityName, this.metadata, this.driver, this.context, this.mainAlias.aliasName, this.connectionType, this.em);
|
|
722
|
+
if (reset) {
|
|
723
|
+
return qb;
|
|
724
|
+
}
|
|
533
725
|
Object.assign(qb, this);
|
|
534
726
|
// clone array/object properties
|
|
535
727
|
const properties = [
|
|
536
|
-
'flags', '_populate', '_populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
|
|
537
|
-
'_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables',
|
|
728
|
+
'flags', '_populate', '_populateWhere', '__populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
|
|
729
|
+
'_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables', '_groupBy', '_having', '_returning',
|
|
730
|
+
'_comments', '_hintComments',
|
|
538
731
|
];
|
|
539
732
|
properties.forEach(prop => qb[prop] = core_1.Utils.copy(this[prop]));
|
|
540
733
|
/* istanbul ignore else */
|
|
@@ -545,9 +738,9 @@ class QueryBuilder {
|
|
|
545
738
|
qb.finalized = false;
|
|
546
739
|
return qb;
|
|
547
740
|
}
|
|
548
|
-
getKnex() {
|
|
741
|
+
getKnex(processVirtualEntity = true) {
|
|
549
742
|
const qb = this.knex.queryBuilder();
|
|
550
|
-
const { subQuery, aliasName, entityName } = this.mainAlias;
|
|
743
|
+
const { subQuery, aliasName, entityName, metadata } = this.mainAlias;
|
|
551
744
|
const ref = subQuery ? subQuery : this.knex.ref(this.helper.getTableName(entityName));
|
|
552
745
|
if (this.finalized && (this._explicitAlias || this.helper.isTableNameAliasRequired(this.type))) {
|
|
553
746
|
ref.as(aliasName);
|
|
@@ -556,20 +749,72 @@ class QueryBuilder {
|
|
|
556
749
|
if (schema) {
|
|
557
750
|
ref.withSchema(schema);
|
|
558
751
|
}
|
|
559
|
-
|
|
752
|
+
if (metadata?.virtual && processVirtualEntity) {
|
|
753
|
+
qb.fromRaw(this.fromVirtual(metadata));
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
qb.from(ref);
|
|
757
|
+
}
|
|
560
758
|
if (this.context) {
|
|
561
759
|
qb.transacting(this.context);
|
|
562
760
|
}
|
|
563
761
|
return qb;
|
|
564
762
|
}
|
|
565
|
-
|
|
763
|
+
fromVirtual(meta) {
|
|
764
|
+
if (typeof meta.expression === 'string') {
|
|
765
|
+
return `(${meta.expression}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
766
|
+
}
|
|
767
|
+
const res = meta.expression(this.em, this._cond, {});
|
|
768
|
+
if (typeof res === 'string') {
|
|
769
|
+
return `(${res}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
770
|
+
}
|
|
771
|
+
if (res instanceof QueryBuilder) {
|
|
772
|
+
return `(${res.getFormattedQuery()}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
773
|
+
}
|
|
774
|
+
if (core_1.Utils.isObject(res)) {
|
|
775
|
+
const { sql, bindings } = res.toSQL();
|
|
776
|
+
const query = this.platform.formatQuery(sql, bindings);
|
|
777
|
+
return `(${query}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
778
|
+
}
|
|
779
|
+
/* istanbul ignore next */
|
|
780
|
+
return res;
|
|
781
|
+
}
|
|
782
|
+
joinReference(field, alias, cond, type, path, schema, subquery) {
|
|
566
783
|
this.ensureNotFinalized();
|
|
784
|
+
if (typeof field === 'object') {
|
|
785
|
+
const prop = {
|
|
786
|
+
name: '__subquery__',
|
|
787
|
+
kind: core_1.ReferenceKind.MANY_TO_ONE,
|
|
788
|
+
};
|
|
789
|
+
if (field instanceof QueryBuilder) {
|
|
790
|
+
prop.type = field.mainAlias.entityName;
|
|
791
|
+
prop.targetMeta = field.mainAlias.metadata;
|
|
792
|
+
field = field.getKnexQuery();
|
|
793
|
+
}
|
|
794
|
+
this._joins[`${this.alias}.${prop.name}#${alias}`] = {
|
|
795
|
+
prop,
|
|
796
|
+
alias,
|
|
797
|
+
type,
|
|
798
|
+
cond,
|
|
799
|
+
schema,
|
|
800
|
+
subquery: field.toString(),
|
|
801
|
+
ownerAlias: this.alias,
|
|
802
|
+
};
|
|
803
|
+
return prop;
|
|
804
|
+
}
|
|
805
|
+
if (!subquery && type.includes('lateral')) {
|
|
806
|
+
throw new Error(`Lateral join can be used only with a sub-query.`);
|
|
807
|
+
}
|
|
567
808
|
const [fromAlias, fromField] = this.helper.splitField(field);
|
|
568
|
-
const
|
|
809
|
+
const q = (str) => `'${str}'`;
|
|
810
|
+
if (!this._aliases[fromAlias]) {
|
|
811
|
+
throw new Error(`Trying to join ${q(fromField)} with alias ${q(fromAlias)}, but ${q(fromAlias)} is not a known alias. Available aliases are: ${Object.keys(this._aliases).map(q).join(', ')}.`);
|
|
812
|
+
}
|
|
813
|
+
const entityName = this._aliases[fromAlias].entityName;
|
|
569
814
|
const meta = this.metadata.get(entityName);
|
|
570
815
|
const prop = meta.properties[fromField];
|
|
571
816
|
if (!prop) {
|
|
572
|
-
throw new Error(`Trying to join ${field}, but ${fromField} is not a defined relation on ${meta.className}
|
|
817
|
+
throw new Error(`Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`);
|
|
573
818
|
}
|
|
574
819
|
this.createAlias(prop.type, alias);
|
|
575
820
|
cond = core_1.QueryHelper.processWhere({
|
|
@@ -583,24 +828,24 @@ class QueryBuilder {
|
|
|
583
828
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
584
829
|
path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
|
|
585
830
|
if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY) {
|
|
586
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
|
|
831
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
587
832
|
}
|
|
588
833
|
else if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY) {
|
|
589
834
|
let pivotAlias = alias;
|
|
590
|
-
if (type !==
|
|
835
|
+
if (type !== enums_1.JoinType.pivotJoin) {
|
|
591
836
|
const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]');
|
|
592
837
|
pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
|
|
593
838
|
aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
|
|
594
839
|
}
|
|
595
|
-
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path);
|
|
840
|
+
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
|
|
596
841
|
Object.assign(this._joins, joins);
|
|
597
842
|
this.createAlias(prop.pivotEntity, pivotAlias);
|
|
598
843
|
}
|
|
599
844
|
else if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE) {
|
|
600
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
|
|
845
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
601
846
|
}
|
|
602
847
|
else { // MANY_TO_ONE
|
|
603
|
-
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond);
|
|
848
|
+
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
|
|
604
849
|
}
|
|
605
850
|
if (!this._joins[aliasedName].path && path) {
|
|
606
851
|
this._joins[aliasedName].path = path;
|
|
@@ -609,7 +854,19 @@ class QueryBuilder {
|
|
|
609
854
|
}
|
|
610
855
|
prepareFields(fields, type = 'where') {
|
|
611
856
|
const ret = [];
|
|
857
|
+
const getFieldName = (name) => {
|
|
858
|
+
if (type === 'groupBy') {
|
|
859
|
+
return this.helper.mapper(name, this.type, undefined, null);
|
|
860
|
+
}
|
|
861
|
+
return this.helper.mapper(name, this.type);
|
|
862
|
+
};
|
|
612
863
|
fields.forEach(field => {
|
|
864
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(field);
|
|
865
|
+
if (rawField) {
|
|
866
|
+
const sql = this.platform.formatQuery(rawField.sql, rawField.params);
|
|
867
|
+
ret.push(this.knex.raw(sql));
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
613
870
|
if (!core_1.Utils.isString(field)) {
|
|
614
871
|
ret.push(field);
|
|
615
872
|
return;
|
|
@@ -626,19 +883,20 @@ class QueryBuilder {
|
|
|
626
883
|
return;
|
|
627
884
|
}
|
|
628
885
|
if (prop?.embedded) {
|
|
629
|
-
const
|
|
630
|
-
|
|
886
|
+
const name = prop.embeddedPath?.join('.') ?? prop.fieldNames[0];
|
|
887
|
+
const aliased = this._aliases[a] ? `${a}.${name}` : name;
|
|
888
|
+
ret.push(getFieldName(aliased));
|
|
631
889
|
return;
|
|
632
890
|
}
|
|
633
891
|
if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
634
892
|
if (prop.object) {
|
|
635
|
-
ret.push(
|
|
893
|
+
ret.push(getFieldName(prop.fieldNames[0]));
|
|
636
894
|
}
|
|
637
895
|
else {
|
|
638
896
|
const nest = (prop) => {
|
|
639
897
|
for (const childProp of Object.values(prop.embeddedProps)) {
|
|
640
|
-
if (childProp.fieldNames) {
|
|
641
|
-
ret.push(
|
|
898
|
+
if (childProp.fieldNames && (childProp.kind !== core_1.ReferenceKind.EMBEDDED || childProp.object) && childProp.persist !== false) {
|
|
899
|
+
ret.push(getFieldName(childProp.fieldNames[0]));
|
|
642
900
|
}
|
|
643
901
|
else {
|
|
644
902
|
nest(childProp);
|
|
@@ -649,11 +907,15 @@ class QueryBuilder {
|
|
|
649
907
|
}
|
|
650
908
|
return;
|
|
651
909
|
}
|
|
652
|
-
|
|
910
|
+
if (prop && prop.fieldNames.length > 1) {
|
|
911
|
+
ret.push(...prop.fieldNames.map(f => getFieldName(f)));
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
ret.push(getFieldName(field));
|
|
653
915
|
});
|
|
654
916
|
const meta = this.mainAlias.metadata;
|
|
655
917
|
/* istanbul ignore next */
|
|
656
|
-
const requiresSQLConversion = meta?.props.filter(p => p.
|
|
918
|
+
const requiresSQLConversion = meta?.props.filter(p => p.hasConvertToJSValueSQL && p.persist !== false) ?? [];
|
|
657
919
|
if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0) {
|
|
658
920
|
requiresSQLConversion.forEach(p => ret.push(this.helper.mapper(p.name, this.type)));
|
|
659
921
|
}
|
|
@@ -662,11 +924,6 @@ class QueryBuilder {
|
|
|
662
924
|
const cols = this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[f]);
|
|
663
925
|
ret.push(...cols);
|
|
664
926
|
}
|
|
665
|
-
if (this._joins[f].prop.kind !== core_1.ReferenceKind.ONE_TO_ONE && this._joins[f].inverseJoinColumns) {
|
|
666
|
-
this._joins[f].inverseJoinColumns.forEach(inverseJoinColumn => {
|
|
667
|
-
core_1.Utils.renameKey(this._cond, inverseJoinColumn, `${this._joins[f].alias}.${inverseJoinColumn}`);
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
927
|
});
|
|
671
928
|
return ret;
|
|
672
929
|
}
|
|
@@ -681,19 +938,20 @@ class QueryBuilder {
|
|
|
681
938
|
}
|
|
682
939
|
if (data) {
|
|
683
940
|
if (core_1.Utils.isEntity(data)) {
|
|
684
|
-
// data = helper(data).toJSON();
|
|
685
941
|
data = this.em?.getComparator().prepareEntity(data) ?? (0, core_1.serialize)(data);
|
|
686
942
|
}
|
|
687
|
-
this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES));
|
|
943
|
+
this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES), false);
|
|
688
944
|
}
|
|
689
945
|
if (cond) {
|
|
690
946
|
this.where(cond);
|
|
691
947
|
}
|
|
692
948
|
return this;
|
|
693
949
|
}
|
|
694
|
-
getQueryBase() {
|
|
695
|
-
const qb = this.getKnex();
|
|
950
|
+
getQueryBase(processVirtualEntity) {
|
|
951
|
+
const qb = this.getKnex(processVirtualEntity);
|
|
696
952
|
const schema = this.getSchema(this.mainAlias);
|
|
953
|
+
// Joined tables doesn't need to belong to the same schema as the main table
|
|
954
|
+
const joinSchema = this._schema ?? this.em?.schema ?? schema;
|
|
697
955
|
if (schema) {
|
|
698
956
|
qb.withSchema(schema);
|
|
699
957
|
}
|
|
@@ -712,12 +970,12 @@ class QueryBuilder {
|
|
|
712
970
|
else if (this.flags.has(core_1.QueryFlag.DISTINCT)) {
|
|
713
971
|
qb.distinct();
|
|
714
972
|
}
|
|
715
|
-
this.helper.processJoins(qb, this._joins,
|
|
973
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
716
974
|
break;
|
|
717
975
|
case enums_1.QueryType.COUNT: {
|
|
718
976
|
const m = this.flags.has(core_1.QueryFlag.DISTINCT) ? 'countDistinct' : 'count';
|
|
719
977
|
qb[m]({ count: this._fields.map(f => this.helper.mapper(f, this.type)) });
|
|
720
|
-
this.helper.processJoins(qb, this._joins,
|
|
978
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
721
979
|
break;
|
|
722
980
|
}
|
|
723
981
|
case enums_1.QueryType.INSERT:
|
|
@@ -736,6 +994,24 @@ class QueryBuilder {
|
|
|
736
994
|
}
|
|
737
995
|
return qb;
|
|
738
996
|
}
|
|
997
|
+
applyDiscriminatorCondition() {
|
|
998
|
+
const meta = this.mainAlias.metadata;
|
|
999
|
+
if (!meta?.discriminatorValue) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
|
|
1003
|
+
const children = [];
|
|
1004
|
+
const lookUpChildren = (ret, type) => {
|
|
1005
|
+
const children = types.filter(meta2 => meta2.extends === type);
|
|
1006
|
+
children.forEach(m => lookUpChildren(ret, m.className));
|
|
1007
|
+
ret.push(...children.filter(c => c.discriminatorValue));
|
|
1008
|
+
return children;
|
|
1009
|
+
};
|
|
1010
|
+
lookUpChildren(children, meta.className);
|
|
1011
|
+
this.andWhere({
|
|
1012
|
+
[meta.root.discriminatorColumn]: children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue,
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
739
1015
|
finalize() {
|
|
740
1016
|
if (this.finalized) {
|
|
741
1017
|
return;
|
|
@@ -744,11 +1020,12 @@ class QueryBuilder {
|
|
|
744
1020
|
this.select('*');
|
|
745
1021
|
}
|
|
746
1022
|
const meta = this.mainAlias.metadata;
|
|
1023
|
+
this.applyDiscriminatorCondition();
|
|
747
1024
|
if (meta && this.flags.has(core_1.QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
|
|
748
1025
|
const relationsToPopulate = this._populate.map(({ field }) => field);
|
|
749
1026
|
meta.relations
|
|
750
|
-
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
|
|
751
|
-
.map(prop => ({ field: prop.name }))
|
|
1027
|
+
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name) && !relationsToPopulate.includes(`${prop.name}:ref`))
|
|
1028
|
+
.map(prop => ({ field: `${prop.name}:ref` }))
|
|
752
1029
|
.forEach(item => this._populate.push(item));
|
|
753
1030
|
}
|
|
754
1031
|
this._populate.forEach(({ field }) => {
|
|
@@ -759,14 +1036,11 @@ class QueryBuilder {
|
|
|
759
1036
|
this._populateMap[join] = this._joins[join].alias;
|
|
760
1037
|
return;
|
|
761
1038
|
}
|
|
762
|
-
if (this.
|
|
763
|
-
this.autoJoinPivotTable(field);
|
|
764
|
-
}
|
|
765
|
-
else if (meta && this.helper.isOneToOneInverse(fromField)) {
|
|
1039
|
+
if (meta && this.helper.isOneToOneInverse(fromField)) {
|
|
766
1040
|
const prop = meta.properties[fromField];
|
|
767
1041
|
const alias = this.getNextAlias(prop.pivotEntity ?? prop.type);
|
|
768
1042
|
const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
769
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias,
|
|
1043
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, enums_1.JoinType.leftJoin);
|
|
770
1044
|
this._populateMap[aliasedName] = this._joins[aliasedName].alias;
|
|
771
1045
|
}
|
|
772
1046
|
});
|
|
@@ -778,14 +1052,20 @@ class QueryBuilder {
|
|
|
778
1052
|
const aliased = this.knex.ref(prop.fieldNames[0]).toString();
|
|
779
1053
|
return `${prop.formula(alias)} as ${aliased}`;
|
|
780
1054
|
})
|
|
781
|
-
.filter(field => !this._fields.
|
|
782
|
-
|
|
1055
|
+
.filter(field => !this._fields.some(f => {
|
|
1056
|
+
if (f instanceof core_1.RawQueryFragment) {
|
|
1057
|
+
return f.sql === field && f.params.length === 0;
|
|
1058
|
+
}
|
|
1059
|
+
return f === field;
|
|
1060
|
+
}))
|
|
1061
|
+
.forEach(field => this._fields.push((0, core_1.raw)(field)));
|
|
783
1062
|
}
|
|
1063
|
+
this.processPopulateWhere();
|
|
784
1064
|
core_1.QueryHelper.processObjectParams(this._data);
|
|
785
1065
|
core_1.QueryHelper.processObjectParams(this._cond);
|
|
786
1066
|
core_1.QueryHelper.processObjectParams(this._having);
|
|
787
|
-
// automatically enable paginate flag when we detect to-many joins
|
|
788
|
-
if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this.hasToManyJoins()) {
|
|
1067
|
+
// automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
|
|
1068
|
+
if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this._groupBy.length === 0 && this.hasToManyJoins()) {
|
|
789
1069
|
this.flags.add(core_1.QueryFlag.PAGINATE);
|
|
790
1070
|
}
|
|
791
1071
|
if (meta && this.flags.has(core_1.QueryFlag.PAGINATE) && (this._limit > 0 || this._offset > 0)) {
|
|
@@ -796,6 +1076,43 @@ class QueryBuilder {
|
|
|
796
1076
|
}
|
|
797
1077
|
this.finalized = true;
|
|
798
1078
|
}
|
|
1079
|
+
processPopulateWhere() {
|
|
1080
|
+
if (this._populateWhere == null || this._populateWhere === core_1.PopulateHint.ALL) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
const joins = Object.values(this._joins);
|
|
1084
|
+
joins.forEach(join => {
|
|
1085
|
+
join.cond_ = join.cond;
|
|
1086
|
+
join.cond = {};
|
|
1087
|
+
});
|
|
1088
|
+
const replaceOnConditions = (cond, op) => {
|
|
1089
|
+
Object.keys(cond).forEach(k => {
|
|
1090
|
+
if (core_1.Utils.isOperator(k)) {
|
|
1091
|
+
if (Array.isArray(cond[k])) {
|
|
1092
|
+
return cond[k].forEach((c) => replaceOnConditions(c, k));
|
|
1093
|
+
}
|
|
1094
|
+
/* istanbul ignore next */
|
|
1095
|
+
return replaceOnConditions(cond[k], k);
|
|
1096
|
+
}
|
|
1097
|
+
const [alias] = this.helper.splitField(k);
|
|
1098
|
+
const join = joins.find(j => j.alias === alias);
|
|
1099
|
+
if (join) {
|
|
1100
|
+
if (join.cond[k]) {
|
|
1101
|
+
join.cond = { [op ?? '$and']: [join.cond, { [k]: cond[k] }] };
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
join.cond = { ...join.cond, [k]: cond[k] };
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
};
|
|
1109
|
+
if (typeof this._populateWhere === 'object') {
|
|
1110
|
+
const cond = CriteriaNodeFactory_1.CriteriaNodeFactory
|
|
1111
|
+
.createNode(this.metadata, this.mainAlias.entityName, this._populateWhere)
|
|
1112
|
+
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true });
|
|
1113
|
+
replaceOnConditions(cond);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
799
1116
|
hasToManyJoins() {
|
|
800
1117
|
return Object.values(this._joins).some(join => {
|
|
801
1118
|
return [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
@@ -804,9 +1121,12 @@ class QueryBuilder {
|
|
|
804
1121
|
wrapPaginateSubQuery(meta) {
|
|
805
1122
|
const pks = this.prepareFields(meta.primaryKeys, 'sub-query');
|
|
806
1123
|
const subQuery = this.clone().select(pks).groupBy(pks).limit(this._limit);
|
|
1124
|
+
// revert the on conditions added via populateWhere, we want to apply those only once
|
|
1125
|
+
Object.values(subQuery._joins).forEach(join => join.cond = join.cond_ ?? {});
|
|
807
1126
|
if (this._offset) {
|
|
808
1127
|
subQuery.offset(this._offset);
|
|
809
1128
|
}
|
|
1129
|
+
const addToSelect = [];
|
|
810
1130
|
if (this._orderBy.length > 0) {
|
|
811
1131
|
const orderBy = [];
|
|
812
1132
|
for (const orderMap of this._orderBy) {
|
|
@@ -814,28 +1134,38 @@ class QueryBuilder {
|
|
|
814
1134
|
const [a, f] = this.helper.splitField(field);
|
|
815
1135
|
const prop = this.helper.getProperty(f, a);
|
|
816
1136
|
const type = this.platform.castColumn(prop);
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1137
|
+
const fieldName = this.helper.mapper(field, this.type, undefined, null);
|
|
1138
|
+
if (!prop?.persist && !prop?.formula) {
|
|
1139
|
+
addToSelect.push(fieldName);
|
|
1140
|
+
}
|
|
1141
|
+
orderBy.push({ [(0, core_1.raw)(`min(${this.knex.ref(fieldName)}${type})`)]: direction });
|
|
820
1142
|
}
|
|
821
1143
|
}
|
|
822
1144
|
subQuery.orderBy(orderBy);
|
|
823
1145
|
}
|
|
824
1146
|
subQuery.finalized = true;
|
|
825
1147
|
const knexQuery = subQuery.as(this.mainAlias.aliasName).clearSelect().select(pks);
|
|
1148
|
+
if (addToSelect.length > 0) {
|
|
1149
|
+
addToSelect.forEach(prop => {
|
|
1150
|
+
const field = this._fields.find(field => {
|
|
1151
|
+
if (typeof field === 'object' && field && '__as' in field) {
|
|
1152
|
+
return field.__as === prop;
|
|
1153
|
+
}
|
|
1154
|
+
// not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
|
|
1155
|
+
return field.toString().includes(prop);
|
|
1156
|
+
});
|
|
1157
|
+
if (field) {
|
|
1158
|
+
knexQuery.select(field);
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
826
1162
|
// multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
|
|
827
1163
|
// https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
|
|
828
1164
|
const subSubQuery = this.getKnex().select(pks).from(knexQuery);
|
|
1165
|
+
subSubQuery.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
829
1166
|
this._limit = undefined;
|
|
830
1167
|
this._offset = undefined;
|
|
831
|
-
const cond = this._cond;
|
|
832
1168
|
this.select(this._fields).where({ [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery } });
|
|
833
|
-
if (this._populateWhere === core_1.PopulateHint.INFER) {
|
|
834
|
-
this.andWhere(cond);
|
|
835
|
-
}
|
|
836
|
-
else if (typeof this._populateWhere === 'object') {
|
|
837
|
-
this.andWhere(this._populateWhere);
|
|
838
|
-
}
|
|
839
1169
|
}
|
|
840
1170
|
wrapModifySubQuery(meta) {
|
|
841
1171
|
const subQuery = this.clone();
|
|
@@ -849,21 +1179,10 @@ class QueryBuilder {
|
|
|
849
1179
|
[core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery },
|
|
850
1180
|
});
|
|
851
1181
|
}
|
|
852
|
-
autoJoinPivotTable(field) {
|
|
853
|
-
const pivotMeta = this.metadata.find(field);
|
|
854
|
-
const owner = pivotMeta.relations[0];
|
|
855
|
-
const inverse = pivotMeta.relations[1];
|
|
856
|
-
const prop = this._cond[pivotMeta.name + '.' + owner.name] || this._orderBy[pivotMeta.name + '.' + owner.name] ? inverse : owner;
|
|
857
|
-
const pivotAlias = this.getNextAlias(pivotMeta.name);
|
|
858
|
-
this._joins[field] = this.helper.joinPivotTable(field, prop, this.mainAlias.aliasName, pivotAlias, 'leftJoin');
|
|
859
|
-
core_1.Utils.renameKey(this._cond, `${field}.${owner.name}`, core_1.Utils.getPrimaryKeyHash(owner.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
|
|
860
|
-
core_1.Utils.renameKey(this._cond, `${field}.${inverse.name}`, core_1.Utils.getPrimaryKeyHash(inverse.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
|
|
861
|
-
this._populateMap[field] = this._joins[field].alias;
|
|
862
|
-
}
|
|
863
1182
|
getSchema(alias) {
|
|
864
1183
|
const { metadata } = alias;
|
|
865
1184
|
const metaSchema = metadata?.schema && metadata.schema !== '*' ? metadata.schema : undefined;
|
|
866
|
-
return this._schema ?? metaSchema ?? this.em?.config.get('schema');
|
|
1185
|
+
return this._schema ?? metaSchema ?? this.em?.schema ?? this.em?.config.get('schema');
|
|
867
1186
|
}
|
|
868
1187
|
createAlias(entityName, aliasName, subQuery) {
|
|
869
1188
|
const metadata = this.metadata.find(entityName);
|
|
@@ -901,6 +1220,7 @@ class QueryBuilder {
|
|
|
901
1220
|
}
|
|
902
1221
|
}
|
|
903
1222
|
/* istanbul ignore next */
|
|
1223
|
+
/** @ignore */
|
|
904
1224
|
[util_1.inspect.custom](depth) {
|
|
905
1225
|
const object = { ...this };
|
|
906
1226
|
const hidden = ['metadata', 'driver', 'context', 'platform', 'knex', 'type'];
|
|
@@ -911,6 +1231,9 @@ class QueryBuilder {
|
|
|
911
1231
|
if (this._data) {
|
|
912
1232
|
object.data = this._data;
|
|
913
1233
|
}
|
|
1234
|
+
if (this._schema) {
|
|
1235
|
+
object.schema = this._schema;
|
|
1236
|
+
}
|
|
914
1237
|
if (!core_1.Utils.isEmpty(this._cond)) {
|
|
915
1238
|
object.where = this._cond;
|
|
916
1239
|
}
|