mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
implemented multiple primary columns in LazyRelationsWrapper
This commit is contained in:
parent
807c819322
commit
c9280ed21c
@ -1,6 +1,7 @@
|
||||
import {RelationMetadata} from "../metadata/RelationMetadata";
|
||||
import {QueryBuilder} from "../query-builder/QueryBuilder";
|
||||
import {Connection} from "../connection/Connection";
|
||||
import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
|
||||
/**
|
||||
* This class wraps entities and provides functions there to lazily load its relations.
|
||||
@ -31,88 +32,26 @@ export class LazyRelationsWrapper {
|
||||
if (this[promiseIndex])
|
||||
return this[promiseIndex];
|
||||
const qb = new QueryBuilder(connection);
|
||||
if (relation.isManyToMany) {
|
||||
|
||||
/*if (relation.isManyToManyOwner) {
|
||||
if (relation.isManyToOne || relation.isOneToOneOwner) {
|
||||
|
||||
const mainAlias = relation.propertyName;
|
||||
const joinAlias = relation.junctionEntityMetadata.table.name;
|
||||
const joinColumnConditions = relation.joinTable.joinColumns.map(joinColumn => {
|
||||
return `${joinAlias}.${joinColumn.name} = :${joinColumn.name}`;
|
||||
});
|
||||
const inverseJoinColumnConditions = relation.joinTable.inverseJoinColumns.map(joinColumn => {
|
||||
return `${joinAlias}.${joinColumn.name} = :${joinColumn.name}`;
|
||||
});
|
||||
const parameters = relation.joinTable.joinColumns.reduce((parameters, joinColumn) => {
|
||||
parameters[joinColumn.name] = this[joinColumn.referencedColumn.propertyName];
|
||||
return parameters;
|
||||
}, {} as ObjectLiteral);
|
||||
const joinColumns = relation.isOwning ? relation.joinColumns : relation.inverseRelation.joinColumns;
|
||||
const conditions = joinColumns.map(joinColumn => {
|
||||
return `${relation.entityMetadata.name}.${relation.propertyName} = ${relation.propertyName}.${joinColumn.referencedColumn.propertyName}`;
|
||||
}).join(" AND ");
|
||||
|
||||
const conditions = joinColumnConditions.concat(inverseJoinColumnConditions).join(" AND ");
|
||||
// (ow) post.category<=>category.post
|
||||
// loaded: category from post
|
||||
// example: SELECT category.id AS category_id, category.name AS category_name FROM category category
|
||||
// INNER JOIN post Post ON Post.category=category.id WHERE Post.id=1
|
||||
qb.select(relation.propertyName) // category
|
||||
.from(relation.type, relation.propertyName) // Category, category
|
||||
.innerJoin(relation.entityMetadata.target as Function, relation.entityMetadata.name, conditions);
|
||||
|
||||
qb.select(mainAlias)
|
||||
.from(relation.type, mainAlias)
|
||||
.innerJoin(relation.junctionEntityMetadata.table.name, joinAlias,
|
||||
`${relation.junctionEntityMetadata.table.name}.${relation.joinTable.joinColumnName}=:${mainAlias}Id AND ` +
|
||||
`${relation.junctionEntityMetadata.table.name}.${relation.joinTable.inverseJoinColumnName}=${mainAlias}.${relation.joinTable.referencedColumn.propertyName}`)
|
||||
.setParameter(mainAlias + "Id", this[relation.joinTable.referencedColumn.propertyName]);
|
||||
|
||||
} else { // non-owner
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.type, relation.propertyName)
|
||||
.innerJoin(relation.junctionEntityMetadata.table.name, relation.junctionEntityMetadata.table.name,
|
||||
`${relation.junctionEntityMetadata.table.name}.${relation.inverseRelation.joinTable.inverseJoinColumnName}=:${relation.propertyName}Id AND ` +
|
||||
`${relation.junctionEntityMetadata.table.name}.${relation.inverseRelation.joinTable.joinColumnName}=${relation.propertyName}.${relation.inverseRelation.joinTable.referencedColumn.propertyName}`)
|
||||
.setParameter(relation.propertyName + "Id", this[relation.inverseRelation.joinTable.referencedColumn.propertyName]);
|
||||
}*/
|
||||
|
||||
this[promiseIndex] = qb.getMany().then(results => {
|
||||
this[index] = results;
|
||||
this[resolveIndex] = true;
|
||||
delete this[promiseIndex];
|
||||
return this[index];
|
||||
}).catch(err => {
|
||||
throw err;
|
||||
relation.joinColumns.forEach(joinColumn => {
|
||||
qb.andWhere(`${relation.entityMetadata.name}.${joinColumn.referencedColumn.fullName} = :${joinColumn.referencedColumn.fullName}`)
|
||||
.setParameter(`${joinColumn.referencedColumn.fullName}`, this[joinColumn.referencedColumn.fullName])
|
||||
});
|
||||
return this[promiseIndex];
|
||||
|
||||
} else if (relation.isOneToMany) {
|
||||
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.inverseRelation.entityMetadata.target, relation.propertyName)
|
||||
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName)
|
||||
.where(relation.entityMetadata.targetName + "." + relation.inverseEntityMetadata.firstPrimaryColumn.propertyName + "=:id", { id: relation.entityMetadata.getEntityIdMixedMap(this) });
|
||||
|
||||
this[promiseIndex] = qb.getMany().then(results => {
|
||||
this[index] = results;
|
||||
this[resolveIndex] = true;
|
||||
delete this[promiseIndex];
|
||||
return this[index];
|
||||
|
||||
}).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
return this[promiseIndex];
|
||||
|
||||
} else { // todo: fix issues with joinColumn[0]
|
||||
|
||||
if (relation.hasInverseSide) {
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.inverseRelation.entityMetadata.target, relation.propertyName)
|
||||
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName)
|
||||
.where(relation.entityMetadata.targetName + "." + relation.joinColumns[0].referencedColumn.fullName + "=:id", { id: relation.entityMetadata.getEntityIdMixedMap(this) }); // is referenced column usage is correct here?
|
||||
|
||||
} else {
|
||||
// (ow) post.category<=>category.post
|
||||
// loaded: category from post
|
||||
// example: SELECT category.id AS category_id, category.name AS category_name FROM category category
|
||||
// INNER JOIN post Post ON Post.category=category.id WHERE Post.id=1
|
||||
qb.select(relation.propertyName) // category
|
||||
.from(relation.type, relation.propertyName) // Category, category
|
||||
.innerJoin(relation.entityMetadata.target as Function, relation.entityMetadata.name,
|
||||
`${relation.entityMetadata.name}.${relation.propertyName}=${relation.propertyName}.${relation.isOwning ? relation.joinColumns[0].referencedColumn.propertyName : relation.inverseRelation.joinColumns[0].referencedColumn.propertyName }`)
|
||||
.where(relation.entityMetadata.name + "." + relation.joinColumns[0].referencedColumn.fullName + "=:id", { id: relation.entityMetadata.getEntityIdMixedMap(this) }); // is referenced column usage is correct here?
|
||||
}
|
||||
|
||||
this[promiseIndex] = qb.getOne().then(result => {
|
||||
this[index] = result;
|
||||
@ -124,6 +63,98 @@ export class LazyRelationsWrapper {
|
||||
throw err;
|
||||
});
|
||||
return this[promiseIndex];
|
||||
|
||||
} else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
|
||||
|
||||
/*
|
||||
SELECT post
|
||||
FROM post post
|
||||
WHERE post.[joinColumn.name] = this[joinColumn.referencedColumn]
|
||||
*/
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.inverseRelation.entityMetadata.target, relation.propertyName);
|
||||
|
||||
relation.inverseRelation.joinColumns.forEach(joinColumn => {
|
||||
qb.andWhere(`${relation.propertyName}.${joinColumn.name} = :${joinColumn.referencedColumn.fullName}`)
|
||||
.setParameter(`${joinColumn.referencedColumn.fullName}`, this[joinColumn.referencedColumn.fullName])
|
||||
});
|
||||
|
||||
const result = relation.isOneToMany ? qb.getMany() : qb.getOne();
|
||||
this[promiseIndex] = result.then(results => {
|
||||
this[index] = results;
|
||||
this[resolveIndex] = true;
|
||||
delete this[promiseIndex];
|
||||
return this[index];
|
||||
|
||||
}).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
return this[promiseIndex];
|
||||
|
||||
} else { // ManyToMany
|
||||
|
||||
const mainAlias = relation.propertyName;
|
||||
const joinAlias = relation.junctionEntityMetadata.table.name;
|
||||
let joinColumnConditions: string[] = [];
|
||||
let inverseJoinColumnConditions: string[] = [];
|
||||
let parameters: ObjectLiteral;
|
||||
|
||||
if (relation.isOwning) {
|
||||
/*
|
||||
SELECT category
|
||||
FROM category category
|
||||
INNER JOIN post_categories post_categories
|
||||
ON post_categories.postId = :postId
|
||||
AND post_categories.categoryId = category.id
|
||||
*/
|
||||
|
||||
joinColumnConditions = relation.joinTable.joinColumns.map(joinColumn => {
|
||||
return `${joinAlias}.${joinColumn.name} = :${joinColumn.name}`;
|
||||
});
|
||||
inverseJoinColumnConditions = relation.joinTable.inverseJoinColumns.map(inverseJoinColumn => {
|
||||
return `${joinAlias}.${inverseJoinColumn.name}=${mainAlias}.${inverseJoinColumn.referencedColumn.fullName}`;
|
||||
});
|
||||
parameters = relation.joinTable.joinColumns.reduce((parameters, joinColumn) => {
|
||||
parameters[joinColumn.name] = this[joinColumn.referencedColumn.propertyName];
|
||||
return parameters;
|
||||
}, {} as ObjectLiteral);
|
||||
|
||||
} else {
|
||||
/*
|
||||
SELECT post
|
||||
FROM post post
|
||||
INNER JOIN post_categories post_categories
|
||||
ON post_categories.postId = post.id
|
||||
AND post_categories.categoryId = post_categories.categoryId
|
||||
*/
|
||||
|
||||
joinColumnConditions = relation.inverseRelation.joinTable.joinColumns.map(joinColumn => {
|
||||
return `${joinAlias}.${joinColumn.name} = ${mainAlias}.${joinColumn.referencedColumn.fullName}`;
|
||||
});
|
||||
inverseJoinColumnConditions = relation.inverseRelation.joinTable.inverseJoinColumns.map(inverseJoinColumn => {
|
||||
return `${joinAlias}.${inverseJoinColumn.name} = :${inverseJoinColumn.name}`;
|
||||
});
|
||||
parameters = relation.inverseRelation.joinTable.inverseJoinColumns.reduce((parameters, joinColumn) => {
|
||||
parameters[joinColumn.name] = this[joinColumn.referencedColumn.propertyName];
|
||||
return parameters;
|
||||
}, {} as ObjectLiteral);
|
||||
}
|
||||
|
||||
const conditions = joinColumnConditions.concat(inverseJoinColumnConditions).join(" AND ");
|
||||
qb.select(mainAlias)
|
||||
.from(relation.type, mainAlias)
|
||||
.innerJoin(joinAlias, joinAlias, conditions)
|
||||
.setParameters(parameters);
|
||||
|
||||
this[promiseIndex] = qb.getMany().then(results => {
|
||||
this[index] = results;
|
||||
this[resolveIndex] = true;
|
||||
delete this[promiseIndex];
|
||||
return this[index];
|
||||
}).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
return this[promiseIndex];
|
||||
}
|
||||
},
|
||||
set: function(promise: Promise<any>) {
|
||||
|
||||
@ -578,7 +578,7 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
const conditions = joinColumnConditions.concat(inverseJoinColumnConditions).join(" AND ");
|
||||
|
||||
// (example) returns us referenced column (detail's id)
|
||||
const parameters = relation.joinTable.joinColumns.reduce((parameters, joinColumn) => {
|
||||
const parameters = relation.inverseRelation.joinTable.inverseJoinColumns.reduce((parameters, joinColumn) => {
|
||||
parameters[joinColumn.name] = subject.databaseEntity[joinColumn.referencedColumn.propertyName];
|
||||
return parameters;
|
||||
}, {} as ObjectLiteral);
|
||||
@ -592,7 +592,7 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
.getMany();
|
||||
|
||||
} else { // this case can only be a oneToMany relation
|
||||
|
||||
// todo: fix issues with joinColumn[0]
|
||||
// (example) returns us referenced column (detail's id)
|
||||
const relationIdInDatabaseEntity = subject.databaseEntity[relation.inverseRelation.joinColumns[0].referencedColumn.propertyName];
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import {Entity} from "../../../../src/decorator/entity/Entity";
|
||||
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
|
||||
import {Column} from "../../../../src/decorator/columns/Column";
|
||||
import {Post} from "./Post";
|
||||
import {ManyToMany} from "../../../../src/decorator/relations/ManyToMany";
|
||||
import {OneToMany} from "../../../../src/decorator/relations/OneToMany";
|
||||
import {OneToOne} from "../../../../src/decorator/relations/OneToOne";
|
||||
import {Post} from "./Post";
|
||||
|
||||
@Entity()
|
||||
export class Category {
|
||||
@ -14,6 +15,9 @@ export class Category {
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@OneToOne(type => Post, post => post.oneCategory)
|
||||
onePost: Promise<Post>;
|
||||
|
||||
@ManyToMany(type => Post, post => post.twoSideCategories)
|
||||
twoSidePosts: Promise<Post[]>;
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@ import {Category} from "./Category";
|
||||
import {ManyToMany} from "../../../../src/decorator/relations/ManyToMany";
|
||||
import {JoinTable} from "../../../../src/decorator/relations/JoinTable";
|
||||
import {ManyToOne} from "../../../../src/decorator/relations/ManyToOne";
|
||||
import {OneToOne} from "../../../../src/decorator/relations/OneToOne";
|
||||
import {JoinColumn} from "../../../../src/decorator/relations/JoinColumn";
|
||||
|
||||
@Entity()
|
||||
export class Post {
|
||||
@ -32,6 +34,10 @@ export class Post {
|
||||
@ManyToOne(type => Category)
|
||||
category: Promise<Category>;
|
||||
|
||||
@OneToOne(type => Category, category => category.onePost)
|
||||
@JoinColumn()
|
||||
oneCategory: Promise<Category>;
|
||||
|
||||
@ManyToOne(type => Category, category => category.twoSidePosts2)
|
||||
twoSideCategory: Promise<Category>;
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import {Category} from "./entity/Category";
|
||||
* Because lazy relations are overriding prototype is impossible to run these tests on multiple connections.
|
||||
* So we run tests only for mysql.
|
||||
*/
|
||||
describe.skip("lazy-relations", () => {
|
||||
describe("lazy-relations", () => {
|
||||
|
||||
let userSchema: any, profileSchema: any;
|
||||
try {
|
||||
@ -222,4 +222,111 @@ describe.skip("lazy-relations", () => {
|
||||
loadedCategory.name.should.be.equal("category of great post");
|
||||
})));
|
||||
|
||||
it("should persist and hydrate successfully on a one-to-many relation", () => Promise.all(connections.map(async connection => {
|
||||
|
||||
// create some fake posts and categories to make sure that there are several post ids in the db
|
||||
const fakePosts: Post[] = [];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const fakePost = new Post();
|
||||
fakePost.title = "post #" + i;
|
||||
fakePost.text = "post #" + i;
|
||||
fakePosts.push(fakePost);
|
||||
}
|
||||
await connection.entityManager.persist(fakePosts);
|
||||
|
||||
const fakeCategories: Category[] = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const fakeCategory = new Category();
|
||||
fakeCategory.name = "category #" + i;
|
||||
fakeCategories.push(fakeCategory);
|
||||
}
|
||||
await connection.entityManager.persist(fakeCategories);
|
||||
|
||||
const category = new Category();
|
||||
category.name = "category of great post";
|
||||
await connection.entityManager.persist(category);
|
||||
|
||||
const post = new Post();
|
||||
post.title = "post with great category";
|
||||
post.text = "post with great category and great text";
|
||||
post.twoSideCategory = Promise.resolve(category);
|
||||
await connection.entityManager.persist(post);
|
||||
|
||||
const loadedCategory = await connection.entityManager.findOne(Category, { where: { name: "category of great post" } });
|
||||
const loadedPost = await loadedCategory!.twoSidePosts2;
|
||||
|
||||
loadedPost[0].title.should.be.equal("post with great category");
|
||||
})));
|
||||
|
||||
it("should persist and hydrate successfully on a one-to-one relation owner side", () => Promise.all(connections.map(async connection => {
|
||||
|
||||
// create some fake posts and categories to make sure that there are several post ids in the db
|
||||
const fakePosts: Post[] = [];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const fakePost = new Post();
|
||||
fakePost.title = "post #" + i;
|
||||
fakePost.text = "post #" + i;
|
||||
fakePosts.push(fakePost);
|
||||
}
|
||||
await connection.entityManager.persist(fakePosts);
|
||||
|
||||
const fakeCategories: Category[] = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const fakeCategory = new Category();
|
||||
fakeCategory.name = "category #" + i;
|
||||
fakeCategories.push(fakeCategory);
|
||||
}
|
||||
await connection.entityManager.persist(fakeCategories);
|
||||
|
||||
const category = new Category();
|
||||
category.name = "category of great post";
|
||||
await connection.entityManager.persist(category);
|
||||
|
||||
const post = new Post();
|
||||
post.title = "post with great category";
|
||||
post.text = "post with great category and great text";
|
||||
post.oneCategory = Promise.resolve(category);
|
||||
await connection.entityManager.persist(post);
|
||||
|
||||
const loadedPost = await connection.entityManager.findOne(Post, { where: { title: "post with great category" } });
|
||||
const loadedCategory = await loadedPost!.oneCategory;
|
||||
|
||||
loadedCategory.name.should.be.equal("category of great post");
|
||||
})));
|
||||
|
||||
it("should persist and hydrate successfully on a one-to-one relation inverse side", () => Promise.all(connections.map(async connection => {
|
||||
|
||||
// create some fake posts and categories to make sure that there are several post ids in the db
|
||||
const fakePosts: Post[] = [];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const fakePost = new Post();
|
||||
fakePost.title = "post #" + i;
|
||||
fakePost.text = "post #" + i;
|
||||
fakePosts.push(fakePost);
|
||||
}
|
||||
await connection.entityManager.persist(fakePosts);
|
||||
|
||||
const fakeCategories: Category[] = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const fakeCategory = new Category();
|
||||
fakeCategory.name = "category #" + i;
|
||||
fakeCategories.push(fakeCategory);
|
||||
}
|
||||
await connection.entityManager.persist(fakeCategories);
|
||||
|
||||
const category = new Category();
|
||||
category.name = "category of great post";
|
||||
await connection.entityManager.persist(category);
|
||||
|
||||
const post = new Post();
|
||||
post.title = "post with great category";
|
||||
post.text = "post with great category and great text";
|
||||
post.oneCategory = Promise.resolve(category);
|
||||
await connection.entityManager.persist(post);
|
||||
|
||||
const loadedCategory = await connection.entityManager.findOne(Category, { where: { name: "category of great post" } });
|
||||
const loadedPost = await loadedCategory!.onePost;
|
||||
loadedPost.title.should.be.equal("post with great category");
|
||||
})));
|
||||
|
||||
});
|
||||
|
||||
@ -6,7 +6,7 @@ import {expect} from "chai";
|
||||
import {Category} from "./entity/Category";
|
||||
import {Tag} from "./entity/Tag";
|
||||
|
||||
describe.skip("github issues > #234 and #223 lazy loading does not work correctly from one-to-many and many-to-many sides", () => {
|
||||
describe("github issues > #234 and #223 lazy loading does not work correctly from one-to-many and many-to-many sides", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await createTestingConnections({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user