@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.
@@ -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 instanceof RawQueryFragment) {
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
- const rawField = RawQueryFragment.getKnownFragment(cond);
310
- if (rawField) {
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', 'rawFragments', 'aliasCounter',
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 instanceof RawQueryFragment) {
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 instanceof RawQueryFragment) {
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
- const cols = this.helper.mapJoinColumns(this.type, this._joins[f]);
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 instanceof RawQueryFragment) {
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 [field, direction] of Object.entries(orderMap)) {
1376
- if (RawQueryFragment.isKnownFragment(field)) {
1377
- const rawField = RawQueryFragment.getKnownFragment(field, false);
1378
- this.rawFragments.add(field);
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 instanceof RawQueryFragment) {
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 instanceof RawQueryFragment) {
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(f => RawQueryFragment.isKnownFragment(f))) {
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, RawQueryFragment } from '@mikro-orm/core';
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 | RawQueryFragment, type?: QueryType): string;
19
- mapper(field: string | RawQueryFragment, type?: QueryType, value?: any, alias?: string | null): 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 | RawQueryFragment)[];
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[] | RawQueryFragment;
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, RawQueryFragment, ReferenceKind, Utils, ValidationError, } from '@mikro-orm/core';
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
- // do not wrap custom expressions
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(...subquery.params);
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 Object.keys(cond)) {
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(...res.params);
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
- const raw = RawQueryFragment.getKnownFragment(key);
376
- if (raw) {
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 val = this.getValueReplacement(fields, value[0], params, key);
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 fields = Utils.splitPrimaryKeys(key);
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 RawQueryFragment || value[op] instanceof NativeQueryBuilder) {
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(v => params.push(v));
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 Object.keys(orderBy)) {
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
- const raw = RawQueryFragment.getKnownFragment(key);
543
- if (raw) {
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) || RawQueryFragment.isKnownFragment(f);
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 RawQueryFragment) {
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 RawQueryFragment) {
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 = Object.keys(sub);
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) {
@@ -1,4 +1,4 @@
1
- import { ReferenceKind, RawQueryFragment, } from '@mikro-orm/core';
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
- let expression = check.expression;
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 (${check.expression})`,
115
+ definition: `check (${expression})`,
120
116
  columnName,
121
117
  });
122
118
  }
@@ -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;
@@ -323,9 +323,8 @@ export class SchemaHelper {
323
323
  if (defaultValue == null) {
324
324
  return defaultValue;
325
325
  }
326
- const raw = RawQueryFragment.getKnownFragment(defaultValue);
327
- if (raw) {
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];