Skip to content

Commit

Permalink
fix: return 'null' (instead of 'undefined') on lazy relations that ha…
Browse files Browse the repository at this point in the history
…ve no results (#7146) (#7147)

Fix an issue where a lazy relation returns a Promise that resolves to 'undefined'. As implied by
documentation, if the database has a NULL or no results, the result should be a literal 'null'
in Javascript. Also added units tests to hilight the three scenarios in which this occurred.

Closes #7146
  • Loading branch information
securedirective committed Jan 11, 2021
1 parent cc044a9 commit 9b278c9
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/query-builder/RelationLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export class RelationLoader {

// nothing is loaded yet, load relation data and save it in the model once they are loaded
const loader = relationLoader.load(relation, this, queryRunner).then(
result => relation.isOneToOne || relation.isManyToOne ? result[0] : result
result => relation.isOneToOne || relation.isManyToOne ? (result.length === 0 ? null : result[0]) : result
);
return setPromise(this, loader);
},
Expand Down
18 changes: 18 additions & 0 deletions test/github-issues/7146/entity/Category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from "../../../../src";
import { Post } from "./Post";

@Entity()
export class Category {

@PrimaryGeneratedColumn()
id: number;

@OneToOne(type => Post, post => post.lazyOneToOne, { nullable: true, eager: false })
@JoinColumn()
backRef1: Post;

@OneToOne(type => Post, post => post.eagerOneToOne, { nullable: true, eager: false })
@JoinColumn()
backRef2: Post;

}
32 changes: 32 additions & 0 deletions test/github-issues/7146/entity/Post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn } from "../../../../src";
import { Category } from "./Category";

@Entity()
export class Post {

@PrimaryGeneratedColumn()
id: number;

@ManyToOne(type => Category, { nullable: true, eager: false })
lazyManyToOne: Promise<Category | null>;

@ManyToOne(type => Category, { nullable: true, eager: true })
eagerManyToOne: Category | null;

@OneToOne(type => Category, { nullable: true, eager: false })
@JoinColumn()
lazyOneToOneOwner: Promise<Category | null>;

@OneToOne(type => Category, { nullable: true, eager: true })
@JoinColumn()
eagerOneToOneOwner: Category | null;

// Not a column; actual value is stored on the other side of this relation
@OneToOne(type => Category, category => category.backRef1, { eager: false })
lazyOneToOne: Promise<Category | null>;

// Not a column; actual value is stored on the other side of this relation
@OneToOne(type => Category, category => category.backRef2, { eager: true })
eagerOneToOne: Category | null;

}
92 changes: 92 additions & 0 deletions test/github-issues/7146/issue-7146.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import "reflect-metadata";
import { createTestingConnections, closeTestingConnections, reloadTestingDatabases } from "../../utils/test-utils";
import { Connection } from "../../../src/connection/Connection";
import { expect } from "chai";
import { Post } from "./entity/Post";

describe("github issues > #7146 Lazy relations resolve to 'undefined' instead of 'null'", () => {

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

async function prepareData(connection: Connection) {
const savedPost = new Post();
await connection.manager.save(savedPost);
}

// The following 3 tests hilight the reported issue.
// The remaining 6 tests were already succeeding before, but are included for completeness sake.

describe("lazy-loaded relations", () => {

it("should return null if ManyToOne relation has NULL in database", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
const post = (await connection.manager.findOneOrFail(Post, 1));
expect(await post.lazyManyToOne).to.be.null;
})));

it("should return null if OneToOne+JoinColumn relation has NULL in database", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
const post = (await connection.manager.findOneOrFail(Post, 1));
expect(await post.lazyOneToOneOwner).to.be.null;
})));

it("should return null if OneToOne relation has NULL in database", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
const post = (await connection.manager.findOneOrFail(Post, 1));
expect(await post.lazyOneToOne).to.be.null;
})));

});

describe("lazy-loaded relations included in 'relations' find option", () => {

it("should return null if ManyToOne relation has NULL in database", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
const post = (await connection.manager.findOneOrFail(Post, 1, { relations: ['lazyManyToOne'] }));
expect(await post.lazyManyToOne).to.be.null;
})));

it("should return null if OneToOne+JoinColumn relation has NULL in database", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
const post = (await connection.manager.findOneOrFail(Post, 1, { relations: ['lazyOneToOneOwner'] }));
expect(await post.lazyOneToOneOwner).to.be.null;
})));

it("should return null if OneToOne relation has NULL in database", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
const post = (await connection.manager.findOneOrFail(Post, 1, { relations: ['lazyOneToOne'] }));
expect(await post.lazyOneToOne).to.be.null;
})));

});

describe("eager-loaded relations", () => {

it("should return null if ManyToOne relation has NULL in database", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
const post = (await connection.manager.findOneOrFail(Post, 1));
expect(post.eagerManyToOne).to.be.null;
})));

it("should return null if OneToOne+JoinColumn relation has NULL in database", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
const post = (await connection.manager.findOneOrFail(Post, 1));
expect(post.eagerOneToOneOwner).to.be.null;
})));

it("should return null if OneToOne relation has NULL in database", () => Promise.all(connections.map(async connection => {
await prepareData(connection);
const post = (await connection.manager.findOneOrFail(Post, 1));
expect(post.eagerOneToOne).to.be.null;
})));

});

});

0 comments on commit 9b278c9

Please sign in to comment.