@mikro-orm/sql 7.0.0-dev.228 → 7.0.0-dev.229

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 { type AnyEntity, type Collection, type Configuration, type ConnectionType, type Constructor, type CountOptions, DatabaseDriver, type DeleteOptions, type Dictionary, type DriverMethodOptions, type EntityData, type EntityDictionary, type EntityField, EntityManagerType, type EntityMetadata, type EntityName, type EntityProperty, type FilterQuery, type FindOneOptions, type FindOptions, type LockOptions, type LoggingOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type ObjectQuery, type Options, type OrderDefinition, type PopulateOptions, type PopulatePath, type Primary, type QueryOrderMap, type QueryResult, RawQueryFragment, type StreamOptions, type Transaction, type UpsertManyOptions, type UpsertOptions } from '@mikro-orm/core';
1
+ import { type AnyEntity, type Collection, type Configuration, type ConnectionType, type Constructor, type CountOptions, DatabaseDriver, type DeleteOptions, type Dictionary, type DriverMethodOptions, type EntityData, type EntityDictionary, type EntityField, EntityManagerType, type EntityMetadata, type EntityName, type EntityProperty, type FilterQuery, type FindOneOptions, type FindOptions, type FormulaTable, type LockOptions, type LoggingOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type ObjectQuery, type Options, type OrderDefinition, type PopulateOptions, type PopulatePath, type Primary, type QueryOrderMap, type QueryResult, type Raw, RawQueryFragment, type StreamOptions, type Transaction, type UpsertManyOptions, type UpsertOptions } from '@mikro-orm/core';
2
2
  import type { AbstractSqlConnection } from './AbstractSqlConnection.js';
3
3
  import type { AbstractSqlPlatform } from './AbstractSqlPlatform.js';
4
4
  import { QueryBuilder } from './query/QueryBuilder.js';
@@ -13,6 +13,12 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
13
13
  protected readonly platform: Platform;
14
14
  protected constructor(config: Configuration, platform: Platform, connection: Constructor<Connection>, connector: string[]);
15
15
  getPlatform(): Platform;
16
+ /** Evaluates a formula callback, handling both string and Raw return values. */
17
+ evaluateFormula(formula: (...args: any[]) => string | Raw, columns: any, table: FormulaTable): string;
18
+ /** For TPT entities, returns ownProps (columns in this table); otherwise returns all props. */
19
+ private getTableProps;
20
+ /** Creates a FormulaTable object for use in formula callbacks. */
21
+ private createFormulaTable;
16
22
  createEntityManager(useContext?: boolean): this[typeof EntityManagerType];
17
23
  private createQueryBuilderFromOptions;
18
24
  find<T extends object, P extends string = never, F extends string = PopulatePath.ALL, E extends string = never>(entityName: EntityName<T>, where: ObjectQuery<T>, options?: FindOptions<T, P, F, E>): Promise<EntityData<T>[]>;
@@ -25,6 +31,12 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
25
31
  protected wrapVirtualExpressionInSubquery<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any>, type: QueryType): Promise<T[] | number>;
26
32
  protected wrapVirtualExpressionInSubqueryStream<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any, any, any>, type: QueryType.SELECT): AsyncIterableIterator<T>;
27
33
  mapResult<T extends object>(result: EntityData<T>, meta: EntityMetadata<T>, populate?: PopulateOptions<T>[], qb?: QueryBuilder<T, any, any, any>, map?: Dictionary): EntityData<T> | null;
34
+ /**
35
+ * Maps aliased columns from TPT parent tables back to their original field names.
36
+ * TPT parent columns are selected with aliases like `parent_alias__column_name`,
37
+ * and need to be renamed back to `column_name` for the result mapper to work.
38
+ */
39
+ private mapTPTColumns;
28
40
  private mapJoinedProps;
