-
-
Notifications
You must be signed in to change notification settings - Fork 6.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: STI types on children in joins (#3160)
* Use most specific matching relation type This allows using a single table for multiple entities without using a type column. Some setups infer the type from the context of how/where the row is loaded. * Use entity's relation metadata for joins If an STI child overrides a parent property and uses a different type doing a query on the parent type will skip the relation. This happens because the query's join is built using the parent relation but when mapping to an entity the child's relation is fetched so they don't match. Instead we now use the relation's propertyPath to allow using relations that reference the same property. We also copy the child's relation type into the join to ensure we get the right type in the child as well.
- Loading branch information
Showing
8 changed files
with
200 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
...ional/table-inheritance/single-table/relations/child-relation-type/child-relation-type.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import "reflect-metadata"; | ||
import { | ||
closeTestingConnections, | ||
createTestingConnections, | ||
reloadTestingDatabases | ||
} from "../../../../../utils/test-utils"; | ||
import {Connection} from "../../../../../../src"; | ||
import {Teacher} from "./entity/Teacher"; | ||
import {Accountant} from "./entity/Accountant"; | ||
import {Person} from "./entity/Person"; | ||
import {Specialization} from "./entity/Specialization"; | ||
import {Department} from "./entity/Department"; | ||
|
||
describe("table-inheritance > single-table > relations > child-relation-type", () => { | ||
|
||
let connections: Connection[]; | ||
before(async () => connections = await createTestingConnections({ | ||
entities: [__dirname + "/entity/*{.js,.ts}"] | ||
})); | ||
beforeEach(() => reloadTestingDatabases(connections)); | ||
after(() => closeTestingConnections(connections)); | ||
|
||
it("should work correctly with OneToMany relations", () => Promise.all(connections.map(async connection => { | ||
|
||
// ------------------------------------------------------------------------- | ||
// Create | ||
// ------------------------------------------------------------------------- | ||
|
||
const specialization1 = new Specialization(); | ||
specialization1.name = "Geography"; | ||
await connection.getRepository(Specialization).save(specialization1); | ||
|
||
const specialization2 = new Specialization(); | ||
specialization2.name = "Economist"; | ||
await connection.getRepository(Specialization).save(specialization2); | ||
|
||
const teacher = new Teacher(); | ||
teacher.name = "Mr. Garrison"; | ||
teacher.data = [specialization1, specialization2]; | ||
await connection.getRepository(Teacher).save(teacher); | ||
|
||
const department1 = new Department(); | ||
department1.name = "Bookkeeping"; | ||
await connection.getRepository(Department).save(department1); | ||
|
||
const department2 = new Department(); | ||
department2.name = "HR"; | ||
await connection.getRepository(Department).save(department2); | ||
|
||
const accountant = new Accountant(); | ||
accountant.name = "Mr. Burns"; | ||
accountant.data = [department1, department2]; | ||
await connection.getRepository(Accountant).save(accountant); | ||
|
||
// ------------------------------------------------------------------------- | ||
// Select | ||
// ------------------------------------------------------------------------- | ||
|
||
let loadedTeacher = await connection.manager | ||
.createQueryBuilder(Teacher, "teacher") | ||
.leftJoinAndSelect("teacher.data", "data") | ||
.where("teacher.name = :name", { name: "Mr. Garrison" }) | ||
.orderBy("teacher.id, data.id") | ||
.getOne(); | ||
|
||
loadedTeacher!.should.have.all.keys("id", "name", "data"); | ||
loadedTeacher!.id.should.equal(1); | ||
loadedTeacher!.name.should.equal("Mr. Garrison"); | ||
loadedTeacher!.data.length.should.equal(2); | ||
loadedTeacher!.data[0].should.be.instanceOf(Specialization); | ||
loadedTeacher!.data[0].name.should.be.equal("Geography"); | ||
loadedTeacher!.data[1].name.should.be.equal("Economist"); | ||
|
||
let loadedAccountant = await connection.manager | ||
.createQueryBuilder(Accountant, "accountant") | ||
.leftJoinAndSelect("accountant.data", "data") | ||
.where("accountant.name = :name", { name: "Mr. Burns" }) | ||
.orderBy("accountant.id, data.id") | ||
.getOne(); | ||
|
||
loadedAccountant!.should.have.all.keys("id", "name", "data"); | ||
loadedAccountant!.id.should.equal(2); | ||
loadedAccountant!.name.should.equal("Mr. Burns"); | ||
loadedAccountant!.data.length.should.equal(2); | ||
loadedAccountant!.data[0].should.be.instanceOf(Department); | ||
loadedAccountant!.data[0].name.should.be.equal("Bookkeeping"); | ||
loadedAccountant!.data[1].name.should.be.equal("HR"); | ||
|
||
const loadedPersons = await connection.manager | ||
.createQueryBuilder(Person, "person") | ||
.leftJoinAndSelect("person.data", "data") | ||
.orderBy("person.id, data.id") | ||
.getMany(); | ||
|
||
loadedPersons[0].should.have.all.keys("id", "name", "data"); | ||
loadedPersons[0].should.be.instanceof(Teacher); | ||
loadedPersons[0].id.should.equal(1); | ||
loadedPersons[0].name.should.equal("Mr. Garrison"); | ||
loadedPersons[0].data[0].should.be.instanceOf(Specialization); | ||
(loadedPersons[0] as Teacher).data.length.should.equal(2); | ||
(loadedPersons[0] as Teacher).data[0].name.should.be.equal("Geography"); | ||
(loadedPersons[0] as Teacher).data[1].name.should.be.equal("Economist"); | ||
loadedPersons[1].should.have.all.keys("id", "name", "data"); | ||
loadedPersons[1].should.be.instanceof(Accountant); | ||
loadedPersons[1].id.should.equal(2); | ||
loadedPersons[1].name.should.equal("Mr. Burns"); | ||
loadedPersons[1].data[0].should.be.instanceOf(Department); | ||
(loadedPersons[1] as Accountant).data.length.should.equal(2); | ||
(loadedPersons[1] as Accountant).data[0].name.should.be.equal("Bookkeeping"); | ||
(loadedPersons[1] as Accountant).data[1].name.should.be.equal("HR"); | ||
|
||
}))); | ||
|
||
}); |
11 changes: 11 additions & 0 deletions
11
...ctional/table-inheritance/single-table/relations/child-relation-type/entity/Accountant.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import {ChildEntity, OneToMany} from "../../../../../../../src"; | ||
import {Department} from "./Department"; | ||
import {Person} from "./Person"; | ||
|
||
@ChildEntity() | ||
export class Accountant extends Person { | ||
|
||
@OneToMany(() => Department, department => department.person) | ||
data: Department[]; | ||
|
||
} |
16 changes: 16 additions & 0 deletions
16
test/functional/table-inheritance/single-table/relations/child-relation-type/entity/Data.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from "../../../../../../../src"; | ||
import {Person} from "./Person"; | ||
|
||
@Entity("data") | ||
export abstract class Data { | ||
|
||
@PrimaryGeneratedColumn() | ||
id: number; | ||
|
||
@Column() | ||
name: string; | ||
|
||
@ManyToOne(() => Person, person => person.data) | ||
person: Person; | ||
|
||
} |
11 changes: 11 additions & 0 deletions
11
...ctional/table-inheritance/single-table/relations/child-relation-type/entity/Department.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import {Entity, ManyToOne} from "../../../../../../../src"; | ||
import {Accountant} from "./Accountant"; | ||
import {Data} from "./Data"; | ||
|
||
@Entity("data") | ||
export class Department extends Data { | ||
|
||
@ManyToOne(() => Accountant, accountant => accountant.data) | ||
person: Accountant; | ||
|
||
} |
17 changes: 17 additions & 0 deletions
17
.../functional/table-inheritance/single-table/relations/child-relation-type/entity/Person.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import {Column, Entity, OneToMany, PrimaryGeneratedColumn, TableInheritance} from "../../../../../../../src"; | ||
import {Data} from "./Data"; | ||
|
||
@Entity() | ||
@TableInheritance({column: {name: "type", type: "varchar"}}) | ||
export class Person { | ||
|
||
@PrimaryGeneratedColumn() | ||
id: number; | ||
|
||
@Column() | ||
name: string; | ||
|
||
@OneToMany(() => Data, data => data.person) | ||
data: Data[]; | ||
|
||
} |
11 changes: 11 additions & 0 deletions
11
...nal/table-inheritance/single-table/relations/child-relation-type/entity/Specialization.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import {Entity, ManyToOne} from "../../../../../../../src"; | ||
import {Teacher} from "./Teacher"; | ||
import {Data} from "./Data"; | ||
|
||
@Entity("data") | ||
export class Specialization extends Data { | ||
|
||
@ManyToOne(() => Teacher, teacher => teacher.data) | ||
person: Teacher; | ||
|
||
} |
11 changes: 11 additions & 0 deletions
11
...functional/table-inheritance/single-table/relations/child-relation-type/entity/Teacher.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import {ChildEntity, OneToMany} from "../../../../../../../src"; | ||
import {Specialization} from "./Specialization"; | ||
import {Person} from "./Person"; | ||
|
||
@ChildEntity() | ||
export class Teacher extends Person { | ||
|
||
@OneToMany(() => Specialization, specialization => specialization.person) | ||
data: Specialization[]; | ||
|
||
} |