@mikro-orm/sql 7.0.0-dev.113 → 7.0.0-dev.115

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.
@@ -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
  }
@@ -133,7 +128,7 @@ export class QueryBuilderHelper {
133
128
  }
134
129
  joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
135
130
  const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
136
- const table = this.getTableName(prop.type);
131
+ const table = this.getTableName(prop.targetMeta.class);
137
132
  const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
138
133
  const inverseJoinColumns = prop.referencedColumnNames;
139
134
  const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
@@ -147,7 +142,7 @@ export class QueryBuilderHelper {
147
142
  joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
148
143
  return {
149
144
  prop, type, cond, ownerAlias, alias,
150
- table: this.getTableName(prop.type),
145
+ table: this.getTableName(prop.targetMeta.class),
151
146
  schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta, { schema }),
152
147
  joinColumns: prop.referencedColumnNames,
153
148
  primaryKeys: prop.fieldNames,
@@ -172,7 +167,7 @@ export class QueryBuilderHelper {
172
167
  if (type === JoinType.pivotJoin) {
173
168
  return ret;
174
169
  }
175
- const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
170
+ const prop2 = pivotMeta.relations[prop.owner ? 1 : 0];
176
171
  ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, cond, schema);
177
172
  ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
178
173
  const tmp = prop2.referencedTableName.split('.');
@@ -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 ')}`;
@@ -269,7 +264,7 @@ export class QueryBuilderHelper {
269
264
  }
270
265
  getTableName(entityName) {
271
266
  const meta = this.metadata.find(entityName);
272
- return meta ? meta.collection : entityName;
267
+ return meta?.tableName ?? Utils.className(entityName);
273
268
  }
274
269
  /**
275
270
  * Checks whether the RE can be rewritten to simple LIKE query
@@ -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;
@@ -624,7 +644,7 @@ export class QueryBuilderHelper {
624
644
  getLockSQL(qb, lockMode, lockTables = [], joinsMap) {
625
645
  const meta = this.metadata.find(this.entityName);
626
646
  if (lockMode === LockMode.OPTIMISTIC && meta && !meta.versionProperty) {
627
- throw OptimisticLockError.lockFailed(this.entityName);
647
+ throw OptimisticLockError.lockFailed(Utils.className(this.entityName));
628
648
  }
629
649
  if (lockMode !== LockMode.OPTIMISTIC && lockTables.length === 0 && joinsMap) {
630
650
  const joins = Object.values(joinsMap);
@@ -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) {
@@ -719,9 +739,9 @@ export class QueryBuilderHelper {
719
739
  }
720
740
  getProperty(field, alias) {
721
741
  const entityName = this.aliasMap[alias]?.entityName || this.entityName;
722
- const meta = this.metadata.find(entityName);
742
+ const meta = this.metadata.get(entityName);
723
743
  // check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
724
- if (alias && meta) {
744
+ if (alias) {
725
745
  const prop = meta.properties[alias];
726
746
  if (prop?.kind === ReferenceKind.EMBEDDED) {
727
747
  // we want to select the full object property so hydration works as expected
@@ -733,13 +753,10 @@ export class QueryBuilderHelper {
733
753
  return nest(prop);
734
754
  }
735
755
  }
736
- if (meta) {
737
- if (meta.properties[field]) {
738
- return meta.properties[field];
739
- }
740
- return meta.relations.find(prop => prop.fieldNames?.some(name => field === name));
756
+ if (meta.properties[field]) {
757
+ return meta.properties[field];
741
758
  }
742
- return undefined;
759
+ return meta.relations.find(prop => prop.fieldNames?.some(name => field === name));
743
760
  }
744
761
  isTableNameAliasRequired(type) {
745
762
  return [QueryType.SELECT, QueryType.COUNT].includes(type);
@@ -12,7 +12,7 @@ export class ScalarCriteriaNode extends CriteriaNode {
12
12
  if (this.shouldJoin(qb, nestedAlias)) {
13
13
  const path = this.getPath();
14
14
  const parentPath = this.parent.getPath(); // the parent is always there, otherwise `shouldJoin` would return `false`
15
- const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
15
+ const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotEntity ?? this.entityName);
16
16
  const field = this.aliased(this.prop.name, options?.alias);
17
17
  const type = this.prop.kind === ReferenceKind.MANY_TO_MANY ? JoinType.pivotJoin : JoinType.leftJoin;
18
18
  qb.join(field, nestedAlias, undefined, type, path);
@@ -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];
@@ -114,7 +114,7 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
114
114
  await this.execute(this.helper.disableForeignKeysSQL());
115
115
  const schema = options?.schema ?? this.config.get('schema', this.platform.getDefaultSchemaName());
116
116
  for (const meta of this.getOrderedMetadata(schema).reverse()) {
117
- await this.driver.createQueryBuilder(meta.className, this.em?.getTransactionContext(), 'write', false)
117
+ await this.driver.createQueryBuilder(meta.class, this.em?.getTransactionContext(), 'write', false)
118
118
  .withSchema(schema)
119
119
  .truncate()
120
120
  .execute();
@@ -197,7 +197,7 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
197
197
  const toSchema = this.getTargetSchema(options.schema);
198
198
  const schemas = toSchema.getNamespaces();
199
199
  const fromSchema = options.fromSchema ?? (await DatabaseSchema.create(this.connection, this.platform, this.config, options.schema, schemas, undefined, this.options.skipTables));
200
- const wildcardSchemaTables = Object.values(this.metadata.getAll()).filter(meta => meta.schema === '*').map(meta => meta.tableName);
200
+ const wildcardSchemaTables = [...this.metadata.getAll().values()].filter(meta => meta.schema === '*').map(meta => meta.tableName);
201
201
  fromSchema.prune(options.schema, wildcardSchemaTables);
202
202
  toSchema.prune(options.schema, wildcardSchemaTables);
203
203
  return { fromSchema, toSchema };