Skip to content

Commit

Permalink
fix(core): fix eager loading detection with multiple populate hints f…
Browse files Browse the repository at this point in the history
…or one property

Closes #5057
  • Loading branch information
B4nan committed Dec 30, 2023
1 parent 222e2b8 commit da1daf5
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 5 deletions.
11 changes: 6 additions & 5 deletions packages/core/src/entity/EntityLoader.ts
Expand Up @@ -65,9 +65,10 @@ export class EntityLoader {
throw ValidationError.invalidPropertyName(entityName, invalid.field);
}

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

for (const pop of populate) {
Expand Down Expand Up @@ -578,8 +579,8 @@ export class EntityLoader {
.forEach(prop => {
const field = this.getRelationName(meta, prop);
const prefixed = prefix ? `${prefix}.${field}` : field;
const nestedPopulate = populate.find(p => p.field === prop.name)?.children ?? [];
const nested = this.lookupEagerLoadedRelationships(prop.type, nestedPopulate, strategy, prefixed, visited.slice());
const nestedPopulate = populate.filter(p => p.field === prop.name).flatMap(p => p.children).filter(Boolean);
const nested = this.lookupEagerLoadedRelationships(prop.type, nestedPopulate as any, strategy, prefixed, visited.slice());

if (nested.length > 0) {
ret.push(...nested);
Expand Down
180 changes: 180 additions & 0 deletions tests/issues/GH5057.test.ts
@@ -0,0 +1,180 @@
import {
BaseEntity,
Collection,
Entity,
ManyToOne,
OneToMany,
PrimaryKey,
Property,
Cascade,
} from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/sqlite';
import { v4 as uuidv4 } from 'uuid';

@Entity()
class LocalizedString extends BaseEntity<LocalizedString, 'id'> {

@PrimaryKey()
id!: string;

@Property()
de_DE: string;

@Property()
en_US: string;

constructor(de: string, en: string) {
super();
this.de_DE = de;
this.en_US = en;
}

}

enum BookGenre {
crime = 'crime',
fantasy = 'fantasy',
horror = 'horror',
romance = 'romance',
}

@Entity()
class Genre {

@PrimaryKey()
id!: string;

@Property()
type: BookGenre;

@ManyToOne({ cascade: [Cascade.ALL], eager: true, nullable: false })
title: LocalizedString;

constructor(type: BookGenre, title: LocalizedString) {
this.type = type;
this.title = title;
}

}

@Entity()
class Author {

@PrimaryKey()
id!: string;

@Property()
firstName: string;

@Property()
lastName: string;

@OneToMany(() => Book, book => book.author)
books = new Collection<Book>(this);

constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}

}

@Entity()
class Book {

@PrimaryKey()
id!: string;

@Property()
name: string;

@ManyToOne()
author: Author;

@ManyToOne()
genre: Genre;

constructor(name: string, author: Author, genre: Genre) {
this.name = name;
this.author = author;
this.genre = genre;
}

}

let orm: MikroORM;

beforeAll(async () => {
orm = await MikroORM.init({
dbName: ':memory:',
entities: [Author, Genre, LocalizedString],
});
await orm.schema.refreshDatabase();
});

afterAll(async () => {
await orm.close(true);
});

describe('basic CRUD example', () => {
beforeAll(async () => {
const string1 = orm.em.create(LocalizedString, {
id: uuidv4(),
de_DE: 'Krimi',
en_US: 'Crime',
});
const string2 = orm.em.create(LocalizedString, {
id: uuidv4(),
de_DE: 'Fantasy',
en_US: 'Fantasy',
});
const genreCrime = orm.em.create(Genre, {
id: uuidv4(),
type: BookGenre.crime,
title: string1,
});
const genreFantasy = orm.em.create(Genre, {
id: uuidv4(),
type: BookGenre.fantasy,
title: string2,
});
const author = orm.em.create(Author, {
id: uuidv4(),
firstName: 'Jon',
lastName: 'Snow',
});
await orm.em.flush();
orm.em.create(Book, {
id: uuidv4(),
name: 'The Girl with the Dragon Tattoo',
author,
genre: genreCrime,
});
orm.em.create(Book, {
id: uuidv4(),
name: 'Game of Thrones',
author,
genre: genreFantasy,
});
await orm.em.flush();
orm.em.clear();
});

test('populate books with genre', async () => {
const authors = await orm.em.find(
Author,
{ firstName: 'Jon' },
{ populate: ['books', 'books.genre'] },
);
expect(authors).toHaveLength(1);
const books = authors[0].books;
expect(books).toHaveLength(2);
expect(books.map(book => book.genre.type).sort()).toEqual(
[BookGenre.crime, BookGenre.fantasy].sort(),
);
// Should load title when populating books.genre according to eager: true
expect(books.map(book => book.genre.title.en_US).sort()).toEqual(
['Crime', 'Fantasy'].sort(),
);
});
});

0 comments on commit da1daf5

Please sign in to comment.