Skip to content

Commit

Permalink
fix(core): fix infinite loop with populate: true and refresh: true (
Browse files Browse the repository at this point in the history
  • Loading branch information
B4nan committed Dec 15, 2023
1 parent 5bc19ba commit 9f63378
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 5 deletions.
14 changes: 9 additions & 5 deletions packages/core/src/entity/EntityLoader.ts
Expand Up @@ -65,12 +65,13 @@ export class EntityLoader {
throw ValidationError.invalidPropertyName(entityName, invalid.field);
}

entities.forEach(e => helper(e).__serializationContext.populate ??= populate as PopulateOptions<T>[]);
for (const e of entities) {
helper(e).__serializationContext.populate ??= populate as PopulateOptions<T>[];
visited.add(e);
}

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 @@ -294,11 +295,13 @@ export class EntityLoader {
const { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType } = options;

return this.em.find(prop.type, where, {
refresh, filters, convertCustomTypes, lockMode, populateWhere,
filters, convertCustomTypes, lockMode, populateWhere,
orderBy: [...Utils.asArray(options.orderBy), ...Utils.asArray(prop.orderBy), { [fk]: QueryOrder.ASC }] as QueryOrderMap<T>[],
populate: populate.children as never ?? populate.all ?? [],
strategy, fields, schema, connectionType,
// @ts-ignore not a public option, will be propagated to the populate call
refresh: refresh && !children.every(item => options.visited.has(item)),
// @ts-ignore not a public option, will be propagated to the populate call
visited: options.visited,
});
}
Expand Down Expand Up @@ -355,12 +358,13 @@ export class EntityLoader {
fields,
validate: false,
lookup: false,
refresh,
filters,
ignoreLazyScalarProperties,
populateWhere,
connectionType,
// @ts-ignore not a public option, will be propagated to the populate call
refresh: refresh && !filtered.every(item => options.visited.has(item)),
// @ts-ignore not a public option, will be propagated to the populate call
visited: options.visited,
});
}
Expand Down
24 changes: 24 additions & 0 deletions tests/features/partial-loading/partial-loading.mysql.test.ts
Expand Up @@ -291,4 +291,28 @@ describe('partial loading (mysql)', () => {
'left join `author2` as `a3` on `b1`.`author_id` = `a3`.`id`');
});

test('populate partial', async () => {
const god = await createEntities();

const r1 = await orm.em.findOneOrFail(BookTag2, '1', {
fields: ['name', 'books.title', 'books.author.email'],
});
expect(r1.name).toBe('t1');
expect(r1.books[0].title).toBe('Bible 1');
expect(r1.books[0].price).toBeUndefined();
expect(r1.books[0].author).toBeDefined();
expect(r1.books[0].author.id).toBeDefined();
expect(r1.books[0].author.name).toBeUndefined();
expect(r1.books[0].author.email).toBe(god.email);

const [r2] = await orm.em.populate(r1, true, { refresh: true });
expect(r2.name).toBe('t1');
expect(r2.books[0].title).toBe('Bible 1');
expect(r2.books[0].price).toBe('123.00');
expect(r2.books[0].author).toBeDefined();
expect(r2.books[0].author.id).toBeDefined();
expect(r2.books[0].author.name).toBe(god.name);
expect(r2.books[0].author.email).toBe(god.email);
});

});

0 comments on commit 9f63378

Please sign in to comment.