@mikro-orm/sql 7.0.0-dev.99 → 7.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/AbstractSqlConnection.d.ts +2 -4
  2. package/AbstractSqlConnection.js +3 -7
  3. package/AbstractSqlDriver.d.ts +89 -23
  4. package/AbstractSqlDriver.js +630 -197
  5. package/AbstractSqlPlatform.d.ts +11 -5
  6. package/AbstractSqlPlatform.js +18 -5
  7. package/PivotCollectionPersister.d.ts +5 -0
  8. package/PivotCollectionPersister.js +30 -12
  9. package/SqlEntityManager.d.ts +2 -2
  10. package/dialects/mysql/{MySqlPlatform.d.ts → BaseMySqlPlatform.d.ts} +4 -3
  11. package/dialects/mysql/{MySqlPlatform.js → BaseMySqlPlatform.js} +9 -4
  12. package/dialects/mysql/MySqlSchemaHelper.d.ts +12 -1
  13. package/dialects/mysql/MySqlSchemaHelper.js +97 -6
  14. package/dialects/mysql/index.d.ts +1 -2
  15. package/dialects/mysql/index.js +1 -2
  16. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +106 -0
  17. package/dialects/postgresql/BasePostgreSqlPlatform.js +350 -0
  18. package/dialects/postgresql/FullTextType.d.ts +14 -0
  19. package/dialects/postgresql/FullTextType.js +59 -0
  20. package/dialects/postgresql/PostgreSqlExceptionConverter.d.ts +8 -0
  21. package/dialects/postgresql/PostgreSqlExceptionConverter.js +47 -0
  22. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +90 -0
  23. package/dialects/postgresql/PostgreSqlSchemaHelper.js +732 -0
  24. package/dialects/postgresql/index.d.ts +3 -0
  25. package/dialects/postgresql/index.js +3 -0
  26. package/dialects/sqlite/BaseSqliteConnection.d.ts +1 -0
  27. package/dialects/sqlite/BaseSqliteConnection.js +13 -0
  28. package/dialects/sqlite/BaseSqlitePlatform.d.ts +6 -0
  29. package/dialects/sqlite/BaseSqlitePlatform.js +12 -0
  30. package/dialects/sqlite/SqliteSchemaHelper.d.ts +25 -0
  31. package/dialects/sqlite/SqliteSchemaHelper.js +145 -19
  32. package/dialects/sqlite/index.d.ts +0 -1
  33. package/dialects/sqlite/index.js +0 -1
  34. package/package.json +5 -6
  35. package/plugin/transformer.d.ts +1 -1
  36. package/plugin/transformer.js +1 -1
  37. package/query/CriteriaNode.d.ts +9 -5
  38. package/query/CriteriaNode.js +16 -15
  39. package/query/CriteriaNodeFactory.d.ts +6 -6
  40. package/query/CriteriaNodeFactory.js +33 -31
  41. package/query/NativeQueryBuilder.d.ts +3 -2
  42. package/query/NativeQueryBuilder.js +1 -2
  43. package/query/ObjectCriteriaNode.js +51 -36
  44. package/query/QueryBuilder.d.ts +569 -79
  45. package/query/QueryBuilder.js +614 -171
  46. package/query/QueryBuilderHelper.d.ts +24 -16
  47. package/query/QueryBuilderHelper.js +167 -78
  48. package/query/ScalarCriteriaNode.js +2 -2
  49. package/query/raw.d.ts +11 -3
  50. package/query/raw.js +1 -2
  51. package/schema/DatabaseSchema.d.ts +15 -2
  52. package/schema/DatabaseSchema.js +143 -15
  53. package/schema/DatabaseTable.d.ts +12 -0
  54. package/schema/DatabaseTable.js +91 -31
  55. package/schema/SchemaComparator.d.ts +8 -0
  56. package/schema/SchemaComparator.js +127 -3
  57. package/schema/SchemaHelper.d.ts +26 -3
  58. package/schema/SchemaHelper.js +98 -11
  59. package/schema/SqlSchemaGenerator.d.ts +10 -0
  60. package/schema/SqlSchemaGenerator.js +137 -9
  61. package/tsconfig.build.tsbuildinfo +1 -0
  62. package/typings.d.ts +78 -38
  63. package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +0 -1
  64. package/dialects/postgresql/PostgreSqlTableCompiler.js +0 -1
