Skip to content

Commit 658604d

Browse files
authored
fix: the mpath is incorrect when the parent of the tree entity is null (#9535)
* fix: the mpath is incorrect when the parent of the tree entity is null * lint: code format * fix: findTrees not have children * test: add unit test * style: format code * fix: unit test * fix: unit test * fix: unit test
1 parent 97fae63 commit 658604d

File tree

4 files changed

+342
-11
lines changed

4 files changed

+342
-11
lines changed

src/persistence/tree/MaterializedPathSubjectExecutor.ts

+41-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { Subject } from "../Subject"
22
import { QueryRunner } from "../../query-runner/QueryRunner"
33
import { OrmUtils } from "../../util/OrmUtils"
44
import { ObjectLiteral } from "../../common/ObjectLiteral"
5+
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
6+
import { EntityMetadata } from "../../metadata/EntityMetadata"
7+
import { Brackets } from "../../query-builder/Brackets"
58

69
/**
710
* Executes subject operations for materialized-path tree entities.
@@ -81,8 +84,14 @@ export class MaterializedPathSubjectExecutor {
8184
const oldParent = subject.metadata.treeParentRelation!.getEntityValue(
8285
entity!,
8386
)
84-
const oldParentId = subject.metadata.getEntityIdMap(oldParent)
85-
const newParentId = subject.metadata.getEntityIdMap(newParent)
87+
const oldParentId = this.getEntityParentReferencedColumnMap(
88+
subject,
89+
oldParent,
90+
)
91+
const newParentId = this.getEntityParentReferencedColumnMap(
92+
subject,
93+
newParent,
94+
)
8695

8796
// Exit if the new and old parents are the same
8897
if (OrmUtils.compareIds(oldParentId, newParentId)) {
@@ -113,18 +122,40 @@ export class MaterializedPathSubjectExecutor {
113122
.update(subject.metadata.target)
114123
.set({
115124
[propertyPath]: () =>
116-
`REPLACE(${propertyPath}, '${oldParentPath}${entityPath}.', '${newParentPath}${entityPath}.')`,
125+
`REPLACE(${this.queryRunner.connection.driver.escape(
126+
propertyPath,
127+
)}, '${oldParentPath}${entityPath}.', '${newParentPath}${entityPath}.')`,
117128
} as any)
118129
.where(`${propertyPath} LIKE :path`, {
119130
path: `${oldParentPath}${entityPath}.%`,
120131
})
121132
.execute()
122133
}
123134

135+
private getEntityParentReferencedColumnMap(
136+
subject: Subject,
137+
entity: ObjectLiteral | undefined,
138+
): ObjectLiteral | undefined {
139+
if (!entity) return undefined
140+
return EntityMetadata.getValueMap(
141+
entity,
142+
subject.metadata
143+
.treeParentRelation!.joinColumns.map(
144+
(column) => column.referencedColumn,
145+
)
146+
.filter((v) => v != null) as ColumnMetadata[],
147+
{ skipNulls: true },
148+
)
149+
}
150+
124151
private getEntityPath(
125152
subject: Subject,
126153
id: ObjectLiteral,
127154
): Promise<string> {
155+
const metadata = subject.metadata
156+
const normalized = (Array.isArray(id) ? id : [id]).map((id) =>
157+
metadata.ensureEntityIdMap(id),
158+
)
128159
return this.queryRunner.manager
129160
.createQueryBuilder()
130161
.select(
@@ -134,7 +165,13 @@ export class MaterializedPathSubjectExecutor {
134165
"path",
135166
)
136167
.from(subject.metadata.target, subject.metadata.targetName)
137-
.whereInIds(id)
168+
.where(
169+
new Brackets((qb) => {
170+
for (const data of normalized) {
171+
qb.orWhere(new Brackets((qb) => qb.where(data)))
172+
}
173+
}),
174+
)
138175
.getRawOne()
139176
.then((result) => (result ? result["path"] : ""))
140177
}

src/util/TreeRepositoryUtils.ts

+17-7
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@ export class TreeRepositoryUtils {
1919
): { id: any; parentId: any }[] {
2020
return rawResults.map((rawResult) => {
2121
const joinColumn = metadata.treeParentRelation!.joinColumns[0]
22+
const referencedColumn =
23+
joinColumn.referencedColumn ?? metadata.primaryColumns[0]
2224
// fixes issue #2518, default to databaseName property when givenDatabaseName is not set
2325
const joinColumnName =
2426
joinColumn.givenDatabaseName || joinColumn.databaseName
25-
const id =
26-
rawResult[alias + "_" + metadata.primaryColumns[0].databaseName]
27+
const referencedColumnName =
28+
referencedColumn.givenDatabaseName ||
29+
referencedColumn.databaseName
30+
const id = rawResult[alias + "_" + referencedColumnName]
2731
const parentId = rawResult[alias + "_" + joinColumnName]
2832
return {
2933
id: manager.connection.driver.prepareHydratedValue(
3034
id,
31-
metadata.primaryColumns[0],
35+
referencedColumn,
3236
),
3337
parentId: manager.connection.driver.prepareHydratedValue(
3438
parentId,
@@ -50,15 +54,18 @@ export class TreeRepositoryUtils {
5054
entity[childProperty] = []
5155
return
5256
}
53-
const parentEntityId = metadata.primaryColumns[0].getEntityValue(entity)
57+
const joinColumn = metadata.treeParentRelation!.joinColumns[0]
58+
const referencedColumn =
59+
joinColumn.referencedColumn ?? metadata.primaryColumns[0]
60+
const parentEntityId = referencedColumn.getEntityValue(entity)
5461
const childRelationMaps = relationMaps.filter(
5562
(relationMap) => relationMap.parentId === parentEntityId,
5663
)
5764
const childIds = new Set(
5865
childRelationMaps.map((relationMap) => relationMap.id),
5966
)
6067
entity[childProperty] = entities.filter((entity) =>
61-
childIds.has(metadata.primaryColumns[0].getEntityValue(entity)),
68+
childIds.has(referencedColumn.getEntityValue(entity)),
6269
)
6370
entity[childProperty].forEach((childEntity: any) => {
6471
TreeRepositoryUtils.buildChildrenEntityTree(
@@ -81,15 +88,18 @@ export class TreeRepositoryUtils {
8188
relationMaps: { id: any; parentId: any }[],
8289
): void {
8390
const parentProperty = metadata.treeParentRelation!.propertyName
84-
const entityId = metadata.primaryColumns[0].getEntityValue(entity)
91+
const joinColumn = metadata.treeParentRelation!.joinColumns[0]
92+
const referencedColumn =
93+
joinColumn.referencedColumn ?? metadata.primaryColumns[0]
94+
const entityId = referencedColumn.getEntityValue(entity)
8595
const parentRelationMap = relationMaps.find(
8696
(relationMap) => relationMap.id === entityId,
8797
)
8898
const parentEntity = entities.find((entity) => {
8999
if (!parentRelationMap) return false
90100

91101
return (
92-
metadata.primaryColumns[0].getEntityValue(entity) ===
102+
referencedColumn.getEntityValue(entity) ===
93103
parentRelationMap.parentId
94104
)
95105
})
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
2+
import { Column } from "../../../../src/decorator/columns/Column"
3+
import { TreeParent } from "../../../../src/decorator/tree/TreeParent"
4+
import { TreeChildren } from "../../../../src/decorator/tree/TreeChildren"
5+
import { Entity } from "../../../../src/decorator/entity/Entity"
6+
import { Tree } from "../../../../src/decorator/tree/Tree"
7+
import { JoinColumn } from "../../../../src/decorator/relations/JoinColumn"
8+
9+
@Entity({ name: "categories" })
10+
@Tree("materialized-path")
11+
export class Category {
12+
@PrimaryGeneratedColumn()
13+
id: number
14+
15+
@Column({
16+
type: "varchar",
17+
name: "uid",
18+
unique: true,
19+
})
20+
uid: string
21+
22+
@Column()
23+
name: string
24+
25+
@Column({
26+
type: "varchar",
27+
name: "parentUid",
28+
nullable: true,
29+
})
30+
parentUid?: string | null
31+
32+
@TreeParent()
33+
@JoinColumn({
34+
name: "parentUid",
35+
referencedColumnName: "uid",
36+
})
37+
parentCategory?: Category | null
38+
39+
@TreeChildren({ cascade: true })
40+
childCategories: Category[]
41+
}

0 commit comments

Comments
 (0)