This commit is contained in:
Umed Khudoiberdiev 2016-11-28 23:32:40 +05:00
parent 543f5c3b75
commit c93d77c36e
17 changed files with 394 additions and 110 deletions

View File

@ -11,6 +11,7 @@ import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
*/
export interface EntityMetadataArgs {
readonly junction: boolean;
readonly target: Function|string;
readonly tablesPrefix?: string;
readonly inheritanceType?: "single-table"|"class-table";

View File

@ -71,6 +71,7 @@ export class ClosureJunctionEntityMetadataBuilder {
});
return new EntityMetadata({
junction: true,
target: "__virtual__",
tablesPrefix: driver.options.tablesPrefix,
namingStrategy: args.namingStrategy,

View File

@ -228,6 +228,7 @@ export class EntityMetadataBuilder {
});
// create a new entity metadata
const entityMetadata = new EntityMetadata({
junction: false,
target: tableArgs.target,
tablesPrefix: driver.options.tablesPrefix,
namingStrategy: namingStrategy,
@ -462,9 +463,6 @@ export class EntityMetadataBuilder {
// check for errors in a built metadata schema (we need to check after relationEntityMetadata is set)
getFromContainer(EntityMetadataValidator).validateMany(entityMetadatas);
// check for errors in a built metadata schema (we need to check after relationEntityMetadata is set)
getFromContainer(EntityMetadataValidator).validateDependencies(entityMetadatas);
return entityMetadatas;
}

View File

@ -25,31 +25,7 @@ export class EntityMetadataValidator {
*/
validateMany(entityMetadatas: EntityMetadata[]) {
entityMetadatas.forEach(entityMetadata => this.validate(entityMetadata, entityMetadatas));
}
/**
* Validates dependencies of the entity metadatas.
*/
validateDependencies(entityMetadatas: EntityMetadata[]) {
const DepGraph = require("dependency-graph").DepGraph;
const graph = new DepGraph();
entityMetadatas.forEach(entityMetadata => {
graph.addNode(entityMetadata.name);
});
entityMetadatas.forEach(entityMetadata => {
entityMetadata.relationsWithJoinColumns
.filter(relation => !relation.isNullable)
.forEach(relation => {
graph.addDependency(entityMetadata.name, relation.inverseEntityMetadata.name);
});
});
try {
graph.overallOrder();
} catch (err) {
throw new CircularRelationsError(err.toString().replace("Error: Dependency Cycle Found: ", ""));
}
this.validateDependencies(entityMetadatas);
}
/**
@ -58,7 +34,7 @@ export class EntityMetadataValidator {
validate(entityMetadata: EntityMetadata, allEntityMetadatas: EntityMetadata[]) {
// check if table metadata has an id
if (!entityMetadata.table.isClassTableChild && !entityMetadata.primaryColumns.length)
if (!entityMetadata.table.isClassTableChild && !entityMetadata.primaryColumns.length && !entityMetadata.junction)
throw new MissingPrimaryColumnError(entityMetadata);
// validate if table is using inheritance it has a discriminator
@ -130,4 +106,30 @@ export class EntityMetadataValidator {
});
}
/**
* Validates dependencies of the entity metadatas.
*/
protected validateDependencies(entityMetadatas: EntityMetadata[]) {
const DepGraph = require("dependency-graph").DepGraph;
const graph = new DepGraph();
entityMetadatas.forEach(entityMetadata => {
graph.addNode(entityMetadata.name);
});
entityMetadatas.forEach(entityMetadata => {
entityMetadata.relationsWithJoinColumns
.filter(relation => !relation.isNullable)
.forEach(relation => {
graph.addDependency(entityMetadata.name, relation.inverseEntityMetadata.name);
});
});
try {
graph.overallOrder();
} catch (err) {
throw new CircularRelationsError(err.toString().replace("Error: Dependency Cycle Found: ", ""));
}
}
}

View File

@ -63,6 +63,7 @@ export class JunctionEntityMetadataBuilder {
});
const entityMetadata = new EntityMetadata({
junction: true,
target: "__virtual__",
tablesPrefix: driver.options.tablesPrefix,
namingStrategy: args.namingStrategy,

View File

@ -46,6 +46,11 @@ export class EntityMetadata {
*/
readonly target: Function|string;
/**
* Indicates if this entity metadata of a junction table, or not.
*/
readonly junction: boolean;
/**
* Entity's table metadata.
*/
@ -104,6 +109,7 @@ export class EntityMetadata {
constructor(args: EntityMetadataArgs,
private lazyRelationsWrapper: LazyRelationsWrapper) {
this.target = args.target;
this.junction = args.junction;
this.tablesPrefix = args.tablesPrefix;
this.namingStrategy = args.namingStrategy;
this.table = args.tableMetadata;
@ -547,18 +553,41 @@ export class EntityMetadata {
const map: ObjectLiteral = {};
if (this.parentEntityMetadata) {
this.primaryColumnsWithParentIdColumns.forEach(column => {
map[column.propertyName] = entity[column.propertyName];
const entityValue = entity[column.propertyName];
if (entityValue === null || entityValue === undefined)
return;
// if entity id is a relation, then extract referenced column from that relation
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
if (columnRelation && columnRelation.joinColumn) {
map[column.name] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
map[column.name] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
} else {
map[column.name] = entityValue;
}
});
} else {
this.primaryColumns.forEach(column => {
map[column.propertyName] = entity[column.propertyName];
const entityValue = entity[column.propertyName];
if (entityValue === null || entityValue === undefined)
return;
// if entity id is a relation, then extract referenced column from that relation
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
if (columnRelation && columnRelation.joinColumn) {
map[column.name] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
map[column.name] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
} else {
map[column.name] = entityValue;
}
});
}
const hasAllIds = this.primaryColumns.every(primaryColumn => {
return map[primaryColumn.propertyName] !== undefined && map[primaryColumn.propertyName] !== null;
});
return hasAllIds ? map : undefined;
return Object.keys(map).length > 0 ? map : undefined;
}
/**
@ -568,22 +597,48 @@ export class EntityMetadata {
const map: ObjectLiteral = {};
if (this.parentEntityMetadata) {
this.primaryColumnsWithParentIdColumns.forEach(column => {
map[column.name] = entity[column.propertyName];
const entityValue = entity[column.propertyName];
if (entityValue === null || entityValue === undefined)
return;
// if entity id is a relation, then extract referenced column from that relation
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
if (columnRelation && columnRelation.joinColumn) {
map[column.name] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
map[column.name] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
} else {
map[column.name] = entityValue;
}
});
} else {
this.primaryColumns.forEach(column => {
map[column.name] = entity[column.propertyName];
const entityValue = entity[column.propertyName];
if (entityValue === null || entityValue === undefined)
return;
// if entity id is a relation, then extract referenced column from that relation
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
if (columnRelation && columnRelation.joinColumn) {
map[column.name] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
map[column.name] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
} else {
map[column.name] = entityValue;
}
});
}
const hasAllIds = this.primaryColumns.every(primaryColumn => {
return map[primaryColumn.name] !== undefined && map[primaryColumn.name] !== null;
const hasAllIds = Object.keys(map).every(key => {
return map[key] !== undefined && map[key] !== null;
});
return hasAllIds ? map : undefined;
}
/**
*/
createSimpleIdMap(id: any): ObjectLiteral {
const map: ObjectLiteral = {};
if (this.parentEntityMetadata) {
@ -597,11 +652,11 @@ export class EntityMetadata {
});
}
return map;
}
} */
/**
* Same as createSimpleIdMap, but instead of id column property names it returns database column names.
*/
createSimpleDatabaseIdMap(id: any): ObjectLiteral {
const map: ObjectLiteral = {};
if (this.parentEntityMetadata) {
@ -615,7 +670,7 @@ export class EntityMetadata {
});
}
return map;
}
}*/
/**
* todo: undefined entities should not go there??
@ -830,8 +885,8 @@ export class EntityMetadata {
* Checks if there any non-nullable column exist in this entity.
*/
get hasNonNullableColumns(): boolean {
return this.relationsWithJoinColumns.length === 0 ||
this.relationsWithJoinColumns.every(relation => relation.isNullable);
return this.relationsWithJoinColumns.some(relation => !relation.isNullable || relation.isPrimary);
// return this.relationsWithJoinColumns.some(relation => relation.isNullable || relation.isPrimary);
}
}

View File

@ -253,7 +253,7 @@ export class Subject {
* If entity is not set then it returns undefined.
* If entity itself has an id then it simply returns it.
* If entity does not have an id then it returns newly generated id.
*/
get getPersistedEntityIdMap(): any|undefined {
if (!this.hasEntity)
return undefined;
@ -266,7 +266,7 @@ export class Subject {
return this.metadata.createSimpleDatabaseIdMap(this.newlyGeneratedId);
return undefined;
}
}*/
// -------------------------------------------------------------------------
// Public Methods

View File

@ -227,7 +227,12 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
const allIds = subjectGroup.subjects
.filter(subject => !subject.hasDatabaseEntity) // we don't load if subject already has a database entity loaded
.map(subject => subject.metadata.getEntityIdMixedMap(subject.entity)) // we only need entity id
.filter(mixedId => mixedId !== undefined && mixedId !== null && mixedId !== ""); // we don't need empty ids
.filter(mixedId => { // we don't need empty ids
if (mixedId instanceof Object)
return Object.keys(mixedId).every(key => mixedId[key] !== undefined && mixedId[key] !== null && mixedId[key] !== "");
return mixedId !== undefined && mixedId !== null && mixedId !== "";
});
// if there no ids found (which means all entities are new and have generated ids) - then nothing to load there
if (!allIds.length)

View File

@ -146,14 +146,16 @@ export class SubjectOperationExecutor {
// separate insert entities into groups:
// TODO: current ordering mechanism is bad. need to create a correct order in which entities should be persisted, need to build a dependency graph
// first group of subjects are subjects without any non-nullable column
// we need to insert first such entities because second group entities may rely on those entities.
const firstInsertSubjects = this.insertSubjects.filter(subject => subject.metadata.hasNonNullableColumns);
const firstInsertSubjects = this.insertSubjects.filter(subject => !subject.metadata.hasNonNullableColumns);
// second group - are all other subjects
// since in this group there are non nullable columns, some of them may depend on value of the
// previously inserted entity (which only can be entity with all nullable columns)
const secondInsertSubjects = this.insertSubjects.filter(subject => !subject.metadata.hasNonNullableColumns);
const secondInsertSubjects = this.insertSubjects.filter(subject => subject.metadata.hasNonNullableColumns);
// note: these operations should be executed in sequence, not in parallel
// because second group depend of obtained data from the first group
@ -168,7 +170,7 @@ export class SubjectOperationExecutor {
// but category in post is inserted with "null".
// now we need to update post table - set category with a newly persisted category id.
const updatePromises: Promise<any>[] = [];
this.insertSubjects.forEach(subject => {
firstInsertSubjects.forEach(subject => {
// first update relations with join columns (one-to-one owner and many-to-one relations)
const updateOptions: ObjectLiteral = {};
@ -180,81 +182,153 @@ export class SubjectOperationExecutor {
if (!relatedEntity)
return;
const insertSubject = this.insertSubjects.find(insertedSubject => relatedEntity === insertedSubject.entity);
// check if relation reference column is a relation
let relationId: any;
const columnRelation = relation.inverseEntityMetadata.relations.find(rel => rel.propertyName === relation.joinColumn.referencedColumn.propertyName);
if (columnRelation) { // if referenced column is a relation
const insertSubject = this.insertSubjects.find(insertedSubject => insertedSubject.entity === relatedEntity[referencedColumn.propertyName]);
// if relation id exist exist in the related entity then simply use it
const relationId = relatedEntity[referencedColumn.propertyName]; // todo: what about relationId got from relation column, not relation itself? todo: create relation.getEntityRelationId(entity)
// if this relation was just inserted
if (insertSubject) {
// otherwise check if relation id was just now inserted and we can use its generated values
if (insertSubject) {
if (relationId) {
updateOptions[relation.name] = relationId;
// check if we have this relation id already
relationId = relatedEntity[referencedColumn.propertyName][columnRelation.propertyName];
if (!relationId) {
} else if (referencedColumn.isGenerated) {
updateOptions[relation.name] = insertSubject.newlyGeneratedId;
// if we don't have relation id then use special values
if (referencedColumn.isGenerated) {
relationId = insertSubject.newlyGeneratedId;
}
// todo: handle other special types too
}
}
// todo: handle other special types too
} else { // if referenced column is a simple non relational column
const insertSubject = this.insertSubjects.find(insertedSubject => insertedSubject.entity === relatedEntity);
// if this relation was just inserted
if (insertSubject) {
// check if we have this relation id already
relationId = relatedEntity[referencedColumn.propertyName];
if (!relationId) {
// if we don't have relation id then use special values
if (referencedColumn.isGenerated) {
relationId = insertSubject.newlyGeneratedId;
}
// todo: handle other special types too
}
}
}
if (relationId) {
updateOptions[relation.name] = relationId;
}
});
// if we found relations which we can update - then update them
if (Object.keys(updateOptions).length > 0) {
const relatedEntityIdMap = subject.getPersistedEntityIdMap;
const updatePromise = this.queryRunner.update(subject.metadata.table.name, updateOptions, relatedEntityIdMap);
if (Object.keys(updateOptions).length > 0 /*&& subject.hasEntity*/) {
// const relatedEntityIdMap = subject.getPersistedEntityIdMap; // todo: this works incorrectly
const columns = subject.metadata.parentEntityMetadata ? subject.metadata.primaryColumnsWithParentIdColumns : subject.metadata.primaryColumns;
const conditions: ObjectLiteral = {};
columns.forEach(column => {
const entityValue = subject.entity[column.propertyName];
// if entity id is a relation, then extract referenced column from that relation
const columnRelation = subject.metadata.relations.find(relation => relation.propertyName === column.propertyName);
if (entityValue && columnRelation && columnRelation.joinColumn) { // not sure if we need handle join column from inverse side
let relationIdOfEntityValue = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
if (!relationIdOfEntityValue) {
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === entityValue);
if (entityValueInsertSubject && columnRelation.joinColumn.referencedColumn.isGenerated) {
relationIdOfEntityValue = entityValueInsertSubject.newlyGeneratedId;
}
}
if (relationIdOfEntityValue) {
conditions[column.name] = relationIdOfEntityValue;
}
} else {
if (entityValue) {
conditions[column.name] = entityValue;
} else {
if (subject.newlyGeneratedId) {
conditions[column.name] = subject.newlyGeneratedId;
}
}
}
});
if (!Object.keys(conditions).length)
return;
const updatePromise = this.queryRunner.update(subject.metadata.table.name, updateOptions, conditions);
updatePromises.push(updatePromise);
}
// we need to update relation ids if newly inserted objects are used from inverse side in one-to-many inverse relation
subject.metadata.oneToManyRelations.forEach(relation => {
const referencedColumn = relation.inverseRelation.joinColumn.referencedColumn;
const relatedEntity = subject.entity[relation.propertyName];
// we also need to update relation ids if newly inserted objects are used from inverse side in one-to-one inverse relation
const oneToManyAndOneToOneNonOwnerRelations = subject.metadata.oneToManyRelations.concat(subject.metadata.oneToOneRelations.filter(relation => !relation.isOwning));
subject.metadata.extractRelationValuesFromEntity(subject.entity, oneToManyAndOneToOneNonOwnerRelations)
.forEach(([relation, subRelatedEntity, inverseEntityMetadata]) => {
const referencedColumn = relation.inverseRelation.joinColumn.referencedColumn;
const columns = inverseEntityMetadata.parentEntityMetadata ? inverseEntityMetadata.primaryColumnsWithParentIdColumns : inverseEntityMetadata.primaryColumns;
const conditions: ObjectLiteral = {};
// if related entity is not an array then no need to proceed
if (!(relatedEntity instanceof Array))
return;
columns.forEach(column => {
const entityValue = subRelatedEntity[column.propertyName];
relatedEntity.forEach(subRelatedEntity => {
// if entity id is a relation, then extract referenced column from that relation
const columnRelation = inverseEntityMetadata.relations.find(relation => relation.propertyName === column.propertyName);
let relationId = subRelatedEntity[referencedColumn.propertyName];
const insertSubject = this.insertSubjects.find(insertedSubject => subRelatedEntity === insertedSubject.entity);
if (entityValue && columnRelation && columnRelation.joinColumn) { // not sure if we need handle join column from inverse side
let relationIdOfEntityValue = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
if (!relationIdOfEntityValue) {
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === entityValue);
if (entityValueInsertSubject && columnRelation.joinColumn.referencedColumn.isGenerated) {
relationIdOfEntityValue = entityValueInsertSubject.newlyGeneratedId;
}
}
if (relationIdOfEntityValue) {
conditions[column.name] = relationIdOfEntityValue;
}
if (insertSubject) {
} else {
const entityValueInsertSubject = this.insertSubjects.find(subject => subject.entity === subRelatedEntity);
if (entityValue) {
conditions[column.name] = entityValue;
} else {
if (entityValueInsertSubject && entityValueInsertSubject.newlyGeneratedId) {
conditions[column.name] = entityValueInsertSubject.newlyGeneratedId;
}
}
}
});
if (!Object.keys(conditions).length)
return;
if (!relationId && referencedColumn.isGenerated)
relationId = insertSubject.newlyGeneratedId;
// todo: implement other special referenced column types (update date, create date, version, discriminator column, etc.)
const updateOptions: ObjectLiteral = { };
const columnRelation = relation.inverseEntityMetadata.relations.find(rel => rel.propertyName === referencedColumn.propertyName);
if (columnRelation) {
let id = subject.entity[referencedColumn.propertyName][columnRelation.propertyName];
if (!id) {
const insertSubject = this.insertSubjects.find(subject => subject.entity === subject.entity[referencedColumn.propertyName]);
if (insertSubject) {
id = insertSubject.newlyGeneratedId;
}
}
updateOptions[relation.inverseRelation.joinColumn.name] = id;
} else {
updateOptions[relation.inverseRelation.joinColumn.name] = subject.entity[referencedColumn.propertyName] || subject.newlyGeneratedId;
}
const id = subject.entity[referencedColumn.propertyName] || subject.newlyGeneratedId;
const conditions = relation.inverseEntityMetadata.getDatabaseEntityIdMap(subRelatedEntity) || relation.inverseEntityMetadata.createSimpleDatabaseIdMap(relationId);
const updateOptions = { [relation.inverseRelation.joinColumn.name]: id }; // todo: what if subject's id is not generated?
const updatePromise = this.queryRunner.update(relation.inverseEntityMetadata.table.name, updateOptions, conditions);
updatePromises.push(updatePromise);
});
});
// we also need to update relation ids if newly inserted objects are used from inverse side in one-to-one inverse relation
subject.metadata.oneToOneRelations.filter(relation => !relation.isOwning).forEach(relation => {
const referencedColumn = relation.inverseRelation.joinColumn.referencedColumn;
const value = subject.entity[relation.propertyName];
this.insertSubjects.forEach(insertedSubject => {
if (value === insertedSubject.entity) {
if (referencedColumn.isGenerated) {
const conditions = insertedSubject.metadata.getDatabaseEntityIdMap(insertedSubject.entity) || insertedSubject.metadata.createSimpleDatabaseIdMap(insertedSubject.newlyGeneratedId);
const updateOptions = { [relation.inverseRelation.joinColumn.name]: subject.newlyGeneratedId };
const updatePromise = this.queryRunner.update(relation.inverseRelation.entityMetadata.table.name, updateOptions, conditions);
updatePromises.push(updatePromise);
}
// todo: implement other special referenced column types (update date, create date, version, discriminator column, etc.)
}
});
});
});

View File

@ -1848,7 +1848,7 @@ export class QueryBuilder<Entity> {
const whereSubStrings: string[] = [];
if (metadata.hasMultiplePrimaryKeys) {
metadata.primaryColumns.forEach((primaryColumn, secondIndex) => {
whereSubStrings.push(id[primaryColumn.name] + "=:id_" + index + "_" + secondIndex);
whereSubStrings.push(primaryColumn.name + "=:id_" + index + "_" + secondIndex);
parameters["id_" + index + "_" + secondIndex] = id[primaryColumn.name];
});
metadata.parentIdColumns.forEach((primaryColumn, secondIndex) => {

View File

@ -73,9 +73,7 @@ describe("persistence > one-to-many", function() {
const loadedPost = await postRepository.findOneById(1, findOptions);
expect(loadedPost).not.to.be.empty;
expect(loadedPost.categories).not.to.be.empty;
if (loadedPost.categories) {
expect(loadedPost.categories[0]).not.to.be.empty;
}
expect(loadedPost.categories![0]).not.to.be.empty;
})));
});

View File

@ -20,7 +20,10 @@ describe("one-to-one", function() {
const options: ConnectionOptions = {
driver: createTestingConnectionOptions("mysql"),
entities: [Post, PostDetails, PostCategory, PostMetadata, PostImage, PostInformation, PostAuthor]
entities: [Post, PostDetails, PostCategory, PostMetadata, PostImage, PostInformation, PostAuthor],
// logging: {
// logQueries: true
// }
};
// connect to db

View File

@ -20,7 +20,10 @@ describe("many-to-one", function() {
const options: ConnectionOptions = {
driver: createTestingConnectionOptions("mysql"),
entities: [Post, PostDetails, PostCategory, PostMetadata, PostImage, PostInformation, PostAuthor]
entities: [Post, PostDetails, PostCategory, PostMetadata, PostImage, PostInformation, PostAuthor],
// logging: {
// logQueries: true
// }
};
// connect to db

View File

@ -0,0 +1,19 @@
import {Table} from "../../../../src/decorator/tables/Table";
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../src/decorator/columns/Column";
import {OneToMany} from "../../../../src/decorator/relations/OneToMany";
import {PostCategory} from "./PostCategory";
@Table()
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(type => PostCategory, postCategory => postCategory.category)
posts: PostCategory[];
}

View File

@ -0,0 +1,19 @@
import {Table} from "../../../../src/decorator/tables/Table";
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../src/decorator/columns/Column";
import {OneToMany} from "../../../../src/decorator/relations/OneToMany";
import {PostCategory} from "./PostCategory";
@Table()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@OneToMany(type => PostCategory, postCategoryRelation => postCategoryRelation.post)
categories: PostCategory[];
}

View File

@ -0,0 +1,28 @@
import {Table} from "../../../../src/decorator/tables/Table";
import {Column} from "../../../../src/decorator/columns/Column";
import {Post} from "./Post";
import {ManyToOne} from "../../../../src/decorator/relations/ManyToOne";
import {Category} from "./Category";
@Table()
export class PostCategory {
@ManyToOne(type => Post, post => post.categories, {
primary: true,
cascadeInsert: true
})
post: Post;
@ManyToOne(type => Category, category => category.posts, {
primary: true,
cascadeInsert: true
})
category: Category;
@Column()
addedByAdmin: boolean;
@Column()
addedByUser: boolean;
}

View File

@ -0,0 +1,77 @@
import "reflect-metadata";
import {setupTestingConnections, closeConnections, reloadDatabases} from "../../utils/test-utils";
import {Connection} from "../../../src/connection/Connection";
import {Post} from "./entity/Post";
import {Category} from "./entity/Category";
import {PostCategory} from "./entity/PostCategory";
import {expect} from "chai";
describe("github issues > #58 relations with multiple primary keys", () => {
let connections: Connection[];
before(async () => connections = await setupTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchemaOnConnection: true,
}));
beforeEach(() => reloadDatabases(connections));
after(() => closeConnections(connections));
it("should persist successfully and return persisted entity", () => Promise.all(connections.map(async connection => {
// create objects to save
const category1 = new Category();
category1.name = "category #1";
const category2 = new Category();
category2.name = "category #2";
const post = new Post();
post.title = "Hello Post #1";
const postCategory1 = new PostCategory();
postCategory1.addedByAdmin = true;
postCategory1.addedByUser = false;
postCategory1.category = category1;
postCategory1.post = post;
const postCategory2 = new PostCategory();
postCategory2.addedByAdmin = false;
postCategory2.addedByUser = true;
postCategory2.category = category2;
postCategory2.post = post;
await connection.entityManager.persist(postCategory1);
await connection.entityManager.persist(postCategory2);
// check that all persisted objects exist
const loadedPost = await connection.entityManager
.createQueryBuilder(Post, "post")
.innerJoinAndSelect("post.categories", "postCategory")
.innerJoinAndSelect("postCategory.category", "category")
.getSingleResult();
expect(loadedPost).not.to.be.empty;
loadedPost.should.be.eql({
id: 1,
title: "Hello Post #1",
categories: [{
addedByAdmin: true,
addedByUser: false,
category: {
id: 1,
name: "category #1"
}
}, {
addedByAdmin: false,
addedByUser: true,
category: {
id: 2,
name: "category #2"
}
}]
});
})));
});