@@ -1,6 +1,6 @@
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 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 { Field, JoinOptions } from '../typings.js';
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
  /**
@@ -12,11 +12,17 @@ export declare class QueryBuilderHelper {
12
12
  private readonly aliasMap;
13
13
  private readonly subQueries;
14
14
  private readonly driver;
15
+ private readonly tptAliasMap;
15
16
  private readonly platform;
16
17
  private readonly metadata;
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
+ constructor(entityName: EntityName, alias: string, aliasMap: Dictionary<Alias<any>>, subQueries: Dictionary<string>, driver: AbstractSqlDriver, tptAliasMap?: Dictionary<string>);
19
+ /**
20
+ * For TPT inheritance, finds the correct alias for a property based on which entity owns it.
21
+ * Returns the main alias if not a TPT property or if the property belongs to the main entity.
22
+ */
23
+ getTPTAliasForProperty(propName: string, defaultAlias: string): string;
24
+ mapper(field: string | Raw | RawQueryFragmentSymbol, type?: QueryType): string;
25
+ mapper(field: string | Raw | RawQueryFragmentSymbol, type?: QueryType, value?: any, alias?: string | null, schema?: string): string;
20
26
  processData(data: Dictionary, convertCustomTypes: boolean, multi?: boolean): any;
21
27
  joinOneToReference(prop: EntityProperty, ownerAlias: string, alias: string, type: JoinType, cond?: Dictionary, schema?: string): JoinOptions;
22
28
  joinManyToOneReference(prop: EntityProperty, ownerAlias: string, alias: string, type: JoinType, cond?: Dictionary, schema?: string): JoinOptions;
@@ -26,9 +32,9 @@ export declare class QueryBuilderHelper {
26
32
  sql: string;
27
33
  params: unknown[];
28
34
  };
29
- mapJoinColumns(type: QueryType, join: JoinOptions): (string | RawQueryFragment)[];
35
+ mapJoinColumns(type: QueryType, join: JoinOptions): (string | Raw)[];
30
36
  isOneToOneInverse(field: string, meta?: EntityMetadata): boolean;
31
- getTableName(entityName: string): string;
37
+ getTableName(entityName: EntityName): string;
32
38
  /**
33
39
  * Checks whether the RE can be rewritten to simple LIKE query
34
40
  */
@@ -45,9 +51,10 @@ export declare class QueryBuilderHelper {
45
51
  private processObjectSubCondition;
46
52
  private getValueReplacement;
47
53
  private getOperatorReplacement;
48
- getQueryOrder(type: QueryType, orderBy: FlatQueryOrderMap | FlatQueryOrderMap[], populate: Dictionary<string>): string[];
49
- getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>): string[];
50
- finalize(type: QueryType, qb: NativeQueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: Field<any>[]): void;
54
+ validateQueryOrder<T>(orderBy: QueryOrderMap<T>): void;
55
+ getQueryOrder(type: QueryType, orderBy: FlatQueryOrderMap | FlatQueryOrderMap[], populate: Dictionary<string>, collation?: string): string[];
56
+ getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>, collation?: string): string[];
57
+ finalize(type: QueryType, qb: NativeQueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: InternalField<any>[]): void;
51
58
  splitField<T>(field: EntityKey<T>, greedyAlias?: boolean): [string, EntityKey<T>, string | undefined];
52
59
  getLockSQL(qb: NativeQueryBuilder, lockMode: LockMode, lockTables?: string[], joinsMap?: Dictionary<JoinOptions>): void;
53
60
  updateVersionProperty(qb: NativeQueryBuilder, data: Dictionary): void;
@@ -57,17 +64,18 @@ export declare class QueryBuilderHelper {
57
64
  private fieldName;
58
65
  getProperty(field: string, alias?: string): EntityProperty | undefined;
59
66
  isTableNameAliasRequired(type: QueryType): boolean;
60
- processOnConflictCondition(cond: QBFilterQuery, schema?: string): QBFilterQuery;
67
+ processOnConflictCondition(cond: FilterQuery<any>, schema?: string): FilterQuery<any>;
68
+ createFormulaTable(alias: string, meta: EntityMetadata, schema?: string): FormulaTable;
61
69
  }
62
70
  export interface Alias<T> {
63
71
  aliasName: string;
64
- entityName: string;
65
- metadata?: EntityMetadata<T>;
72
+ entityName: EntityName<T>;
73
+ meta: EntityMetadata<T>;
66
74
  subQuery?: NativeQueryBuilder;
67
75
  }
68
76
  export interface OnConflictClause<T> {
69
- fields: string[] | RawQueryFragment;
77
+ fields: string[] | Raw;
70
78
  ignore?: boolean;
71
- merge?: EntityData<T> | Field<T>[];
72
- where?: QBFilterQuery<T>;
79
+ merge?: EntityData<T> | InternalField<T>[];
80
+ where?: FilterQuery<T>;
73
81
  }
@@ -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, QueryHelper, ReferenceKind, Utils, ValidationError, } from '@mikro-orm/core';
2
2
  import { JoinType, QueryType } from './enums.js';
3
3
  import { NativeQueryBuilder } from './NativeQueryBuilder.js';