29
41
  /**
30
42
  * Maps a single property from a joined result row into the relation pojo.
@@ -82,6 +94,29 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
82
94
  mergeJoinedResult<T extends object>(rawResults: EntityData<T>[], meta: EntityMetadata<T>, joinedProps: PopulateOptions<T>[]): EntityData<T>[];
83
95
  protected shouldHaveColumn<T, U>(meta: EntityMetadata<T>, prop: EntityProperty<U>, populate: readonly PopulateOptions<U>[], fields?: readonly InternalField<U>[], exclude?: readonly InternalField<U>[]): boolean;
84
96
  protected getFieldsForJoinedLoad<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, options: FieldsForJoinedLoadOptions<T>): InternalField<T>[];
97
+ /**
98
+ * Adds LEFT JOINs and fields for TPT polymorphic loading when populating a relation to a TPT base class.
99
+ * @internal
100
+ */
101
+ protected addTPTPolymorphicJoinsForRelation<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, baseAlias: string, fields: InternalField<T>[]): void;
102
+ /**
103
+ * Find the alias for a TPT child table in the query builder.
104
+ * @internal
105
+ */
106
+ protected findTPTChildAlias<T extends object>(qb: QueryBuilder<T, any, any, any>, childMeta: EntityMetadata): string | undefined;
107
+ /**
108
+ * Builds a CASE WHEN expression for TPT discriminator.
109
+ * Determines concrete entity type based on which child table has a non-null PK.
110
+ * @internal
111
+ */
112
+ buildTPTDiscriminatorExpression(meta: EntityMetadata, descendants: EntityMetadata[], aliasMap: Dictionary<string>, baseAlias: string): Raw;
113
+ /**
114
+ * Maps TPT child-specific fields during hydration.
115
+ * When a relation points to a TPT base class, the actual entity might be a child class.
116
+ * This method reads the discriminator to determine the concrete type and maps child-specific fields.
117
+ * @internal
118
+ */
119
+ protected mapTPTChildFields<T extends object>(relationPojo: EntityData<T>, meta: EntityMetadata<T>, relationAlias: string, qb: QueryBuilder<T, any, any, any>, root: EntityData<T>): void;
85
120
  /**
86
121
  * @internal
87
122
  */
