diff --git a/packages/core/src/metadata/MetadataDiscovery.ts b/packages/core/src/metadata/MetadataDiscovery.ts index 4dc92a8a05cc..0002c22c856b 100644 --- a/packages/core/src/metadata/MetadataDiscovery.ts +++ b/packages/core/src/metadata/MetadataDiscovery.ts @@ -376,10 +376,15 @@ export class MetadataDiscovery { const meta2 = this.metadata.get(prop.type); Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn); const pivotMeta = this.metadata.find(prop.pivotEntity); + const pks = Object.values(pivotMeta?.properties ?? {}).filter(p => p.primary); - if (pivotMeta) { + if (pivotMeta && pks.length === 2) { + const owner = prop.mappedBy ? meta2.properties[prop.mappedBy] : prop; + const [first, second] = this.ensureCorrectFKOrderInPivotEntity(pivotMeta, owner); pivotMeta.pivotTable = true; prop.pivotTable = pivotMeta.tableName; + prop.joinColumns = first.fieldNames; + prop.inverseJoinColumns = second.fieldNames; } if (!prop.pivotTable && prop.owner && this.platform.usesPivotTable()) { @@ -491,8 +496,8 @@ export class MetadataDiscovery { }); } - private ensureCorrectFKOrderInPivotEntity(meta: EntityMetadata, owner: EntityProperty): void { - const [first, second] = meta.relations; + private ensureCorrectFKOrderInPivotEntity(meta: EntityMetadata, owner: EntityProperty): [EntityProperty, EntityProperty] { + let [first, second] = Object.values(meta.properties).filter(p => p.primary); // wrong FK order, first FK needs to point to the owning side // (note that we can detect this only if the FKs target different types) @@ -500,7 +505,10 @@ export class MetadataDiscovery { delete meta.properties[first.name]; meta.removeProperty(first.name, false); meta.addProperty(first); + [first, second] = [second, first]; } + + return [first, second]; } private async definePivotTableEntity(meta: EntityMetadata, prop: EntityProperty): Promise { diff --git a/tests/features/composite-keys/__snapshots__/custom-pivot-entity-with-sti.test.ts.snap b/tests/features/composite-keys/__snapshots__/custom-pivot-entity-with-sti.test.ts.snap new file mode 100644 index 000000000000..b41e5a75e2f7 --- /dev/null +++ b/tests/features/composite-keys/__snapshots__/custom-pivot-entity-with-sti.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`schema 1`] = ` +"pragma foreign_keys = off; + +create table \`task\` (\`id\` integer not null primary key autoincrement); + +create table \`user\` (\`id\` integer not null primary key autoincrement, \`role\` text not null default 'CREATOR'); +create index \`user_role_index\` on \`user\` (\`role\`); + +create table \`creators_on_tasks\` (\`creator_id\` integer not null, \`task_id\` integer not null, constraint \`creators_on_tasks_creator_id_foreign\` foreign key(\`creator_id\`) references \`user\`(\`id\`) on update cascade, constraint \`creators_on_tasks_task_id_foreign\` foreign key(\`task_id\`) references \`task\`(\`id\`) on update cascade, primary key (\`creator_id\`, \`task_id\`)); +create index \`creators_on_tasks_creator_id_index\` on \`creators_on_tasks\` (\`creator_id\`); +create index \`creators_on_tasks_task_id_index\` on \`creators_on_tasks\` (\`task_id\`); + +pragma foreign_keys = on; +" +`; diff --git a/tests/features/composite-keys/custom-pivot-entity-with-sti.test.ts b/tests/features/composite-keys/custom-pivot-entity-with-sti.test.ts new file mode 100644 index 000000000000..72e2996aacf8 --- /dev/null +++ b/tests/features/composite-keys/custom-pivot-entity-with-sti.test.ts @@ -0,0 +1,86 @@ +import { Collection, Entity, ManyToMany, ManyToOne, MikroORM, PrimaryKey, Property } from '@mikro-orm/core'; + +@Entity({ + discriminatorColumn: 'role', + discriminatorMap: { + CREATOR: 'Creator', + }, +}) +class User { + + @PrimaryKey() + id!: number; + + @Property() + role: 'CREATOR' = 'CREATOR' as const; + +} + +@Entity() +class Creator extends User { + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + @ManyToMany({ entity: () => Task, pivotEntity: () => CreatorsOnTasks }) + tasks = new Collection(this); + +} + +@Entity() +class Task { + + @PrimaryKey() + id!: number; + + @ManyToMany(() => Creator, c => c.tasks) + creators = new Collection(this); + +} + +@Entity() +class CreatorsOnTasks { + + @ManyToOne({ primary: true, entity: () => Creator }) + creator!: Creator; + + @ManyToOne({ primary: true, entity: () => Task }) + task!: Task; + +} + +let orm: MikroORM; + +beforeAll(async () => { + orm = await MikroORM.init({ + entities: [User, Creator, CreatorsOnTasks, Task], + dbName: ':memory:', + type: 'sqlite', + }); + await orm.schema.createSchema(); +}); + +afterAll(async () => await orm.close(true)); + +beforeEach(() => orm.schema.clearDatabase()); + +test('schema', async () => { + const sql = await orm.schema.getCreateSchemaSQL(); + expect(sql).toMatchSnapshot(); +}); + +async function createEntities() { + const task = new Task(); + const creator = new Creator(); + task.creators.add(creator); + await orm.em.fork().persistAndFlush(task); + + return { task }; +} + +test('should insert', async () => { + await expect(createEntities()).resolves.not.toThrow(); +}); + +test('should not findOne and populate m:n relation', async () => { + const { task } = await createEntities(); + await expect(orm.em.findOne(Task, { id: task.id }, { populate: ['creators'] })).resolves.not.toThrow(); +});