4
4
  /**
@@ -10,21 +10,51 @@ export class QueryBuilderHelper {
10
10
  aliasMap;
11
11
  subQueries;
12
12
  driver;
13
+ tptAliasMap;
13
14
  platform;
14
15
  metadata;
15
- constructor(entityName, alias, aliasMap, subQueries, driver) {
16
+ constructor(entityName, alias, aliasMap, subQueries, driver, tptAliasMap = {}) {
16
17
  this.entityName = entityName;
17
18
  this.alias = alias;
18
19
  this.aliasMap = aliasMap;
19
20
  this.subQueries = subQueries;
20
21
  this.driver = driver;
22
+ this.tptAliasMap = tptAliasMap;
21
23
  this.platform = this.driver.getPlatform();
22
24
  this.metadata = this.driver.getMetadata();
23
25
  }
24
- mapper(field, type = QueryType.SELECT, value, alias) {
26
+ /**
27
+ * For TPT inheritance, finds the correct alias for a property based on which entity owns it.
28
+ * Returns the main alias if not a TPT property or if the property belongs to the main entity.
29
+ */
30
+ getTPTAliasForProperty(propName, defaultAlias) {
31
+ const meta = this.aliasMap[defaultAlias]?.meta ?? this.metadata.get(this.entityName);
32
+ if (meta?.inheritanceType !== 'tpt' || !meta.tptParent) {
33
+ return defaultAlias;
34
+ }
35
+ // Check if property is in the main entity's ownProps
36
+ if (meta.ownProps?.some(p => p.name === propName || p.fieldNames?.includes(propName))) {
37
+ return defaultAlias;
38
+ }
39
+ // Walk up the TPT hierarchy to find which parent owns this property
40
+ let parentMeta = meta.tptParent;
41
+ while (parentMeta) {
42
+ const parentAlias = this.tptAliasMap[parentMeta.className];
43
+ if (parentAlias && parentMeta.ownProps?.some(p => p.name === propName || p.fieldNames?.includes(propName))) {
44
+ return parentAlias;
45
+ }
46
+ parentMeta = parentMeta.tptParent;
47
+ }
48
+ // Property not found in hierarchy, return default alias
49
+ return defaultAlias;
50
+ }
51
+ mapper(field, type = QueryType.SELECT, value, alias, schema) {
25
52
  if (isRaw(field)) {
26
53
  return raw(field.sql, field.params);
27
54
  }
55
+ if (Raw.isKnownFragmentSymbol(field)) {
56
+ return Raw.getKnownFragment(field);
57
+ }
28
58
  /* v8 ignore next */
29
59
  if (typeof field !== 'string') {
30
60
  return field;
@@ -60,20 +90,17 @@ export class QueryBuilderHelper {
60
90
  }
61
91
  return raw('(' + parts.map(part => this.platform.quoteIdentifier(part)).join(', ') + ')');
62
92
  }
63
- const rawField = RawQueryFragment.getKnownFragment(field);
64
- if (rawField) {
65
- return rawField;
66
- }
67
- const aliasPrefix = isTableNameAliasRequired ? this.alias + '.' : '';
68
93
  const [a, f] = this.splitField(field);
69
94
  const prop = this.getProperty(f, a);
95
+ // For TPT inheritance, resolve the correct alias for this property
96
+ // Only apply TPT resolution when `a` is an actual table alias (in aliasMap),
97
+ // not when it's an embedded property name like 'profile1.identity.links'
98
+ const isTableAlias = !!this.aliasMap[a];
99
+ const baseAlias = isTableAlias ? a : this.alias;
100
+ const resolvedAlias = isTableAlias ? this.getTPTAliasForProperty(prop?.name ?? f, a) : this.alias;
101
+ const aliasPrefix = isTableNameAliasRequired ? resolvedAlias + '.' : '';
70
102
  const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
71
103
  const fkIdx = fkIdx2 === -1 ? 0 : fkIdx2;
72
- let ret = field;
73
- // embeddable nested path instead of a regular property with table alias, reset alias
74
- if (prop?.name === a && prop.embeddedProps[f]) {
75
- return aliasPrefix + prop.fieldNames[fkIdx];
76
- }
77
104
  if (a === prop?.embedded?.[0]) {
78
105
  return aliasPrefix + prop.fieldNames[fkIdx];
79
106
  }
@@ -83,9 +110,12 @@ export class QueryBuilderHelper {
83
110
  }
84
111
  if (prop?.formula) {
85
112
  const alias2 = this.platform.quoteIdentifier(a).toString();
86
- const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]).toString();
87
- const as = alias === null ? '' : ` as ${aliased}`;
88
- let value = prop.formula(alias2);
113
+ const aliasName = alias === undefined ? prop.fieldNames[0] : alias;
114
+ const as = aliasName === null ? '' : ` as ${this.platform.quoteIdentifier(aliasName)}`;
115
+ const meta = this.aliasMap[a]?.meta ?? this.metadata.get(this.entityName);
116
+ const table = this.createFormulaTable(alias2, meta, schema);
117
+ const columns = meta.createColumnMappingObject(p => this.getTPTAliasForProperty(p.name, a), alias2);
118
+ let value = this.driver.evaluateFormula(prop.formula, columns, table);
89
119
  if (!this.isTableNameAliasRequired(type)) {
90
120
  value = value.replaceAll(alias2 + '.', '');
91
121
  }
