@yandjin-mikro-orm/knex 6.1.4-rc-sti-changes-1
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 +58 -0
- package/AbstractSqlConnection.js +206 -0
- package/AbstractSqlDriver.d.ts +73 -0
- package/AbstractSqlDriver.js +1378 -0
- package/AbstractSqlPlatform.d.ts +25 -0
- package/AbstractSqlPlatform.js +86 -0
- package/LICENSE +21 -0
- package/MonkeyPatchable.d.ts +11 -0
- package/MonkeyPatchable.js +39 -0
- package/PivotCollectionPersister.d.ts +17 -0
- package/PivotCollectionPersister.js +131 -0
- package/README.md +383 -0
- package/SqlEntityManager.d.ts +25 -0
- package/SqlEntityManager.js +40 -0
- package/SqlEntityRepository.d.ts +24 -0
- package/SqlEntityRepository.js +36 -0
- package/index.d.ts +18 -0
- package/index.js +40 -0
- package/index.mjs +215 -0
- package/package.json +71 -0
- package/query/ArrayCriteriaNode.d.ts +10 -0
- package/query/ArrayCriteriaNode.js +25 -0
- package/query/CriteriaNode.d.ts +31 -0
- package/query/CriteriaNode.js +147 -0
- package/query/CriteriaNodeFactory.d.ts +12 -0
- package/query/CriteriaNodeFactory.js +90 -0
- package/query/ObjectCriteriaNode.d.ts +15 -0
- package/query/ObjectCriteriaNode.js +233 -0
- package/query/QueryBuilder.d.ts +291 -0
- package/query/QueryBuilder.js +1445 -0
- package/query/QueryBuilderHelper.d.ts +64 -0
- package/query/QueryBuilderHelper.js +747 -0
- package/query/ScalarCriteriaNode.d.ts +10 -0
- package/query/ScalarCriteriaNode.js +56 -0
- package/query/enums.d.ts +15 -0
- package/query/enums.js +20 -0
- package/query/index.d.ts +8 -0
- package/query/index.js +24 -0
- package/schema/DatabaseSchema.d.ts +29 -0
- package/schema/DatabaseSchema.js +140 -0
- package/schema/DatabaseTable.d.ts +61 -0
- package/schema/DatabaseTable.js +727 -0
- package/schema/SchemaComparator.d.ts +59 -0
- package/schema/SchemaComparator.js +603 -0
- package/schema/SchemaHelper.d.ts +56 -0
- package/schema/SchemaHelper.js +274 -0
- package/schema/SqlSchemaGenerator.d.ts +63 -0
- package/schema/SqlSchemaGenerator.js +598 -0
- package/schema/index.d.ts +5 -0
- package/schema/index.js +21 -0
- package/typings.d.ts +174 -0
- package/typings.js +2 -0
|
@@ -0,0 +1,1445 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueryBuilder = void 0;
|
|
4
|
+
const util_1 = require("util");
|
|
5
|
+
const core_1 = require("@yandjin-mikro-orm/core");
|
|
6
|
+
const enums_1 = require("./enums");
|
|
7
|
+
const QueryBuilderHelper_1 = require("./QueryBuilderHelper");
|
|
8
|
+
const CriteriaNodeFactory_1 = require("./CriteriaNodeFactory");
|
|
9
|
+
/**
|
|
10
|
+
* SQL query builder with fluent interface.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const qb = orm.em.createQueryBuilder(Publisher);
|
|
14
|
+
* qb.select('*')
|
|
15
|
+
* .where({
|
|
16
|
+
* name: 'test 123',
|
|
17
|
+
* type: PublisherType.GLOBAL,
|
|
18
|
+
* })
|
|
19
|
+
* .orderBy({
|
|
20
|
+
* name: QueryOrder.DESC,
|
|
21
|
+
* type: QueryOrder.ASC,
|
|
22
|
+
* })
|
|
23
|
+
* .limit(2, 1);
|
|
24
|
+
*
|
|
25
|
+
* const publisher = await qb.getSingleResult();
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
class QueryBuilder {
|
|
29
|
+
metadata;
|
|
30
|
+
driver;
|
|
31
|
+
context;
|
|
32
|
+
connectionType;
|
|
33
|
+
em;
|
|
34
|
+
loggerContext;
|
|
35
|
+
get mainAlias() {
|
|
36
|
+
this.ensureFromClause();
|
|
37
|
+
return this._mainAlias;
|
|
38
|
+
}
|
|
39
|
+
get alias() {
|
|
40
|
+
return this.mainAlias.aliasName;
|
|
41
|
+
}
|
|
42
|
+
get helper() {
|
|
43
|
+
this.ensureFromClause();
|
|
44
|
+
return this._helper;
|
|
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
|
+
/** @internal */
|
|
59
|
+
rawFragments = new Set();
|
|
60
|
+
aliasCounter = 0;
|
|
61
|
+
flags = new Set([core_1.QueryFlag.CONVERT_CUSTOM_TYPES]);
|
|
62
|
+
finalized = false;
|
|
63
|
+
_joins = {};
|
|
64
|
+
_explicitAlias = false;
|
|
65
|
+
_schema;
|
|
66
|
+
_cond = {};
|
|
67
|
+
_data;
|
|
68
|
+
_orderBy = [];
|
|
69
|
+
_groupBy = [];
|
|
70
|
+
_having = {};
|
|
71
|
+
_returning;
|
|
72
|
+
_onConflict;
|
|
73
|
+
_limit;
|
|
74
|
+
_offset;
|
|
75
|
+
_distinctOn;
|
|
76
|
+
_joinedProps = new Map();
|
|
77
|
+
_cache;
|
|
78
|
+
_indexHint;
|
|
79
|
+
_comments = [];
|
|
80
|
+
_hintComments = [];
|
|
81
|
+
flushMode;
|
|
82
|
+
lockMode;
|
|
83
|
+
lockTables;
|
|
84
|
+
subQueries = {};
|
|
85
|
+
_mainAlias;
|
|
86
|
+
_aliases = {};
|
|
87
|
+
_helper;
|
|
88
|
+
platform;
|
|
89
|
+
knex;
|
|
90
|
+
/**
|
|
91
|
+
* @internal
|
|
92
|
+
*/
|
|
93
|
+
constructor(entityName, metadata, driver, context, alias, connectionType, em, loggerContext) {
|
|
94
|
+
this.metadata = metadata;
|
|
95
|
+
this.driver = driver;
|
|
96
|
+
this.context = context;
|
|
97
|
+
this.connectionType = connectionType;
|
|
98
|
+
this.em = em;
|
|
99
|
+
this.loggerContext = loggerContext;
|
|
100
|
+
this.platform = this.driver.getPlatform();
|
|
101
|
+
this.knex = this.driver.getConnection(this.connectionType).getKnex();
|
|
102
|
+
if (alias) {
|
|
103
|
+
this.aliasCounter++;
|
|
104
|
+
this._explicitAlias = true;
|
|
105
|
+
}
|
|
106
|
+
// @ts-expect-error union type does not match the overloaded method signature
|
|
107
|
+
this.from(entityName, alias);
|
|
108
|
+
}
|
|
109
|
+
select(fields, distinct = false) {
|
|
110
|
+
this.ensureNotFinalized();
|
|
111
|
+
this._fields = core_1.Utils.asArray(fields);
|
|
112
|
+
if (distinct) {
|
|
113
|
+
this.flags.add(core_1.QueryFlag.DISTINCT);
|
|
114
|
+
}
|
|
115
|
+
return this.init(enums_1.QueryType.SELECT);
|
|
116
|
+
}
|
|
117
|
+
addSelect(fields) {
|
|
118
|
+
this.ensureNotFinalized();
|
|
119
|
+
if (this.type && this.type !== enums_1.QueryType.SELECT) {
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
return this.select([
|
|
123
|
+
...core_1.Utils.asArray(this._fields),
|
|
124
|
+
...core_1.Utils.asArray(fields),
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
distinct() {
|
|
128
|
+
this.ensureNotFinalized();
|
|
129
|
+
return this.setFlag(core_1.QueryFlag.DISTINCT);
|
|
130
|
+
}
|
|
131
|
+
/** postgres only */
|
|
132
|
+
distinctOn(fields) {
|
|
133
|
+
this.ensureNotFinalized();
|
|
134
|
+
this._distinctOn = core_1.Utils.asArray(fields);
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
insert(data) {
|
|
138
|
+
return this.init(enums_1.QueryType.INSERT, data);
|
|
139
|
+
}
|
|
140
|
+
update(data) {
|
|
141
|
+
return this.init(enums_1.QueryType.UPDATE, data);
|
|
142
|
+
}
|
|
143
|
+
delete(cond) {
|
|
144
|
+
return this.init(enums_1.QueryType.DELETE, undefined, cond);
|
|
145
|
+
}
|
|
146
|
+
truncate() {
|
|
147
|
+
return this.init(enums_1.QueryType.TRUNCATE);
|
|
148
|
+
}
|
|
149
|
+
count(field, distinct = false) {
|
|
150
|
+
if (field) {
|
|
151
|
+
this._fields = core_1.Utils.asArray(field);
|
|
152
|
+
}
|
|
153
|
+
else if (this.hasToManyJoins()) {
|
|
154
|
+
this._fields = this.mainAlias.metadata.primaryKeys;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
this._fields = [(0, core_1.raw)("*")];
|
|
158
|
+
}
|
|
159
|
+
if (distinct) {
|
|
160
|
+
this.flags.add(core_1.QueryFlag.DISTINCT);
|
|
161
|
+
}
|
|
162
|
+
return this.init(enums_1.QueryType.COUNT);
|
|
163
|
+
}
|
|
164
|
+
join(field, alias, cond = {}, type = enums_1.JoinType.innerJoin, path, schema) {
|
|
165
|
+
this.joinReference(field, alias, cond, type, path, schema);
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
innerJoin(field, alias, cond = {}, schema) {
|
|
169
|
+
this.join(field, alias, cond, enums_1.JoinType.innerJoin, undefined, schema);
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
innerJoinLateral(field, alias, cond = {}, schema) {
|
|
173
|
+
this.join(field, alias, cond, enums_1.JoinType.innerJoinLateral, undefined, schema);
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
leftJoin(field, alias, cond = {}, schema) {
|
|
177
|
+
return this.join(field, alias, cond, enums_1.JoinType.leftJoin, undefined, schema);
|
|
178
|
+
}
|
|
179
|
+
leftJoinLateral(field, alias, cond = {}, schema) {
|
|
180
|
+
return this.join(field, alias, cond, enums_1.JoinType.leftJoinLateral, undefined, schema);
|
|
181
|
+
}
|
|
182
|
+
joinAndSelect(field, alias, cond = {}, type = enums_1.JoinType.innerJoin, path, fields, schema) {
|
|
183
|
+
if (!this.type) {
|
|
184
|
+
this.select("*");
|
|
185
|
+
}
|
|
186
|
+
let subquery;
|
|
187
|
+
if (Array.isArray(field)) {
|
|
188
|
+
subquery =
|
|
189
|
+
field[1] instanceof QueryBuilder
|
|
190
|
+
? field[1].getFormattedQuery()
|
|
191
|
+
: field[1].toString();
|
|
192
|
+
field = field[0];
|
|
193
|
+
}
|
|
194
|
+
const prop = this.joinReference(field, alias, cond, type, path, schema, subquery);
|
|
195
|
+
const [fromAlias] = this.helper.splitField(field);
|
|
196
|
+
if (subquery) {
|
|
197
|
+
this._joins[`${fromAlias}.${prop.name}#${alias}`].subquery = subquery;
|
|
198
|
+
}
|
|
199
|
+
const populate = this._joinedProps.get(fromAlias);
|
|
200
|
+
const item = {
|
|
201
|
+
field: prop.name,
|
|
202
|
+
strategy: core_1.LoadStrategy.JOINED,
|
|
203
|
+
children: [],
|
|
204
|
+
};
|
|
205
|
+
if (populate) {
|
|
206
|
+
populate.children.push(item);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// root entity
|
|
210
|
+
this._populate.push(item);
|
|
211
|
+
}
|
|
212
|
+
this._joinedProps.set(alias, item);
|
|
213
|
+
this.addSelect(this.getFieldsForJoinedLoad(prop, alias, fields));
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
leftJoinAndSelect(field, alias, cond = {}, fields, schema) {
|
|
217
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.leftJoin, undefined, fields, schema);
|
|
218
|
+
}
|
|
219
|
+
leftJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
|
|
220
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.leftJoinLateral, undefined, fields, schema);
|
|
221
|
+
}
|
|
222
|
+
innerJoinAndSelect(field, alias, cond = {}, fields, schema) {
|
|
223
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.innerJoin, undefined, fields, schema);
|
|
224
|
+
}
|
|
225
|
+
innerJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
|
|
226
|
+
return this.joinAndSelect(field, alias, cond, enums_1.JoinType.innerJoinLateral, undefined, fields, schema);
|
|
227
|
+
}
|
|
228
|
+
getFieldsForJoinedLoad(prop, alias, explicitFields) {
|
|
229
|
+
const fields = [];
|
|
230
|
+
const populate = [];
|
|
231
|
+
const joinKey = Object.keys(this._joins).find((join) => join.endsWith(`#${alias}`));
|
|
232
|
+
if (joinKey) {
|
|
233
|
+
const path = this._joins[joinKey].path.split(".").slice(1);
|
|
234
|
+
let children = this._populate;
|
|
235
|
+
for (let i = 0; i < path.length; i++) {
|
|
236
|
+
const child = children.filter((hint) => {
|
|
237
|
+
const [propName] = hint.field.split(":", 2);
|
|
238
|
+
return propName === path[i];
|
|
239
|
+
});
|
|
240
|
+
children = child.flatMap((c) => c.children);
|
|
241
|
+
}
|
|
242
|
+
populate.push(...children);
|
|
243
|
+
}
|
|
244
|
+
prop
|
|
245
|
+
.targetMeta.props.filter((prop) => explicitFields
|
|
246
|
+
? explicitFields.includes(prop.name) || prop.primary
|
|
247
|
+
: this.platform.shouldHaveColumn(prop, populate))
|
|
248
|
+
.forEach((prop) => fields.push(...this.driver.mapPropToFieldNames(this, prop, alias)));
|
|
249
|
+
return fields;
|
|
250
|
+
}
|
|
251
|
+
withSubQuery(subQuery, alias) {
|
|
252
|
+
this.ensureNotFinalized();
|
|
253
|
+
this.subQueries[alias] = subQuery.toString();
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
where(cond, params, operator) {
|
|
257
|
+
this.ensureNotFinalized();
|
|
258
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(cond);
|
|
259
|
+
if (rawField) {
|
|
260
|
+
const sql = this.platform.formatQuery(rawField.sql, rawField.params);
|
|
261
|
+
cond = { [(0, core_1.raw)(`(${sql})`)]: core_1.Utils.asArray(params) };
|
|
262
|
+
operator ??= "$and";
|
|
263
|
+
}
|
|
264
|
+
else if (core_1.Utils.isString(cond)) {
|
|
265
|
+
cond = { [(0, core_1.raw)(`(${cond})`, core_1.Utils.asArray(params))]: [] };
|
|
266
|
+
operator ??= "$and";
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
cond = core_1.QueryHelper.processWhere({
|
|
270
|
+
where: cond,
|
|
271
|
+
entityName: this.mainAlias.entityName,
|
|
272
|
+
metadata: this.metadata,
|
|
273
|
+
platform: this.platform,
|
|
274
|
+
aliasMap: this.getAliasMap(),
|
|
275
|
+
aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
|
|
276
|
+
convertCustomTypes: this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const op = operator || params;
|
|
280
|
+
const topLevel = !op || !core_1.Utils.hasObjectKeys(this._cond);
|
|
281
|
+
const criteriaNode = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond);
|
|
282
|
+
const ignoreBranching = this.__populateWhere === "infer";
|
|
283
|
+
if ([enums_1.QueryType.UPDATE, enums_1.QueryType.DELETE].includes(this.type) &&
|
|
284
|
+
criteriaNode.willAutoJoin(this)) {
|
|
285
|
+
// use sub-query to support joining
|
|
286
|
+
this.setFlag(this.type === enums_1.QueryType.UPDATE
|
|
287
|
+
? core_1.QueryFlag.UPDATE_SUB_QUERY
|
|
288
|
+
: core_1.QueryFlag.DELETE_SUB_QUERY);
|
|
289
|
+
this.select(this.mainAlias.metadata.primaryKeys, true);
|
|
290
|
+
}
|
|
291
|
+
if (topLevel) {
|
|
292
|
+
this._cond = criteriaNode.process(this, { ignoreBranching });
|
|
293
|
+
}
|
|
294
|
+
else if (Array.isArray(this._cond[op])) {
|
|
295
|
+
this._cond[op].push(criteriaNode.process(this, { ignoreBranching }));
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const cond1 = [
|
|
299
|
+
this._cond,
|
|
300
|
+
criteriaNode.process(this, { ignoreBranching }),
|
|
301
|
+
];
|
|
302
|
+
this._cond = { [op]: cond1 };
|
|
303
|
+
}
|
|
304
|
+
if (this._onConflict) {
|
|
305
|
+
this._onConflict[this._onConflict.length - 1].where = this._cond;
|
|
306
|
+
this._cond = {};
|
|
307
|
+
}
|
|
308
|
+
return this;
|
|
309
|
+
}
|
|
310
|
+
andWhere(cond, params) {
|
|
311
|
+
return this.where(cond, params, "$and");
|
|
312
|
+
}
|
|
313
|
+
orWhere(cond, params) {
|
|
314
|
+
return this.where(cond, params, "$or");
|
|
315
|
+
}
|
|
316
|
+
orderBy(orderBy) {
|
|
317
|
+
this.ensureNotFinalized();
|
|
318
|
+
this._orderBy = [];
|
|
319
|
+
core_1.Utils.asArray(orderBy).forEach((o) => {
|
|
320
|
+
const processed = core_1.QueryHelper.processWhere({
|
|
321
|
+
where: o,
|
|
322
|
+
entityName: this.mainAlias.entityName,
|
|
323
|
+
metadata: this.metadata,
|
|
324
|
+
platform: this.platform,
|
|
325
|
+
aliasMap: this.getAliasMap(),
|
|
326
|
+
aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
|
|
327
|
+
convertCustomTypes: false,
|
|
328
|
+
type: "orderBy",
|
|
329
|
+
});
|
|
330
|
+
this._orderBy.push(CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, { matchPopulateJoins: true }));
|
|
331
|
+
});
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
groupBy(fields) {
|
|
335
|
+
this.ensureNotFinalized();
|
|
336
|
+
this._groupBy = core_1.Utils.asArray(fields);
|
|
337
|
+
return this;
|
|
338
|
+
}
|
|
339
|
+
having(cond = {}, params) {
|
|
340
|
+
this.ensureNotFinalized();
|
|
341
|
+
if (core_1.Utils.isString(cond)) {
|
|
342
|
+
cond = { [(0, core_1.raw)(`(${cond})`, params)]: [] };
|
|
343
|
+
}
|
|
344
|
+
this._having = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond).process(this);
|
|
345
|
+
return this;
|
|
346
|
+
}
|
|
347
|
+
onConflict(fields = []) {
|
|
348
|
+
const meta = this.mainAlias.metadata;
|
|
349
|
+
this.ensureNotFinalized();
|
|
350
|
+
this._onConflict ??= [];
|
|
351
|
+
this._onConflict.push({
|
|
352
|
+
fields: core_1.Utils.asArray(fields).flatMap((f) => {
|
|
353
|
+
const key = f.toString();
|
|
354
|
+
/* istanbul ignore next */
|
|
355
|
+
return meta.properties[key]?.fieldNames ?? [key];
|
|
356
|
+
}),
|
|
357
|
+
});
|
|
358
|
+
return this;
|
|
359
|
+
}
|
|
360
|
+
ignore() {
|
|
361
|
+
if (!this._onConflict) {
|
|
362
|
+
throw new Error("You need to call `qb.onConflict()` first to use `qb.ignore()`");
|
|
363
|
+
}
|
|
364
|
+
this._onConflict[this._onConflict.length - 1].ignore = true;
|
|
365
|
+
return this;
|
|
366
|
+
}
|
|
367
|
+
merge(data) {
|
|
368
|
+
if (!this._onConflict) {
|
|
369
|
+
throw new Error("You need to call `qb.onConflict()` first to use `qb.merge()`");
|
|
370
|
+
}
|
|
371
|
+
if (Array.isArray(data) && data.length === 0) {
|
|
372
|
+
return this.ignore();
|
|
373
|
+
}
|
|
374
|
+
this._onConflict[this._onConflict.length - 1].merge = data;
|
|
375
|
+
return this;
|
|
376
|
+
}
|
|
377
|
+
returning(fields) {
|
|
378
|
+
this._returning = core_1.Utils.asArray(fields);
|
|
379
|
+
return this;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* @internal
|
|
383
|
+
*/
|
|
384
|
+
populate(populate, populateWhere) {
|
|
385
|
+
this.ensureNotFinalized();
|
|
386
|
+
this._populate = populate;
|
|
387
|
+
this._populateWhere = populateWhere;
|
|
388
|
+
return this;
|
|
389
|
+
}
|
|
390
|
+
limit(limit, offset = 0) {
|
|
391
|
+
this.ensureNotFinalized();
|
|
392
|
+
this._limit = limit;
|
|
393
|
+
if (offset) {
|
|
394
|
+
this.offset(offset);
|
|
395
|
+
}
|
|
396
|
+
return this;
|
|
397
|
+
}
|
|
398
|
+
offset(offset) {
|
|
399
|
+
this.ensureNotFinalized();
|
|
400
|
+
this._offset = offset;
|
|
401
|
+
return this;
|
|
402
|
+
}
|
|
403
|
+
withSchema(schema) {
|
|
404
|
+
this.ensureNotFinalized();
|
|
405
|
+
this._schema = schema;
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
408
|
+
setLockMode(mode, tables) {
|
|
409
|
+
this.ensureNotFinalized();
|
|
410
|
+
if (mode != null && mode !== core_1.LockMode.OPTIMISTIC && !this.context) {
|
|
411
|
+
throw core_1.ValidationError.transactionRequired();
|
|
412
|
+
}
|
|
413
|
+
this.lockMode = mode;
|
|
414
|
+
this.lockTables = tables;
|
|
415
|
+
return this;
|
|
416
|
+
}
|
|
417
|
+
setFlushMode(flushMode) {
|
|
418
|
+
this.ensureNotFinalized();
|
|
419
|
+
this.flushMode = flushMode;
|
|
420
|
+
return this;
|
|
421
|
+
}
|
|
422
|
+
setFlag(flag) {
|
|
423
|
+
this.ensureNotFinalized();
|
|
424
|
+
this.flags.add(flag);
|
|
425
|
+
return this;
|
|
426
|
+
}
|
|
427
|
+
unsetFlag(flag) {
|
|
428
|
+
this.ensureNotFinalized();
|
|
429
|
+
this.flags.delete(flag);
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
hasFlag(flag) {
|
|
433
|
+
return this.flags.has(flag);
|
|
434
|
+
}
|
|
435
|
+
cache(config = true) {
|
|
436
|
+
this.ensureNotFinalized();
|
|
437
|
+
this._cache = config;
|
|
438
|
+
return this;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Adds index hint to the FROM clause.
|
|
442
|
+
*/
|
|
443
|
+
indexHint(sql) {
|
|
444
|
+
this.ensureNotFinalized();
|
|
445
|
+
this._indexHint = sql;
|
|
446
|
+
return this;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Prepend comment to the sql query using the syntax `/* ... *‍/`. Some characters are forbidden such as `/*, *‍/` and `?`.
|
|
450
|
+
*/
|
|
451
|
+
comment(comment) {
|
|
452
|
+
this.ensureNotFinalized();
|
|
453
|
+
this._comments.push(...core_1.Utils.asArray(comment));
|
|
454
|
+
return this;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Add hints to the query using comment-like syntax `/*+ ... *‍/`. MySQL and Oracle use this syntax for optimizer hints.
|
|
458
|
+
* Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints
|
|
459
|
+
* are ignored as simple comments.
|
|
460
|
+
*/
|
|
461
|
+
hintComment(comment) {
|
|
462
|
+
this.ensureNotFinalized();
|
|
463
|
+
this._hintComments.push(...core_1.Utils.asArray(comment));
|
|
464
|
+
return this;
|
|
465
|
+
}
|
|
466
|
+
from(target, aliasName) {
|
|
467
|
+
this.ensureNotFinalized();
|
|
468
|
+
if (target instanceof QueryBuilder) {
|
|
469
|
+
this.fromSubQuery(target, aliasName);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
const entityName = core_1.Utils.className(target);
|
|
473
|
+
if (aliasName &&
|
|
474
|
+
this._mainAlias &&
|
|
475
|
+
entityName !== this._mainAlias.aliasName) {
|
|
476
|
+
throw new Error(`Cannot override the alias to '${aliasName}' since a query already contains references to '${this._mainAlias.aliasName}'`);
|
|
477
|
+
}
|
|
478
|
+
this.fromEntityName(entityName, aliasName);
|
|
479
|
+
}
|
|
480
|
+
return this;
|
|
481
|
+
}
|
|
482
|
+
getKnexQuery(processVirtualEntity = true) {
|
|
483
|
+
if (this.#query) {
|
|
484
|
+
return this.#query.qb;
|
|
485
|
+
}
|
|
486
|
+
this.#query = {};
|
|
487
|
+
this.finalize();
|
|
488
|
+
const qb = this.getQueryBase(processVirtualEntity);
|
|
489
|
+
const type = this.type ?? enums_1.QueryType.SELECT;
|
|
490
|
+
qb.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
491
|
+
core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(type, this._cond, qb), this._cond && !this._onConflict);
|
|
492
|
+
core_1.Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this._groupBy, "groupBy")), this._groupBy);
|
|
493
|
+
core_1.Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(type, this._having, qb, undefined, "having"), this._having);
|
|
494
|
+
core_1.Utils.runIfNotEmpty(() => {
|
|
495
|
+
const queryOrder = this.helper.getQueryOrder(type, this._orderBy, this._populateMap);
|
|
496
|
+
if (queryOrder.length > 0) {
|
|
497
|
+
const sql = core_1.Utils.unique(queryOrder).join(", ");
|
|
498
|
+
qb.orderByRaw(sql);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
}, this._orderBy);
|
|
502
|
+
core_1.Utils.runIfNotEmpty(() => qb.limit(this._limit), this._limit != null);
|
|
503
|
+
core_1.Utils.runIfNotEmpty(() => qb.offset(this._offset), this._offset);
|
|
504
|
+
core_1.Utils.runIfNotEmpty(() => this._comments.forEach((comment) => qb.comment(comment)), this._comments);
|
|
505
|
+
core_1.Utils.runIfNotEmpty(() => this._hintComments.forEach((comment) => qb.hintComment(comment)), this._hintComments);
|
|
506
|
+
core_1.Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(type, this._onConflict, qb), this._onConflict);
|
|
507
|
+
if (this.type === enums_1.QueryType.TRUNCATE &&
|
|
508
|
+
this.platform.usesCascadeStatement()) {
|
|
509
|
+
return (this.#query.qb = this.knex.raw(qb.toSQL().toNative().sql + " cascade"));
|
|
510
|
+
}
|
|
511
|
+
if (this.lockMode) {
|
|
512
|
+
this.helper.getLockSQL(qb, this.lockMode, this.lockTables);
|
|
513
|
+
}
|
|
514
|
+
this.helper.finalize(type, qb, this.mainAlias.metadata, this._data, this._returning);
|
|
515
|
+
this.clearRawFragmentsCache();
|
|
516
|
+
return (this.#query.qb = qb);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* @internal
|
|
520
|
+
*/
|
|
521
|
+
clearRawFragmentsCache() {
|
|
522
|
+
this.rawFragments.forEach((key) => core_1.RawQueryFragment.remove(key));
|
|
523
|
+
this.rawFragments.clear();
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Returns the query with parameters as wildcards.
|
|
527
|
+
*/
|
|
528
|
+
getQuery() {
|
|
529
|
+
return this.toQuery().sql;
|
|
530
|
+
}
|
|
531
|
+
#query;
|
|
532
|
+
toQuery() {
|
|
533
|
+
if (this.#query?.sql) {
|
|
534
|
+
return {
|
|
535
|
+
sql: this.#query.sql,
|
|
536
|
+
_sql: this.#query._sql,
|
|
537
|
+
params: this.#query.params,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
const sql = this.getKnexQuery().toSQL();
|
|
541
|
+
const query = sql.toNative();
|
|
542
|
+
this.#query.sql = query.sql;
|
|
543
|
+
this.#query._sql = sql;
|
|
544
|
+
this.#query.params = query.bindings ?? [];
|
|
545
|
+
return {
|
|
546
|
+
sql: this.#query.sql,
|
|
547
|
+
_sql: this.#query._sql,
|
|
548
|
+
params: this.#query.params,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Returns the list of all parameters for this query.
|
|
553
|
+
*/
|
|
554
|
+
getParams() {
|
|
555
|
+
return this.toQuery().params;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Returns raw interpolated query string with all the parameters inlined.
|
|
559
|
+
*/
|
|
560
|
+
getFormattedQuery() {
|
|
561
|
+
const query = this.toQuery()._sql;
|
|
562
|
+
return this.platform.formatQuery(query.sql, query.bindings);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* @internal
|
|
566
|
+
*/
|
|
567
|
+
getAliasForJoinPath(path, options) {
|
|
568
|
+
if (!path || path === this.mainAlias.entityName) {
|
|
569
|
+
return this.mainAlias.aliasName;
|
|
570
|
+
}
|
|
571
|
+
const join = typeof path === "string" ? this.getJoinForPath(path, options) : path;
|
|
572
|
+
if (join?.path?.endsWith("[pivot]")) {
|
|
573
|
+
return join.alias;
|
|
574
|
+
}
|
|
575
|
+
return join?.inverseAlias || join?.alias;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* @internal
|
|
579
|
+
*/
|
|
580
|
+
getJoinForPath(path, options) {
|
|
581
|
+
const joins = Object.values(this._joins);
|
|
582
|
+
if (joins.length === 0) {
|
|
583
|
+
return undefined;
|
|
584
|
+
}
|
|
585
|
+
let join = joins.find((j) => j.path === path);
|
|
586
|
+
if (options?.preferNoBranch) {
|
|
587
|
+
join = joins.find((j) => {
|
|
588
|
+
return (j.path?.replace(/\[\d+]|\[populate]/g, "") ===
|
|
589
|
+
path.replace(/\[\d+]|\[populate]/g, ""));
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
if (!join && options?.ignoreBranching) {
|
|
593
|
+
join = joins.find((j) => {
|
|
594
|
+
return j.path?.replace(/\[\d+]/g, "") === path.replace(/\[\d+]/g, "");
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
if (!join && options?.matchPopulateJoins && options?.ignoreBranching) {
|
|
598
|
+
join = joins.find((j) => {
|
|
599
|
+
return (j.path?.replace(/\[\d+]|\[populate]/g, "") ===
|
|
600
|
+
path.replace(/\[\d+]|\[populate]/g, ""));
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
if (!join && options?.matchPopulateJoins) {
|
|
604
|
+
join = joins.find((j) => {
|
|
605
|
+
return (j.path?.replace(/\[populate]/g, "") ===
|
|
606
|
+
path.replace(/\[populate]/g, ""));
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
return join;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* @internal
|
|
613
|
+
*/
|
|
614
|
+
getNextAlias(entityName = "e") {
|
|
615
|
+
return this.driver.config
|
|
616
|
+
.getNamingStrategy()
|
|
617
|
+
.aliasName(entityName, this.aliasCounter++);
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* @internal
|
|
621
|
+
*/
|
|
622
|
+
getAliasMap() {
|
|
623
|
+
return Object.fromEntries(Object.entries(this._aliases).map(([key, value]) => [key, value.entityName]));
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
|
|
627
|
+
* Use `method` to specify what kind of result you want to get (array/single/meta).
|
|
628
|
+
*/
|
|
629
|
+
async execute(method = "all", options) {
|
|
630
|
+
options =
|
|
631
|
+
typeof options === "boolean" ? { mapResults: options } : (options ?? {});
|
|
632
|
+
options.mergeResults ??= true;
|
|
633
|
+
options.mapResults ??= true;
|
|
634
|
+
if (!this.connectionType &&
|
|
635
|
+
method !== "run" &&
|
|
636
|
+
[
|
|
637
|
+
enums_1.QueryType.INSERT,
|
|
638
|
+
enums_1.QueryType.UPDATE,
|
|
639
|
+
enums_1.QueryType.DELETE,
|
|
640
|
+
enums_1.QueryType.TRUNCATE,
|
|
641
|
+
].includes(this.type ?? enums_1.QueryType.SELECT)) {
|
|
642
|
+
this.connectionType = "write";
|
|
643
|
+
}
|
|
644
|
+
const query = this.toQuery()._sql;
|
|
645
|
+
const cached = await this.em?.tryCache(this.mainAlias.entityName, this._cache, ["qb.execute", query.sql, query.bindings, method]);
|
|
646
|
+
if (cached?.data) {
|
|
647
|
+
return cached.data;
|
|
648
|
+
}
|
|
649
|
+
const write = method === "run" || !this.platform.getConfig().get("preferReadReplicas");
|
|
650
|
+
const type = this.connectionType || (write ? "write" : "read");
|
|
651
|
+
const res = await this.driver
|
|
652
|
+
.getConnection(type)
|
|
653
|
+
.execute(query.sql, query.bindings, method, this.context, this.loggerContext);
|
|
654
|
+
const meta = this.mainAlias.metadata;
|
|
655
|
+
if (!options.mapResults || !meta) {
|
|
656
|
+
await this.em?.storeCache(this._cache, cached, res);
|
|
657
|
+
return res;
|
|
658
|
+
}
|
|
659
|
+
if (method === "run") {
|
|
660
|
+
return res;
|
|
661
|
+
}
|
|
662
|
+
const joinedProps = this.driver.joinedProps(meta, this._populate);
|
|
663
|
+
let mapped;
|
|
664
|
+
if (Array.isArray(res)) {
|
|
665
|
+
const map = {};
|
|
666
|
+
mapped = res.map((r) => this.driver.mapResult(r, meta, this._populate, this, map));
|
|
667
|
+
if (options.mergeResults && joinedProps.length > 0) {
|
|
668
|
+
mapped = this.driver.mergeJoinedResult(mapped, this.mainAlias.metadata, joinedProps);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
mapped = [this.driver.mapResult(res, meta, joinedProps, this)];
|
|
673
|
+
}
|
|
674
|
+
await this.em?.storeCache(this._cache, cached, mapped);
|
|
675
|
+
if (method === "get") {
|
|
676
|
+
return mapped[0];
|
|
677
|
+
}
|
|
678
|
+
return mapped;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Alias for `qb.getResultList()`
|
|
682
|
+
*/
|
|
683
|
+
async getResult() {
|
|
684
|
+
return this.getResultList();
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Executes the query, returning array of results
|
|
688
|
+
*/
|
|
689
|
+
async getResultList(limit) {
|
|
690
|
+
await this.em.tryFlush(this.mainAlias.entityName, {
|
|
691
|
+
flushMode: this.flushMode,
|
|
692
|
+
});
|
|
693
|
+
const res = await this.execute("all", true);
|
|
694
|
+
const entities = [];
|
|
695
|
+
function propagatePopulateHint(entity, hint) {
|
|
696
|
+
(0, core_1.helper)(entity).__serializationContext.populate ??= hint;
|
|
697
|
+
hint.forEach((hint) => {
|
|
698
|
+
const [propName] = hint.field.split(":", 2);
|
|
699
|
+
const value = entity[propName];
|
|
700
|
+
if (core_1.Utils.isEntity(value, true)) {
|
|
701
|
+
(0, core_1.helper)(value).populated();
|
|
702
|
+
propagatePopulateHint(value, hint.children ?? []);
|
|
703
|
+
}
|
|
704
|
+
else if (core_1.Utils.isCollection(value)) {
|
|
705
|
+
value.populated();
|
|
706
|
+
value
|
|
707
|
+
.getItems(false)
|
|
708
|
+
.forEach((item) => propagatePopulateHint(item, hint.children ?? []));
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
for (const r of res) {
|
|
713
|
+
const entity = this.em.map(this.mainAlias.entityName, r, {
|
|
714
|
+
schema: this._schema,
|
|
715
|
+
});
|
|
716
|
+
propagatePopulateHint(entity, this._populate);
|
|
717
|
+
entities.push(entity);
|
|
718
|
+
if (limit != null && --limit === 0) {
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return entities;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Executes the query, returning the first result or null
|
|
726
|
+
*/
|
|
727
|
+
async getSingleResult() {
|
|
728
|
+
const [res] = await this.getResultList(1);
|
|
729
|
+
return res || null;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Executes count query (without offset and limit), returning total count of results
|
|
733
|
+
*/
|
|
734
|
+
async getCount(field, distinct) {
|
|
735
|
+
let res;
|
|
736
|
+
if (this.type === enums_1.QueryType.COUNT) {
|
|
737
|
+
res = await this.execute("get", false);
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
const qb = this.type === undefined ? this : this.clone();
|
|
741
|
+
qb.count(field, distinct ?? qb.hasToManyJoins())
|
|
742
|
+
.limit(undefined)
|
|
743
|
+
.offset(undefined)
|
|
744
|
+
.orderBy([]);
|
|
745
|
+
res = await qb.execute("get", false);
|
|
746
|
+
}
|
|
747
|
+
return res ? +res.count : 0;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Executes the query, returning both array of results and total count query (without offset and limit).
|
|
751
|
+
*/
|
|
752
|
+
async getResultAndCount() {
|
|
753
|
+
return [await this.getResultList(), await this.getCount()];
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Provides promise-like interface so we can await the QB instance.
|
|
757
|
+
*/
|
|
758
|
+
then(onfulfilled, onrejected) {
|
|
759
|
+
let type = this.type ?? enums_1.QueryType.SELECT;
|
|
760
|
+
if (this.flags.has(core_1.QueryFlag.UPDATE_SUB_QUERY) ||
|
|
761
|
+
this.flags.has(core_1.QueryFlag.DELETE_SUB_QUERY)) {
|
|
762
|
+
type = enums_1.QueryType.UPDATE;
|
|
763
|
+
}
|
|
764
|
+
switch (type) {
|
|
765
|
+
case enums_1.QueryType.INSERT:
|
|
766
|
+
case enums_1.QueryType.UPDATE:
|
|
767
|
+
case enums_1.QueryType.DELETE:
|
|
768
|
+
case enums_1.QueryType.TRUNCATE:
|
|
769
|
+
return this.execute("run").then(onfulfilled, onrejected);
|
|
770
|
+
case enums_1.QueryType.COUNT:
|
|
771
|
+
return this.getCount().then(onfulfilled, onrejected);
|
|
772
|
+
case enums_1.QueryType.SELECT:
|
|
773
|
+
return this.getResultList().then(onfulfilled, onrejected);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Returns knex instance with sub-query aliased with given alias.
|
|
778
|
+
* You can provide `EntityName.propName` as alias, then the field name will be used based on the metadata
|
|
779
|
+
*/
|
|
780
|
+
as(alias) {
|
|
781
|
+
const qb = this.getKnexQuery();
|
|
782
|
+
if (alias.includes(".")) {
|
|
783
|
+
const [a, f] = alias.split(".");
|
|
784
|
+
const meta = this.metadata.find(a);
|
|
785
|
+
/* istanbul ignore next */
|
|
786
|
+
alias = meta?.properties[f]?.fieldNames[0] ?? alias;
|
|
787
|
+
}
|
|
788
|
+
const ret = qb.as(alias);
|
|
789
|
+
// tag the instance, so it is possible to detect it easily
|
|
790
|
+
Object.defineProperty(ret, "__as", { enumerable: false, value: alias });
|
|
791
|
+
return ret;
|
|
792
|
+
}
|
|
793
|
+
clone(reset) {
|
|
794
|
+
const qb = new QueryBuilder(this.mainAlias.entityName, this.metadata, this.driver, this.context, this.mainAlias.aliasName, this.connectionType, this.em);
|
|
795
|
+
if (reset === true) {
|
|
796
|
+
return qb;
|
|
797
|
+
}
|
|
798
|
+
reset = reset || [];
|
|
799
|
+
// clone array/object properties
|
|
800
|
+
const properties = [
|
|
801
|
+
"flags",
|
|
802
|
+
"_populate",
|
|
803
|
+
"_populateWhere",
|
|
804
|
+
"__populateWhere",
|
|
805
|
+
"_populateMap",
|
|
806
|
+
"_joins",
|
|
807
|
+
"_joinedProps",
|
|
808
|
+
"_cond",
|
|
809
|
+
"_data",
|
|
810
|
+
"_orderBy",
|
|
811
|
+
"_schema",
|
|
812
|
+
"_indexHint",
|
|
813
|
+
"_cache",
|
|
814
|
+
"subQueries",
|
|
815
|
+
"lockMode",
|
|
816
|
+
"lockTables",
|
|
817
|
+
"_groupBy",
|
|
818
|
+
"_having",
|
|
819
|
+
"_returning",
|
|
820
|
+
"_comments",
|
|
821
|
+
"_hintComments",
|
|
822
|
+
"rawFragments",
|
|
823
|
+
];
|
|
824
|
+
core_1.RawQueryFragment.cloneRegistry = this.rawFragments;
|
|
825
|
+
for (const prop of Object.keys(this)) {
|
|
826
|
+
if (reset.includes(prop)) {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
qb[prop] = properties.includes(prop)
|
|
830
|
+
? core_1.Utils.copy(this[prop])
|
|
831
|
+
: this[prop];
|
|
832
|
+
}
|
|
833
|
+
delete core_1.RawQueryFragment.cloneRegistry;
|
|
834
|
+
/* istanbul ignore else */
|
|
835
|
+
if (this._fields && !reset.includes("_fields")) {
|
|
836
|
+
qb._fields = [...this._fields];
|
|
837
|
+
}
|
|
838
|
+
qb._aliases = { ...this._aliases };
|
|
839
|
+
qb.finalized = false;
|
|
840
|
+
return qb;
|
|
841
|
+
}
|
|
842
|
+
getKnex(processVirtualEntity = true) {
|
|
843
|
+
const qb = this.knex.queryBuilder();
|
|
844
|
+
const { subQuery, aliasName, entityName, metadata } = this.mainAlias;
|
|
845
|
+
const ref = subQuery
|
|
846
|
+
? subQuery
|
|
847
|
+
: this.knex.ref(this.helper.getTableName(entityName));
|
|
848
|
+
if (this.finalized &&
|
|
849
|
+
(this._explicitAlias || this.helper.isTableNameAliasRequired(this.type))) {
|
|
850
|
+
ref.as(aliasName);
|
|
851
|
+
}
|
|
852
|
+
const schema = this.getSchema(this.mainAlias);
|
|
853
|
+
if (schema) {
|
|
854
|
+
ref.withSchema(schema);
|
|
855
|
+
}
|
|
856
|
+
if (metadata?.virtual && processVirtualEntity) {
|
|
857
|
+
qb.fromRaw(this.fromVirtual(metadata));
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
qb.from(ref);
|
|
861
|
+
}
|
|
862
|
+
if (this.context) {
|
|
863
|
+
qb.transacting(this.context);
|
|
864
|
+
}
|
|
865
|
+
return qb;
|
|
866
|
+
}
|
|
867
|
+
fromVirtual(meta) {
|
|
868
|
+
if (typeof meta.expression === "string") {
|
|
869
|
+
return `(${meta.expression}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
870
|
+
}
|
|
871
|
+
const res = meta.expression(this.em, this._cond, {});
|
|
872
|
+
if (typeof res === "string") {
|
|
873
|
+
return `(${res}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
874
|
+
}
|
|
875
|
+
if (res instanceof QueryBuilder) {
|
|
876
|
+
return `(${res.getFormattedQuery()}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
877
|
+
}
|
|
878
|
+
if (core_1.Utils.isObject(res)) {
|
|
879
|
+
const { sql, bindings } = res.toSQL();
|
|
880
|
+
const query = this.platform.formatQuery(sql, bindings);
|
|
881
|
+
return `(${query}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
882
|
+
}
|
|
883
|
+
/* istanbul ignore next */
|
|
884
|
+
return res;
|
|
885
|
+
}
|
|
886
|
+
joinReference(field, alias, cond, type, path, schema, subquery) {
|
|
887
|
+
this.ensureNotFinalized();
|
|
888
|
+
if (typeof field === "object") {
|
|
889
|
+
const prop = {
|
|
890
|
+
name: "__subquery__",
|
|
891
|
+
kind: core_1.ReferenceKind.MANY_TO_ONE,
|
|
892
|
+
};
|
|
893
|
+
if (field instanceof QueryBuilder) {
|
|
894
|
+
prop.type = field.mainAlias.entityName;
|
|
895
|
+
prop.targetMeta = field.mainAlias.metadata;
|
|
896
|
+
field = field.getKnexQuery();
|
|
897
|
+
}
|
|
898
|
+
this._joins[`${this.alias}.${prop.name}#${alias}`] = {
|
|
899
|
+
prop,
|
|
900
|
+
alias,
|
|
901
|
+
type,
|
|
902
|
+
cond,
|
|
903
|
+
schema,
|
|
904
|
+
subquery: field.toString(),
|
|
905
|
+
ownerAlias: this.alias,
|
|
906
|
+
};
|
|
907
|
+
return prop;
|
|
908
|
+
}
|
|
909
|
+
if (!subquery && type.includes("lateral")) {
|
|
910
|
+
throw new Error(`Lateral join can be used only with a sub-query.`);
|
|
911
|
+
}
|
|
912
|
+
const [fromAlias, fromField] = this.helper.splitField(field);
|
|
913
|
+
const q = (str) => `'${str}'`;
|
|
914
|
+
if (!this._aliases[fromAlias]) {
|
|
915
|
+
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(", ")}.`);
|
|
916
|
+
}
|
|
917
|
+
const entityName = this._aliases[fromAlias].entityName;
|
|
918
|
+
const meta = this.metadata.get(entityName);
|
|
919
|
+
const prop = meta.properties[fromField];
|
|
920
|
+
if (!prop) {
|
|
921
|
+
throw new Error(`Trying to join ${q(field)}, but ${q(fromField)} is not a defined relation on ${meta.className}.`);
|
|
922
|
+
}
|
|
923
|
+
this.createAlias(prop.type, alias);
|
|
924
|
+
cond = core_1.QueryHelper.processWhere({
|
|
925
|
+
where: cond,
|
|
926
|
+
entityName: this.mainAlias.entityName,
|
|
927
|
+
metadata: this.metadata,
|
|
928
|
+
platform: this.platform,
|
|
929
|
+
aliasMap: this.getAliasMap(),
|
|
930
|
+
aliased: !this.type || [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(this.type),
|
|
931
|
+
});
|
|
932
|
+
let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
933
|
+
path ??= `${Object.values(this._joins).find((j) => j.alias === fromAlias)?.path ?? entityName}.${prop.name}`;
|
|
934
|
+
if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY) {
|
|
935
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
936
|
+
}
|
|
937
|
+
else if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY) {
|
|
938
|
+
let pivotAlias = alias;
|
|
939
|
+
if (type !== enums_1.JoinType.pivotJoin) {
|
|
940
|
+
const oldPivotAlias = this.getAliasForJoinPath(path + "[pivot]");
|
|
941
|
+
pivotAlias = oldPivotAlias ?? this.getNextAlias(prop.pivotEntity);
|
|
942
|
+
aliasedName = `${fromAlias}.${prop.name}#${pivotAlias}`;
|
|
943
|
+
}
|
|
944
|
+
const joins = this.helper.joinManyToManyReference(prop, fromAlias, alias, pivotAlias, type, cond, path, schema);
|
|
945
|
+
Object.assign(this._joins, joins);
|
|
946
|
+
this.createAlias(prop.pivotEntity, pivotAlias);
|
|
947
|
+
}
|
|
948
|
+
else if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE) {
|
|
949
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, fromAlias, alias, type, cond, schema);
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
// MANY_TO_ONE
|
|
953
|
+
this._joins[aliasedName] = this.helper.joinManyToOneReference(prop, fromAlias, alias, type, cond, schema);
|
|
954
|
+
}
|
|
955
|
+
if (!this._joins[aliasedName].path && path) {
|
|
956
|
+
this._joins[aliasedName].path = path;
|
|
957
|
+
}
|
|
958
|
+
return prop;
|
|
959
|
+
}
|
|
960
|
+
prepareFields(fields, type = "where") {
|
|
961
|
+
const ret = [];
|
|
962
|
+
const getFieldName = (name) => {
|
|
963
|
+
if (type === "groupBy") {
|
|
964
|
+
return this.helper.mapper(name, this.type, undefined, null);
|
|
965
|
+
}
|
|
966
|
+
return this.helper.mapper(name, this.type);
|
|
967
|
+
};
|
|
968
|
+
fields.forEach((field) => {
|
|
969
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(field);
|
|
970
|
+
if (rawField) {
|
|
971
|
+
const sql = this.platform.formatQuery(rawField.sql, rawField.params);
|
|
972
|
+
ret.push(this.knex.raw(sql));
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
if (!core_1.Utils.isString(field)) {
|
|
976
|
+
ret.push(field);
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const join = Object.keys(this._joins).find((k) => field === k.substring(0, k.indexOf("#")));
|
|
980
|
+
if (join && type === "where") {
|
|
981
|
+
ret.push(...this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[join]));
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
const [a, f] = this.helper.splitField(field);
|
|
985
|
+
const prop = this.helper.getProperty(f, a);
|
|
986
|
+
/* istanbul ignore next */
|
|
987
|
+
if (prop &&
|
|
988
|
+
[core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
if (prop?.embedded) {
|
|
992
|
+
const name = prop.embeddedPath?.join(".") ?? prop.fieldNames[0];
|
|
993
|
+
const aliased = this._aliases[a] ? `${a}.${name}` : name;
|
|
994
|
+
ret.push(getFieldName(aliased));
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
998
|
+
if (prop.object) {
|
|
999
|
+
ret.push(getFieldName(prop.fieldNames[0]));
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
const nest = (prop) => {
|
|
1003
|
+
for (const childProp of Object.values(prop.embeddedProps)) {
|
|
1004
|
+
if (childProp.fieldNames &&
|
|
1005
|
+
(childProp.kind !== core_1.ReferenceKind.EMBEDDED ||
|
|
1006
|
+
childProp.object) &&
|
|
1007
|
+
childProp.persist !== false) {
|
|
1008
|
+
ret.push(getFieldName(childProp.fieldNames[0]));
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
nest(childProp);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
nest(prop);
|
|
1016
|
+
}
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
if (prop && prop.fieldNames.length > 1) {
|
|
1020
|
+
ret.push(...prop.fieldNames.map((f) => getFieldName(f)));
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
ret.push(getFieldName(field));
|
|
1024
|
+
});
|
|
1025
|
+
const meta = this.mainAlias.metadata;
|
|
1026
|
+
/* istanbul ignore next */
|
|
1027
|
+
const requiresSQLConversion = meta?.props.filter((p) => p.hasConvertToJSValueSQL && p.persist !== false) ?? [];
|
|
1028
|
+
if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) &&
|
|
1029
|
+
(fields.includes("*") ||
|
|
1030
|
+
fields.includes(`${this.mainAlias.aliasName}.*`)) &&
|
|
1031
|
+
requiresSQLConversion.length > 0) {
|
|
1032
|
+
requiresSQLConversion.forEach((p) => ret.push(this.helper.mapper(p.name, this.type)));
|
|
1033
|
+
}
|
|
1034
|
+
Object.keys(this._populateMap).forEach((f) => {
|
|
1035
|
+
if (!fields.includes(f.replace(/#\w+$/, "")) && type === "where") {
|
|
1036
|
+
const cols = this.helper.mapJoinColumns(this.type ?? enums_1.QueryType.SELECT, this._joins[f]);
|
|
1037
|
+
ret.push(...cols);
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
return ret;
|
|
1041
|
+
}
|
|
1042
|
+
init(type, data, cond) {
|
|
1043
|
+
this.ensureNotFinalized();
|
|
1044
|
+
this.type = type;
|
|
1045
|
+
if ([enums_1.QueryType.UPDATE, enums_1.QueryType.DELETE].includes(type) &&
|
|
1046
|
+
core_1.Utils.hasObjectKeys(this._cond)) {
|
|
1047
|
+
throw new Error(`You are trying to call \`qb.where().${type.toLowerCase()}()\`. Calling \`qb.${type.toLowerCase()}()\` before \`qb.where()\` is required.`);
|
|
1048
|
+
}
|
|
1049
|
+
if (!this.helper.isTableNameAliasRequired(type)) {
|
|
1050
|
+
delete this._fields;
|
|
1051
|
+
}
|
|
1052
|
+
if (data) {
|
|
1053
|
+
if (core_1.Utils.isEntity(data)) {
|
|
1054
|
+
data =
|
|
1055
|
+
this.em?.getComparator().prepareEntity(data) ??
|
|
1056
|
+
(0, core_1.serialize)(data);
|
|
1057
|
+
}
|
|
1058
|
+
this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES), false);
|
|
1059
|
+
}
|
|
1060
|
+
if (cond) {
|
|
1061
|
+
this.where(cond);
|
|
1062
|
+
}
|
|
1063
|
+
return this;
|
|
1064
|
+
}
|
|
1065
|
+
getQueryBase(processVirtualEntity) {
|
|
1066
|
+
const qb = this.getKnex(processVirtualEntity);
|
|
1067
|
+
const schema = this.getSchema(this.mainAlias);
|
|
1068
|
+
// Joined tables doesn't need to belong to the same schema as the main table
|
|
1069
|
+
const joinSchema = this._schema ?? this.em?.schema ?? schema;
|
|
1070
|
+
if (schema) {
|
|
1071
|
+
qb.withSchema(schema);
|
|
1072
|
+
}
|
|
1073
|
+
if (this._indexHint) {
|
|
1074
|
+
const alias = this.helper.isTableNameAliasRequired(this.type)
|
|
1075
|
+
? ` as ${this.platform.quoteIdentifier(this.mainAlias.aliasName)}`
|
|
1076
|
+
: "";
|
|
1077
|
+
const schemaQuoted = schema
|
|
1078
|
+
? this.platform.quoteIdentifier(schema) + "."
|
|
1079
|
+
: "";
|
|
1080
|
+
const tableName = schemaQuoted +
|
|
1081
|
+
this.platform.quoteIdentifier(this.helper.getTableName(this.mainAlias.entityName)) +
|
|
1082
|
+
alias;
|
|
1083
|
+
qb.from(this.knex.raw(`${tableName} ${this._indexHint}`));
|
|
1084
|
+
}
|
|
1085
|
+
switch (this.type) {
|
|
1086
|
+
case enums_1.QueryType.SELECT:
|
|
1087
|
+
qb.select(this.prepareFields(this._fields));
|
|
1088
|
+
if (this._distinctOn) {
|
|
1089
|
+
qb.distinctOn(this._distinctOn);
|
|
1090
|
+
}
|
|
1091
|
+
else if (this.flags.has(core_1.QueryFlag.DISTINCT)) {
|
|
1092
|
+
qb.distinct();
|
|
1093
|
+
}
|
|
1094
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
1095
|
+
break;
|
|
1096
|
+
case enums_1.QueryType.COUNT: {
|
|
1097
|
+
const m = this.flags.has(core_1.QueryFlag.DISTINCT)
|
|
1098
|
+
? "countDistinct"
|
|
1099
|
+
: "count";
|
|
1100
|
+
qb[m]({
|
|
1101
|
+
count: this._fields.map((f) => this.helper.mapper(f, this.type)),
|
|
1102
|
+
});
|
|
1103
|
+
this.helper.processJoins(qb, this._joins, joinSchema);
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
case enums_1.QueryType.INSERT:
|
|
1107
|
+
qb.insert(this._data);
|
|
1108
|
+
break;
|
|
1109
|
+
case enums_1.QueryType.UPDATE:
|
|
1110
|
+
qb.update(this._data);
|
|
1111
|
+
this.helper.updateVersionProperty(qb, this._data);
|
|
1112
|
+
break;
|
|
1113
|
+
case enums_1.QueryType.DELETE:
|
|
1114
|
+
qb.delete();
|
|
1115
|
+
break;
|
|
1116
|
+
case enums_1.QueryType.TRUNCATE:
|
|
1117
|
+
qb.truncate();
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
return qb;
|
|
1121
|
+
}
|
|
1122
|
+
applyDiscriminatorCondition() {
|
|
1123
|
+
const meta = this.mainAlias.metadata;
|
|
1124
|
+
if (!meta?.discriminatorValue) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
const types = Object.values(meta.root.discriminatorMap).map((cls) => this.metadata.find(cls));
|
|
1128
|
+
const children = [];
|
|
1129
|
+
const lookUpChildren = (ret, type) => {
|
|
1130
|
+
const children = types.filter((meta2) => meta2.extends === type);
|
|
1131
|
+
children.forEach((m) => lookUpChildren(ret, m.className));
|
|
1132
|
+
ret.push(...children.filter((c) => c.discriminatorValue));
|
|
1133
|
+
return children;
|
|
1134
|
+
};
|
|
1135
|
+
lookUpChildren(children, meta.className);
|
|
1136
|
+
this.andWhere({
|
|
1137
|
+
[meta.root.discriminatorColumn]: children.length > 0
|
|
1138
|
+
? {
|
|
1139
|
+
$in: [
|
|
1140
|
+
meta.discriminatorValue,
|
|
1141
|
+
...children.map((c) => c.discriminatorValue),
|
|
1142
|
+
],
|
|
1143
|
+
}
|
|
1144
|
+
: meta.discriminatorValue,
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
finalize() {
|
|
1148
|
+
if (this.finalized) {
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (!this.type) {
|
|
1152
|
+
this.select("*");
|
|
1153
|
+
}
|
|
1154
|
+
const meta = this.mainAlias.metadata;
|
|
1155
|
+
this.applyDiscriminatorCondition();
|
|
1156
|
+
if (meta && this.flags.has(core_1.QueryFlag.AUTO_JOIN_ONE_TO_ONE_OWNER)) {
|
|
1157
|
+
const relationsToPopulate = this._populate.map(({ field }) => field);
|
|
1158
|
+
meta.relations
|
|
1159
|
+
.filter((prop) => prop.kind === core_1.ReferenceKind.ONE_TO_ONE &&
|
|
1160
|
+
!prop.owner &&
|
|
1161
|
+
!relationsToPopulate.includes(prop.name) &&
|
|
1162
|
+
!relationsToPopulate.includes(`${prop.name}:ref`))
|
|
1163
|
+
.map((prop) => ({ field: `${prop.name}:ref` }))
|
|
1164
|
+
.forEach((item) => this._populate.push(item));
|
|
1165
|
+
}
|
|
1166
|
+
this._populate.forEach(({ field }) => {
|
|
1167
|
+
const [fromAlias, fromField] = this.helper.splitField(field);
|
|
1168
|
+
const aliasedField = `${fromAlias}.${fromField}`;
|
|
1169
|
+
const join = Object.keys(this._joins).find((k) => `${aliasedField}#${this._joins[k].alias}` === k);
|
|
1170
|
+
if (join &&
|
|
1171
|
+
this._joins[join] &&
|
|
1172
|
+
this.helper.isOneToOneInverse(fromField)) {
|
|
1173
|
+
this._populateMap[join] = this._joins[join].alias;
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (meta && this.helper.isOneToOneInverse(fromField)) {
|
|
1177
|
+
const prop = meta.properties[fromField];
|
|
1178
|
+
const alias = this.getNextAlias(prop.pivotEntity ?? prop.type);
|
|
1179
|
+
const aliasedName = `${fromAlias}.${prop.name}#${alias}`;
|
|
1180
|
+
this._joins[aliasedName] = this.helper.joinOneToReference(prop, this.mainAlias.aliasName, alias, enums_1.JoinType.leftJoin);
|
|
1181
|
+
this._populateMap[aliasedName] = this._joins[aliasedName].alias;
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
if (meta &&
|
|
1185
|
+
(this._fields?.includes("*") ||
|
|
1186
|
+
this._fields?.includes(`${this.mainAlias.aliasName}.*`))) {
|
|
1187
|
+
meta.props
|
|
1188
|
+
.filter((prop) => prop.formula &&
|
|
1189
|
+
(!prop.lazy || this.flags.has(core_1.QueryFlag.INCLUDE_LAZY_FORMULAS)))
|
|
1190
|
+
.map((prop) => {
|
|
1191
|
+
const alias = this.knex.ref(this.mainAlias.aliasName).toString();
|
|
1192
|
+
const aliased = this.knex.ref(prop.fieldNames[0]).toString();
|
|
1193
|
+
return `${prop.formula(alias)} as ${aliased}`;
|
|
1194
|
+
})
|
|
1195
|
+
.filter((field) => !this._fields.some((f) => {
|
|
1196
|
+
if (f instanceof core_1.RawQueryFragment) {
|
|
1197
|
+
return f.sql === field && f.params.length === 0;
|
|
1198
|
+
}
|
|
1199
|
+
return f === field;
|
|
1200
|
+
}))
|
|
1201
|
+
.forEach((field) => this._fields.push((0, core_1.raw)(field)));
|
|
1202
|
+
}
|
|
1203
|
+
this.processPopulateWhere();
|
|
1204
|
+
core_1.QueryHelper.processObjectParams(this._data);
|
|
1205
|
+
core_1.QueryHelper.processObjectParams(this._cond);
|
|
1206
|
+
core_1.QueryHelper.processObjectParams(this._having);
|
|
1207
|
+
// automatically enable paginate flag when we detect to-many joins, but only if there is no `group by` clause
|
|
1208
|
+
if (!this.flags.has(core_1.QueryFlag.DISABLE_PAGINATE) &&
|
|
1209
|
+
this._groupBy.length === 0 &&
|
|
1210
|
+
this.hasToManyJoins()) {
|
|
1211
|
+
this.flags.add(core_1.QueryFlag.PAGINATE);
|
|
1212
|
+
}
|
|
1213
|
+
if (meta &&
|
|
1214
|
+
this.flags.has(core_1.QueryFlag.PAGINATE) &&
|
|
1215
|
+
(this._limit > 0 || this._offset > 0)) {
|
|
1216
|
+
this.wrapPaginateSubQuery(meta);
|
|
1217
|
+
}
|
|
1218
|
+
if (meta &&
|
|
1219
|
+
(this.flags.has(core_1.QueryFlag.UPDATE_SUB_QUERY) ||
|
|
1220
|
+
this.flags.has(core_1.QueryFlag.DELETE_SUB_QUERY))) {
|
|
1221
|
+
this.wrapModifySubQuery(meta);
|
|
1222
|
+
}
|
|
1223
|
+
this.finalized = true;
|
|
1224
|
+
}
|
|
1225
|
+
processPopulateWhere() {
|
|
1226
|
+
if (this._populateWhere == null ||
|
|
1227
|
+
this._populateWhere === core_1.PopulateHint.ALL) {
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
const joins = Object.values(this._joins);
|
|
1231
|
+
joins.forEach((join) => {
|
|
1232
|
+
join.cond_ = join.cond;
|
|
1233
|
+
join.cond = {};
|
|
1234
|
+
});
|
|
1235
|
+
const replaceOnConditions = (cond, op) => {
|
|
1236
|
+
Object.keys(cond).forEach((k) => {
|
|
1237
|
+
if (core_1.Utils.isOperator(k)) {
|
|
1238
|
+
if (Array.isArray(cond[k])) {
|
|
1239
|
+
return cond[k].forEach((c) => replaceOnConditions(c, k));
|
|
1240
|
+
}
|
|
1241
|
+
/* istanbul ignore next */
|
|
1242
|
+
return replaceOnConditions(cond[k], k);
|
|
1243
|
+
}
|
|
1244
|
+
const [alias] = this.helper.splitField(k);
|
|
1245
|
+
const join = joins.find((j) => j.alias === alias);
|
|
1246
|
+
if (join) {
|
|
1247
|
+
if (join.cond[k]) {
|
|
1248
|
+
join.cond = { [op ?? "$and"]: [join.cond, { [k]: cond[k] }] };
|
|
1249
|
+
}
|
|
1250
|
+
else {
|
|
1251
|
+
join.cond = { ...join.cond, [k]: cond[k] };
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
};
|
|
1256
|
+
if (typeof this._populateWhere === "object") {
|
|
1257
|
+
const cond = CriteriaNodeFactory_1.CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, this._populateWhere).process(this, {
|
|
1258
|
+
matchPopulateJoins: true,
|
|
1259
|
+
ignoreBranching: true,
|
|
1260
|
+
preferNoBranch: true,
|
|
1261
|
+
});
|
|
1262
|
+
replaceOnConditions(cond);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
hasToManyJoins() {
|
|
1266
|
+
return Object.values(this._joins).some((join) => {
|
|
1267
|
+
return [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(join.prop.kind);
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
wrapPaginateSubQuery(meta) {
|
|
1271
|
+
const pks = this.prepareFields(meta.primaryKeys, "sub-query");
|
|
1272
|
+
const subQuery = this.clone(["_orderBy", "_fields"])
|
|
1273
|
+
.select(pks)
|
|
1274
|
+
.groupBy(pks)
|
|
1275
|
+
.limit(this._limit);
|
|
1276
|
+
// revert the on conditions added via populateWhere, we want to apply those only once
|
|
1277
|
+
Object.values(subQuery._joins).forEach((join) => (join.cond = join.cond_ ?? {}));
|
|
1278
|
+
if (this._offset) {
|
|
1279
|
+
subQuery.offset(this._offset);
|
|
1280
|
+
}
|
|
1281
|
+
const addToSelect = [];
|
|
1282
|
+
if (this._orderBy.length > 0) {
|
|
1283
|
+
const orderBy = [];
|
|
1284
|
+
for (const orderMap of this._orderBy) {
|
|
1285
|
+
for (const [field, direction] of Object.entries(orderMap)) {
|
|
1286
|
+
if (core_1.RawQueryFragment.isKnownFragment(field)) {
|
|
1287
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(field, false);
|
|
1288
|
+
this.rawFragments.add(field);
|
|
1289
|
+
orderBy.push({ [rawField.clone()]: direction });
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
const [a, f] = this.helper.splitField(field);
|
|
1293
|
+
const prop = this.helper.getProperty(f, a);
|
|
1294
|
+
const type = this.platform.castColumn(prop);
|
|
1295
|
+
const fieldName = this.helper.mapper(field, this.type, undefined, null);
|
|
1296
|
+
if (!prop?.persist && !prop?.formula) {
|
|
1297
|
+
addToSelect.push(fieldName);
|
|
1298
|
+
}
|
|
1299
|
+
const key = (0, core_1.raw)(`min(${this.knex.ref(fieldName)}${type})`);
|
|
1300
|
+
orderBy.push({ [key]: direction });
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
subQuery.orderBy(orderBy);
|
|
1304
|
+
}
|
|
1305
|
+
subQuery.finalized = true;
|
|
1306
|
+
const knexQuery = subQuery
|
|
1307
|
+
.as(this.mainAlias.aliasName)
|
|
1308
|
+
.clearSelect()
|
|
1309
|
+
.select(pks);
|
|
1310
|
+
if (addToSelect.length > 0) {
|
|
1311
|
+
addToSelect.forEach((prop) => {
|
|
1312
|
+
const field = this._fields.find((field) => {
|
|
1313
|
+
if (typeof field === "object" && field && "__as" in field) {
|
|
1314
|
+
return field.__as === prop;
|
|
1315
|
+
}
|
|
1316
|
+
if (field instanceof core_1.RawQueryFragment) {
|
|
1317
|
+
// not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
|
|
1318
|
+
return field.sql.includes(prop);
|
|
1319
|
+
}
|
|
1320
|
+
// not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
|
|
1321
|
+
return field.toString().includes(prop);
|
|
1322
|
+
});
|
|
1323
|
+
if (field instanceof core_1.RawQueryFragment) {
|
|
1324
|
+
knexQuery.select(this.platform.formatQuery(field.sql, field.params));
|
|
1325
|
+
}
|
|
1326
|
+
else if (field) {
|
|
1327
|
+
knexQuery.select(field);
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
// multiple sub-queries are needed to get around mysql limitations with order by + limit + where in + group by (o.O)
|
|
1332
|
+
// https://stackoverflow.com/questions/17892762/mysql-this-version-of-mysql-doesnt-yet-support-limit-in-all-any-some-subqu
|
|
1333
|
+
const subSubQuery = this.getKnex().select(pks).from(knexQuery);
|
|
1334
|
+
subSubQuery.__raw = true; // tag it as there is now way to check via `instanceof`
|
|
1335
|
+
this._limit = undefined;
|
|
1336
|
+
this._offset = undefined;
|
|
1337
|
+
this.select(this._fields).where({
|
|
1338
|
+
[core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery },
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
wrapModifySubQuery(meta) {
|
|
1342
|
+
const subQuery = this.clone();
|
|
1343
|
+
subQuery.finalized = true;
|
|
1344
|
+
// wrap one more time to get around MySQL limitations
|
|
1345
|
+
// https://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause
|
|
1346
|
+
const subSubQuery = this.getKnex()
|
|
1347
|
+
.select(this.prepareFields(meta.primaryKeys))
|
|
1348
|
+
.from(subQuery.as(this.mainAlias.aliasName));
|
|
1349
|
+
const method = this.flags.has(core_1.QueryFlag.UPDATE_SUB_QUERY)
|
|
1350
|
+
? "update"
|
|
1351
|
+
: "delete";
|
|
1352
|
+
this._cond = {}; // otherwise we would trigger validation error
|
|
1353
|
+
this[method](this._data).where({
|
|
1354
|
+
[core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery },
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
getSchema(alias) {
|
|
1358
|
+
const { metadata } = alias;
|
|
1359
|
+
const metaSchema = metadata?.schema && metadata.schema !== "*" ? metadata.schema : undefined;
|
|
1360
|
+
return (this._schema ??
|
|
1361
|
+
metaSchema ??
|
|
1362
|
+
this.em?.schema ??
|
|
1363
|
+
this.em?.config.get("schema"));
|
|
1364
|
+
}
|
|
1365
|
+
createAlias(entityName, aliasName, subQuery) {
|
|
1366
|
+
const metadata = this.metadata.find(entityName);
|
|
1367
|
+
const alias = { aliasName, entityName, metadata, subQuery };
|
|
1368
|
+
this._aliases[aliasName] = alias;
|
|
1369
|
+
return alias;
|
|
1370
|
+
}
|
|
1371
|
+
createMainAlias(entityName, aliasName, subQuery) {
|
|
1372
|
+
this._mainAlias = this.createAlias(entityName, aliasName, subQuery);
|
|
1373
|
+
this._helper = this.createQueryBuilderHelper();
|
|
1374
|
+
return this._mainAlias;
|
|
1375
|
+
}
|
|
1376
|
+
fromSubQuery(target, aliasName) {
|
|
1377
|
+
const subQuery = target.getKnexQuery();
|
|
1378
|
+
const { entityName } = target.mainAlias;
|
|
1379
|
+
aliasName ??= this.getNextAlias(entityName);
|
|
1380
|
+
this.createMainAlias(entityName, aliasName, subQuery);
|
|
1381
|
+
}
|
|
1382
|
+
fromEntityName(entityName, aliasName) {
|
|
1383
|
+
aliasName ??= this._mainAlias?.aliasName ?? this.getNextAlias(entityName);
|
|
1384
|
+
this.createMainAlias(entityName, aliasName);
|
|
1385
|
+
}
|
|
1386
|
+
createQueryBuilderHelper() {
|
|
1387
|
+
return new QueryBuilderHelper_1.QueryBuilderHelper(this.mainAlias.entityName, this.mainAlias.aliasName, this._aliases, this.subQueries, this.knex, this.driver);
|
|
1388
|
+
}
|
|
1389
|
+
ensureFromClause() {
|
|
1390
|
+
/* istanbul ignore next */
|
|
1391
|
+
if (!this._mainAlias) {
|
|
1392
|
+
throw new Error(`Cannot proceed to build a query because the main alias is not set.`);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
ensureNotFinalized() {
|
|
1396
|
+
if (this.finalized) {
|
|
1397
|
+
throw new Error("This QueryBuilder instance is already finalized, clone it first if you want to modify it.");
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
/* istanbul ignore next */
|
|
1401
|
+
/** @ignore */
|
|
1402
|
+
[util_1.inspect.custom](depth) {
|
|
1403
|
+
const object = { ...this };
|
|
1404
|
+
const hidden = [
|
|
1405
|
+
"metadata",
|
|
1406
|
+
"driver",
|
|
1407
|
+
"context",
|
|
1408
|
+
"platform",
|
|
1409
|
+
"knex",
|
|
1410
|
+
"type",
|
|
1411
|
+
];
|
|
1412
|
+
Object.keys(object)
|
|
1413
|
+
.filter((k) => k.startsWith("_"))
|
|
1414
|
+
.forEach((k) => delete object[k]);
|
|
1415
|
+
Object.keys(object)
|
|
1416
|
+
.filter((k) => object[k] == null)
|
|
1417
|
+
.forEach((k) => delete object[k]);
|
|
1418
|
+
hidden.forEach((k) => delete object[k]);
|
|
1419
|
+
let prefix = this.type
|
|
1420
|
+
? this.type.substring(0, 1) + this.type.toLowerCase().substring(1)
|
|
1421
|
+
: "";
|
|
1422
|
+
if (this._data) {
|
|
1423
|
+
object.data = this._data;
|
|
1424
|
+
}
|
|
1425
|
+
if (this._schema) {
|
|
1426
|
+
object.schema = this._schema;
|
|
1427
|
+
}
|
|
1428
|
+
if (!core_1.Utils.isEmpty(this._cond)) {
|
|
1429
|
+
object.where = this._cond;
|
|
1430
|
+
}
|
|
1431
|
+
if (this._onConflict?.[0]) {
|
|
1432
|
+
prefix = "Upsert";
|
|
1433
|
+
object.onConflict = this._onConflict[0];
|
|
1434
|
+
}
|
|
1435
|
+
if (!core_1.Utils.isEmpty(this._orderBy)) {
|
|
1436
|
+
object.orderBy = this._orderBy;
|
|
1437
|
+
}
|
|
1438
|
+
const name = this._mainAlias
|
|
1439
|
+
? `${prefix}QueryBuilder<${this._mainAlias?.entityName}>`
|
|
1440
|
+
: "QueryBuilder";
|
|
1441
|
+
const ret = (0, util_1.inspect)(object, { depth });
|
|
1442
|
+
return ret === "[Object]" ? `[${name}]` : name + " " + ret;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
exports.QueryBuilder = QueryBuilder;
|