Skip to content

Commit

Permalink
feat(core): fix populating relations with cycles via select-in strategy
Browse files Browse the repository at this point in the history
Closes #4899
  • Loading branch information
B4nan committed Nov 6, 2023
1 parent 1ebfbdd commit d0b35da
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 4 deletions.
7 changes: 3 additions & 4 deletions packages/core/src/entity/EntityLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ export class EntityLoader {
throw ValidationError.invalidPropertyName(entityName, invalid.field);
}

entities = entities.filter(e => !visited.has(e));
entities.forEach(e => visited.add(e));
entities.forEach(e => helper(e).__serializationContext.populate ??= populate as PopulateOptions<T>[]);

for (const pop of populate) {
entities.forEach(e => visited.add(e));
await this.populateField<T>(entityName, entities, pop, options as Required<EntityLoaderOptions<T>>);
entities.forEach(e => visited.delete(e));
}
}

Expand Down Expand Up @@ -338,8 +338,6 @@ export class EntityLoader {
}
}

const filtered = Utils.unique(children);

if (populated.length === 0 && !populate.children) {
return;
}
Expand All @@ -349,6 +347,7 @@ export class EntityLoader {
.filter(orderBy => Utils.isObject(orderBy[prop.name as string]))
.map(orderBy => orderBy[prop.name as string]);
const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType } = options;
const filtered = Utils.unique(children.filter(e => !(options as Dictionary).visited.has(e)));

await this.populate<T>(prop.type, filtered, populate.children ?? populate.all!, {
where: await this.extractChildCondition(options, prop, false) as FilterQuery<T>,
Expand Down
95 changes: 95 additions & 0 deletions tests/issues/GH4899.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property, Unique } from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/sqlite';

@Entity()
class User {

@PrimaryKey()
id!: number;

@OneToMany({ entity: () => UserSkill, mappedBy: 'user' })
userSkills = new Collection<UserSkill>(this);

}

@Entity()
class Skill {

@PrimaryKey()
id!: number;

@Property()
label!: string;

@OneToMany(() => UserSkill, 'skill')
userSkills = new Collection<UserSkill>(this);

}

@Entity()
@Unique({ properties: ['user', 'skill'] })
class UserSkill {

@PrimaryKey()
id!: number;

@ManyToOne(() => User)
user!: User;

@ManyToOne(() => Skill)
skill!: Skill;

}

let orm: MikroORM;

beforeAll(async () => {
orm = await MikroORM.init({
dbName: ':memory:',
entities: [UserSkill, User, Skill],
// debug: true,
});
await orm.schema.createSchema();
});

afterAll(() => orm.close(true));

test('4899', async () => {
orm.em.create(User, {
id: 1,
});
orm.em.create(Skill, {
id: 10,
label: 'JS',
});
orm.em.create(Skill, {
id: 11,
label: 'TS',
});
orm.em.create(UserSkill, {
id: 20,
skill: 10,
user: 1,
});
orm.em.create(UserSkill, {
id: 21,
skill: 11,
user: 1,
});

await orm.em.flush();
orm.em.clear();

const userSkill = await orm.em.findOneOrFail(
UserSkill,
{ skill: [10] },
{
populate: ['user.userSkills.skill', 'user.userSkills.user'],
},
);

expect(userSkill.id).toBe(20);
expect(userSkill.user.userSkills).toHaveLength(2);
expect(userSkill.user.userSkills[0].skill).toHaveProperty('label', 'JS');
expect(userSkill.user.userSkills[1].skill).toHaveProperty('label', 'TS');
});

0 comments on commit d0b35da

Please sign in to comment.