Skip to content

Commit

Permalink
fix(core): prefer custom pivot entity for inference of FK names
Browse files Browse the repository at this point in the history
Closes #3626
  • Loading branch information
B4nan committed Nov 7, 2022
1 parent 1354304 commit 08a7dc2
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 3 deletions.
14 changes: 11 additions & 3 deletions packages/core/src/metadata/MetadataDiscovery.ts
Expand Up @@ -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()) {
Expand Down Expand Up @@ -491,16 +496,19 @@ 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)
if (owner.type === first.type && first.type !== second.type) {
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<EntityMetadata> {
Expand Down
@@ -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;
"
`;
86 changes: 86 additions & 0 deletions 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<Task>(this);

}

@Entity()
class Task {

@PrimaryKey()
id!: number;

@ManyToMany(() => Creator, c => c.tasks)
creators = new Collection<Creator>(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();
});

0 comments on commit 08a7dc2

Please sign in to comment.