Skip to content

Commit 06c1e98

Browse files
authored
fix: create correct children during cascade saving entities with STI (#9034)
* test: test saving disciminators STI, cascading This commit adds an test for checking whether discriminators are saved correctly when saving a field with cascade that uses Single-Table-Inheritance. Related to: #7758 * fix: Create correct children with STI This commit fixes the `create` function for EntityManager and Repository to create entities of correct type when using Single Table Inheritance. Refactors the otherwise repeated code into a new function on EntityMetadata. Related to: #7758 * test: check STI type setting discriminator manually Related to: #9033 * feature: allow setting discriminator value manually This commit allows using an instance of a base class in a Single Table Inheritance scenario and setting the discriminator value manually. Related to: #9033 * test: test saving disciminators with trees in STI This commit adds an test for checking whether discriminators are saved correctly when saving a tree that also uses Single-Table-Inheritance. Related to: #7758 * fix: Create correct children with STI and trees This commit fixes the `create` function for EntityManager and TreeRepository to create entities of correct type when using Single Table Inheritance and complex inheritance with Trees. Related to: #7758
1 parent 96b7ee4 commit 06c1e98

File tree

18 files changed

+507
-39
lines changed

18 files changed

+507
-39
lines changed

src/metadata-builder/EntityMetadataBuilder.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,23 @@ export class EntityMetadataBuilder {
607607
) {
608608
entityMetadata.ownColumns.push(discriminatorColumn)
609609
}
610+
// also copy the inheritance pattern & tree metadata
611+
// this comes in handy when inheritance and trees are used together
612+
entityMetadata.inheritancePattern =
613+
entityMetadata.parentEntityMetadata.inheritancePattern
614+
if (
615+
!entityMetadata.treeType &&
616+
!!entityMetadata.parentEntityMetadata.treeType
617+
) {
618+
entityMetadata.treeType =
619+
entityMetadata.parentEntityMetadata.treeType
620+
entityMetadata.treeOptions =
621+
entityMetadata.parentEntityMetadata.treeOptions
622+
entityMetadata.treeParentRelation =
623+
entityMetadata.parentEntityMetadata.treeParentRelation
624+
entityMetadata.treeLevelColumn =
625+
entityMetadata.parentEntityMetadata.treeLevelColumn
626+
}
610627
}
611628

612629
const { namingStrategy } = this.connection

src/metadata/EntityMetadata.ts

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -829,31 +829,67 @@ export class EntityMetadata {
829829
relationsAndValues.push([
830830
relation,
831831
subValue,
832-
this.getInverseEntityMetadata(subValue, relation),
832+
EntityMetadata.getInverseEntityMetadata(
833+
subValue,
834+
relation,
835+
),
833836
]),
834837
)
835838
} else if (value) {
836839
relationsAndValues.push([
837840
relation,
838841
value,
839-
this.getInverseEntityMetadata(value, relation),
842+
EntityMetadata.getInverseEntityMetadata(value, relation),
840843
])
841844
}
842845
})
843846
return relationsAndValues
844847
}
845848

846-
private getInverseEntityMetadata(
849+
/**
850+
* In the case of SingleTableInheritance, find the correct metadata
851+
* for a given value.
852+
*
853+
* @param value The value to find the metadata for.
854+
* @returns The found metadata for the entity or the base metadata if no matching metadata
855+
* was found in the whole inheritance tree.
856+
*/
857+
findInheritanceMetadata(value: any): EntityMetadata {
858+
// Check for single table inheritance and find the correct metadata in that case.
859+
// Goal is to use the correct discriminator as we could have a repository
860+
// for an (abstract) base class and thus the target would not match.
861+
862+
if (
863+
this.inheritancePattern === "STI" &&
864+
this.childEntityMetadatas.length > 0
865+
) {
866+
// There could be a column on the base class that can manually be set to override the type.
867+
let manuallySetDiscriminatorValue: unknown
868+
if (this.discriminatorColumn) {
869+
manuallySetDiscriminatorValue =
870+
value[this.discriminatorColumn.propertyName]
871+
}
872+
return (
873+
this.childEntityMetadatas.find(
874+
(meta) =>
875+
manuallySetDiscriminatorValue ===
876+
meta.discriminatorValue ||
877+
value.constructor === meta.target,
878+
) || this
879+
)
880+
}
881+
return this
882+
}
883+
884+
// -------------------------------------------------------------------------
885+
// Private Static Methods
886+
// -------------------------------------------------------------------------
887+
888+
private static getInverseEntityMetadata(
847889
value: any,
848890
relation: RelationMetadata,
849891
): EntityMetadata {
850-
const childEntityMetadata =
851-
relation.inverseEntityMetadata.childEntityMetadatas.find(
852-
(metadata) => metadata.target === value.constructor,
853-
)
854-
return childEntityMetadata
855-
? childEntityMetadata
856-
: relation.inverseEntityMetadata
892+
return relation.inverseEntityMetadata.findInheritanceMetadata(value)
857893
}
858894

859895
// -------------------------------------------------------------------------

src/persistence/EntityPersistExecutor.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -81,24 +81,9 @@ export class EntityPersistExecutor {
8181
if (entityTarget === Object)
8282
throw new CannotDetermineEntityError(this.mode)
8383

84-
let metadata = this.connection.getMetadata(entityTarget)
85-
86-
// Check for single table inheritance and find the correct metadata in that case.
87-
// Goal is to use the correct discriminator as we could have a repository
88-
// for an (abstract) base class and thus the target would not match.
89-
if (
90-
metadata.inheritancePattern === "STI" &&
91-
metadata.childEntityMetadatas.length > 0
92-
) {
93-
const matchingChildMetadata =
94-
metadata.childEntityMetadatas.find(
95-
(meta) =>
96-
entity.constructor === meta.target,
97-
)
98-
if (matchingChildMetadata) {
99-
metadata = matchingChildMetadata
100-
}
101-
}
84+
let metadata = this.connection
85+
.getMetadata(entityTarget)
86+
.findInheritanceMetadata(entity)
10287

10388
subjects.push(
10489
new Subject({

src/query-builder/transformer/PlainObjectToNewEntityTransformer.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,24 @@ export class PlainObjectToNewEntityTransformer {
8282
)
8383
})
8484

85+
const inverseEntityMetadata =
86+
relation.inverseEntityMetadata.findInheritanceMetadata(
87+
objectRelatedValueItem,
88+
)
89+
8590
// if such item already exist then merge new data into it, if its not we create a new entity and merge it into the array
8691
if (!objectRelatedValueEntity) {
8792
objectRelatedValueEntity =
88-
relation.inverseEntityMetadata.create(
89-
undefined,
90-
{ fromDeserializer: true },
91-
)
93+
inverseEntityMetadata.create(undefined, {
94+
fromDeserializer: true,
95+
})
9296
entityRelatedValue.push(objectRelatedValueEntity)
9397
}
9498

9599
this.groupAndTransform(
96100
objectRelatedValueEntity,
97101
objectRelatedValueItem,
98-
relation.inverseEntityMetadata,
102+
inverseEntityMetadata,
99103
getLazyRelationsPromiseValue,
100104
)
101105
})
@@ -110,18 +114,25 @@ export class PlainObjectToNewEntityTransformer {
110114
return
111115
}
112116

117+
const inverseEntityMetadata =
118+
relation.inverseEntityMetadata.findInheritanceMetadata(
119+
objectRelatedValue,
120+
)
121+
113122
if (!entityRelatedValue) {
114-
entityRelatedValue =
115-
relation.inverseEntityMetadata.create(undefined, {
123+
entityRelatedValue = inverseEntityMetadata.create(
124+
undefined,
125+
{
116126
fromDeserializer: true,
117-
})
127+
},
128+
)
118129
relation.setEntityValue(entity, entityRelatedValue)
119130
}
120131

121132
this.groupAndTransform(
122133
entityRelatedValue,
123134
objectRelatedValue,
124-
relation.inverseEntityMetadata,
135+
inverseEntityMetadata,
125136
getLazyRelationsPromiseValue,
126137
)
127138
}

