mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
fixed bug with many-to-many when not all junction operations were collected
This commit is contained in:
parent
711e3a0698
commit
cbdaf1acaf
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "typeorm",
|
||||
"private": true,
|
||||
"version": "0.0.2-alpha.20",
|
||||
"version": "0.0.2-alpha.23",
|
||||
"description": "Data-mapper ORM for Typescript",
|
||||
"license": "Apache-2.0",
|
||||
"readmeFilename": "README.md",
|
||||
|
||||
@ -312,42 +312,50 @@ export class EntityPersistOperationBuilder {
|
||||
return operations;
|
||||
}
|
||||
|
||||
private findJunctionInsertOperations(metadata: EntityMetadata, newEntity: any, dbEntities: EntityWithId[]): JunctionInsertOperation[] {
|
||||
private findJunctionInsertOperations(metadata: EntityMetadata, newEntity: any, dbEntities: EntityWithId[], isRoot = true): JunctionInsertOperation[] {
|
||||
const dbEntity = dbEntities.find(dbEntity => {
|
||||
return dbEntity.id === newEntity[metadata.primaryColumn.name] && dbEntity.entity.constructor === metadata.target;
|
||||
});
|
||||
return metadata.relations
|
||||
.filter(relation => relation.isManyToMany)
|
||||
// .filter(relation => newEntity[relation.propertyName] instanceof Array)
|
||||
.filter(relation => newEntity[relation.propertyName] !== null && newEntity[relation.propertyName] !== undefined)
|
||||
.reduce((operations, relation) => {
|
||||
const relationMetadata = relation.inverseEntityMetadata;
|
||||
const relationIdProperty = relationMetadata.primaryColumn.name;
|
||||
const value = this.getEntityRelationValue(relation, newEntity);
|
||||
const dbValue = dbEntity ? this.getEntityRelationValue(relation, dbEntity.entity) : null;
|
||||
|
||||
if (!(value instanceof Array))
|
||||
return operations;
|
||||
|
||||
value.forEach((subEntity: any) => {
|
||||
|
||||
const has = !dbValue || !dbValue.find((e: any) => e[relationIdProperty] === subEntity[relationIdProperty]);
|
||||
if (value instanceof Array) {
|
||||
value.forEach((subEntity: any) => {
|
||||
|
||||
if (has) {
|
||||
operations.push({
|
||||
metadata: relation.junctionEntityMetadata,
|
||||
entity1: newEntity,
|
||||
entity2: subEntity
|
||||
});
|
||||
if (relation.isManyToMany) {
|
||||
const has = !dbValue || !dbValue.find((e: any) => e[relationIdProperty] === subEntity[relationIdProperty]);
|
||||
|
||||
if (has) {
|
||||
operations.push({
|
||||
metadata: relation.junctionEntityMetadata,
|
||||
entity1: newEntity,
|
||||
entity2: subEntity
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) {
|
||||
const subOperations = this.findJunctionInsertOperations(relationMetadata, subEntity, dbEntities, false);
|
||||
operations.push(...subOperations);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) {
|
||||
const subOperations = this.findJunctionInsertOperations(relationMetadata, value, dbEntities, false);
|
||||
operations.push(...subOperations);
|
||||
}
|
||||
}
|
||||
|
||||
const subOperations = this.findJunctionInsertOperations(relationMetadata, subEntity, dbEntities);
|
||||
operations.push(...subOperations);
|
||||
});
|
||||
return operations;
|
||||
}, <JunctionInsertOperation[]> []);
|
||||
}
|
||||
|
||||
private findJunctionRemoveOperations(metadata: EntityMetadata, dbEntity: any, newEntities: EntityWithId[]): JunctionInsertOperation[] {
|
||||
private findJunctionRemoveOperations(metadata: EntityMetadata, dbEntity: any, newEntities: EntityWithId[], isRoot = true): JunctionInsertOperation[] {
|
||||
if (!dbEntity) // if new entity is persisted then it does not have anything to be deleted
|
||||
return [];
|
||||
|
||||
@ -355,32 +363,40 @@ export class EntityPersistOperationBuilder {
|
||||
return newEntity.id === dbEntity[metadata.primaryColumn.name] && newEntity.entity.constructor === metadata.target;
|
||||
});
|
||||
return metadata.relations
|
||||
.filter(relation => relation.isManyToMany)
|
||||
// .filter(relation => dbEntity[relation.propertyName] instanceof Array)
|
||||
.filter(relation => dbEntity[relation.propertyName] !== null && dbEntity[relation.propertyName] !== undefined)
|
||||
.reduce((operations, relation) => {
|
||||
const relationMetadata = relation.inverseEntityMetadata;
|
||||
const relationIdProperty = relationMetadata.primaryColumn.name;
|
||||
const value = newEntity ? this.getEntityRelationValue(relation, newEntity.entity) : null;
|
||||
const dbValue = this.getEntityRelationValue(relation, dbEntity);
|
||||
|
||||
if (!(dbValue instanceof Array))
|
||||
return operations;
|
||||
|
||||
dbValue.forEach((subEntity: any) => {
|
||||
if (dbValue instanceof Array) {
|
||||
dbValue.forEach((subEntity: any) => {
|
||||
|
||||
const has = !value || !value.find((e: any) => e[relationIdProperty] === subEntity[relationIdProperty]);
|
||||
if (relation.isManyToMany) {
|
||||
const has = !value || !value.find((e: any) => e[relationIdProperty] === subEntity[relationIdProperty]);
|
||||
|
||||
if (has) {
|
||||
operations.push({
|
||||
metadata: relation.junctionEntityMetadata,
|
||||
entity1: dbEntity,
|
||||
entity2: subEntity
|
||||
});
|
||||
if (has) {
|
||||
operations.push({
|
||||
metadata: relation.junctionEntityMetadata,
|
||||
entity1: dbEntity,
|
||||
entity2: subEntity
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) {
|
||||
const subOperations = this.findJunctionRemoveOperations(relationMetadata, subEntity, newEntities, false);
|
||||
operations.push(...subOperations);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) {
|
||||
const subOperations = this.findJunctionRemoveOperations(relationMetadata, dbValue, newEntities, false);
|
||||
operations.push(...subOperations);
|
||||
}
|
||||
}
|
||||
|
||||
const subOperations = this.findJunctionRemoveOperations(relationMetadata, subEntity, newEntities);
|
||||
operations.push(...subOperations);
|
||||
});
|
||||
return operations;
|
||||
}, <JunctionInsertOperation[]> []);
|
||||
}
|
||||
|
||||
19
test/functional/persistence/many-to-many/entity/Category.ts
Normal file
19
test/functional/persistence/many-to-many/entity/Category.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {Table} from "../../../../../src/decorator/tables/Table";
|
||||
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
|
||||
import {Post} from "./Post";
|
||||
import {Column} from "../../../../../src/decorator/columns/Column";
|
||||
import {ManyToMany} from "../../../../../src/decorator/relations/ManyToMany";
|
||||
|
||||
@Table()
|
||||
export class Category {
|
||||
|
||||
@PrimaryColumn("int", { generated: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@ManyToMany(type => Post, post => post.categories)
|
||||
posts: Post[];
|
||||
|
||||
}
|
||||
21
test/functional/persistence/many-to-many/entity/Post.ts
Normal file
21
test/functional/persistence/many-to-many/entity/Post.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {Category} from "./Category";
|
||||
import {Table} from "../../../../../src/decorator/tables/Table";
|
||||
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
|
||||
import {Column} from "../../../../../src/decorator/columns/Column";
|
||||
import {ManyToMany} from "../../../../../src/decorator/relations/ManyToMany";
|
||||
import {JoinTable} from "../../../../../src/decorator/relations/JoinTable";
|
||||
|
||||
@Table()
|
||||
export class Post {
|
||||
|
||||
@PrimaryColumn("int", { generated: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@ManyToMany(type => Category, category => category.posts)
|
||||
@JoinTable()
|
||||
categories: Category[]|null;
|
||||
|
||||
}
|
||||
19
test/functional/persistence/many-to-many/entity/User.ts
Normal file
19
test/functional/persistence/many-to-many/entity/User.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {Table} from "../../../../../src/decorator/tables/Table";
|
||||
import {PrimaryColumn} from "../../../../../src/decorator/columns/PrimaryColumn";
|
||||
import {Column} from "../../../../../src/decorator/columns/Column";
|
||||
import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne";
|
||||
import {Post} from "./Post";
|
||||
|
||||
@Table()
|
||||
export class User {
|
||||
|
||||
@PrimaryColumn("int", { generated: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@ManyToOne(type => Post)
|
||||
post: Post;
|
||||
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
import "reflect-metadata";
|
||||
import * as chai from "chai";
|
||||
import {expect} from "chai";
|
||||
import {Connection} from "../../../../src/connection/Connection";
|
||||
import {Repository} from "../../../../src/repository/Repository";
|
||||
import {Post} from "./entity/Post";
|
||||
import {Category} from "./entity/Category";
|
||||
import {CreateConnectionOptions} from "../../../../src/connection-manager/CreateConnectionOptions";
|
||||
import {createConnection} from "../../../../src/typeorm";
|
||||
import {User} from "./entity/User";
|
||||
|
||||
chai.should();
|
||||
chai.use(require("sinon-chai"));
|
||||
chai.use(require("chai-as-promised"));
|
||||
|
||||
describe("persistence > many-to-many", function() {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Configuration
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
const parameters: CreateConnectionOptions = {
|
||||
driver: "mysql",
|
||||
connection: {
|
||||
host: "192.168.99.100",
|
||||
port: 3306,
|
||||
username: "root",
|
||||
password: "admin",
|
||||
database: "test",
|
||||
autoSchemaCreate: true,
|
||||
logging: {
|
||||
logFailedQueryError: true
|
||||
}
|
||||
},
|
||||
entities: [Post, Category, User]
|
||||
};
|
||||
// connect to db
|
||||
let connection: Connection;
|
||||
before(function() {
|
||||
return createConnection(parameters)
|
||||
.then(con => connection = con)
|
||||
.catch(e => {
|
||||
console.log("Error during connection to db: " + e);
|
||||
throw e;
|
||||
});
|
||||
});
|
||||
|
||||
after(function() {
|
||||
connection.close();
|
||||
});
|
||||
|
||||
// clean up database before each test
|
||||
function reloadDatabase() {
|
||||
return connection.driver
|
||||
.clearDatabase()
|
||||
.then(() => connection.syncSchema())
|
||||
.catch(e => console.log("Error during schema re-creation: ", e));
|
||||
}
|
||||
|
||||
let postRepository: Repository<Post>;
|
||||
let categoryRepository: Repository<Category>;
|
||||
let userRepository: Repository<User>;
|
||||
before(function() {
|
||||
postRepository = connection.getRepository(Post);
|
||||
categoryRepository = connection.getRepository(Category);
|
||||
userRepository = connection.getRepository(User);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Specifications
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
describe("add exist element to exist object with empty one-to-many relation and save it", function() {
|
||||
let newPost: Post, newCategory: Category, newUser: User, loadedUser: User;
|
||||
|
||||
before(reloadDatabase);
|
||||
|
||||
// save a new category
|
||||
before(function () {
|
||||
newCategory = categoryRepository.create();
|
||||
newCategory.name = "Animals";
|
||||
return categoryRepository.persist(newCategory);
|
||||
});
|
||||
|
||||
// save a new post
|
||||
before(function() {
|
||||
newPost = postRepository.create();
|
||||
newPost.title = "All about animals";
|
||||
return postRepository.persist(newPost);
|
||||
});
|
||||
|
||||
// save a new user
|
||||
before(function() {
|
||||
newUser = userRepository.create();
|
||||
newUser.name = "Dima";
|
||||
return userRepository.persist(newUser);
|
||||
});
|
||||
|
||||
// now add a category to the post and attach post to a user and save a user
|
||||
before(function() {
|
||||
newPost.categories = [newCategory];
|
||||
newUser.post = newPost;
|
||||
return userRepository.persist(newUser);
|
||||
});
|
||||
|
||||
// load a post
|
||||
before(function() {
|
||||
return userRepository
|
||||
.findOneById(1, { alias: "user", leftJoinAndSelect: { post: "user.post", categories: "post.categories" } })
|
||||
.then(post => loadedUser = post);
|
||||
});
|
||||
|
||||
it("should contain a new category", function () {
|
||||
expect(loadedUser).not.to.be.empty;
|
||||
expect(loadedUser.post).not.to.be.empty;
|
||||
expect(loadedUser.post.categories).not.to.be.empty;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user