@mikro-orm/sql 7.0.0-dev.112 → 7.0.0-dev.114
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.js +34 -28
- package/package.json +2 -2
- package/query/CriteriaNode.d.ts +3 -3
- package/query/CriteriaNode.js +9 -11
- package/query/CriteriaNodeFactory.d.ts +4 -4
- package/query/CriteriaNodeFactory.js +18 -16
- package/query/NativeQueryBuilder.js +1 -2
- package/query/ObjectCriteriaNode.js +23 -23
- package/query/QueryBuilder.d.ts +3 -9
- package/query/QueryBuilder.js +23 -43
- package/query/QueryBuilderHelper.d.ts +6 -5
- package/query/QueryBuilderHelper.js +52 -32
- package/schema/DatabaseSchema.js +3 -7
- package/schema/SchemaHelper.d.ts +2 -2
- package/schema/SchemaHelper.js +2 -3
- package/tsconfig.build.tsbuildinfo +1 -1
- package/typings.d.ts +1 -1
package/query/QueryBuilder.js
CHANGED
|
@@ -47,8 +47,6 @@ export class QueryBuilder {
|
|
|
47
47
|
_populate = [];
|
|
48
48
|
/** @internal */
|
|
49
49
|
_populateMap = {};
|
|
50
|
-
/** @internal */
|
|
51
|
-
rawFragments = new Set();
|
|
52
50
|
aliasCounter = 0;
|
|
53
51
|
flags = new Set([QueryFlag.CONVERT_CUSTOM_TYPES]);
|
|
54
52
|
finalized = false;
|
|
@@ -277,14 +275,14 @@ export class QueryBuilder {
|
|
|
277
275
|
}
|
|
278
276
|
filterOptions = QueryHelper.mergePropertyFilters(join.prop.filters, filterOptions);
|
|
279
277
|
const cond = await em.applyFilters(join.prop.type, join.cond, filterOptions, 'read');
|
|
280
|
-
if (Utils.hasObjectKeys(cond)) {
|
|
278
|
+
if (Utils.hasObjectKeys(cond) || RawQueryFragment.hasObjectFragments(cond)) {
|
|
281
279
|
// remove nested filters, we only care about scalars here, nesting would require another join branch
|
|
282
280
|
for (const key of Object.keys(cond)) {
|
|
283
281
|
if (Utils.isPlainObject(cond[key]) && Object.keys(cond[key]).every(k => !(Utils.isOperator(k) && !['$some', '$none', '$every'].includes(k)))) {
|
|
284
282
|
delete cond[key];
|
|
285
283
|
}
|
|
286
284
|
}
|
|
287
|
-
if (Utils.hasObjectKeys(join.cond)) {
|
|
285
|
+
if (Utils.hasObjectKeys(join.cond) || RawQueryFragment.hasObjectFragments(join.cond)) {
|
|
288
286
|
/* v8 ignore next */
|
|
289
287
|
join.cond = { $and: [join.cond, cond] };
|
|
290
288
|
}
|
|
@@ -296,7 +294,7 @@ export class QueryBuilder {
|
|
|
296
294
|
}
|
|
297
295
|
withSubQuery(subQuery, alias) {
|
|
298
296
|
this.ensureNotFinalized();
|
|
299
|
-
if (subQuery
|
|
297
|
+
if (isRaw(subQuery)) {
|
|
300
298
|
this.subQueries[alias] = this.platform.formatQuery(subQuery.sql, subQuery.params);
|
|
301
299
|
}
|
|
302
300
|
else {
|
|
@@ -306,9 +304,8 @@ export class QueryBuilder {
|
|
|
306
304
|
}
|
|
307
305
|
where(cond, params, operator) {
|
|
308
306
|
this.ensureNotFinalized();
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const sql = this.platform.formatQuery(rawField.sql, rawField.params);
|
|
307
|
+
if (isRaw(cond)) {
|
|
308
|
+
const sql = this.platform.formatQuery(cond.sql, cond.params);
|
|
312
309
|
cond = { [raw(`(${sql})`)]: Utils.asArray(params) };
|
|
313
310
|
operator ??= '$and';
|
|
314
311
|
}
|
|
@@ -328,7 +325,7 @@ export class QueryBuilder {
|
|
|
328
325
|
});
|
|
329
326
|
}
|
|
330
327
|
const op = operator || params;
|
|
331
|
-
const topLevel = !op || !Utils.hasObjectKeys(this._cond);
|
|
328
|
+
const topLevel = !op || !(Utils.hasObjectKeys(this._cond) || RawQueryFragment.hasObjectFragments(this._cond));
|
|
332
329
|
const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, this.mainAlias.entityName, cond);
|
|
333
330
|
const ignoreBranching = this.__populateWhere === 'infer';
|
|
334
331
|
if ([QueryType.UPDATE, QueryType.DELETE].includes(this.type) && criteriaNode.willAutoJoin(this, undefined, { ignoreBranching })) {
|
|
@@ -370,6 +367,7 @@ export class QueryBuilder {
|
|
|
370
367
|
this._orderBy = [];
|
|
371
368
|
}
|
|
372
369
|
Utils.asArray(orderBy).forEach(o => {
|
|
370
|
+
this.helper.validateQueryOrder(o);
|
|
373
371
|
const processed = QueryHelper.processWhere({
|
|
374
372
|
where: o,
|
|
375
373
|
entityName: this.mainAlias.entityName,
|
|
@@ -553,9 +551,10 @@ export class QueryBuilder {
|
|
|
553
551
|
this._query = {};
|
|
554
552
|
this.finalize();
|
|
555
553
|
const qb = this.getQueryBase(processVirtualEntity);
|
|
554
|
+
const isNotEmptyObject = (obj) => Utils.hasObjectKeys(obj) || RawQueryFragment.hasObjectFragments(obj);
|
|
556
555
|
Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this._cond, qb), this._cond && !this._onConflict);
|
|
557
|
-
Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this._groupBy, 'groupBy')), this._groupBy);
|
|
558
|
-
Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this._having, qb, undefined, 'having'), this._having);
|
|
556
|
+
Utils.runIfNotEmpty(() => qb.groupBy(this.prepareFields(this._groupBy, 'groupBy')), isNotEmptyObject(this._groupBy));
|
|
557
|
+
Utils.runIfNotEmpty(() => this.helper.appendQueryCondition(this.type, this._having, qb, undefined, 'having'), isNotEmptyObject(this._having));
|
|
559
558
|
Utils.runIfNotEmpty(() => {
|
|
560
559
|
const queryOrder = this.helper.getQueryOrder(this.type, this._orderBy, this._populateMap);
|
|
561
560
|
if (queryOrder.length > 0) {
|
|
@@ -563,7 +562,7 @@ export class QueryBuilder {
|
|
|
563
562
|
qb.orderBy(sql);
|
|
564
563
|
return;
|
|
565
564
|
}
|
|
566
|
-
}, this._orderBy);
|
|
565
|
+
}, isNotEmptyObject(this._orderBy));
|
|
567
566
|
Utils.runIfNotEmpty(() => qb.limit(this._limit), this._limit != null);
|
|
568
567
|
Utils.runIfNotEmpty(() => qb.offset(this._offset), this._offset);
|
|
569
568
|
Utils.runIfNotEmpty(() => qb.comment(this._comments), this._comments);
|
|
@@ -573,16 +572,8 @@ export class QueryBuilder {
|
|
|
573
572
|
this.helper.getLockSQL(qb, this.lockMode, this.lockTables, this._joins);
|
|
574
573
|
}
|
|
575
574
|
this.helper.finalize(this.type, qb, this.mainAlias.metadata, this._data, this._returning);
|
|
576
|
-
this.clearRawFragmentsCache();
|
|
577
575
|
return this._query.qb = qb;
|
|
578
576
|
}
|
|
579
|
-
/**
|
|
580
|
-
* @internal
|
|
581
|
-
*/
|
|
582
|
-
clearRawFragmentsCache() {
|
|
583
|
-
this.rawFragments.forEach(key => RawQueryFragment.remove(key));
|
|
584
|
-
this.rawFragments.clear();
|
|
585
|
-
}
|
|
586
577
|
/**
|
|
587
578
|
* Returns the query with parameters as wildcards.
|
|
588
579
|
*/
|
|
@@ -892,16 +883,14 @@ export class QueryBuilder {
|
|
|
892
883
|
const properties = [
|
|
893
884
|
'flags', '_populate', '_populateWhere', '_populateFilter', '__populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
|
|
894
885
|
'_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables', '_groupBy', '_having', '_returning',
|
|
895
|
-
'_comments', '_hintComments', '
|
|
886
|
+
'_comments', '_hintComments', 'aliasCounter',
|
|
896
887
|
];
|
|
897
|
-
RawQueryFragment.cloneRegistry = this.rawFragments;
|
|
898
888
|
for (const prop of Object.keys(this)) {
|
|
899
889
|
if (reset.includes(prop) || ['_helper', '_query'].includes(prop)) {
|
|
900
890
|
continue;
|
|
901
891
|
}
|
|
902
892
|
qb[prop] = properties.includes(prop) ? Utils.copy(this[prop]) : this[prop];
|
|
903
893
|
}
|
|
904
|
-
delete RawQueryFragment.cloneRegistry;
|
|
905
894
|
/* v8 ignore next */
|
|
906
895
|
if (this._fields && !reset.includes('_fields')) {
|
|
907
896
|
qb._fields = [...this._fields];
|
|
@@ -935,7 +924,7 @@ export class QueryBuilder {
|
|
|
935
924
|
if (res instanceof QueryBuilder) {
|
|
936
925
|
return `(${res.getFormattedQuery()}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
937
926
|
}
|
|
938
|
-
if (res
|
|
927
|
+
if (isRaw(res)) {
|
|
939
928
|
const query = this.platform.formatQuery(res.sql, res.params);
|
|
940
929
|
return `(${query}) as ${this.platform.quoteIdentifier(this.alias)}`;
|
|
941
930
|
}
|
|
@@ -954,7 +943,7 @@ export class QueryBuilder {
|
|
|
954
943
|
prop.targetMeta = field.mainAlias.metadata;
|
|
955
944
|
field = field.getNativeQuery();
|
|
956
945
|
}
|
|
957
|
-
if (field
|
|
946
|
+
if (isRaw(field)) {
|
|
958
947
|
field = this.platform.formatQuery(field.sql, field.params);
|
|
959
948
|
}
|
|
960
949
|
const key = `${this.alias}.${prop.name}#${alias}`;
|
|
@@ -1029,11 +1018,6 @@ export class QueryBuilder {
|
|
|
1029
1018
|
return this.helper.mapper(name, this.type, undefined, type === 'groupBy' ? null : undefined);
|
|
1030
1019
|
};
|
|
1031
1020
|
fields.forEach(field => {
|
|
1032
|
-
const rawField = RawQueryFragment.getKnownFragment(field, false);
|
|
1033
|
-
if (rawField) {
|
|
1034
|
-
ret.push(rawField);
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
1021
|
if (typeof field !== 'string') {
|
|
1038
1022
|
ret.push(field);
|
|
1039
1023
|
return;
|
|
@@ -1088,10 +1072,7 @@ export class QueryBuilder {
|
|
|
1088
1072
|
}
|
|
1089
1073
|
for (const f of Object.keys(this._populateMap)) {
|
|
1090
1074
|
if (type === 'where' && this._joins[f]) {
|
|
1091
|
-
|
|
1092
|
-
for (const col of cols) {
|
|
1093
|
-
ret.push(col);
|
|
1094
|
-
}
|
|
1075
|
+
ret.push(...this.helper.mapJoinColumns(this.type, this._joins[f]));
|
|
1095
1076
|
}
|
|
1096
1077
|
}
|
|
1097
1078
|
return Utils.unique(ret);
|
|
@@ -1206,7 +1187,7 @@ export class QueryBuilder {
|
|
|
1206
1187
|
return `${prop.formula(alias)} as ${aliased}`;
|
|
1207
1188
|
})
|
|
1208
1189
|
.filter(field => !this._fields.some(f => {
|
|
1209
|
-
if (f
|
|
1190
|
+
if (isRaw(f)) {
|
|
1210
1191
|
return f.sql === field && f.params.length === 0;
|
|
1211
1192
|
}
|
|
1212
1193
|
return f === field;
|
|
@@ -1372,11 +1353,10 @@ export class QueryBuilder {
|
|
|
1372
1353
|
if (this._orderBy.length > 0) {
|
|
1373
1354
|
const orderBy = [];
|
|
1374
1355
|
for (const orderMap of this._orderBy) {
|
|
1375
|
-
for (const
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
orderBy.push({ [rawField.clone()]: direction });
|
|
1356
|
+
for (const field of Utils.getObjectQueryKeys(orderMap)) {
|
|
1357
|
+
const direction = orderMap[field];
|
|
1358
|
+
if (RawQueryFragment.isKnownFragmentSymbol(field)) {
|
|
1359
|
+
orderBy.push({ [field]: direction });
|
|
1380
1360
|
continue;
|
|
1381
1361
|
}
|
|
1382
1362
|
const [a, f] = this.helper.splitField(field);
|
|
@@ -1401,14 +1381,14 @@ export class QueryBuilder {
|
|
|
1401
1381
|
if (typeof field === 'object' && field && '__as' in field) {
|
|
1402
1382
|
return field.__as === prop;
|
|
1403
1383
|
}
|
|
1404
|
-
if (field
|
|
1384
|
+
if (isRaw(field)) {
|
|
1405
1385
|
// not perfect, but should work most of the time, ideally we should check only the alias (`... as alias`)
|
|
1406
1386
|
return field.sql.includes(prop);
|
|
1407
1387
|
}
|
|
1408
1388
|
return false;
|
|
1409
1389
|
});
|
|
1410
1390
|
/* v8 ignore next */
|
|
1411
|
-
if (field
|
|
1391
|
+
if (isRaw(field)) {
|
|
1412
1392
|
innerQuery.select(field);
|
|
1413
1393
|
}
|
|
1414
1394
|
else if (field instanceof NativeQueryBuilder) {
|
|
@@ -1425,7 +1405,7 @@ export class QueryBuilder {
|
|
|
1425
1405
|
subSubQuery.select(pks).from(innerQuery);
|
|
1426
1406
|
this._limit = undefined;
|
|
1427
1407
|
this._offset = undefined;
|
|
1428
|
-
if (!this._fields.some(
|
|
1408
|
+
if (!this._fields.some(field => isRaw(field))) {
|
|
1429
1409
|
this.pruneExtraJoins(meta);
|
|
1430
1410
|
}
|
|
1431
1411
|
const { sql, params } = subSubQuery.compile();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityProperty, type FlatQueryOrderMap, LockMode, type QBFilterQuery,
|
|
1
|
+
import { type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityProperty, type FlatQueryOrderMap, LockMode, type QBFilterQuery, type QBQueryOrderMap, Raw, type RawQueryFragmentSymbol } from '@mikro-orm/core';
|
|
2
2
|
import { JoinType, QueryType } from './enums.js';
|
|
3
3
|
import type { Field, JoinOptions } from '../typings.js';
|
|
4
4
|
import type { AbstractSqlDriver } from '../AbstractSqlDriver.js';
|
|
@@ -15,8 +15,8 @@ export declare class QueryBuilderHelper {
|
|
|
15
15
|
private readonly platform;
|
|
16
16
|
private readonly metadata;
|
|
17
17
|
constructor(entityName: string, alias: string, aliasMap: Dictionary<Alias<any>>, subQueries: Dictionary<string>, driver: AbstractSqlDriver);
|
|
18
|
-
mapper(field: string |
|
|
19
|
-
mapper(field: string |
|
|
18
|
+
mapper(field: string | Raw | RawQueryFragmentSymbol, type?: QueryType): string;
|
|
19
|
+
mapper(field: string | Raw | RawQueryFragmentSymbol, type?: QueryType, value?: any, alias?: string | null): string;
|
|
20
20
|
processData(data: Dictionary, convertCustomTypes: boolean, multi?: boolean): any;
|
|
21
21
|
joinOneToReference(prop: EntityProperty, ownerAlias: string, alias: string, type: JoinType, cond?: Dictionary, schema?: string): JoinOptions;
|
|
22
22
|
joinManyToOneReference(prop: EntityProperty, ownerAlias: string, alias: string, type: JoinType, cond?: Dictionary, schema?: string): JoinOptions;
|
|
@@ -26,7 +26,7 @@ export declare class QueryBuilderHelper {
|
|
|
26
26
|
sql: string;
|
|
27
27
|
params: unknown[];
|
|
28
28
|
};
|
|
29
|
-
mapJoinColumns(type: QueryType, join: JoinOptions): (string |
|
|
29
|
+
mapJoinColumns(type: QueryType, join: JoinOptions): (string | Raw)[];
|
|
30
30
|
isOneToOneInverse(field: string, meta?: EntityMetadata): boolean;
|
|
31
31
|
getTableName(entityName: string): string;
|
|
32
32
|
/**
|
|
@@ -45,6 +45,7 @@ export declare class QueryBuilderHelper {
|
|
|
45
45
|
private processObjectSubCondition;
|
|
46
46
|
private getValueReplacement;
|
|
47
47
|
private getOperatorReplacement;
|
|
48
|
+
validateQueryOrder<T>(orderBy: QBQueryOrderMap<T>): void;
|
|
48
49
|
getQueryOrder(type: QueryType, orderBy: FlatQueryOrderMap | FlatQueryOrderMap[], populate: Dictionary<string>): string[];
|
|
49
50
|
getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>): string[];
|
|
50
51
|
finalize(type: QueryType, qb: NativeQueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: Field<any>[]): void;
|
|
@@ -66,7 +67,7 @@ export interface Alias<T> {
|
|
|
66
67
|
subQuery?: NativeQueryBuilder;
|
|
67
68
|
}
|
|
68
69
|
export interface OnConflictClause<T> {
|
|
69
|
-
fields: string[] |
|
|
70
|
+
fields: string[] | Raw;
|
|
70
71
|
ignore?: boolean;
|
|
71
72
|
merge?: EntityData<T> | Field<T>[];
|
|
72
73
|
where?: QBFilterQuery<T>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ALIAS_REPLACEMENT, ALIAS_REPLACEMENT_RE, ArrayType, isRaw, LockMode, OptimisticLockError, QueryOperator, QueryOrderNumeric, raw,
|
|
1
|
+
import { ALIAS_REPLACEMENT, ALIAS_REPLACEMENT_RE, ArrayType, inspect, isRaw, LockMode, OptimisticLockError, QueryOperator, QueryOrderNumeric, raw, Raw, ReferenceKind, Utils, ValidationError, } from '@mikro-orm/core';
|
|
2
2
|
import { JoinType, QueryType } from './enums.js';
|
|
3
3
|
import { NativeQueryBuilder } from './NativeQueryBuilder.js';
|
|
4
4
|
/**
|
|
@@ -25,6 +25,9 @@ export class QueryBuilderHelper {
|
|
|
25
25
|
if (isRaw(field)) {
|
|
26
26
|
return raw(field.sql, field.params);
|
|
27
27
|
}
|
|
28
|
+
if (Raw.isKnownFragmentSymbol(field)) {
|
|
29
|
+
return Raw.getKnownFragment(field);
|
|
30
|
+
}
|
|
28
31
|
/* v8 ignore next */
|
|
29
32
|
if (typeof field !== 'string') {
|
|
30
33
|
return field;
|
|
@@ -60,16 +63,11 @@ export class QueryBuilderHelper {
|
|
|
60
63
|
}
|
|
61
64
|
return raw('(' + parts.map(part => this.platform.quoteIdentifier(part)).join(', ') + ')');
|
|
62
65
|
}
|
|
63
|
-
const rawField = RawQueryFragment.getKnownFragment(field);
|
|
64
|
-
if (rawField) {
|
|
65
|
-
return rawField;
|
|
66
|
-
}
|
|
67
66
|
const aliasPrefix = isTableNameAliasRequired ? this.alias + '.' : '';
|
|
68
67
|
const [a, f] = this.splitField(field);
|
|
69
68
|
const prop = this.getProperty(f, a);
|
|
70
69
|
const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
|
|
71
70
|
const fkIdx = fkIdx2 === -1 ? 0 : fkIdx2;
|
|
72
|
-
let ret = field;
|
|
73
71
|
// embeddable nested path instead of a regular property with table alias, reset alias
|
|
74
72
|
if (prop?.name === a && prop.embeddedProps[f]) {
|
|
75
73
|
return aliasPrefix + prop.fieldNames[fkIdx];
|
|
@@ -107,10 +105,7 @@ export class QueryBuilderHelper {
|
|
|
107
105
|
}
|
|
108
106
|
return raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[fkIdx])}`);
|
|
109
107
|
}
|
|
110
|
-
|
|
111
|
-
if (!rawField) {
|
|
112
|
-
ret = this.prefix(field, false, false, fkIdx);
|
|
113
|
-
}
|
|
108
|
+
let ret = this.prefix(field, false, false, fkIdx);
|
|
114
109
|
if (alias) {
|
|
115
110
|
ret += ' as ' + alias;
|
|
116
111
|
}
|
|
@@ -243,7 +238,7 @@ export class QueryBuilderHelper {
|
|
|
243
238
|
this.alias = oldAlias;
|
|
244
239
|
if (subquery.sql) {
|
|
245
240
|
conditions.push(subquery.sql);
|
|
246
|
-
params.push(
|
|
241
|
+
subquery.params.forEach(p => params.push(p));
|
|
247
242
|
}
|
|
248
243
|
if (conditions.length > 0) {
|
|
249
244
|
sql += ` on ${conditions.join(' and ')}`;
|
|
@@ -332,7 +327,7 @@ export class QueryBuilderHelper {
|
|
|
332
327
|
_appendQueryCondition(type, cond, operator) {
|
|
333
328
|
const parts = [];
|
|
334
329
|
const params = [];
|
|
335
|
-
for (const k of
|
|
330
|
+
for (const k of Utils.getObjectQueryKeys(cond)) {
|
|
336
331
|
if (k === '$and' || k === '$or') {
|
|
337
332
|
if (operator) {
|
|
338
333
|
this.append(() => this.appendGroupCondition(type, k, cond[k]), parts, params, operator);
|
|
@@ -344,7 +339,7 @@ export class QueryBuilderHelper {
|
|
|
344
339
|
if (k === '$not') {
|
|
345
340
|
const res = this._appendQueryCondition(type, cond[k]);
|
|
346
341
|
parts.push(`not (${res.sql})`);
|
|
347
|
-
params.push(
|
|
342
|
+
res.params.forEach(p => params.push(p));
|
|
348
343
|
continue;
|
|
349
344
|
}
|
|
350
345
|
this.append(() => this.appendQuerySubCondition(type, cond, k), parts, params);
|
|
@@ -362,7 +357,6 @@ export class QueryBuilderHelper {
|
|
|
362
357
|
appendQuerySubCondition(type, cond, key) {
|
|
363
358
|
const parts = [];
|
|
364
359
|
const params = [];
|
|
365
|
-
const fields = Utils.splitPrimaryKeys(key);
|
|
366
360
|
if (this.isSimpleRegExp(cond[key])) {
|
|
367
361
|
parts.push(`${this.platform.quoteIdentifier(this.mapper(key, type))} like ?`);
|
|
368
362
|
params.push(this.getRegExpParam(cond[key]));
|
|
@@ -372,19 +366,21 @@ export class QueryBuilderHelper {
|
|
|
372
366
|
return this.processObjectSubCondition(cond, key, type);
|
|
373
367
|
}
|
|
374
368
|
const op = cond[key] === null ? 'is' : '=';
|
|
375
|
-
|
|
376
|
-
|
|
369
|
+
if (Raw.isKnownFragmentSymbol(key)) {
|
|
370
|
+
const raw = Raw.getKnownFragment(key);
|
|
377
371
|
const sql = raw.sql.replaceAll(ALIAS_REPLACEMENT, this.alias);
|
|
378
372
|
const value = Utils.asArray(cond[key]);
|
|
379
373
|
params.push(...raw.params);
|
|
380
374
|
if (value.length > 0) {
|
|
381
|
-
const
|
|
375
|
+
const k = key;
|
|
376
|
+
const val = this.getValueReplacement([k], value[0], params, k);
|
|
382
377
|
parts.push(`${sql} ${op} ${val}`);
|
|
383
378
|
return { sql: parts.join(' and '), params };
|
|
384
379
|
}
|
|
385
380
|
parts.push(sql);
|
|
386
381
|
return { sql: parts.join(' and '), params };
|
|
387
382
|
}
|
|
383
|
+
const fields = Utils.splitPrimaryKeys(key);
|
|
388
384
|
if (this.subQueries[key]) {
|
|
389
385
|
const val = this.getValueReplacement(fields, cond[key], params, key);
|
|
390
386
|
parts.push(`(${this.subQueries[key]}) ${op} ${val}`);
|
|
@@ -404,9 +400,7 @@ export class QueryBuilderHelper {
|
|
|
404
400
|
}
|
|
405
401
|
// grouped condition for one field, e.g. `{ age: { $gte: 10, $lt: 50 } }`
|
|
406
402
|
if (size > 1) {
|
|
407
|
-
const rawField = RawQueryFragment.getKnownFragment(key);
|
|
408
403
|
const subCondition = Object.entries(value).map(([subKey, subValue]) => {
|
|
409
|
-
key = rawField?.clone().toString() ?? key;
|
|
410
404
|
return ({ [key]: { [subKey]: subValue } });
|
|
411
405
|
});
|
|
412
406
|
for (const sub of subCondition) {
|
|
@@ -424,7 +418,8 @@ export class QueryBuilderHelper {
|
|
|
424
418
|
throw ValidationError.invalidQueryCondition(cond);
|
|
425
419
|
}
|
|
426
420
|
const replacement = this.getOperatorReplacement(op, value);
|
|
427
|
-
const
|
|
421
|
+
const rawField = Raw.isKnownFragmentSymbol(key);
|
|
422
|
+
const fields = rawField ? [key] : Utils.splitPrimaryKeys(key);
|
|
428
423
|
if (fields.length > 1 && Array.isArray(value[op])) {
|
|
429
424
|
const singleTuple = !value[op].every((v) => Array.isArray(v));
|
|
430
425
|
if (!this.platform.allowsComparingTuples()) {
|
|
@@ -452,12 +447,12 @@ export class QueryBuilderHelper {
|
|
|
452
447
|
parts.push(`(${this.subQueries[key]}) ${replacement} ${val}`);
|
|
453
448
|
return { sql: parts.join(' and '), params };
|
|
454
449
|
}
|
|
455
|
-
const [a, f] = this.splitField(key);
|
|
456
|
-
const prop = this.getProperty(f, a);
|
|
450
|
+
const [a, f] = rawField ? [] : this.splitField(key);
|
|
451
|
+
const prop = f && this.getProperty(f, a);
|
|
457
452
|
if (op === '$fulltext') {
|
|
458
453
|
/* v8 ignore next */
|
|
459
454
|
if (!prop) {
|
|
460
|
-
throw new Error(`Cannot use $fulltext operator on ${key}, property not found`);
|
|
455
|
+
throw new Error(`Cannot use $fulltext operator on ${String(key)}, property not found`);
|
|
461
456
|
}
|
|
462
457
|
const { sql, params: params2 } = raw(this.platform.getFullTextWhereClause(prop), {
|
|
463
458
|
column: this.mapper(key, type, undefined, null),
|
|
@@ -469,7 +464,7 @@ export class QueryBuilderHelper {
|
|
|
469
464
|
else if (['$in', '$nin'].includes(op) && Array.isArray(value[op]) && value[op].length === 0) {
|
|
470
465
|
parts.push(`1 = ${op === '$in' ? 0 : 1}`);
|
|
471
466
|
}
|
|
472
|
-
else if (value[op] instanceof
|
|
467
|
+
else if (value[op] instanceof Raw || value[op] instanceof NativeQueryBuilder) {
|
|
473
468
|
const query = value[op] instanceof NativeQueryBuilder ? value[op].toRaw() : value[op];
|
|
474
469
|
const mappedKey = this.mapper(key, type, query, null);
|
|
475
470
|
let sql = query.sql;
|
|
@@ -501,7 +496,7 @@ export class QueryBuilderHelper {
|
|
|
501
496
|
params.push(item);
|
|
502
497
|
}
|
|
503
498
|
else {
|
|
504
|
-
value.forEach(
|
|
499
|
+
value.forEach(p => params.push(p));
|
|
505
500
|
}
|
|
506
501
|
return `(${value.map(() => '?').join(', ')})`;
|
|
507
502
|
}
|
|
@@ -528,6 +523,31 @@ export class QueryBuilderHelper {
|
|
|
528
523
|
}
|
|
529
524
|
return replacement;
|
|
530
525
|
}
|
|
526
|
+
validateQueryOrder(orderBy) {
|
|
527
|
+
const strKeys = [];
|
|
528
|
+
const rawKeys = [];
|
|
529
|
+
for (const key of Utils.getObjectQueryKeys(orderBy)) {
|
|
530
|
+
const raw = Raw.getKnownFragment(key);
|
|
531
|
+
if (raw) {
|
|
532
|
+
rawKeys.push(raw);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
strKeys.push(key);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (strKeys.length > 0 && rawKeys.length > 0) {
|
|
539
|
+
const example = [
|
|
540
|
+
...strKeys.map(key => ({ [key]: orderBy[key] })),
|
|
541
|
+
...rawKeys.map(rawKey => ({ [`raw('${rawKey.sql}')`]: orderBy[rawKey] })),
|
|
542
|
+
];
|
|
543
|
+
throw new Error([
|
|
544
|
+
`Invalid "orderBy": You are mixing field-based keys and raw SQL fragments inside a single object.`,
|
|
545
|
+
`This is not allowed because object key order cannot reliably preserve evaluation order.`,
|
|
546
|
+
`To fix this, split them into separate objects inside an array:\n`,
|
|
547
|
+
`orderBy: ${inspect(example, { depth: 5 }).replace(/"raw\('(.*)'\)"/g, `[raw('$1')]`)}`,
|
|
548
|
+
].join('\n'));
|
|
549
|
+
}
|
|
550
|
+
}
|
|
531
551
|
getQueryOrder(type, orderBy, populate) {
|
|
532
552
|
if (Array.isArray(orderBy)) {
|
|
533
553
|
return orderBy.flatMap(o => this.getQueryOrder(type, o, populate));
|
|
@@ -536,11 +556,11 @@ export class QueryBuilderHelper {
|
|
|
536
556
|
}
|
|
537
557
|
getQueryOrderFromObject(type, orderBy, populate) {
|
|
538
558
|
const ret = [];
|
|
539
|
-
for (const key of
|
|
559
|
+
for (const key of Utils.getObjectQueryKeys(orderBy)) {
|
|
540
560
|
const direction = orderBy[key];
|
|
541
561
|
const order = typeof direction === 'number' ? QueryOrderNumeric[direction] : direction;
|
|
542
|
-
|
|
543
|
-
|
|
562
|
+
if (Raw.isKnownFragmentSymbol(key)) {
|
|
563
|
+
const raw = Raw.getKnownFragment(key);
|
|
544
564
|
ret.push(...this.platform.getOrderByExpression(this.platform.formatQuery(raw.sql, raw.params), order));
|
|
545
565
|
continue;
|
|
546
566
|
}
|
|
@@ -549,7 +569,7 @@ export class QueryBuilderHelper {
|
|
|
549
569
|
let [alias, field] = this.splitField(f, true);
|
|
550
570
|
alias = populate[alias] || alias;
|
|
551
571
|
const prop = this.getProperty(field, alias);
|
|
552
|
-
const noPrefix = (prop?.persist === false && !prop.formula && !prop.embedded) ||
|
|
572
|
+
const noPrefix = (prop?.persist === false && !prop.formula && !prop.embedded) || Raw.isKnownFragment(f);
|
|
553
573
|
const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
|
|
554
574
|
/* v8 ignore next */
|
|
555
575
|
const rawColumn = typeof column === 'string' ? column.split('.').map(e => this.platform.quoteIdentifier(e)).join('.') : column;
|
|
@@ -652,7 +672,7 @@ export class QueryBuilderHelper {
|
|
|
652
672
|
if (!this.isPrefixed(field)) {
|
|
653
673
|
const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
|
|
654
674
|
const fieldName = this.fieldName(field, this.alias, always, idx);
|
|
655
|
-
if (fieldName instanceof
|
|
675
|
+
if (fieldName instanceof Raw) {
|
|
656
676
|
return fieldName.sql;
|
|
657
677
|
}
|
|
658
678
|
ret = alias + fieldName;
|
|
@@ -661,7 +681,7 @@ export class QueryBuilderHelper {
|
|
|
661
681
|
const [a, ...rest] = field.split('.');
|
|
662
682
|
const f = rest.join('.');
|
|
663
683
|
const fieldName = this.fieldName(f, a, always, idx);
|
|
664
|
-
if (fieldName instanceof
|
|
684
|
+
if (fieldName instanceof Raw) {
|
|
665
685
|
return fieldName.sql;
|
|
666
686
|
}
|
|
667
687
|
ret = a + '.' + fieldName;
|
|
@@ -683,7 +703,7 @@ export class QueryBuilderHelper {
|
|
|
683
703
|
}
|
|
684
704
|
for (const sub of subCondition) {
|
|
685
705
|
// skip nesting parens if the value is simple = scalar or object without operators or with only single key, being the operator
|
|
686
|
-
const keys =
|
|
706
|
+
const keys = Utils.getObjectQueryKeys(sub);
|
|
687
707
|
const val = sub[keys[0]];
|
|
688
708
|
const simple = !Utils.isPlainObject(val) || Utils.getObjectKeysSize(val) === 1 || Object.keys(val).every(k => !Utils.isOperator(k));
|
|
689
709
|
if (keys.length === 1 && simple) {
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReferenceKind,
|
|
1
|
+
import { ReferenceKind, isRaw, } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseTable } from './DatabaseTable.js';
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
@@ -108,15 +108,11 @@ export class DatabaseSchema {
|
|
|
108
108
|
table.addIndex(meta, { properties: meta.props.filter(prop => prop.primary).map(prop => prop.name) }, 'primary');
|
|
109
109
|
for (const check of meta.checks) {
|
|
110
110
|
const columnName = check.property ? meta.properties[check.property].fieldNames[0] : undefined;
|
|
111
|
-
|
|
112
|
-
const raw = RawQueryFragment.getKnownFragment(expression);
|
|
113
|
-
if (raw) {
|
|
114
|
-
expression = platform.formatQuery(raw.sql, raw.params);
|
|
115
|
-
}
|
|
111
|
+
const expression = isRaw(check.expression) ? platform.formatQuery(check.expression.sql, check.expression.params) : check.expression;
|
|
116
112
|
table.addCheck({
|
|
117
113
|
name: check.name,
|
|
118
114
|
expression,
|
|
119
|
-
definition: `check (${
|
|
115
|
+
definition: `check (${expression})`,
|
|
120
116
|
columnName,
|
|
121
117
|
});
|
|
122
118
|
}
|
package/schema/SchemaHelper.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Connection, type Dictionary } from '@mikro-orm/core';
|
|
1
|
+
import { type Connection, type Dictionary, RawQueryFragment } from '@mikro-orm/core';
|
|
2
2
|
import type { AbstractSqlConnection } from '../AbstractSqlConnection.js';
|
|
3
3
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
4
4
|
import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference } from '../typings.js';
|
|
@@ -39,7 +39,7 @@ export declare abstract class SchemaHelper {
|
|
|
39
39
|
getNamespaces(connection: AbstractSqlConnection): Promise<string[]>;
|
|
40
40
|
protected mapIndexes(indexes: IndexDef[]): Promise<IndexDef[]>;
|
|
41
41
|
mapForeignKeys(fks: any[], tableName: string, schemaName?: string): Dictionary;
|
|
42
|
-
normalizeDefaultValue(defaultValue: string, length?: number, defaultValues?: Dictionary<string[]>): string | number;
|
|
42
|
+
normalizeDefaultValue(defaultValue: string | RawQueryFragment, length?: number, defaultValues?: Dictionary<string[]>): string | number;
|
|
43
43
|
getCreateDatabaseSQL(name: string): string;
|
|
44
44
|
getDropDatabaseSQL(name: string): string;
|
|
45
45
|
getCreateNamespaceSQL(name: string): string;
|
package/schema/SchemaHelper.js
CHANGED
|
@@ -323,9 +323,8 @@ export class SchemaHelper {
|
|
|
323
323
|
if (defaultValue == null) {
|
|
324
324
|
return defaultValue;
|
|
325
325
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
return this.platform.formatQuery(raw.sql, raw.params);
|
|
326
|
+
if (defaultValue instanceof RawQueryFragment) {
|
|
327
|
+
return this.platform.formatQuery(defaultValue.sql, defaultValue.params);
|
|
329
328
|
}
|
|
330
329
|
const genericValue = defaultValue.replace(/\(\d+\)/, '(?)').toLowerCase();
|
|
331
330
|
const norm = defaultValues[genericValue];
|