@@ -107,17 +137,14 @@ export class QueryBuilderHelper {
107
137
  }
108
138
  return raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[fkIdx])}`);
109
139
  }
110
- // do not wrap custom expressions
111
- if (!rawField) {
112
- ret = this.prefix(field, false, false, fkIdx);
113
- }
140
+ let ret = this.prefix(field, false, false, fkIdx);
114
141
  if (alias) {
115
142
  ret += ' as ' + alias;
116
143
  }
117
144
  if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
118
145
  return ret;
119
146
  }
120
- return this.alias + '.' + ret;
147
+ return resolvedAlias + '.' + ret;
121
148
  }
122
149
  processData(data, convertCustomTypes, multi = false) {
123
150
  if (Array.isArray(data)) {
@@ -133,12 +160,20 @@ export class QueryBuilderHelper {
133
160
  }
134
161
  joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
135
162
  const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
136
- const table = this.getTableName(prop.type);
163
+ const table = this.getTableName(prop.targetMeta.class);
137
164
  const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
138
165
  const inverseJoinColumns = prop.referencedColumnNames;
139
166
  const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
140
167
  schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
141
168
  cond = Utils.merge(cond, prop.where);
169
+ // For inverse side of polymorphic relations, add discriminator condition
170
+ if (!prop.owner && prop2.polymorphic && prop2.discriminatorColumn && prop2.discriminatorMap) {
171
+ const ownerMeta = this.aliasMap[ownerAlias]?.meta ?? this.metadata.get(this.entityName);
172
+ const discriminatorValue = QueryHelper.findDiscriminatorValue(prop2.discriminatorMap, ownerMeta.class);
173
+ if (discriminatorValue) {
174
+ cond[`${alias}.${prop2.discriminatorColumn}`] = discriminatorValue;
175
+ }
176
+ }
142
177
  return {
143
178
  prop, type, cond, ownerAlias, alias, table, schema,
144
179
  joinColumns, inverseJoinColumns, primaryKeys,
@@ -147,10 +182,12 @@ export class QueryBuilderHelper {
147
182
  joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
148
183
  return {
149
184
  prop, type, cond, ownerAlias, alias,
150
- table: this.getTableName(prop.type),
185
+ table: this.getTableName(prop.targetMeta.class),
151
186
  schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta, { schema }),
152
187
  joinColumns: prop.referencedColumnNames,
153
- primaryKeys: prop.fieldNames,
188
+ // For polymorphic relations, fieldNames includes the discriminator column which is not
189
+ // part of the join condition - use joinColumns (the FK columns only) instead
190
+ primaryKeys: prop.polymorphic ? prop.joinColumns : prop.fieldNames,
154
191
  };
155
192
  }
156
193
  joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path, schema) {
@@ -172,7 +209,7 @@ export class QueryBuilderHelper {
172
209
  if (type === JoinType.pivotJoin) {
173
210
  return ret;
174
211
  }
175
- const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
212
+ const prop2 = pivotMeta.relations[prop.owner ? 1 : 0];
176
213
  ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, cond, schema);
177
214
  ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
178
215
  const tmp = prop2.referencedTableName.split('.');
@@ -205,8 +242,11 @@ export class QueryBuilderHelper {
205
242
  join.primaryKeys.forEach((primaryKey, idx) => {
206
243
  const right = `${join.alias}.${join.joinColumns[idx]}`;
207
244
  if (join.prop.formula) {
208
- const alias = this.platform.quoteIdentifier(join.ownerAlias);
209
- const left = join.prop.formula(alias);
245
+ const quotedAlias = this.platform.quoteIdentifier(join.ownerAlias).toString();
246
+ const ownerMeta = this.aliasMap[join.ownerAlias]?.meta ?? this.metadata.get(this.entityName);
247
+ const table = this.createFormulaTable(quotedAlias, ownerMeta, schema);
248
+ const columns = ownerMeta.createColumnMappingObject(p => this.getTPTAliasForProperty(p.name, join.ownerAlias), quotedAlias);
249
+ const left = this.driver.evaluateFormula(join.prop.formula, columns, table);
210
250
  conditions.push(`${left} = ${this.platform.quoteIdentifier(right)}`);
211
251
  return;
212
252
  }
@@ -216,11 +256,20 @@ export class QueryBuilderHelper {
216
256
  conditions.push(`${left} = ${this.platform.quoteIdentifier(right)}`);
217
257
  });
218
258
  }
219
- if (join.prop.targetMeta?.discriminatorValue && !join.path?.endsWith('[pivot]')) {
259
+ if (join.prop.targetMeta?.root.inheritanceType === 'sti' && join.prop.targetMeta?.discriminatorValue && !join.path?.endsWith('[pivot]')) {
220
260
  const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
221
261
  const alias = join.inverseAlias ?? join.alias;
222
262
  join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
223
263
  }
264
+ // For polymorphic relations, add discriminator condition to filter by target entity type
265
+ if (join.prop.polymorphic && join.prop.discriminatorColumn && join.prop.discriminatorMap) {
266
+ const discriminatorValue = QueryHelper.findDiscriminatorValue(join.prop.discriminatorMap, join.prop.targetMeta.class);
267
+ if (discriminatorValue) {
268
+ const discriminatorCol = this.platform.quoteIdentifier(`${join.ownerAlias}.${join.prop.discriminatorColumn}`);
269
+ conditions.push(`${discriminatorCol} = ?`);
270
+ params.push(discriminatorValue);
271
+ }
272
+ }
224
273
  let sql = method + ' ';
225
274
  if (join.nested) {
226
275
  sql += `(${this.platform.quoteIdentifier(table)} as ${this.platform.quoteIdentifier(join.alias)}`;
@@ -243,7 +292,7 @@ export class QueryBuilderHelper {
243
292
  this.alias = oldAlias;
244
293
  if (subquery.sql) {
245
294
  conditions.push(subquery.sql);
246
- params.push(...subquery.params);
295
+ subquery.params.forEach(p => params.push(p));
247
296
  }
248
297
  if (conditions.length > 0) {
249
298
  sql += ` on ${conditions.join(' and ')}`;
@@ -265,11 +314,11 @@ export class QueryBuilderHelper {
265
314
  isOneToOneInverse(field, meta) {
266
315
  meta ??= this.metadata.find(this.entityName);
267
316
  const prop = meta.properties[field.replace(/:ref$/, '')];
268
- return prop && prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
317
+ return prop?.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
269
318
  }
270
319
  getTableName(entityName) {
271
320
  const meta = this.metadata.find(entityName);
272
- return meta ? meta.collection : entityName;
321
+ return meta?.tableName ?? Utils.className(entityName);
273
322
  }
274
323
  /**
275
324
  * Checks whether the RE can be rewritten to simple LIKE query
@@ -332,7 +381,7 @@ export class QueryBuilderHelper {
332
381
  _appendQueryCondition(type, cond, operator) {
333
382
  const parts = [];
334
383
  const params = [];
335
- for (const k of Object.keys(cond)) {
384
+ for (const k of Utils.getObjectQueryKeys(cond)) {
336
385
  if (k === '$and' || k === '$or') {
337
386
  if (operator) {
338
387
  this.append(() => this.appendGroupCondition(type, k, cond[k]), parts, params, operator);
@@ -344,7 +393,7 @@ export class QueryBuilderHelper {
344
393
  if (k === '$not') {
345
394
  const res = this._appendQueryCondition(type, cond[k]);
346
395
  parts.push(`not (${res.sql})`);
347
- params.push(...res.params);
396
+ res.params.forEach(p => params.push(p));
348
397
  continue;
349
398
  }
350
399
  this.append(() => this.appendQuerySubCondition(type, cond, k), parts, params);
@@ -362,7 +411,6 @@ export class QueryBuilderHelper {
362
411
  appendQuerySubCondition(type, cond, key) {
363
412
  const parts = [];
364
413
  const params = [];
365
- const fields = Utils.splitPrimaryKeys(key);
366
414
  if (this.isSimpleRegExp(cond[key])) {
367
415
  parts.push(`${this.platform.quoteIdentifier(this.mapper(key, type))} like ?`);
368
416
  params.push(this.getRegExpParam(cond[key]));
@@ -372,19 +420,21 @@ export class QueryBuilderHelper {
372
420
  return this.processObjectSubCondition(cond, key, type);
373
421
  }
374
422
  const op = cond[key] === null ? 'is' : '=';
375
- const raw = RawQueryFragment.getKnownFragment(key);
376
- if (raw) {
423
+ if (Raw.isKnownFragmentSymbol(key)) {
424
+ const raw = Raw.getKnownFragment(key);
377
425
  const sql = raw.sql.replaceAll(ALIAS_REPLACEMENT, this.alias);
378
426
  const value = Utils.asArray(cond[key]);
379
427
  params.push(...raw.params);
380
428
  if (value.length > 0) {
381
- const val = this.getValueReplacement(fields, value[0], params, key);
429
+ const k = key;
430
+ const val = this.getValueReplacement([k], value[0], params, k);
382
431
  parts.push(`${sql} ${op} ${val}`);
383
432
  return { sql: parts.join(' and '), params };
384
433
  }
385
434
  parts.push(sql);
386
435
  return { sql: parts.join(' and '), params };
387
436
  }
437
+ const fields = Utils.splitPrimaryKeys(key);
388
438
  if (this.subQueries[key]) {
389
439
  const val = this.getValueReplacement(fields, cond[key], params, key);
390
440
  parts.push(`(${this.subQueries[key]}) ${op} ${val}`);
@@ -404,9 +454,7 @@ export class QueryBuilderHelper {
404
454
  }
405
455
  // grouped condition for one field, e.g. `{ age: { $gte: 10, $lt: 50 } }`
406
456
  if (size > 1) {
407
- const rawField = RawQueryFragment.getKnownFragment(key);
408
457
  const subCondition = Object.entries(value).map(([subKey, subValue]) => {
409
- key = rawField?.clone().toString() ?? key;
410
458
  return ({ [key]: { [subKey]: subValue } });
411
459
  });
412
460
  for (const sub of subCondition) {
@@ -424,7 +472,8 @@ export class QueryBuilderHelper {
424
472
  throw ValidationError.invalidQueryCondition(cond);
425
473
  }
426
474
  const replacement = this.getOperatorReplacement(op, value);
427
- const fields = Utils.splitPrimaryKeys(key);
475
+ const rawField = Raw.isKnownFragmentSymbol(key);
476
+ const fields = rawField ? [key] : Utils.splitPrimaryKeys(key);
428
477
  if (fields.length > 1 && Array.isArray(value[op])) {
429
478
  const singleTuple = !value[op].every((v) => Array.isArray(v));
430
479
  if (!this.platform.allowsComparingTuples()) {
@@ -452,12 +501,15 @@ export class QueryBuilderHelper {
452
501
  parts.push(`(${this.subQueries[key]}) ${replacement} ${val}`);
453
502
  return { sql: parts.join(' and '), params };
454
503
  }
455
- const [a, f] = this.splitField(key);
456
- const prop = this.getProperty(f, a);
504
+ const [a, f] = rawField ? [] : this.splitField(key);
505
+ const prop = f && this.getProperty(f, a);
506
+ if (prop && [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
507
+ return { sql: '', params };
508
+ }
457
509
  if (op === '$fulltext') {
458
510
  /* v8 ignore next */
459
511
  if (!prop) {
460
- throw new Error(`Cannot use $fulltext operator on ${key}, property not found`);
512
+ throw new Error(`Cannot use $fulltext operator on ${String(key)}, property not found`);
461
513
  }
462
514
  const { sql, params: params2 } = raw(this.platform.getFullTextWhereClause(prop), {
463
515
  column: this.mapper(key, type, undefined, null),
@@ -469,7 +521,7 @@ export class QueryBuilderHelper {
469
521
  else if (['$in', '$nin'].includes(op) && Array.isArray(value[op]) && value[op].length === 0) {
470
522
  parts.push(`1 = ${op === '$in' ? 0 : 1}`);
471
523
  }
472
- else if (value[op] instanceof RawQueryFragment || value[op] instanceof NativeQueryBuilder) {
524
+ else if (value[op] instanceof Raw || value[op] instanceof NativeQueryBuilder) {
473
525
  const query = value[op] instanceof NativeQueryBuilder ? value[op].toRaw() : value[op];
474
526
  const mappedKey = this.mapper(key, type, query, null);
475
527
  let sql = query.sql;
@@ -501,7 +553,7 @@ export class QueryBuilderHelper {
501
553
  params.push(item);
502
554
  }
503
555
  else {
504
- value.forEach(v => params.push(v));
556
+ value.forEach(p => params.push(p));
505
557
  }
506
558
  return `(${value.map(() => '?').join(', ')})`;
507
559
  }
@@ -528,20 +580,45 @@ export class QueryBuilderHelper {
528
580
  }
529
581
  return replacement;
530
582
  }
531
- getQueryOrder(type, orderBy, populate) {
583
+ validateQueryOrder(orderBy) {
584
+ const strKeys = [];
585
+ const rawKeys = [];
586
+ for (const key of Utils.getObjectQueryKeys(orderBy)) {
587
+ const raw = Raw.getKnownFragment(key);
588
+ if (raw) {
589
+ rawKeys.push(raw);
590
+ }
591
+ else {
592
+ strKeys.push(key);
593
+ }
594
+ }
595
+ if (strKeys.length > 0 && rawKeys.length > 0) {
596
+ const example = [
597
+ ...strKeys.map(key => ({ [key]: orderBy[key] })),
598
+ ...rawKeys.map(rawKey => ({ [`raw('${rawKey.sql}')`]: orderBy[rawKey] })),
599
+ ];
600
+ throw new Error([
601
+ `Invalid "orderBy": You are mixing field-based keys and raw SQL fragments inside a single object.`,
602
+ `This is not allowed because object key order cannot reliably preserve evaluation order.`,
603
+ `To fix this, split them into separate objects inside an array:\n`,
604
+ `orderBy: ${inspect(example, { depth: 5 }).replace(/"raw\('(.*)'\)"/g, `[raw('$1')]`)}`,
605
+ ].join('\n'));
606
+ }
607
+ }
608
+ getQueryOrder(type, orderBy, populate, collation) {
532
609
  if (Array.isArray(orderBy)) {
533
- return orderBy.flatMap(o => this.getQueryOrder(type, o, populate));
610
+ return orderBy.flatMap(o => this.getQueryOrder(type, o, populate, collation));
534
611
  }
535
- return this.getQueryOrderFromObject(type, orderBy, populate);
612
+ return this.getQueryOrderFromObject(type, orderBy, populate, collation);
536
613
  }
537
- getQueryOrderFromObject(type, orderBy, populate) {
614
+ getQueryOrderFromObject(type, orderBy, populate, collation) {
538
615
  const ret = [];
539
- for (const key of Object.keys(orderBy)) {
616
+ for (const key of Utils.getObjectQueryKeys(orderBy)) {
540
617
  const direction = orderBy[key];
541
618
  const order = typeof direction === 'number' ? QueryOrderNumeric[direction] : direction;
542
- const raw = RawQueryFragment.getKnownFragment(key);
543
- if (raw) {
544
- ret.push(...this.platform.getOrderByExpression(this.platform.formatQuery(raw.sql, raw.params), order));
619
+ if (Raw.isKnownFragmentSymbol(key)) {
620
+ const raw = Raw.getKnownFragment(key);
621
+ ret.push(...this.platform.getOrderByExpression(this.platform.formatQuery(raw.sql, raw.params), order, collation));
545
622
  continue;
546
623
  }
547
624
  for (const f of Utils.splitPrimaryKeys(key)) {
@@ -549,7 +626,7 @@ export class QueryBuilderHelper {
549
626
  let [alias, field] = this.splitField(f, true);
550
627
  alias = populate[alias] || alias;
551
628
  const prop = this.getProperty(field, alias);
552
- const noPrefix = (prop?.persist === false && !prop.formula && !prop.embedded) || RawQueryFragment.isKnownFragment(f);
629
+ const noPrefix = (prop?.persist === false && !prop.formula && !prop.embedded) || Raw.isKnownFragment(f);
553
630
  const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
554
631
  /* v8 ignore next */
555
632
  const rawColumn = typeof column === 'string' ? column.split('.').map(e => this.platform.quoteIdentifier(e)).join('.') : column;
@@ -561,10 +638,10 @@ export class QueryBuilderHelper {
561
638
  colPart = this.platform.formatQuery(colPart.sql, colPart.params);
562
639
  }
563
640
  if (Array.isArray(order)) {
564
- order.forEach(part => ret.push(...this.getQueryOrderFromObject(type, part, populate)));
641
+ order.forEach(part => ret.push(...this.getQueryOrderFromObject(type, part, populate, collation)));
565
642
  }
566
643
  else {
567
- ret.push(...this.platform.getOrderByExpression(colPart, order));
644
+ ret.push(...this.platform.getOrderByExpression(colPart, order, collation));
568
645
  }
569
646
  }
570
647
  }
@@ -592,14 +669,15 @@ export class QueryBuilderHelper {
592
669
  if (type === QueryType.UPDATE) {
593
670
  const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]]));
594
671
  if (returningProps.length > 0) {
595
- qb.returning(returningProps.flatMap(prop => {
672
+ const fields = returningProps.flatMap((prop) => {
596
673
  if (prop.hasConvertToJSValueSQL) {
597
674
  const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
598
675
  const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) + ' as ' + this.platform.quoteIdentifier(prop.fieldNames[0]);
599
676
  return [raw(sql)];
600
677
  }
601
678
  return prop.fieldNames;
602
- }));
679
+ });
680
+ qb.returning(fields);
603
681
  }
604
682
  }
605
683
  }
@@ -624,7 +702,7 @@ export class QueryBuilderHelper {
624
702
  getLockSQL(qb, lockMode, lockTables = [], joinsMap) {
625
703
  const meta = this.metadata.find(this.entityName);
626
704
  if (lockMode === LockMode.OPTIMISTIC && meta && !meta.versionProperty) {
627
- throw OptimisticLockError.lockFailed(this.entityName);
705
+ throw OptimisticLockError.lockFailed(Utils.className(this.entityName));
628
706
  }
629
707
  if (lockMode !== LockMode.OPTIMISTIC && lockTables.length === 0 && joinsMap) {
630
708
  const joins = Object.values(joinsMap);
@@ -650,9 +728,11 @@ export class QueryBuilderHelper {
650
728
  prefix(field, always = false, quote = false, idx) {
651
729
  let ret;
652
730
  if (!this.isPrefixed(field)) {
653
- const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
654
- const fieldName = this.fieldName(field, this.alias, always, idx);
655
- if (fieldName instanceof RawQueryFragment) {
731
+ // For TPT inheritance, resolve the correct alias for this property
732
+ const tptAlias = this.getTPTAliasForProperty(field, this.alias);
733
+ const alias = always ? (quote ? tptAlias : this.platform.quoteIdentifier(tptAlias)) + '.' : '';
734
+ const fieldName = this.fieldName(field, tptAlias, always, idx);
735
+ if (fieldName instanceof Raw) {
656
736
  return fieldName.sql;
657
737
  }
658
738
  ret = alias + fieldName;
@@ -660,11 +740,16 @@ export class QueryBuilderHelper {
660
740
  else {
661
741
  const [a, ...rest] = field.split('.');
662
742
  const f = rest.join('.');
663
- const fieldName = this.fieldName(f, a, always, idx);
664
- if (fieldName instanceof RawQueryFragment) {
743
+ // For TPT inheritance, resolve the correct alias for this property
744
+ // Only apply TPT resolution when `a` is an actual table alias (in aliasMap),
745
+ // not when it's an embedded property name like 'profile1.identity.links'
746
+ const isTableAlias = !!this.aliasMap[a];
747
+ const resolvedAlias = isTableAlias ? this.getTPTAliasForProperty(f, a) : a;
748
+ const fieldName = this.fieldName(f, resolvedAlias, always, idx);
749
+ if (fieldName instanceof Raw) {
665
750
  return fieldName.sql;
666
751
  }
667
- ret = a + '.' + fieldName;
752
+ ret = resolvedAlias + '.' + fieldName;
668
753
  }
669
754
  if (quote) {
670
755
  return this.platform.quoteIdentifier(ret);
@@ -683,7 +768,7 @@ export class QueryBuilderHelper {
683
768
  }
684
769
  for (const sub of subCondition) {
685
770
  // 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);
771
+ const keys = Utils.getObjectQueryKeys(sub);
687
772
  const val = sub[keys[0]];
688
773
  const simple = !Utils.isPlainObject(val) || Utils.getObjectKeysSize(val) === 1 || Object.keys(val).every(k => !Utils.isOperator(k));
689
774
  if (keys.length === 1 && simple) {
@@ -719,27 +804,20 @@ export class QueryBuilderHelper {
719
804
  }
720
805
  getProperty(field, alias) {
721
806
  const entityName = this.aliasMap[alias]?.entityName || this.entityName;
722
- const meta = this.metadata.find(entityName);
807
+ const meta = this.metadata.get(entityName);
723
808
  // check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
724
- if (alias && meta) {
809
+ if (alias) {
725
810
  const prop = meta.properties[alias];
726
811
  if (prop?.kind === ReferenceKind.EMBEDDED) {
727
- // we want to select the full object property so hydration works as expected
728
- if (prop.object) {
729
- return prop;
730
- }
731
812
  const parts = field.split('.');
732
813
  const nest = (p) => parts.length > 0 ? nest(p.embeddedProps[parts.shift()]) : p;
733
814
  return nest(prop);
734
815
  }
735
816
  }
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));
817
+ if (meta.properties[field]) {
818
+ return meta.properties[field];
741
819
  }
742
- return undefined;
820
+ return meta.relations.find(prop => prop.fieldNames?.some(name => field === name));
743
821
  }
744
822
  isTableNameAliasRequired(type) {
745
823
  return [QueryType.SELECT, QueryType.COUNT].includes(type);
@@ -753,4 +831,15 @@ export class QueryBuilderHelper {
753
831
  }
754
832
  return cond;
755
833
  }
834
+ createFormulaTable(alias, meta, schema) {
835
+ const effectiveSchema = schema ?? (meta.schema !== '*' ? meta.schema : undefined);
836
+ const qualifiedName = effectiveSchema ? `${effectiveSchema}.${meta.tableName}` : meta.tableName;
837
+ return {
838
+ alias,
839
+ name: meta.tableName,
840
+ schema: effectiveSchema,
841
+ qualifiedName,
842
+ toString: () => alias,
843
+ };
844
+ }
756
845
  }
@@ -8,11 +8,11 @@ import { QueryBuilder } from './QueryBuilder.js';
8
8
  export class ScalarCriteriaNode extends CriteriaNode {
9
9
  process(qb, options) {
10
10
  const matchPopulateJoins = options?.matchPopulateJoins || (this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
11
- const nestedAlias = qb.getAliasForJoinPath(this.getPath(), { ...options, matchPopulateJoins });
11
+ const nestedAlias = qb.getAliasForJoinPath(this.getPath(options), { ...options, matchPopulateJoins });
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);
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
- import { QueryBuilder } from './QueryBuilder.js';
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<T extends object = any, R = any>(sql: SelectQueryBuilder<any, any, any> | QueryBuilder<T> | EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): NoInfer<R>;
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 {};