@mikro-orm/sql 7.0.0-dev.215 → 7.0.0-dev.217
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/AbstractSqlDriver.d.ts +9 -9
- package/AbstractSqlDriver.js +61 -57
- package/AbstractSqlPlatform.d.ts +3 -3
- package/SqlEntityManager.d.ts +1 -1
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +2 -2
- package/package.json +2 -2
- package/query/CriteriaNode.d.ts +1 -0
- package/query/CriteriaNode.js +2 -0
- package/query/CriteriaNodeFactory.d.ts +5 -5
- package/query/CriteriaNodeFactory.js +17 -17
- package/query/NativeQueryBuilder.d.ts +3 -2
- package/query/ObjectCriteriaNode.js +5 -0
- package/query/QueryBuilder.d.ts +477 -64
- package/query/QueryBuilder.js +225 -29
- package/query/QueryBuilderHelper.d.ts +7 -7
- package/query/QueryBuilderHelper.js +3 -10
- package/query/raw.d.ts +11 -3
- package/query/raw.js +1 -2
- package/tsconfig.build.tsbuildinfo +1 -1
- package/typings.d.ts +17 -19
package/query/QueryBuilder.js
CHANGED
|
@@ -98,12 +98,20 @@ export class QueryBuilder {
|
|
|
98
98
|
}
|
|
99
99
|
select(fields, distinct = false) {
|
|
100
100
|
this.ensureNotFinalized();
|
|
101
|
-
this._fields = Utils.asArray(fields)
|
|
101
|
+
this._fields = Utils.asArray(fields).flatMap(f => {
|
|
102
|
+
if (typeof f !== 'string') {
|
|
103
|
+
return f;
|
|
104
|
+
}
|
|
105
|
+
return this.resolveNestedPath(f);
|
|
106
|
+
});
|
|
102
107
|
if (distinct) {
|
|
103
108
|
this.flags.add(QueryFlag.DISTINCT);
|
|
104
109
|
}
|
|
105
110
|
return this.init(QueryType.SELECT);
|
|
106
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Adds fields to an existing SELECT query.
|
|
114
|
+
*/
|
|
107
115
|
addSelect(fields) {
|
|
108
116
|
this.ensureNotFinalized();
|
|
109
117
|
if (this._type && this._type !== QueryType.SELECT) {
|
|
@@ -115,24 +123,81 @@ export class QueryBuilder {
|
|
|
115
123
|
this.ensureNotFinalized();
|
|
116
124
|
return this.setFlag(QueryFlag.DISTINCT);
|
|
117
125
|
}
|
|
118
|
-
/** postgres only */
|
|
119
126
|
distinctOn(fields) {
|
|
120
127
|
this.ensureNotFinalized();
|
|
121
128
|
this._distinctOn = Utils.asArray(fields);
|
|
122
129
|
return this;
|
|
123
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Creates an INSERT query with the given data.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* await em.createQueryBuilder(User)
|
|
137
|
+
* .insert({ name: 'John', email: 'john@example.com' })
|
|
138
|
+
* .execute();
|
|
139
|
+
*
|
|
140
|
+
* // Bulk insert
|
|
141
|
+
* await em.createQueryBuilder(User)
|
|
142
|
+
* .insert([{ name: 'John' }, { name: 'Jane' }])
|
|
143
|
+
* .execute();
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
124
146
|
insert(data) {
|
|
125
147
|
return this.init(QueryType.INSERT, data);
|
|
126
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Creates an UPDATE query with the given data.
|
|
151
|
+
* Use `where()` to specify which rows to update.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* await em.createQueryBuilder(User)
|
|
156
|
+
* .update({ name: 'John Doe' })
|
|
157
|
+
* .where({ id: 1 })
|
|
158
|
+
* .execute();
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
127
161
|
update(data) {
|
|
128
162
|
return this.init(QueryType.UPDATE, data);
|
|
129
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Creates a DELETE query.
|
|
166
|
+
* Use `where()` to specify which rows to delete.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```ts
|
|
170
|
+
* await em.createQueryBuilder(User)
|
|
171
|
+
* .delete()
|
|
172
|
+
* .where({ id: 1 })
|
|
173
|
+
* .execute();
|
|
174
|
+
*
|
|
175
|
+
* // Or pass the condition directly
|
|
176
|
+
* await em.createQueryBuilder(User)
|
|
177
|
+
* .delete({ isActive: false })
|
|
178
|
+
* .execute();
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
130
181
|
delete(cond) {
|
|
131
182
|
return this.init(QueryType.DELETE, undefined, cond);
|
|
132
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Creates a TRUNCATE query to remove all rows from the table.
|
|
186
|
+
*/
|
|
133
187
|
truncate() {
|
|
134
188
|
return this.init(QueryType.TRUNCATE);
|
|
135
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Creates a COUNT query to count matching rows.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* const count = await em.createQueryBuilder(User)
|
|
196
|
+
* .count()
|
|
197
|
+
* .where({ isActive: true })
|
|
198
|
+
* .execute('get');
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
136
201
|
count(field, distinct = false) {
|
|
137
202
|
if (field) {
|
|
138
203
|
this._fields = Utils.asArray(field);
|
|
@@ -156,16 +221,27 @@ export class QueryBuilder {
|
|
|
156
221
|
this.join(field, alias, cond, JoinType.innerJoin, undefined, schema);
|
|
157
222
|
return this;
|
|
158
223
|
}
|
|
159
|
-
innerJoinLateral(field, alias, cond, schema) {
|
|
160
|
-
this.join(field, alias, cond, JoinType.innerJoinLateral, undefined, schema);
|
|
161
|
-
return this;
|
|
224
|
+
innerJoinLateral(field, alias, cond = {}, schema) {
|
|
225
|
+
return this.join(field, alias, cond, JoinType.innerJoinLateral, undefined, schema);
|
|
162
226
|
}
|
|
163
227
|
leftJoin(field, alias, cond = {}, schema) {
|
|
164
228
|
return this.join(field, alias, cond, JoinType.leftJoin, undefined, schema);
|
|
165
229
|
}
|
|
166
|
-
leftJoinLateral(field, alias, cond, schema) {
|
|
230
|
+
leftJoinLateral(field, alias, cond = {}, schema) {
|
|
167
231
|
return this.join(field, alias, cond, JoinType.leftJoinLateral, undefined, schema);
|
|
168
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Adds a JOIN clause and automatically selects the joined entity's fields.
|
|
235
|
+
* This is useful for eager loading related entities.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```ts
|
|
239
|
+
* const qb = em.createQueryBuilder(Book, 'b');
|
|
240
|
+
* qb.select('*')
|
|
241
|
+
* .joinAndSelect('b.author', 'a')
|
|
242
|
+
* .where({ 'a.name': 'John' });
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
169
245
|
joinAndSelect(field, alias, cond = {}, type = JoinType.innerJoin, path, fields, schema) {
|
|
170
246
|
if (!this._type) {
|
|
171
247
|
this.select('*');
|
|
@@ -197,13 +273,15 @@ export class QueryBuilder {
|
|
|
197
273
|
return this.joinAndSelect(field, alias, cond, JoinType.leftJoin, undefined, fields, schema);
|
|
198
274
|
}
|
|
199
275
|
leftJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
|
|
200
|
-
|
|
276
|
+
this.joinAndSelect(field, alias, cond, JoinType.leftJoinLateral, undefined, fields, schema);
|
|
277
|
+
return this;
|
|
201
278
|
}
|
|
202
279
|
innerJoinAndSelect(field, alias, cond = {}, fields, schema) {
|
|
203
280
|
return this.joinAndSelect(field, alias, cond, JoinType.innerJoin, undefined, fields, schema);
|
|
204
281
|
}
|
|
205
282
|
innerJoinLateralAndSelect(field, alias, cond = {}, fields, schema) {
|
|
206
|
-
|
|
283
|
+
this.joinAndSelect(field, alias, cond, JoinType.innerJoinLateral, undefined, fields, schema);
|
|
284
|
+
return this;
|
|
207
285
|
}
|
|
208
286
|
getFieldsForJoinedLoad(prop, alias, explicitFields) {
|
|
209
287
|
const fields = [];
|
|
@@ -278,7 +356,13 @@ export class QueryBuilder {
|
|
|
278
356
|
filterOptions = QueryHelper.mergePropertyFilters(join.prop.filters, filterOptions);
|
|
279
357
|
let cond = await em.applyFilters(join.prop.targetMeta.class, join.cond, filterOptions, 'read');
|
|
280
358
|
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, join.prop.targetMeta.class, cond);
|
|
281
|
-
cond = criteriaNode.process(this, {
|
|
359
|
+
cond = criteriaNode.process(this, {
|
|
360
|
+
matchPopulateJoins: true,
|
|
361
|
+
filter: true,
|
|
362
|
+
alias: join.alias,
|
|
363
|
+
ignoreBranching: true,
|
|
364
|
+
parentPath: join.path,
|
|
365
|
+
});
|
|
282
366
|
if (Utils.hasObjectKeys(cond) || RawQueryFragment.hasObjectFragments(cond)) {
|
|
283
367
|
// remove nested filters, we only care about scalars here, nesting would require another join branch
|
|
284
368
|
for (const key of Object.keys(cond)) {
|
|
@@ -308,17 +392,18 @@ export class QueryBuilder {
|
|
|
308
392
|
}
|
|
309
393
|
where(cond, params, operator) {
|
|
310
394
|
this.ensureNotFinalized();
|
|
395
|
+
let processedCond;
|
|
311
396
|
if (isRaw(cond)) {
|
|
312
397
|
const sql = this.platform.formatQuery(cond.sql, cond.params);
|
|
313
|
-
|
|
398
|
+
processedCond = { [raw(`(${sql})`)]: Utils.asArray(params) };
|
|
314
399
|
operator ??= '$and';
|
|
315
400
|
}
|
|
316
401
|
else if (typeof cond === 'string') {
|
|
317
|
-
|
|
402
|
+
processedCond = { [raw(`(${cond})`, Utils.asArray(params))]: [] };
|
|
318
403
|
operator ??= '$and';
|
|
319
404
|
}
|
|
320
405
|
else {
|
|
321
|
-
|
|
406
|
+
processedCond = QueryHelper.processWhere({
|
|
322
407
|
where: cond,
|
|
323
408
|
entityName: this.mainAlias.entityName,
|
|
324
409
|
metadata: this.metadata,
|
|
@@ -330,7 +415,7 @@ export class QueryBuilder {
|
|
|
330
415
|
}
|
|
331
416
|
const op = operator || params;
|
|
332
417
|
const topLevel = !op || !(Utils.hasObjectKeys(this._cond) || RawQueryFragment.hasObjectFragments(this._cond));
|
|
333
|
-
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName,
|
|
418
|
+
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processedCond);
|
|
334
419
|
const ignoreBranching = this.__populateWhere === 'infer';
|
|
335
420
|
if ([QueryType.UPDATE, QueryType.DELETE].includes(this.type) && criteriaNode.willAutoJoin(this, undefined, { ignoreBranching })) {
|
|
336
421
|
// use sub-query to support joining
|
|
@@ -382,27 +467,44 @@ export class QueryBuilder {
|
|
|
382
467
|
convertCustomTypes: false,
|
|
383
468
|
type: 'orderBy',
|
|
384
469
|
});
|
|
385
|
-
this._orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, {
|
|
386
|
-
|
|
470
|
+
this._orderBy.push(CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, processed).process(this, {
|
|
471
|
+
matchPopulateJoins: true,
|
|
472
|
+
type: 'orderBy',
|
|
473
|
+
}));
|
|
387
474
|
});
|
|
388
475
|
return this;
|
|
389
476
|
}
|
|
390
477
|
groupBy(fields) {
|
|
391
478
|
this.ensureNotFinalized();
|
|
392
|
-
this._groupBy = Utils.asArray(fields)
|
|
479
|
+
this._groupBy = Utils.asArray(fields).flatMap(f => {
|
|
480
|
+
if (typeof f !== 'string') {
|
|
481
|
+
return f;
|
|
482
|
+
}
|
|
483
|
+
return this.resolveNestedPath(f);
|
|
484
|
+
});
|
|
393
485
|
return this;
|
|
394
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* Adds a HAVING clause to the query, typically used with GROUP BY.
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* ```ts
|
|
492
|
+
* qb.select([raw('count(*) as count'), 'status'])
|
|
493
|
+
* .groupBy('status')
|
|
494
|
+
* .having({ count: { $gt: 5 } });
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
395
497
|
having(cond = {}, params, operator) {
|
|
396
498
|
this.ensureNotFinalized();
|
|
397
499
|
if (typeof cond === 'string') {
|
|
398
500
|
cond = { [raw(`(${cond})`, params)]: [] };
|
|
399
501
|
}
|
|
400
|
-
|
|
502
|
+
const processed = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond, undefined, undefined, false).process(this);
|
|
401
503
|
if (!this._having || !operator) {
|
|
402
|
-
this._having =
|
|
504
|
+
this._having = processed;
|
|
403
505
|
}
|
|
404
506
|
else {
|
|
405
|
-
const cond1 = [this._having,
|
|
507
|
+
const cond1 = [this._having, processed];
|
|
406
508
|
this._having = { [operator]: cond1 };
|
|
407
509
|
}
|
|
408
510
|
return this;
|
|
@@ -459,6 +561,15 @@ export class QueryBuilder {
|
|
|
459
561
|
this._populateFilter = populateFilter;
|
|
460
562
|
return this;
|
|
461
563
|
}
|
|
564
|
+
/**
|
|
565
|
+
* Sets a LIMIT clause to restrict the number of results.
|
|
566
|
+
*
|
|
567
|
+
* @example
|
|
568
|
+
* ```ts
|
|
569
|
+
* qb.select('*').limit(10); // First 10 results
|
|
570
|
+
* qb.select('*').limit(10, 20); // 10 results starting from offset 20
|
|
571
|
+
* ```
|
|
572
|
+
*/
|
|
462
573
|
limit(limit, offset = 0) {
|
|
463
574
|
this.ensureNotFinalized();
|
|
464
575
|
this._limit = limit;
|
|
@@ -467,6 +578,14 @@ export class QueryBuilder {
|
|
|
467
578
|
}
|
|
468
579
|
return this;
|
|
469
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* Sets an OFFSET clause to skip a number of results.
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* ```ts
|
|
586
|
+
* qb.select('*').limit(10).offset(20); // Results 21-30
|
|
587
|
+
* ```
|
|
588
|
+
*/
|
|
470
589
|
offset(offset) {
|
|
471
590
|
this.ensureNotFinalized();
|
|
472
591
|
this._offset = offset;
|
|
@@ -837,16 +956,13 @@ export class QueryBuilder {
|
|
|
837
956
|
const [res] = await this.getResultList(1);
|
|
838
957
|
return res || null;
|
|
839
958
|
}
|
|
840
|
-
/**
|
|
841
|
-
* Executes count query (without offset and limit), returning total count of results
|
|
842
|
-
*/
|
|
843
959
|
async getCount(field, distinct) {
|
|
844
960
|
let res;
|
|
845
961
|
if (this.type === QueryType.COUNT) {
|
|
846
962
|
res = await this.execute('get', false);
|
|
847
963
|
}
|
|
848
964
|
else {
|
|
849
|
-
const qb = this._type === undefined ? this : this.clone();
|
|
965
|
+
const qb = (this._type === undefined ? this : this.clone());
|
|
850
966
|
qb.processPopulateHint(); // needs to happen sooner so `qb.hasToManyJoins()` reports correctly
|
|
851
967
|
qb.count(field, distinct ?? qb.hasToManyJoins()).limit(undefined).offset(undefined).orderBy([]);
|
|
852
968
|
res = await qb.execute('get', false);
|
|
@@ -857,10 +973,7 @@ export class QueryBuilder {
|
|
|
857
973
|
* Executes the query, returning both array of results and total count query (without offset and limit).
|
|
858
974
|
*/
|
|
859
975
|
async getResultAndCount() {
|
|
860
|
-
return [
|
|
861
|
-
await this.clone().getResultList(),
|
|
862
|
-
await this.clone().getCount(),
|
|
863
|
-
];
|
|
976
|
+
return [await this.clone().getResultList(), await this.clone().getCount()];
|
|
864
977
|
}
|
|
865
978
|
as(aliasOrTargetEntity, alias) {
|
|
866
979
|
const qb = this.getNativeQuery();
|
|
@@ -1066,7 +1179,9 @@ export class QueryBuilder {
|
|
|
1066
1179
|
ret.push(getFieldName(field));
|
|
1067
1180
|
});
|
|
1068
1181
|
const requiresSQLConversion = this.mainAlias.meta.props.filter(p => p.hasConvertToJSValueSQL && p.persist !== false);
|
|
1069
|
-
if (this.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES) &&
|
|
1182
|
+
if (this.flags.has(QueryFlag.CONVERT_CUSTOM_TYPES) &&
|
|
1183
|
+
(fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) &&
|
|
1184
|
+
requiresSQLConversion.length > 0) {
|
|
1070
1185
|
for (const p of requiresSQLConversion) {
|
|
1071
1186
|
ret.push(this.helper.mapper(p.name, this.type));
|
|
1072
1187
|
}
|
|
@@ -1078,6 +1193,84 @@ export class QueryBuilder {
|
|
|
1078
1193
|
}
|
|
1079
1194
|
return Utils.unique(ret);
|
|
1080
1195
|
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Resolves nested paths like `a.books.title` to their actual field references.
|
|
1198
|
+
* Auto-joins relations as needed and returns `{alias}.{field}`.
|
|
1199
|
+
* For embeddeds: navigates into flattened embeddeds to return the correct field name.
|
|
1200
|
+
*/
|
|
1201
|
+
resolveNestedPath(field) {
|
|
1202
|
+
if (typeof field !== 'string' || !field.includes('.')) {
|
|
1203
|
+
return field;
|
|
1204
|
+
}
|
|
1205
|
+
const parts = field.split('.');
|
|
1206
|
+
// Simple alias.property case - let prepareFields handle it
|
|
1207
|
+
if (parts.length === 2 && this._aliases[parts[0]]) {
|
|
1208
|
+
return field;
|
|
1209
|
+
}
|
|
1210
|
+
// Start with root alias
|
|
1211
|
+
let currentAlias = parts[0];
|
|
1212
|
+
let currentMeta = this._aliases[currentAlias] ? this.metadata.get(this._aliases[currentAlias].entityName) : this.mainAlias.meta;
|
|
1213
|
+
// If first part is not an alias, it's a property of the main entity
|
|
1214
|
+
if (!this._aliases[currentAlias]) {
|
|
1215
|
+
currentAlias = this.mainAlias.aliasName;
|
|
1216
|
+
parts.unshift(currentAlias);
|
|
1217
|
+
}
|
|
1218
|
+
// Walk through the path parts (skip the alias)
|
|
1219
|
+
for (let i = 1; i < parts.length; i++) {
|
|
1220
|
+
const propName = parts[i];
|
|
1221
|
+
const prop = currentMeta.properties[propName];
|
|
1222
|
+
if (!prop) {
|
|
1223
|
+
return field; // Unknown property, return as-is for raw SQL support
|
|
1224
|
+
}
|
|
1225
|
+
const isLastPart = i === parts.length - 1;
|
|
1226
|
+
// Handle embedded properties - navigate into flattened embeddeds
|
|
1227
|
+
if (prop.kind === ReferenceKind.EMBEDDED) {
|
|
1228
|
+
if (prop.object) {
|
|
1229
|
+
return `${currentAlias}.${propName}`;
|
|
1230
|
+
}
|
|
1231
|
+
// Navigate through remaining path to find the leaf property
|
|
1232
|
+
const remainingPath = parts.slice(i + 1);
|
|
1233
|
+
let embeddedProp = prop;
|
|
1234
|
+
for (const part of remainingPath) {
|
|
1235
|
+
embeddedProp = embeddedProp?.embeddedProps?.[part];
|
|
1236
|
+
if (embeddedProp?.object && embeddedProp.fieldNames?.[0]) {
|
|
1237
|
+
return `${currentAlias}.${embeddedProp.fieldNames[0]}`;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
return `${currentAlias}.${embeddedProp?.fieldNames?.[0] ?? propName}`;
|
|
1241
|
+
}
|
|
1242
|
+
// Handle relations - auto-join if not the last part
|
|
1243
|
+
if (prop.kind === ReferenceKind.MANY_TO_ONE ||
|
|
1244
|
+
prop.kind === ReferenceKind.ONE_TO_ONE ||
|
|
1245
|
+
prop.kind === ReferenceKind.ONE_TO_MANY ||
|
|
1246
|
+
prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
1247
|
+
if (isLastPart) {
|
|
1248
|
+
return `${currentAlias}.${propName}`;
|
|
1249
|
+
}
|
|
1250
|
+
// Find existing join or create new one
|
|
1251
|
+
const joinPath = parts.slice(0, i + 1).join('.');
|
|
1252
|
+
const existingJoinKey = Object.keys(this._joins).find(k => {
|
|
1253
|
+
const join = this._joins[k];
|
|
1254
|
+
// Check by path or by key prefix (key format is `alias.field#joinAlias`)
|
|
1255
|
+
return join.path === joinPath || k.startsWith(`${currentAlias}.${propName}#`);
|
|
1256
|
+
});
|
|
1257
|
+
let joinAlias;
|
|
1258
|
+
if (existingJoinKey) {
|
|
1259
|
+
joinAlias = this._joins[existingJoinKey].alias;
|
|
1260
|
+
}
|
|
1261
|
+
else {
|
|
1262
|
+
joinAlias = this.getNextAlias(prop.targetMeta?.className ?? propName);
|
|
1263
|
+
this.join(`${currentAlias}.${propName}`, joinAlias, {}, JoinType.leftJoin);
|
|
1264
|
+
}
|
|
1265
|
+
currentAlias = joinAlias;
|
|
1266
|
+
currentMeta = prop.targetMeta;
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
// Scalar property - return it (if not last part, it's an invalid path but let SQL handle it)
|
|
1270
|
+
return `${currentAlias}.${propName}`;
|
|
1271
|
+
}
|
|
1272
|
+
return field;
|
|
1273
|
+
}
|
|
1081
1274
|
init(type, data, cond) {
|
|
1082
1275
|
this.ensureNotFinalized();
|
|
1083
1276
|
this._type = type;
|
|
@@ -1344,7 +1537,10 @@ export class QueryBuilder {
|
|
|
1344
1537
|
wrapPaginateSubQuery(meta) {
|
|
1345
1538
|
const schema = this.getSchema(this.mainAlias);
|
|
1346
1539
|
const pks = this.prepareFields(meta.primaryKeys, 'sub-query', schema);
|
|
1347
|
-
const subQuery = this.clone(['_orderBy', '_fields', 'lockMode', 'lockTableAliases'])
|
|
1540
|
+
const subQuery = this.clone(['_orderBy', '_fields', 'lockMode', 'lockTableAliases'])
|
|
1541
|
+
.select(pks)
|
|
1542
|
+
.groupBy(pks)
|
|
1543
|
+
.limit(this._limit);
|
|
1348
1544
|
// revert the on conditions added via populateWhere, we want to apply those only once
|
|
1349
1545
|
for (const join of Object.values(subQuery._joins)) {
|
|
1350
1546
|
if (join.cond_) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityName, type EntityProperty, type FlatQueryOrderMap, type FormulaTable, LockMode, type
|
|
1
|
+
import { type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityName, type EntityProperty, type FilterQuery, type FlatQueryOrderMap, type FormulaTable, LockMode, type QueryOrderMap, Raw, type RawQueryFragmentSymbol } from '@mikro-orm/core';
|
|
2
2
|
import { JoinType, QueryType } from './enums.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { InternalField, JoinOptions } from '../typings.js';
|
|
4
4
|
import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
|
|
5
5
|
import { NativeQueryBuilder } from './NativeQueryBuilder.js';
|
|
6
6
|
/**
|
|
@@ -45,10 +45,10 @@ export declare class QueryBuilderHelper {
|
|
|
45
45
|
private processObjectSubCondition;
|
|
46
46
|
private getValueReplacement;
|
|
47
47
|
private getOperatorReplacement;
|
|
48
|
-
validateQueryOrder<T>(orderBy:
|
|
48
|
+
validateQueryOrder<T>(orderBy: QueryOrderMap<T>): void;
|
|
49
49
|
getQueryOrder(type: QueryType, orderBy: FlatQueryOrderMap | FlatQueryOrderMap[], populate: Dictionary<string>): string[];
|
|
50
50
|
getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>): string[];
|
|
51
|
-
finalize(type: QueryType, qb: NativeQueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?:
|
|
51
|
+
finalize(type: QueryType, qb: NativeQueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: InternalField<any>[]): void;
|
|
52
52
|
splitField<T>(field: EntityKey<T>, greedyAlias?: boolean): [string, EntityKey<T>, string | undefined];
|
|
53
53
|
getLockSQL(qb: NativeQueryBuilder, lockMode: LockMode, lockTables?: string[], joinsMap?: Dictionary<JoinOptions>): void;
|
|
54
54
|
updateVersionProperty(qb: NativeQueryBuilder, data: Dictionary): void;
|
|
@@ -58,7 +58,7 @@ export declare class QueryBuilderHelper {
|
|
|
58
58
|
private fieldName;
|
|
59
59
|
getProperty(field: string, alias?: string): EntityProperty | undefined;
|
|
60
60
|
isTableNameAliasRequired(type: QueryType): boolean;
|
|
61
|
-
processOnConflictCondition(cond:
|
|
61
|
+
processOnConflictCondition(cond: FilterQuery<any>, schema?: string): FilterQuery<any>;
|
|
62
62
|
createFormulaTable(alias: string, meta: EntityMetadata, schema?: string): FormulaTable;
|
|
63
63
|
}
|
|
64
64
|
export interface Alias<T> {
|
|
@@ -70,6 +70,6 @@ export interface Alias<T> {
|
|
|
70
70
|
export interface OnConflictClause<T> {
|
|
71
71
|
fields: string[] | Raw;
|
|
72
72
|
ignore?: boolean;
|
|
73
|
-
merge?: EntityData<T> |
|
|
74
|
-
where?:
|
|
73
|
+
merge?: EntityData<T> | InternalField<T>[];
|
|
74
|
+
where?: FilterQuery<T>;
|
|
75
75
|
}
|
|
@@ -68,10 +68,6 @@ export class QueryBuilderHelper {
|
|
|
68
68
|
const prop = this.getProperty(f, a);
|
|
69
69
|
const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
|
|
70
70
|
const fkIdx = fkIdx2 === -1 ? 0 : fkIdx2;
|
|
71
|
-
// embeddable nested path instead of a regular property with table alias, reset alias
|
|
72
|
-
if (prop?.name === a && prop.embeddedProps[f]) {
|
|
73
|
-
return aliasPrefix + prop.fieldNames[fkIdx];
|
|
74
|
-
}
|
|
75
71
|
if (a === prop?.embedded?.[0]) {
|
|
76
72
|
return aliasPrefix + prop.fieldNames[fkIdx];
|
|
77
73
|
}
|
|
@@ -621,14 +617,15 @@ export class QueryBuilderHelper {
|
|
|
621
617
|
if (type === QueryType.UPDATE) {
|
|
622
618
|
const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]]));
|
|
623
619
|
if (returningProps.length > 0) {
|
|
624
|
-
|
|
620
|
+
const fields = returningProps.flatMap((prop) => {
|
|
625
621
|
if (prop.hasConvertToJSValueSQL) {
|
|
626
622
|
const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
627
623
|
const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) + ' as ' + this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
628
624
|
return [raw(sql)];
|
|
629
625
|
}
|
|
630
626
|
return prop.fieldNames;
|
|
631
|
-
})
|
|
627
|
+
});
|
|
628
|
+
qb.returning(fields);
|
|
632
629
|
}
|
|
633
630
|
}
|
|
634
631
|
}
|
|
@@ -753,10 +750,6 @@ export class QueryBuilderHelper {
|
|
|
753
750
|
if (alias) {
|
|
754
751
|
const prop = meta.properties[alias];
|
|
755
752
|
if (prop?.kind === ReferenceKind.EMBEDDED) {
|
|
756
|
-
// we want to select the full object property so hydration works as expected
|
|
757
|
-
if (prop.object) {
|
|
758
|
-
return prop;
|
|
759
|
-
}
|
|
760
753
|
const parts = field.split('.');
|
|
761
754
|
const nest = (p) => parts.length > 0 ? nest(p.embeddedProps[parts.shift()]) : p;
|
|
762
755
|
return nest(prop);
|
package/query/raw.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { type AnyString, type Dictionary, type EntityKey, type RawQueryFragment } from '@mikro-orm/core';
|
|
2
|
-
import type { SelectQueryBuilder } from 'kysely';
|
|
3
|
-
|
|
2
|
+
import type { SelectQueryBuilder as KyselySelectQueryBuilder } from 'kysely';
|
|
3
|
+
/** @internal Type for QueryBuilder instances passed to raw() - uses toRaw to distinguish from Kysely QueryBuilder */
|
|
4
|
+
type QueryBuilderLike = {
|
|
5
|
+
toQuery(): {
|
|
6
|
+
sql: string;
|
|
7
|
+
params: readonly unknown[];
|
|
8
|
+
};
|
|
9
|
+
toRaw(): RawQueryFragment;
|
|
10
|
+
};
|
|
4
11
|
/**
|
|
5
12
|
* Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
|
|
6
13
|
* by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
|
|
@@ -56,4 +63,5 @@ import { QueryBuilder } from './QueryBuilder.js';
|
|
|
56
63
|
* export class Author { ... }
|
|
57
64
|
* ```
|
|
58
65
|
*/
|
|
59
|
-
export declare function raw<
|
|
66
|
+
export declare function raw<R = RawQueryFragment & symbol, T extends object = any>(sql: QueryBuilderLike | KyselySelectQueryBuilder<any, any, any> | EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): R;
|
|
67
|
+
export {};
|
package/query/raw.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { raw as raw_, Utils } from '@mikro-orm/core';
|
|
2
|
-
import { QueryBuilder } from './QueryBuilder.js';
|
|
3
2
|
/**
|
|
4
3
|
* Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
|
|
5
4
|
* by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
|
|
@@ -60,7 +59,7 @@ export function raw(sql, params) {
|
|
|
60
59
|
const query = sql.compile();
|
|
61
60
|
return raw_(query.sql, query.parameters);
|
|
62
61
|
}
|
|
63
|
-
if (sql
|
|
62
|
+
if (Utils.isObject(sql) && 'toQuery' in sql) {
|
|
64
63
|
const query = sql.toQuery();
|
|
65
64
|
return raw_(query.sql, query.params);
|
|
66
65
|
}
|