-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
Description
Issue description
When declaring manual @ManyToMany relations that involve a specific Junction Table, scanning the junction table's metadata gives the wrong column results
Expected Behavior
Given the following entities:
@Entity('parent_one')
export class ParentOne {
@PrimaryColumn({ type: 'int', name: 'id', unsigned: true })
public id!: number;
@Column({ type: 'int', name: 'test_column', unsigned: true })
public testColumn!: number;
@OneToMany((type) => ParentOneHasParentTwos, (parentOneHasParentTwos) => parentOneHasParentTwos.parentOne)
public parentOneHasParentTwos!: ParentOneHasParentTwos[]
@ManyToMany((type) => ParentTwo)
@JoinTable({
name: 'parent_one_has_parent_twos',
joinColumn: {
name: 'parent_one_id',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'parent_two_id',
referencedColumnName: 'id',
},
})
public parentTwos!: ParentTwo[];
}
@Entity('parent_two')
export class ParentTwo {
@PrimaryColumn({ type: 'int', name: 'id', unsigned: true })
public id!: number;
@OneToMany((type) => ParentOneHasParentTwos, (parentOneHasParentTwos) => parentOneHasParentTwos.parentOne)
public parentOneHasParentTwos!: ParentOneHasParentTwos[]
@ManyToMany((type) => ParentOne)
@JoinTable({
name: 'parent_one_has_parent_twos',
joinColumn: {
name: 'parent_two_id',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'parent_one_id',
referencedColumnName: 'id',
},
})
public parentOnes!: ParentOne[];
}
@Entity('parent_one_has_parent_twos')
export class ParentOneHasParentTwos {
@PrimaryColumn({ type: 'int', name: 'id', unsigned: true })
public id!: number;
@Column({ type: 'int', name: 'parent_one_id', unsigned: true })
public parentOneId!: number;
@Column({ type: 'int', name: 'parent_two_id', unsigned: true })
public parentTwoId!: number;
@ManyToOne(
() => ParentOne,
(parentOne) => parentOne.parentOneHasParentTwos,
{ onDelete: 'CASCADE', onUpdate: 'RESTRICT' },
)
@JoinColumn([{ name: 'parent_one_id', referencedColumnName: 'id' }])
public parentOne!: ParentOne;
@ManyToOne(
() => ParentTwo,
(parentTwo) => parentTwo.parentOneHasParentTwos,
{ onDelete: 'CASCADE', onUpdate: 'RESTRICT' },
)
@JoinColumn([{ name: 'parent_two_id', referencedColumnName: 'id' }])
public parentTwo!: ParentTwo;
}ParentOne and ParentTwo are the parent tables of the ParentOneHasParentTwos junction table. Each parent table has a @ManyToMany definition that targets this junction table explicitly.
We have a system our team uses that scans entity metadata by referencing an entity by table name dynamically. For example, we often fetch repository metadata TypeORM-side using connection.getRepository('parent_one').metadata and rely on that for some dynamic data fetching, saving, etc.
What I noticed when upgrading to TypeORM 0.3.x is that when I get a junction table's repository metadata, results are not the same as v2. Specifically, when fetching column property names. They output the DB column names instead of the entity column names.
The following code snippet illustrates what I'm saying a little better:
import 'reflect-metadata';
import { Entity, ManyToMany, JoinTable, ManyToOne, OneToMany, JoinColumn, DataSource, Column, PrimaryColumn } from 'typeorm';
@Entity('parent_one')
export class ParentOne {
@PrimaryColumn({ type: 'int', name: 'id', unsigned: true })
public id!: number;
@Column({ type: 'int', name: 'test_column', unsigned: true })
public testColumn!: number;
@OneToMany((type) => ParentOneHasParentTwos, (parentOneHasParentTwos) => parentOneHasParentTwos.parentOne)
public parentOneHasParentTwos!: ParentOneHasParentTwos[]
@ManyToMany((type) => ParentTwo)
@JoinTable({
name: 'parent_one_has_parent_twos',
joinColumn: {
name: 'parent_one_id',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'parent_two_id',
referencedColumnName: 'id',
},
})
public parentTwos!: ParentTwo[];
}
@Entity('parent_two')
export class ParentTwo {
@PrimaryColumn({ type: 'int', name: 'id', unsigned: true })
public id!: number;
@OneToMany((type) => ParentOneHasParentTwos, (parentOneHasParentTwos) => parentOneHasParentTwos.parentOne)
public parentOneHasParentTwos!: ParentOneHasParentTwos[]
@ManyToMany((type) => ParentOne)
@JoinTable({
name: 'parent_one_has_parent_twos',
joinColumn: {
name: 'parent_two_id',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'parent_one_id',
referencedColumnName: 'id',
},
})
public parentOnes!: ParentOne[];
}
@Entity('parent_one_has_parent_twos')
export class ParentOneHasParentTwos {
@PrimaryColumn({ type: 'int', name: 'id', unsigned: true })
public id!: number;
@Column({ type: 'int', name: 'parent_one_id', unsigned: true })
public parentOneId!: number;
@Column({ type: 'int', name: 'parent_two_id', unsigned: true })
public parentTwoId!: number;
@ManyToOne(
() => ParentOne,
(parentOne) => parentOne.parentOneHasParentTwos,
{ onDelete: 'CASCADE', onUpdate: 'RESTRICT' },
)
@JoinColumn([{ name: 'parent_one_id', referencedColumnName: 'id' }])
public parentOne!: ParentOne;
@ManyToOne(
() => ParentTwo,
(parentTwo) => parentTwo.parentOneHasParentTwos,
{ onDelete: 'CASCADE', onUpdate: 'RESTRICT' },
)
@JoinColumn([{ name: 'parent_two_id', referencedColumnName: 'id' }])
public parentTwo!: ParentTwo;
}
const run = async () => {
const con = new DataSource({
type: 'sqlite',
database: './db.sql',
entities: [
ParentOne,
ParentTwo,
ParentOneHasParentTwos,
],
synchronize: true,
dropSchema: true,
});
await con.initialize();
// This outputs the correct column metadata for propertyName
// [ 'id', 'parentOneId', 'parentTwoId' ]
console.log(con.getRepository(ParentOneHasParentTwos).metadata.columns.map((c) => c.propertyName));
// When referencing by table name, this does not output the correct column metadata for propertyName.
// This is the use case I have that needs to work. It used to work in TypeORM 0.2.x
// [ 'parent_two_id', 'parent_one_id' ]
console.log(con.getRepository('parent_one_has_parent_twos').metadata.columns.map((c) => c.propertyName));
// When referencing by table name this outputs the correct column metadata for propertyName (is not a junction table)
// [ 'id', 'testColumn' ]
console.log(con.getRepository('parent_one').metadata.columns.map((c) => c.propertyName));
};
run();In summary, expected behaviour is that:
console.log(con.getRepository('parent_one_has_parent_twos').metadata.columns.map((c) => c.propertyName));Should output:
[ 'id', 'parentOneId', 'parentTwoId' ]We rely on fetching accurate entity metadata to facilitate dynamic operations within our backend system. We layered TypeORM overtop of an existing 10+ year old database with 200+ tables, which is why we have all the manual work of tying many to many relations together within our entities.
Actual Behavior
This snippet:
console.log(con.getRepository('parent_one_has_parent_twos').metadata.columns.map((c) => c.propertyName));Outputs:
[ 'parent_two_id', 'parent_one_id' ]Which is wrong, as I am asking for column property names, and id property is also missing here.
This worked for me in TypeORM v0.2.x
Steps to reproduce
I added a test repo here: https://github.com/alexdavislws/typeorm-many-to-many-metadata-repro
npm install then npm run dev to see the outputs from src/index.ts.
My Environment
| Dependency | Version |
|---|---|
| Operating System | Ubuntu 22 |
| Node.js version | 16 |
| Typescript version | 5^ |
| TypeORM version | 0.3.17 |
Additional Context
No response
Relevant Database Driver(s)
- aurora-mysql
- aurora-postgres
- better-sqlite3
- cockroachdb
- cordova
- expo
- mongodb
- mysql
- nativescript
- oracle
- postgres
- react-native
- sap
- spanner
- sqlite
- sqlite-abstract
- sqljs
- sqlserver
Are you willing to resolve this issue by submitting a Pull Request?
No, I don’t have the time and I’m okay to wait for the community / maintainers to resolve this issue.