mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
implemented basic entity persistment
This commit is contained in:
parent
9cd8d5aced
commit
35d363f887
@ -19,6 +19,74 @@ let options = {
|
||||
|
||||
TypeORM.createMysqlConnection(options, [Post, PostDetails, Image, ImageDetails, Cover, Category, Chapter]).then(connection => {
|
||||
|
||||
let postRepository = connection.getRepository<Post>(Post);
|
||||
|
||||
let postCover = new Cover();
|
||||
postCover.url = "http://covers.com/post.jpg";
|
||||
|
||||
let details = new PostDetails();
|
||||
details.meta = "hello";
|
||||
details.comment = "wow";
|
||||
|
||||
let category1 = new Category();
|
||||
category1.description = "about post1";
|
||||
|
||||
let category2 = new Category();
|
||||
category2.description = "about post2";
|
||||
|
||||
let image = new Image();
|
||||
image.name = "post.jpg";
|
||||
|
||||
let post = new Post();
|
||||
post.title = "Hello post";
|
||||
post.text = "Hello world of post#1";
|
||||
post.cover = postCover;
|
||||
post.details = details;
|
||||
post.images.push(image);
|
||||
post.categories = [category1, category2];
|
||||
|
||||
postRepository.persist(post).then(result => {
|
||||
|
||||
const qb = postRepository.createQueryBuilder("post")
|
||||
.leftJoinAndSelect("post.details", "details")
|
||||
.leftJoinAndSelect("post.images", "images")
|
||||
// .leftJoinAndSelect("post.coverId", "coverId")
|
||||
.leftJoinAndSelect("post.categories", "categories")
|
||||
.where("post.id=:id")
|
||||
.setParameter("id", 6);
|
||||
|
||||
return qb
|
||||
.getSingleResult()
|
||||
.then(post => {
|
||||
console.log("loaded post: ", post);
|
||||
|
||||
let category1 = new Category();
|
||||
category1.id = 12;
|
||||
category1.description = "about cat#12";
|
||||
|
||||
let category2 = new Category();
|
||||
category2.id = 52;
|
||||
category2.description = "about cat#52";
|
||||
|
||||
let image = new Image();
|
||||
image.name = "second image of the post";
|
||||
|
||||
//post
|
||||
post.title = "This! is updated post$";
|
||||
post.text = "Hello world of post#4";
|
||||
post.categories = [category2, category1];
|
||||
post.images.push(image);
|
||||
return postRepository.persist(post);
|
||||
|
||||
})
|
||||
.then(() => qb.getSingleResult())
|
||||
.then(reloadedPost => console.log("reloadedPost: ", reloadedPost));
|
||||
})
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.log(error.stack ? error.stack : error));
|
||||
|
||||
return;
|
||||
|
||||
const postJson = {
|
||||
id: 1, // changed
|
||||
text: "This is post about hello", // changed
|
||||
@ -59,7 +127,6 @@ TypeORM.createMysqlConnection(options, [Post, PostDetails, Image, ImageDetails,
|
||||
}]
|
||||
};
|
||||
|
||||
let postRepository = connection.getRepository<Post>(Post);
|
||||
let entity = postRepository.create(postJson);
|
||||
return postRepository.initialize(postJson)
|
||||
.then(result => {
|
||||
|
||||
@ -34,7 +34,7 @@ export class Post {
|
||||
isCascadeUpdate: true,
|
||||
isCascadeRemove: true
|
||||
})
|
||||
images: Image[];
|
||||
images: Image[] = [];
|
||||
|
||||
@OneToMany<Image>(type => Image, image => image.secondaryPost)
|
||||
secondaryImages: Image[];
|
||||
@ -53,6 +53,7 @@ export class Post {
|
||||
|
||||
@ManyToMany<Category>(true, type => Category, category => category.posts, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeUpdate: true,
|
||||
isCascadeRemove: true
|
||||
})
|
||||
categories: Category[];
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import {ConnectionOptions} from "../connection/ConnectionOptions";
|
||||
import {SchemaBuilder} from "../schema-builder/SchemaBuilder";
|
||||
import {QueryBuilder} from "../query-builder/QueryBuilder";
|
||||
import {EntityMetadata} from "../metadata-builder/metadata/EntityMetadata";
|
||||
import {Connection} from "../connection/Connection";
|
||||
|
||||
/**
|
||||
|
||||
@ -1,17 +1,62 @@
|
||||
import {EntityMetadata} from "../metadata-builder/metadata/EntityMetadata";
|
||||
import {ColumnMetadata} from "../metadata-builder/metadata/ColumnMetadata";
|
||||
import {EntityDifferenceMap} from "./Repository";
|
||||
import {RelationMetadata} from "../metadata-builder/metadata/RelationMetadata";
|
||||
import metadata = Reflect.metadata;
|
||||
|
||||
interface EntityWithId {
|
||||
export interface EntityWithId {
|
||||
id: any;
|
||||
entity: any;
|
||||
}
|
||||
|
||||
interface UpdateOperation {
|
||||
export interface InsertOperation {
|
||||
entity: any;
|
||||
entityId: number;
|
||||
}
|
||||
|
||||
export interface UpdateOperation {
|
||||
entity: any;
|
||||
columns: ColumnMetadata[];
|
||||
}
|
||||
|
||||
export interface JunctionInsertOperation {
|
||||
metadata: EntityMetadata;
|
||||
entity1: any;
|
||||
entity2: any;
|
||||
}
|
||||
|
||||
export interface JunctionRemoveOperation {
|
||||
metadata: EntityMetadata;
|
||||
entity1: any;
|
||||
entity2: any;
|
||||
}
|
||||
|
||||
export class PersistOperation {
|
||||
inserts: InsertOperation[];
|
||||
removes: any[];
|
||||
updates: UpdateOperation[];
|
||||
junctionInserts: JunctionInsertOperation[];
|
||||
junctionRemoves: JunctionRemoveOperation[];
|
||||
|
||||
constructor(inserts: any[],
|
||||
removes: any[],
|
||||
updates: UpdateOperation[],
|
||||
junctionInserts: JunctionInsertOperation[],
|
||||
junctionRemoves: JunctionRemoveOperation[]) {
|
||||
|
||||
this.inserts = inserts;
|
||||
this.removes = removes;
|
||||
this.updates = updates;
|
||||
this.junctionInserts = junctionInserts;
|
||||
this.junctionRemoves = junctionRemoves;
|
||||
}
|
||||
}
|
||||
|
||||
/*export class JunctionEntity {
|
||||
relation: RelationMetadata;
|
||||
entity1: any;
|
||||
entity2: any;
|
||||
}*/
|
||||
|
||||
export class EntityPersistOperationsBuilder {
|
||||
|
||||
// 1. collect all exist objects from the db entity
|
||||
@ -28,6 +73,8 @@ export class EntityPersistOperationsBuilder {
|
||||
// 5.1. compare with entities from the collection of db entities, find difference and generate a change set
|
||||
// 5.2. check if relation has rights to do cascade operation and throw exception if it cannot
|
||||
// 5.3.
|
||||
// 6. go throw all relations and find junction
|
||||
// 6.1.
|
||||
|
||||
// if relation has "all" then all of above:
|
||||
// if relation has "insert" it can insert a new entity
|
||||
@ -41,13 +88,15 @@ export class EntityPersistOperationsBuilder {
|
||||
/**
|
||||
* Finds columns and relations from entity2 which does not exist or does not match in entity1.
|
||||
*/
|
||||
difference(metadata: EntityMetadata, entity1: any, entity2: any): EntityDifferenceMap[] {
|
||||
const diffMaps: EntityDifferenceMap[] = [];
|
||||
difference(metadata: EntityMetadata, entity1: any, entity2: any): PersistOperation {
|
||||
const dbEntities = this.extractObjectsById(entity1, metadata);
|
||||
const allEntities = this.extractObjectsById(entity2, metadata);
|
||||
const insertedEntities = this.findCascadeInsertedEntities(entity2, metadata, dbEntities);
|
||||
const removedEntities = this.findCascadeRemovedEntities(entity1, metadata, allEntities);
|
||||
const updatedEntities = this.findCascadeUpdateEntities(metadata, entity1, entity2);
|
||||
const insertOperations = this.findCascadeInsertedEntities(metadata, entity2, dbEntities, null);
|
||||
const removeOperations = this.findCascadeRemovedEntities(metadata, entity1, allEntities);
|
||||
const updateOperations = this.findCascadeUpdateEntities(metadata, entity1, entity2, null);
|
||||
const junctionInsertOperations = this.findJunctionInsertOperations(metadata, entity2, dbEntities);
|
||||
const junctionRemoveOperations = this.findJunctionRemoveOperations(metadata, entity1, allEntities);
|
||||
//const insertJunctionOperations = ;//this.a();
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log("DB ENTITIES");
|
||||
console.log("---------------------------------------------------------");
|
||||
@ -59,106 +108,158 @@ export class EntityPersistOperationsBuilder {
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log("INSERTED ENTITIES");
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(insertedEntities);
|
||||
console.log(insertOperations);
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log("REMOVED ENTITIES");
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(removedEntities);
|
||||
console.log(removeOperations);
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log("UPDATED ENTITIES");
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(updatedEntities);
|
||||
console.log(updateOperations);
|
||||
console.log("---------------------------------------------------------");
|
||||
return diffMaps;
|
||||
console.log("JUNCTION INSERT ENTITIES");
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(junctionInsertOperations);
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log("JUNCTION REMOVE ENTITIES");
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(junctionRemoveOperations);
|
||||
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);
|
||||
|
||||
// todo: implement duplicates removal later
|
||||
// remove duplicates from inserted entities (todo: shall we check if duplicates differ and throw exception?)
|
||||
/*const uniqueInsertOperations = insertOperations.map(insertOperation => {
|
||||
const otherInsertOperations = insertOperations.filter(o => o !== insertOperation && o.entity === insertOperation.entity);
|
||||
const otherRelations = otherInsertOperations.reduce((relations, o) => {
|
||||
return relations.concat(o.relations);
|
||||
}, <RelationMetadata[]> []);
|
||||
return <InsertOperation> {
|
||||
entity: insertOperation.entity,
|
||||
relations: insertOperation.relations.concat(otherRelations)
|
||||
};
|
||||
});*/
|
||||
|
||||
// sort inserts
|
||||
return new PersistOperation(
|
||||
insertOperations,
|
||||
removeOperations,
|
||||
updateOperations,
|
||||
junctionInsertOperations,
|
||||
junctionRemoveOperations
|
||||
);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private diffColumns(metadata: EntityMetadata, newEntity: any, dbEntity: any) {
|
||||
return metadata.columns
|
||||
.filter(column => !column.isVirtual)
|
||||
.filter(column => newEntity[column.propertyName] !== dbEntity[column.propertyName]);
|
||||
private findJunctionInsertOperations(metadata: EntityMetadata, newEntity: any, dbEntities: EntityWithId[]): JunctionInsertOperation[] {
|
||||
const dbEntity = dbEntities.find(dbEntity => {
|
||||
return dbEntity.id === newEntity[metadata.primaryColumn.name] && dbEntity.entity.constructor.name === metadata.name;
|
||||
});
|
||||
return metadata.relations
|
||||
.filter(relation => relation.isManyToMany)
|
||||
.filter(relation => newEntity[relation.propertyName] instanceof Array)
|
||||
.reduce((operations, relation) => {
|
||||
const relationMetadata = relation.relatedEntityMetadata;
|
||||
const relationIdProperty = relationMetadata.primaryColumn.name;
|
||||
newEntity[relation.propertyName].map((subEntity: any) => {
|
||||
|
||||
const has = !dbEntity ||
|
||||
!dbEntity.entity[relation.propertyName] ||
|
||||
!dbEntity.entity[relation.propertyName].find((e: any) => e[relationIdProperty] === subEntity[relationIdProperty]);
|
||||
|
||||
if (has) {
|
||||
operations.push({
|
||||
metadata: relation.junctionEntityMetadata,
|
||||
entity1: newEntity,
|
||||
entity2: subEntity
|
||||
});
|
||||
}
|
||||
|
||||
const subOperations = this.findJunctionInsertOperations(relationMetadata, subEntity, dbEntities);
|
||||
operations = operations.concat(subOperations);
|
||||
});
|
||||
return operations;
|
||||
}, <JunctionInsertOperation[]> []);
|
||||
}
|
||||
|
||||
private findCascadeUpdateEntities(metadata: EntityMetadata, newEntity: any, dbEntity: any): UpdateOperation[] {
|
||||
const updatedColumns = [{
|
||||
entity: newEntity,
|
||||
columns: this.diffColumns(metadata, newEntity, dbEntity)
|
||||
}];
|
||||
private findJunctionRemoveOperations(metadata: EntityMetadata, dbEntity: any, newEntities: EntityWithId[]): JunctionInsertOperation[] {
|
||||
if (!dbEntity) // if new entity is persisted then it does not have anything to be deleted
|
||||
return [];
|
||||
|
||||
const newEntity = newEntities.find(newEntity => {
|
||||
return newEntity.id === dbEntity[metadata.primaryColumn.name] && newEntity.entity.constructor.name === metadata.name;
|
||||
});
|
||||
return metadata.relations
|
||||
.filter(relation => newEntity[relation.propertyName] && dbEntity[relation.propertyName])
|
||||
.reduce((updatedColumns, relation) => {
|
||||
const relMetadata = relation.relatedEntityMetadata;
|
||||
const relationIdColumnName = relMetadata.primaryColumn.name;
|
||||
if (newEntity[relation.propertyName] instanceof Array) {
|
||||
newEntity[relation.propertyName].forEach((subEntity: any) => {
|
||||
const subDbEntity = (dbEntity[relation.propertyName] as any[]).find(subDbEntity => {
|
||||
return subDbEntity[relationIdColumnName] === subEntity[relationIdColumnName];
|
||||
.filter(relation => relation.isManyToMany)
|
||||
.filter(relation => dbEntity[relation.propertyName] instanceof Array)
|
||||
.reduce((operations, relation) => {
|
||||
const relationMetadata = relation.relatedEntityMetadata;
|
||||
const relationIdProperty = relationMetadata.primaryColumn.name;
|
||||
dbEntity[relation.propertyName].map((subEntity: any) => {
|
||||
|
||||
const has = !newEntity ||
|
||||
!newEntity.entity[relation.propertyName] ||
|
||||
!newEntity.entity[relation.propertyName].find((e: any) => e[relationIdProperty] === subEntity[relationIdProperty]);
|
||||
|
||||
if (has) {
|
||||
operations.push({
|
||||
metadata: relation.junctionEntityMetadata,
|
||||
entity1: dbEntity,
|
||||
entity2: subEntity
|
||||
});
|
||||
if (subDbEntity) {
|
||||
const relationUpdatedColumns = this.findCascadeUpdateEntities(relMetadata, subEntity, subDbEntity);
|
||||
if (!relation.isCascadeUpdate)
|
||||
throw new Error("Cascade updates are not allowed in " + metadata.name + "#" + relation.propertyName);
|
||||
|
||||
updatedColumns = updatedColumns.concat(relationUpdatedColumns);
|
||||
}
|
||||
|
||||
});
|
||||
} else {
|
||||
const relationUpdatedColumns = this.findCascadeUpdateEntities(relMetadata, newEntity[relation.propertyName], dbEntity[relation.propertyName]);
|
||||
if (updatedColumns.length > 0) {
|
||||
if (!relation.isCascadeUpdate)
|
||||
throw new Error("Cascade updates are not allowed in " + metadata.name + "#" + relation.propertyName);
|
||||
|
||||
updatedColumns = updatedColumns.concat(relationUpdatedColumns);
|
||||
}
|
||||
}
|
||||
|
||||
return updatedColumns;
|
||||
}, updatedColumns);
|
||||
const subOperations = this.findJunctionRemoveOperations(relationMetadata, subEntity, newEntities);
|
||||
operations = operations.concat(subOperations);
|
||||
});
|
||||
return operations;
|
||||
}, <JunctionInsertOperation[]> []);
|
||||
}
|
||||
|
||||
private findCascadeInsertedEntities(newEntity: any, metadata: EntityMetadata, dbEntities: any[]): any[] {
|
||||
private findCascadeInsertedEntities(metadata: EntityMetadata, newEntity: any, dbEntities: EntityWithId[], fromRelation: RelationMetadata): any[] {
|
||||
|
||||
const insertedEntities: any[] = [];
|
||||
const isObjectNew = !dbEntities.find(dbEntity => {
|
||||
return dbEntity.id === newEntity[metadata.primaryColumn.name] && dbEntity.entity.constructor.name === metadata.name;
|
||||
});
|
||||
|
||||
if (isObjectNew && fromRelation && !fromRelation.isCascadeInsert)
|
||||
throw new Error("Cascade inserts are not allowed in " + metadata.name + "#" + fromRelation.propertyName);
|
||||
|
||||
if (isObjectNew)
|
||||
insertedEntities.push({
|
||||
entity: newEntity
|
||||
});
|
||||
|
||||
return metadata.relations
|
||||
.filter(relation => !!newEntity[relation.propertyName])
|
||||
.reduce((insertedEntities, relation) => {
|
||||
const relationIdColumnName = relation.relatedEntityMetadata.primaryColumn.name;
|
||||
const relMetadata = relation.relatedEntityMetadata;
|
||||
if (newEntity[relation.propertyName] instanceof Array) {
|
||||
newEntity[relation.propertyName].forEach((subEntity: any) => {
|
||||
const isObjectNew = !dbEntities.find(dbEntity => {
|
||||
return dbEntity.id === subEntity[relationIdColumnName] && dbEntity.entity === relMetadata.name;
|
||||
});
|
||||
if (isObjectNew) {
|
||||
if (!relation.isCascadeInsert)
|
||||
throw new Error("Cascade inserts are not allowed in " + metadata.name + "#" + relation.propertyName);
|
||||
|
||||
insertedEntities.push(subEntity);
|
||||
}
|
||||
|
||||
insertedEntities = insertedEntities.concat(this.findCascadeInsertedEntities(subEntity, relMetadata, dbEntities));
|
||||
const value = newEntity[relation.propertyName];
|
||||
if (value instanceof Array) {
|
||||
value.forEach((subEntity: any) => {
|
||||
const subInserted = this.findCascadeInsertedEntities(relMetadata, subEntity, dbEntities, relation);
|
||||
insertedEntities = insertedEntities.concat(subInserted);
|
||||
});
|
||||
} else {
|
||||
const relationId = newEntity[relation.propertyName][relationIdColumnName];
|
||||
const isObjectNew = !dbEntities.find(dbEntity => {
|
||||
return dbEntity.id === relationId && dbEntity.entity === relMetadata.name;
|
||||
});
|
||||
if (isObjectNew) {
|
||||
if (!relation.isCascadeInsert)
|
||||
throw new Error("Cascade inserts are not allowed in " + metadata.name + "#" + relation.propertyName);
|
||||
|
||||
insertedEntities.push(newEntity[relation.propertyName]);
|
||||
}
|
||||
insertedEntities = insertedEntities.concat(this.findCascadeInsertedEntities(newEntity[relation.propertyName], relMetadata, dbEntities));
|
||||
const subInserted = this.findCascadeInsertedEntities(relMetadata, value, dbEntities, relation);
|
||||
insertedEntities = insertedEntities.concat(subInserted);
|
||||
}
|
||||
|
||||
return insertedEntities;
|
||||
}, []);
|
||||
}
|
||||
|
||||
private findCascadeRemovedEntities(dbEntity: any, metadata: EntityMetadata, newEntities: any[]): any[] {
|
||||
return insertedEntities;
|
||||
}, insertedEntities);
|
||||
}
|
||||
|
||||
private findCascadeRemovedEntities(metadata: EntityMetadata, dbEntity: any, newEntities: EntityWithId[]): any[] {
|
||||
if (!dbEntity)
|
||||
return [];
|
||||
|
||||
return metadata.relations
|
||||
.filter(relation => !!dbEntity[relation.propertyName])
|
||||
.reduce((removedEntities, relation) => {
|
||||
@ -167,22 +268,22 @@ export class EntityPersistOperationsBuilder {
|
||||
if (dbEntity[relation.propertyName] instanceof Array) {
|
||||
dbEntity[relation.propertyName].forEach((subEntity: any) => {
|
||||
const isObjectRemoved = !newEntities.find(newEntity => {
|
||||
return newEntity.id === subEntity[relationIdColumnName] && newEntity.entity === relMetadata.name;
|
||||
return newEntity.id === subEntity[relationIdColumnName] && newEntity.entity.constructor.name === relMetadata.name;
|
||||
});
|
||||
if (isObjectRemoved && relation.isCascadeRemove)
|
||||
removedEntities.push(subEntity);
|
||||
|
||||
removedEntities = removedEntities.concat(this.findCascadeRemovedEntities(subEntity, relMetadata, newEntities));
|
||||
removedEntities = removedEntities.concat(this.findCascadeRemovedEntities(relMetadata, subEntity, newEntities));
|
||||
});
|
||||
} else {
|
||||
const relationId = dbEntity[relation.propertyName][relationIdColumnName];
|
||||
const isObjectRemoved = !newEntities.find(newEntity => {
|
||||
return newEntity.id === relationId && newEntity.entity === relMetadata.name;
|
||||
return newEntity.id === relationId && newEntity.entity.constructor.name === relMetadata.name;
|
||||
});
|
||||
if (isObjectRemoved && relation.isCascadeRemove)
|
||||
removedEntities.push(dbEntity[relation.propertyName]);
|
||||
|
||||
removedEntities = removedEntities.concat(this.findCascadeRemovedEntities(dbEntity[relation.propertyName], relMetadata, newEntities));
|
||||
removedEntities = removedEntities.concat(this.findCascadeRemovedEntities(relMetadata, dbEntity[relation.propertyName], newEntities));
|
||||
}
|
||||
|
||||
return removedEntities;
|
||||
@ -193,6 +294,9 @@ export class EntityPersistOperationsBuilder {
|
||||
* Extracts unique objects from given entity and all its downside relations.
|
||||
*/
|
||||
private extractObjectsById(entity: any, metadata: EntityMetadata): EntityWithId[] {
|
||||
if (!entity)
|
||||
return [];
|
||||
|
||||
return metadata.relations
|
||||
.filter(relation => !!entity[relation.propertyName])
|
||||
.map(relation => {
|
||||
@ -207,9 +311,56 @@ export class EntityPersistOperationsBuilder {
|
||||
.reduce((col1: any[], col2: any[]) => col1.concat(col2), []) // flatten
|
||||
.concat([{
|
||||
id: entity[metadata.primaryColumn.name],
|
||||
entity: entity.constructor.name
|
||||
entity: entity//.constructor.name
|
||||
}])
|
||||
.filter((entity: any, index: number, allEntities: any[]) => allEntities.indexOf(entity) === index); // unique
|
||||
}
|
||||
|
||||
private findCascadeUpdateEntities(metadata: EntityMetadata, dbEntity: any, newEntity: any, fromRelation: RelationMetadata): UpdateOperation[] {
|
||||
if (!dbEntity)
|
||||
return [];
|
||||
|
||||
const updatedEntities: any[] = [];
|
||||
const diff = this.diffColumns(metadata, newEntity, dbEntity);
|
||||
if (diff.length && fromRelation && !fromRelation.isCascadeUpdate)
|
||||
throw new Error("Cascade updates are not allowed in " + metadata.name + "#" + fromRelation.propertyName);
|
||||
|
||||
if (diff.length) {
|
||||
updatedEntities.push({
|
||||
entity: newEntity,
|
||||
columns: diff
|
||||
});
|
||||
}
|
||||
|
||||
return metadata.relations
|
||||
.filter(relation => newEntity[relation.propertyName] && dbEntity[relation.propertyName])
|
||||
.reduce((updatedColumns, relation) => {
|
||||
const relMetadata = relation.relatedEntityMetadata;
|
||||
const relationIdColumnName = relMetadata.primaryColumn.name;
|
||||
if (newEntity[relation.propertyName] instanceof Array) {
|
||||
newEntity[relation.propertyName].forEach((subEntity: any) => {
|
||||
const subDbEntity = (dbEntity[relation.propertyName] as any[]).find(subDbEntity => {
|
||||
return subDbEntity[relationIdColumnName] === subEntity[relationIdColumnName];
|
||||
});
|
||||
if (subDbEntity) {
|
||||
const relationUpdatedColumns = this.findCascadeUpdateEntities(relMetadata, subDbEntity, subEntity, relation);
|
||||
updatedColumns = updatedColumns.concat(relationUpdatedColumns);
|
||||
}
|
||||
|
||||
});
|
||||
} else {
|
||||
const relationUpdatedColumns = this.findCascadeUpdateEntities(relMetadata, dbEntity[relation.propertyName], newEntity[relation.propertyName], relation);
|
||||
updatedColumns = updatedColumns.concat(relationUpdatedColumns);
|
||||
}
|
||||
|
||||
return updatedColumns;
|
||||
}, updatedEntities);
|
||||
}
|
||||
|
||||
private diffColumns(metadata: EntityMetadata, newEntity: any, dbEntity: any) {
|
||||
return metadata.columns
|
||||
.filter(column => !column.isVirtual)
|
||||
.filter(column => newEntity[column.propertyName] !== dbEntity[column.propertyName]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,36 +4,15 @@ import {OrmBroadcaster} from "../subscriber/OrmBroadcaster";
|
||||
import {QueryBuilder} from "../query-builder/QueryBuilder";
|
||||
import {PlainObjectToNewEntityTransformer} from "../query-builder/transformer/PlainObjectToNewEntityTransformer";
|
||||
import {PlainObjectToDatabaseEntityTransformer} from "../query-builder/transformer/PlainObjectToDatabaseEntityTransformer";
|
||||
import {ColumnMetadata} from "../metadata-builder/metadata/ColumnMetadata";
|
||||
import {RelationMetadata} from "../metadata-builder/metadata/RelationMetadata";
|
||||
import {EntityPersistOperationsBuilder} from "./EntityPersistOperationsBuilder";
|
||||
import {
|
||||
EntityPersistOperationsBuilder, PersistOperation, JunctionInsertOperation,
|
||||
InsertOperation, JunctionRemoveOperation, UpdateOperation
|
||||
} from "./EntityPersistOperationsBuilder";
|
||||
|
||||
// todo: think how we can implement queryCount, queryManyAndCount
|
||||
// todo: extract non safe methods from repository (removeById, removeByConditions)
|
||||
|
||||
interface RelationDifference {
|
||||
value: any;
|
||||
relation: RelationMetadata;
|
||||
}
|
||||
|
||||
export interface EntityDifferenceMap {
|
||||
entity: any;
|
||||
columns: ColumnMetadata[];
|
||||
changedRelations: RelationDifference[];
|
||||
removedRelations: RelationDifference[];
|
||||
addedRelations: RelationDifference[];
|
||||
}
|
||||
|
||||
interface EntityWithId {
|
||||
id: any;
|
||||
entity: any;
|
||||
}
|
||||
|
||||
interface UpdateOperation {
|
||||
entity: any;
|
||||
columns: ColumnMetadata[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Repository is supposed to work with your entity objects. Find entities, insert, update, delete, etc.
|
||||
*/
|
||||
@ -96,125 +75,166 @@ export class Repository<Entity> {
|
||||
return Object.assign(this.metadata.create(), entity1, entity2);
|
||||
}
|
||||
|
||||
// 1. collect all exist objects from the db entity
|
||||
// 2. collect all objects from the new entity
|
||||
// 3. first need to go throw all relations of the new entity and:
|
||||
// 3.1. find all objects that are new (e.g. cascade="insert") by comparing ids from the exist objects
|
||||
// 3.2. check if relation has rights to do cascade operation and throw exception if it cannot
|
||||
// 3.3. save new objects for insert operation
|
||||
// 4. second need to go throw all relations of the db entity and:
|
||||
// 4.1. find all objects that are removed (e.g. cascade="remove") by comparing data with collected objects of the new entity
|
||||
// 4.2. check if relation has rights to do cascade operation and throw exception if it cannot
|
||||
// 4.3. save new objects for remove operation
|
||||
// 5. third need to go throw collection of all new entities
|
||||
// 5.1. compare with entities from the collection of db entities, find difference and generate a change set
|
||||
// 5.2. check if relation has rights to do cascade operation and throw exception if it cannot
|
||||
// 5.3.
|
||||
|
||||
// if relation has "all" then all of above:
|
||||
// if relation has "insert" it can insert a new entity
|
||||
// if relation has "update" it can only update related entity
|
||||
// if relation has "remove" it can only remove related entity
|
||||
|
||||
/**
|
||||
* Finds columns and relations from entity2 which does not exist or does not match in entity1.
|
||||
*/
|
||||
difference(entity1: Entity, entity2: Entity): EntityDifferenceMap[] {
|
||||
difference(entity1: Entity, entity2: Entity): PersistOperation {
|
||||
const builder = new EntityPersistOperationsBuilder();
|
||||
return builder.difference(this.metadata, entity1, entity2);
|
||||
}
|
||||
|
||||
findDifference(e1: any, e2: any, metadata: EntityMetadata, diffMaps: EntityDifferenceMap[]) {
|
||||
const diffColumns = metadata.columns
|
||||
.filter(column => !column.isVirtual)
|
||||
.filter(column => e1[column.propertyName] !== e2[column.propertyName]);
|
||||
|
||||
const changedRelations = metadata.relations
|
||||
.filter(relation => relation.isOneToOne || relation.isManyToOne)
|
||||
.filter(relation => e1[relation.propertyName] && e2[relation.propertyName])
|
||||
.filter(relation => {
|
||||
const relationId = relation.relatedEntityMetadata.primaryColumn.name;
|
||||
return e1[relation.propertyName][relationId] !== e2[relation.propertyName][relationId];
|
||||
})
|
||||
.map(relation => ({ value: e2[relation.propertyName], relation: relation }));
|
||||
|
||||
const removedRelations = metadata.relations
|
||||
.filter(relation => relation.isOneToOne || relation.isManyToOne)
|
||||
.filter(relation => e1[relation.propertyName] && !e2[relation.propertyName])
|
||||
.map(relation => ({ value: e2[relation.propertyName], relation: relation }));
|
||||
|
||||
const addedRelations = metadata.relations
|
||||
.filter(relation => relation.isOneToOne || relation.isManyToOne)
|
||||
.filter(relation => !e1[relation.propertyName] && e2[relation.propertyName])
|
||||
.map(relation => ({ value: e2[relation.propertyName], relation: relation }));
|
||||
|
||||
const addedManyRelations = metadata.relations
|
||||
.filter(relation => relation.isManyToMany || relation.isOneToMany)
|
||||
.filter(relation => e2[relation.propertyName] instanceof Array)
|
||||
.map(relation => {
|
||||
const relationId = relation.relatedEntityMetadata.primaryColumn.name;
|
||||
return e2[relation.propertyName].filter((e2Item: any) => {
|
||||
if (!e1[relation.propertyName]) return false;
|
||||
return !e1[relation.propertyName].find((e1Item: any) => e1Item[relationId] === e2Item[relationId]);
|
||||
}).map((e2Item: any) => {
|
||||
return { value: e2Item, relation: relation };
|
||||
persist(entity: Entity) {
|
||||
const promise = !this.hasId(entity) ? Promise.resolve(null) : this.initialize(entity);
|
||||
//if (!this.hasId(entity)) { // do insert
|
||||
return promise.then(dbEntity => {
|
||||
const persistOperations = this.difference(dbEntity, entity);
|
||||
// create update queries based on diff map
|
||||
return Promise.all(persistOperations.inserts.map(operation => {
|
||||
return this.insert(operation.entity).then((result: any) => {
|
||||
operation.entityId = result.insertId;
|
||||
});
|
||||
}).reduce((a: EntityDifferenceMap[], b: EntityDifferenceMap[]) => a.concat(b), []);
|
||||
|
||||
const removedManyRelations = metadata.relations
|
||||
.filter(relation => relation.isManyToMany || relation.isOneToMany)
|
||||
.filter(relation => e2[relation.propertyName] instanceof Array)
|
||||
.map(relation => {
|
||||
const relationId = relation.relatedEntityMetadata.primaryColumn.name;
|
||||
return e1[relation.propertyName].filter((e1Item: any) => {
|
||||
if (!e2[relation.propertyName]) return false;
|
||||
return !e2[relation.propertyName].find((e2Item: any) => e2Item[relationId] === e1Item[relationId]);
|
||||
}).map((e1Item: any) => {
|
||||
return { value: e1Item, relation: relation };
|
||||
});
|
||||
}).reduce((a: EntityDifferenceMap[], b: EntityDifferenceMap[]) => a.concat(b), []);
|
||||
})).then(() => { // insert junction table insertions
|
||||
|
||||
metadata.relations
|
||||
.filter(relation => e2[relation.propertyName])
|
||||
.filter(relation => relation.isOneToOne || relation.isManyToOne)
|
||||
.forEach(relation => {
|
||||
const property = relation.propertyName;
|
||||
this.findDifference(e1[property] || {}, e2[property], relation.relatedEntityMetadata, diffMaps);
|
||||
});
|
||||
return Promise.all(persistOperations.junctionInserts.map(junctionOperation => {
|
||||
return this.insertJunctions(junctionOperation, persistOperations.inserts);
|
||||
}));
|
||||
}).then(() => { // remove junction table insertions
|
||||
|
||||
metadata.relations
|
||||
.filter(relation => /*e1[relation.propertyName] && */e2[relation.propertyName] instanceof Array)
|
||||
.filter(relation => relation.isManyToMany || relation.isOneToMany)
|
||||
.forEach(relation => {
|
||||
const relationId = relation.relatedEntityMetadata.primaryColumn.name;
|
||||
const e1Items = e1[relation.propertyName] || [];
|
||||
e2[relation.propertyName].map((e2Item: any) => {
|
||||
const e1Item = e1Items.find((e1Item: any) => e1Item[relationId] === e2Item[relationId]);
|
||||
this.findDifference(e1Item || {}, e2Item, relation.relatedEntityMetadata, diffMaps);
|
||||
});
|
||||
});
|
||||
return Promise.all(persistOperations.junctionRemoves.map(junctionOperation => {
|
||||
return this.removeJunctions(junctionOperation);
|
||||
}));
|
||||
|
||||
}).then(() => {
|
||||
return Promise.all(persistOperations.inserts.map(operation => {
|
||||
|
||||
const meta = this.connection.getMetadata(operation.entity.constructor);
|
||||
const oneToOneManyToOneUpdates = Promise.all(meta.relations.map(relation => {
|
||||
|
||||
let insertOperationUpdates: Promise<any>, updateOperationUpdates: Promise<any>;
|
||||
|
||||
if (operation.entity[relation.propertyName] instanceof Array && relation.isOneToMany) {
|
||||
|
||||
insertOperationUpdates = Promise.all(persistOperations.inserts.filter(o => {
|
||||
return operation.entity[relation.propertyName].indexOf(o.entity) !== -1;
|
||||
}).map(o => {
|
||||
const oMetadata = this.connection.getMetadata(o.entity.constructor);
|
||||
const inverseRelation = relation.inverseRelation;
|
||||
const query = `UPDATE ${oMetadata.table.name} SET ${inverseRelation.name}='${operation.entityId}' WHERE ${oMetadata.primaryColumn.name}='${o.entityId}'`;
|
||||
return this.connection.driver.query(query);
|
||||
}));
|
||||
|
||||
updateOperationUpdates = Promise.all(persistOperations.updates.filter(o => {
|
||||
return operation.entity[relation.propertyName].indexOf(o.entity) !== -1;
|
||||
}).map(o => {
|
||||
const oMetadata = this.connection.getMetadata(o.entity.constructor);
|
||||
const inverseRelation = relation.inverseRelation;
|
||||
const id = operation.entity[meta.primaryColumn.name];
|
||||
const query = `UPDATE ${oMetadata.table.name} SET ${inverseRelation.name}='${operation.entityId}' WHERE ${oMetadata.primaryColumn.name}='${id}'`;
|
||||
return this.connection.driver.query(query);
|
||||
}));
|
||||
|
||||
} else {
|
||||
|
||||
insertOperationUpdates = Promise.all(persistOperations.inserts.filter(o => {
|
||||
return operation.entity[relation.propertyName] === o.entity; // only one-to-one and many-to-one
|
||||
}).map(o => {
|
||||
const query = `UPDATE ${meta.table.name} SET ${relation.name}='${o.entityId}' WHERE ${meta.primaryColumn.name}='${operation.entityId}'`;
|
||||
return this.connection.driver.query(query);
|
||||
}));
|
||||
|
||||
updateOperationUpdates = Promise.all(persistOperations.updates.filter(o => {
|
||||
return operation.entity[relation.propertyName] === o.entity; // only one-to-one and many-to-one
|
||||
}).map(o => {
|
||||
const reverseMeta = this.connection.getMetadata(o.entity.constructor);
|
||||
const id = o.entity[reverseMeta.primaryColumn.name];
|
||||
const query = `UPDATE ${meta.table.name} SET ${relation.name}='${id}' WHERE ${meta.primaryColumn.name}='${operation.entityId}'`;
|
||||
return this.connection.driver.query(query);
|
||||
}));
|
||||
}
|
||||
|
||||
return Promise.all([insertOperationUpdates, updateOperationUpdates]);
|
||||
}));
|
||||
|
||||
return Promise.all([oneToOneManyToOneUpdates]);
|
||||
}));
|
||||
}).then(() => { // perform updates
|
||||
|
||||
return Promise.all(persistOperations.updates.map(updateOperation => {
|
||||
return this.update(updateOperation);
|
||||
}));
|
||||
|
||||
if (diffColumns.length > 0 || changedRelations.length > 0 || removedRelations.length > 0 || addedRelations.length > 0)
|
||||
diffMaps.push({
|
||||
entity: e2,
|
||||
columns: diffColumns,
|
||||
changedRelations: changedRelations,
|
||||
removedRelations: removedRelations.concat(removedManyRelations),
|
||||
addedRelations: addedRelations.concat(addedManyRelations)
|
||||
});
|
||||
//} else {
|
||||
// do update
|
||||
/*return this.initialize(entity).then(dbEntity => {
|
||||
const persistOperations = this.difference(dbEntity, entity);
|
||||
// create update queries based on diff map
|
||||
return Promise.all(persistOperations.inserts.map(operation => {
|
||||
return this.insert(operation.entity);
|
||||
}));
|
||||
});*/
|
||||
//}
|
||||
});
|
||||
}
|
||||
|
||||
private update(updateOperation: UpdateOperation) {
|
||||
const entity = updateOperation.entity;
|
||||
const metadata = this.connection.getMetadata(entity.constructor);
|
||||
const values = updateOperation.columns.map(column => {
|
||||
return column.name + "='" + entity[column.propertyName] + "'";
|
||||
});
|
||||
|
||||
const query = `UPDATE ${metadata.table.name} SET ${values} WHERE ${metadata.primaryColumn.name}='${metadata.getEntityId(entity)}'` ;
|
||||
return this.connection.driver.query(query);
|
||||
}
|
||||
|
||||
persist(entity: Entity) {
|
||||
if (!this.hasId(entity)) {
|
||||
// do insert
|
||||
} else {
|
||||
// do update
|
||||
this.initialize(entity)
|
||||
.then(dbEntity => {
|
||||
const diffMap = this.difference(dbEntity, entity);
|
||||
// create update queries based on diff map
|
||||
});
|
||||
}
|
||||
private insert(entity: any) {
|
||||
const metadata = this.connection.getMetadata(entity.constructor);
|
||||
const columns = metadata.columns
|
||||
.filter(column => !column.isVirtual)
|
||||
.filter(column => entity.hasOwnProperty(column.propertyName))
|
||||
.map(column => column.name);
|
||||
/*const virtualColumns = metadata.columns
|
||||
.filter(column => column.isVirtual)
|
||||
.filter(column => entity.hasOwnProperty(column.propertyName))
|
||||
.map(column => column.name);*/
|
||||
const values = metadata.columns
|
||||
.filter(column => !column.isVirtual)
|
||||
.filter(column => entity.hasOwnProperty(column.propertyName))
|
||||
.map(column => "'" + entity[column.propertyName] + "'");
|
||||
/*const virtualValues = metadata.columns
|
||||
.filter(column => column.isVirtual)
|
||||
.filter(column => entity.hasOwnProperty(column.propertyName))
|
||||
.map(column => "'" + entity[column.propertyName] + "'");
|
||||
const allColumns = columns.concat(virtualColumns);
|
||||
const allVolumes = values.concat(virtualValues);*/
|
||||
|
||||
const query = `INSERT INTO ${metadata.table.name}(${columns.join(",")}) VALUES (${values.join(",")})`;
|
||||
return this.connection.driver.query(query);
|
||||
}
|
||||
|
||||
private insertJunctions(junctionOperation: JunctionInsertOperation, insertOperations: InsertOperation[]) {
|
||||
const junctionMetadata = junctionOperation.metadata;
|
||||
const metadata1 = this.connection.getMetadata(junctionOperation.entity1.constructor);
|
||||
const metadata2 = this.connection.getMetadata(junctionOperation.entity2.constructor);
|
||||
const columns = junctionMetadata.columns.map(column => column.name);
|
||||
const id1 = junctionOperation.entity1[metadata1.primaryColumn.name] || insertOperations.find(o => o.entity === junctionOperation.entity1).entityId;
|
||||
const id2 = junctionOperation.entity2[metadata2.primaryColumn.name] || insertOperations.find(o => o.entity === junctionOperation.entity2).entityId;
|
||||
const values = [id1, id2]; // todo: order may differ, find solution (column.table to compare with entity metadata table?)
|
||||
|
||||
const query = `INSERT INTO ${junctionMetadata.table.name}(${columns.join(",")}) VALUES (${values.join(",")})`;
|
||||
return this.connection.driver.query(query);
|
||||
}
|
||||
|
||||
private removeJunctions(junctionOperation: JunctionRemoveOperation) {
|
||||
const junctionMetadata = junctionOperation.metadata;
|
||||
const metadata1 = this.connection.getMetadata(junctionOperation.entity1.constructor);
|
||||
const metadata2 = this.connection.getMetadata(junctionOperation.entity2.constructor);
|
||||
const columns = junctionMetadata.columns.map(column => column.name);
|
||||
const id1 = junctionOperation.entity1[metadata1.primaryColumn.name];
|
||||
const id2 = junctionOperation.entity2[metadata2.primaryColumn.name];
|
||||
const query = `DELETE FROM ${junctionMetadata.table.name} WHERE ${columns[0]}='${id1}' AND ${columns[1]}='${id2}'`;
|
||||
return this.connection.driver.query(query);
|
||||
}
|
||||
|
||||
/*copyEntity(entity1: Entity, entity2: Entity) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user