@mikro-orm/knex 6.0.0-dev.31 → 6.0.0-dev.311
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 +47 -17
- package/AbstractSqlDriver.d.ts +30 -24
- package/AbstractSqlDriver.js +604 -266
- 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 +58 -26
- package/query/QueryBuilder.js +445 -142
- 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 +3 -4
- package/schema/SqlSchemaGenerator.js +66 -23
- 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);
|
|
153
211
|
}
|
|
154
|
-
|
|
212
|
+
innerJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
|
|
213
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.innerJoinLateral, undefined, fields, schema);
|
|
214
|
+
}
|
|
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) {
|
|
@@ -355,59 +459,105 @@ class QueryBuilder {
|
|
|
355
459
|
getKnexQuery() {
|
|
356
460
|
this.finalize();
|
|
357
461
|
const qb = this.getQueryBase();
|
|
358
|
-
|
|
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);
|
|
472
|
+
return;
|
|
365
473
|
}
|
|
366
474
|
}, this._orderBy);
|
|
367
475
|
core_1.Utils.runIfNotEmpty(() => qb.limit(this._limit), this._limit != null);
|
|
368
476
|
core_1.Utils.runIfNotEmpty(() => qb.offset(this._offset), this._offset);
|
|
369
|
-
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);
|
|
370
480
|
if (this.type === enums_1.QueryType.TRUNCATE && this.platform.usesCascadeStatement()) {
|
|
371
481
|
return this.knex.raw(qb.toSQL().toNative().sql + ' cascade');
|
|
372
482
|
}
|
|
373
483
|
if (this.lockMode) {
|
|
374
484
|
this.helper.getLockSQL(qb, this.lockMode, this.lockTables);
|
|
375
485
|
}
|
|
376
|
-
this.helper.finalize(
|
|
486
|
+
this.helper.finalize(type, qb, this.mainAlias.metadata, this._data, this._returning);
|
|
377
487
|
return qb;
|
|
378
488
|
}
|
|
379
489
|
/**
|
|
380
490
|
* Returns the query with parameters as wildcards.
|
|
381
491
|
*/
|
|
382
492
|
getQuery() {
|
|
383
|
-
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 };
|
|
384
503
|
}
|
|
385
504
|
/**
|
|
386
505
|
* Returns the list of all parameters for this query.
|
|
387
506
|
*/
|
|
388
507
|
getParams() {
|
|
389
|
-
return this.
|
|
508
|
+
return this.toQuery().params;
|
|
390
509
|
}
|
|
391
510
|
/**
|
|
392
511
|
* Returns raw interpolated query string with all the parameters inlined.
|
|
393
512
|
*/
|
|
394
513
|
getFormattedQuery() {
|
|
395
|
-
const query = this.
|
|
514
|
+
const query = this.toQuery()._sql;
|
|
396
515
|
return this.platform.formatQuery(query.sql, query.bindings);
|
|
397
516
|
}
|
|
398
517
|
/**
|
|
399
518
|
* @internal
|
|
400
519
|
*/
|
|
401
|
-
getAliasForJoinPath(path) {
|
|
520
|
+
getAliasForJoinPath(path, options) {
|
|
402
521
|
if (!path || path === this.mainAlias.entityName) {
|
|
403
522
|
return this.mainAlias.aliasName;
|
|
404
523
|
}
|
|
405
|
-
const join =
|
|
406
|
-
if (path
|
|
524
|
+
const join = typeof path === 'string' ? this.getJoinForPath(path, options) : path;
|
|
525
|
+
if (join?.path?.endsWith('[pivot]')) {
|
|
407
526
|
return join.alias;
|
|
408
527
|
}
|
|
409
528
|
return join?.inverseAlias || join?.alias;
|
|
410
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
|
+
}
|
|
411
561
|
/**
|
|
412
562
|
* @internal
|
|
413
563
|
*/
|
|
@@ -424,7 +574,10 @@ class QueryBuilder {
|
|
|
424
574
|
* Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
|
|
425
575
|
* Use `method` to specify what kind of result you want to get (array/single/meta).
|
|
426
576
|
*/
|
|
427
|
-
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;
|
|
428
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)) {
|
|
429
582
|
this.connectionType = 'write';
|
|
430
583
|
}
|
|
@@ -433,21 +586,33 @@ class QueryBuilder {
|
|
|
433
586
|
if (cached?.data) {
|
|
434
587
|
return cached.data;
|
|
435
588
|
}
|
|
436
|
-
const
|
|
437
|
-
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);
|
|
438
592
|
const meta = this.mainAlias.metadata;
|
|
439
|
-
if (!mapResults || !meta) {
|
|
593
|
+
if (!options.mapResults || !meta) {
|
|
440
594
|
await this.em?.storeCache(this._cache, cached, res);
|
|
441
595
|
return res;
|
|
442
596
|
}
|
|
443
|
-
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)) {
|
|
444
603
|
const map = {};
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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)];
|
|
448
611
|
}
|
|
449
|
-
const mapped = this.driver.mapResult(res, meta, this._populate, this);
|
|
450
612
|
await this.em?.storeCache(this._cache, cached, mapped);
|
|
613
|
+
if (method === 'get') {
|
|
614
|
+
return mapped[0];
|
|
615
|
+
}
|
|
451
616
|
return mapped;
|
|
452
617
|
}
|
|
453
618
|
/**
|
|
@@ -461,11 +626,29 @@ class QueryBuilder {
|
|
|
461
626
|
*/
|
|
462
627
|
async getResultList() {
|
|
463
628
|
await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
+
});
|
|
467
645
|
}
|
|
468
|
-
|
|
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;
|
|
469
652
|
}
|
|
470
653
|
/**
|
|
471
654
|
* Executes the query, returning the first result or null
|
|
@@ -483,7 +666,7 @@ class QueryBuilder {
|
|
|
483
666
|
res = await this.execute('get', false);
|
|
484
667
|
}
|
|
485
668
|
else {
|
|
486
|
-
const qb = this.clone();
|
|
669
|
+
const qb = this.type === undefined ? this : this.clone();
|
|
487
670
|
qb.count(field, distinct ?? qb.hasToManyJoins()).limit(undefined).offset(undefined).orderBy([]);
|
|
488
671
|
res = await qb.execute('get', false);
|
|
489
672
|
}
|
|
@@ -502,7 +685,11 @@ class QueryBuilder {
|
|
|
502
685
|
* Provides promise-like interface so we can await the QB instance.
|
|
503
686
|
*/
|
|
504
687
|
then(onfulfilled, onrejected) {
|
|
505
|
-
|
|
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) {
|
|
506
693
|
case enums_1.QueryType.INSERT:
|
|
507
694
|
case enums_1.QueryType.UPDATE:
|
|
508
695
|
case enums_1.QueryType.DELETE:
|
|
@@ -525,15 +712,22 @@ class QueryBuilder {
|
|
|
525
712
|
/* istanbul ignore next */
|
|
526
713
|
alias = meta?.properties[f]?.fieldNames[0] ?? alias;
|
|
527
714
|
}
|
|
528
|
-
|
|
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;
|
|
529
719
|
}
|
|
530
|
-
clone() {
|
|
720
|
+
clone(reset) {
|
|
531
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
|
+
}
|
|
532
725
|
Object.assign(qb, this);
|
|
533
726
|
// clone array/object properties
|
|
534
727
|
const properties = [
|
|
535
|
-
'flags', '_populate', '_populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
|
|
536
|
-
'_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',
|
|
537
731
|
];
|
|
538
732
|
properties.forEach(prop => qb[prop] = core_1.Utils.copy(this[prop]));
|
|
539
733
|
/* istanbul ignore else */
|
|
@@ -561,14 +755,42 @@ class QueryBuilder {
|
|
|
561
755
|
}
|
|
562
756
|
return qb;
|
|
563
757
|
}
|
|
564
|
-
joinReference(field, alias, cond, type, path) {
|
|
758
|
+
joinReference(field, alias, cond, type, path, schema, subquery) {
|
|
565
759
|
this.ensureNotFinalized();
|
|
760
|
+
if (typeof field === 'object') {
|
|
761
|
+
const prop = {
|
|
762
|
+
name: '__subquery__',
|
|
763
|
+
kind: core_1.ReferenceKind.MANY_TO_ONE,
|
|
764
|
+
};
|
|
765
|
+
if (field instanceof QueryBuilder) {
|
|
766
|
+
prop.type = field.mainAlias.entityName;
|
|
767
|
+
prop.targetMeta = field.mainAlias.metadata;
|
|
768
|
+
field = field.getKnexQuery();
|
|
769
|
+
}
|
|
770
|
+
this._joins[`${this.alias}.${prop.name}#${alias}`] = {
|
|
771
|
+
prop,
|
|
772
|
+
alias,
|
|
773
|
+
type,
|
|
774
|
+
cond,
|
|
775
|
+
schema,
|
|
776
|
+
subquery: field.toString(),
|
|
777
|
+
ownerAlias: this.alias,
|
|
778
|
+
};
|
|
779
|
+
return prop;
|
|
780
|
+
}
|
|
781
|
+
if (!subquery && type.includes('lateral')) {
|
|
782
|
+
throw new Error(`Lateral join can be used only with a sub-query.`);
|
|
783
|
+
}
|
|
566
784
|
const [fromAlias, fromField] = this.helper.splitField(field);
|
|
567
|
-
const
|
|
785
|
+
const q = (str) => `'${str}'`;
|
|
786
|
+
if (!this._aliases[fromAlias]) {
|
|
787
|
+
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(', ')}.`);
|
|
788
|
+
}
|
|
789
|
+
const entityName = this._aliases[fromAlias].entityName;
|
|
568
790
|
const meta = this.metadata.get(entityName);
|
|
569
791
|
const prop = meta.properties[fromField];
|
|
570
792
|
if (!prop) {
|
|
571
|
-
throw new Error(`Trying to join ${field}, but ${fromField} is not a defined relation on ${meta.className}
|
|
793
|
+
throw new Error(`Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`);
|
|
572
794
|
}
|
|
573
795
|
this.createAlias(prop.type, alias);
|
|
574
796
|
cond = core_1.QueryHelper.processWhere({
|
|
@@ -582,24 +804,24 @@ class QueryBuilder {
|
|
|
582
804
|
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
583
805
|
path ??= `${(Object.values(this._joins).find(j => j.alias === fromAlias)?.path ?? entityName)}.${prop.name}`;
|
|
584
806
|
if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY) {
|
|
585
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
|
|
807
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
586
808
|
}
|
|
587
809
|
else if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY) {
|
|
588
810
|
let pivotAlias = alias;
|
|
589
|
-
if (type !==
|
|
811
|
+
if (type !== enums_1.JoinType.pivotJoin) {
|
|
590
812
|
const oldPivotAlias = this.getAliasForJoinPath(path + '[pivot]');
|
|
591
813
|
pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
|
|
592
814
|
aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
|
|
593
815
|
}
|
|
594
|
-
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path);
|
|
816
|
+
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
|
|
595
817
|
Object.assign(this._joins, joins);
|
|
596
818
|
this.createAlias(prop.pivotEntity, pivotAlias);
|
|
597
819
|
}
|
|
598
820
|
else if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE) {
|
|
599
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond);
|
|
821
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
600
822
|
}
|
|
601
823
|
else { // MANY_TO_ONE
|
|
602
|
-
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond);
|
|
824
|
+
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
|
|
603
825
|
}
|
|
604
826
|
if (!this._joins[aliasedName].path && path) {
|
|
605
827
|
this._joins[aliasedName].path = path;
|
|
@@ -608,13 +830,27 @@ class QueryBuilder {
|
|
|
608
830
|
}
|
|
609
831
|
prepareFields(fields, type = 'where') {
|
|
610
832
|
const ret = [];
|
|
833
|
+
const getFieldName = (name) => {
|
|
834
|
+
if (type === 'groupBy') {
|
|
835
|
+
return this.helper.mapper(name, this.type, undefined, null);
|
|
836
|
+
}
|
|
837
|
+
return this.helper.mapper(name, this.type);
|
|
838
|
+
};
|
|
611
839
|
fields.forEach(field => {
|
|
840
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(field);
|
|
841
|
+
if (rawField) {
|
|
842
|
+
const sql = this.platform.formatQuery(rawField.sql, rawField.params);
|
|
843
|
+
ret.push(this.knex.raw(sql));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
612
846
|
if (!core_1.Utils.isString(field)) {
|
|
613
|
-
|
|
847
|
+
ret.push(field);
|
|
848
|
+
return;
|
|
614
849
|
}
|
|
615
850
|
const join = Object.keys(this._joins).find(k => field === k.substring(0, k.indexOf('#')));
|
|
616
851
|
if (join && type === 'where') {
|
|
617
|
-
|
|
852
|
+
ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
|
|
853
|
+
return;
|
|
618
854
|
}
|
|
619
855
|
const [a, f] = this.helper.splitField(field);
|
|
620
856
|
const prop = this.helper.getProperty(f, a);
|
|
@@ -623,19 +859,20 @@ class QueryBuilder {
|
|
|
623
859
|
return;
|
|
624
860
|
}
|
|
625
861
|
if (prop?.embedded) {
|
|
626
|
-
const
|
|
627
|
-
|
|
862
|
+
const name = prop.embeddedPath?.join('.') ?? prop.fieldNames[0];
|
|
863
|
+
const aliased = this._aliases[a] ? `${a}.${name}` : name;
|
|
864
|
+
ret.push(getFieldName(aliased));
|
|
628
865
|
return;
|
|
629
866
|
}
|
|
630
867
|
if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
631
868
|
if (prop.object) {
|
|
632
|
-
ret.push(
|
|
869
|
+
ret.push(getFieldName(prop.fieldNames[0]));
|
|
633
870
|
}
|
|
634
871
|
else {
|
|
635
872
|
const nest = (prop) => {
|
|
636
873
|
for (const childProp of Object.values(prop.embeddedProps)) {
|
|
637
|
-
if (childProp.fieldNames) {
|
|
638
|
-
ret.push(
|
|
874
|
+
if (childProp.fieldNames && (childProp.kind !== core_1.ReferenceKind.EMBEDDED || childProp.object) && childProp.persist !== false) {
|
|
875
|
+
ret.push(getFieldName(childProp.fieldNames[0]));
|
|
639
876
|
}
|
|
640
877
|
else {
|
|
641
878
|
nest(childProp);
|
|
@@ -646,11 +883,15 @@ class QueryBuilder {
|
|
|
646
883
|
}
|
|
647
884
|
return;
|
|
648
885
|
}
|
|
649
|
-
|
|
886
|
+
if (prop && prop.fieldNames.length > 1) {
|
|
887
|
+
ret.push(...prop.fieldNames.map(f => getFieldName(f)));
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
ret.push(getFieldName(field));
|
|
650
891
|
});
|
|
651
892
|
const meta = this.mainAlias.metadata;
|
|
652
893
|
/* istanbul ignore next */
|
|
653
|
-
const requiresSQLConversion = meta?.props.filter(p => p.
|
|
894
|
+
const requiresSQLConversion = meta?.props.filter(p => p.hasConvertToJSValueSQL && p.persist !== false) ?? [];
|
|
654
895
|
if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0) {
|
|
655
896
|
requiresSQLConversion.forEach(p => ret.push(this.helper.mapper(p.name, this.type)));
|
|
656
897
|
}
|
|
@@ -659,11 +900,6 @@ class QueryBuilder {
|
|
|
659
900
|
const cols = this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[f]);
|
|
660
901
|
ret.push(...cols);
|
|
661
902
|
}
|
|
662
|
-
if (this._joins[f].prop.kind !== core_1.ReferenceKind.ONE_TO_ONE && this._joins[f].inverseJoinColumns) {
|
|
663
|
-
this._joins[f].inverseJoinColumns.forEach(inverseJoinColumn => {
|
|
664
|
-
core_1.Utils.renameKey(this._cond, inverseJoinColumn, `${this._joins[f].alias}.${inverseJoinColumn}`);
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
903
|
});
|
|
668
904
|
return ret;
|
|
669
905
|
}
|
|
@@ -678,10 +914,9 @@ class QueryBuilder {
|
|
|
678
914
|
}
|
|
679
915
|
if (data) {
|
|
680
916
|
if (core_1.Utils.isEntity(data)) {
|
|
681
|
-
// data = helper(data).toJSON();
|
|
682
917
|
data = this.em?.getComparator().prepareEntity(data) ?? (0, core_1.serialize)(data);
|
|
683
918
|
}
|
|
684
|
-
this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES));
|
|
919
|
+
this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES), false);
|
|
685
920
|
}
|
|
686
921
|
if (cond) {
|
|
687
922
|
this.where(cond);
|
|
@@ -691,6 +926,8 @@ class QueryBuilder {
|
|
|
691
926
|
getQueryBase() {
|
|
692
927
|
const qb = this.getKnex();
|
|
693
928
|
const schema = this.getSchema(this.mainAlias);
|
|
929
|
+
// Joined tables doesn't need to belong to the same schema as the main table
|
|
930
|
+
const joinSchema = this._schema ?? this.em?.schema ?? schema;
|
|
694
931
|
if (schema) {
|
|
695
932
|
qb.withSchema(schema);
|
|
696
933
|
}
|
|
@@ -709,12 +946,12 @@ class QueryBuilder {
|
|
|
709
946
|
else if (this.flags.has(core_1.QueryFlag.DISTINCT)) {
|
|
710
947
|
qb.distinct();
|
|
711
948
|
}
|
|
712
|
-
this.helper.processJoins(qb, this._joins,
|
|
949
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
713
950
|
break;
|
|
714
951
|
case enums_1.QueryType.COUNT: {
|
|
715
952
|
const m = this.flags.has(core_1.QueryFlag.DISTINCT) ? 'countDistinct' : 'count';
|
|
716
953
|
qb[m]({ count: this._fields.map(f => this.helper.mapper(f, this.type)) });
|
|
717
|
-
this.helper.processJoins(qb, this._joins,
|
|
954
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
718
955
|
break;
|
|
719
956
|
}
|
|
720
957
|
case enums_1.QueryType.INSERT:
|
|
@@ -733,6 +970,24 @@ class QueryBuilder {
|
|
|
733
970
|
}
|
|
734
971
|
return qb;
|
|
735
972
|
}
|
|
973
|
+
applyDiscriminatorCondition() {
|
|
974
|
+
const meta = this.mainAlias.metadata;
|
|
975
|
+
if (!meta?.discriminatorValue) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
|
|
979
|
+
const children = [];
|
|
980
|
+
const lookUpChildren = (ret, type) => {
|
|
981
|
+
const children = types.filter(meta2 => meta2.extends === type);
|
|
982
|
+
children.forEach(m => lookUpChildren(ret, m.className));
|
|
983
|
+
ret.push(...children.filter(c => c.discriminatorValue));
|
|
984
|
+
return children;
|
|
985
|
+
};
|
|
986
|
+
lookUpChildren(children, meta.className);
|
|
987
|
+
this.andWhere({
|
|
988
|
+
[meta.root.discriminatorColumn]: children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue,
|
|
989
|
+
});
|
|
990
|
+
}
|
|
736
991
|
finalize() {
|
|
737
992
|
if (this.finalized) {
|
|
738
993
|
return;
|
|
@@ -741,11 +996,12 @@ class QueryBuilder {
|
|
|
741
996
|
this.select('*');
|
|
742
997
|
}
|
|
743
998
|
const meta = this.mainAlias.metadata;
|
|
999
|
+
this.applyDiscriminatorCondition();
|
|
744
1000
|
if (meta && this.flags.has(core_1.QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
|
|
745
1001
|
const relationsToPopulate = this._populate.map(({ field }) => field);
|
|
746
1002
|
meta.relations
|
|
747
|
-
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
|
|
748
|
-
.map(prop => ({ field: prop.name }))
|
|
1003
|
+
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name) && !relationsToPopulate.includes(`${prop.name}:ref`))
|
|
1004
|
+
.map(prop => ({ field: `${prop.name}:ref` }))
|
|
749
1005
|
.forEach(item => this._populate.push(item));
|
|
750
1006
|
}
|
|
751
1007
|
this._populate.forEach(({ field }) => {
|
|
@@ -753,16 +1009,14 @@ class QueryBuilder {
|
|
|
753
1009
|
const aliasedField = `${fromAlias}.${fromField}`;
|
|
754
1010
|
const join = Object.keys(this._joins).find(k => `${aliasedField}#${this._joins[k].alias}` === k);
|
|
755
1011
|
if (join && this._joins[join] && this.helper.isOneToOneInverse(fromField)) {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (this.metadata.find(field)?.pivotTable) { // pivot table entity
|
|
759
|
-
this.autoJoinPivotTable(field);
|
|
1012
|
+
this._populateMap[join] = this._joins[join].alias;
|
|
1013
|
+
return;
|
|
760
1014
|
}
|
|
761
|
-
|
|
1015
|
+
if (meta && this.helper.isOneToOneInverse(fromField)) {
|
|
762
1016
|
const prop = meta.properties[fromField];
|
|
763
1017
|
const alias = this.getNextAlias(prop.pivotEntity ?? prop.type);
|
|
764
1018
|
const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
765
|
-
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias,
|
|
1019
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, enums_1.JoinType.leftJoin);
|
|
766
1020
|
this._populateMap[aliasedName] = this._joins[aliasedName].alias;
|
|
767
1021
|
}
|
|
768
1022
|
});
|
|
@@ -774,14 +1028,20 @@ class QueryBuilder {
|
|
|
774
1028
|
const aliased = this.knex.ref(prop.fieldNames[0]).toString();
|
|
775
1029
|
return `${prop.formula(alias)} as ${aliased}`;
|
|
776
1030
|
})
|
|
777
|
-
.filter(field => !this._fields.
|
|
778
|
-
|
|
1031
|
+
.filter(field => !this._fields.some(f => {
|
|
1032
|
+
if (f instanceof core_1.RawQueryFragment) {
|
|
1033
|
+
return f.sql === field && f.params.length === 0;
|
|
1034
|
+
}
|
|
1035
|
+
return f === field;
|
|
1036
|
+
}))
|
|
1037
|
+
.forEach(field => this._fields.push((0, core_1.raw)(field)));
|
|
779
1038
|
}
|
|
1039
|
+
this.processPopulateWhere();
|
|
780
1040
|
core_1.QueryHelper.processObjectParams(this._data);
|
|
781
1041
|
core_1.QueryHelper.processObjectParams(this._cond);
|
|
782
1042
|
core_1.QueryHelper.processObjectParams(this._having);
|
|
783
|
-
// automatically enable paginate flag when we detect to-many joins
|
|
784
|
-
if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this.hasToManyJoins()) {
|
|
1043
|
+
// automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
|
|
1044
|
+
if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) && this._groupBy.length === 0 && this.hasToManyJoins()) {
|
|
785
1045
|
this.flags.add(core_1.QueryFlag.PAGINATE);
|
|
786
1046
|
}
|
|
787
1047
|
if (meta && this.flags.has(core_1.QueryFlag.PAGINATE) && (this._limit > 0 || this._offset > 0)) {
|
|
@@ -792,6 +1052,43 @@ class QueryBuilder {
|
|
|
792
1052
|
}
|
|
793
1053
|
this.finalized = true;
|
|
794
1054
|
}
|
|
1055
|
+
processPopulateWhere() {
|
|
1056
|
+
if (this._populateWhere == null || this._populateWhere === core_1.PopulateHint.ALL) {
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const joins = Object.values(this._joins);
|
|
1060
|
+
joins.forEach(join => {
|
|
1061
|
+
join.cond_ = join.cond;
|
|
1062
|
+
join.cond = {};
|
|
1063
|
+
});
|
|
1064
|
+
const replaceOnConditions = (cond, op) => {
|
|
1065
|
+
Object.keys(cond).forEach(k => {
|
|
1066
|
+
if (core_1.Utils.isOperator(k)) {
|
|
1067
|
+
if (Array.isArray(cond[k])) {
|
|
1068
|
+
return cond[k].forEach((c) => replaceOnConditions(c, k));
|
|
1069
|
+
}
|
|
1070
|
+
/* istanbul ignore next */
|
|
1071
|
+
return replaceOnConditions(cond[k], k);
|
|
1072
|
+
}
|
|
1073
|
+
const [alias] = this.helper.splitField(k);
|
|
1074
|
+
const join = joins.find(j => j.alias === alias);
|
|
1075
|
+
if (join) {
|
|
1076
|
+
if (join.cond[k]) {
|
|
1077
|
+
join.cond = { [op ?? '$and']: [join.cond, { [k]: cond[k] }] };
|
|
1078
|
+
}
|
|
1079
|
+
else {
|
|
1080
|
+
join.cond = { ...join.cond, [k]: cond[k] };
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
};
|
|
1085
|
+
if (typeof this._populateWhere === 'object') {
|
|
1086
|
+
const cond = CriteriaNodeFactory_1.CriteriaNodeFactory
|
|
1087
|
+
.createNode(this.metadata, this.mainAlias.entityName, this._populateWhere)
|
|
1088
|
+
.process(this, { matchPopulateJoins: true, ignoreBranching: true, preferNoBranch: true });
|
|
1089
|
+
replaceOnConditions(cond);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
795
1092
|
hasToManyJoins() {
|
|
796
1093
|
return Object.values(this._joins).some(join => {
|
|
797
1094
|
return [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
@@ -800,9 +1097,12 @@ class QueryBuilder {
|
|
|
800
1097
|
wrapPaginateSubQuery(meta) {
|
|
801
1098
|
const pks = this.prepareFields(meta.primaryKeys, 'sub-query');
|
|
802
1099
|
const subQuery = this.clone().select(pks).groupBy(pks).limit(this._limit);
|
|
1100
|
+
// revert the on conditions added via populateWhere, we want to apply those only once
|
|
1101
|
+
Object.values(subQuery._joins).forEach(join => join.cond = join.cond_ ?? {});
|
|
803
1102
|
if (this._offset) {
|
|
804
1103
|
subQuery.offset(this._offset);
|
|
805
1104
|
}
|
|
1105
|
+
const addToSelect = [];
|
|
806
1106
|
if (this._orderBy.length > 0) {
|
|
807
1107
|
const orderBy = [];
|
|
808
1108
|
for (const orderMap of this._orderBy) {
|
|
@@ -810,28 +1110,38 @@ class QueryBuilder {
|
|
|
810
1110
|
const [a, f] = this.helper.splitField(field);
|
|
811
1111
|
const prop = this.helper.getProperty(f, a);
|
|
812
1112
|
const type = this.platform.castColumn(prop);
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
1113
|
+
const fieldName = this.helper.mapper(field, this.type, undefined, null);
|
|
1114
|
+
if (!prop?.persist && !prop?.formula) {
|
|
1115
|
+
addToSelect.push(fieldName);
|
|
1116
|
+
}
|
|
1117
|
+
orderBy.push({ [(0, core_1.raw)(`min(${this.knex.ref(fieldName)}${type})`)]: direction });
|
|
816
1118
|
}
|
|
817
1119
|
}
|
|
818
1120
|
subQuery.orderBy(orderBy);
|
|
819
1121
|
}
|
|
820
1122
|
subQuery.finalized = true;
|
|
821
1123
|
const knexQuery = subQuery.as(this.mainAlias.aliasName).clearSelect().select(pks);
|
|
1124
|
+
if (addToSelect.length > 0) {
|
|
1125
|
+
addToSelect.forEach(prop => {
|
|
1126
|
+
const field = this._fields.find(field => {
|
|
1127
|
+
if (typeof field === 'object' && field && '__as' in field) {
|
|
1128
|
+
return field.__as === prop;
|
|
1129
|
+
}
|
|
1130
|
+
// not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
|
|
1131
|
+
return field.toString().includes(prop);
|
|
1132
|
+
});
|
|
1133
|
+
if (field) {
|
|
1134
|
+
knexQuery.select(field);
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
822
1138
|
// multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
|
|
823
1139
|
// https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
|
|
824
1140
|
const subSubQuery = this.getKnex().select(pks).from(knexQuery);
|
|
1141
|
+
subSubQuery.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
825
1142
|
this._limit = undefined;
|
|
826
1143
|
this._offset = undefined;
|
|
827
|
-
const cond = this._cond;
|
|
828
1144
|
this.select(this._fields).where({ [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery } });
|
|
829
|
-
if (this._populateWhere === core_1.PopulateHint.INFER) {
|
|
830
|
-
this.andWhere(cond);
|
|
831
|
-
}
|
|
832
|
-
else if (typeof this._populateWhere === 'object') {
|
|
833
|
-
this.andWhere(this._populateWhere);
|
|
834
|
-
}
|
|
835
1145
|
}
|
|
836
1146
|
wrapModifySubQuery(meta) {
|
|
837
1147
|
const subQuery = this.clone();
|
|
@@ -845,21 +1155,10 @@ class QueryBuilder {
|
|
|
845
1155
|
[core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery },
|
|
846
1156
|
});
|
|
847
1157
|
}
|
|
848
|
-
autoJoinPivotTable(field) {
|
|
849
|
-
const pivotMeta = this.metadata.find(field);
|
|
850
|
-
const owner = pivotMeta.relations[0];
|
|
851
|
-
const inverse = pivotMeta.relations[1];
|
|
852
|
-
const prop = this._cond[pivotMeta.name + '.' + owner.name] || this._orderBy[pivotMeta.name + '.' + owner.name] ? inverse : owner;
|
|
853
|
-
const pivotAlias = this.getNextAlias(pivotMeta.name);
|
|
854
|
-
this._joins[field] = this.helper.joinPivotTable(field, prop, this.mainAlias.aliasName, pivotAlias, 'leftJoin');
|
|
855
|
-
core_1.Utils.renameKey(this._cond, `${field}.${owner.name}`, core_1.Utils.getPrimaryKeyHash(owner.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
|
|
856
|
-
core_1.Utils.renameKey(this._cond, `${field}.${inverse.name}`, core_1.Utils.getPrimaryKeyHash(inverse.fieldNames.map(fieldName => `${pivotAlias}.${fieldName}`)));
|
|
857
|
-
this._populateMap[field] = this._joins[field].alias;
|
|
858
|
-
}
|
|
859
1158
|
getSchema(alias) {
|
|
860
1159
|
const { metadata } = alias;
|
|
861
1160
|
const metaSchema = metadata?.schema && metadata.schema !== '*' ? metadata.schema : undefined;
|
|
862
|
-
return this._schema ?? metaSchema ?? this.em?.config.get('schema');
|
|
1161
|
+
return this._schema ?? metaSchema ?? this.em?.schema ?? this.em?.config.get('schema');
|
|
863
1162
|
}
|
|
864
1163
|
createAlias(entityName, aliasName, subQuery) {
|
|
865
1164
|
const metadata = this.metadata.find(entityName);
|
|
@@ -897,6 +1196,7 @@ class QueryBuilder {
|
|
|
897
1196
|
}
|
|
898
1197
|
}
|
|
899
1198
|
/* istanbul ignore next */
|
|
1199
|
+
/** @ignore */
|
|
900
1200
|
[util_1.inspect.custom](depth) {
|
|
901
1201
|
const object = { ...this };
|
|
902
1202
|
const hidden = ['metadata', 'driver', 'context', 'platform', 'knex', 'type'];
|
|
@@ -907,6 +1207,9 @@ class QueryBuilder {
|
|
|
907
1207
|
if (this._data) {
|
|
908
1208
|
object.data = this._data;
|
|
909
1209
|
}
|
|
1210
|
+
if (this._schema) {
|
|
1211
|
+
object.schema = this._schema;
|
|
1212
|
+
}
|
|
910
1213
|
if (!core_1.Utils.isEmpty(this._cond)) {
|
|
911
1214
|
object.where = this._cond;
|
|
912
1215
|
}
|