mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
added functionality to create, initailize, merge and diff entities
This commit is contained in:
parent
886212c873
commit
9cd8d5aced
@ -36,7 +36,7 @@ export class EntityCreator {
|
||||
objectToEntity<Entity>(objects: any[], metadata: EntityMetadata, aliasMap: AliasMap,fetchProperty?: Object): Entity;
|
||||
objectToEntity<Entity>(objects: any[], metadata: EntityMetadata, aliasMap: AliasMap, fetchOption?: boolean|Object): Entity {
|
||||
|
||||
return this.toEntity(objects, metadata, aliasMap.getMainAlias(), aliasMap);
|
||||
return this.toEntity(objects, metadata, aliasMap.mainAlias(), aliasMap);
|
||||
//return this.objectToEntity(object, metadata, fetchOption);
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import {Image} from "./entity/Image";
|
||||
import {ImageDetails} from "./entity/ImageDetails";
|
||||
import {Cover} from "./entity/Cover";
|
||||
import {Category} from "./entity/Category";
|
||||
import {Chapter} from "./entity/Chapter";
|
||||
|
||||
// first create a connection
|
||||
let options = {
|
||||
@ -16,31 +17,73 @@ let options = {
|
||||
autoSchemaCreate: true
|
||||
};
|
||||
|
||||
TypeORM.createMysqlConnection(options, [Post, PostDetails, Image, ImageDetails, Cover, Category]).then(connection => {
|
||||
TypeORM.createMysqlConnection(options, [Post, PostDetails, Image, ImageDetails, Cover, Category, Chapter]).then(connection => {
|
||||
|
||||
const postJson = {
|
||||
id: 1,
|
||||
text: "This is post about hello",
|
||||
title: "hello",
|
||||
details: {
|
||||
id: 1,
|
||||
id: 1, // changed
|
||||
text: "This is post about hello", // changed
|
||||
title: "hello", // changed
|
||||
details: { // new relation added
|
||||
id: 10, // new object persisted
|
||||
comment: "This is post about hello",
|
||||
meta: "about-hello"
|
||||
}
|
||||
meta: "about-hello!",
|
||||
chapter: {
|
||||
id: 1, // new object persisted
|
||||
about: "part I"
|
||||
},
|
||||
categories: [{
|
||||
id: 5, // new object persisted
|
||||
description: "cat5"
|
||||
}]
|
||||
},
|
||||
cover: null, // relation removed
|
||||
images: [{ // new relation added
|
||||
id: 4, // new object persisted
|
||||
name: "post!.jpg",
|
||||
secondaryPost: {
|
||||
id: 2,
|
||||
title: "secondary post"
|
||||
}
|
||||
}, { // secondaryPost relation removed
|
||||
id: 3,
|
||||
name: "post_2!.jpg", // changed
|
||||
details: { // new relation added
|
||||
id: 3, // new object persisted
|
||||
meta: "sec image",
|
||||
comment: "image sec"
|
||||
}
|
||||
}],
|
||||
categories: [{ // two categories removed, new category added
|
||||
id: 4, // new persisted
|
||||
description: "cat2"
|
||||
}]
|
||||
};
|
||||
|
||||
let postRepository = connection.getRepository<Post>(Post);
|
||||
let entity = postRepository.create(postJson);
|
||||
return postRepository.initialize(postJson)
|
||||
.then(result => {
|
||||
const mergedEntity = postRepository.merge(result, entity);
|
||||
console.log("entity created from json: ", entity);
|
||||
console.log("entity initialized from db: ", result);
|
||||
console.log("entity merged: ", mergedEntity);
|
||||
const diff = postRepository.difference(result, mergedEntity);
|
||||
console.log("diff: ", diff);
|
||||
//console.log("diff[0]: ", diff[0].removedRelations);
|
||||
})
|
||||
.catch(error => console.log(error.stack ? error.stack : error));
|
||||
|
||||
let qb = postRepository
|
||||
.createQueryBuilder("post")
|
||||
.addSelect("cover")
|
||||
.addSelect("image")
|
||||
.addSelect("imageDetails")
|
||||
.addSelect("secondaryImage")
|
||||
.addSelect("cover")
|
||||
.addSelect("category")
|
||||
.innerJoin("post.coverId", "cover")
|
||||
.leftJoin("post.images", "image")
|
||||
.leftJoin("post.secondaryImages", "secondaryImage")
|
||||
.leftJoin("image.details", "imageDetails", "on", "imageDetails.meta=:meta")
|
||||
.innerJoin("post.coverId", "cover")
|
||||
.leftJoin("post.categories", "category", "on", "category.description=:description")
|
||||
//.leftJoin(Image, "image", "on", "image.post=post.id")
|
||||
//.where("post.id=:id")
|
||||
@ -50,7 +93,7 @@ TypeORM.createMysqlConnection(options, [Post, PostDetails, Image, ImageDetails,
|
||||
|
||||
return qb
|
||||
.getSingleResult()
|
||||
.then(result => console.log(result))
|
||||
.then(post => console.log(post))
|
||||
// .then(result => console.log(JSON.stringify(result, null, 4)))
|
||||
.catch(error => console.log(error.stack ? error.stack : error));
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
import {OneToMany, ManyToMany} from "../../../src/decorator/Relations";
|
||||
import {OneToMany, ManyToMany, ManyToOne} from "../../../src/decorator/Relations";
|
||||
import {Post} from "./Post";
|
||||
import {PostDetails} from "./PostDetails";
|
||||
|
||||
@Table("sample2_category")
|
||||
export class Category {
|
||||
@ -15,4 +16,7 @@ export class Category {
|
||||
@ManyToMany<Post>(false, type => Post, post => post.categories)
|
||||
posts: Post[];
|
||||
|
||||
@ManyToOne<PostDetails>(_ => PostDetails, postDetails => postDetails.categories)
|
||||
details: PostDetails;
|
||||
|
||||
}
|
||||
18
sample/sample2-one-to-one/entity/Chapter.ts
Normal file
18
sample/sample2-one-to-one/entity/Chapter.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
import {OneToMany} from "../../../src/decorator/Relations";
|
||||
import {PostDetails} from "./PostDetails";
|
||||
|
||||
@Table("sample2_chapter")
|
||||
export class Chapter {
|
||||
|
||||
@PrimaryColumn("int", { autoIncrement: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
about: string;
|
||||
|
||||
@OneToMany<PostDetails>(type => PostDetails, postDetails => postDetails.chapter)
|
||||
postDetails: PostDetails[];
|
||||
|
||||
}
|
||||
@ -16,10 +16,16 @@ export class Image {
|
||||
@ManyToOne<Post>(() => Post, post => post.images)
|
||||
post: Post;
|
||||
|
||||
@ManyToOne<Post>(() => Post, post => post.secondaryImages)
|
||||
@ManyToOne<Post>(() => Post, post => post.secondaryImages, {
|
||||
isCascadeInsert: true
|
||||
})
|
||||
secondaryPost: Post;
|
||||
|
||||
@OneToOne<ImageDetails>(true, () => ImageDetails, details => details.image)
|
||||
@OneToOne<ImageDetails>(true, () => ImageDetails, details => details.image, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeUpdate: true,
|
||||
isCascadeRemove: true
|
||||
})
|
||||
details: ImageDetails;
|
||||
|
||||
}
|
||||
@ -22,27 +22,39 @@ export class Post {
|
||||
})
|
||||
text: string;
|
||||
|
||||
@OneToOne<PostDetails>(true, () => PostDetails, details => details.post)
|
||||
@OneToOne<PostDetails>(true, () => PostDetails, details => details.post, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeUpdate: true,
|
||||
isCascadeRemove: true
|
||||
})
|
||||
details: PostDetails;
|
||||
|
||||
@OneToMany<Image>(type => Image, image => image.post)
|
||||
@OneToMany<Image>(type => Image, image => image.post, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeUpdate: true,
|
||||
isCascadeRemove: true
|
||||
})
|
||||
images: Image[];
|
||||
|
||||
@OneToMany<Image>(type => Image, image => image.secondaryPost)
|
||||
secondaryImages: Image[];
|
||||
|
||||
@ManyToOne<Cover>(type => Cover, cover => cover.posts, {
|
||||
name: "coverId"
|
||||
name: "coverId",
|
||||
isCascadeInsert: true,
|
||||
isCascadeRemove: true
|
||||
})
|
||||
cover: Cover;
|
||||
|
||||
/*@Column({
|
||||
nullable: true,
|
||||
type: "int"
|
||||
@Column("int", {
|
||||
nullable: true
|
||||
})
|
||||
coverId: number;*/
|
||||
coverId: number;
|
||||
|
||||
@ManyToMany<Category>(true, type => Category, category => category.posts)
|
||||
categories: Category;
|
||||
@ManyToMany<Category>(true, type => Category, category => category.posts, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeRemove: true
|
||||
})
|
||||
categories: Category[];
|
||||
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
import {OneToOne} from "../../../src/decorator/Relations";
|
||||
import {OneToOne, OneToMany, ManyToOne} from "../../../src/decorator/Relations";
|
||||
import {Post} from "./Post";
|
||||
import {Chapter} from "./Chapter";
|
||||
import {Category} from "./Category";
|
||||
|
||||
@Table("sample2_post_details")
|
||||
export class PostDetails {
|
||||
@ -15,7 +17,19 @@ export class PostDetails {
|
||||
@Column()
|
||||
comment: string;
|
||||
|
||||
@OneToOne<Post>(false, () => Post, post => post.details)
|
||||
@OneToOne<Post>(false, type => Post, post => post.details)
|
||||
post: Post;
|
||||
|
||||
@OneToMany<Category>(type => Category, category => category.details, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeRemove: true
|
||||
})
|
||||
categories: Category[];
|
||||
|
||||
@ManyToOne<Chapter>(_ => Chapter, chapter => chapter.postDetails, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeRemove: true
|
||||
})
|
||||
chapter: Chapter;
|
||||
|
||||
}
|
||||
@ -32,7 +32,7 @@ export function Column(typeOrOptions?: string|ColumnOptions, options?: ColumnOpt
|
||||
|
||||
// todo: need proper type validation here
|
||||
|
||||
const metadata = new ColumnMetadata(object.constructor, propertyName, false, false, false, options);
|
||||
const metadata = new ColumnMetadata(object.constructor, propertyName, false, false, false, false, options);
|
||||
defaultMetadataStorage.addColumnMetadata(metadata);
|
||||
};
|
||||
}
|
||||
@ -66,7 +66,7 @@ export function PrimaryColumn(typeOrOptions?: string|ColumnOptions, options?: Co
|
||||
|
||||
// todo: need proper type validation here
|
||||
|
||||
const metadata = new ColumnMetadata(object.constructor, propertyName, true, false, false, options);
|
||||
const metadata = new ColumnMetadata(object.constructor, propertyName, true, false, false, false, options);
|
||||
defaultMetadataStorage.addColumnMetadata(metadata);
|
||||
};
|
||||
}
|
||||
@ -108,7 +108,7 @@ export class EntityMetadataBuilder {
|
||||
oldColumnName: relation.oldColumnName,
|
||||
nullable: relation.isNullable
|
||||
};
|
||||
relationalColumn = new ColumnMetadata(metadata.target, relation.name, false, false, false, options);
|
||||
relationalColumn = new ColumnMetadata(metadata.target, relation.name, false, false, false, true, options);
|
||||
metadata.columns.push(relationalColumn);
|
||||
}
|
||||
|
||||
@ -145,8 +145,8 @@ export class EntityMetadataBuilder {
|
||||
name: inverseSideMetadata.table.name + "_" + inverseSideMetadata.primaryColumn.name
|
||||
};
|
||||
const columns = [
|
||||
new ColumnMetadata(null, null, false, false, false, column1options),
|
||||
new ColumnMetadata(null, null, false, false, false, column2options)
|
||||
new ColumnMetadata(null, null, false, false, false, false, column1options),
|
||||
new ColumnMetadata(null, null, false, false, false, false, column2options)
|
||||
];
|
||||
const foreignKeys = [
|
||||
new ForeignKeyMetadata(tableMetadata, [columns[0]], metadata.table, [metadata.primaryColumn]),
|
||||
|
||||
@ -62,6 +62,11 @@ export class ColumnMetadata extends PropertyMetadata {
|
||||
*/
|
||||
private _isUpdateDate: boolean = false;
|
||||
|
||||
/**
|
||||
* Indicates if column will contain an updated date or not.
|
||||
*/
|
||||
private _isVirtual: boolean = false;
|
||||
|
||||
/**
|
||||
* Extra sql definition for the given column.
|
||||
*/
|
||||
@ -86,14 +91,22 @@ export class ColumnMetadata extends PropertyMetadata {
|
||||
isPrimaryKey: boolean,
|
||||
isCreateDate: boolean,
|
||||
isUpdateDate: boolean,
|
||||
isVirtual: boolean,
|
||||
options: ColumnOptions) {
|
||||
super(target, propertyName);
|
||||
|
||||
this._isPrimary = isPrimaryKey;
|
||||
this._isCreateDate = isCreateDate;
|
||||
this._isUpdateDate = isUpdateDate;
|
||||
this._name = options.name;
|
||||
this._type = this.convertType(options.type);
|
||||
if (isPrimaryKey)
|
||||
this._isPrimary = isPrimaryKey;
|
||||
if (isCreateDate)
|
||||
this._isCreateDate = isCreateDate;
|
||||
if (isUpdateDate)
|
||||
this._isUpdateDate = isUpdateDate;
|
||||
if (isVirtual)
|
||||
this._isVirtual = isVirtual;
|
||||
if (options.name)
|
||||
this._name = options.name;
|
||||
if (options.type)
|
||||
this._type = this.convertType(options.type);
|
||||
|
||||
if (options.length)
|
||||
this._length = options.length;
|
||||
@ -157,6 +170,10 @@ export class ColumnMetadata extends PropertyMetadata {
|
||||
return this._isUpdateDate;
|
||||
}
|
||||
|
||||
get isVirtual(): boolean {
|
||||
return this._isVirtual;
|
||||
}
|
||||
|
||||
get columnDefinition(): string {
|
||||
return this._columnDefinition;
|
||||
}
|
||||
|
||||
@ -44,6 +44,10 @@ export class EntityMetadata {
|
||||
// Accessors
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
get name(): string {
|
||||
return (<any> this._table.target).name;
|
||||
}
|
||||
|
||||
get target(): Function {
|
||||
return this._table.target;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {Alias} from "./alias/Alias";
|
||||
import {AliasMap} from "./alias/AliasMap";
|
||||
import {Connection} from "../connection/Connection";
|
||||
import {RawSqlResultsToObjectTransformer} from "./transformer/RawSqlResultsToObjectTransformer";
|
||||
import {RawSqlResultsToEntityTransformer} from "./transformer/RawSqlResultsToEntityTransformer";
|
||||
|
||||
export interface Join {
|
||||
alias: Alias;
|
||||
@ -16,7 +16,7 @@ export class QueryBuilder<Entity> {
|
||||
// Pirvate properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private aliasMap: AliasMap;
|
||||
private _aliasMap: AliasMap;
|
||||
private type: "select"|"update"|"delete";
|
||||
private selects: string[] = [];
|
||||
private froms: { alias: Alias };
|
||||
@ -34,7 +34,15 @@ export class QueryBuilder<Entity> {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(private connection: Connection) {
|
||||
this.aliasMap = new AliasMap(connection.metadatas);
|
||||
this._aliasMap = new AliasMap(connection.metadatas);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Accessors
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
get aliasMap() {
|
||||
return this._aliasMap;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -83,17 +91,31 @@ export class QueryBuilder<Entity> {
|
||||
//from(entityOrTableName: Function|string, alias: string): this {
|
||||
const aliasObj = new Alias(alias);
|
||||
aliasObj.target = entity;
|
||||
this.aliasMap.addMainAlias(aliasObj);
|
||||
this._aliasMap.addMainAlias(aliasObj);
|
||||
this.froms = { alias: aliasObj };
|
||||
return this;
|
||||
}
|
||||
|
||||
innerJoinAndSelect(property: string, alias: string, conditionType?: "on"|"with", condition?: string): this;
|
||||
innerJoinAndSelect(entity: Function, alias: string, conditionType?: "on"|"with", condition?: string): this;
|
||||
innerJoinAndSelect(entityOrProperty: Function|string, alias: string, conditionType?: "on"|"with", condition?: string): this {
|
||||
this.addSelect(alias);
|
||||
return this.join("inner", entityOrProperty, alias, conditionType, condition);
|
||||
}
|
||||
|
||||
innerJoin(property: string, alias: string, conditionType?: "on"|"with", condition?: string): this;
|
||||
innerJoin(entity: Function, alias: string, conditionType?: "on"|"with", condition?: string): this;
|
||||
innerJoin(entityOrProperty: Function|string, alias: string, conditionType?: "on"|"with", condition?: string): this {
|
||||
return this.join("inner", entityOrProperty, alias, conditionType, condition);
|
||||
}
|
||||
|
||||
leftJoinAndSelect(property: string, alias: string, conditionType?: "on"|"with", condition?: string): this;
|
||||
leftJoinAndSelect(entity: Function, alias: string, conditionType?: "on"|"with", condition?: string): this;
|
||||
leftJoinAndSelect(entityOrProperty: Function|string, alias: string, conditionType: "on"|"with" = "on", condition?: string): this {
|
||||
this.addSelect(alias);
|
||||
return this.join("left", entityOrProperty, alias, conditionType, condition);
|
||||
}
|
||||
|
||||
leftJoin(property: string, alias: string, conditionType?: "on"|"with", condition?: string): this;
|
||||
leftJoin(entity: Function, alias: string, conditionType?: "on"|"with", condition?: string): this;
|
||||
leftJoin(entityOrProperty: Function|string, alias: string, conditionType: "on"|"with" = "on", condition?: string): this {
|
||||
@ -106,7 +128,7 @@ export class QueryBuilder<Entity> {
|
||||
join(joinType: "inner"|"left", entityOrProperty: Function|string, alias: string, conditionType: "on"|"with" = "on", condition?: string): this {
|
||||
|
||||
const aliasObj = new Alias(alias);
|
||||
this.aliasMap.addAlias(aliasObj);
|
||||
this._aliasMap.addAlias(aliasObj);
|
||||
if (entityOrProperty instanceof Function) {
|
||||
aliasObj.target = entityOrProperty;
|
||||
|
||||
@ -204,21 +226,22 @@ export class QueryBuilder<Entity> {
|
||||
return sql;
|
||||
}
|
||||
|
||||
execute<T>(): Promise<T> {
|
||||
return this.connection.driver.query<T>(this.getSql());
|
||||
execute(): Promise<void> {
|
||||
return this.connection.driver.query(this.getSql()).then(() => {});
|
||||
}
|
||||
|
||||
getScalarResults(): Promise<any[]> {
|
||||
return this.execute<any[]>().then(results => this.rawResultsToObjects(results));
|
||||
|
||||
getScalarResults<T>(): Promise<T[]> {
|
||||
return this.connection.driver.query<T[]>(this.getSql());
|
||||
}
|
||||
|
||||
getSingleScalarResult(): Promise<any> {
|
||||
getSingleScalarResult<T>(): Promise<T> {
|
||||
return this.getScalarResults().then(results => results[0]);
|
||||
}
|
||||
|
||||
getResults(): Promise<Entity[]> {
|
||||
return this.getScalarResults().then(objects => this.objectsToEntities(objects));
|
||||
return this.connection.driver
|
||||
.query<any[]>(this.getSql())
|
||||
.then(results => this.rawResultsToEntities(results));
|
||||
}
|
||||
|
||||
getSingleResult(): Promise<Entity> {
|
||||
@ -229,18 +252,14 @@ export class QueryBuilder<Entity> {
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
protected rawResultsToObjects(results: any[]) {
|
||||
const transformer = new RawSqlResultsToObjectTransformer(this.aliasMap);
|
||||
protected rawResultsToEntities(results: any[]) {
|
||||
const transformer = new RawSqlResultsToEntityTransformer(this._aliasMap);
|
||||
return transformer.transform(results);
|
||||
}
|
||||
|
||||
protected objectsToEntities(entities: any[]) {
|
||||
return entities;
|
||||
}
|
||||
|
||||
protected createSelectExpression() {
|
||||
// todo throw exception if selects or from is missing
|
||||
const metadata = this.aliasMap.getEntityMetadataByAlias(this.froms.alias);
|
||||
const metadata = this._aliasMap.getEntityMetadataByAlias(this.froms.alias);
|
||||
const tableName = metadata.table.name;
|
||||
const alias = this.froms.alias.name;
|
||||
const allSelects: string[] = [];
|
||||
@ -255,7 +274,7 @@ export class QueryBuilder<Entity> {
|
||||
this.joins
|
||||
.filter(join => this.selects.indexOf(join.alias.name) !== -1)
|
||||
.forEach(join => {
|
||||
const joinMetadata = this.aliasMap.getEntityMetadataByAlias(join.alias);
|
||||
const joinMetadata = this._aliasMap.getEntityMetadataByAlias(join.alias);
|
||||
joinMetadata.columns.forEach(column => {
|
||||
allSelects.push(join.alias.name + "." + column.name + " AS " + join.alias.name + "_" + column.propertyName);
|
||||
});
|
||||
@ -297,12 +316,12 @@ export class QueryBuilder<Entity> {
|
||||
const joinType = join.type === "inner" ? "INNER" : "LEFT";
|
||||
const appendedCondition = join.condition ? " AND " + join.condition : "";
|
||||
const parentAlias = join.alias.parentAliasName;
|
||||
const parentMetadata = this.aliasMap.getEntityMetadataByAlias(this.aliasMap.findAliasByName(parentAlias));
|
||||
const parentMetadata = this._aliasMap.getEntityMetadataByAlias(this._aliasMap.findAliasByName(parentAlias));
|
||||
const parentTable = parentMetadata.table.name;
|
||||
const parentTableColumn = parentMetadata.primaryColumn.name;
|
||||
const relation = parentMetadata.findRelationWithDbName(join.alias.parentPropertyName);
|
||||
const junctionMetadata = relation.junctionEntityMetadata;
|
||||
const joinMetadata = this.aliasMap.getEntityMetadataByAlias(join.alias);
|
||||
const joinMetadata = this._aliasMap.getEntityMetadataByAlias(join.alias);
|
||||
const joinTable = joinMetadata.table.name;
|
||||
const joinTableColumn = joinMetadata.primaryColumn.name;
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ export class AliasMap {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
addMainAlias(alias: Alias) {
|
||||
const mainAlias = this.getMainAlias();
|
||||
const mainAlias = this.mainAlias;
|
||||
if (mainAlias)
|
||||
this.aliases.splice(this.aliases.indexOf(mainAlias), 1);
|
||||
|
||||
@ -33,7 +33,7 @@ export class AliasMap {
|
||||
this.aliases.push(alias);
|
||||
}
|
||||
|
||||
getMainAlias() {
|
||||
get mainAlias() {
|
||||
return this.aliases.find(alias => alias.isMain);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import {EntityMetadata} from "../../metadata-builder/metadata/EntityMetadata";
|
||||
import {QueryBuilder} from "../QueryBuilder";
|
||||
|
||||
interface LoadMap {
|
||||
name: string;
|
||||
child: LoadMap[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms plain old javascript object
|
||||
* Entity is constructed based on its entity metadata.
|
||||
*/
|
||||
export class PlainObjectToDatabaseEntityTransformer<Entity> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
transform(object: any, metadata: EntityMetadata, queryBuilder: QueryBuilder<Entity>): Promise<Entity> {
|
||||
|
||||
// if object does not have id then nothing to load really
|
||||
if (!metadata.hasPrimaryKey || !object[metadata.primaryColumn.name])
|
||||
return null;
|
||||
|
||||
const alias = queryBuilder.aliasMap.mainAlias.name;
|
||||
const needToLoad = this.buildLoadMap(object, metadata);
|
||||
|
||||
this.join(queryBuilder, needToLoad, alias);
|
||||
return queryBuilder
|
||||
.where(alias + "." + metadata.primaryColumn.name + "=:id")
|
||||
.setParameter("id", object[metadata.primaryColumn.name])
|
||||
.getSingleResult();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private join(qb: QueryBuilder<Entity>, needToLoad: LoadMap[], parentAlias: string) {
|
||||
needToLoad.forEach(i => {
|
||||
const alias = parentAlias + "_" + i.name;
|
||||
qb.leftJoinAndSelect(parentAlias + "." + i.name, alias);
|
||||
if (i.child && i.child.length)
|
||||
this.join(qb, i.child, alias);
|
||||
});
|
||||
}
|
||||
|
||||
private buildLoadMap(object: any, metadata: EntityMetadata): LoadMap[] {
|
||||
return metadata.relations
|
||||
.filter(relation => object.hasOwnProperty(relation.propertyName))
|
||||
.map(relation => {
|
||||
let value = object[relation.propertyName];
|
||||
if (value instanceof Array)
|
||||
value = Object.assign({}, ...value);
|
||||
|
||||
const child = value ? this.buildLoadMap(value, relation.relatedEntityMetadata) : [];
|
||||
return <LoadMap> { name: relation.name, child: child };
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
import {EntityMetadata} from "../../metadata-builder/metadata/EntityMetadata";
|
||||
|
||||
/**
|
||||
* Transforms plain old javascript object
|
||||
* Entity is constructed based on its entity metadata.
|
||||
*/
|
||||
export class PlainObjectToNewEntityTransformer {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
transform(object: any, metadata: EntityMetadata): any {
|
||||
return this.groupAndTransform(object, metadata);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Since db returns a duplicated rows of the data where accuracies of the same object can be duplicated
|
||||
* we need to group our result and we must have some unique id (primary key in our case)
|
||||
*/
|
||||
private groupAndTransform(object: any, metadata: EntityMetadata) {
|
||||
const entity = metadata.create();
|
||||
|
||||
// copy regular column properties from the given object
|
||||
metadata.columns
|
||||
.filter(column => object.hasOwnProperty(column.propertyName))
|
||||
.forEach(column => entity[column.propertyName] = object[column.propertyName]); // todo: also need to be sure that type is correct
|
||||
|
||||
// if relation is loaded then go into it recursively and transform its values too
|
||||
metadata.relations
|
||||
.filter(relation => object.hasOwnProperty(relation.propertyName))
|
||||
.forEach(relation => {
|
||||
const relationMetadata = relation.relatedEntityMetadata;
|
||||
if (!relationMetadata)
|
||||
throw new Error("Relation metadata for the relation " + (<any> metadata.target).name + "#" + relation.propertyName + " is missing");
|
||||
|
||||
if (relation.isManyToMany || relation.isOneToMany) {
|
||||
if (object[relation.propertyName] instanceof Array) {
|
||||
entity[relation.propertyName] = object[relation.propertyName].map((subObject: any) => {
|
||||
return this.groupAndTransform(subObject, relationMetadata);
|
||||
});
|
||||
} else {
|
||||
entity[relation.propertyName] = object[relation.propertyName];
|
||||
}
|
||||
} else {
|
||||
if (object[relation.propertyName]) {
|
||||
entity[relation.propertyName] = this.groupAndTransform(object[relation.propertyName], relationMetadata);
|
||||
} else {
|
||||
entity[relation.propertyName] = object[relation.propertyName];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,13 +4,10 @@ import * as _ from "lodash";
|
||||
import {EntityMetadata} from "../../metadata-builder/metadata/EntityMetadata";
|
||||
|
||||
/**
|
||||
* Transforms raw sql results returned from the database into object. Object is constructed for entity
|
||||
* based on the entity metadata.
|
||||
* Transforms raw sql results returned from the database into entity object.
|
||||
* Entity is constructed based on its entity metadata.
|
||||
*/
|
||||
export class RawSqlResultsToObjectTransformer {
|
||||
|
||||
// todo: add check for property relation with id as a column
|
||||
// todo: create metadata or do it later?
|
||||
export class RawSqlResultsToEntityTransformer {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
@ -24,7 +21,7 @@ export class RawSqlResultsToObjectTransformer {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
transform(rawSqlResults: any[]): any[] {
|
||||
return this.groupAndTransform(rawSqlResults, this.aliasMap.getMainAlias());
|
||||
return this.groupAndTransform(rawSqlResults, this.aliasMap.mainAlias);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -48,30 +45,35 @@ export class RawSqlResultsToObjectTransformer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms set of data results of the single value.
|
||||
* Transforms set of data results into single entity.
|
||||
*/
|
||||
private transformIntoSingleResult(rawSqlResults: any[], alias: Alias, metadata: EntityMetadata) {
|
||||
const jsonObject: any = {};
|
||||
const entity: any = metadata.create();
|
||||
let hasData = false;
|
||||
|
||||
// get value from columns selections and put them into object
|
||||
metadata.columns.forEach(column => {
|
||||
const valueInObject = alias.getColumnValue(rawSqlResults[0], column); // we use zero index since its grouped data
|
||||
if (valueInObject && column.propertyName)
|
||||
jsonObject[column.propertyName] = valueInObject;
|
||||
if (valueInObject && column.propertyName && !column.isVirtual) {
|
||||
entity[column.propertyName] = valueInObject;
|
||||
hasData = true;
|
||||
}
|
||||
});
|
||||
|
||||
// if relation is loaded then go into it recursively and transform its values too
|
||||
metadata.relations.forEach(relation => {
|
||||
const relationAlias = this.aliasMap.findAliasByParent(alias.name, relation.propertyName);
|
||||
const relationAlias = this.aliasMap.findAliasByParent(alias.name, relation.name);
|
||||
if (relationAlias) {
|
||||
const relatedObjects = this.groupAndTransform(rawSqlResults, relationAlias);
|
||||
const result = (relation.isManyToOne || relation.isOneToOne) ? relatedObjects[0] : relatedObjects;
|
||||
if (result)
|
||||
jsonObject[relation.propertyName] = result;
|
||||
const relatedEntities = this.groupAndTransform(rawSqlResults, relationAlias);
|
||||
const result = (relation.isManyToOne || relation.isOneToOne) ? relatedEntities[0] : relatedEntities;
|
||||
if (result) {
|
||||
entity[relation.propertyName] = result;
|
||||
hasData = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Object.keys(jsonObject).length > 0 ? jsonObject : null;
|
||||
return hasData ? entity : null;
|
||||
}
|
||||
|
||||
}
|
||||
215
src/repository/EntityPersistOperationsBuilder.ts
Normal file
215
src/repository/EntityPersistOperationsBuilder.ts
Normal file
@ -0,0 +1,215 @@
|
||||
import {EntityMetadata} from "../metadata-builder/metadata/EntityMetadata";
|
||||
import {ColumnMetadata} from "../metadata-builder/metadata/ColumnMetadata";
|
||||
import {EntityDifferenceMap} from "./Repository";
|
||||
|
||||
interface EntityWithId {
|
||||
id: any;
|
||||
entity: any;
|
||||
}
|
||||
|
||||
interface UpdateOperation {
|
||||
entity: any;
|
||||
columns: ColumnMetadata[];
|
||||
}
|
||||
|
||||
export class EntityPersistOperationsBuilder {
|
||||
|
||||
// 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
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 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[] = [];
|
||||
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);
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log("DB ENTITIES");
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(dbEntities);
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log("ALL NEW ENTITIES");
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(allEntities);
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log("INSERTED ENTITIES");
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(insertedEntities);
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log("REMOVED ENTITIES");
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(removedEntities);
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log("UPDATED ENTITIES");
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(updatedEntities);
|
||||
console.log("---------------------------------------------------------");
|
||||
return diffMaps;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 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 findCascadeUpdateEntities(metadata: EntityMetadata, newEntity: any, dbEntity: any): UpdateOperation[] {
|
||||
const updatedColumns = [{
|
||||
entity: newEntity,
|
||||
columns: this.diffColumns(metadata, newEntity, dbEntity)
|
||||
}];
|
||||
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, 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);
|
||||
}
|
||||
|
||||
private findCascadeInsertedEntities(newEntity: any, metadata: EntityMetadata, dbEntities: any[]): any[] {
|
||||
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));
|
||||
});
|
||||
} 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));
|
||||
}
|
||||
|
||||
return insertedEntities;
|
||||
}, []);
|
||||
}
|
||||
|
||||
private findCascadeRemovedEntities(dbEntity: any, metadata: EntityMetadata, newEntities: any[]): any[] {
|
||||
return metadata.relations
|
||||
.filter(relation => !!dbEntity[relation.propertyName])
|
||||
.reduce((removedEntities, relation) => {
|
||||
const relationIdColumnName = relation.relatedEntityMetadata.primaryColumn.name;
|
||||
const relMetadata = relation.relatedEntityMetadata;
|
||||
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;
|
||||
});
|
||||
if (isObjectRemoved && relation.isCascadeRemove)
|
||||
removedEntities.push(subEntity);
|
||||
|
||||
removedEntities = removedEntities.concat(this.findCascadeRemovedEntities(subEntity, relMetadata, newEntities));
|
||||
});
|
||||
} else {
|
||||
const relationId = dbEntity[relation.propertyName][relationIdColumnName];
|
||||
const isObjectRemoved = !newEntities.find(newEntity => {
|
||||
return newEntity.id === relationId && newEntity.entity === relMetadata.name;
|
||||
});
|
||||
if (isObjectRemoved && relation.isCascadeRemove)
|
||||
removedEntities.push(dbEntity[relation.propertyName]);
|
||||
|
||||
removedEntities = removedEntities.concat(this.findCascadeRemovedEntities(dbEntity[relation.propertyName], relMetadata, newEntities));
|
||||
}
|
||||
|
||||
return removedEntities;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts unique objects from given entity and all its downside relations.
|
||||
*/
|
||||
private extractObjectsById(entity: any, metadata: EntityMetadata): EntityWithId[] {
|
||||
return metadata.relations
|
||||
.filter(relation => !!entity[relation.propertyName])
|
||||
.map(relation => {
|
||||
const relMetadata = relation.relatedEntityMetadata;
|
||||
if (!(entity[relation.propertyName] instanceof Array))
|
||||
return this.extractObjectsById(entity[relation.propertyName], relMetadata);
|
||||
|
||||
return entity[relation.propertyName]
|
||||
.map((subEntity: any) => this.extractObjectsById(subEntity, relMetadata))
|
||||
.reduce((col1: any[], col2: any[]) => col1.concat(col2), []); // flatten
|
||||
})
|
||||
.reduce((col1: any[], col2: any[]) => col1.concat(col2), []) // flatten
|
||||
.concat([{
|
||||
id: entity[metadata.primaryColumn.name],
|
||||
entity: entity.constructor.name
|
||||
}])
|
||||
.filter((entity: any, index: number, allEntities: any[]) => allEntities.indexOf(entity) === index); // unique
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,10 +2,38 @@ import {Connection} from "../connection/Connection";
|
||||
import {EntityMetadata} from "../metadata-builder/metadata/EntityMetadata";
|
||||
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";
|
||||
|
||||
// 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.
|
||||
*/
|
||||
@ -50,9 +78,156 @@ export class Repository<Entity> {
|
||||
/**
|
||||
* Creates a new entity.
|
||||
*/
|
||||
create(): Entity {
|
||||
create(copyFrom?: any): Entity {
|
||||
if (copyFrom) {
|
||||
const transformer = new PlainObjectToNewEntityTransformer();
|
||||
return transformer.transform(copyFrom, this.metadata);
|
||||
}
|
||||
return <Entity> this.metadata.create();
|
||||
}
|
||||
|
||||
initialize(object: any): Promise<Entity> {
|
||||
const transformer = new PlainObjectToDatabaseEntityTransformer();
|
||||
const queryBuilder = this.createQueryBuilder(this.metadata.table.name);
|
||||
return transformer.transform(object, this.metadata, queryBuilder);
|
||||
}
|
||||
|
||||
merge(entity1: Entity, entity2: Entity): 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[] {
|
||||
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 };
|
||||
});
|
||||
}).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), []);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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)
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*copyEntity(entity1: Entity, entity2: Entity) {
|
||||
this.metadata.columns
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Creates a entities from the given array of plain javascript objects. If fetchAllData param is specified then
|
||||
* entities data will be loaded from the database first, then filled with given json data.
|
||||
*/
|
||||
createMany(copyFromObjects: any[]): Entity[] {
|
||||
return copyFromObjects.map(object => this.create(object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if entity has an id.
|
||||
@ -61,32 +236,6 @@ export class Repository<Entity> {
|
||||
return entity && this.metadata.primaryColumn && entity.hasOwnProperty(this.metadata.primaryColumn.propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates entity from the given json data. If fetchAllData param is specified then entity data will be
|
||||
* loaded from the database first, then filled with given json data.
|
||||
*/
|
||||
createFromJson(json: any, fetchProperty?: boolean): Promise<Entity>;
|
||||
createFromJson(json: any, fetchConditions?: Object): Promise<Entity>;
|
||||
createFromJson(json: any, fetchOption?: boolean|Object): Promise<Entity> {
|
||||
return Promise.resolve<Entity>(null); // todo
|
||||
/* const creator = new EntityCreator(this.connection);
|
||||
return creator.createFromJson<Entity>(json, this.metadata, fetchOption);*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a entities from the given array of plain javascript objects. If fetchAllData param is specified then
|
||||
* entities data will be loaded from the database first, then filled with given json data.
|
||||
*/
|
||||
createManyFromJson(objects: any[], fetchProperties?: boolean[]): Promise<Entity[]>;
|
||||
createManyFromJson(objects: any[], fetchConditions?: Object[]): Promise<Entity[]>;
|
||||
createManyFromJson(objects: any[], fetchOption?: boolean[]|Object[]): Promise<Entity[]> {
|
||||
return Promise.resolve<Entity[]>(null); // todo
|
||||
/*return Promise.all(objects.map((object, key) => {
|
||||
const fetchConditions = (fetchOption && fetchOption[key]) ? fetchOption[key] : undefined;
|
||||
return this.createFromJson(object, fetchConditions);
|
||||
}));*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new query builder that can be used to build an sql query.
|
||||
*/
|
||||
@ -143,9 +292,9 @@ export class Repository<Entity> {
|
||||
* Saves a given entity. If entity is not inserted yet then it inserts a new entity.
|
||||
* If entity already inserted then performs its update.
|
||||
*/
|
||||
persist(entity: Entity/*, dynamicCascadeOptions?: DynamicCascadeOptions<Entity>*/): Promise<Entity> {
|
||||
//persist(entity: Entity/*, dynamicCascadeOptions?: DynamicCascadeOptions<Entity>*/): Promise<Entity> {
|
||||
// todo
|
||||
return Promise.resolve<Entity>(null);
|
||||
// return Promise.resolve<Entity>(null);
|
||||
|
||||
// if (!this.schema.isEntityTypeCorrect(entity))
|
||||
// throw new BadEntityInstanceException(entity, this.schema.entityClass);
|
||||
@ -158,7 +307,7 @@ export class Repository<Entity> {
|
||||
.then(result => remover.executeRemoveOperations())
|
||||
.then(result => remover.executeUpdateInverseSideRelationRemoveIds())
|
||||
.then(result => entity);*/
|
||||
}
|
||||
// }
|
||||
|
||||
/*computeChangeSet(entity: Entity) {
|
||||
// if there is no primary key - there is no way to determine if object needs to be updated or insert
|
||||
|
||||
@ -28,6 +28,7 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
|
||||
dbData.IS_NULLABLE !== isNullable ||
|
||||
hasDbColumnAutoIncrement !== column.isAutoIncrement ||
|
||||
hasDbColumnPrimaryIndex !== column.isPrimary;
|
||||
|
||||
}).map(column => {
|
||||
const dbData = results.find(result => result.COLUMN_NAME === column.name);
|
||||
const hasDbColumnPrimaryIndex = dbData.COLUMN_KEY.indexOf("PRI") !== -1;
|
||||
@ -111,7 +112,7 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
|
||||
}
|
||||
|
||||
createTableQuery(table: TableMetadata, columns: ColumnMetadata[]): Promise<void> {
|
||||
const columnDefinitions = columns.map(column => this.buildCreateColumnSql(column, true)).join(", ");
|
||||
const columnDefinitions = columns.map(column => this.buildCreateColumnSql(column, false)).join(", ");
|
||||
const sql = `CREATE TABLE \`${table.name}\` (${columnDefinitions}) ENGINE=InnoDB;`;
|
||||
return this.query(sql).then(() => {});
|
||||
}
|
||||
@ -130,7 +131,7 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
|
||||
c += " NOT NULL";
|
||||
if (column.isPrimary === true && !skipPrimary)
|
||||
c += " PRIMARY KEY";
|
||||
if (column.isAutoIncrement === true && !skipPrimary)
|
||||
if (column.isAutoIncrement === true) // don't use skipPrimary here since updates can update already exist primary without auto inc.
|
||||
c += " AUTO_INCREMENT";
|
||||
if (column.comment)
|
||||
c += " COMMENT '" + column.comment + "'";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user