diff --git a/src/decorator/options/JoinTableMuplipleColumnsOptions.ts b/src/decorator/options/JoinTableMuplipleColumnsOptions.ts index 700710e26..d3d7b3ff5 100644 --- a/src/decorator/options/JoinTableMuplipleColumnsOptions.ts +++ b/src/decorator/options/JoinTableMuplipleColumnsOptions.ts @@ -3,7 +3,7 @@ import {JoinColumnOptions} from "./JoinColumnOptions"; /** * Describes all relation's options. */ -export interface JoinTableMuplipleColumnsOptions { +export interface JoinTableMultipleColumnsOptions { /** * Name of the table that will be created to store values of the both tables (join table). diff --git a/src/decorator/relations/JoinTable.ts b/src/decorator/relations/JoinTable.ts index 79f4975ae..6abd8cffa 100644 --- a/src/decorator/relations/JoinTable.ts +++ b/src/decorator/relations/JoinTable.ts @@ -1,7 +1,7 @@ import {getMetadataArgsStorage} from "../../index"; import {JoinTableOptions} from "../options/JoinTableOptions"; import {JoinTableMetadataArgs} from "../../metadata-args/JoinTableMetadataArgs"; -import {JoinTableMuplipleColumnsOptions} from "../options/JoinTableMuplipleColumnsOptions"; +import {JoinTableMultipleColumnsOptions} from "../options/JoinTableMuplipleColumnsOptions"; /** * JoinTable decorator is used in many-to-many relationship to specify owner side of relationship. @@ -19,21 +19,21 @@ export function JoinTable(options: JoinTableOptions): Function; * JoinTable decorator is used in many-to-many relationship to specify owner side of relationship. * Its also used to set a custom junction table's name, column names and referenced columns. */ -export function JoinTable(options: JoinTableMuplipleColumnsOptions): Function; +export function JoinTable(options: JoinTableMultipleColumnsOptions): Function; /** * JoinTable decorator is used in many-to-many relationship to specify owner side of relationship. * Its also used to set a custom junction table's name, column names and referenced columns. */ -export function JoinTable(options?: JoinTableOptions|JoinTableMuplipleColumnsOptions): Function { +export function JoinTable(options?: JoinTableOptions|JoinTableMultipleColumnsOptions): Function { return function (object: Object, propertyName: string) { - options = options || {} as JoinTableOptions|JoinTableMuplipleColumnsOptions; + options = options || {} as JoinTableOptions|JoinTableMultipleColumnsOptions; const args: JoinTableMetadataArgs = { target: object.constructor, propertyName: propertyName, name: options.name, - joinColumns: (options && (options as JoinTableOptions).joinColumn ? [(options as JoinTableOptions).joinColumn!] : (options as JoinTableMuplipleColumnsOptions).joinColumns) as any, - inverseJoinColumns: (options && (options as JoinTableOptions).inverseJoinColumn ? [(options as JoinTableOptions).inverseJoinColumn!] : (options as JoinTableMuplipleColumnsOptions).inverseJoinColumns) as any, + joinColumns: (options && (options as JoinTableOptions).joinColumn ? [(options as JoinTableOptions).joinColumn!] : (options as JoinTableMultipleColumnsOptions).joinColumns) as any, + inverseJoinColumns: (options && (options as JoinTableOptions).inverseJoinColumn ? [(options as JoinTableOptions).inverseJoinColumn!] : (options as JoinTableMultipleColumnsOptions).inverseJoinColumns) as any, }; getMetadataArgsStorage().joinTables.add(args); }; diff --git a/src/entity-schema/EntitySchema.ts b/src/entity-schema/EntitySchema.ts index 1848e560f..4ecfd77e8 100644 --- a/src/entity-schema/EntitySchema.ts +++ b/src/entity-schema/EntitySchema.ts @@ -4,7 +4,7 @@ import {OnDeleteType} from "../metadata/ForeignKeyMetadata"; import {JoinColumnOptions} from "../decorator/options/JoinColumnOptions"; import {ColumnType} from "../metadata/types/ColumnTypes"; import {RelationType} from "../metadata/types/RelationTypes"; -import {JoinTableMuplipleColumnsOptions} from "../decorator/options/JoinTableMuplipleColumnsOptions"; +import {JoinTableMultipleColumnsOptions} from "../decorator/options/JoinTableMuplipleColumnsOptions"; export interface EntitySchema { @@ -177,7 +177,7 @@ export interface EntitySchema { /** * Join table options of this column. If set to true then it simply means that it has a join table. */ - joinTable?: boolean|JoinColumnOptions|JoinTableMuplipleColumnsOptions; + joinTable?: boolean|JoinColumnOptions|JoinTableMultipleColumnsOptions; /** * Join column options of this column. If set to true then it simply means that it has a join column. diff --git a/src/lazy-loading/LazyRelationsWrapper.ts b/src/lazy-loading/LazyRelationsWrapper.ts index 9ebcfb8a8..2962e4a56 100644 --- a/src/lazy-loading/LazyRelationsWrapper.ts +++ b/src/lazy-loading/LazyRelationsWrapper.ts @@ -50,7 +50,7 @@ export class LazyRelationsWrapper { relation.joinColumns.forEach(joinColumn => { qb.andWhere(`${relation.entityMetadata.name}.${joinColumn.referencedColumn.fullName} = :${joinColumn.referencedColumn.fullName}`) - .setParameter(`${joinColumn.referencedColumn.fullName}`, this[joinColumn.referencedColumn.fullName]) + .setParameter(`${joinColumn.referencedColumn.fullName}`, this[joinColumn.referencedColumn.fullName]); }); this[promiseIndex] = qb.getOne().then(result => { @@ -76,7 +76,7 @@ export class LazyRelationsWrapper { relation.inverseRelation.joinColumns.forEach(joinColumn => { qb.andWhere(`${relation.propertyName}.${joinColumn.name} = :${joinColumn.referencedColumn.fullName}`) - .setParameter(`${joinColumn.referencedColumn.fullName}`, this[joinColumn.referencedColumn.fullName]) + .setParameter(`${joinColumn.referencedColumn.fullName}`, this[joinColumn.referencedColumn.fullName]); }); const result = relation.isOneToMany ? qb.getMany() : qb.getOne(); diff --git a/src/metadata-builder/EntityMetadataBuilder.ts b/src/metadata-builder/EntityMetadataBuilder.ts index 92b4c6df9..0c363a12f 100644 --- a/src/metadata-builder/EntityMetadataBuilder.ts +++ b/src/metadata-builder/EntityMetadataBuilder.ts @@ -26,7 +26,7 @@ import {EmbeddedMetadataArgs} from "../metadata-args/EmbeddedMetadataArgs"; import {RelationIdMetadata} from "../metadata/RelationIdMetadata"; import {RelationCountMetadata} from "../metadata/RelationCountMetadata"; import {JoinTableOptions} from "../decorator/options/JoinTableOptions"; -import {JoinTableMuplipleColumnsOptions} from "../decorator/options/JoinTableMuplipleColumnsOptions"; +import {JoinTableMultipleColumnsOptions} from "../decorator/options/JoinTableMuplipleColumnsOptions"; /** * Aggregates all metadata: table, column, relation into one collection grouped by tables for a given set of classes. @@ -159,8 +159,8 @@ export class EntityMetadataBuilder { target: schema.target || schema.name, propertyName: relationName, name: relationSchema.joinTable.name, - joinColumns: ((relationSchema.joinTable as JoinTableOptions).joinColumn ? [(relationSchema.joinTable as JoinTableOptions).joinColumn!] : (relationSchema.joinTable as JoinTableMuplipleColumnsOptions).joinColumns) as any, - inverseJoinColumns: ((relationSchema.joinTable as JoinTableOptions).inverseJoinColumn ? [(relationSchema.joinTable as JoinTableOptions).inverseJoinColumn!] : (relationSchema.joinTable as JoinTableMuplipleColumnsOptions).inverseJoinColumns) as any, + joinColumns: ((relationSchema.joinTable as JoinTableOptions).joinColumn ? [(relationSchema.joinTable as JoinTableOptions).joinColumn!] : (relationSchema.joinTable as JoinTableMultipleColumnsOptions).joinColumns) as any, + inverseJoinColumns: ((relationSchema.joinTable as JoinTableOptions).inverseJoinColumn ? [(relationSchema.joinTable as JoinTableOptions).inverseJoinColumn!] : (relationSchema.joinTable as JoinTableMultipleColumnsOptions).inverseJoinColumns) as any, }; metadataArgsStorage.joinTables.add(joinTable); } @@ -403,6 +403,7 @@ export class EntityMetadataBuilder { // and create join column metadata args for them const joinColumnArgsArray = mergedArgs.joinColumns.filterByProperty(relation.propertyName); + relation.joinColumns = reusable( joinColumnArgsArray, relation.inverseEntityMetadata.primaryColumnsWithParentIdColumns, @@ -411,7 +412,6 @@ export class EntityMetadataBuilder { (columnName => namingStrategy.joinColumnName(relation.propertyName, columnName)) ); }); - }); entityMetadatas.forEach(entityMetadata => { @@ -483,8 +483,7 @@ export class EntityMetadataBuilder { // generate columns and foreign keys for tables with relations entityMetadatas.forEach(metadata => { metadata.relationsWithJoinColumns.forEach(relation => { - - const columns = relation.joinColumns.map(joinColumn => { + relation.joinColumns.map(joinColumn => { // find relational column and if it does not exist - add it let relationalColumn = metadata.columns.find(column => column.fullName === joinColumn.name); @@ -503,8 +502,19 @@ export class EntityMetadataBuilder { relationalColumn.relationMetadata = relation; metadata.addColumn(relationalColumn); } - return relationalColumn; }); + }); + }); + entityMetadatas.forEach(metadata => { + metadata.relationsWithJoinColumns.forEach(relation => { + + const columns = relation.joinColumns.map(joinColumn => { + return metadata.columns.find(column => column.fullName === joinColumn.name)!; + }); + // console.log("metadata:" , metadata.name); + // console.log("relation.relation:" , relation.propertyName); + // console.log("relation.joinColumns:" , relation.joinColumns); + // console.log("columns:" , columns); // create and add foreign key const inverseSideColumns = relation.joinColumns.map(joinColumn => joinColumn.referencedColumn); diff --git a/src/schema-builder/SchemaBuilder.ts b/src/schema-builder/SchemaBuilder.ts index 9ffbc2aec..7f1470e55 100644 --- a/src/schema-builder/SchemaBuilder.ts +++ b/src/schema-builder/SchemaBuilder.ts @@ -76,8 +76,8 @@ export class SchemaBuilder { await this.addNewColumns(); await this.updateExistColumns(); await this.updatePrimaryKeys(); + await this.createIndices(); // we need to create indices before foreign keys because foreign keys rely on unique indices await this.createForeignKeys(); - await this.createIndices(); await this.queryRunner.commitTransaction(); } catch (error) { @@ -364,8 +364,6 @@ export class SchemaBuilder { if (!tableSchema) return; - // console.log(allIndexMetadatas); - // find depend indices to drop them const dependIndices = allIndexMetadatas.filter(indexMetadata => { return indexMetadata.tableName === tableName && indexMetadata.columns.indexOf(columnName) !== -1; diff --git a/test/functional/relations/multiple-primary-keys/entity/Category.ts b/test/functional/relations/multiple-primary-keys/entity/Category.ts deleted file mode 100644 index e65472bc2..000000000 --- a/test/functional/relations/multiple-primary-keys/entity/Category.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Entity} from "../../../../../src/decorator/entity/Entity"; -import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn"; - -@Entity() -export class Category { - - @PrimaryColumn() - name: string; - - @PrimaryColumn() - type: string; - -} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/entity/Post.ts b/test/functional/relations/multiple-primary-keys/entity/Post.ts deleted file mode 100644 index fbc7a8226..000000000 --- a/test/functional/relations/multiple-primary-keys/entity/Post.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {Entity} from "../../../../../src/decorator/entity/Entity"; -import {Column} from "../../../../../src/decorator/columns/Column"; -import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn"; -import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne"; -import {Category} from "./Category"; - -@Entity() -export class Post { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - title: string; - - @ManyToOne(type => Category) - // @JoinColumn([ - // { name: "category_type", referencedColumnName: "type" }, - // { name: "category_name", referencedColumnName: "name" } - // ]) - category: Category; - - // todo: test relation with multiple + empty join column one-to-one - // todo: test relation with multiple + empty join column many-to-one - // todo: test relation with multiple + single join column - // todo: test relation with multiple + multiple join columns - -} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/entity/Category.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/entity/Category.ts new file mode 100644 index 000000000..c1922a0c8 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/entity/Category.ts @@ -0,0 +1,46 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn"; +import {Column} from "../../../../../../src/decorator/columns/Column"; +import {Index} from "../../../../../../src/decorator/Index"; +import {Post} from "./Post"; +import {ManyToMany} from "../../../../../../src/decorator/relations/ManyToMany"; +import {Tag} from "./Tag"; + +@Entity() +@Index(["code", "version", "description"], { unique: true }) +export class Category { + + @PrimaryColumn() + name: string; + + @PrimaryColumn() + type: string; + + @Column() + code: number; + + @Column() + version: number; + + @Column({nullable: true}) + description: string; + + @ManyToMany(type => Post, post => post.categories) + posts: Post[]; + + @ManyToMany(type => Post, post => post.categoriesWithOptions) + postsWithOptions: Post[]; + + @ManyToMany(type => Post, post => post.categoriesWithNonPrimaryColumns) + postsWithNonPrimaryColumns: Post[]; + + @ManyToMany(type => Tag, tag => tag.categories) + tags: Tag[]; + + @ManyToMany(type => Tag, tag => tag.categoriesWithOptions) + tagsWithOptions: Tag[]; + + @ManyToMany(type => Tag, tag => tag.categoriesWithNonPrimaryColumns) + tagsWithNonPrimaryColumns: Tag[]; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/entity/Post.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/entity/Post.ts new file mode 100644 index 000000000..d3874ca0b --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/entity/Post.ts @@ -0,0 +1,58 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {Column} from "../../../../../../src/decorator/columns/Column"; +import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {ManyToMany} from "../../../../../../src/decorator/relations/ManyToMany"; +import {JoinTable} from "../../../../../../src/decorator/relations/JoinTable"; +import {Category} from "./Category"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @ManyToMany(type => Category, category => category.posts) + @JoinTable() + categories: Category[]; + + @ManyToMany(type => Category, category => category.postsWithOptions) + @JoinTable({ + name: "post_categories", + joinColumns: [{ + name: "postId", + referencedColumnName: "id" + }], + inverseJoinColumns: [{ + name: "categoryName", + referencedColumnName: "name" + }, { + name: "categoryType", + referencedColumnName: "type" + }] + }) + categoriesWithOptions: Category[]; + + @ManyToMany(type => Category, category => category.postsWithNonPrimaryColumns) + @JoinTable({ + name: "post_categories_non_primary", + joinColumns: [{ + name: "postId", + referencedColumnName: "id" + }], + inverseJoinColumns: [{ + name: "categoryCode", + referencedColumnName: "code" + }, { + name: "categoryVersion", + referencedColumnName: "version" + }, { + name: "categoryDescription", + referencedColumnName: "description" + }] + }) + categoriesWithNonPrimaryColumns: Category[]; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/entity/Tag.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/entity/Tag.ts new file mode 100644 index 000000000..8f61e2145 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/entity/Tag.ts @@ -0,0 +1,67 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {ManyToMany} from "../../../../../../src/decorator/relations/ManyToMany"; +import {JoinTable} from "../../../../../../src/decorator/relations/JoinTable"; +import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn"; +import {Category} from "./Category"; +import {Column} from "../../../../../../src/decorator/columns/Column"; + +@Entity() +export class Tag { + + @Column() + code: number; + + @PrimaryColumn() + title: string; + + @PrimaryColumn() + description: string; + + @ManyToMany(type => Category, category => category.tags) + @JoinTable() + categories: Category[]; + + @ManyToMany(type => Category, category => category.tagsWithOptions) + @JoinTable({ + name: "tag_categories", + joinColumns: [{ + name: "tagTitle", + referencedColumnName: "title" + }, { + name: "tagDescription", + referencedColumnName: "description" + }], + inverseJoinColumns: [{ + name: "categoryName", + referencedColumnName: "name" + }, { + name: "categoryType", + referencedColumnName: "type" + }] + }) + categoriesWithOptions: Category[]; + + @ManyToMany(type => Category, category => category.tagsWithNonPrimaryColumns) + @JoinTable({ + name: "tag_categories_non_primary", + joinColumns: [{ + name: "tagTitle", + referencedColumnName: "title" + }, { + name: "tagDescription", + referencedColumnName: "description" + }], + inverseJoinColumns: [{ + name: "categoryCode", + referencedColumnName: "code" + }, { + name: "categoryVersion", + referencedColumnName: "version" + }, { + name: "categoryDescription", + referencedColumnName: "description" + }] + }) + categoriesWithNonPrimaryColumns: Category[]; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/multiple-primary-keys-many-to-many.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/multiple-primary-keys-many-to-many.ts new file mode 100644 index 000000000..513e4d9b8 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-many/multiple-primary-keys-many-to-many.ts @@ -0,0 +1,784 @@ +import "reflect-metadata"; +import * as chai from "chai"; +import {expect} from "chai"; +import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; +import {Connection} from "../../../../../src/connection/Connection"; +import {Post} from "./entity/Post"; +import {Category} from "./entity/Category"; +import {Tag} from "./entity/Tag"; + +const should = chai.should(); + +describe("relations > multiple-primary-keys > many-to-many", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + describe("owning side", () => { + + it("should load related entity when JoinTable used without options", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "BMW"; + category2.type = "cars-category"; + category2.code = 2; + category2.version = 1; + await connection.entityManager.persist(category2); + + const category3 = new Category(); + category3.name = "airplanes"; + category3.type = "common-category"; + category3.code = 3; + category3.version = 1; + await connection.entityManager.persist(category3); + + const post1 = new Post(); + post1.title = "About BMW"; + post1.categories = [category1, category2]; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Boeing"; + post2.categories = [category3]; + await connection.entityManager.persist(post2); + + const loadedPosts = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categories", "categories") + .orderBy("post.id, categories.code") + .getMany(); + + expect(loadedPosts[0].categories).to.not.be.empty; + expect(loadedPosts[0].categories[0].name).to.be.equal("cars"); + expect(loadedPosts[0].categories[0].type).to.be.equal("common-category"); + expect(loadedPosts[0].categories[1].name).to.be.equal("BMW"); + expect(loadedPosts[0].categories[1].type).to.be.equal("cars-category"); + expect(loadedPosts[1].categories).to.not.be.empty; + expect(loadedPosts[1].categories[0].name).to.be.equal("airplanes"); + expect(loadedPosts[1].categories[0].type).to.be.equal("common-category"); + + const loadedPost = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categories", "categories") + .orderBy("categories.code") + .where("post.id = :id", { id: 1 }) + .getOne(); + + expect(loadedPost!.categories).to.not.be.empty; + expect(loadedPost!.categories[0].name).to.be.equal("cars"); + expect(loadedPost!.categories[0].type).to.be.equal("common-category"); + + }))); + + it("should load related entity when JoinTable used with options", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "BMW"; + category2.type = "cars-category"; + category2.code = 2; + category2.version = 1; + await connection.entityManager.persist(category2); + + const category3 = new Category(); + category3.name = "airplanes"; + category3.type = "common-category"; + category3.code = 3; + category3.version = 1; + await connection.entityManager.persist(category3); + + const post1 = new Post(); + post1.title = "About BMW"; + post1.categoriesWithOptions = [category1, category2]; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Boeing"; + post2.categoriesWithOptions = [category3]; + await connection.entityManager.persist(post2); + + const loadedPosts = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoriesWithOptions", "categories") + .orderBy("post.id, categories.code") + .getMany(); + + expect(loadedPosts[0].categoriesWithOptions).to.not.be.empty; + expect(loadedPosts[0].categoriesWithOptions[0].name).to.be.equal("cars"); + expect(loadedPosts[0].categoriesWithOptions[0].type).to.be.equal("common-category"); + expect(loadedPosts[0].categoriesWithOptions[1].name).to.be.equal("BMW"); + expect(loadedPosts[0].categoriesWithOptions[1].type).to.be.equal("cars-category"); + expect(loadedPosts[1].categoriesWithOptions).to.not.be.empty; + expect(loadedPosts[1].categoriesWithOptions[0].name).to.be.equal("airplanes"); + expect(loadedPosts[1].categoriesWithOptions[0].type).to.be.equal("common-category"); + + const loadedPost = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoriesWithOptions", "categories") + .orderBy("categories.code") + .where("post.id = :id", { id: 1 }) + .getOne(); + + expect(loadedPost!.categoriesWithOptions).to.not.be.empty; + expect(loadedPost!.categoriesWithOptions[0].name).to.be.equal("cars"); + expect(loadedPost!.categoriesWithOptions[0].type).to.be.equal("common-category"); + + }))); + + it("should load related entity when JoinTable references with non-primary columns", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.description = "category of cars"; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "BMW"; + category2.type = "cars-category"; + category2.code = 2; + category2.version = 1; + category2.description = "category of BMW"; + await connection.entityManager.persist(category2); + + const category3 = new Category(); + category3.name = "airplanes"; + category3.type = "common-category"; + category3.code = 3; + category3.version = 1; + category3.description = "category of airplanes"; + await connection.entityManager.persist(category3); + + const post1 = new Post(); + post1.title = "About BMW"; + post1.categoriesWithNonPrimaryColumns = [category1, category2]; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Boeing"; + post2.categoriesWithNonPrimaryColumns = [category3]; + await connection.entityManager.persist(post2); + + const loadedPosts = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoriesWithNonPrimaryColumns", "categories") + .orderBy("post.id, categories.code") + .getMany(); + + expect(loadedPosts[0].categoriesWithNonPrimaryColumns).to.not.be.empty; + expect(loadedPosts[0].categoriesWithNonPrimaryColumns[0].code).to.be.equal(1); + expect(loadedPosts[0].categoriesWithNonPrimaryColumns[0].version).to.be.equal(1); + expect(loadedPosts[0].categoriesWithNonPrimaryColumns[0].description).to.be.equal("category of cars"); + expect(loadedPosts[0].categoriesWithNonPrimaryColumns[1].code).to.be.equal(2); + expect(loadedPosts[0].categoriesWithNonPrimaryColumns[1].version).to.be.equal(1); + expect(loadedPosts[0].categoriesWithNonPrimaryColumns[1].description).to.be.equal("category of BMW"); + expect(loadedPosts[1].categoriesWithNonPrimaryColumns).to.not.be.empty; + expect(loadedPosts[1].categoriesWithNonPrimaryColumns[0].code).to.be.equal(3); + expect(loadedPosts[1].categoriesWithNonPrimaryColumns[0].version).to.be.equal(1); + expect(loadedPosts[1].categoriesWithNonPrimaryColumns[0].description).to.be.equal("category of airplanes"); + + const loadedPost = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoriesWithNonPrimaryColumns", "categories") + .orderBy("categories.code") + .where("post.id = :id", { id: 1 }) + .getOne(); + + expect(loadedPost!.categoriesWithNonPrimaryColumns).to.not.be.empty; + expect(loadedPost!.categoriesWithNonPrimaryColumns[0].code).to.be.equal(1); + expect(loadedPost!.categoriesWithNonPrimaryColumns[0].version).to.be.equal(1); + expect(loadedPost!.categoriesWithNonPrimaryColumns[0].description).to.be.equal("category of cars"); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinTable used without options", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "BMW"; + category2.type = "cars-category"; + category2.code = 2; + category2.version = 1; + await connection.entityManager.persist(category2); + + const category3 = new Category(); + category3.name = "airplanes"; + category3.type = "common-category"; + category3.code = 3; + category3.version = 1; + await connection.entityManager.persist(category3); + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + tag1.categories = [category1, category2]; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 2; + tag2.title = "About Boeing"; + tag2.description = "tag about Boeing"; + tag2.categories = [category3]; + await connection.entityManager.persist(tag2); + + const loadedTags = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.categories", "categories") + .orderBy("tag.code, categories.code") + .getMany(); + + expect(loadedTags[0].categories).to.not.be.empty; + expect(loadedTags[0].categories[0].name).to.be.equal("cars"); + expect(loadedTags[0].categories[0].type).to.be.equal("common-category"); + expect(loadedTags[0].categories[1].name).to.be.equal("BMW"); + expect(loadedTags[0].categories[1].type).to.be.equal("cars-category"); + expect(loadedTags[1].categories).to.not.be.empty; + expect(loadedTags[1].categories[0].name).to.be.equal("airplanes"); + expect(loadedTags[1].categories[0].type).to.be.equal("common-category"); + + const loadedTag = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.categories", "categories") + .orderBy("categories.code") + .where("tag.code = :code", { code: 1 }) + .getOne(); + + expect(loadedTag!.categories).to.not.be.empty; + expect(loadedTag!.categories[0].name).to.be.equal("cars"); + expect(loadedTag!.categories[0].type).to.be.equal("common-category"); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinTable used with options", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "BMW"; + category2.type = "cars-category"; + category2.code = 2; + category2.version = 1; + await connection.entityManager.persist(category2); + + const category3 = new Category(); + category3.name = "airplanes"; + category3.type = "common-category"; + category3.code = 3; + category3.version = 1; + await connection.entityManager.persist(category3); + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + tag1.categoriesWithOptions = [category1, category2]; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 2; + tag2.title = "About Boeing"; + tag2.description = "Tag about Boeing"; + tag2.categoriesWithOptions = [category3]; + await connection.entityManager.persist(tag2); + + const loadedTags = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.categoriesWithOptions", "categories") + .orderBy("tag.code, categories.code") + .getMany(); + + expect(loadedTags[0].categoriesWithOptions).to.not.be.empty; + expect(loadedTags[0].categoriesWithOptions[0].name).to.be.equal("cars"); + expect(loadedTags[0].categoriesWithOptions[0].type).to.be.equal("common-category"); + expect(loadedTags[0].categoriesWithOptions[1].name).to.be.equal("BMW"); + expect(loadedTags[0].categoriesWithOptions[1].type).to.be.equal("cars-category"); + expect(loadedTags[1].categoriesWithOptions).to.not.be.empty; + expect(loadedTags[1].categoriesWithOptions[0].name).to.be.equal("airplanes"); + expect(loadedTags[1].categoriesWithOptions[0].type).to.be.equal("common-category"); + + const loadedTag = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.categoriesWithOptions", "categories") + .orderBy("categories.code") + .where("tag.code = :code", { code: 1 }) + .getOne(); + + expect(loadedTag!.categoriesWithOptions).to.not.be.empty; + expect(loadedTag!.categoriesWithOptions[0].name).to.be.equal("cars"); + expect(loadedTag!.categoriesWithOptions[0].type).to.be.equal("common-category"); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinTable references with non-primary columns", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.description = "category of cars"; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "BMW"; + category2.type = "cars-category"; + category2.code = 2; + category2.version = 1; + category2.description = "category of BMW"; + await connection.entityManager.persist(category2); + + const category3 = new Category(); + category3.name = "airplanes"; + category3.type = "common-category"; + category3.code = 3; + category3.version = 1; + category3.description = "category of airplanes"; + await connection.entityManager.persist(category3); + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + tag1.categoriesWithNonPrimaryColumns = [category1, category2]; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 2; + tag2.title = "About Boeing"; + tag2.description = "Tag about Boeing"; + tag2.categoriesWithNonPrimaryColumns = [category3]; + await connection.entityManager.persist(tag2); + + const loadedTags = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.categoriesWithNonPrimaryColumns", "categories") + .orderBy("tag.code, categories.code") + .getMany(); + + expect(loadedTags[0].categoriesWithNonPrimaryColumns).to.not.be.empty; + expect(loadedTags[0].categoriesWithNonPrimaryColumns[0].code).to.be.equal(1); + expect(loadedTags[0].categoriesWithNonPrimaryColumns[0].version).to.be.equal(1); + expect(loadedTags[0].categoriesWithNonPrimaryColumns[0].description).to.be.equal("category of cars"); + expect(loadedTags[0].categoriesWithNonPrimaryColumns[1].code).to.be.equal(2); + expect(loadedTags[0].categoriesWithNonPrimaryColumns[1].version).to.be.equal(1); + expect(loadedTags[0].categoriesWithNonPrimaryColumns[1].description).to.be.equal("category of BMW"); + expect(loadedTags[1].categoriesWithNonPrimaryColumns).to.not.be.empty; + expect(loadedTags[1].categoriesWithNonPrimaryColumns[0].code).to.be.equal(3); + expect(loadedTags[1].categoriesWithNonPrimaryColumns[0].version).to.be.equal(1); + expect(loadedTags[1].categoriesWithNonPrimaryColumns[0].description).to.be.equal("category of airplanes"); + + const loadedTag = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.categoriesWithNonPrimaryColumns", "categories") + .orderBy("categories.code") + .where("tag.code = :code", { code: 1 }) + .getOne(); + + expect(loadedTag!.categoriesWithNonPrimaryColumns).to.not.be.empty; + expect(loadedTag!.categoriesWithNonPrimaryColumns[0].code).to.be.equal(1); + expect(loadedTag!.categoriesWithNonPrimaryColumns[0].version).to.be.equal(1); + expect(loadedTag!.categoriesWithNonPrimaryColumns[0].description).to.be.equal("category of cars"); + + }))); + + }); + + describe("inverse side", () => { + + it("should load related entity when JoinTable used without options", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.title = "About BMW"; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Audi"; + await connection.entityManager.persist(post2); + + const post3 = new Post(); + post3.title = "About Boeing"; + await connection.entityManager.persist(post3); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.posts = [post1, post2]; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.posts = [post3]; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.posts", "posts") + .orderBy("category.code, posts.id") + .getMany(); + + expect(loadedCategories[0].posts).to.not.be.empty; + expect(loadedCategories[0].posts[0].id).to.be.equal(1); + expect(loadedCategories[0].posts[1].id).to.be.equal(2); + expect(loadedCategories[1].posts).to.not.be.empty; + expect(loadedCategories[1].posts[0].id).to.be.equal(3); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.posts", "posts") + .orderBy("posts.id") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.posts).to.not.be.empty; + expect(loadedCategory!.posts[0].id).to.be.equal(1); + expect(loadedCategory!.posts[1].id).to.be.equal(2); + + }))); + + it("should load related entity when JoinTable used with options", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.title = "About BMW"; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Audi"; + await connection.entityManager.persist(post2); + + const post3 = new Post(); + post3.title = "About Boeing"; + await connection.entityManager.persist(post3); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.postsWithOptions = [post1, post2]; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.postsWithOptions = [post3]; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.postsWithOptions", "posts") + .orderBy("category.code, posts.id") + .getMany(); + + expect(loadedCategories[0].postsWithOptions).to.not.be.empty; + expect(loadedCategories[0].postsWithOptions[0].id).to.be.equal(1); + expect(loadedCategories[0].postsWithOptions[1].id).to.be.equal(2); + expect(loadedCategories[1].postsWithOptions).to.not.be.empty; + expect(loadedCategories[1].postsWithOptions[0].id).to.be.equal(3); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.postsWithOptions", "posts") + .orderBy("posts.id") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.postsWithOptions).to.not.be.empty; + expect(loadedCategory!.postsWithOptions[0].id).to.be.equal(1); + expect(loadedCategory!.postsWithOptions[1].id).to.be.equal(2); + + }))); + + it("should load related entity when JoinTable references with non-primary columns", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.title = "About BMW"; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Audi"; + await connection.entityManager.persist(post2); + + const post3 = new Post(); + post3.title = "About Boeing"; + await connection.entityManager.persist(post3); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.description = "category of cars"; + category1.postsWithNonPrimaryColumns = [post1, post2]; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.description = "category of airplanes"; + category2.postsWithNonPrimaryColumns = [post3]; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.postsWithNonPrimaryColumns", "posts") + .orderBy("category.code, posts.id") + .getMany(); + + expect(loadedCategories[0].postsWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategories[0].postsWithNonPrimaryColumns[0].id).to.be.equal(1); + expect(loadedCategories[0].postsWithNonPrimaryColumns[1].id).to.be.equal(2); + expect(loadedCategories[1].postsWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategories[1].postsWithNonPrimaryColumns[0].id).to.be.equal(3); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.postsWithNonPrimaryColumns", "posts") + .orderBy("posts.id") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.postsWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategory!.postsWithNonPrimaryColumns[0].id).to.be.equal(1); + expect(loadedCategory!.postsWithNonPrimaryColumns[1].id).to.be.equal(2); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinTable used without options", () => Promise.all(connections.map(async connection => { + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 2; + tag2.title = "About Audi"; + tag2.description = "Tag about Audi"; + await connection.entityManager.persist(tag2); + + const tag3 = new Tag(); + tag3.code = 3; + tag3.title = "About Boeing"; + tag3.description = "tag about Boeing"; + await connection.entityManager.persist(tag3); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.tags = [tag1, tag2]; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.tags = [tag3]; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tags", "tags") + .orderBy("category.code, tags.code") + .getMany(); + + expect(loadedCategories[0].tags).to.not.be.empty; + expect(loadedCategories[0].tags[0].title).to.be.equal("About BMW"); + expect(loadedCategories[0].tags[0].description).to.be.equal("Tag about BMW"); + expect(loadedCategories[0].tags[1].title).to.be.equal("About Audi"); + expect(loadedCategories[0].tags[1].description).to.be.equal("Tag about Audi"); + expect(loadedCategories[1].tags).to.not.be.empty; + expect(loadedCategories[1].tags[0].title).to.be.equal("About Boeing"); + expect(loadedCategories[1].tags[0].description).to.be.equal("tag about Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tags", "tags") + .orderBy("tags.code") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.tags).to.not.be.empty; + expect(loadedCategory!.tags[0].title).to.be.equal("About BMW"); + expect(loadedCategory!.tags[0].description).to.be.equal("Tag about BMW"); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinTable used with options", () => Promise.all(connections.map(async connection => { + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 2; + tag2.title = "About Audi"; + tag2.description = "Tag about Audi"; + await connection.entityManager.persist(tag2); + + const tag3 = new Tag(); + tag3.code = 3; + tag3.title = "About Boeing"; + tag3.description = "tag about Boeing"; + await connection.entityManager.persist(tag3); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.tagsWithOptions = [tag1, tag2]; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.tagsWithOptions = [tag3]; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagsWithOptions", "tags") + .orderBy("category.code, tags.code") + .getMany(); + + expect(loadedCategories[0].tagsWithOptions).to.not.be.empty; + expect(loadedCategories[0].tagsWithOptions[0].title).to.be.equal("About BMW"); + expect(loadedCategories[0].tagsWithOptions[0].description).to.be.equal("Tag about BMW"); + expect(loadedCategories[0].tagsWithOptions[1].title).to.be.equal("About Audi"); + expect(loadedCategories[0].tagsWithOptions[1].description).to.be.equal("Tag about Audi"); + expect(loadedCategories[1].tagsWithOptions).to.not.be.empty; + expect(loadedCategories[1].tagsWithOptions[0].title).to.be.equal("About Boeing"); + expect(loadedCategories[1].tagsWithOptions[0].description).to.be.equal("tag about Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagsWithOptions", "tags") + .orderBy("tags.code") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.tagsWithOptions).to.not.be.empty; + expect(loadedCategory!.tagsWithOptions[0].title).to.be.equal("About BMW"); + expect(loadedCategory!.tagsWithOptions[0].description).to.be.equal("Tag about BMW"); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinTable references with non-primary columns", () => Promise.all(connections.map(async connection => { + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 2; + tag2.title = "About Audi"; + tag2.description = "Tag about Audi"; + await connection.entityManager.persist(tag2); + + const tag3 = new Tag(); + tag3.code = 3; + tag3.title = "About Boeing"; + tag3.description = "tag about Boeing"; + await connection.entityManager.persist(tag3); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.description = "category of cars"; + category1.tagsWithNonPrimaryColumns = [tag1, tag2]; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.description = "category of airplanes"; + category2.tagsWithNonPrimaryColumns = [tag3]; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagsWithNonPrimaryColumns", "tags") + .orderBy("category.code, tags.code") + .getMany(); + + expect(loadedCategories[0].tagsWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategories[0].tagsWithNonPrimaryColumns[0].title).to.be.equal("About BMW"); + expect(loadedCategories[0].tagsWithNonPrimaryColumns[0].description).to.be.equal("Tag about BMW"); + expect(loadedCategories[0].tagsWithNonPrimaryColumns[1].title).to.be.equal("About Audi"); + expect(loadedCategories[0].tagsWithNonPrimaryColumns[1].description).to.be.equal("Tag about Audi"); + expect(loadedCategories[1].tagsWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategories[1].tagsWithNonPrimaryColumns[0].title).to.be.equal("About Boeing"); + expect(loadedCategories[1].tagsWithNonPrimaryColumns[0].description).to.be.equal("tag about Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagsWithNonPrimaryColumns", "tags") + .orderBy("tags.code") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.tagsWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategory!.tagsWithNonPrimaryColumns[0].title).to.be.equal("About BMW"); + expect(loadedCategory!.tagsWithNonPrimaryColumns[0].description).to.be.equal("Tag about BMW"); + + }))); + + }); + +}); \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-one/entity/Category.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-one/entity/Category.ts new file mode 100644 index 000000000..b94ee5798 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-one/entity/Category.ts @@ -0,0 +1,39 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn"; +import {Column} from "../../../../../../src/decorator/columns/Column"; +import {Index} from "../../../../../../src/decorator/Index"; +import {OneToMany} from "../../../../../../src/decorator/relations/OneToMany"; +import {Post} from "./Post"; + +@Entity() +@Index(["code", "version", "description"], { unique: true }) +export class Category { + + @PrimaryColumn() + name: string; + + @PrimaryColumn() + type: string; + + @Column() + code: number; + + @Column() + version: number; + + @Column({nullable: true}) + description: string; + + @OneToMany(type => Post, post => post.category) + posts: Post[]; + + @OneToMany(type => Post, post => post.categoryWithEmptyJoinColumn) + postsWithEmptyJoinColumn: Post[]; + + @OneToMany(type => Post, post => post.categoryWithOptions) + postsWithOptions: Post[]; + + @OneToMany(type => Post, post => post.categoryWithNonPrimaryColumns) + postsWithNonPrimaryColumns: Post[]; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-one/entity/Post.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-one/entity/Post.ts new file mode 100644 index 000000000..f818829e8 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-one/entity/Post.ts @@ -0,0 +1,39 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {Column} from "../../../../../../src/decorator/columns/Column"; +import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {ManyToOne} from "../../../../../../src/decorator/relations/ManyToOne"; +import {JoinColumn} from "../../../../../../src/decorator/relations/JoinColumn"; +import {Category} from "./Category"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @ManyToOne(type => Category) + category: Category; + + @ManyToOne(type => Category) + @JoinColumn() + categoryWithEmptyJoinColumn: Category; + + @ManyToOne(type => Category) + @JoinColumn([ + { name: "category_name", referencedColumnName: "name" }, + { name: "category_type", referencedColumnName: "type" } + ]) + categoryWithOptions: Category; + + @ManyToOne(type => Category) + @JoinColumn([ + { name: "category_code", referencedColumnName: "code" }, + { name: "category_version", referencedColumnName: "version" }, + { name: "category_description", referencedColumnName: "description" } + ]) + categoryWithNonPrimaryColumns: Category; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-one/multiple-primary-keys-many-to-one.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-one/multiple-primary-keys-many-to-one.ts new file mode 100644 index 000000000..c6afd8e96 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-many-to-one/multiple-primary-keys-many-to-one.ts @@ -0,0 +1,479 @@ +import "reflect-metadata"; +import * as chai from "chai"; +import {expect} from "chai"; +import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; +import {Connection} from "../../../../../src/connection/Connection"; +import {Post} from "./entity/Post"; +import {Category} from "./entity/Category"; + +const should = chai.should(); + +describe("relations > multiple-primary-keys > many-to-one", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + describe("owning side", () => { + + it("should load related entity when JoinColumn is not specified", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + await connection.entityManager.persist(category2); + + const post1 = new Post(); + post1.title = "About BMW"; + post1.category = category1; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Boeing"; + post2.category = category2; + await connection.entityManager.persist(post2); + + const loadedPosts = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.category", "category") + .orderBy("post.id") + .getMany(); + + expect(loadedPosts[0].category).to.not.be.empty; + expect(loadedPosts[0].category.name).to.be.equal("cars"); + expect(loadedPosts[0].category.type).to.be.equal("common-category"); + expect(loadedPosts[1].category).to.not.be.empty; + expect(loadedPosts[1].category.name).to.be.equal("airplanes"); + expect(loadedPosts[1].category.type).to.be.equal("common-category"); + + const loadedPost = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.category", "category") + .where("post.id = :id", {id: 1}) + .getOne(); + + expect(loadedPost!.category).to.not.be.empty; + expect(loadedPost!.category.name).to.be.equal("cars"); + expect(loadedPost!.category.type).to.be.equal("common-category"); + + }))); + + it("should load related entity when JoinColumn is specified without options", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + await connection.entityManager.persist(category2); + + const post1 = new Post(); + post1.title = "About BMW"; + post1.categoryWithEmptyJoinColumn = category1; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Boeing"; + post2.categoryWithEmptyJoinColumn = category2; + await connection.entityManager.persist(post2); + + const loadedPosts = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoryWithEmptyJoinColumn", "category") + .orderBy("post.id") + .getMany(); + + expect(loadedPosts[0].categoryWithEmptyJoinColumn).to.not.be.empty; + expect(loadedPosts[0].categoryWithEmptyJoinColumn.name).to.be.equal("cars"); + expect(loadedPosts[0].categoryWithEmptyJoinColumn.type).to.be.equal("common-category"); + expect(loadedPosts[1].categoryWithEmptyJoinColumn).to.not.be.empty; + expect(loadedPosts[1].categoryWithEmptyJoinColumn.name).to.be.equal("airplanes"); + expect(loadedPosts[1].categoryWithEmptyJoinColumn.type).to.be.equal("common-category"); + + const loadedPost = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoryWithEmptyJoinColumn", "category") + .where("post.id = :id", {id: 1}) + .getOne(); + + expect(loadedPost!.categoryWithEmptyJoinColumn).to.not.be.empty; + expect(loadedPost!.categoryWithEmptyJoinColumn.name).to.be.equal("cars"); + expect(loadedPost!.categoryWithEmptyJoinColumn.type).to.be.equal("common-category"); + + }))); + + it("should load related entity when JoinColumn is specified with options", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + await connection.entityManager.persist(category2); + + const post1 = new Post(); + post1.title = "About BMW"; + post1.categoryWithOptions = category1; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Boeing"; + post2.categoryWithOptions = category2; + await connection.entityManager.persist(post2); + + const loadedPosts = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoryWithOptions", "category") + .orderBy("post.id") + .getMany(); + + expect(loadedPosts[0].categoryWithOptions).to.not.be.empty; + expect(loadedPosts[0].categoryWithOptions.name).to.be.equal("cars"); + expect(loadedPosts[0].categoryWithOptions.type).to.be.equal("common-category"); + expect(loadedPosts[1].categoryWithOptions).to.not.be.empty; + expect(loadedPosts[1].categoryWithOptions.name).to.be.equal("airplanes"); + expect(loadedPosts[1].categoryWithOptions.type).to.be.equal("common-category"); + + const loadedPost = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoryWithOptions", "category") + .where("post.id = :id", {id: 1}) + .getOne(); + + expect(loadedPost!.categoryWithOptions).to.not.be.empty; + expect(loadedPost!.categoryWithOptions.name).to.be.equal("cars"); + expect(loadedPost!.categoryWithOptions.type).to.be.equal("common-category"); + + }))); + + it("should load related entity when JoinColumn references on to non-primary columns", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.description = "category about cars"; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.description = "category about airplanes"; + await connection.entityManager.persist(category2); + + const post1 = new Post(); + post1.title = "About BMW"; + post1.categoryWithNonPrimaryColumns = category1; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Boeing"; + post2.categoryWithNonPrimaryColumns = category2; + await connection.entityManager.persist(post2); + + const loadedPosts = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoryWithNonPrimaryColumns", "category") + .orderBy("post.id") + .getMany(); + + expect(loadedPosts[0].categoryWithNonPrimaryColumns).to.not.be.empty; + expect(loadedPosts[0].categoryWithNonPrimaryColumns.code).to.be.equal(1); + expect(loadedPosts[0].categoryWithNonPrimaryColumns.version).to.be.equal(1); + expect(loadedPosts[0].categoryWithNonPrimaryColumns.description).to.be.equal("category about cars"); + expect(loadedPosts[1].categoryWithNonPrimaryColumns).to.not.be.empty; + expect(loadedPosts[1].categoryWithNonPrimaryColumns.code).to.be.equal(2); + expect(loadedPosts[1].categoryWithNonPrimaryColumns.version).to.be.equal(1); + + const loadedPost = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoryWithNonPrimaryColumns", "category") + .where("post.id = :id", {id: 1}) + .getOne(); + + expect(loadedPost!.categoryWithNonPrimaryColumns).to.not.be.empty; + expect(loadedPost!.categoryWithNonPrimaryColumns.code).to.be.equal(1); + expect(loadedPost!.categoryWithNonPrimaryColumns.version).to.be.equal(1); + expect(loadedPost!.categoryWithNonPrimaryColumns.description).to.be.equal("category about cars"); + + }))); + }); + + describe("inverse side", () => { + + it("should load related entity when JoinColumn is not specified", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.title = "About BMW"; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Audi"; + await connection.entityManager.persist(post2); + + const post3 = new Post(); + post3.title = "About Boeing"; + await connection.entityManager.persist(post3); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.posts = [post1, post2]; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.posts = [post3]; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.posts", "posts") + .orderBy("category.code, posts.id") + .getMany(); + + expect(loadedCategories[0].posts).to.not.be.empty; + expect(loadedCategories[0].posts[0].id).to.be.equal(1); + expect(loadedCategories[0].posts[0].title).to.be.equal("About BMW"); + expect(loadedCategories[0].posts[1].id).to.be.equal(2); + expect(loadedCategories[0].posts[1].title).to.be.equal("About Audi"); + expect(loadedCategories[1].posts).to.not.be.empty; + expect(loadedCategories[1].posts[0].id).to.be.equal(3); + expect(loadedCategories[1].posts[0].title).to.be.equal("About Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.posts", "posts") + .orderBy("posts.id") + .where("category.code = :code", {code: 1}) + .getOne(); + + expect(loadedCategory!.posts).to.not.be.empty; + expect(loadedCategory!.posts[0].id).to.be.equal(1); + expect(loadedCategory!.posts[0].title).to.be.equal("About BMW"); + expect(loadedCategory!.posts[1].id).to.be.equal(2); + expect(loadedCategory!.posts[1].title).to.be.equal("About Audi"); + + }))); + + it("should load related entity when JoinColumn is specified without options", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.title = "About BMW"; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Audi"; + await connection.entityManager.persist(post2); + + const post3 = new Post(); + post3.title = "About Boeing"; + await connection.entityManager.persist(post3); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.postsWithEmptyJoinColumn = [post1, post2]; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.postsWithEmptyJoinColumn = [post3]; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.postsWithEmptyJoinColumn", "posts") + .orderBy("category.code, posts.id") + .getMany(); + + expect(loadedCategories[0].postsWithEmptyJoinColumn).to.not.be.empty; + expect(loadedCategories[0].postsWithEmptyJoinColumn[0].id).to.be.equal(1); + expect(loadedCategories[0].postsWithEmptyJoinColumn[0].title).to.be.equal("About BMW"); + expect(loadedCategories[0].postsWithEmptyJoinColumn[1].id).to.be.equal(2); + expect(loadedCategories[0].postsWithEmptyJoinColumn[1].title).to.be.equal("About Audi"); + expect(loadedCategories[1].postsWithEmptyJoinColumn).to.not.be.empty; + expect(loadedCategories[1].postsWithEmptyJoinColumn[0].id).to.be.equal(3); + expect(loadedCategories[1].postsWithEmptyJoinColumn[0].title).to.be.equal("About Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.postsWithEmptyJoinColumn", "posts") + .orderBy("posts.id") + .where("category.code = :code", {code: 1}) + .getOne(); + + expect(loadedCategory!.postsWithEmptyJoinColumn).to.not.be.empty; + expect(loadedCategory!.postsWithEmptyJoinColumn[0].id).to.be.equal(1); + expect(loadedCategory!.postsWithEmptyJoinColumn[0].title).to.be.equal("About BMW"); + expect(loadedCategory!.postsWithEmptyJoinColumn[1].id).to.be.equal(2); + expect(loadedCategory!.postsWithEmptyJoinColumn[1].title).to.be.equal("About Audi"); + + }))); + + it("should load related entity when JoinColumn is specified with options", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.title = "About BMW"; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Audi"; + await connection.entityManager.persist(post2); + + const post3 = new Post(); + post3.title = "About Boeing"; + await connection.entityManager.persist(post3); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.postsWithOptions = [post1, post2]; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.postsWithOptions = [post3]; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.postsWithOptions", "posts") + .orderBy("category.code, posts.id") + .getMany(); + + expect(loadedCategories[0].postsWithOptions).to.not.be.empty; + expect(loadedCategories[0].postsWithOptions[0].id).to.be.equal(1); + expect(loadedCategories[0].postsWithOptions[0].title).to.be.equal("About BMW"); + expect(loadedCategories[0].postsWithOptions[1].id).to.be.equal(2); + expect(loadedCategories[0].postsWithOptions[1].title).to.be.equal("About Audi"); + expect(loadedCategories[1].postsWithOptions).to.not.be.empty; + expect(loadedCategories[1].postsWithOptions[0].id).to.be.equal(3); + expect(loadedCategories[1].postsWithOptions[0].title).to.be.equal("About Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.postsWithOptions", "posts") + .orderBy("posts.id") + .where("category.code = :code", {code: 1}) + .getOne(); + + expect(loadedCategory!.postsWithOptions).to.not.be.empty; + expect(loadedCategory!.postsWithOptions[0].id).to.be.equal(1); + expect(loadedCategory!.postsWithOptions[0].title).to.be.equal("About BMW"); + expect(loadedCategory!.postsWithOptions[1].id).to.be.equal(2); + expect(loadedCategory!.postsWithOptions[1].title).to.be.equal("About Audi"); + + }))); + + it("should load related entity when JoinColumn references on to non-primary columns", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.title = "About BMW"; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Audi"; + await connection.entityManager.persist(post2); + + const post3 = new Post(); + post3.title = "About Boeing"; + await connection.entityManager.persist(post3); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.description = "category of cars"; + category1.postsWithNonPrimaryColumns = [post1, post2]; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.description = "category of airplanes"; + category2.postsWithNonPrimaryColumns = [post3]; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.postsWithNonPrimaryColumns", "posts") + .orderBy("category.code, posts.id") + .getMany(); + + expect(loadedCategories[0].postsWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategories[0].postsWithNonPrimaryColumns[0].id).to.be.equal(1); + expect(loadedCategories[0].postsWithNonPrimaryColumns[0].title).to.be.equal("About BMW"); + expect(loadedCategories[0].postsWithNonPrimaryColumns[1].id).to.be.equal(2); + expect(loadedCategories[0].postsWithNonPrimaryColumns[1].title).to.be.equal("About Audi"); + expect(loadedCategories[1].postsWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategories[1].postsWithNonPrimaryColumns[0].id).to.be.equal(3); + expect(loadedCategories[1].postsWithNonPrimaryColumns[0].title).to.be.equal("About Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.postsWithNonPrimaryColumns", "posts") + .orderBy("posts.id") + .where("category.code = :code", {code: 1}) + .getOne(); + + expect(loadedCategory!.postsWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategory!.postsWithNonPrimaryColumns[0].id).to.be.equal(1); + expect(loadedCategory!.postsWithNonPrimaryColumns[0].title).to.be.equal("About BMW"); + expect(loadedCategory!.postsWithNonPrimaryColumns[1].id).to.be.equal(2); + expect(loadedCategory!.postsWithNonPrimaryColumns[1].title).to.be.equal("About Audi"); + + }))); + + }); + +}); \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/entity/Category.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/entity/Category.ts new file mode 100644 index 000000000..85ee4a6f1 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/entity/Category.ts @@ -0,0 +1,46 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn"; +import {Column} from "../../../../../../src/decorator/columns/Column"; +import {Index} from "../../../../../../src/decorator/Index"; +import {OneToOne} from "../../../../../../src/decorator/relations/OneToOne"; +import {Post} from "./Post"; +import {Tag} from "./Tag"; + +@Entity() +@Index(["code", "version", "description"], { unique: true }) +export class Category { + + @PrimaryColumn() + name: string; + + @PrimaryColumn() + type: string; + + @Column() + code: number; + + @Column() + version: number; + + @Column({nullable: true}) + description: string; + + @OneToOne(type => Post, post => post.category) + post: Post; + + @OneToOne(type => Post, post => post.categoryWithOptions) + postWithOptions: Post; + + @OneToOne(type => Post, post => post.categoryWithNonPrimaryColumns) + postWithNonPrimaryColumns: Post; + + @OneToOne(type => Tag, tag => tag.category) + tag: Tag; + + @OneToOne(type => Tag, tag => tag.categoryWithOptions) + tagWithOptions: Tag; + + @OneToOne(type => Tag, tag => tag.categoryWithNonPrimaryColumns) + tagWithNonPrimaryColumns: Tag; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/entity/Post.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/entity/Post.ts new file mode 100644 index 000000000..b631082d5 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/entity/Post.ts @@ -0,0 +1,36 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {Column} from "../../../../../../src/decorator/columns/Column"; +import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {JoinColumn} from "../../../../../../src/decorator/relations/JoinColumn"; +import {OneToOne} from "../../../../../../src/decorator/relations/OneToOne"; +import {Category} from "./Category"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @OneToOne(type => Category, category => category.post) + @JoinColumn() + category: Category; + + @OneToOne(type => Category, category => category.postWithOptions) + @JoinColumn([ + { name: "category_name", referencedColumnName: "name" }, + { name: "category_type", referencedColumnName: "type" } + ]) + categoryWithOptions: Category; + + @OneToOne(type => Category, category => category.postWithNonPrimaryColumns) + @JoinColumn([ + { name: "category_code", referencedColumnName: "code" }, + { name: "category_version", referencedColumnName: "version" }, + { name: "category_description", referencedColumnName: "description" } + ]) + categoryWithNonPrimaryColumns: Category; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/entity/Tag.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/entity/Tag.ts new file mode 100644 index 000000000..8b4834e4c --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/entity/Tag.ts @@ -0,0 +1,39 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {PrimaryColumn} from "../../../../../../src/decorator/columns/PrimaryColumn"; +import {Column} from "../../../../../../src/decorator/columns/Column"; +import {OneToOne} from "../../../../../../src/decorator/relations/OneToOne"; +import {JoinColumn} from "../../../../../../src/decorator/relations/JoinColumn"; +import {Category} from "./Category"; + +@Entity() +export class Tag { + + @Column() + code: number; + + @PrimaryColumn() + title: string; + + @PrimaryColumn() + description: string; + + @OneToOne(type => Category, category => category.tag) + @JoinColumn() + category: Category; + + @OneToOne(type => Category, category => category.tagWithOptions) + @JoinColumn([ + { name: "category_name", referencedColumnName: "name" }, + { name: "category_type", referencedColumnName: "type" } + ]) + categoryWithOptions: Category; + + @OneToOne(type => Category, category => category.tagWithNonPrimaryColumns) + @JoinColumn([ + { name: "category_code", referencedColumnName: "code" }, + { name: "category_version", referencedColumnName: "version" }, + { name: "category_description", referencedColumnName: "description" } + ]) + categoryWithNonPrimaryColumns: Category; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/multiple-primary-keys-one-to-one.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/multiple-primary-keys-one-to-one.ts new file mode 100644 index 000000000..8d4d8f564 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-one-to-one/multiple-primary-keys-one-to-one.ts @@ -0,0 +1,691 @@ +import "reflect-metadata"; +import * as chai from "chai"; +import {expect} from "chai"; +import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; +import {Connection} from "../../../../../src/connection/Connection"; +import {Category} from "./entity/Category"; +import {Post} from "./entity/Post"; +import {Tag} from "./entity/Tag"; + +const should = chai.should(); + +describe("relations > multiple-primary-keys > one-to-one", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + describe("owning side", () => { + + it("should load related entity when JoinColumn is specified without options", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + await connection.entityManager.persist(category2); + + const post1 = new Post(); + post1.title = "About cars #1"; + post1.category = category1; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About cars #2"; + post2.category = category2; + await connection.entityManager.persist(post2); + + const loadedPosts = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.category", "category") + .orderBy("post.id") + .getMany(); + + expect(loadedPosts[0].category).to.not.be.empty; + expect(loadedPosts[0].category.name).to.be.equal("cars"); + expect(loadedPosts[0].category.type).to.be.equal("common-category"); + expect(loadedPosts[1].category).to.not.be.empty; + expect(loadedPosts[1].category.name).to.be.equal("airplanes"); + expect(loadedPosts[1].category.type).to.be.equal("common-category"); + + const loadedPost = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.category", "category") + .where("post.id = :id", {id: 1}) + .getOne(); + + expect(loadedPost!.category).to.not.be.empty; + expect(loadedPost!.category.name).to.be.equal("cars"); + expect(loadedPost!.category.type).to.be.equal("common-category"); + + }))); + + it("should load related entity when JoinColumn is specified with options", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + await connection.entityManager.persist(category2); + + const post1 = new Post(); + post1.title = "About cars #1"; + post1.categoryWithOptions = category1; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About cars #2"; + post2.categoryWithOptions = category2; + await connection.entityManager.persist(post2); + + const loadedPosts = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoryWithOptions", "category") + .orderBy("post.id") + .getMany(); + + expect(loadedPosts[0].categoryWithOptions).to.not.be.empty; + expect(loadedPosts[0].categoryWithOptions.name).to.be.equal("cars"); + expect(loadedPosts[0].categoryWithOptions.type).to.be.equal("common-category"); + expect(loadedPosts[1].categoryWithOptions).to.not.be.empty; + expect(loadedPosts[1].categoryWithOptions.name).to.be.equal("airplanes"); + expect(loadedPosts[1].categoryWithOptions.type).to.be.equal("common-category"); + + const loadedPost = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoryWithOptions", "category") + .where("post.id = :id", { id: 1 }) + .getOne(); + + expect(loadedPost!.categoryWithOptions).to.not.be.empty; + expect(loadedPost!.categoryWithOptions.name).to.be.equal("cars"); + expect(loadedPost!.categoryWithOptions.type).to.be.equal("common-category"); + + }))); + + it("should load related entity when JoinColumn references on to non-primary columns", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.description = "category about cars"; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.description = "category about airplanes"; + await connection.entityManager.persist(category2); + + const post1 = new Post(); + post1.title = "About cars #1"; + post1.categoryWithNonPrimaryColumns = category1; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About cars #2"; + post2.categoryWithNonPrimaryColumns = category2; + await connection.entityManager.persist(post2); + + const loadedPosts = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoryWithNonPrimaryColumns", "category") + .orderBy("post.id") + .getMany(); + + expect(loadedPosts[0].categoryWithNonPrimaryColumns).to.not.be.empty; + expect(loadedPosts[0].categoryWithNonPrimaryColumns.code).to.be.equal(1); + expect(loadedPosts[0].categoryWithNonPrimaryColumns.version).to.be.equal(1); + expect(loadedPosts[0].categoryWithNonPrimaryColumns.description).to.be.equal("category about cars"); + expect(loadedPosts[1].categoryWithNonPrimaryColumns).to.not.be.empty; + expect(loadedPosts[1].categoryWithNonPrimaryColumns.code).to.be.equal(2); + expect(loadedPosts[1].categoryWithNonPrimaryColumns.version).to.be.equal(1); + + const loadedPost = await connection.entityManager + .createQueryBuilder(Post, "post") + .leftJoinAndSelect("post.categoryWithNonPrimaryColumns", "category") + .where("post.id = :id", { id: 1 }) + .getOne(); + + expect(loadedPost!.categoryWithNonPrimaryColumns).to.not.be.empty; + expect(loadedPost!.categoryWithNonPrimaryColumns.code).to.be.equal(1); + expect(loadedPost!.categoryWithNonPrimaryColumns.version).to.be.equal(1); + expect(loadedPost!.categoryWithNonPrimaryColumns.description).to.be.equal("category about cars"); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinColumn defined without options", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + await connection.entityManager.persist(category2); + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + tag1.category = category1; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 3; + tag2.title = "About Boeing"; + tag2.description = "tag about Boeing"; + tag2.category = category2; + await connection.entityManager.persist(tag2); + + const loadedTags = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.category", "category") + .orderBy("tag.code, category.code") + .getMany(); + + expect(loadedTags[0].category).to.not.be.empty; + expect(loadedTags[0].category.name).to.be.equal("cars"); + expect(loadedTags[0].category.type).to.be.equal("common-category"); + expect(loadedTags[1].category).to.not.be.empty; + expect(loadedTags[1].category.name).to.be.equal("airplanes"); + expect(loadedTags[1].category.type).to.be.equal("common-category"); + + const loadedTag = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.category", "category") + .orderBy("category.code") + .where("tag.code = :code", { code: 1 }) + .getOne(); + + expect(loadedTag!.category).to.not.be.empty; + expect(loadedTag!.category.name).to.be.equal("cars"); + expect(loadedTag!.category.type).to.be.equal("common-category"); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinColumn defined with options", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + await connection.entityManager.persist(category2); + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + tag1.categoryWithOptions = category1; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 3; + tag2.title = "About Boeing"; + tag2.description = "tag about Boeing"; + tag2.categoryWithOptions = category2; + await connection.entityManager.persist(tag2); + + const loadedTags = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.categoryWithOptions", "category") + .orderBy("tag.code, category.code") + .getMany(); + + expect(loadedTags[0].categoryWithOptions).to.not.be.empty; + expect(loadedTags[0].categoryWithOptions.name).to.be.equal("cars"); + expect(loadedTags[0].categoryWithOptions.type).to.be.equal("common-category"); + expect(loadedTags[1].categoryWithOptions).to.not.be.empty; + expect(loadedTags[1].categoryWithOptions.name).to.be.equal("airplanes"); + expect(loadedTags[1].categoryWithOptions.type).to.be.equal("common-category"); + + const loadedTag = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.categoryWithOptions", "category") + .orderBy("category.code") + .where("tag.code = :code", { code: 1 }) + .getOne(); + + expect(loadedTag!.categoryWithOptions).to.not.be.empty; + expect(loadedTag!.categoryWithOptions.name).to.be.equal("cars"); + expect(loadedTag!.categoryWithOptions.type).to.be.equal("common-category"); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinColumn references on to non-primary columns", () => Promise.all(connections.map(async connection => { + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.description = "category of cars"; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.description = "category of airplanes"; + await connection.entityManager.persist(category2); + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + tag1.categoryWithNonPrimaryColumns = category1; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 3; + tag2.title = "About Boeing"; + tag2.description = "tag about Boeing"; + tag2.categoryWithNonPrimaryColumns = category2; + await connection.entityManager.persist(tag2); + + const loadedTags = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.categoryWithNonPrimaryColumns", "category") + .orderBy("tag.code, category.code") + .getMany(); + + expect(loadedTags[0].categoryWithNonPrimaryColumns).to.not.be.empty; + expect(loadedTags[0].categoryWithNonPrimaryColumns.name).to.be.equal("cars"); + expect(loadedTags[0].categoryWithNonPrimaryColumns.type).to.be.equal("common-category"); + expect(loadedTags[1].categoryWithNonPrimaryColumns).to.not.be.empty; + expect(loadedTags[1].categoryWithNonPrimaryColumns.name).to.be.equal("airplanes"); + expect(loadedTags[1].categoryWithNonPrimaryColumns.type).to.be.equal("common-category"); + + const loadedTag = await connection.entityManager + .createQueryBuilder(Tag, "tag") + .leftJoinAndSelect("tag.categoryWithNonPrimaryColumns", "category") + .orderBy("category.code") + .where("tag.code = :code", { code: 1 }) + .getOne(); + + expect(loadedTag!.categoryWithNonPrimaryColumns).to.not.be.empty; + expect(loadedTag!.categoryWithNonPrimaryColumns.name).to.be.equal("cars"); + expect(loadedTag!.categoryWithNonPrimaryColumns.type).to.be.equal("common-category"); + + }))); + + }); + + describe("inverse side", () => { + + it("should load related entity when JoinColumn is specified without options", () => Promise.all(connections.map(async connection => { + + const post1 = new Post(); + post1.title = "About BMW"; + await connection.entityManager.persist(post1); + + const post2 = new Post(); + post2.title = "About Boeing"; + await connection.entityManager.persist(post2); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.post = post1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.post = post2; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.post", "post") + .orderBy("category.code, post.id") + .getMany(); + + expect(loadedCategories[0].post).to.not.be.empty; + expect(loadedCategories[0].post.id).to.be.equal(1); + expect(loadedCategories[1].post).to.not.be.empty; + expect(loadedCategories[1].post.id).to.be.equal(2); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.post", "post") + .orderBy("post.id") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.post).to.not.be.empty; + expect(loadedCategory!.post.id).to.be.equal(1); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinColumn defined without options", () => Promise.all(connections.map(async connection => { + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 3; + tag2.title = "About Boeing"; + tag2.description = "tag about Boeing"; + await connection.entityManager.persist(tag2); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.tag = tag1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.tag = tag2; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tag", "tag") + .orderBy("category.code, tag.code") + .getMany(); + + expect(loadedCategories[0].tag).to.not.be.empty; + expect(loadedCategories[0].tag.title).to.be.equal("About BMW"); + expect(loadedCategories[0].tag.description).to.be.equal("Tag about BMW"); + expect(loadedCategories[1].tag).to.not.be.empty; + expect(loadedCategories[1].tag.title).to.be.equal("About Boeing"); + expect(loadedCategories[1].tag.description).to.be.equal("tag about Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tag", "tag") + .orderBy("tag.code") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.tag).to.not.be.empty; + expect(loadedCategory!.tag.title).to.be.equal("About BMW"); + expect(loadedCategory!.tag.description).to.be.equal("Tag about BMW"); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinColumn defined with options", () => Promise.all(connections.map(async connection => { + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 3; + tag2.title = "About Boeing"; + tag2.description = "tag about Boeing"; + await connection.entityManager.persist(tag2); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.tagWithOptions = tag1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.tagWithOptions = tag2; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagWithOptions", "tag") + .orderBy("category.code, tag.code") + .getMany(); + + expect(loadedCategories[0].tagWithOptions).to.not.be.empty; + expect(loadedCategories[0].tagWithOptions.title).to.be.equal("About BMW"); + expect(loadedCategories[0].tagWithOptions.description).to.be.equal("Tag about BMW"); + expect(loadedCategories[1].tagWithOptions).to.not.be.empty; + expect(loadedCategories[1].tagWithOptions.title).to.be.equal("About Boeing"); + expect(loadedCategories[1].tagWithOptions.description).to.be.equal("tag about Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagWithOptions", "tag") + .orderBy("tag.code") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.tagWithOptions).to.not.be.empty; + expect(loadedCategory!.tagWithOptions.title).to.be.equal("About BMW"); + expect(loadedCategory!.tagWithOptions.description).to.be.equal("Tag about BMW"); + + }))); + + it("should load related entity when JoinColumns references on to non-primary columns", () => Promise.all(connections.map(async connection => { + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 3; + tag2.title = "About Boeing"; + tag2.description = "tag about Boeing"; + await connection.entityManager.persist(tag2); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.description = "category of cars"; + category1.tagWithNonPrimaryColumns = tag1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.description = "category of airplanes"; + category2.tagWithNonPrimaryColumns = tag2; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagWithNonPrimaryColumns", "tag") + .orderBy("category.code, tag.code") + .getMany(); + + expect(loadedCategories[0].tagWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategories[0].tagWithNonPrimaryColumns.title).to.be.equal("About BMW"); + expect(loadedCategories[0].tagWithNonPrimaryColumns.description).to.be.equal("Tag about BMW"); + expect(loadedCategories[1].tagWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategories[1].tagWithNonPrimaryColumns.title).to.be.equal("About Boeing"); + expect(loadedCategories[1].tagWithNonPrimaryColumns.description).to.be.equal("tag about Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagWithNonPrimaryColumns", "tag") + .orderBy("tag.code") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.tagWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategory!.tagWithNonPrimaryColumns.title).to.be.equal("About BMW"); + expect(loadedCategory!.tagWithNonPrimaryColumns.description).to.be.equal("Tag about BMW"); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinColumn defined with options", () => Promise.all(connections.map(async connection => { + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 3; + tag2.title = "About Boeing"; + tag2.description = "tag about Boeing"; + await connection.entityManager.persist(tag2); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.tagWithOptions = tag1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.tagWithOptions = tag2; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagWithOptions", "tag") + .orderBy("category.code, tag.code") + .getMany(); + + expect(loadedCategories[0].tagWithOptions).to.not.be.empty; + expect(loadedCategories[0].tagWithOptions.title).to.be.equal("About BMW"); + expect(loadedCategories[0].tagWithOptions.description).to.be.equal("Tag about BMW"); + expect(loadedCategories[1].tagWithOptions).to.not.be.empty; + expect(loadedCategories[1].tagWithOptions.title).to.be.equal("About Boeing"); + expect(loadedCategories[1].tagWithOptions.description).to.be.equal("tag about Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagWithOptions", "tag") + .orderBy("tag.code") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.tagWithOptions).to.not.be.empty; + expect(loadedCategory!.tagWithOptions.title).to.be.equal("About BMW"); + expect(loadedCategory!.tagWithOptions.description).to.be.equal("Tag about BMW"); + + }))); + + it("should load related entity when both entities have multiple primary columns and JoinColumn references on to non-primary columns", () => Promise.all(connections.map(async connection => { + + const tag1 = new Tag(); + tag1.code = 1; + tag1.title = "About BMW"; + tag1.description = "Tag about BMW"; + await connection.entityManager.persist(tag1); + + const tag2 = new Tag(); + tag2.code = 3; + tag2.title = "About Boeing"; + tag2.description = "tag about Boeing"; + await connection.entityManager.persist(tag2); + + const category1 = new Category(); + category1.name = "cars"; + category1.type = "common-category"; + category1.code = 1; + category1.version = 1; + category1.description = "category of cars"; + category1.tagWithNonPrimaryColumns = tag1; + await connection.entityManager.persist(category1); + + const category2 = new Category(); + category2.name = "airplanes"; + category2.type = "common-category"; + category2.code = 2; + category2.version = 1; + category2.description = "category of airplanes"; + category2.tagWithNonPrimaryColumns = tag2; + await connection.entityManager.persist(category2); + + const loadedCategories = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagWithNonPrimaryColumns", "tag") + .orderBy("category.code, tag.code") + .getMany(); + + expect(loadedCategories[0].tagWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategories[0].tagWithNonPrimaryColumns.title).to.be.equal("About BMW"); + expect(loadedCategories[0].tagWithNonPrimaryColumns.description).to.be.equal("Tag about BMW"); + expect(loadedCategories[1].tagWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategories[1].tagWithNonPrimaryColumns.title).to.be.equal("About Boeing"); + expect(loadedCategories[1].tagWithNonPrimaryColumns.description).to.be.equal("tag about Boeing"); + + const loadedCategory = await connection.entityManager + .createQueryBuilder(Category, "category") + .leftJoinAndSelect("category.tagWithNonPrimaryColumns", "tag") + .orderBy("tag.code") + .where("category.code = :code", { code: 1 }) + .getOne(); + + expect(loadedCategory!.tagWithNonPrimaryColumns).to.not.be.empty; + expect(loadedCategory!.tagWithNonPrimaryColumns.title).to.be.equal("About BMW"); + expect(loadedCategory!.tagWithNonPrimaryColumns.description).to.be.equal("Tag about BMW"); + + }))); + + }); + +}); \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/Event.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/Event.ts new file mode 100644 index 000000000..86e471d77 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/Event.ts @@ -0,0 +1,22 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {OneToMany} from "../../../../../../src/decorator/relations/OneToMany"; +import {Column} from "../../../../../../src/decorator/columns/Column"; +import {EventMember} from "./EventMember"; + +@Entity() +export class Event { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + /* @ManyToOne(type => Person) + author: Person;*/ + + @OneToMany(type => EventMember, member => member.event) + members: EventMember[]; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/EventMember.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/EventMember.ts new file mode 100644 index 000000000..e56dcf1ab --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/EventMember.ts @@ -0,0 +1,15 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {ManyToOne} from "../../../../../../src/decorator/relations/ManyToOne"; +import {Event} from "./Event"; +import {User} from "./User"; + +@Entity() +export class EventMember { + + @ManyToOne(type => Event, event => event.members, { primary: true }) + event: Event; + + @ManyToOne(type => User, user => user.members, { primary: true }) + user: User; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/Person.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/Person.ts new file mode 100644 index 000000000..d72180bcb --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/Person.ts @@ -0,0 +1,17 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {Column} from "../../../../../../src/decorator/columns/Column"; +import {OneToOne} from "../../../../../../src/decorator/relations/OneToOne"; +import {JoinColumn} from "../../../../../../src/decorator/relations/JoinColumn"; +import {User} from "./User"; + +@Entity() +export class Person { + + @Column() + fullName: string; + + @OneToOne(type => User, { primary: true }) + @JoinColumn() + user: User; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/User.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/User.ts new file mode 100644 index 000000000..1af1f6e71 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/entity/User.ts @@ -0,0 +1,19 @@ +import {Entity} from "../../../../../../src/decorator/entity/Entity"; +import {PrimaryGeneratedColumn} from "../../../../../../src/decorator/columns/PrimaryGeneratedColumn"; +import {OneToMany} from "../../../../../../src/decorator/relations/OneToMany"; +import {Column} from "../../../../../../src/decorator/columns/Column"; +import {EventMember} from "./EventMember"; + +@Entity() +export class User { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @OneToMany(type => EventMember, member => member.user) + members: EventMember[]; + +} \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/multiple-primary-keys-other-cases.ts b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/multiple-primary-keys-other-cases.ts new file mode 100644 index 000000000..fcd56fff5 --- /dev/null +++ b/test/functional/relations/multiple-primary-keys/multiple-primary-keys-other-cases/multiple-primary-keys-other-cases.ts @@ -0,0 +1,104 @@ +import "reflect-metadata"; +import * as chai from "chai"; +import {expect} from "chai"; +import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; +import {Connection} from "../../../../../src/connection/Connection"; +import {User} from "./entity/User"; +import {EventMember} from "./entity/EventMember"; +import {Event} from "./entity/Event"; + +const should = chai.should(); + +describe("relations > multiple-primary-keys > other-cases", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + schemaCreate: true, + dropSchemaOnConnection: true, + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("should load related entity when entity uses relation ids as primary id", () => Promise.all(connections.map(async connection => { + + const user1 = new User(); + user1.name = "Alice"; + await connection.entityManager.persist(user1); + + const user2 = new User(); + user2.name = "Bob"; + await connection.entityManager.persist(user2); + + const user3 = new User(); + user3.name = "Clara"; + await connection.entityManager.persist(user3); + + const event1 = new Event(); + event1.name = "Event #1"; + await connection.entityManager.persist(event1); + + const event2 = new Event(); + event2.name = "Event #2"; + await connection.entityManager.persist(event2); + + const eventMember1 = new EventMember(); + eventMember1.user = user1; + eventMember1.event = event1; + await connection.entityManager.persist(eventMember1); + + const eventMember2 = new EventMember(); + eventMember2.user = user2; + eventMember2.event = event1; + await connection.entityManager.persist(eventMember2); + + const eventMember3 = new EventMember(); + eventMember3.user = user1; + eventMember3.event = event2; + await connection.entityManager.persist(eventMember3); + + const eventMember4 = new EventMember(); + eventMember4.user = user3; + eventMember4.event = event2; + await connection.entityManager.persist(eventMember4); + + const loadedEvents = await connection.entityManager + .createQueryBuilder(Event, "event") + .leftJoinAndSelect("event.members", "members") + .leftJoinAndSelect("members.user", "user") + .orderBy("event.id, user.id") + .getMany(); + + expect(loadedEvents[0].members).to.not.be.empty; + expect(loadedEvents[0].members[0].user.id).to.be.equal(1); + expect(loadedEvents[0].members[0].user.name).to.be.equal("Alice"); + expect(loadedEvents[0].members[1].user.id).to.be.equal(2); + expect(loadedEvents[0].members[1].user.name).to.be.equal("Bob"); + expect(loadedEvents[1].members).to.not.be.empty; + expect(loadedEvents[1].members[0].user.id).to.be.equal(1); + expect(loadedEvents[1].members[0].user.name).to.be.equal("Alice"); + expect(loadedEvents[1].members[1].user.id).to.be.equal(3); + expect(loadedEvents[1].members[1].user.name).to.be.equal("Clara"); + + const loadedUsers = await connection.entityManager + .createQueryBuilder(User, "user") + .leftJoinAndSelect("user.members", "members") + .leftJoinAndSelect("members.event", "event") + .orderBy("user.id, event.id") + .getMany(); + + expect(loadedUsers[0].members).to.not.be.empty; + expect(loadedUsers[0].members[0].event.id).to.be.equal(1); + expect(loadedUsers[0].members[0].event.name).to.be.equal("Event #1"); + expect(loadedUsers[0].members[1].event.id).to.be.equal(2); + expect(loadedUsers[0].members[1].event.name).to.be.equal("Event #2"); + expect(loadedUsers[1].members).to.not.be.empty; + expect(loadedUsers[1].members[0].event.id).to.be.equal(1); + expect(loadedUsers[1].members[0].event.name).to.be.equal("Event #1"); + expect(loadedUsers[2].members).to.not.be.empty; + expect(loadedUsers[2].members[0].event.id).to.be.equal(2); + expect(loadedUsers[2].members[0].event.name).to.be.equal("Event #2"); + + }))); + +}); \ No newline at end of file diff --git a/test/functional/relations/multiple-primary-keys/multiple-primaty-keys.ts b/test/functional/relations/multiple-primary-keys/multiple-primaty-keys.ts deleted file mode 100644 index 52bdeb37d..000000000 --- a/test/functional/relations/multiple-primary-keys/multiple-primaty-keys.ts +++ /dev/null @@ -1,65 +0,0 @@ -import "reflect-metadata"; -import * as chai from "chai"; -import {expect} from "chai"; -import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils"; -import {Connection} from "../../../../src/connection/Connection"; -import {Post} from "./entity/Post"; -import {Category} from "./entity/Category"; - -const should = chai.should(); - -describe("relations > multiple-primary-keys", () => { - - let connections: Connection[]; - before(async () => connections = await createTestingConnections({ - entities: [__dirname + "/entity/*{.js,.ts}"], - schemaCreate: true, - dropSchemaOnConnection: true, - })); - beforeEach(() => reloadTestingDatabases(connections)); - after(() => closeTestingConnections(connections)); - - it("should load related entity when multiple primary keys used", () => Promise.all(connections.map(async connection => { - - const category1 = new Category(); - category1.name = "cars"; - category1.type = "common-category"; - await connection.entityManager.persist(category1); - - const category2 = new Category(); - category2.name = "airplanes"; - category2.type = "common-category"; - await connection.entityManager.persist(category2); - - const post1 = new Post(); - post1.title = "About cars #1"; - post1.category = category1; - await connection.entityManager.persist(post1); - - const post2 = new Post(); - post2.title = "About cars #2"; - post2.category = category2; - await connection.entityManager.persist(post2); - - let loadedPosts = await connection.entityManager - .createQueryBuilder(Post, "post") - .leftJoinAndSelect("post.category", "category") - .getMany(); - - expect(loadedPosts![0].category).to.not.be.empty; - expect(loadedPosts![0].category.type).to.be.equal("common-category"); - expect(loadedPosts![1].category).to.not.be.empty; - expect(loadedPosts![1].category.type).to.be.equal("common-category"); - - let loadedPost = await connection.entityManager - .createQueryBuilder(Post, "post") - .leftJoinAndSelect("post.category", "category") - .where("post.id = :id", { id: 1 }) - .getOne(); - - expect(loadedPost!.category).to.not.be.empty; - expect(loadedPost!.category.type).to.be.equal("common-category"); - - }))); - -}); \ No newline at end of file