mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
repairing single table inheritance;
This commit is contained in:
parent
cdae9fb31c
commit
00d131be58
@ -14,6 +14,7 @@ import {InheritanceMetadataArgs} from "./InheritanceMetadataArgs";
|
||||
import {DiscriminatorValueMetadataArgs} from "./DiscriminatorValueMetadataArgs";
|
||||
import {EntityRepositoryMetadataArgs} from "./EntityRepositoryMetadataArgs";
|
||||
import {TransactionEntityMetadataArgs} from "./TransactionEntityMetadataArgs";
|
||||
import {MetadataUtils} from "../metadata-builder/MetadataUtils";
|
||||
|
||||
/**
|
||||
* Storage all metadatas args of all available types: tables, columns, subscribers, relations, etc.
|
||||
@ -147,4 +148,20 @@ export class MetadataArgsStorage {
|
||||
});
|
||||
}
|
||||
|
||||
filterSingleTableChildren(target: Function|string): TableMetadataArgs[] {
|
||||
return this.tables.filter(table => {
|
||||
return table.target instanceof Function
|
||||
&& target instanceof Function
|
||||
&& MetadataUtils.isInherited(table.target, target)
|
||||
&& table.type === "single-table-child";
|
||||
});
|
||||
}
|
||||
|
||||
findInheritanceType(target: Function|string): InheritanceMetadataArgs|undefined {
|
||||
return this.inheritances.find(inheritance => inheritance.target === target)
|
||||
}
|
||||
|
||||
findDiscriminatorValue(target: Function|string): DiscriminatorValueMetadataArgs|undefined {
|
||||
return this.discriminatorValues.find(discriminatorValue => discriminatorValue.target === target)
|
||||
}
|
||||
}
|
||||
@ -62,7 +62,7 @@ export class EntityMetadataBuilder {
|
||||
const allTables = entityClasses ? this.metadataArgsStorage.filterTables(entityClasses) : this.metadataArgsStorage.tables;
|
||||
|
||||
// filter out table metadata args for those we really create entity metadatas and tables in the db
|
||||
const realTables = allTables.filter(table => table.type === "regular" || table.type === "closure" || table.type === "class-table-child");
|
||||
const realTables = allTables.filter(table => table.type === "regular" || table.type === "closure" || table.type === "class-table-child" || table.type === "single-table-child");
|
||||
|
||||
// create entity metadatas for a user defined entities (marked with @Entity decorator or loaded from entity schemas)
|
||||
const entityMetadatas = realTables.map(tableArgs => this.createEntityMetadata(tableArgs));
|
||||
@ -118,9 +118,27 @@ export class EntityMetadataBuilder {
|
||||
entityMetadatas.push(closureJunctionEntityMetadata);
|
||||
});
|
||||
|
||||
// after all metadatas created we set parent entity metadata for class-table inheritance
|
||||
entityMetadatas
|
||||
.filter(metadata => metadata.tableType === "single-table-child")
|
||||
.forEach(entityMetadata => {
|
||||
const inheritanceTree: any[] = entityMetadata.target instanceof Function
|
||||
? MetadataUtils.getInheritanceTree(entityMetadata.target)
|
||||
: [entityMetadata.target];
|
||||
|
||||
const parentMetadata = entityMetadatas.find(metadata => {
|
||||
return inheritanceTree.find(inheritance => inheritance === metadata.target) && metadata.inheritanceType === "single-table";
|
||||
});
|
||||
|
||||
if (parentMetadata) {
|
||||
entityMetadata.parentEntityMetadata = parentMetadata;
|
||||
entityMetadata.tableName = parentMetadata.tableName;
|
||||
}
|
||||
});
|
||||
|
||||
// generate keys for tables with single-table inheritance
|
||||
entityMetadatas
|
||||
.filter(metadata => metadata.inheritanceType === "single-table")
|
||||
.filter(metadata => metadata.inheritanceType === "single-table" && metadata.discriminatorColumn)
|
||||
.forEach(entityMetadata => this.createKeysForTableInheritance(entityMetadata));
|
||||
|
||||
// build all indices (need to do it after relations and their join columns are built)
|
||||
@ -155,11 +173,26 @@ export class EntityMetadataBuilder {
|
||||
// we take all "inheritance tree" from a target entity to collect all stored metadata args
|
||||
// (by decorators or inside entity schemas). For example for target Post < ContentModel < Unit
|
||||
// it will be an array of [Post, ContentModel, Unit] and we can then get all metadata args of those classes
|
||||
const inheritanceTree = tableArgs.target instanceof Function
|
||||
const inheritanceTree: any[] = tableArgs.target instanceof Function
|
||||
? MetadataUtils.getInheritanceTree(tableArgs.target)
|
||||
: [tableArgs.target]; // todo: implement later here inheritance for string-targets
|
||||
|
||||
// if single table inheritance used, we need to copy all children columns in to parent table
|
||||
const singleTableChildrenTargets: any[] = this.metadataArgsStorage
|
||||
.filterSingleTableChildren(tableArgs.target)
|
||||
.map(args => args.target)
|
||||
.filter(target => target instanceof Function);
|
||||
|
||||
inheritanceTree.push(...singleTableChildrenTargets);
|
||||
|
||||
const entityMetadata = new EntityMetadata({ connection: this.connection, args: tableArgs });
|
||||
|
||||
const inheritanceType = this.metadataArgsStorage.findInheritanceType(tableArgs.target);
|
||||
entityMetadata.inheritanceType = inheritanceType ? inheritanceType.type : undefined;
|
||||
|
||||
const discriminatorValue = this.metadataArgsStorage.findDiscriminatorValue(tableArgs.target);
|
||||
entityMetadata.discriminatorValue = discriminatorValue ? discriminatorValue.value : (tableArgs.target as any).name; // todo: pass this to naming strategy to generate a name
|
||||
|
||||
entityMetadata.embeddeds = this.createEmbeddedsRecursively(entityMetadata, this.metadataArgsStorage.filterEmbeddeds(inheritanceTree));
|
||||
entityMetadata.ownColumns = this.metadataArgsStorage.filterColumns(inheritanceTree).map(args => {
|
||||
return new ColumnMetadata({ entityMetadata, args });
|
||||
|
||||
@ -22,6 +22,16 @@ export class MetadataUtils {
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this table is inherited from another table.
|
||||
*/
|
||||
static isInherited(target1: Function, target2: Function) {
|
||||
// we cannot use instanceOf in this method, because we need order of inherited tables, to ensure that
|
||||
// properties get inherited in a right order. To achieve it we can only check a first parent of the class
|
||||
// return this.target.prototype instanceof anotherTable.target;
|
||||
return Object.getPrototypeOf(target1.prototype).constructor === target2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters given array of targets by a given classes.
|
||||
* If classes are not given, then it returns array itself.
|
||||
|
||||
@ -36,14 +36,12 @@ import {RelationCountMetadataToAttributeTransformer} from "./relation-count/Rela
|
||||
// todo: add selectAndMap
|
||||
|
||||
// todo: tests for:
|
||||
// todo: entityOrProperty can be a table name. implement if its a table
|
||||
// todo: entityOrProperty can be target name. implement proper behaviour if it is.
|
||||
// todo: think about subselect in joins syntax
|
||||
// todo: create multiple representations of QueryBuilder: UpdateQueryBuilder, DeleteQueryBuilder
|
||||
// qb.update() returns UpdateQueryBuilder
|
||||
// qb.delete() returns DeleteQueryBuilder
|
||||
// qb.select() returns SelectQueryBuilder
|
||||
// todo: tests for leftJoinAndMap...
|
||||
// todo: COMPLETELY COVER QUERY BUILDER WITH TESTS
|
||||
|
||||
// todo: SUBSELECT IMPLEMENTATION
|
||||
@ -51,8 +49,6 @@ import {RelationCountMetadataToAttributeTransformer} from "./relation-count/Rela
|
||||
// todo: also create qb.createSubQueryBuilder()
|
||||
// todo: check in persistment if id exist on object and throw exception (can be in partial selection?)
|
||||
// todo: STREAMING
|
||||
// todo: switch to embedded task?
|
||||
// todo: add test for @JoinColumn({ referencedColumnName })
|
||||
|
||||
/**
|
||||
* Allows to build complex sql queries in a fashion way and execute those queries.
|
||||
@ -1312,10 +1308,11 @@ export class QueryBuilder<Entity> {
|
||||
const aliasName = this.expressionMap.mainAlias.name;
|
||||
|
||||
if (this.expressionMap.mainAlias.hasMetadata) {
|
||||
tableName = this.expressionMap.mainAlias.metadata.tableName;
|
||||
const metadata = this.expressionMap.mainAlias.metadata;
|
||||
tableName = metadata.tableName;
|
||||
|
||||
allSelects.push(...this.buildEscapedEntityColumnSelects(aliasName, this.expressionMap.mainAlias.metadata));
|
||||
excludedSelects.push(...this.findEntityColumnSelects(aliasName, this.expressionMap.mainAlias.metadata));
|
||||
allSelects.push(...this.buildEscapedEntityColumnSelects(aliasName, metadata));
|
||||
excludedSelects.push(...this.findEntityColumnSelects(aliasName, metadata));
|
||||
|
||||
} else { // if alias does not have metadata - selections will be from custom table
|
||||
tableName = this.expressionMap.mainAlias.tableName!;
|
||||
@ -1337,12 +1334,12 @@ export class QueryBuilder<Entity> {
|
||||
});
|
||||
|
||||
if (!this.expressionMap.ignoreParentTablesJoins && this.expressionMap.mainAlias.hasMetadata) {
|
||||
if (this.expressionMap.mainAlias!.metadata.parentEntityMetadata && this.expressionMap.mainAlias!.metadata.parentIdColumns) {
|
||||
const alias = "parentIdColumn_" + ea(this.expressionMap.mainAlias!.metadata.parentEntityMetadata.tableName);
|
||||
this.expressionMap.mainAlias!.metadata.parentEntityMetadata.columns.forEach(column => {
|
||||
const metadata = this.expressionMap.mainAlias.metadata;
|
||||
if (metadata.parentEntityMetadata && metadata.parentEntityMetadata.inheritanceType === "class-table" && metadata.parentIdColumns) {
|
||||
const alias = "parentIdColumn_" + metadata.parentEntityMetadata.tableName;
|
||||
metadata.parentEntityMetadata.columns.forEach(column => {
|
||||
// TODO implement partial select
|
||||
allSelects.push({ selection: ea(alias + "." + column.databaseName), aliasName: alias + "_" + column.databaseName });
|
||||
// allSelects.push(alias + "." + ec(column.fullName) + " AS " + alias + "_" + ea(column.fullName));
|
||||
allSelects.push({ selection: ea(alias) + "." + ec(column.databaseName), aliasName: alias + "_" + column.databaseName });
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1445,7 +1442,7 @@ export class QueryBuilder<Entity> {
|
||||
if (this.expressionMap.mainAlias!.hasMetadata) {
|
||||
const mainMetadata = this.expressionMap.mainAlias!.metadata;
|
||||
if (mainMetadata.discriminatorColumn)
|
||||
return ` WHERE ${ conditions.length ? "(" + conditions + ") AND" : "" } ${this.escapeColumn(mainMetadata.discriminatorColumn.databaseName)}=:discriminatorColumnValue`;
|
||||
return ` WHERE ${ conditions.length ? "(" + conditions + ") AND" : "" } ${this.replacePropertyNames(this.expressionMap.mainAlias!.name + "." + mainMetadata.discriminatorColumn.databaseName)}=:discriminatorColumnValue`;
|
||||
}
|
||||
|
||||
if (!conditions.length)
|
||||
@ -1563,7 +1560,6 @@ export class QueryBuilder<Entity> {
|
||||
}).join(" AND ");
|
||||
}
|
||||
|
||||
|
||||
return " " + joinAttr.direction + " JOIN " + et(junctionTableName) + " " + ea(junctionAlias) + " ON " + this.replacePropertyNames(junctionCondition) +
|
||||
" " + joinAttr.direction + " JOIN " + et(destinationTableName) + " " + ea(destinationTableAlias) + " ON " + this.replacePropertyNames(destinationCondition + appendedCondition);
|
||||
|
||||
@ -1572,13 +1568,13 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
if (!this.expressionMap.ignoreParentTablesJoins && this.expressionMap.mainAlias!.hasMetadata) {
|
||||
const metadata = this.expressionMap.mainAlias!.metadata;
|
||||
if (metadata.parentEntityMetadata && metadata.parentIdColumns) {
|
||||
if (metadata.parentEntityMetadata && metadata.parentEntityMetadata.inheritanceType === "class-table" && metadata.parentIdColumns) {
|
||||
const alias = "parentIdColumn_" + metadata.parentEntityMetadata.tableName;
|
||||
const parentJoin = " JOIN " + et(metadata.parentEntityMetadata.tableName) + " " + ea(alias) + " ON " +
|
||||
metadata.parentIdColumns.map(parentIdColumn => {
|
||||
return this.expressionMap.mainAlias!.name + "." + parentIdColumn.databaseName + "=" + ea(alias) + "." + parentIdColumn.propertyName;
|
||||
});
|
||||
joins.push(parentJoin);
|
||||
const condition = metadata.parentIdColumns.map(parentIdColumn => {
|
||||
return this.expressionMap.mainAlias!.name + "." + parentIdColumn.databaseName + "=" + ea(alias) + "." + parentIdColumn.propertyName;
|
||||
}).join(" AND ");
|
||||
const join = " JOIN " + et(metadata.parentEntityMetadata.tableName) + " " + ea(alias) + " ON " + condition;
|
||||
joins.push(join);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -94,7 +94,7 @@ export class SchemaBuilder {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
protected get entityToSyncMetadatas(): EntityMetadata[] {
|
||||
return this.entityMetadatas.filter(metadata => !metadata.skipSchemaSync);
|
||||
return this.entityMetadatas.filter(metadata => !metadata.skipSchemaSync && metadata.tableType !== "single-table-child");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
11
test/github-issues/131/entity/Student.ts
Normal file
11
test/github-issues/131/entity/Student.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {Column} from "../../../../src/decorator/columns/Column";
|
||||
import {Person} from "./Person";
|
||||
import {SingleEntityChild} from "../../../../src/decorator/entity/SingleEntityChild";
|
||||
|
||||
@SingleEntityChild()
|
||||
export class Student extends Person {
|
||||
|
||||
@Column()
|
||||
faculty: string;
|
||||
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
import "reflect-metadata";
|
||||
import { createTestingConnections, closeTestingConnections, reloadTestingDatabases } from "../../utils/test-utils";
|
||||
import { Connection } from "../../../src/connection/Connection";
|
||||
import { Employee } from "./entity/Employee";
|
||||
import { expect } from "chai";
|
||||
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../utils/test-utils";
|
||||
import {Connection} from "../../../src/connection/Connection";
|
||||
import {expect} from "chai";
|
||||
import {Student} from "./entity/Student";
|
||||
import {Employee} from "./entity/Employee";
|
||||
|
||||
// unskip once table inheritance is back
|
||||
describe.skip("github issues > #131 Error with single table inheritance query without additional conditions", () => {
|
||||
describe("github issues > #131 Error with single table inheritance query without additional conditions", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await createTestingConnections({
|
||||
@ -13,11 +13,15 @@ describe.skip("github issues > #131 Error with single table inheritance query wi
|
||||
schemaCreate: true,
|
||||
dropSchemaOnConnection: true,
|
||||
}));
|
||||
beforeEach(() => reloadTestingDatabases(connections));
|
||||
after(() => closeTestingConnections(connections));
|
||||
|
||||
it("should not fail when querying for single table inheritance model without additional conditions", () => Promise.all(connections.map(async connection => {
|
||||
const employees = await connection.getRepository(Employee).find();
|
||||
expect(employees).not.to.be.undefined;
|
||||
|
||||
const students = await connection.getRepository(Student).find();
|
||||
expect(students).not.to.be.undefined;
|
||||
})));
|
||||
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user