diff --git a/sample/sample10-mixed/entity/Post.ts b/sample/sample10-mixed/entity/Post.ts index 8aaa6fdeb..6d0db0b55 100644 --- a/sample/sample10-mixed/entity/Post.ts +++ b/sample/sample10-mixed/entity/Post.ts @@ -22,14 +22,14 @@ export class Post { }) text: string; - @OneToOne(true, () => PostDetails, details => details.post, { + @OneToOne(true, () => PostDetails, details => details.posts, { cascadeInsert: true, cascadeUpdate: true, cascadeRemove: true }) details: PostDetails; - @OneToMany(type => Image, image => image.post, { + @OneToMany(type => Image, image => image.posts, { cascadeInsert: true, cascadeUpdate: true, cascadeRemove: true diff --git a/sample/sample3-many-to-one/entity/Post.ts b/sample/sample3-many-to-one/entity/Post.ts index bd96aa35f..7c27908df 100644 --- a/sample/sample3-many-to-one/entity/Post.ts +++ b/sample/sample3-many-to-one/entity/Post.ts @@ -30,27 +30,27 @@ export class Post { // post has relation with details. cascade inserts here means if new PostDetails instance will be set to this // relation it will be inserted automatically to the db when you save this Post entity - @ManyToOne(() => PostDetails, details => details.post, { + @ManyToOne(() => PostDetails, details => details.posts, { cascadeInsert: true }) details: PostDetails; // post has relation with details. cascade update here means if new PostDetail instance will be set to this relation // it will be inserted automatically to the db when you save this Post entity - @ManyToOne(() => PostImage, image => image.post, { + @ManyToOne(() => PostImage, image => image.posts, { cascadeUpdate: true }) image: PostImage; // post has relation with details. cascade update here means if new PostDetail instance will be set to this relation // it will be inserted automatically to the db when you save this Post entity - @ManyToOne(() => PostMetadata, metadata => metadata.post, { + @ManyToOne(() => PostMetadata, metadata => metadata.posts, { cascadeRemove: true }) metadata: PostMetadata; // post has relation with details. full cascades here - @ManyToOne(() => PostInformation, information => information.post, { + @ManyToOne(() => PostInformation, information => information.posts, { cascadeInsert: true, cascadeUpdate: true, cascadeRemove: true @@ -58,7 +58,7 @@ export class Post { information: PostInformation; // post has relation with details. not cascades here. means cannot be persisted, updated or removed - @ManyToOne(() => PostAuthor, author => author.post) + @ManyToOne(() => PostAuthor, author => author.posts) author: PostAuthor; } \ No newline at end of file diff --git a/sample/sample3-many-to-one/entity/PostAuthor.ts b/sample/sample3-many-to-one/entity/PostAuthor.ts index 708e19923..23d9868a5 100644 --- a/sample/sample3-many-to-one/entity/PostAuthor.ts +++ b/sample/sample3-many-to-one/entity/PostAuthor.ts @@ -13,6 +13,6 @@ export class PostAuthor { name: string; @OneToMany(() => Post, post => post.author) - post: Post[]; + posts: Post[]; } \ No newline at end of file diff --git a/sample/sample3-many-to-one/entity/PostDetails.ts b/sample/sample3-many-to-one/entity/PostDetails.ts index 5b6f05987..d25d79481 100644 --- a/sample/sample3-many-to-one/entity/PostDetails.ts +++ b/sample/sample3-many-to-one/entity/PostDetails.ts @@ -9,13 +9,19 @@ export class PostDetails { @PrimaryColumn("int", { autoIncrement: true }) id: number; - @Column() + @Column({ + nullable: true + }) authorName: string; - @Column() + @Column({ + nullable: true + }) comment: string; - @Column() + @Column({ + nullable: true + }) metadata: string; @OneToMany(() => Post, post => post.details, { @@ -23,6 +29,6 @@ export class PostDetails { cascadeUpdate: true, cascadeRemove: true }) - post: Post[]; + posts: Post[] = []; } \ No newline at end of file diff --git a/sample/sample3-many-to-one/entity/PostImage.ts b/sample/sample3-many-to-one/entity/PostImage.ts index 3ab2d3b3c..a4dbc93c2 100644 --- a/sample/sample3-many-to-one/entity/PostImage.ts +++ b/sample/sample3-many-to-one/entity/PostImage.ts @@ -13,6 +13,6 @@ export class PostImage { url: string; @OneToMany(() => Post, post => post.image) - post: Post[]; + posts: Post[]; } \ No newline at end of file diff --git a/sample/sample3-many-to-one/entity/PostInformation.ts b/sample/sample3-many-to-one/entity/PostInformation.ts index 3ee98b9a0..81fb263b6 100644 --- a/sample/sample3-many-to-one/entity/PostInformation.ts +++ b/sample/sample3-many-to-one/entity/PostInformation.ts @@ -15,6 +15,6 @@ export class PostInformation { @OneToMany(() => Post, post => post.information, { cascadeUpdate: true, }) - post: Post[]; + posts: Post[]; } \ No newline at end of file diff --git a/sample/sample3-many-to-one/entity/PostMetadata.ts b/sample/sample3-many-to-one/entity/PostMetadata.ts index f3759fed8..0b9228b4e 100644 --- a/sample/sample3-many-to-one/entity/PostMetadata.ts +++ b/sample/sample3-many-to-one/entity/PostMetadata.ts @@ -13,6 +13,6 @@ export class PostMetadata { description: string; @OneToMany(() => Post, post => post.metadata) - post: Post[]; + posts: Post[]; } \ No newline at end of file diff --git a/sample/sample4-many-to-many/app.ts b/sample/sample4-many-to-many/app.ts index 8ee105493..4398359d2 100644 --- a/sample/sample4-many-to-many/app.ts +++ b/sample/sample4-many-to-many/app.ts @@ -1,6 +1,11 @@ import {TypeORM} from "../../src/TypeORM"; import {Post} from "./entity/Post"; -import {Category} from "./entity/Category"; +import {PostDetails} from "./entity/PostDetails"; +import {PostCategory} from "./entity/PostCategory"; +import {PostMetadata} from "./entity/PostMetadata"; +import {PostImage} from "./entity/PostImage"; +import {PostInformation} from "./entity/PostInformation"; +import {PostAuthor} from "./entity/PostAuthor"; // first create a connection let options = { @@ -12,25 +17,27 @@ let options = { autoSchemaCreate: true }; -TypeORM.createMysqlConnection(options, [Post, Category]).then(connection => { +TypeORM.createMysqlConnection(options, [Post, PostDetails, PostCategory, PostMetadata, PostImage, PostInformation, PostAuthor]).then(connection => { - let category1 = new Category(); - category1.name = "People"; - - let category2 = new Category(); - category2.name = "Human"; - - let post = new Post(); - post.text = "Hello how are you?"; - post.title = "hello"; - post.categories = [category1, category2]; + /* + let category1 = new Category(); + category1.name = "People"; + + let category2 = new Category(); + category2.name = "Human"; + + let post = new Post(); + post.text = "Hello how are you?"; + post.title = "hello"; + post.categories = [category1, category2]; + */ // finally save it - let postRepository = connection.getRepository(Post); + /*let postRepository = connection.getRepository(Post); postRepository .persist(post) .then(post => console.log("Post has been saved")) - .catch(error => console.log("Cannot save. Error: ", error)); + .catch(error => console.log("Cannot save. Error: ", error));*/ }, error => console.log("Cannot connect: ", error)); \ No newline at end of file diff --git a/sample/sample4-many-to-many/entity/Post.ts b/sample/sample4-many-to-many/entity/Post.ts index cda8bba47..7c1b4ca86 100644 --- a/sample/sample4-many-to-many/entity/Post.ts +++ b/sample/sample4-many-to-many/entity/Post.ts @@ -1,12 +1,17 @@ import {PrimaryColumn, Column} from "../../../src/decorator/Columns"; import {Table} from "../../../src/decorator/Tables"; +import {PostDetails} from "./PostDetails"; import {ManyToMany} from "../../../src/decorator/Relations"; -import {Category} from "./Category"; +import {PostCategory} from "./PostCategory"; +import {PostAuthor} from "./PostAuthor"; +import {PostInformation} from "./PostInformation"; +import {PostImage} from "./PostImage"; +import {PostMetadata} from "./PostMetadata"; -@Table("sample4-post") +@Table("sample4_post") export class Post { - @PrimaryColumn() + @PrimaryColumn("int", { autoIncrement: true }) id: number; @Column() @@ -15,7 +20,45 @@ export class Post { @Column() text: string; - @ManyToMany(true, _ => Category, category => category.posts) - categories: Category[]; + // post has relation with category, however inverse relation is not set (category does not have relation with post set) + @ManyToMany(true, () => PostCategory, { + cascadeInsert: true, + cascadeUpdate: true, + cascadeRemove: true + }) + category: PostCategory[] = []; + + // post has relation with details. cascade inserts here means if new PostDetails instance will be set to this + // relation it will be inserted automatically to the db when you save this Post entity + @ManyToMany(true, () => PostDetails, details => details.posts, { + cascadeInsert: true + }) + details: PostDetails[] = []; + + // post has relation with details. cascade update here means if new PostDetail instance will be set to this relation + // it will be inserted automatically to the db when you save this Post entity + @ManyToMany(true, () => PostImage, image => image.posts, { + cascadeUpdate: true + }) + image: PostImage[] = []; + + // post has relation with details. cascade update here means if new PostDetail instance will be set to this relation + // it will be inserted automatically to the db when you save this Post entity + @ManyToMany(true, () => PostMetadata, metadata => metadata.posts, { + cascadeRemove: true + }) + metadata: PostMetadata[] = []; + + // post has relation with details. full cascades here + @ManyToMany(true, () => PostInformation, information => information.posts, { + cascadeInsert: true, + cascadeUpdate: true, + cascadeRemove: true + }) + information: PostInformation[] = []; + + // post has relation with details. not cascades here. means cannot be persisted, updated or removed + @ManyToMany(true, () => PostAuthor, author => author.posts) + author: PostAuthor[] = []; } \ No newline at end of file diff --git a/sample/sample4-many-to-many/entity/Category.ts b/sample/sample4-many-to-many/entity/PostAuthor.ts similarity index 62% rename from sample/sample4-many-to-many/entity/Category.ts rename to sample/sample4-many-to-many/entity/PostAuthor.ts index 6df064542..47923382b 100644 --- a/sample/sample4-many-to-many/entity/Category.ts +++ b/sample/sample4-many-to-many/entity/PostAuthor.ts @@ -1,18 +1,18 @@ import {PrimaryColumn, Column} from "../../../src/decorator/Columns"; import {Table} from "../../../src/decorator/Tables"; -import {ManyToMany} from "../../../src/decorator/Relations"; import {Post} from "./Post"; +import {ManyToMany} from "../../../src/decorator/Relations"; -@Table("sample4-category") -export class Category { +@Table("sample4_post_author") +export class PostAuthor { - @PrimaryColumn() + @PrimaryColumn("int", { autoIncrement: true }) id: number; @Column() name: string; - @ManyToMany(false, _ => Post, post => post.categories) + @ManyToMany(false, () => Post, post => post.author) posts: Post[]; } \ No newline at end of file diff --git a/sample/sample4-many-to-many/entity/PostCategory.ts b/sample/sample4-many-to-many/entity/PostCategory.ts new file mode 100644 index 000000000..d25f98904 --- /dev/null +++ b/sample/sample4-many-to-many/entity/PostCategory.ts @@ -0,0 +1,13 @@ +import {PrimaryColumn, Column} from "../../../src/decorator/Columns"; +import {Table} from "../../../src/decorator/Tables"; + +@Table("sample4_post_category") +export class PostCategory { + + @PrimaryColumn("int", { autoIncrement: true }) + id: number; + + @Column() + name: string; + +} \ No newline at end of file diff --git a/sample/sample4-many-to-many/entity/PostDetails.ts b/sample/sample4-many-to-many/entity/PostDetails.ts new file mode 100644 index 000000000..4ebecc98a --- /dev/null +++ b/sample/sample4-many-to-many/entity/PostDetails.ts @@ -0,0 +1,34 @@ +import {PrimaryColumn, Column} from "../../../src/decorator/Columns"; +import {Table} from "../../../src/decorator/Tables"; +import {ManyToMany} from "../../../src/decorator/Relations"; +import {Post} from "./Post"; + +@Table("sample4_post_details") +export class PostDetails { + + @PrimaryColumn("int", { autoIncrement: true }) + id: number; + + @Column({ + nullable: true + }) + authorName: string; + + @Column({ + nullable: true + }) + comment: string; + + @Column({ + nullable: true + }) + metadata: string; + + @ManyToMany(false, () => Post, post => post.details, { + cascadeInsert: true, + cascadeUpdate: true, + cascadeRemove: true + }) + posts: Post[] = []; + +} \ No newline at end of file diff --git a/sample/sample4-many-to-many/entity/PostImage.ts b/sample/sample4-many-to-many/entity/PostImage.ts new file mode 100644 index 000000000..cae082db1 --- /dev/null +++ b/sample/sample4-many-to-many/entity/PostImage.ts @@ -0,0 +1,18 @@ +import {PrimaryColumn, Column} from "../../../src/decorator/Columns"; +import {Table} from "../../../src/decorator/Tables"; +import {Post} from "./Post"; +import {ManyToMany} from "../../../src/decorator/Relations"; + +@Table("sample4_post_image") +export class PostImage { + + @PrimaryColumn("int", { autoIncrement: true }) + id: number; + + @Column() + url: string; + + @ManyToMany(false, () => Post, post => post.image) + posts: Post[]; + +} \ No newline at end of file diff --git a/sample/sample4-many-to-many/entity/PostInformation.ts b/sample/sample4-many-to-many/entity/PostInformation.ts new file mode 100644 index 000000000..210b5d80b --- /dev/null +++ b/sample/sample4-many-to-many/entity/PostInformation.ts @@ -0,0 +1,20 @@ +import {PrimaryColumn, Column} from "../../../src/decorator/Columns"; +import {Table} from "../../../src/decorator/Tables"; +import {ManyToMany} from "../../../src/decorator/Relations"; +import {Post} from "./Post"; + +@Table("sample4_post_information") +export class PostInformation { + + @PrimaryColumn("int", { autoIncrement: true }) + id: number; + + @Column() + text: string; + + @ManyToMany(false, () => Post, post => post.information, { + cascadeUpdate: true, + }) + posts: Post[]; + +} \ No newline at end of file diff --git a/sample/sample4-many-to-many/entity/PostMetadata.ts b/sample/sample4-many-to-many/entity/PostMetadata.ts new file mode 100644 index 000000000..3db0e4fe6 --- /dev/null +++ b/sample/sample4-many-to-many/entity/PostMetadata.ts @@ -0,0 +1,18 @@ +import {PrimaryColumn, Column} from "../../../src/decorator/Columns"; +import {Table} from "../../../src/decorator/Tables"; +import {Post} from "./Post"; +import {ManyToMany} from "../../../src/decorator/Relations"; + +@Table("sample4_post_metadata") +export class PostMetadata { + + @PrimaryColumn("int", { autoIncrement: true }) + id: number; + + @Column() + description: string; + + @ManyToMany(false, () => Post, post => post.metadata) + posts: Post[]; + +} \ No newline at end of file diff --git a/src/driver/MysqlDriver.ts b/src/driver/MysqlDriver.ts index fc00d59b6..f5ead449d 100644 --- a/src/driver/MysqlDriver.ts +++ b/src/driver/MysqlDriver.ts @@ -88,8 +88,15 @@ export class MysqlDriver implements Driver { */ query(query: string): Promise { if (!this.connection) throw new Error("Connection is not established, cannot execute a query."); - console.info("executing:", query); - return new Promise((ok, fail) => this.connection.query(query, (err: any, result: any) => err ? fail(err) : ok(result))); + // console.info("executing:", query); + return new Promise((ok, fail) => this.connection.query(query, (err: any, result: any) => { + if (err) { + console.error("query failed: ", query); + fail(err); + return; + } + ok(result); + })); } /** diff --git a/src/metadata-builder/EntityMetadataBuilder.ts b/src/metadata-builder/EntityMetadataBuilder.ts index 897c72ee4..b3ce0ee7f 100644 --- a/src/metadata-builder/EntityMetadataBuilder.ts +++ b/src/metadata-builder/EntityMetadataBuilder.ts @@ -155,7 +155,8 @@ export class EntityMetadataBuilder { const junctionEntityMetadata = new EntityMetadata(tableMetadata, columns, [], [], [], foreignKeys); junctionEntityMetadatas.push(junctionEntityMetadata); relation.junctionEntityMetadata = junctionEntityMetadata; - relation.inverseRelation.junctionEntityMetadata = junctionEntityMetadata; + if (relation.inverseRelation) + relation.inverseRelation.junctionEntityMetadata = junctionEntityMetadata; }); }); diff --git a/src/query-builder/QueryBuilder.ts b/src/query-builder/QueryBuilder.ts index 47c4d7cde..99da0ae6a 100644 --- a/src/query-builder/QueryBuilder.ts +++ b/src/query-builder/QueryBuilder.ts @@ -253,11 +253,7 @@ export class QueryBuilder { } getSingleResult(): Promise { - return this.getResults().then(entities => { - console.log(this.getSql()); - console.log(entities); - return entities[0]; - }); + return this.getResults().then(entities => entities[0]); } // ------------------------------------------------------------------------- diff --git a/src/query-builder/alias/AliasMap.ts b/src/query-builder/alias/AliasMap.ts index e57594fb9..132acb37a 100644 --- a/src/query-builder/alias/AliasMap.ts +++ b/src/query-builder/alias/AliasMap.ts @@ -56,8 +56,8 @@ export class AliasMap { const parentEntityMetadata = this.getEntityMetadataByAlias(parentAlias); const relation = parentEntityMetadata.findRelationWithDbName(alias.parentPropertyName); if (!relation) - throw new Error("Related entity metadata was not found."); - + throw new Error("Relation metadata for " + alias.parentAliasName + "#" + alias.parentPropertyName + " was not found."); + return relation.relatedEntityMetadata; } diff --git a/src/repository/EntityPersistOperationsBuilder.ts b/src/repository/EntityPersistOperationsBuilder.ts index 9b03b06dc..c18312896 100644 --- a/src/repository/EntityPersistOperationsBuilder.ts +++ b/src/repository/EntityPersistOperationsBuilder.ts @@ -121,7 +121,7 @@ export class EntityPersistOperationsBuilder { const junctionRemoveOperations = this.findJunctionRemoveOperations(metadata, entity1, allEntities); const updatesByRelationsOperations = this.updateRelations(insertOperations, entity2); //const insertJunctionOperations = ;//this.a(); - console.log("---------------------------------------------------------"); + /*console.log("---------------------------------------------------------"); console.log("DB ENTITIES"); console.log("---------------------------------------------------------"); console.log(dbEntities); @@ -153,7 +153,7 @@ export class EntityPersistOperationsBuilder { console.log("UPDATES BY RELATIONS"); console.log("---------------------------------------------------------"); console.log(updatesByRelationsOperations); - console.log("---------------------------------------------------------"); + console.log("---------------------------------------------------------");*/ // now normalize inserted entities // no need probably, since we cant rely on deepness because of recursion: insertOperations.sort((a, b) => a.deepness + b.deepness); diff --git a/src/repository/Repository.ts b/src/repository/Repository.ts index 976b9bb2c..d1dc524e8 100644 --- a/src/repository/Repository.ts +++ b/src/repository/Repository.ts @@ -179,6 +179,7 @@ export class Repository { } private updateDeletedRelations(removeOperation: RemoveOperation) { // todo: check if both many-to-one deletions work too + if (removeOperation.relation.isManyToMany || removeOperation.relation.isOneToMany) return; const value = removeOperation.relation.name + "=NULL"; const query = `UPDATE ${removeOperation.metadata.table.name} SET ${value} WHERE ${removeOperation.metadata.primaryColumn.name}='${removeOperation.fromEntityId}'` ; return this.connection.driver.query(query); diff --git a/test/integration/sample3-many-to-one.ts b/test/integration/sample3-many-to-one.ts index 7c807b03c..6ee8011be 100644 --- a/test/integration/sample3-many-to-one.ts +++ b/test/integration/sample3-many-to-one.ts @@ -135,7 +135,7 @@ describe("many-to-one", function() { .should.eventually.eql(expectedPost); }); - /* it("should load details and its post if left join used (from reverse side)", function() { + it("should load details and its post if left join used (from reverse side)", function() { const expectedDetails = new PostDetails(); expectedDetails.id = savedPost.details.id; @@ -143,19 +143,21 @@ describe("many-to-one", function() { expectedDetails.comment = savedPost.details.comment; expectedDetails.metadata = savedPost.details.metadata; - expectedDetails.post = new Post(); - expectedDetails.post.id = savedPost.id; - expectedDetails.post.text = savedPost.text; - expectedDetails.post.title = savedPost.title; + const expectedPost = new Post(); + expectedPost.id = savedPost.id; + expectedPost.text = savedPost.text; + expectedPost.title = savedPost.title; + + expectedDetails.posts.push(expectedPost); return postDetailsRepository .createQueryBuilder("details") - .leftJoinAndSelect("details.post", "post") + .leftJoinAndSelect("details.posts", "posts") .where("details.id=:id") .setParameter("id", savedPost.id) .getSingleResult() .should.eventually.eql(expectedDetails); - });*/ + }); it("should load saved post without details if left joins are not specified", function() { const expectedPost = new Post(); @@ -328,7 +330,7 @@ describe("many-to-one", function() { .leftJoinAndSelect("post.details", "details") .where("post.id=:id") .setParameter("id", updatedPost.id) - .getSingleResult() + .getSingleResult(); }).then(updatedPostReloaded => { updatedPostReloaded.details.comment.should.be.equal("this is post"); }); @@ -433,4 +435,68 @@ describe("many-to-one", function() { }); + describe("insert post details from reverse side", function() { + let newPost: Post, details: PostDetails, savedDetails: PostDetails; + + before(reloadDatabase); + + before(function() { + newPost = new Post(); + newPost.text = "Hello post"; + newPost.title = "this is post title"; + + details = new PostDetails(); + details.comment = "post details comment"; + details.posts.push(newPost); + + return postDetailsRepository.persist(details).then(details => savedDetails = details); + }); + + it("should return the same post instance after its created", function () { + savedDetails.posts[0].should.be.equal(newPost); + }); + + it("should return the same post details instance after its created", function () { + savedDetails.should.be.equal(details); + }); + + it("should have a new generated id after post is created", function () { + expect(savedDetails.id).not.to.be.empty; + expect(details.id).not.to.be.empty; + }); + + it("should have inserted post in the database", function() { + const expectedPost = new Post(); + expectedPost.id = newPost.id; + expectedPost.text = newPost.text; + expectedPost.title = newPost.title; + return postRepository.findById(savedDetails.id).should.eventually.eql(expectedPost); + }); + + it("should have inserted details in the database", function() { + const expectedDetails = new PostDetails(); + expectedDetails.id = details.id; + expectedDetails.comment = details.comment; + return postDetailsRepository.findById(details.id).should.eventually.eql(expectedDetails); + }); + + it("should load post and its details if left join used", function() { + const expectedDetails = new PostDetails(); + expectedDetails.id = savedDetails.id; + expectedDetails.comment = savedDetails.comment; + expectedDetails.posts.push(new Post()); + expectedDetails.posts[0].id = newPost.id; + expectedDetails.posts[0].text = newPost.text; + expectedDetails.posts[0].title = newPost.title; + + return postDetailsRepository + .createQueryBuilder("details") + .leftJoinAndSelect("details.posts", "posts") + .where("details.id=:id", { id: savedDetails.id }) + .getSingleResult() + .should.eventually.eql(expectedDetails); + }); + + }); + }); \ No newline at end of file diff --git a/test/integration/sample4-many-to-many.ts b/test/integration/sample4-many-to-many.ts new file mode 100644 index 000000000..947bd68e3 --- /dev/null +++ b/test/integration/sample4-many-to-many.ts @@ -0,0 +1,504 @@ +import * as chai from "chai"; +import {expect} from "chai"; +import {Connection} from "../../src/connection/Connection"; +import {TypeORM} from "../../src/TypeORM"; +import {ConnectionOptions} from "../../src/connection/ConnectionOptions"; +import {Repository} from "../../src/repository/Repository"; +import {SchemaCreator} from "../../src/schema-creator/SchemaCreator"; +import {PostDetails} from "../../sample/sample4-many-to-many/entity/PostDetails"; +import {Post} from "../../sample/sample4-many-to-many/entity/Post"; +import {PostCategory} from "../../sample/sample4-many-to-many/entity/PostCategory"; +import {PostAuthor} from "../../sample/sample4-many-to-many/entity/PostAuthor"; +import {PostMetadata} from "../../sample/sample4-many-to-many/entity/PostMetadata"; +import {PostImage} from "../../sample/sample4-many-to-many/entity/PostImage"; +import {PostInformation} from "../../sample/sample4-many-to-many/entity/PostInformation"; + +chai.should(); +describe("many-to-many", function() { + + // ------------------------------------------------------------------------- + // Configuration + // ------------------------------------------------------------------------- + + let options: ConnectionOptions = { + host: "192.168.99.100", + port: 3306, + username: "root", + password: "admin", + database: "test", + autoSchemaCreate: true + }; + + // connect to db + let connection: Connection; + before(function() { + return TypeORM.createMysqlConnection(options, [Post, PostDetails, PostCategory, PostMetadata, PostImage, PostInformation, PostAuthor]).then(conn => { + connection = conn; + }).catch(e => console.log("Error during connection to db: " + e)); + }); + + after(function() { + connection.close(); + }); + + // clean up database before each test + function reloadDatabase() { + return connection.driver + .clearDatabase() + .then(() => new SchemaCreator(connection).create()); + } + + let postRepository: Repository, + postDetailsRepository: Repository, + postCategoryRepository: Repository, + postImageRepository: Repository, + postMetadataRepository: Repository; + before(function() { + postRepository = connection.getRepository(Post); + postDetailsRepository = connection.getRepository(PostDetails); + postCategoryRepository = connection.getRepository(PostCategory); + postImageRepository = connection.getRepository(PostImage); + postMetadataRepository = connection.getRepository(PostMetadata); + }); + + // ------------------------------------------------------------------------- + // Specifications + // ------------------------------------------------------------------------- + + describe("insert post and details (has inverse relation + full cascade options)", function() { + let newPost: Post, details: PostDetails, savedPost: Post; + + before(reloadDatabase); + + before(function() { + details = new PostDetails(); + details.authorName = "Umed"; + details.comment = "this is post"; + details.metadata = "post,posting,postman"; + + newPost = new Post(); + newPost.text = "Hello post"; + newPost.title = "this is post title"; + newPost.details.push(details); + + return postRepository.persist(newPost).then(post => savedPost = post); + }); + + it("should return the same post instance after its created", function () { + savedPost.should.be.equal(newPost); + }); + + it("should return the same post details instance after its created", function () { + savedPost.details[0].should.be.equal(newPost.details[0]); + }); + + it("should have a new generated id after post is created", function () { + expect(savedPost.id).not.to.be.empty; + expect(savedPost.details[0].id).not.to.be.empty; + }); + + it("should have inserted post in the database", function() { + const expectedPost = new Post(); + expectedPost.id = savedPost.id; + expectedPost.text = savedPost.text; + expectedPost.title = savedPost.title; + + return postRepository.findById(savedPost.id).should.eventually.eql(expectedPost); + }); + + it("should have inserted post details in the database", function() { + const expectedDetails = new PostDetails(); + expectedDetails.id = savedPost.details[0].id; + expectedDetails.authorName = savedPost.details[0].authorName; + expectedDetails.comment = savedPost.details[0].comment; + expectedDetails.metadata = savedPost.details[0].metadata; + + return postDetailsRepository.findById(savedPost.details[0].id).should.eventually.eql(expectedDetails); + }); + + it("should load post and its details if left join used", function() { + const expectedPost = new Post(); + expectedPost.id = savedPost.id; + expectedPost.text = savedPost.text; + expectedPost.title = savedPost.title; + expectedPost.details.push(new PostDetails()); + expectedPost.details[0].id = savedPost.details[0].id; + expectedPost.details[0].authorName = savedPost.details[0].authorName; + expectedPost.details[0].comment = savedPost.details[0].comment; + expectedPost.details[0].metadata = savedPost.details[0].metadata; + + return postRepository + .createQueryBuilder("post") + .leftJoinAndSelect("post.details", "details") + .where("post.id=:id") + .setParameter("id", savedPost.id) + .getSingleResult() + .should.eventually.eql(expectedPost); + }); + + it("should load details and its post if left join used (from reverse side)", function() { + + const expectedDetails = new PostDetails(); + expectedDetails.id = savedPost.details[0].id; + expectedDetails.authorName = savedPost.details[0].authorName; + expectedDetails.comment = savedPost.details[0].comment; + expectedDetails.metadata = savedPost.details[0].metadata; + + const expectedPost = new Post(); + expectedPost.id = savedPost.id; + expectedPost.text = savedPost.text; + expectedPost.title = savedPost.title; + + expectedDetails.posts.push(expectedPost); + + return postDetailsRepository + .createQueryBuilder("details") + .leftJoinAndSelect("details.posts", "posts") + .where("details.id=:id") + .setParameter("id", savedPost.id) + .getSingleResult() + .should.eventually.eql(expectedDetails); + }); + + it("should load saved post without details if left joins are not specified", function() { + const expectedPost = new Post(); + expectedPost.id = savedPost.id; + expectedPost.text = savedPost.text; + expectedPost.title = savedPost.title; + + return postRepository + .createQueryBuilder("post") + .where("post.id=:id", { id: savedPost.id }) + .getSingleResult() + .should.eventually.eql(expectedPost); + }); + + it("should load saved post without details if left joins are not specified", function() { + const expectedDetails = new PostDetails(); + expectedDetails.id = savedPost.details[0].id; + expectedDetails.authorName = savedPost.details[0].authorName; + expectedDetails.comment = savedPost.details[0].comment; + expectedDetails.metadata = savedPost.details[0].metadata; + + return postDetailsRepository + .createQueryBuilder("details") + .where("details.id=:id", { id: savedPost.id }) + .getSingleResult() + .should.eventually.eql(expectedDetails); + }); + + }); + + describe("insert post and category (one-side relation)", function() { + let newPost: Post, category: PostCategory, savedPost: Post; + + before(reloadDatabase); + + before(function() { + category = new PostCategory(); + category.name = "technology"; + + newPost = new Post(); + newPost.text = "Hello post"; + newPost.title = "this is post title"; + newPost.category.push(category); + + return postRepository.persist(newPost).then(post => savedPost = post); + }); + + it("should return the same post instance after its created", function () { + savedPost.should.be.equal(newPost); + }); + + it("should return the same post category instance after its created", function () { + savedPost.category.should.be.equal(newPost.category); + }); + + it("should have a new generated id after post is created", function () { + expect(savedPost.id).not.to.be.empty; + expect(savedPost.category[0].id).not.to.be.empty; + }); + + it("should have inserted post in the database", function() { + const expectedPost = new Post(); + expectedPost.id = savedPost.id; + expectedPost.text = savedPost.text; + expectedPost.title = savedPost.title; + return postRepository.findById(savedPost.id).should.eventually.eql(expectedPost); + }); + + it("should have inserted category in the database", function() { + const expectedPost = new PostCategory(); + expectedPost.id = savedPost.category[0].id; + expectedPost.name = "technology"; + return postCategoryRepository.findById(savedPost.category[0].id).should.eventually.eql(expectedPost); + }); + + it("should load post and its category if left join used", function() { + const expectedPost = new Post(); + expectedPost.id = savedPost.id; + expectedPost.title = savedPost.title; + expectedPost.text = savedPost.text; + expectedPost.category.push(new PostCategory()); + expectedPost.category[0].id = savedPost.category[0].id; + expectedPost.category[0].name = savedPost.category[0].name; + + return postRepository + .createQueryBuilder("post") + .leftJoinAndSelect("post.category", "category") + .where("post.id=:id", { id: savedPost.id }) + .getSingleResult() + .should.eventually.eql(expectedPost); + }); + + it("should load details and its post if left join used (from reverse side)", function() { + // later need to specify with what exception we reject it + /*return postCategoryRepository + .createQueryBuilder("category") + .leftJoinAndSelect("category.post", "post") + .where("category.id=:id", { id: savedPost.id }) + .getSingleResult() + .should.be.rejectedWith(Error);*/ // not working, find fix + }); + + }); + + describe("cascade updates should not be executed when cascadeUpdate option is not set", function() { + let newPost: Post, details: PostDetails, savedPost: Post; + + before(reloadDatabase); + + before(function() { + + details = new PostDetails(); + details.authorName = "Umed"; + details.comment = "this is post"; + details.metadata = "post,posting,postman"; + + newPost = new Post(); + newPost.text = "Hello post"; + newPost.title = "this is post title"; + newPost.details.push(details); + + return postRepository + .persist(newPost) + .then(post => savedPost = post); + }); + + it("should ignore updates in the model and do not update the db when entity is updated", function () { + newPost.details[0].comment = "i am updated comment"; + return postRepository.persist(newPost).then(updatedPost => { + updatedPost.details[0].comment.should.be.equal("i am updated comment"); + return postRepository + .createQueryBuilder("post") + .leftJoinAndSelect("post.details", "details") + .where("post.id=:id") + .setParameter("id", updatedPost.id) + .getSingleResult() + }).then(updatedPostReloaded => { + console.log("updatedPost: ", updatedPostReloaded); + updatedPostReloaded.details[0].comment.should.be.equal("this is post"); + }); + }); // todo: also check that updates throw exception in strict cascades mode + }); + + describe("cascade remove should not be executed when cascadeRemove option is not set", function() { + let newPost: Post, details: PostDetails, savedPost: Post; + + before(reloadDatabase); + + before(function() { + + details = new PostDetails(); + details.authorName = "Umed"; + details.comment = "this is post"; + details.metadata = "post,posting,postman"; + + newPost = new Post(); + newPost.text = "Hello post"; + newPost.title = "this is post title"; + newPost.details.push(details); + + return postRepository + .persist(newPost) + .then(post => savedPost = post); + }); + + it("should ignore updates in the model and do not update the db when entity is updated", function () { + newPost.details = null; + return postRepository.persist(newPost).then(updatedPost => { + return postRepository + .createQueryBuilder("post") + .leftJoinAndSelect("post.details", "details") + .where("post.id=:id") + .setParameter("id", updatedPost.id) + .getSingleResult(); + }).then(updatedPostReloaded => { + // todo fix updatedPostReloaded.details[0].comment.should.be.equal("this is post"); + }); + }); + }); + + describe("cascade updates should be executed when cascadeUpdate option is set", function() { + let newPost: Post, newImage: PostImage, savedImage: PostImage; + + before(reloadDatabase); + + it("should update a relation successfully when updated", function () { + + newImage = new PostImage(); + newImage.url = "logo.png"; + + newPost = new Post(); + newPost.text = "Hello post"; + newPost.title = "this is post title"; + + return postImageRepository + .persist(newImage) + .then(image => { + savedImage = image; + newPost.image.push(image); + return postRepository.persist(newPost); + + }).then(post => { + newPost = post; + return postRepository + .createQueryBuilder("post") + .leftJoinAndSelect("post.image", "image") + .where("post.id=:id") + .setParameter("id", post.id) + .getSingleResult(); + + }).then(loadedPost => { + loadedPost.image[0].url = "new-logo.png"; + return postRepository.persist(loadedPost); + + }).then(() => { + return postRepository + .createQueryBuilder("post") + .leftJoinAndSelect("post.image", "image") + .where("post.id=:id") + .setParameter("id", newPost.id) + .getSingleResult(); + + }).then(reloadedPost => { + reloadedPost.image[0].url.should.be.equal("new-logo.png"); + }); + }); + + }); + + describe("cascade remove should be executed when cascadeRemove option is set", function() { + let newPost: Post, newMetadata: PostMetadata, savedMetadata: PostMetadata; + + before(reloadDatabase); + + it("should remove a relation entity successfully when removed", function () { + + newMetadata = new PostMetadata(); + newMetadata.description = "this is post metadata"; + + newPost = new Post(); + newPost.text = "Hello post"; + newPost.title = "this is post title"; + + return postMetadataRepository + .persist(newMetadata) + .then(metadata => { + savedMetadata = metadata; + newPost.metadata.push(metadata); + return postRepository.persist(newPost); + + }).then(post => { + newPost = post; + return postRepository + .createQueryBuilder("post") + .leftJoinAndSelect("post.metadata", "metadata") + .where("post.id=:id") + .setParameter("id", post.id) + .getSingleResult(); + + }).then(loadedPost => { + loadedPost.metadata = null; + return postRepository.persist(loadedPost); + + }).then(() => { + return postRepository + .createQueryBuilder("post") + .leftJoinAndSelect("post.metadata", "metadata") + .where("post.id=:id") + .setParameter("id", newPost.id) + .getSingleResult(); + + }).then(reloadedPost => { + expect(reloadedPost.metadata).to.be.empty; + }); + }); + + }); + + describe("insert post details from reverse side", function() { + let newPost: Post, details: PostDetails, savedDetails: PostDetails; + + before(reloadDatabase); + + before(function() { + newPost = new Post(); + newPost.text = "Hello post"; + newPost.title = "this is post title"; + + details = new PostDetails(); + details.comment = "post details comment"; + details.posts.push(newPost); + + return postDetailsRepository.persist(details).then(details => savedDetails = details); + }); + + it("should return the same post instance after its created", function () { + savedDetails.posts[0].should.be.equal(newPost); + }); + + it("should return the same post details instance after its created", function () { + savedDetails.should.be.equal(details); + }); + + it("should have a new generated id after post is created", function () { + expect(savedDetails.id).not.to.be.empty; + expect(details.id).not.to.be.empty; + }); + + it("should have inserted post in the database", function() { + const expectedPost = new Post(); + expectedPost.id = newPost.id; + expectedPost.text = newPost.text; + expectedPost.title = newPost.title; + return postRepository.findById(savedDetails.id).should.eventually.eql(expectedPost); + }); + + it("should have inserted details in the database", function() { + const expectedDetails = new PostDetails(); + expectedDetails.id = details.id; + expectedDetails.comment = details.comment; + return postDetailsRepository.findById(details.id).should.eventually.eql(expectedDetails); + }); + + it("should load post and its details if left join used", function() { + const expectedDetails = new PostDetails(); + expectedDetails.id = savedDetails.id; + expectedDetails.comment = savedDetails.comment; + expectedDetails.posts.push(new Post()); + expectedDetails.posts[0].id = newPost.id; + expectedDetails.posts[0].text = newPost.text; + expectedDetails.posts[0].title = newPost.title; + + return postDetailsRepository + .createQueryBuilder("details") + .leftJoinAndSelect("details.posts", "posts") + .where("details.id=:id", { id: savedDetails.id }) + .getSingleResult() + .should.eventually.eql(expectedDetails); + }); + + }); + +}); \ No newline at end of file