fix(tree-entity): closure junction table primary key definition should match parent table (#11422)

* fix #9600 https://github.com/typeorm/typeorm/issues/9600

* implement tests.

* implement tests.

* implement tests.

* move tests fo functional tab.
implement remaining tests.
implement remaining fields.

* fix code rabbit detected errors.

* add MySQL-specific type checks to closure-table tests

* split closure-table tests for MySQL-specific scenarios

* remove redundant MySQL type checks from closure-table tests

* move MySQL-specific closure-table tests to a separate file

---------

Co-authored-by: Gonçalo Alves <goncalo.alves@knowledgeworks.pt>
Co-authored-by: gioboa <giorgiob.boa@gmail.com>
This commit is contained in:
gongAll 2025-06-16 22:08:08 +01:00 committed by GitHub
parent 03faa7867e
commit ce23d4648e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 265 additions and 0 deletions

View File

@ -63,6 +63,13 @@ export class ClosureJunctionEntityMetadataBuilder {
primary: true,
length: primaryColumn.length,
type: primaryColumn.type,
unsigned: primaryColumn.unsigned,
width: primaryColumn.width,
precision: primaryColumn.precision,
scale: primaryColumn.scale,
zerofill: primaryColumn.zerofill,
charset: primaryColumn.charset,
collation: primaryColumn.collation,
},
},
}),
@ -88,6 +95,13 @@ export class ClosureJunctionEntityMetadataBuilder {
primary: true,
length: primaryColumn.length,
type: primaryColumn.type,
unsigned: primaryColumn.unsigned,
width: primaryColumn.width,
precision: primaryColumn.precision,
scale: primaryColumn.scale,
zerofill: primaryColumn.zerofill,
charset: primaryColumn.charset,
collation: primaryColumn.collation,
},
},
}),

View File

@ -0,0 +1,135 @@
import "reflect-metadata"
import { Foo1Entity } from "./entity/Foo1"
import { Foo2Entity } from "./entity/Foo2"
import { Foo3Entity } from "./entity/Foo3"
import { DataSource } from "../../../../src/data-source/DataSource"
import {
closeTestingConnections,
createTestingConnections,
reloadTestingDatabases,
} from "../../../utils/test-utils"
import { expect } from "chai"
describe("mysql > tree tables > closure-table", () => {
let connections: DataSource[]
before(
async () =>
(connections = await createTestingConnections({
entities: [Foo1Entity, Foo2Entity, Foo3Entity],
enabledDrivers: ["mysql"],
})),
)
beforeEach(() => reloadTestingDatabases(connections))
after(() => closeTestingConnections(connections))
it("foo1 should create closure columns unsigned", () =>
Promise.all(
connections.map(async (dataSource) => {
const fooMetadata = dataSource.entityMetadatas.find(
(el) => el.tableName === "foo1",
)!
expect(fooMetadata).to.exist
const fooClosureMetadata = dataSource.entityMetadatas.find(
(el) => el.tableName === "foo1_closure",
)!
expect(fooClosureMetadata).to.exist
const ancestorCol = fooClosureMetadata.columns.find(
(col) => col.databaseName === "ancestor_id",
)!
expect(ancestorCol).to.exist
const descendantCol = fooClosureMetadata.columns.find(
(col) => col.databaseName === "descendant_id",
)!
expect(descendantCol).to.exist
expect(ancestorCol.unsigned).to.be.true
expect(descendantCol.unsigned).to.be.true
}),
))
it("foo2 should create closure columns with specified zerofill, width, precision and scale", () =>
Promise.all(
connections.map(async (dataSource) => {
const fooMetadata = dataSource.entityMetadatas.find(
(el) => el.tableName === "foo2",
)!
expect(fooMetadata).to.exist
const fooClosureMetadata = dataSource.entityMetadatas.find(
(el) => el.tableName === "foo2_closure",
)!
expect(fooClosureMetadata).to.exist
const ancestorCol = fooClosureMetadata.columns.find(
(col) => col.databaseName === "ancestor_id",
)!
expect(ancestorCol).to.exist
const descendantCol = fooClosureMetadata.columns.find(
(col) => col.databaseName === "descendant_id",
)!
expect(descendantCol).to.exist
expect(ancestorCol.zerofill).to.be.true
expect(descendantCol.zerofill).to.be.true
expect(ancestorCol.width).to.be.eq(13)
expect(descendantCol.width).to.be.eq(13)
expect(ancestorCol.precision).to.be.eq(9)
expect(descendantCol.precision).to.be.eq(9)
expect(ancestorCol.scale).to.be.eq(3)
expect(descendantCol.scale).to.be.eq(3)
}),
))
it("foo3 should create closure columns with specified length, charset and collation", () =>
Promise.all(
connections.map(async (dataSource) => {
const fooMetadata = dataSource.entityMetadatas.find(
(el) => el.tableName === "foo3",
)!
expect(fooMetadata).to.exist
const fooClosureMetadata = dataSource.entityMetadatas.find(
(el) => el.tableName === "foo3_closure",
)!
expect(fooClosureMetadata).to.exist
const ancestorCol = fooClosureMetadata.columns.find(
(col) => col.databaseName === "ancestor_id",
)!
expect(ancestorCol).to.exist
const descendantCol = fooClosureMetadata.columns.find(
(col) => col.databaseName === "descendant_id",
)!
expect(descendantCol).to.exist
expect(ancestorCol.length).to.be.eq("201")
expect(descendantCol.length).to.be.eq("201")
expect(ancestorCol.charset).to.be.eq("latin1")
expect(descendantCol.charset).to.be.eq("latin1")
expect(ancestorCol.collation).to.be.eq("latin1_bin")
expect(descendantCol.collation).to.be.eq("latin1_bin")
}),
))
})