@@ -17,6 +17,21 @@ export class AbstractSqlDriver extends DatabaseDriver {
17
17
  getPlatform() {
18
18
  return this.platform;
19
19
  }
20
+ /** Evaluates a formula callback, handling both string and Raw return values. */
21
+ evaluateFormula(formula, columns, table) {
22
+ const result = formula(columns, table);
23
+ return isRaw(result) ? this.platform.formatQuery(result.sql, result.params) : result;
24
+ }
25
+ /** For TPT entities, returns ownProps (columns in this table); otherwise returns all props. */
26
+ getTableProps(meta) {
27
+ return meta.inheritanceType === 'tpt' && meta.ownProps ? meta.ownProps : meta.props;
28
+ }
29
+ /** Creates a FormulaTable object for use in formula callbacks. */
30
+ createFormulaTable(alias, meta, schema) {
31
+ const effectiveSchema = schema ?? (meta.schema !== '*' ? meta.schema : undefined);
32
+ const qualifiedName = effectiveSchema ? `${effectiveSchema}.${meta.tableName}` : meta.tableName;
33
+ return { alias, name: meta.tableName, schema: effectiveSchema, qualifiedName, toString: () => alias };
34
+ }
20
35
  createEntityManager(useContext) {
21
36
  const EntityManagerClass = this.config.get('entityManager', SqlEntityManager);
22
37
  return new EntityManagerClass(this.config, this, this.metadata, useContext);
@@ -196,6 +211,15 @@ export class AbstractSqlDriver extends DatabaseDriver {
196
211
  }
197
212
  }
198
213
  mapResult(result, meta, populate = [], qb, map = {}) {
214
+ // For TPT inheritance, map aliased parent table columns back to their field names
215
+ if (qb && meta.inheritanceType === 'tpt' && meta.tptParent) {
216
+ this.mapTPTColumns(result, meta, qb);
217
+ }
218
+ // For TPT polymorphic queries (querying a base class), map child table fields
219
+ if (qb && meta.inheritanceType === 'tpt' && meta.allTPTDescendants?.length) {
220
+ const mainAlias = qb.mainAlias?.aliasName ?? 'e0';
221
+ this.mapTPTChildFields(result, meta, mainAlias, qb, result);
222
+ }
199
223
  const ret = super.mapResult(result, meta);
200
224
  /* v8 ignore next */
201
225
  if (!ret) {
@@ -207,6 +231,33 @@ export class AbstractSqlDriver extends DatabaseDriver {
207
231
  }
208
232
  return ret;
209
233
  }
234
+ /**
235
+ * Maps aliased columns from TPT parent tables back to their original field names.
236
+ * TPT parent columns are selected with aliases like `parent_alias__column_name`,
237
+ * and need to be renamed back to `column_name` for the result mapper to work.
238
+ */
239
+ mapTPTColumns(result, meta, qb) {
240
+ const tptAliases = qb._tptAlias;
241
+ // Walk up the TPT hierarchy
242
+ let parentMeta = meta.tptParent;
243
+ while (parentMeta) {
244
+ const parentAlias = tptAliases[parentMeta.className];
245
+ if (parentAlias) {
246
+ // Rename columns from this parent table
247
+ for (const prop of parentMeta.ownProps) {
248
+ for (const fieldName of prop.fieldNames) {
249
+ const aliasedKey = `${parentAlias}__${fieldName}`;
250
+ if (aliasedKey in result) {
251
+ // Copy the value to the unaliased field name and remove the aliased key
252
+ result[fieldName] = result[aliasedKey];
253
+ delete result[aliasedKey];
254
+ }
255
+ }
256
+ }
257
+ }
258
+ parentMeta = parentMeta.tptParent;
259
+ }
260
+ }
210
261
  mapJoinedProps(result, meta, populate, qb, root, map, parentJoinPath) {
211
262
  const joinedProps = this.joinedProps(meta, populate);
212
263
  joinedProps.forEach(hint => {
@@ -325,6 +376,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
325
376
  for (const prop of targetProps) {
326
377
  this.mapJoinedProp(relationPojo, prop, relationAlias, root, tz, meta2);
327
378
  }
379
+ // Handle TPT polymorphic child fields - map fields from child table aliases
380
+ this.mapTPTChildFields(relationPojo, meta2, relationAlias, qb, root);
328
381
  // properties can be mapped to multiple places, e.g. when sharing a column in multiple FKs,
329
382
  // so we need to delete them after everything is mapped from given level
330
383
  for (const prop of targetProps) {
@@ -352,7 +405,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
352
405
  * Maps a single property from a joined result row into the relation pojo.
353
406
  * Handles polymorphic FKs, composite keys, Date parsing, and embedded objects.
354
407
  */
355
- mapJoinedProp(relationPojo, prop, relationAlias, root, tz, meta) {
408
+ mapJoinedProp(relationPojo, prop, relationAlias, root, tz, meta, options) {
356
409
  if (prop.fieldNames.every(name => typeof root[`${relationAlias}__${name}`] === 'undefined')) {
357
410
  return;
358
411
  }
@@ -401,6 +454,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
401
454
  }
402
455
  }
403
456
  }
457
+ if (options?.deleteFromRoot) {
458
+ for (const name of prop.fieldNames) {
459
+ delete root[`${relationAlias}__${name}`];
460
+ }
461
+ }
404
462
  }
405
463
  async count(entityName, where, options = {}) {
406
464
  const meta = this.metadata.get(entityName);
@@ -453,7 +511,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
453
511
  async nativeInsertMany(entityName, data, options = {}, transform) {
454
512
  options.processCollections ??= true;
455
513
  options.convertCustomTypes ??= true;
456
- const meta = this.metadata.get(entityName).root;
514
+ const entityMeta = this.metadata.get(entityName);
515
+ const meta = entityMeta.inheritanceType === 'tpt' ? entityMeta : entityMeta.root;
457
516
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(meta, d)) : [];
458
517
  const pks = this.getPrimaryKeyFields(meta);
459
518
  const set = new Set();
@@ -470,7 +529,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
470
529
  let sql = `insert into ${tableName} `;
471
530
  sql += fields.length > 0 ? '(' + fields.map(k => this.platform.quoteIdentifier(k)).join(', ') + ')' : `(${this.platform.quoteIdentifier(pks[0])})`;
472
531
  if (this.platform.usesOutputStatement()) {
473
- const returningProps = meta.props
532
+ const returningProps = this.getTableProps(meta)
474
533
  .filter(prop => (prop.persist !== false && prop.defaultRaw) || prop.autoincrement || prop.generated)
475
534
  .filter(prop => !(prop.name in data[0]) || isRaw(data[0][prop.name]));
476
535
  const returningFields = Utils.flatten(returningProps.map(prop => prop.fieldNames));
@@ -577,7 +636,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
577
636
  .join(', ');
578
637
  }
579
638
  if (meta && this.platform.usesReturningStatement()) {
580
- const returningProps = meta.props
639
+ const returningProps = this.getTableProps(meta)
581
640
  .filter(prop => (prop.persist !== false && prop.defaultRaw) || prop.autoincrement || prop.generated)
582
641
  .filter(prop => !(prop.name in data[0]) || isRaw(data[0][prop.name]));
583
642
  const returningFields = Utils.flatten(returningProps.map(prop => prop.fieldNames));
@@ -1189,6 +1248,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
1189
1248
  const populate = options.populate ?? [];
1190
1249
  const joinedProps = this.joinedProps(meta, populate, options);
1191
1250
  const populateWhereAll = options?._populateWhere === 'all' || Utils.isEmpty(options?._populateWhere);
1251
+ // Ensure TPT joins are applied early so that _tptAlias is available for join resolution
1252
+ // This is needed when populating relations that are inherited from TPT parent entities
1253
+ if (!options.parentJoinPath) {
1254
+ qb.ensureTPTJoins();
1255
+ }
1192
1256
  // root entity is already handled, skip that
1193
1257
  if (options.parentJoinPath) {
1194
1258
  // alias all fields in the primary table
@@ -1241,6 +1305,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
1241
1305
  : JoinType.leftJoin;
1242
1306
  const schema = prop.targetMeta.schema === '*' ? (options?.schema ?? this.config.get('schema')) : prop.targetMeta.schema;
1243
1307
  qb.join(field, tableAlias, {}, joinType, path, schema);
1308
+ // For relations to TPT base classes, add LEFT JOINs for all child tables (polymorphic loading)
1309
+ if (meta2.inheritanceType === 'tpt' && meta2.tptChildren?.length && !ref) {
1310
+ // Use the registry metadata to ensure allTPTDescendants is available
1311
+ const tptMeta = this.metadata.get(meta2.class);
1312
+ this.addTPTPolymorphicJoinsForRelation(qb, tptMeta, tableAlias, fields);
1313
+ }
1244
1314
  if (pivotRefJoin) {
1245
1315
  fields.push(...prop.joinColumns.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)), ...prop.inverseJoinColumns.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
1246
1316
  }
@@ -1277,6 +1347,110 @@ export class AbstractSqlDriver extends DatabaseDriver {
1277
1347
  }
1278
1348
  return fields;
1279
1349
  }
1350
+ /**
1351
+ * Adds LEFT JOINs and fields for TPT polymorphic loading when populating a relation to a TPT base class.
1352
+ * @internal
1353
+ */
1354
+ addTPTPolymorphicJoinsForRelation(qb, meta, baseAlias, fields) {
1355
+ // allTPTDescendants is pre-computed during discovery, sorted by depth (deepest first)
1356
+ const descendants = meta.allTPTDescendants;
1357
+ const childAliases = {};
1358
+ // LEFT JOIN each descendant table
1359
+ for (const childMeta of descendants) {
1360
+ const childAlias = qb.getNextAlias(childMeta.className);
1361
+ qb.createAlias(childMeta.class, childAlias);
1362
+ childAliases[childMeta.className] = childAlias;
1363
+ qb.addPropertyJoin(childMeta.tptInverseProp, baseAlias, childAlias, JoinType.leftJoin, `[tpt]${meta.className}`);
1364
+ // Add fields from this child (only ownProps, skip PKs)
1365
+ for (const prop of childMeta.ownProps.filter(p => !p.primary && this.platform.shouldHaveColumn(p, []))) {
1366
+ for (const fieldName of prop.fieldNames) {
1367
+ const field = `${childAlias}.${fieldName}`;
1368
+ const fieldAlias = `${childAlias}__${fieldName}`;
1369
+ fields.push(raw(`${this.platform.quoteIdentifier(field)} as ${this.platform.quoteIdentifier(fieldAlias)}`));
1370
+ }
1371
+ }
1372
+ }
1373
+ // Add computed discriminator (descendants already sorted by depth)
1374
+ if (meta.root.tptDiscriminatorColumn) {
1375
+ fields.push(this.buildTPTDiscriminatorExpression(meta, descendants, childAliases, baseAlias));
1376
+ }
1377
+ }
1378
+ /**
1379
+ * Find the alias for a TPT child table in the query builder.
1380
+ * @internal
1381
+ */
1382
+ findTPTChildAlias(qb, childMeta) {
1383
+ const joins = qb._joins;
1384
+ for (const key of Object.keys(joins)) {
1385
+ if (joins[key].table === childMeta.tableName && key.includes('[tpt]')) {
1386
+ return joins[key].alias;
1387
+ }
1388
+ }
1389
+ return undefined;
1390
+ }
1391
+ /**
1392
+ * Builds a CASE WHEN expression for TPT discriminator.
1393
+ * Determines concrete entity type based on which child table has a non-null PK.
1394
+ * @internal
1395
+ */
1396
+ buildTPTDiscriminatorExpression(meta, descendants, aliasMap, baseAlias) {
1397
+ const cases = descendants.map(child => {
1398
+ const childAlias = aliasMap[child.className];
1399
+ const pkFieldName = child.properties[child.primaryKeys[0]].fieldNames[0];
1400
+ return `when ${this.platform.quoteIdentifier(`${childAlias}.${pkFieldName}`)} is not null then '${child.discriminatorValue}'`;
1401
+ });
1402
+ const defaultValue = meta.abstract ? 'null' : `'${meta.discriminatorValue}'`;
1403
+ const caseExpr = `case ${cases.join(' ')} else ${defaultValue} end`;
1404
+ const aliased = this.platform.quoteIdentifier(`${baseAlias}__${meta.root.tptDiscriminatorColumn}`);
1405
+ return raw(`${caseExpr} as ${aliased}`);
1406
+ }
1407
+ /**
1408
+ * Maps TPT child-specific fields during hydration.
1409
+ * When a relation points to a TPT base class, the actual entity might be a child class.
1410
+ * This method reads the discriminator to determine the concrete type and maps child-specific fields.
1411
+ * @internal
1412
+ */
1413
+ mapTPTChildFields(relationPojo, meta, relationAlias, qb, root) {
1414
+ // Check if this is a TPT base with polymorphic children
1415
+ if (meta.inheritanceType !== 'tpt' || !meta.root.tptDiscriminatorColumn) {
1416
+ return;
1417
+ }
1418
+ // Read the discriminator value
1419
+ const discriminatorAlias = `${relationAlias}__${meta.root.tptDiscriminatorColumn}`;
1420
+ const discriminatorValue = root[discriminatorAlias];
1421
+ if (!discriminatorValue) {
1422
+ return;
1423
+ }
1424
+ // Set the discriminator in the pojo for EntityFactory
1425
+ relationPojo[meta.root.tptDiscriminatorColumn] = discriminatorValue;
1426
+ // Find the concrete metadata from discriminator map
1427
+ const concreteClass = meta.root.discriminatorMap?.[discriminatorValue];
1428
+ /* v8 ignore next 3 - defensive check for invalid discriminator values */
1429
+ if (!concreteClass) {
1430
+ return;
1431
+ }
1432
+ const concreteMeta = this.metadata.get(concreteClass);
1433
+ if (concreteMeta === meta) {
1434
+ // Already the concrete type, no child fields to map
1435
+ delete root[discriminatorAlias];
1436
+ return;
1437
+ }
1438
+ // Traverse up from concrete type and map fields from each level's table
1439
+ const tz = this.platform.getTimezone();
1440
+ let currentMeta = concreteMeta;
1441
+ while (currentMeta && currentMeta !== meta) {
1442
+ const childAlias = this.findTPTChildAlias(qb, currentMeta);
1443
+ if (childAlias) {
1444
+ // Map fields using same filtering as joined loading, plus skip PKs
1445
+ for (const prop of currentMeta.ownProps.filter(p => !p.primary && this.platform.shouldHaveColumn(p, []))) {
1446
+ this.mapJoinedProp(relationPojo, prop, childAlias, root, tz, currentMeta, { deleteFromRoot: true });
1447
+ }
1448
+ }
1449
+ currentMeta = currentMeta.tptParent;
1450
+ }
1451
+ // Clean up the discriminator alias
1452
+ delete root[discriminatorAlias];
1453
+ }
1280
1454
  /**
1281
1455
  * @internal
1282
1456
  */
@@ -1306,18 +1480,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
1306
1480
  return [raw(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
1307
1481
  }
1308
1482
  if (prop.formula) {
1309
- const alias = this.platform.quoteIdentifier(tableAlias);
1310
- const effectiveSchema = schema ?? (meta.schema !== '*' ? meta.schema : undefined);
1311
- const qualifiedName = effectiveSchema ? `${effectiveSchema}.${meta.tableName}` : meta.tableName;
1312
- const table = {
1313
- alias: alias.toString(),
1314
- name: meta.tableName,
1315
- schema: effectiveSchema,
1316
- qualifiedName,
1317
- toString: () => alias.toString(),
1318
- };
1319
- const columns = meta.createColumnMappingObject();
1320
- return [raw(`${prop.formula(table, columns)} as ${aliased}`)];
1483
+ const quotedAlias = this.platform.quoteIdentifier(tableAlias).toString();
1484
+ const table = this.createFormulaTable(quotedAlias, meta, schema);
1485
+ const columns = meta.createColumnMappingObject(tableAlias);
1486
+ return [raw(`${this.evaluateFormula(prop.formula, columns, table)} as ${aliased}`)];
1321
1487
  }
1322
1488
  return prop.fieldNames.map(fieldName => {
1323
1489
  return `${tableAlias}.${fieldName} as ${tableAlias}__${fieldName}`;
@@ -1572,7 +1738,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
1572
1738
  if (!options.fields.includes('*') && !options.fields.includes(`${qb.alias}.*`)) {
1573
1739
  ret.unshift(...meta.primaryKeys.filter(pk => !options.fields.includes(pk)));
1574
1740
  }
1575
- if (meta.root.discriminatorColumn && !options.fields.includes(`${qb.alias}.${meta.root.discriminatorColumn}`)) {
1741
+ if (meta.root.inheritanceType === 'sti' && !options.fields.includes(`${qb.alias}.${meta.root.discriminatorColumn}`)) {
1576
1742
  ret.push(meta.root.discriminatorColumn);
1577
1743
  }
1578
1744
  }
@@ -1589,24 +1755,18 @@ export class AbstractSqlDriver extends DatabaseDriver {
1589
1755
  ret.push('*');
1590
1756
  }
1591
1757
  if (ret.length > 0 && !hasExplicitFields && addFormulas) {
1592
- const columns = meta.createColumnMappingObject();
1758
+ // Create formula column mapping with unquoted aliases - quoting should be handled by the user via `quote` helper
1759
+ const quotedAlias = this.platform.quoteIdentifier(alias);
1760
+ const columns = meta.createColumnMappingObject(alias);
1593
1761
  const effectiveSchema = schema ?? (meta.schema !== '*' ? meta.schema : undefined);
1594
1762
  for (const prop of meta.props) {
1595
1763
  if (lazyProps.includes(prop)) {
1596
1764
  continue;
1597
1765
  }
1598
1766
  if (prop.formula) {
1599
- const a = this.platform.quoteIdentifier(alias);
1600
1767
  const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
1601
- const qualifiedName = effectiveSchema ? `${effectiveSchema}.${meta.tableName}` : meta.tableName;
1602
- const table = {
1603
- alias: a.toString(),
1604
- name: meta.tableName,
1605
- schema: effectiveSchema,
1606
- qualifiedName,
1607
- toString: () => a.toString(),
1608
- };
1609
- ret.push(raw(`${prop.formula(table, columns)} as ${aliased}`));
1768
+ const table = this.createFormulaTable(quotedAlias.toString(), meta, effectiveSchema);
1769
+ ret.push(raw(`${this.evaluateFormula(prop.formula, columns, table)} as ${aliased}`));
1610
1770
  }
1611
1771
  if (!prop.object && (prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)) {
1612
1772
  ret.push(prop.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/sql",
3
- "version": "7.0.0-dev.228",
3
+ "version": "7.0.0-dev.229",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -56,6 +56,6 @@
56
56
  "@mikro-orm/core": "^6.6.4"
57
57
  },
58
58
  "peerDependencies": {
59
- "@mikro-orm/core": "7.0.0-dev.228"
59
+ "@mikro-orm/core": "7.0.0-dev.229"
60
60
  }
61
61
  }
@@ -202,6 +202,7 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
202
202
  protected subQueries: Dictionary<string>;
203
203
  protected _mainAlias?: Alias<Entity>;
204
204
  protected _aliases: Dictionary<Alias<any>>;
205
+ protected _tptAlias: Dictionary<string>;
205
206
  protected _helper?: QueryBuilderHelper;
206
207
  protected _query?: {
207
208
  sql?: string;
@@ -209,6 +210,8 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
209
210
  qb: NativeQueryBuilder;
210
211
  };
211
212
  protected readonly platform: AbstractSqlPlatform;
213
+ private tptJoinsApplied;
214
+ private readonly autoJoinedPaths;
212
215
  /**
213
216
  * @internal
214
217
  */
@@ -403,7 +406,6 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
403
406
  * Apply filters to the QB where condition.
404
407
  */
405
408
  applyFilters(filterOptions?: FilterOptions): Promise<void>;
406
- private readonly autoJoinedPaths;
407
409
  /**
408
410
  * @internal
409
411
  */
@@ -737,6 +739,13 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
737
739
  */
738
740
  getLoggerContext<T extends Dictionary & LoggingOptions = Dictionary>(): T;
739
741
  private fromVirtual;
742
+ /**
743
+ * Adds a join from a property object. Used internally for TPT joins where the property
744
+ * is synthetic (not in entity.properties) but defined on metadata (e.g., tptParentProp).
745
+ * The caller must create the alias first via createAlias().
746
+ * @internal
747
+ */
748
+ addPropertyJoin(prop: EntityProperty, ownerAlias: string, alias: string, type: JoinType, path: string, schema?: string): string;
740
749
  private joinReference;
741
750
  protected prepareFields<T>(fields: InternalField<T>[], type?: 'where' | 'groupBy' | 'sub-query', schema?: string): (string | RawQueryFragment)[];
742
751
  /**
@@ -748,6 +757,27 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
748
757
  private init;
749
758
  private getQueryBase;
750
759
  private applyDiscriminatorCondition;
760
+ /**
761
+ * Ensures TPT joins are applied. Can be called early before finalize() to populate
762
+ * the _tptAlias map for use in join resolution. Safe to call multiple times.
763
+ * @internal
764
+ */
765
+ ensureTPTJoins(): void;
766
+ /**
767
+ * For TPT (Table-Per-Type) inheritance: INNER JOINs parent tables.
768
+ * When querying a child entity, we need to join all parent tables.
769
+ * Field selection is handled separately in addTPTParentFields().
770
+ */
771
+ private applyTPTJoins;
772
+ /**
773
+ * For TPT inheritance: adds field selections from parent tables.
774
+ */
775
+ private addTPTParentFields;
776
+ /**
777
+ * For TPT polymorphic queries: LEFT JOINs all child tables when querying a TPT base class.
778
+ * Adds discriminator and child fields to determine and load the concrete type.
779
+ */
780
+ private applyTPTPolymorphicJoins;
751
781
  private finalize;
752
782
  /** @internal */
753
783
  processPopulateHint(): void;
@@ -784,7 +814,8 @@ export declare class QueryBuilder<Entity extends object = AnyEntity, RootAlias e
784
814
  protected transferConditionsToJoin(cond: Dictionary, join: JoinOptions, path?: string): void;
785
815
  private wrapModifySubQuery;
786
816
  private getSchema;
787
- private createAlias;
817
+ /** @internal */
818
+ createAlias<U = unknown>(entityName: EntityName<U>, aliasName: string, subQuery?: NativeQueryBuilder): Alias<U>;
788
819
  private createMainAlias;
789
820
  private fromSubQuery;
790
821
  private fromEntityName;