@mikro-orm/sql 7.0.0-dev.228 → 7.0.0-dev.230
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AbstractSqlDriver.d.ts +36 -1
- package/AbstractSqlDriver.js +199 -40
- package/package.json +2 -2
- package/query/QueryBuilder.d.ts +33 -2
- package/query/QueryBuilder.js +125 -9
- package/query/QueryBuilderHelper.d.ts +7 -1
- package/query/QueryBuilderHelper.js +54 -14
- package/schema/DatabaseSchema.d.ts +5 -0
- package/schema/DatabaseSchema.js +55 -2
- package/schema/DatabaseTable.js +6 -8
- package/tsconfig.build.tsbuildinfo +1 -1
package/AbstractSqlDriver.d.ts
CHANGED
|
@@ -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
|
*/
|
package/AbstractSqlDriver.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
1310
|
-
const
|
|
1311
|
-
const
|
|
1312
|
-
|
|
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}`;
|
|
@@ -1484,19 +1650,18 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1484
1650
|
const alias = ref ? propAlias : join.ownerAlias;
|
|
1485
1651
|
orderBy.push({ [`${alias}.${prop.fixedOrderColumn}`]: QueryOrder.ASC });
|
|
1486
1652
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
}
|
|
1498
|
-
orderBy.push({ [`${propAlias}.${field}`]: order });
|
|
1653
|
+
const effectiveOrderBy = QueryHelper.mergeOrderBy(propOrderBy, prop.targetMeta?.orderBy);
|
|
1654
|
+
for (const item of effectiveOrderBy) {
|
|
1655
|
+
for (const field of Utils.getObjectQueryKeys(item)) {
|
|
1656
|
+
const order = item[field];
|
|
1657
|
+
if (RawQueryFragment.isKnownFragmentSymbol(field)) {
|
|
1658
|
+
const { sql, params } = RawQueryFragment.getKnownFragment(field);
|
|
1659
|
+
const sql2 = propAlias ? sql.replace(new RegExp(ALIAS_REPLACEMENT_RE, 'g'), propAlias) : sql;
|
|
1660
|
+
const key = raw(sql2, params);
|
|
1661
|
+
orderBy.push({ [key]: order });
|
|
1662
|
+
continue;
|
|
1499
1663
|
}
|
|
1664
|
+
orderBy.push({ [`${propAlias}.${field}`]: order });
|
|
1500
1665
|
}
|
|
1501
1666
|
}
|
|
1502
1667
|
if (hint.children) {
|
|
@@ -1572,7 +1737,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1572
1737
|
if (!options.fields.includes('*') && !options.fields.includes(`${qb.alias}.*`)) {
|
|
1573
1738
|
ret.unshift(...meta.primaryKeys.filter(pk => !options.fields.includes(pk)));
|
|
1574
1739
|
}
|
|
1575
|
-
if (meta.root.
|
|
1740
|
+
if (meta.root.inheritanceType === 'sti' && !options.fields.includes(`${qb.alias}.${meta.root.discriminatorColumn}`)) {
|
|
1576
1741
|
ret.push(meta.root.discriminatorColumn);
|
|
1577
1742
|
}
|
|
1578
1743
|
}
|
|
@@ -1589,24 +1754,18 @@ export class AbstractSqlDriver extends DatabaseDriver {
|
|
|
1589
1754
|
ret.push('*');
|
|
1590
1755
|
}
|
|
1591
1756
|
if (ret.length > 0 && !hasExplicitFields && addFormulas) {
|
|
1592
|
-
|
|
1757
|
+
// Create formula column mapping with unquoted aliases - quoting should be handled by the user via `quote` helper
|
|
1758
|
+
const quotedAlias = this.platform.quoteIdentifier(alias);
|
|
1759
|
+
const columns = meta.createColumnMappingObject(alias);
|
|
1593
1760
|
const effectiveSchema = schema ?? (meta.schema !== '*' ? meta.schema : undefined);
|
|
1594
1761
|
for (const prop of meta.props) {
|
|
1595
1762
|
if (lazyProps.includes(prop)) {
|
|
1596
1763
|
continue;
|
|
1597
1764
|
}
|
|
1598
1765
|
if (prop.formula) {
|
|
1599
|
-
const a = this.platform.quoteIdentifier(alias);
|
|
1600
1766
|
const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
1601
|
-
const
|
|
1602
|
-
|
|
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}`));
|
|
1767
|
+
const table = this.createFormulaTable(quotedAlias.toString(), meta, effectiveSchema);
|
|
1768
|
+
ret.push(raw(`${this.evaluateFormula(prop.formula, columns, table)} as ${aliased}`));
|
|
1610
1769
|
}
|
|
1611
1770
|
if (!prop.object && (prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)) {
|
|
1612
1771
|
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.
|
|
3
|
+
"version": "7.0.0-dev.230",
|
|
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.
|
|
59
|
+
"@mikro-orm/core": "7.0.0-dev.230"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|