View File

@ -0,0 +1,30 @@
import {
Column,
Entity,
JoinColumn,
PrimaryGeneratedColumn,
Tree,
TreeChildren,
TreeParent,
} from "../../../../../src"
@Entity({ name: "foo1" })
@Tree("closure-table", {
closureTableName: "foo1",
ancestorColumnName: () => "ancestor_id",
descendantColumnName: () => "descendant_id",
})
export class Foo1Entity {
@PrimaryGeneratedColumn({ type: "int", name: "id", unsigned: true })
id: number
@Column({ type: "int", name: "parent_id", unsigned: true })
parentId: number
@TreeParent()
@JoinColumn({ name: "parent_id", referencedColumnName: "id" })
parent: Foo1Entity
@TreeChildren()
children: Foo1Entity[]
}

View File

@ -0,0 +1,44 @@
import {
Column,
Entity,
JoinColumn,
PrimaryColumn,
Tree,
TreeChildren,
TreeParent,
} from "../../../../../src"
@Entity({ name: "foo2" })
@Tree("closure-table", {
closureTableName: "foo2",
ancestorColumnName: () => "ancestor_id",
descendantColumnName: () => "descendant_id",
})
export class Foo2Entity {
@PrimaryColumn({
type: "decimal",
name: "id",
zerofill: true,
width: 13,
precision: 9,
scale: 3,
})
id: number
@Column({
type: "decimal",
name: "parent_id",
zerofill: true,
width: 13,
precision: 9,
scale: 3,
})
parentId: number
@TreeParent()
@JoinColumn({ name: "parent_id", referencedColumnName: "id" })
parent: Foo2Entity
@TreeChildren()
children: Foo2Entity[]
}

View File

@ -0,0 +1,42 @@
import {
Column,
Entity,
JoinColumn,
PrimaryColumn,
Tree,
TreeChildren,
TreeParent,
} from "../../../../../src"
@Entity({ name: "foo3" })
@Tree("closure-table", {
closureTableName: "foo3",
ancestorColumnName: () => "ancestor_id",
descendantColumnName: () => "descendant_id",
})
export class Foo3Entity {
@PrimaryColumn({
type: "varchar",
name: "id",
length: 201,
charset: "latin1",
collation: "latin1_bin",
})
id: string
@Column({
type: "varchar",
name: "parent_id",
length: 201,
charset: "latin1",
collation: "latin1_bin",
})
parentId: string
@TreeParent()
@JoinColumn({ name: "parent_id", referencedColumnName: "id" })
parent: Foo3Entity
@TreeChildren()
children: Foo3Entity[]
}