Skip to content

Commit

Permalink
fix: nested eager relations in a lazy-loaded entity are not loaded (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Bartmr committed Jan 31, 2022
1 parent 26815c6 commit 1cfd7b9
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 6 deletions.
24 changes: 18 additions & 6 deletions src/query-builder/RelationLoader.ts
Expand Up @@ -2,6 +2,7 @@ import {Connection} from "../connection/Connection";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {QueryRunner} from "../query-runner/QueryRunner";
import {RelationMetadata} from "../metadata/RelationMetadata";
import { FindOptionsUtils } from "..";

/**
* Wraps entities and creates getters/setters for their relations
Expand Down Expand Up @@ -77,6 +78,8 @@ export class RelationLoader {
qb.where(condition);
}

FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);

return qb.getMany();
// return qb.getOne(); todo: fix all usages
}
Expand Down Expand Up @@ -111,6 +114,9 @@ export class RelationLoader {
}).map(condition => "(" + condition + ")").join(" OR ");
qb.where(condition);
}

FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);

return qb.getMany();
// return relation.isOneToMany ? qb.getMany() : qb.getOne(); todo: fix all usages
}
Expand Down Expand Up @@ -139,13 +145,16 @@ export class RelationLoader {
return parameters;
}, {} as ObjectLiteral);

return this.connection
const qb = this.connection
.createQueryBuilder(queryRunner)
.select(mainAlias)
.from(relation.type, mainAlias)
.innerJoin(joinAlias, joinAlias, [...joinColumnConditions, ...inverseJoinColumnConditions].join(" AND "))
.setParameters(parameters)
.getMany();
.setParameters(parameters);

FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);

return qb.getMany();
}

/**
Expand All @@ -172,13 +181,16 @@ export class RelationLoader {
return parameters;
}, {} as ObjectLiteral);

return this.connection
const qb = this.connection
.createQueryBuilder(queryRunner)
.select(mainAlias)
.from(relation.type, mainAlias)
.innerJoin(joinAlias, joinAlias, [...joinColumnConditions, ...inverseJoinColumnConditions].join(" AND "))
.setParameters(parameters)
.getMany();
.setParameters(parameters);

FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);

return qb.getMany();
}

/**
Expand Down
@@ -0,0 +1,21 @@
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../../src/decorator/columns/Column";
import {ManyToMany} from "../../../../../../src/decorator/relations/ManyToMany";
import {JoinTable} from "../../../../../../src/decorator/relations/JoinTable";
import {Post} from "./Post";

@Entity()
export class Category {

@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@ManyToMany(type => Post, post => post.categories2)
@JoinTable()
posts2: Post[];

}
@@ -0,0 +1,22 @@
import { Entity } from "../../../../../../src/decorator/entity/Entity";
import { ManyToOne } from "../../../../../../src/decorator/relations/ManyToOne";
import { User } from "./User";
import { Post } from "./Post";
import {
JoinColumn,
OneToOne,
PrimaryGeneratedColumn,
} from "../../../../../../src";

@Entity()
export class Editor {
@PrimaryGeneratedColumn()
id: number;

@OneToOne((type) => User, { eager: true })
@JoinColumn()
user: User;

@ManyToOne((type) => Post, { lazy: true })
post: Promise<Post>;
}
@@ -0,0 +1,34 @@
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../../src/decorator/columns/Column";
import {ManyToMany} from "../../../../../../src/decorator/relations/ManyToMany";
import {JoinTable} from "../../../../../../src/decorator/relations/JoinTable";
import {Category} from "./Category";
import {User} from "./User";
import {ManyToOne} from "../../../../../../src/decorator/relations/ManyToOne";
import {OneToMany} from "../../../../../../src/decorator/relations/OneToMany";
import {Editor} from "./Editor";

@Entity()
export class Post {

@PrimaryGeneratedColumn()
id: number;

@Column()
title: string;

@ManyToMany(type => Category, { eager: true })
@JoinTable()
categories1: Category[];

@ManyToMany(type => Category, category => category.posts2, { eager: true })
categories2: Category[];

@ManyToOne(type => User, { eager: true })
author: User;

@OneToMany(type => Editor, editor => editor.post, { eager: true })
editors: Editor[];

}
@@ -0,0 +1,14 @@
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../../src/decorator/columns/Column";

@Entity()
export class Profile {

@PrimaryGeneratedColumn()
id: number;

@Column()
about: string;

}
@@ -0,0 +1,24 @@
import {Entity} from "../../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../../src/decorator/columns/Column";
import {OneToOne} from "../../../../../../src/decorator/relations/OneToOne";
import {JoinColumn} from "../../../../../../src/decorator/relations/JoinColumn";
import {Profile} from "./Profile";

@Entity()
export class User {

@PrimaryGeneratedColumn()
id: number;

@Column()
firstName: string;

@Column()
lastName: string;

@OneToOne(type => Profile, { eager: true })
@JoinColumn()
profile: Profile;

}
@@ -0,0 +1,123 @@
import "reflect-metadata";
import { Connection } from "../../../../../src/connection/Connection";
import {
closeTestingConnections,
createTestingConnections,
reloadTestingDatabases,
} from "../../../../utils/test-utils";
import { User } from "./entity/User";
import { Profile } from "./entity/Profile";
import { Editor } from "./entity/Editor";
import { Post } from "./entity/Post";
import { Category } from "./entity/Category";
import { expect } from "chai";

describe("relations > eager relations > lazy nested eager relations", () => {
let connections: Connection[];
before(
async () =>
(connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
}))
);
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));

async function prepareData(connection: Connection) {
const profile = new Profile();
profile.about = "I cut trees!";
await connection.manager.save(profile);

const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.profile = profile;
await connection.manager.save(user);

const primaryCategory1 = new Category();
primaryCategory1.name = "primary category #1";
await connection.manager.save(primaryCategory1);

const primaryCategory2 = new Category();
primaryCategory2.name = "primary category #2";
await connection.manager.save(primaryCategory2);

const secondaryCategory1 = new Category();
secondaryCategory1.name = "secondary category #1";
await connection.manager.save(secondaryCategory1);

const secondaryCategory2 = new Category();
secondaryCategory2.name = "secondary category #2";
await connection.manager.save(secondaryCategory2);

const post = new Post();
post.title = "about eager relations";
post.categories1 = [primaryCategory1, primaryCategory2];
post.categories2 = [secondaryCategory1, secondaryCategory2];
post.author = user;
await connection.manager.save(post);

const editor = new Editor();
editor.post = Promise.resolve(post);
editor.user = user;
await connection.manager.save(editor);
}

it("should load all eager relations nested inside a lazy relation", () =>
Promise.all(
connections.map(async (connection) => {
await prepareData(connection);

const loadedEditor = await connection.manager.findOne(Editor, 1);

const loadedPost = await loadedEditor?.post;

expect(loadedPost?.categories1).to.have.deep.members([
{
id: 1,
name: "primary category #1",
},
{
id: 2,
name: "primary category #2",
},
]);

expect(loadedPost?.categories2).to.have.deep.members([
{
id: 3,
name: "secondary category #1",
},
{
id: 4,
name: "secondary category #2",
},
]);

expect(loadedPost?.author).to.deep.equal({
id: 1,
firstName: "Timber",
lastName: "Saw",
profile: {
id: 1,
about: "I cut trees!",
},
});

expect(loadedPost?.editors).to.have.deep.members([
{
id: 1,
user: {
id: 1,
firstName: "Timber",
lastName: "Saw",
profile: {
id: 1,
about: "I cut trees!",
},
},
},
]);
})
));
});

0 comments on commit 1cfd7b9

Please sign in to comment.