mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
repairing single table inheritance;
working on single table inheritance tests;
This commit is contained in:
parent
1c496edca5
commit
eb51cb3ef3
@ -74,37 +74,39 @@ export class EntityMetadataBuilder {
|
||||
entityMetadatas.forEach(entityMetadata => this.computeInverseProperties(entityMetadata, entityMetadatas));
|
||||
|
||||
// go through all entity metadatas and create foreign keys / junction entity metadatas for their relations
|
||||
entityMetadatas.forEach(entityMetadata => {
|
||||
entityMetadatas
|
||||
.filter(entityMetadata => entityMetadata.tableType !== "single-table-child")
|
||||
.forEach(entityMetadata => {
|
||||
|
||||
// create entity's relations join columns (for many-to-one and one-to-one owner)
|
||||
entityMetadata.relations.filter(relation => relation.isOneToOne || relation.isManyToOne).forEach(relation => {
|
||||
const joinColumns = this.metadataArgsStorage.filterJoinColumns(relation.target, relation.propertyName);
|
||||
const foreignKey = this.relationJoinColumnBuilder.build(joinColumns, relation); // create a foreign key based on its metadata args
|
||||
if (foreignKey) {
|
||||
relation.registerForeignKeys(foreignKey); // push it to the relation and thus register there a join column
|
||||
entityMetadata.foreignKeys.push(foreignKey);
|
||||
}
|
||||
});
|
||||
// create entity's relations join columns (for many-to-one and one-to-one owner)
|
||||
entityMetadata.relations.filter(relation => relation.isOneToOne || relation.isManyToOne).forEach(relation => {
|
||||
const joinColumns = this.metadataArgsStorage.filterJoinColumns(relation.target, relation.propertyName);
|
||||
const foreignKey = this.relationJoinColumnBuilder.build(joinColumns, relation); // create a foreign key based on its metadata args
|
||||
if (foreignKey) {
|
||||
relation.registerForeignKeys(foreignKey); // push it to the relation and thus register there a join column
|
||||
entityMetadata.foreignKeys.push(foreignKey);
|
||||
}
|
||||
});
|
||||
|
||||
// create junction entity metadatas for entity many-to-many relations
|
||||
entityMetadata.relations.filter(relation => relation.isManyToMany).forEach(relation => {
|
||||
const joinTable = this.metadataArgsStorage.findJoinTable(relation.target, relation.propertyName);
|
||||
if (!joinTable) return; // no join table set - no need to do anything (it means this is many-to-many inverse side)
|
||||
// create junction entity metadatas for entity many-to-many relations
|
||||
entityMetadata.relations.filter(relation => relation.isManyToMany).forEach(relation => {
|
||||
const joinTable = this.metadataArgsStorage.findJoinTable(relation.target, relation.propertyName);
|
||||
if (!joinTable) return; // no join table set - no need to do anything (it means this is many-to-many inverse side)
|
||||
|
||||
// here we create a junction entity metadata for a new junction table of many-to-many relation
|
||||
const junctionEntityMetadata = this.junctionEntityMetadataBuilder.build(relation, joinTable);
|
||||
relation.registerForeignKeys(...junctionEntityMetadata.foreignKeys);
|
||||
relation.registerJunctionEntityMetadata(junctionEntityMetadata);
|
||||
// here we create a junction entity metadata for a new junction table of many-to-many relation
|
||||
const junctionEntityMetadata = this.junctionEntityMetadataBuilder.build(relation, joinTable);
|
||||
relation.registerForeignKeys(...junctionEntityMetadata.foreignKeys);
|
||||
relation.registerJunctionEntityMetadata(junctionEntityMetadata);
|
||||
|
||||
// compute new entity metadata properties and push it to entity metadatas pool
|
||||
this.computeEntityMetadata(junctionEntityMetadata);
|
||||
this.computeInverseProperties(junctionEntityMetadata, entityMetadatas);
|
||||
entityMetadatas.push(junctionEntityMetadata);
|
||||
});
|
||||
// compute new entity metadata properties and push it to entity metadatas pool
|
||||
this.computeEntityMetadata(junctionEntityMetadata);
|
||||
this.computeInverseProperties(junctionEntityMetadata, entityMetadatas);
|
||||
entityMetadatas.push(junctionEntityMetadata);
|
||||
});
|
||||
|
||||
// update entity metadata depend properties
|
||||
entityMetadata.relationsWithJoinColumns = entityMetadata.relations.filter(relation => relation.isWithJoinColumn);
|
||||
entityMetadata.hasNonNullableRelations = entityMetadata.relationsWithJoinColumns.some(relation => !relation.isNullable || relation.isPrimary);
|
||||
// update entity metadata depend properties
|
||||
entityMetadata.relationsWithJoinColumns = entityMetadata.relations.filter(relation => relation.isWithJoinColumn);
|
||||
entityMetadata.hasNonNullableRelations = entityMetadata.relationsWithJoinColumns.some(relation => !relation.isNullable || relation.isPrimary);
|
||||
});
|
||||
|
||||
// generate closure junction tables for all closure tables
|
||||
@ -125,7 +127,7 @@ export class EntityMetadataBuilder {
|
||||
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";
|
||||
});
|
||||
|
||||
@ -751,8 +751,8 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
*/
|
||||
private async buildJunctionOperations(options: { insert: boolean, remove: boolean }): Promise<void> {
|
||||
const promises = this.operateSubjects.filter(subject => subject.hasEntity).map(subject => {
|
||||
const promises = subject.metadata.manyToManyRelations.map(async relation => {
|
||||
|
||||
const metadata = subject.metadata.parentEntityMetadata ? subject.metadata.parentEntityMetadata : subject.metadata;
|
||||
const promises = metadata.manyToManyRelations.map(async relation => {
|
||||
// if subject marked to be removed then all its junctions must be removed
|
||||
if (subject.mustBeRemoved && options.remove) {
|
||||
// load from db all relation ids of inverse entities that are "bind" to the currently persisted entity
|
||||
|
||||
@ -119,7 +119,10 @@ export class JoinAttribute {
|
||||
return undefined;
|
||||
|
||||
const relationOwnerSelection = this.queryExpressionMap.findAliasByName(this.parentAlias!);
|
||||
const relation = relationOwnerSelection.metadata.findRelationWithPropertyPath(this.relationPropertyPath!);
|
||||
const metadata = relationOwnerSelection.metadata.parentEntityMetadata
|
||||
? relationOwnerSelection.metadata.parentEntityMetadata
|
||||
: relationOwnerSelection.metadata;
|
||||
const relation = metadata.findRelationWithPropertyPath(this.relationPropertyPath!);
|
||||
if (!relation)
|
||||
throw new Error(`Relation with property path ${this.relationPropertyPath} in entity was not found.`);
|
||||
return relation;
|
||||
|
||||
@ -111,12 +111,22 @@ export class RawSqlResultsToEntityTransformer {
|
||||
*/
|
||||
protected transformJoins(rawResults: any[], entity: ObjectLiteral, alias: Alias) {
|
||||
let hasData = false;
|
||||
let discriminatorValue: string = "";
|
||||
|
||||
if (alias.metadata.discriminatorColumn)
|
||||
discriminatorValue = rawResults[0][alias.name + "_" + alias.metadata.discriminatorColumn!.databaseName];
|
||||
|
||||
this.joinAttributes.forEach(join => {
|
||||
|
||||
// skip joins without metadata
|
||||
if (!join.metadata)
|
||||
return;
|
||||
|
||||
// this check need to avoid setting properties than not belong to entity when single table inheritance used.
|
||||
const metadata = alias.metadata.childEntityMetadatas.find(childEntityMetadata => discriminatorValue === childEntityMetadata.discriminatorValue);
|
||||
if (metadata && join.relation && metadata.target !== join.relation.target)
|
||||
return;
|
||||
|
||||
// some checks to make sure this join is for current alias
|
||||
if (join.mapToProperty) {
|
||||
if (join.mapToPropertyParentAlias !== alias.name)
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import {SingleEntityChild} from "../../../../../../../src/decorator/entity/SingleEntityChild";
|
||||
import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany";
|
||||
import {JoinTable} from "../../../../../../../src/decorator/relations/JoinTable";
|
||||
import {Employee} from "./Employee";
|
||||
import {Department} from "./Department";
|
||||
|
||||
@SingleEntityChild()
|
||||
export class Accountant extends Employee {
|
||||
|
||||
@ManyToMany(type => Department, department => department.accountants)
|
||||
@JoinTable()
|
||||
departments: Department[];
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import {Column} from "../../../../../../../src/decorator/columns/Column";
|
||||
import {Entity} from "../../../../../../../src/decorator/entity/Entity";
|
||||
import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
|
||||
import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany";
|
||||
import {Accountant} from "./Accountant";
|
||||
|
||||
@Entity()
|
||||
export class Department {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@ManyToMany(type => Accountant, accountant => accountant.departments)
|
||||
accountants: Accountant[];
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
import {Column} from "../../../../../../../src/decorator/columns/Column";
|
||||
import {SingleEntityChild} from "../../../../../../../src/decorator/entity/SingleEntityChild";
|
||||
import {Person} from "./Person";
|
||||
|
||||
@SingleEntityChild()
|
||||
export class Employee extends Person {
|
||||
|
||||
@Column()
|
||||
salary: number;
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import {Column} from "../../../../../../../src/decorator/columns/Column";
|
||||
import {Entity} from "../../../../../../../src/decorator/entity/Entity";
|
||||
import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
|
||||
import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany";
|
||||
import {Student} from "./Student";
|
||||
|
||||
@Entity()
|
||||
export class Faculty {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@ManyToMany(type => Student, student => student.faculties)
|
||||
students: Student[];
|
||||
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import {Column} from "../../../../../../../src/decorator/columns/Column";
|
||||
import {TableInheritance} from "../../../../../../../src/decorator/entity/TableInheritance";
|
||||
import {DiscriminatorColumn} from "../../../../../../../src/decorator/columns/DiscriminatorColumn";
|
||||
import {Entity} from "../../../../../../../src/decorator/entity/Entity";
|
||||
import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
|
||||
|
||||
@Entity()
|
||||
@TableInheritance("single-table")
|
||||
@DiscriminatorColumn({ name: "type", type: "string" })
|
||||
export class Person {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import {Column} from "../../../../../../../src/decorator/columns/Column";
|
||||
import {Entity} from "../../../../../../../src/decorator/entity/Entity";
|
||||
import {PrimaryGeneratedColumn} from "../../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
|
||||
import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany";
|
||||
import {Teacher} from "./Teacher";
|
||||
|
||||
@Entity()
|
||||
export class Specialization {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@ManyToMany(type => Teacher, teacher => teacher.specializations)
|
||||
teachers: Teacher[];
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import {SingleEntityChild} from "../../../../../../../src/decorator/entity/SingleEntityChild";
|
||||
import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany";
|
||||
import {Person} from "./Person";
|
||||
import {Faculty} from "./Faculty";
|
||||
import {JoinTable} from "../../../../../../../src/decorator/relations/JoinTable";
|
||||
|
||||
@SingleEntityChild()
|
||||
export class Student extends Person {
|
||||
|
||||
@ManyToMany(type => Faculty, faculty => faculty.students)
|
||||
@JoinTable()
|
||||
faculties: Faculty[];
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import {SingleEntityChild} from "../../../../../../../src/decorator/entity/SingleEntityChild";
|
||||
import {ManyToMany} from "../../../../../../../src/decorator/relations/ManyToMany";
|
||||
import {JoinTable} from "../../../../../../../src/decorator/relations/JoinTable";
|
||||
import {Employee} from "./Employee";
|
||||
import {Specialization} from "./Specialization";
|
||||
|
||||
@SingleEntityChild()
|
||||
export class Teacher extends Employee {
|
||||
|
||||
@ManyToMany(type => Specialization, specialization => specialization.teachers)
|
||||
@JoinTable()
|
||||
specializations: Specialization[];
|
||||
|
||||
}
|
||||
@ -0,0 +1,182 @@
|
||||
import "reflect-metadata";
|
||||
import {
|
||||
closeTestingConnections,
|
||||
createTestingConnections,
|
||||
reloadTestingDatabases
|
||||
} from "../../../../../utils/test-utils";
|
||||
import {Connection} from "../../../../../../src/connection/Connection";
|
||||
import {Student} from "./entity/Student";
|
||||
import {Teacher} from "./entity/Teacher";
|
||||
import {Accountant} from "./entity/Accountant";
|
||||
import {Employee} from "./entity/Employee";
|
||||
import {Person} from "./entity/Person";
|
||||
import {Faculty} from "./entity/Faculty";
|
||||
import {Specialization} from "./entity/Specialization";
|
||||
import {Department} from "./entity/Department";
|
||||
|
||||
describe("table-inheritance > single-table > relations > many-to-many", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await createTestingConnections({
|
||||
entities: [__dirname + "/entity/*{.js,.ts}"],
|
||||
schemaCreate: true,
|
||||
dropSchemaOnConnection: true,
|
||||
}));
|
||||
beforeEach(() => reloadTestingDatabases(connections));
|
||||
after(() => closeTestingConnections(connections));
|
||||
|
||||
describe("owner side", () => {
|
||||
|
||||
it("should work correctly with ManyToMany relations", () => Promise.all(connections.map(async connection => {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Create
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
const faculty1 = new Faculty();
|
||||
faculty1.name = "Economics";
|
||||
await connection.getRepository(Faculty).persist(faculty1);
|
||||
|
||||
const faculty2 = new Faculty();
|
||||
faculty2.name = "Programming";
|
||||
await connection.getRepository(Faculty).persist(faculty2);
|
||||
|
||||
const student = new Student();
|
||||
student.name = "Alice";
|
||||
student.faculties = [faculty1, faculty2];
|
||||
await connection.getRepository(Student).persist(student);
|
||||
|
||||
const specialization1 = new Specialization();
|
||||
specialization1.name = "Geography";
|
||||
await connection.getRepository(Specialization).persist(specialization1);
|
||||
|
||||
const specialization2 = new Specialization();
|
||||
specialization2.name = "Economist";
|
||||
await connection.getRepository(Specialization).persist(specialization2);
|
||||
|
||||
const teacher = new Teacher();
|
||||
teacher.name = "Mr. Garrison";
|
||||
teacher.specializations = [specialization1, specialization2];
|
||||
teacher.salary = 2000;
|
||||
await connection.getRepository(Teacher).persist(teacher);
|
||||
|
||||
const department1 = new Department();
|
||||
department1.name = "Bookkeeping";
|
||||
await connection.getRepository(Department).persist(department1);
|
||||
|
||||
const department2 = new Department();
|
||||
department2.name = "HR";
|
||||
await connection.getRepository(Department).persist(department2);
|
||||
|
||||
const accountant = new Accountant();
|
||||
accountant.name = "Mr. Burns";
|
||||
accountant.departments = [department1, department2];
|
||||
accountant.salary = 3000;
|
||||
await connection.getRepository(Accountant).persist(accountant);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Select
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
let loadedStudent = await connection.manager
|
||||
.createQueryBuilder(Student, "student")
|
||||
.leftJoinAndSelect("student.faculties", "faculty")
|
||||
.where("student.name = :name", { name: "Alice" })
|
||||
.getOne();
|
||||
|
||||
loadedStudent!.should.have.all.keys("id", "name", "faculties");
|
||||
loadedStudent!.id.should.equal(1);
|
||||
loadedStudent!.name.should.equal("Alice");
|
||||
loadedStudent!.faculties.length.should.equal(2);
|
||||
loadedStudent!.faculties[0].name.should.be.equal("Economics");
|
||||
loadedStudent!.faculties[1].name.should.be.equal("Programming");
|
||||
|
||||
let loadedTeacher = await connection.manager
|
||||
.createQueryBuilder(Teacher, "teacher")
|
||||
.leftJoinAndSelect("teacher.specializations", "specialization")
|
||||
.where("teacher.name = :name", { name: "Mr. Garrison" })
|
||||
.getOne();
|
||||
|
||||
loadedTeacher!.should.have.all.keys("id", "name", "specializations", "salary");
|
||||
loadedTeacher!.id.should.equal(2);
|
||||
loadedTeacher!.name.should.equal("Mr. Garrison");
|
||||
loadedTeacher!.specializations.length.should.equal(2);
|
||||
loadedTeacher!.specializations[0].name.should.be.equal("Geography");
|
||||
loadedTeacher!.specializations[1].name.should.be.equal("Economist");
|
||||
loadedTeacher!.salary.should.equal(2000);
|
||||
|
||||
let loadedAccountant = await connection.manager
|
||||
.createQueryBuilder(Accountant, "accountant")
|
||||
.leftJoinAndSelect("accountant.departments", "department")
|
||||
.where("accountant.name = :name", { name: "Mr. Burns" })
|
||||
.getOne();
|
||||
|
||||
loadedAccountant!.should.have.all.keys("id", "name", "departments", "salary");
|
||||
loadedAccountant!.id.should.equal(3);
|
||||
loadedAccountant!.name.should.equal("Mr. Burns");
|
||||
loadedAccountant!.departments.length.should.equal(2);
|
||||
loadedAccountant!.departments[0].name.should.be.equal("Bookkeeping");
|
||||
loadedAccountant!.departments[1].name.should.be.equal("HR");
|
||||
loadedAccountant!.salary.should.equal(3000);
|
||||
|
||||
const loadedEmployees = await connection.manager
|
||||
.createQueryBuilder(Employee, "employee")
|
||||
.leftJoinAndSelect("employee.specializations", "specialization")
|
||||
.leftJoinAndSelect("employee.departments", "department")
|
||||
.orderBy("employee.id, specialization.id, department.id")
|
||||
.getMany();
|
||||
|
||||
loadedEmployees[0].should.have.all.keys("id", "name", "salary", "specializations");
|
||||
loadedEmployees[0].should.be.instanceof(Teacher);
|
||||
loadedEmployees[0].id.should.equal(2);
|
||||
loadedEmployees[0].name.should.equal("Mr. Garrison");
|
||||
(loadedEmployees[0] as Teacher).specializations.length.should.equal(2);
|
||||
(loadedEmployees[0] as Teacher).specializations[0].name.should.be.equal("Geography");
|
||||
(loadedEmployees[0] as Teacher).specializations[1].name.should.be.equal("Economist");
|
||||
loadedEmployees[0].salary.should.equal(2000);
|
||||
loadedEmployees[1].should.have.all.keys("id", "name", "salary", "departments");
|
||||
loadedEmployees[1].should.be.instanceof(Accountant);
|
||||
loadedEmployees[1].id.should.equal(3);
|
||||
loadedEmployees[1].name.should.equal("Mr. Burns");
|
||||
(loadedEmployees[1] as Accountant).departments.length.should.equal(2);
|
||||
(loadedEmployees[1] as Accountant).departments[0].name.should.be.equal("Bookkeeping");
|
||||
(loadedEmployees[1] as Accountant).departments[1].name.should.be.equal("HR");
|
||||
loadedEmployees[1].salary.should.equal(3000);
|
||||
|
||||
const loadedPersons = await connection.manager
|
||||
.createQueryBuilder(Person, "person")
|
||||
.leftJoinAndSelect("person.faculties", "faculty")
|
||||
.leftJoinAndSelect("person.specializations", "specialization")
|
||||
.leftJoinAndSelect("person.departments", "department")
|
||||
.orderBy("person.id, specialization.id, department.id")
|
||||
.getMany();
|
||||
|
||||
loadedPersons[0].should.have.all.keys("id", "name", "faculties");
|
||||
loadedPersons[0].should.be.instanceof(Student);
|
||||
loadedPersons[0].id.should.equal(1);
|
||||
loadedPersons[0].name.should.equal("Alice");
|
||||
(loadedPersons[0] as Student).faculties.length.should.equal(2);
|
||||
(loadedPersons[0] as Student).faculties[0].name.should.be.equal("Economics");
|
||||
(loadedPersons[0] as Student).faculties[1].name.should.be.equal("Programming");
|
||||
loadedPersons[1].should.have.all.keys("id", "name", "salary", "specializations");
|
||||
loadedPersons[1].should.be.instanceof(Teacher);
|
||||
loadedPersons[1].id.should.equal(2);
|
||||
loadedPersons[1].name.should.equal("Mr. Garrison");
|
||||
(loadedPersons[1] as Teacher).specializations.length.should.equal(2);
|
||||
(loadedPersons[1] as Teacher).specializations[0].name.should.be.equal("Geography");
|
||||
(loadedPersons[1] as Teacher).specializations[1].name.should.be.equal("Economist");
|
||||
(loadedPersons[1] as Teacher).salary.should.equal(2000);
|
||||
loadedPersons[2].should.have.all.keys("id", "name", "salary", "departments");
|
||||
loadedPersons[2].should.be.instanceof(Accountant);
|
||||
loadedPersons[2].id.should.equal(3);
|
||||
loadedPersons[2].name.should.equal("Mr. Burns");
|
||||
(loadedPersons[2] as Accountant).departments.length.should.equal(2);
|
||||
(loadedPersons[2] as Accountant).departments[0].name.should.be.equal("Bookkeeping");
|
||||
(loadedPersons[2] as Accountant).departments[1].name.should.be.equal("HR");
|
||||
(loadedPersons[2] as Accountant).salary.should.equal(3000);
|
||||
|
||||
})));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user