test/github-issues/2927/issue-2927.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe("github issues > #2927 When using base class' custom repository, the di
4141
// Retrieve it back from the DB.
4242
const contents = await repository.find()
4343
expect(contents.length).to.equal(1)
44-
expect(contents[0] instanceof Photo).to.equal(true)
44+
expect(contents[0]).to.be.an.instanceOf(Photo)
4545
const fetchedPhoto = contents[0] as Photo
4646
expect(fetchedPhoto).to.eql(photo)
4747
}),
@@ -64,7 +64,7 @@ describe("github issues > #2927 When using base class' custom repository, the di
6464
// Retrieve it back from the DB.
6565
const contents = await repository.find()
6666
expect(contents.length).to.equal(1)
67-
expect(contents[0] instanceof SpecialPhoto).to.equal(true)
67+
expect(contents[0]).to.be.an.instanceOf(SpecialPhoto)
6868
const fetchedSpecialPhoto = contents[0] as SpecialPhoto
6969
expect(fetchedSpecialPhoto).to.eql(specialPhoto)
7070
}),
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {
2+
Column,
3+
Entity,
4+
ManyToOne,
5+
PrimaryGeneratedColumn,
6+
TableInheritance,
7+
} from "../../../../src"
8+
9+
import { PersonEntity } from "./Person"
10+
11+
@Entity("animal")
12+
@TableInheritance({ column: { type: "varchar", name: "type" } })
13+
export class AnimalEntity {
14+
@PrimaryGeneratedColumn()
15+
id: number
16+
17+
@Column({ type: "varchar" })
18+
name: string
19+
20+
@ManyToOne(() => PersonEntity, ({ pets }) => pets, {
21+
onDelete: "CASCADE",
22+
onUpdate: "CASCADE",
23+
})
24+
person: PersonEntity
25+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ChildEntity, Column } from "../../../../src"
2+
3+
import { AnimalEntity } from "./Animal"
4+
5+
@ChildEntity("cat")
6+
export class CatEntity extends AnimalEntity {
7+
// Cat stuff
8+
@Column()
9+
livesLeft: number
10+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {
2+
Column,
3+
Entity,
4+
PrimaryGeneratedColumn,
5+
TableInheritance,
6+
} from "../../../../src"
7+
8+
@Entity("content")
9+
@TableInheritance({ column: { type: "varchar", name: "type" } })
10+
export class Content {
11+
@PrimaryGeneratedColumn()
12+
id: number
13+
14+
@Column()
15+
title: string
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ChildEntity, Column } from "../../../../src"
2+
3+
import { AnimalEntity } from "./Animal"
4+
5+
@ChildEntity("dog")
6+
export class DogEntity extends AnimalEntity {
7+
// Dog stuff
8+
@Column()
9+
steaksEaten: number
10+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ChildEntity, Column, TreeChildren } from "../../../../src"
2+
3+
import { OperatorTreeEntry } from "./OperatorTreeEntry"
4+
5+
@ChildEntity("nnary")
6+
export class NnaryOperator extends OperatorTreeEntry {
7+
@TreeChildren({ cascade: true })
8+
children: OperatorTreeEntry[]
9+
10+
@Column()
11+
operator: string
12+
}

0 commit comments

Comments
 (0)