Skip to content

Commit

Permalink
fix: the mpath is incorrect when the parent of the tree entity is null (
Browse files Browse the repository at this point in the history
#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
  • Loading branch information
Yuuki-Sakura authored Dec 3, 2022
1 parent 97fae63 commit 658604d
Show file tree
Hide file tree
Showing 4 changed files with 342 additions and 11 deletions.
45 changes: 41 additions & 4 deletions src/persistence/tree/MaterializedPathSubjectExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { Subject } from "../Subject"
import { QueryRunner } from "../../query-runner/QueryRunner"
import { OrmUtils } from "../../util/OrmUtils"
import { ObjectLiteral } from "../../common/ObjectLiteral"
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
import { EntityMetadata } from "../../metadata/EntityMetadata"
import { Brackets } from "../../query-builder/Brackets"

/**
* Executes subject operations for materialized-path tree entities.
Expand Down Expand Up @@ -81,8 +84,14 @@ export class MaterializedPathSubjectExecutor {
const oldParent = subject.metadata.treeParentRelation!.getEntityValue(
entity!,
)
const oldParentId = subject.metadata.getEntityIdMap(oldParent)
const newParentId = subject.metadata.getEntityIdMap(newParent)
const oldParentId = this.getEntityParentReferencedColumnMap(
subject,
oldParent,
)
const newParentId = this.getEntityParentReferencedColumnMap(
subject,
newParent,
)

// Exit if the new and old parents are the same
if (OrmUtils.compareIds(oldParentId, newParentId)) {
Expand Down Expand Up @@ -113,18 +122,40 @@ export class MaterializedPathSubjectExecutor {
.update(subject.metadata.target)
.set({
[propertyPath]: () =>
`REPLACE(${propertyPath}, '${oldParentPath}${entityPath}.', '${newParentPath}${entityPath}.')`,
`REPLACE(${this.queryRunner.connection.driver.escape(
propertyPath,
)}, '${oldParentPath}${entityPath}.', '${newParentPath}${entityPath}.')`,
} as any)
.where(`${propertyPath} LIKE :path`, {
path: `${oldParentPath}${entityPath}.%`,
})
.execute()
}

private getEntityParentReferencedColumnMap(
subject: Subject,
entity: ObjectLiteral | undefined,
): ObjectLiteral | undefined {
if (!entity) return undefined
return EntityMetadata.getValueMap(
entity,
subject.metadata
.treeParentRelation!.joinColumns.map(
(column) => column.referencedColumn,
)
.filter((v) => v != null) as ColumnMetadata[],
{ skipNulls: true },
)
}

private getEntityPath(
subject: Subject,
id: ObjectLiteral,
): Promise<string> {
const metadata = subject.metadata
const normalized = (Array.isArray(id) ? id : [id]).map((id) =>
metadata.ensureEntityIdMap(id),
)
return this.queryRunner.manager
.createQueryBuilder()
.select(
Expand All @@ -134,7 +165,13 @@ export class MaterializedPathSubjectExecutor {
"path",
)
.from(subject.metadata.target, subject.metadata.targetName)
.whereInIds(id)
.where(
new Brackets((qb) => {
for (const data of normalized) {
qb.orWhere(new Brackets((qb) => qb.where(data)))
}
}),
)
.getRawOne()
.then((result) => (result ? result["path"] : ""))
}
Expand Down
24 changes: 17 additions & 7 deletions src/util/TreeRepositoryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,20 @@ export class TreeRepositoryUtils {
): { id: any; parentId: any }[] {
return rawResults.map((rawResult) => {
const joinColumn = metadata.treeParentRelation!.joinColumns[0]
const referencedColumn =
joinColumn.referencedColumn ?? metadata.primaryColumns[0]
// fixes issue #2518, default to databaseName property when givenDatabaseName is not set
const joinColumnName =
joinColumn.givenDatabaseName || joinColumn.databaseName
const id =
rawResult[alias + "_" + metadata.primaryColumns[0].databaseName]
const referencedColumnName =
referencedColumn.givenDatabaseName ||
referencedColumn.databaseName
const id = rawResult[alias + "_" + referencedColumnName]
const parentId = rawResult[alias + "_" + joinColumnName]
return {
id: manager.connection.driver.prepareHydratedValue(
id,
metadata.primaryColumns[0],
referencedColumn,
),
parentId: manager.connection.driver.prepareHydratedValue(
parentId,
Expand All @@ -50,15 +54,18 @@ export class TreeRepositoryUtils {
entity[childProperty] = []
return
}
const parentEntityId = metadata.primaryColumns[0].getEntityValue(entity)
const joinColumn = metadata.treeParentRelation!.joinColumns[0]
const referencedColumn =
joinColumn.referencedColumn ?? metadata.primaryColumns[0]
const parentEntityId = referencedColumn.getEntityValue(entity)
const childRelationMaps = relationMaps.filter(
(relationMap) => relationMap.parentId === parentEntityId,
)
const childIds = new Set(
childRelationMaps.map((relationMap) => relationMap.id),
)
entity[childProperty] = entities.filter((entity) =>
childIds.has(metadata.primaryColumns[0].getEntityValue(entity)),
childIds.has(referencedColumn.getEntityValue(entity)),
)
entity[childProperty].forEach((childEntity: any) => {
TreeRepositoryUtils.buildChildrenEntityTree(
Expand All @@ -81,15 +88,18 @@ export class TreeRepositoryUtils {
relationMaps: { id: any; parentId: any }[],
): void {
const parentProperty = metadata.treeParentRelation!.propertyName
const entityId = metadata.primaryColumns[0].getEntityValue(entity)
const joinColumn = metadata.treeParentRelation!.joinColumns[0]
const referencedColumn =
joinColumn.referencedColumn ?? metadata.primaryColumns[0]
const entityId = referencedColumn.getEntityValue(entity)
const parentRelationMap = relationMaps.find(
(relationMap) => relationMap.id === entityId,
)
const parentEntity = entities.find((entity) => {
if (!parentRelationMap) return false

return (
metadata.primaryColumns[0].getEntityValue(entity) ===
referencedColumn.getEntityValue(entity) ===
parentRelationMap.parentId
)
})
Expand Down
41 changes: 41 additions & 0 deletions test/github-issues/9534/entity/Category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { TreeParent } from "../../../../src/decorator/tree/TreeParent"
import { TreeChildren } from "../../../../src/decorator/tree/TreeChildren"
import { Entity } from "../../../../src/decorator/entity/Entity"
import { Tree } from "../../../../src/decorator/tree/Tree"
import { JoinColumn } from "../../../../src/decorator/relations/JoinColumn"

@Entity({ name: "categories" })
@Tree("materialized-path")
export class Category {
@PrimaryGeneratedColumn()
id: number

@Column({
type: "varchar",
name: "uid",
unique: true,
})
uid: string

@Column()
name: string

@Column({
type: "varchar",
name: "parentUid",
nullable: true,
})
parentUid?: string | null

@TreeParent()
@JoinColumn({
name: "parentUid",
referencedColumnName: "uid",
})
parentCategory?: Category | null

@TreeChildren({ cascade: true })
childCategories: Category[]
}
Loading

0 comments on commit 658604d

Please sign in to comment.