From 20afae93ef771c667aafe54da2533b79edb5582c Mon Sep 17 00:00:00 2001 From: Umed Khudoiberdiev Date: Fri, 3 Jun 2016 04:22:45 +0500 Subject: [PATCH] implementing entity schemas - step 3 --- sample/sample24-schemas/app.ts | 10 +- .../schemas/post-details.json | 3 +- sample/sample24-schemas/schemas/post.json | 4 +- src/connection-manager/ConnectionManager.ts | 2 +- .../CreateConnectionOptions.ts | 3 +- src/connection/Connection.ts | 13 +- src/entity-manager/EntityManager.ts | 32 ++- src/entity-manager/ReactiveEntityManager.ts | 30 ++- src/metadata-args/JoinTableMetadataArgs.ts | 2 +- src/metadata-args/MetadataArgsStorage.ts | 11 +- .../collection/EntityMetadataCollection.ts | 8 - src/metadata-builder/EntityMetadataBuilder.ts | 74 ++++++- src/metadata/EntityMetadata.ts | 2 +- src/metadata/RelationMetadata.ts | 2 +- src/metadata/entity-schema/EntitySchema.ts | 57 +++++- .../EntityPersistOperationsBuilder.ts | 186 +++++++++++++----- src/persistment/PersistOperationExecutor.ts | 44 ++--- src/persistment/operation/InsertOperation.ts | 3 +- .../operation/JunctionInsertOperation.ts | 4 +- .../operation/JunctionRemoveOperation.ts | 4 +- src/persistment/operation/PersistOperation.ts | 4 +- src/persistment/operation/RemoveOperation.ts | 3 +- .../operation/UpdateByInverseSideOperation.ts | 4 +- .../operation/UpdateByRelationOperation.ts | 3 +- src/persistment/operation/UpdateOperation.ts | 3 +- src/query-builder/QueryBuilder.ts | 4 +- src/repository/Repository.ts | 31 ++- src/subscriber/Broadcaster.ts | 16 +- 28 files changed, 407 insertions(+), 155 deletions(-) diff --git a/sample/sample24-schemas/app.ts b/sample/sample24-schemas/app.ts index b1cb2817d..9d9d74eab 100644 --- a/sample/sample24-schemas/app.ts +++ b/sample/sample24-schemas/app.ts @@ -15,15 +15,15 @@ const options: CreateConnectionOptions = { }, // entitySchemaDirectories: [__dirname + "/schemas"], entitySchemas: [ - require("./schemas/post.json"), - require("./schemas/post-details.json"), - require("./schemas/category.json"), - require("./schemas/image.json") + require(__dirname + "/../../../../sample/sample24-schemas/schemas/post.json"), + require(__dirname + "/../../../../sample/sample24-schemas/schemas/post-details.json"), + require(__dirname + "/../../../../sample/sample24-schemas/schemas/category.json"), + require(__dirname + "/../../../../sample/sample24-schemas/schemas/image.json") ] }; createConnection(options).then(connection => { - let postRepository = connection.getRepository("post"); + let postRepository = connection.getRepository("Post"); let post: Post = { title: "Hello post", diff --git a/sample/sample24-schemas/schemas/post-details.json b/sample/sample24-schemas/schemas/post-details.json index ecc6fe0ef..0a3828811 100644 --- a/sample/sample24-schemas/schemas/post-details.json +++ b/sample/sample24-schemas/schemas/post-details.json @@ -22,8 +22,7 @@ "post": { "target": "Post", "type": "one-to-one", - "owner": false, - "inverseSide": "postDetails" + "inverseSide": "details" } } } \ No newline at end of file diff --git a/sample/sample24-schemas/schemas/post.json b/sample/sample24-schemas/schemas/post.json index 421e09d93..dbee96edf 100644 --- a/sample/sample24-schemas/schemas/post.json +++ b/sample/sample24-schemas/schemas/post.json @@ -22,7 +22,7 @@ "details": { "target": "PostDetails", "type": "one-to-one", - "owner": true, + "joinColumn": true, "inverseSide": "post", "cascadeInsert": true, "cascadeUpdate": true, @@ -44,7 +44,7 @@ "categories": { "target": "Category", "type": "many-to-many", - "owner": true, + "joinTable": true, "cascadeInsert": true, "cascadeUpdate": true, "cascadeRemove": true, diff --git a/src/connection-manager/ConnectionManager.ts b/src/connection-manager/ConnectionManager.ts index 91a6afed4..b0cab5d1b 100644 --- a/src/connection-manager/ConnectionManager.ts +++ b/src/connection-manager/ConnectionManager.ts @@ -33,7 +33,7 @@ export class ConnectionManager { connection.importEntitySchemaFromDirectories(options.entitySchemaDirectories); if (options.entitySchemas) - connection.importEntities(options.entitySchemas); + connection.importSchemas(options.entitySchemas); if (options.entityDirectories && options.entityDirectories.length > 0) connection.importEntitiesFromDirectories(options.entityDirectories); diff --git a/src/connection-manager/CreateConnectionOptions.ts b/src/connection-manager/CreateConnectionOptions.ts index 6e8bbcabf..09cd25b11 100644 --- a/src/connection-manager/CreateConnectionOptions.ts +++ b/src/connection-manager/CreateConnectionOptions.ts @@ -1,4 +1,5 @@ import {ConnectionOptions} from "../connection/ConnectionOptions"; +import {EntitySchema} from "../metadata/entity-schema/EntitySchema"; /** * All options to help to create a new connection. @@ -38,7 +39,7 @@ export interface CreateConnectionOptions { /** * Entity schemas to be loaded for the new connection. */ - entitySchemas?: any[]; + entitySchemas?: EntitySchema[]; /** * List of directories from where entities will be loaded. diff --git a/src/connection/Connection.ts b/src/connection/Connection.ts index b250e410d..58ef682cf 100644 --- a/src/connection/Connection.ts +++ b/src/connection/Connection.ts @@ -280,12 +280,15 @@ export class Connection { * Gets repository for the given entity class. */ getRepository(entityClass: ConstructorFunction|Function): Repository; - // getRepository(entityClass: Function): Repository; /** * Gets repository for the given entity name. */ getRepository(entityClass: string): Repository; + /** + * Gets repository for the given entity name. + */ + getRepository(entityClassOrName: ConstructorFunction|Function|string): Repository; /** * Gets repository for the given entity class or name. @@ -294,7 +297,7 @@ export class Connection { if (!this.isConnected) throw new NoConnectionForRepositoryError(this.name); - const metadata = this.entityMetadatas.findByNameOrTarget(entityClassOrName); + const metadata = this.entityMetadatas.findByTarget(entityClassOrName); const repository = this.repositories.find(repository => Repository.ownsMetadata(repository, metadata)); if (!repository) throw new RepositoryNotFoundError(this.name, entityClassOrName); @@ -320,7 +323,7 @@ export class Connection { if (!this.isConnected) throw new NoConnectionForRepositoryError(this.name); - const metadata = this.entityMetadatas.findByNameOrTarget(entityClassOrName); + const metadata = this.entityMetadatas.findByTarget(entityClassOrName); const repository = this.repositories.find(repository => Repository.ownsMetadata(repository, metadata)); if (!repository) throw new RepositoryNotFoundError(this.name, entityClassOrName); @@ -337,7 +340,7 @@ export class Connection { if (!this.isConnected) throw new NoConnectionForRepositoryError(this.name); - const metadata = this.entityMetadatas.findByNameOrTarget(entityClass); + const metadata = this.entityMetadatas.findByTarget(entityClass); const repository = this.reactiveRepositories.find(repository => ReactiveRepository.ownsMetadata(repository, metadata)); if (!repository) throw new ReactiveRepositoryNotFoundError(this.name, entityClass); @@ -352,7 +355,7 @@ export class Connection { if (!this.isConnected) throw new NoConnectionForRepositoryError(this.name); - const metadata = this.entityMetadatas.findByNameOrTarget(entityClass); + const metadata = this.entityMetadatas.findByTarget(entityClass); const repository = this.reactiveRepositories.find(repository => ReactiveRepository.ownsMetadata(repository, metadata)); if (!repository) throw new RepositoryNotFoundError(this.name, entityClass); diff --git a/src/entity-manager/EntityManager.ts b/src/entity-manager/EntityManager.ts index e3385ebdd..e5eb82efe 100644 --- a/src/entity-manager/EntityManager.ts +++ b/src/entity-manager/EntityManager.ts @@ -34,6 +34,11 @@ export class EntityManager { */ getRepository(entityClass: string): Repository; + /** + * Gets repository for the given entity class or name. + */ + getRepository(entityClassOrName: ConstructorFunction|Function|string): Repository; + /** * Gets repository for the given entity class or name. */ @@ -77,8 +82,12 @@ export class EntityManager { /** * Checks if entity has an id. */ - hasId(entity: Function): boolean { - return this.getRepository(entity.constructor).hasId(entity); + hasId(entity: Function): boolean; + hasId(target: Function|string, entity: Function): boolean; + hasId(targetOrEntity: Function|string, maybeEntity?: Function): boolean { + const target = arguments.length === 2 ? targetOrEntity : targetOrEntity.constructor; + const entity = arguments.length === 2 ? maybeEntity : targetOrEntity; + return this.getRepository(target).hasId(entity); } /** @@ -123,17 +132,26 @@ export class EntityManager { /** * Persists (saves) a given entity in the database. */ - persist(entity: Entity): Promise { - return this.getRepository( entity.constructor).persist(entity); + persist(entity: Entity): Promise; + persist(targetOrEntity: Function|string, entity: Entity): Promise; + persist(targetOrEntity: Entity|Function|string, maybeEntity?: Entity): Promise { + // todo: extra casting is used strange tsc error here, check later maybe typescript bug + const target = arguments.length === 2 ? targetOrEntity : targetOrEntity.constructor; + const entity = arguments.length === 2 ? maybeEntity : targetOrEntity; + return this.getRepository( target).persist(entity); } /** * Removes a given entity from the database. */ - remove(entity: Entity) { - return this.getRepository( entity.constructor).remove(entity); + remove(entity: Entity): Promise; + remove(targetOrEntity: Function|string, entity: Entity): Promise; + remove(targetOrEntity: Entity|Function|string, maybeEntity?: Entity): Promise { + // todo: extra casting is used strange tsc error here, check later maybe typescript bug + const target = arguments.length === 2 ? targetOrEntity : targetOrEntity.constructor; + const entity = arguments.length === 2 ? maybeEntity : targetOrEntity; + return this.getRepository( target).remove(entity); } - /** * Finds entities that match given conditions. */ diff --git a/src/entity-manager/ReactiveEntityManager.ts b/src/entity-manager/ReactiveEntityManager.ts index c7523f6fb..94bd71dfa 100644 --- a/src/entity-manager/ReactiveEntityManager.ts +++ b/src/entity-manager/ReactiveEntityManager.ts @@ -27,29 +27,33 @@ export class ReactiveEntityManager { /** * Gets repository of the given entity. */ - getRepository(entityClass: ConstructorFunction|Function): Repository { + getRepository(entityClass: ConstructorFunction|Function|string): Repository { return this.connection.getRepository(entityClass); } /** * Gets reactive repository of the given entity. */ - getReactiveRepository(entityClass: ConstructorFunction|Function): ReactiveRepository { + getReactiveRepository(entityClass: ConstructorFunction|Function|string): ReactiveRepository { return this.connection.getReactiveRepository(entityClass); } /** * Gets reactive tree repository of the given entity. */ - getReactiveTreeRepository(entityClass: ConstructorFunction|Function): ReactiveTreeRepository { + getReactiveTreeRepository(entityClass: ConstructorFunction|Function|string): ReactiveTreeRepository { return this.connection.getReactiveTreeRepository(entityClass); } /** * Checks if entity has an id. */ - hasId(entity: Function): boolean { - return this.getReactiveRepository(entity.constructor).hasId(entity); + hasId(entity: Function): boolean; + hasId(target: Function|string, entity: Function): boolean; + hasId(targetOrEntity: Function|string, maybeEntity?: Function): boolean { + const target = arguments.length === 2 ? targetOrEntity : targetOrEntity.constructor; + const entity = arguments.length === 2 ? maybeEntity : targetOrEntity; + return this.getReactiveRepository(target).hasId(entity); } /** @@ -94,17 +98,25 @@ export class ReactiveEntityManager { /** * Persists (saves) a given entity in the database. */ - persist(entity: Entity): Rx.Observable { + persist(entity: Entity): Rx.Observable; + persist(targetOrEntity: Function|string, entity: Entity): Rx.Observable; + persist(targetOrEntity: Entity|Function|string, maybeEntity?: Entity): Rx.Observable { // todo: extra casting is used strange tsc error here, check later maybe typescript bug - return this.getReactiveRepository( entity.constructor).persist(entity); + const target = arguments.length === 2 ? targetOrEntity : targetOrEntity.constructor; + const entity = arguments.length === 2 ? maybeEntity : targetOrEntity; + return this.getReactiveRepository( target).persist(entity); } /** * Removes a given entity from the database. */ - remove(entity: Entity): Rx.Observable { + remove(entity: Entity): Rx.Observable; + remove(targetOrEntity: Function|string, entity: Entity): Rx.Observable; + remove(targetOrEntity: Entity|Function|string, maybeEntity?: Entity): Rx.Observable { // todo: extra casting is used strange tsc error here, check later maybe typescript bug - return this.getReactiveRepository( entity.constructor).remove(entity); + const target = arguments.length === 2 ? targetOrEntity : targetOrEntity.constructor; + const entity = arguments.length === 2 ? maybeEntity : targetOrEntity; + return this.getReactiveRepository( target).remove(entity); } /** diff --git a/src/metadata-args/JoinTableMetadataArgs.ts b/src/metadata-args/JoinTableMetadataArgs.ts index 29742194d..1ad9275f5 100644 --- a/src/metadata-args/JoinTableMetadataArgs.ts +++ b/src/metadata-args/JoinTableMetadataArgs.ts @@ -8,7 +8,7 @@ export interface JoinTableMetadataArgs { /** * Class to which this column is applied. */ - readonly target: Function; + readonly target: Function|string; /** * Class's property name to which this column is applied. diff --git a/src/metadata-args/MetadataArgsStorage.ts b/src/metadata-args/MetadataArgsStorage.ts index 725325b05..4104ba0a4 100644 --- a/src/metadata-args/MetadataArgsStorage.ts +++ b/src/metadata-args/MetadataArgsStorage.ts @@ -46,9 +46,9 @@ export class MetadataArgsStorage { /** * Gets merged (with all abstract classes) table metadatas for the given classes. */ - getMergedTableMetadatas(classes: Function[]) { - const allTableMetadataArgs = this.tables.filterByTargets(classes); - const tableMetadatas = this.tables.filterByTargets(classes).filter(table => table.type === "regular" || table.type === "closure"); + getMergedTableMetadatas(classes?: Function[]) { + const allTableMetadataArgs = classes ? this.tables.filterByTargets(classes) : this.tables; + const tableMetadatas = allTableMetadataArgs.filter(table => table.type === "regular" || table.type === "closure"); return tableMetadatas.map(tableMetadata => { return this.mergeWithAbstract(allTableMetadataArgs, tableMetadata); @@ -58,8 +58,9 @@ export class MetadataArgsStorage { /** * Gets merged (with all abstract classes) embeddable table metadatas for the given classes. */ - getMergedEmbeddableTableMetadatas(classes: Function[]) { - const embeddableTableMetadatas = this.tables.filterByTargets(classes).filter(table => table.type === "embeddable"); + getMergedEmbeddableTableMetadatas(classes?: Function[]) { + const tables = classes ? this.tables.filterByTargets(classes) : this.tables; + const embeddableTableMetadatas = tables.filter(table => table.type === "embeddable"); return embeddableTableMetadatas.map(embeddableTableMetadata => { return this.mergeWithEmbeddable(embeddableTableMetadatas, embeddableTableMetadata); diff --git a/src/metadata-args/collection/EntityMetadataCollection.ts b/src/metadata-args/collection/EntityMetadataCollection.ts index 701b88801..feb6ff89f 100644 --- a/src/metadata-args/collection/EntityMetadataCollection.ts +++ b/src/metadata-args/collection/EntityMetadataCollection.ts @@ -21,14 +21,6 @@ export class EntityMetadataCollection extends Array { return metadata; } - - findByNameOrTarget(nameOrTarget: Function|string) { - if (typeof nameOrTarget === "string") { - return this.findByName(nameOrTarget); - } else { - return this.findByTarget(nameOrTarget); - } - } findByName(name: string) { const metadata = this.find(metadata => metadata.name === name); diff --git a/src/metadata-builder/EntityMetadataBuilder.ts b/src/metadata-builder/EntityMetadataBuilder.ts index 105230719..37544852f 100644 --- a/src/metadata-builder/EntityMetadataBuilder.ts +++ b/src/metadata-builder/EntityMetadataBuilder.ts @@ -1,6 +1,6 @@ import {EntityMetadata} from "../metadata/EntityMetadata"; import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface"; -import {ColumnMetadata} from "../metadata/ColumnMetadata"; +import {ColumnMetadata, ColumnMode} from "../metadata/ColumnMetadata"; import {ColumnOptions} from "../decorator/options/ColumnOptions"; import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata"; import {EntityMetadataValidator} from "./EntityMetadataValidator"; @@ -18,6 +18,9 @@ import {MetadataArgsStorage} from "../metadata-args/MetadataArgsStorage"; import {TableMetadataArgs} from "../metadata-args/TableMetadataArgs"; import {ColumnMetadataArgs} from "../metadata-args/ColumnMetadataArgs"; import {RelationMetadataArgs} from "../metadata-args/RelationMetadataArgs"; +import {JoinColumnMetadataArgs} from "../metadata-args/JoinColumnMetadataArgs"; +import {JoinTableMetadataArgs} from "../metadata-args/JoinTableMetadataArgs"; +import {JoinColumnOptions} from "../decorator/options/JoinColumnOptions"; /** * Aggregates all metadata: table, column, relation into one collection grouped by tables for a given set of classes. @@ -57,10 +60,24 @@ export class EntityMetadataBuilder { // add columns metadata args from the schema Object.keys(schema.columns).forEach(columnName => { const columnSchema = schema.columns[columnName]; + let mode: ColumnMode = "regular"; + if (columnSchema.primary) + mode = "primary"; + if (columnSchema.createDate) + mode = "createDate"; + if (columnSchema.updateDate) + mode = "updateDate"; + if (columnSchema.version) + mode = "version"; + if (columnSchema.treeChildrenCount) + mode = "treeChildrenCount"; + if (columnSchema.treeLevel) + mode = "treeLevel"; + const column: ColumnMetadataArgs = { target: schema.target || schema.name, // targetId: schema.name, - mode: columnSchema.mode, + mode: mode, propertyName: columnName, // todo: what to do with it?: propertyType: options: { @@ -90,8 +107,8 @@ export class EntityMetadataBuilder { // targetId: schema.name, propertyName: relationName, // todo: what to do with it?: propertyType: - relationType: relationSchema.relationType, - type: relationSchema.type, + relationType: relationSchema.type, + type: relationSchema.target, inverseSideProperty: relationSchema.inverseSide, isTreeParent: relationSchema.isTreeParent, isTreeChildren: relationSchema.isTreeChildren, @@ -107,20 +124,63 @@ export class EntityMetadataBuilder { }; metadataArgsStorage.relations.add(relation); + + // add join column + if (relationSchema.joinColumn) { + if (typeof relationSchema.joinColumn === "boolean") { + const joinColumn: JoinColumnMetadataArgs = { + target: schema.target || schema.name, + // targetId: schema.name, + propertyName: relationName + }; + metadataArgsStorage.joinColumns.push(joinColumn); + } else { + const joinColumn: JoinColumnMetadataArgs = { + target: schema.target || schema.name, + // targetId: schema.name, + propertyName: relationName, + name: relationSchema.joinColumn.name, + referencedColumnName: relationSchema.joinColumn.referencedColumnName + }; + metadataArgsStorage.joinColumns.push(joinColumn); + } + } + + // add join table + if (relationSchema.joinTable) { + if (typeof relationSchema.joinTable === "boolean") { + const joinTable: JoinTableMetadataArgs = { + target: schema.target || schema.name, + // targetId: schema.name, + propertyName: relationName + }; + metadataArgsStorage.joinTables.push(joinTable); + } else { + const joinTable: JoinTableMetadataArgs = { + target: schema.target || schema.name, + // targetId: schema.name, + propertyName: relationName, + name: relationSchema.joinTable.name, + joinColumn: relationSchema.joinTable.joinColumn, + inverseJoinColumn: relationSchema.joinTable.inverseJoinColumn + }; + metadataArgsStorage.joinTables.push(joinTable); + } + } }); }); - return this.buildFromAnyMetadataArgsStorage(metadataArgsStorage, namingStrategy, []); + return this.buildFromAnyMetadataArgsStorage(metadataArgsStorage, namingStrategy); } /** * Builds a complete metadata aggregations for the given entity classes. */ - buildFromMetadataArgsStorage(namingStrategy: NamingStrategyInterface, entityClasses: Function[]): EntityMetadata[] { + buildFromMetadataArgsStorage(namingStrategy: NamingStrategyInterface, entityClasses?: Function[]): EntityMetadata[] { return this.buildFromAnyMetadataArgsStorage(getMetadataArgsStorage(), namingStrategy, entityClasses); } - buildFromAnyMetadataArgsStorage(metadataArgsStorage: MetadataArgsStorage, namingStrategy: NamingStrategyInterface, entityClasses: Function[]): EntityMetadata[] { + buildFromAnyMetadataArgsStorage(metadataArgsStorage: MetadataArgsStorage, namingStrategy: NamingStrategyInterface, entityClasses?: Function[]): EntityMetadata[] { const embeddableMergedArgs = metadataArgsStorage.getMergedEmbeddableTableMetadatas(entityClasses); const entityMetadatas = metadataArgsStorage.getMergedTableMetadatas(entityClasses).map(mergedArgs => { diff --git a/src/metadata/EntityMetadata.ts b/src/metadata/EntityMetadata.ts index 0754e5c70..da9079ec3 100644 --- a/src/metadata/EntityMetadata.ts +++ b/src/metadata/EntityMetadata.ts @@ -329,7 +329,7 @@ export class EntityMetadata { * Returns entity id of the given entity. */ getEntityId(entity: any) { - return entity[this.primaryColumn.propertyName]; + return entity ? entity[this.primaryColumn.propertyName] : undefined; } /** diff --git a/src/metadata/RelationMetadata.ts b/src/metadata/RelationMetadata.ts index 023706683..5b71f5b48 100644 --- a/src/metadata/RelationMetadata.ts +++ b/src/metadata/RelationMetadata.ts @@ -9,7 +9,7 @@ import {RelationMetadataArgs} from "../metadata-args/RelationMetadataArgs"; /** * Function that returns a type of the field. Returned value must be a class used on the relation. */ -export type RelationTypeInFunction = ((type?: any) => Function)|string; // todo: |string ? +export type RelationTypeInFunction = ((type?: any) => Function)|Function|string; // todo: |string ? /** diff --git a/src/metadata/entity-schema/EntitySchema.ts b/src/metadata/entity-schema/EntitySchema.ts index f0e4e6d50..e3763cc1f 100644 --- a/src/metadata/entity-schema/EntitySchema.ts +++ b/src/metadata/entity-schema/EntitySchema.ts @@ -3,6 +3,7 @@ import {ColumnType} from "../types/ColumnTypes"; import {TableType} from "../TableMetadata"; import {RelationType} from "../types/RelationTypes"; import {OnDeleteType} from "../ForeignKeyMetadata"; +import {JoinColumnOptions} from "../../decorator/options/JoinColumnOptions"; export interface EntitySchema { @@ -54,11 +55,34 @@ export interface EntitySchema { [columnName: string]: { /** - * Column mode in which column will work. - * For example, "primary" means that it will be a primary column, or "createDate" means that it will create a create - * date column. + * Indicates if this column is a primary column. */ - mode: ColumnMode; + primary: boolean; + + /** + * Indicates if this column is a created date column. + */ + createDate: boolean; + + /** + * Indicates if this column is an update date column. + */ + updateDate: boolean; + + /** + * Indicates if this column is a version column. + */ + version: boolean; + + /** + * Indicates if this column is a treeChildrenCount column. + */ + treeChildrenCount: boolean; + + /** + * Indicates if this column is a treeLevel column. + */ + treeLevel: boolean; /** * Column type. Must be one of the value from the ColumnTypes class. @@ -135,15 +159,14 @@ export interface EntitySchema { [relationName: string]: { /** - * Type of relation. Can be one of the value of the RelationTypes class. + * Indicates with which entity this relation is made. */ - relationType: RelationType; + target: Function|string; /** - * Type of the relation. This type is in function because of language specifics and problems with recursive - * referenced classes. + * Type of relation. Can be one of the value of the RelationTypes class. */ - type: string; + type: RelationType; /** * Inverse side of the relation. @@ -154,6 +177,22 @@ export interface EntitySchema { * Join table options of this column. If set to true then it simply means that it has a join table. */ joinTable?: boolean|{ + + /** + * Name of the table that will be created to store values of the both tables (join table). + * By default is auto generated. + */ + name?: string; + + /** + * First column of the join table. + */ + joinColumn?: JoinColumnOptions; + + /** + * Second (inverse) column of the join table. + */ + inverseJoinColumn?: JoinColumnOptions; }; diff --git a/src/persistment/EntityPersistOperationsBuilder.ts b/src/persistment/EntityPersistOperationsBuilder.ts index 5fcd7a9ea..a60d7ad53 100644 --- a/src/persistment/EntityPersistOperationsBuilder.ts +++ b/src/persistment/EntityPersistOperationsBuilder.ts @@ -9,6 +9,7 @@ import {CascadesNotAllowedError} from "./error/CascadesNotAllowedError"; import {RemoveOperation} from "./operation/RemoveOperation"; import {EntityMetadataCollection} from "../metadata-args/collection/EntityMetadataCollection"; import {UpdateByInverseSideOperation} from "./operation/UpdateByInverseSideOperation"; +import {JunctionRemoveOperation} from "./operation/JunctionRemoveOperation"; /** * 1. collect all exist objects from the db entity @@ -57,8 +58,8 @@ export class EntityPersistOperationBuilder { * Finds columns and relations from entity2 which does not exist or does not match in entity1. */ buildFullPersistment(metadata: EntityMetadata, - dbEntity: any, - persistedEntity: any, + dbEntity: EntityWithId, + persistedEntity: EntityWithId, dbEntities: EntityWithId[], allPersistedEntities: EntityWithId[]): PersistOperation { @@ -87,8 +88,8 @@ export class EntityPersistOperationBuilder { * Finds columns and relations from entity2 which does not exist or does not match in entity1. */ buildOnlyRemovement(metadata: EntityMetadata, - dbEntity: any, - persistedEntity: any, + dbEntity: EntityWithId, + persistedEntity: EntityWithId, dbEntities: EntityWithId[], allPersistedEntities: EntityWithId[]): PersistOperation { // const dbEntities = this.extractObjectsById(dbEntity, metadata); @@ -109,12 +110,12 @@ export class EntityPersistOperationBuilder { // Private Methods // ------------------------------------------------------------------------- - private findCascadeInsertedEntities(newEntity: any, + private findCascadeInsertedEntities(newEntityWithId: EntityWithId, dbEntities: EntityWithId[], fromRelation?: RelationMetadata, operations: InsertOperation[] = []): InsertOperation[] { - // const metadata = this.connection.getEntityMetadata(newEntity.constructor); - const metadata = this.entityMetadatas.findByTarget(newEntity.constructor); + const newEntity = newEntityWithId.entity; + const metadata = this.entityMetadatas.findByTarget(newEntityWithId.entityTarget); const isObjectNew = !this.findEntityWithId(dbEntities, metadata.target, newEntity[metadata.primaryColumn.propertyName]); // if object is new and should be inserted, we check if cascades are allowed before add it to operations list @@ -122,17 +123,30 @@ export class EntityPersistOperationBuilder { return operations; // looks like object is new here, but cascades are not allowed - then we should stop iteration } else if (isObjectNew && !operations.find(o => o.entity === newEntity)) { // object is new and cascades are allow here - operations.push(new InsertOperation(newEntity)); + operations.push(new InsertOperation(newEntityWithId.entityTarget, newEntity)); } metadata.relations.forEach(relation => { const value = this.getEntityRelationValue(relation, newEntity); + const inverseMetadata = relation.inverseEntityMetadata; if (!value) return; if (value instanceof Array) { - value.map((subValue: any) => this.findCascadeInsertedEntities(subValue, dbEntities, relation, operations)); + value.forEach((subValue: any) => { + const subValueWithId: EntityWithId = { + id: inverseMetadata.getEntityId(subValue), + entity: subValue, + entityTarget: inverseMetadata.target + }; + this.findCascadeInsertedEntities(subValueWithId, dbEntities, relation, operations); + }); } else { - this.findCascadeInsertedEntities(value, dbEntities, relation, operations); + const valueWithId: EntityWithId = { + id: inverseMetadata.getEntityId(value), + entity: value, + entityTarget: inverseMetadata.target + }; + this.findCascadeInsertedEntities(valueWithId, dbEntities, relation, operations); } }); @@ -141,11 +155,14 @@ export class EntityPersistOperationBuilder { private findCascadeUpdateEntities(updatesByRelations: UpdateByRelationOperation[], metadata: EntityMetadata, - dbEntity: any, - newEntity: any, + dbEntityWithId: EntityWithId, + newEntityWithId: EntityWithId, dbEntities: EntityWithId[], fromRelation?: RelationMetadata, operations: UpdateOperation[] = []): UpdateOperation[] { + const dbEntity = dbEntityWithId.entity; + const newEntity = newEntityWithId.entity; + if (!dbEntity) return operations; @@ -157,7 +174,7 @@ export class EntityPersistOperationBuilder { } else if (diffColumns.length || diffRelations.length) { const entityId = newEntity[metadata.primaryColumn.propertyName]; if (entityId) - operations.push(new UpdateOperation(newEntity, entityId, diffColumns, diffRelations)); + operations.push(new UpdateOperation(newEntityWithId.entityTarget, newEntity, entityId, diffColumns, diffRelations)); } metadata.relations.forEach(relation => { @@ -179,16 +196,40 @@ export class EntityPersistOperationBuilder { const dbValue = dbEntities.find(dbValue => { return dbValue.entityTarget === valueTarget && dbValue.entity[referencedColumnName] === subEntity[relationIdColumnName]; }); - if (dbValue) - this.findCascadeUpdateEntities(updatesByRelations, relMetadata, dbValue.entity, subEntity, dbEntities, relation, operations); + if (dbValue) { + const dbValueWithId: EntityWithId = { + id: relMetadata.getEntityId(dbValue.entity), + entity: dbValue.entity, + entityTarget: relMetadata.target + }; + const subEntityWithId: EntityWithId = { + id: relMetadata.getEntityId(subEntity), + entity: subEntity, + entityTarget: relMetadata.target + }; + this.findCascadeUpdateEntities(updatesByRelations, relMetadata, dbValueWithId, subEntityWithId, dbEntities, relation, operations); + } }); } else { const dbValue = dbEntities.find(dbValue => { return dbValue.entityTarget === valueTarget && dbValue.entity[referencedColumnName] === value[relationIdColumnName]; }); - if (dbValue) - this.findCascadeUpdateEntities(updatesByRelations, relMetadata, dbValue.entity, value, dbEntities, relation, operations); + if (dbValue) { + + const dbValueWithId: EntityWithId = { + id: relMetadata.getEntityId(dbValue.entity), + entity: dbValue.entity, + entityTarget: relMetadata.target + }; + const valueWithId: EntityWithId = { + id: relMetadata.getEntityId(value), + entity: value, + entityTarget: relMetadata.target + }; + + this.findCascadeUpdateEntities(updatesByRelations, relMetadata, dbValueWithId, valueWithId, dbEntities, relation, operations); + } } }); @@ -196,12 +237,14 @@ export class EntityPersistOperationBuilder { } private findCascadeRemovedEntities(metadata: EntityMetadata, - dbEntity: any, + dbEntityWithId: EntityWithId, allPersistedEntities: EntityWithId[], fromRelation: RelationMetadata|undefined, fromMetadata: EntityMetadata|undefined, fromEntityId: any, parentAlreadyRemoved: boolean = false): RemoveOperation[] { + const dbEntity = dbEntityWithId.entity; + let operations: RemoveOperation[] = []; if (!dbEntity) return operations; @@ -214,7 +257,7 @@ export class EntityPersistOperationBuilder { return operations; // looks like object is removed here, but cascades are not allowed - then we should stop iteration } else if (isObjectRemoved) { // object is remove and cascades are allow here - operations.push(new RemoveOperation(dbEntity, entityId, fromMetadata, fromRelation, fromEntityId)); + operations.push(new RemoveOperation(dbEntityWithId.entityTarget, dbEntity, entityId, fromMetadata, fromRelation, fromEntityId)); } metadata.relations.forEach(relation => { @@ -224,11 +267,23 @@ export class EntityPersistOperationBuilder { if (dbValue instanceof Array) { dbValue.forEach((subDbEntity: any) => { - const relationOperations = this.findCascadeRemovedEntities(relMetadata, subDbEntity, allPersistedEntities, relation, metadata, dbEntity[metadata.primaryColumn.propertyName], isObjectRemoved); + const subDbEntityWithId: EntityWithId = { + id: relMetadata.getEntityId(subDbEntity), + entity: subDbEntity, + entityTarget: relMetadata.target + }; + + const relationOperations = this.findCascadeRemovedEntities(relMetadata, subDbEntityWithId, allPersistedEntities, relation, metadata, dbEntity[metadata.primaryColumn.propertyName], isObjectRemoved); relationOperations.forEach(o => operations.push(o)); }); } else { - const relationOperations = this.findCascadeRemovedEntities(relMetadata, dbValue, allPersistedEntities, relation, metadata, dbEntity[metadata.primaryColumn.propertyName], isObjectRemoved); + const dbValueWithId: EntityWithId = { + id: relMetadata.getEntityId(dbValue), + entity: dbValue, + entityTarget: relMetadata.target + }; + + const relationOperations = this.findCascadeRemovedEntities(relMetadata, dbValueWithId, allPersistedEntities, relation, metadata, dbEntity[metadata.primaryColumn.propertyName], isObjectRemoved); relationOperations.forEach(o => operations.push(o)); } }, []); @@ -237,13 +292,16 @@ export class EntityPersistOperationBuilder { } private updateInverseRelations(metadata: EntityMetadata, - dbEntity: any, - newEntity: any, + dbEntityWithId: EntityWithId, + newEntityWithId: EntityWithId, operations: UpdateByInverseSideOperation[] = []): UpdateByInverseSideOperation[] { + const dbEntity = dbEntityWithId.entity; + const newEntity = newEntityWithId.entity; metadata.relations .filter(relation => relation.isOneToMany) // todo: maybe need to check isOneToOne and not owner // .filter(relation => newEntity[relation.propertyName] instanceof Array) // todo: what to do with empty relations? need to set to NULL from inverse side? .forEach(relation => { + const relationMetadata = relation.inverseEntityMetadata; // to find new objects in relation go throw all objects in newEntity and check if they don't exist in dbEntity if (newEntity && newEntity[relation.propertyName] instanceof Array) { @@ -255,7 +313,7 @@ export class EntityPersistOperationBuilder { return relation.inverseEntityMetadata.getEntityId(newSubEntity) === relation.inverseEntityMetadata.getEntityId(dbSubEntity); }); }).forEach((subEntity: any) => { - operations.push(new UpdateByInverseSideOperation("update", subEntity, newEntity, relation)); + operations.push(new UpdateByInverseSideOperation(relationMetadata.target, newEntityWithId.entityTarget, "update", subEntity, newEntity, relation)); }); } @@ -269,7 +327,7 @@ export class EntityPersistOperationBuilder { return relation.inverseEntityMetadata.getEntityId(dbSubEntity) === relation.inverseEntityMetadata.getEntityId(newSubEntity); }); }).forEach((subEntity: any) => { - operations.push(new UpdateByInverseSideOperation("remove", subEntity, newEntity, relation)); + operations.push(new UpdateByInverseSideOperation(relationMetadata.target, newEntityWithId.entityTarget, "remove", subEntity, newEntity, relation)); }); } @@ -286,37 +344,49 @@ export class EntityPersistOperationBuilder { * */ - private updateRelations(insertOperations: InsertOperation[], newEntity: any): UpdateByRelationOperation[] { + private updateRelations(insertOperations: InsertOperation[], newEntity: EntityWithId): UpdateByRelationOperation[] { return insertOperations.reduce((operations, insertOperation) => { return operations.concat(this.findRelationsWithEntityInside(insertOperation, newEntity)); }, []); } - private findRelationsWithEntityInside(insertOperation: InsertOperation, entityToSearchIn: any): UpdateByRelationOperation[] { - // updateByt metadata = this.connection.getEntityMetadata(entityToSearchIn.constructor); - const metadata = this.entityMetadatas.findByTarget(entityToSearchIn.constructor); + private findRelationsWithEntityInside(insertOperation: InsertOperation, entityToSearchInWithId: EntityWithId): UpdateByRelationOperation[] { + const entityToSearchIn = entityToSearchInWithId.entity; + const metadata = this.entityMetadatas.findByTarget(entityToSearchInWithId.entityTarget); const operations: UpdateByRelationOperation[] = []; metadata.relations.forEach(relation => { const value = this.getEntityRelationValue(relation, entityToSearchIn); + const inverseMetadata = relation.inverseEntityMetadata; if (!value) return; if (value instanceof Array) { value.forEach((sub: any) => { if (!relation.isManyToMany && sub === insertOperation.entity) - operations.push(new UpdateByRelationOperation(entityToSearchIn, insertOperation, relation)); + operations.push(new UpdateByRelationOperation(entityToSearchInWithId.entityTarget, entityToSearchIn, insertOperation, relation)); - const subOperations = this.findRelationsWithEntityInside(insertOperation, sub); + const subWithId: EntityWithId = { + id: inverseMetadata.getEntityId(sub), + entity: sub, + entityTarget: inverseMetadata.target + }; + const subOperations = this.findRelationsWithEntityInside(insertOperation, subWithId); subOperations.forEach(o => operations.push(o)); }); } else if (value) { if (value === insertOperation.entity) { - operations.push(new UpdateByRelationOperation(entityToSearchIn, insertOperation, relation)); + operations.push(new UpdateByRelationOperation(entityToSearchInWithId.entityTarget, entityToSearchIn, insertOperation, relation)); } - const subOperations = this.findRelationsWithEntityInside(insertOperation, value); + + const valueWithId: EntityWithId = { + id: inverseMetadata.getEntityId(value), + entity: value, + entityTarget: inverseMetadata.target + }; + const subOperations = this.findRelationsWithEntityInside(insertOperation, valueWithId); subOperations.forEach(o => operations.push(o)); } @@ -325,7 +395,8 @@ export class EntityPersistOperationBuilder { return operations; } - private findJunctionInsertOperations(metadata: EntityMetadata, newEntity: any, dbEntities: EntityWithId[], isRoot = true): JunctionInsertOperation[] { + private findJunctionInsertOperations(metadata: EntityMetadata, newEntityWithId: EntityWithId, dbEntities: EntityWithId[], isRoot = true): JunctionInsertOperation[] { + const newEntity = newEntityWithId.entity; const dbEntity = dbEntities.find(dbEntity => { return dbEntity.id === newEntity[metadata.primaryColumn.propertyName] && dbEntity.entityTarget === metadata.target; }); @@ -347,19 +418,31 @@ export class EntityPersistOperationBuilder { operations.push({ metadata: relation.junctionEntityMetadata, entity1: newEntity, - entity2: subEntity + entity2: subEntity, + entity1Target: newEntityWithId.entityTarget, + entity2Target: relationMetadata.target }); } } if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) { - const subOperations = this.findJunctionInsertOperations(relationMetadata, subEntity, dbEntities, false); + const subEntityWithId: EntityWithId = { + id: relationMetadata.getEntityId(subEntity), + entity: subEntity, + entityTarget: relationMetadata.target + }; + const subOperations = this.findJunctionInsertOperations(relationMetadata, subEntityWithId, dbEntities, false); subOperations.forEach(o => operations.push(o)); } }); } else { if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) { - const subOperations = this.findJunctionInsertOperations(relationMetadata, value, dbEntities, false); + const valueWithId: EntityWithId = { + id: relationMetadata.getEntityId(value), + entity: value, + entityTarget: relationMetadata.target + }; + const subOperations = this.findJunctionInsertOperations(relationMetadata, valueWithId, dbEntities, false); subOperations.forEach(o => operations.push(o)); } } @@ -368,7 +451,8 @@ export class EntityPersistOperationBuilder { }, []); } - private findJunctionRemoveOperations(metadata: EntityMetadata, dbEntity: any, newEntities: EntityWithId[], isRoot = true): JunctionInsertOperation[] { + private findJunctionRemoveOperations(metadata: EntityMetadata, dbEntityWithId: EntityWithId, newEntities: EntityWithId[], isRoot = true): JunctionInsertOperation[] { + const dbEntity = dbEntityWithId.entity; if (!dbEntity) // if new entity is persisted then it does not have anything to be deleted return []; @@ -393,25 +477,39 @@ export class EntityPersistOperationBuilder { operations.push({ metadata: relation.junctionEntityMetadata, entity1: dbEntity, - entity2: subEntity + entity2: subEntity, + entity1Target: dbEntityWithId.entityTarget, + entity2Target: relationMetadata.target }); } } if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) { - const subOperations = this.findJunctionRemoveOperations(relationMetadata, subEntity, newEntities, false); + const subEntityWithId: EntityWithId = { + id: relationMetadata.getEntityId(subEntity), + entity: subEntity, + entityTarget: relationMetadata.target + }; + + const subOperations = this.findJunctionRemoveOperations(relationMetadata, subEntityWithId, newEntities, false); subOperations.forEach(o => operations.push(o)); } }); } else { if (isRoot || this.checkCascadesAllowed("update", metadata, relation)) { - const subOperations = this.findJunctionRemoveOperations(relationMetadata, dbValue, newEntities, false); + const dbValueWithId: EntityWithId = { + id: relationMetadata.getEntityId(dbValue), + entity: dbValue, + entityTarget: relationMetadata.target + }; + + const subOperations = this.findJunctionRemoveOperations(relationMetadata, dbValueWithId, newEntities, false); subOperations.forEach(o => operations.push(o)); } } return operations; - }, []); + }, []); } private diffColumns(metadata: EntityMetadata, newEntity: any, dbEntity: any) { @@ -441,14 +539,12 @@ export class EntityPersistOperationBuilder { const entityTarget = relation.target; const newEntityRelationMetadata = this.entityMetadatas.findByTarget(entityTarget); - const dbEntityRelationMetadata = this.entityMetadatas.findByTarget(dbEntity[relation.propertyName].constructor); + const dbEntityRelationMetadata = this.entityMetadatas.findByTarget(entityTarget); return newEntityRelationMetadata.getEntityId(newEntity[relation.propertyName]) !== dbEntityRelationMetadata.getEntityId(dbEntity[relation.propertyName]); }); } private findEntityWithId(entityWithIds: EntityWithId[], entityTarget: Function|string, id: any) { - // console.log(entityWithId.entityTarget); - // console.log(entityWithId.entity.constructor); return entityWithIds.find(entityWithId => entityWithId.id === id && entityWithId.entityTarget === entityTarget); } diff --git a/src/persistment/PersistOperationExecutor.ts b/src/persistment/PersistOperationExecutor.ts index e3aa17409..a545c6226 100644 --- a/src/persistment/PersistOperationExecutor.ts +++ b/src/persistment/PersistOperationExecutor.ts @@ -136,7 +136,7 @@ export class PersistOperationExecutor { private executeInsertClosureTableOperations(persistOperation: PersistOperation) { const promises = persistOperation.inserts .filter(operation => { - const metadata = this.entityMetadatas.findByTarget(operation.entity.constructor); + const metadata = this.entityMetadatas.findByTarget(operation.target); return metadata.table.isClosure; }) .map(operation => { @@ -221,7 +221,7 @@ export class PersistOperationExecutor { */ private executeRemoveOperations(persistOperation: PersistOperation) { return Promise.all(persistOperation.removes.map(operation => { - return this.delete(operation.entity); + return this.delete(operation.target, operation.entity); })); } @@ -230,7 +230,7 @@ export class PersistOperationExecutor { */ private updateIdsOfInsertedEntities(persistOperation: PersistOperation) { persistOperation.inserts.forEach(insertOperation => { - const metadata = this.entityMetadatas.findByTarget(insertOperation.entity.constructor); + const metadata = this.entityMetadatas.findByTarget(insertOperation.target); insertOperation.entity[metadata.primaryColumn.propertyName] = insertOperation.entityId; }); } @@ -240,7 +240,7 @@ export class PersistOperationExecutor { */ private updateSpecialColumnsInEntities(persistOperation: PersistOperation) { persistOperation.inserts.forEach(insertOperation => { - const metadata = this.entityMetadatas.findByTarget(insertOperation.entity.constructor); + const metadata = this.entityMetadatas.findByTarget(insertOperation.target); if (metadata.hasUpdateDateColumn) insertOperation.entity[metadata.updateDateColumn.propertyName] = insertOperation.date; if (metadata.hasCreateDateColumn) @@ -257,7 +257,7 @@ export class PersistOperationExecutor { }*/ }); persistOperation.updates.forEach(updateOperation => { - const metadata = this.entityMetadatas.findByTarget(updateOperation.entity.constructor); + const metadata = this.entityMetadatas.findByTarget(updateOperation.target); if (metadata.hasUpdateDateColumn) updateOperation.entity[metadata.updateDateColumn.propertyName] = updateOperation.date; if (metadata.hasCreateDateColumn) @@ -272,9 +272,9 @@ export class PersistOperationExecutor { */ private updateIdsOfRemovedEntities(persistOperation: PersistOperation) { persistOperation.removes.forEach(removeOperation => { - const metadata = this.entityMetadatas.findByTarget(removeOperation.entity.constructor); + const metadata = this.entityMetadatas.findByTarget(removeOperation.target); const removedEntity = persistOperation.allPersistedEntities.find(allNewEntity => { - return allNewEntity.entity.constructor === removeOperation.entity.constructor && allNewEntity.id === removeOperation.entity[metadata.primaryColumn.propertyName]; + return allNewEntity.entityTarget === removeOperation.target && allNewEntity.id === removeOperation.entity[metadata.primaryColumn.propertyName]; }); if (removedEntity) removedEntity.entity[metadata.primaryColumn.propertyName] = undefined; @@ -289,7 +289,7 @@ export class PersistOperationExecutor { const relatedInsertOperation = insertOperations.find(o => o.entity === operation.targetEntity); const idInInserts = relatedInsertOperation ? relatedInsertOperation.entityId : null; if (operation.updatedRelation.isOneToMany) { - const metadata = this.entityMetadatas.findByTarget(operation.insertOperation.entity.constructor); + const metadata = this.entityMetadatas.findByTarget(operation.insertOperation.target); if (operation.insertOperation.entity === target) updateMap[operation.updatedRelation.inverseRelation.propertyName] = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts; @@ -307,7 +307,7 @@ export class PersistOperationExecutor { const relatedInsertOperation = insertOperations.find(o => o.entity === operation.targetEntity); const idInInserts = relatedInsertOperation ? relatedInsertOperation.entityId : null; if (operation.updatedRelation.isOneToMany || operation.updatedRelation.isOneToOneNotOwner) { - const metadata = this.entityMetadatas.findByTarget(operation.insertOperation.entity.constructor); + const metadata = this.entityMetadatas.findByTarget(operation.insertOperation.target); tableName = metadata.table.name; relationName = operation.updatedRelation.inverseRelation.name; relationId = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts; @@ -315,7 +315,7 @@ export class PersistOperationExecutor { id = operation.insertOperation.entityId; } else { - const metadata = this.entityMetadatas.findByTarget(operation.targetEntity.constructor); + const metadata = this.entityMetadatas.findByTarget(operation.entityTarget); tableName = metadata.table.name; relationName = operation.updatedRelation.name; relationId = operation.insertOperation.entityId; @@ -326,8 +326,8 @@ export class PersistOperationExecutor { } private updateInverseRelation(operation: UpdateByInverseSideOperation, insertOperations: InsertOperation[]) { - const targetEntityMetadata = this.entityMetadatas.findByTarget(operation.targetEntity.constructor); - const fromEntityMetadata = this.entityMetadatas.findByTarget(operation.fromEntity.constructor); + const targetEntityMetadata = this.entityMetadatas.findByTarget(operation.entityTarget); + const fromEntityMetadata = this.entityMetadatas.findByTarget(operation.fromEntityTarget); const tableName = targetEntityMetadata.table.name; const targetRelation = operation.fromRelation.inverseRelation; const idColumn = targetEntityMetadata.primaryColumn.name; @@ -350,7 +350,7 @@ export class PersistOperationExecutor { private update(updateOperation: UpdateOperation) { const entity = updateOperation.entity; - const metadata = this.entityMetadatas.findByTarget(entity.constructor); + const metadata = this.entityMetadatas.findByTarget(updateOperation.target); const values: { [key: string]: any } = {}; updateOperation.columns.forEach(column => { @@ -386,14 +386,14 @@ export class PersistOperationExecutor { throw new Error("Remove operation relation is not set"); // todo: find out how its possible } - private delete(entity: any) { - const metadata = this.entityMetadatas.findByTarget(entity.constructor); + private delete(target: Function|string, entity: any) { + const metadata = this.entityMetadatas.findByTarget(target); return this.driver.delete(metadata.table.name, { [metadata.primaryColumn.name]: entity[metadata.primaryColumn.propertyName] }); } private insert(operation: InsertOperation) { const entity = operation.entity; - const metadata = this.entityMetadatas.findByTarget(entity.constructor); + const metadata = this.entityMetadatas.findByTarget(operation.target); const columns = metadata.columns .filter(column => !column.isVirtual && column.hasEntityValue(entity)); @@ -449,7 +449,7 @@ export class PersistOperationExecutor { private insertIntoClosureTable(operation: InsertOperation, updateMap: { [key: string]: any }) { const entity = operation.entity; - const metadata = this.entityMetadatas.findByTarget(entity.constructor); + const metadata = this.entityMetadatas.findByTarget(operation.target); const parentEntity = entity[metadata.treeParentRelation.propertyName]; let parentEntityId: any = 0; @@ -472,7 +472,7 @@ export class PersistOperationExecutor { } private updateTreeLevel(operation: InsertOperation) { - const metadata = this.entityMetadatas.findByTarget(operation.entity.constructor); + const metadata = this.entityMetadatas.findByTarget(operation.target); if (metadata.hasTreeLevelColumn && operation.treeLevel) { const values = { [metadata.treeLevelColumn.name]: operation.treeLevel }; @@ -485,8 +485,8 @@ export class PersistOperationExecutor { private insertJunctions(junctionOperation: JunctionInsertOperation, insertOperations: InsertOperation[]) { const junctionMetadata = junctionOperation.metadata; - const metadata1 = this.entityMetadatas.findByTarget(junctionOperation.entity1.constructor); - const metadata2 = this.entityMetadatas.findByTarget(junctionOperation.entity2.constructor); + const metadata1 = this.entityMetadatas.findByTarget(junctionOperation.entity1Target); + const metadata2 = this.entityMetadatas.findByTarget(junctionOperation.entity2Target); const columns = junctionMetadata.columns.map(column => column.name); const insertOperation1 = insertOperations.find(o => o.entity === junctionOperation.entity1); const insertOperation2 = insertOperations.find(o => o.entity === junctionOperation.entity2); @@ -523,8 +523,8 @@ export class PersistOperationExecutor { private removeJunctions(junctionOperation: JunctionRemoveOperation) { const junctionMetadata = junctionOperation.metadata; - const metadata1 = this.entityMetadatas.findByTarget(junctionOperation.entity1.constructor); - const metadata2 = this.entityMetadatas.findByTarget(junctionOperation.entity2.constructor); + const metadata1 = this.entityMetadatas.findByTarget(junctionOperation.entity1Target); + const metadata2 = this.entityMetadatas.findByTarget(junctionOperation.entity2Target); const columns = junctionMetadata.columns.map(column => column.name); const id1 = junctionOperation.entity1[metadata1.primaryColumn.propertyName]; const id2 = junctionOperation.entity2[metadata2.primaryColumn.propertyName]; diff --git a/src/persistment/operation/InsertOperation.ts b/src/persistment/operation/InsertOperation.ts index 64ed3eb23..2c3dce6ca 100644 --- a/src/persistment/operation/InsertOperation.ts +++ b/src/persistment/operation/InsertOperation.ts @@ -5,7 +5,8 @@ export class InsertOperation { public treeLevel: number; - constructor(public entity: any, + constructor(public target: Function|string, // todo: probably should be metadata here + public entity: any, public entityId?: number, public date = new Date()) { } diff --git a/src/persistment/operation/JunctionInsertOperation.ts b/src/persistment/operation/JunctionInsertOperation.ts index aa75505f3..7f7e52cb7 100644 --- a/src/persistment/operation/JunctionInsertOperation.ts +++ b/src/persistment/operation/JunctionInsertOperation.ts @@ -6,6 +6,8 @@ import {EntityMetadata} from "../../metadata/EntityMetadata"; export class JunctionInsertOperation { constructor(public metadata: EntityMetadata, public entity1: any, - public entity2: any) { + public entity2: any, + public entity1Target: Function|string, + public entity2Target: Function|string) { } } \ No newline at end of file diff --git a/src/persistment/operation/JunctionRemoveOperation.ts b/src/persistment/operation/JunctionRemoveOperation.ts index 59fda9d60..f64897718 100644 --- a/src/persistment/operation/JunctionRemoveOperation.ts +++ b/src/persistment/operation/JunctionRemoveOperation.ts @@ -6,6 +6,8 @@ import {EntityMetadata} from "../../metadata/EntityMetadata"; export class JunctionRemoveOperation { constructor(public metadata: EntityMetadata, public entity1: any, - public entity2: any) { + public entity2: any, + public entity1Target: any, + public entity2Target: any) { } } \ No newline at end of file diff --git a/src/persistment/operation/PersistOperation.ts b/src/persistment/operation/PersistOperation.ts index 5eacd195f..d1052a0e7 100644 --- a/src/persistment/operation/PersistOperation.ts +++ b/src/persistment/operation/PersistOperation.ts @@ -22,8 +22,8 @@ export class PersistOperation { // todo: what if we have two same entities in the insert operations? - dbEntity: any; - persistedEntity: any; + dbEntity: EntityWithId; + persistedEntity: EntityWithId; allDbEntities: EntityWithId[]; allPersistedEntities: EntityWithId[]; inserts: InsertOperation[] = []; diff --git a/src/persistment/operation/RemoveOperation.ts b/src/persistment/operation/RemoveOperation.ts index 244fc817c..db96f0e73 100644 --- a/src/persistment/operation/RemoveOperation.ts +++ b/src/persistment/operation/RemoveOperation.ts @@ -5,7 +5,8 @@ import {EntityMetadata} from "../../metadata/EntityMetadata"; * @internal */ export class RemoveOperation { - constructor(public entity: any, + constructor(public target: Function|string, // todo: probably should be metadata here + public entity: any, public entityId: any, public fromMetadata: EntityMetadata, // todo: use relation.metadata instead? public relation: RelationMetadata|undefined, diff --git a/src/persistment/operation/UpdateByInverseSideOperation.ts b/src/persistment/operation/UpdateByInverseSideOperation.ts index 2266a72f3..44f1c96ef 100644 --- a/src/persistment/operation/UpdateByInverseSideOperation.ts +++ b/src/persistment/operation/UpdateByInverseSideOperation.ts @@ -4,7 +4,9 @@ import {RelationMetadata} from "../../metadata/RelationMetadata"; * @internal */ export class UpdateByInverseSideOperation { - constructor(public operationType: "update"|"remove", + constructor(public entityTarget: Function|string, // todo: probably must be entity metadata here? + public fromEntityTarget: Function|string, + public operationType: "update"|"remove", public targetEntity: any, public fromEntity: any, public fromRelation: RelationMetadata) { diff --git a/src/persistment/operation/UpdateByRelationOperation.ts b/src/persistment/operation/UpdateByRelationOperation.ts index a0e976d1f..6c6b99c65 100644 --- a/src/persistment/operation/UpdateByRelationOperation.ts +++ b/src/persistment/operation/UpdateByRelationOperation.ts @@ -5,7 +5,8 @@ import {RelationMetadata} from "../../metadata/RelationMetadata"; * @internal */ export class UpdateByRelationOperation { - constructor(public targetEntity: any, + constructor(public entityTarget: Function|string, // todo: probably must be entity metadata here? + public targetEntity: any, public insertOperation: InsertOperation, public updatedRelation: RelationMetadata) { } diff --git a/src/persistment/operation/UpdateOperation.ts b/src/persistment/operation/UpdateOperation.ts index e9cdd6f57..77c7f5002 100644 --- a/src/persistment/operation/UpdateOperation.ts +++ b/src/persistment/operation/UpdateOperation.ts @@ -5,7 +5,8 @@ import {RelationMetadata} from "../../metadata/RelationMetadata"; * @internal */ export class UpdateOperation { - constructor(public entity: any, + constructor(public target: Function|string, + public entity: any, public entityId: any, public columns: ColumnMetadata[], public relations: RelationMetadata[], diff --git a/src/query-builder/QueryBuilder.ts b/src/query-builder/QueryBuilder.ts index d7a6f1783..3834bd135 100644 --- a/src/query-builder/QueryBuilder.ts +++ b/src/query-builder/QueryBuilder.ts @@ -366,7 +366,7 @@ export class QueryBuilder { }) .then(results => this.rawResultsToEntities(results)) .then(results => this.addLazyProperties(results)) - .then(results => this.broadcaster.broadcastLoadEventsForAll(results).then(() => results)) + .then(results => this.broadcaster.broadcastLoadEventsForAll(this.aliasMap.mainAlias.target, results).then(() => results)) .then(results => { return { entities: results, @@ -384,7 +384,7 @@ export class QueryBuilder { .then(results => this.addLazyProperties(results)) .then(results => { return this.broadcaster - .broadcastLoadEventsForAll(results) + .broadcastLoadEventsForAll(this.aliasMap.mainAlias.target, results) .then(() => results); }) .then(results => { diff --git a/src/repository/Repository.ts b/src/repository/Repository.ts index 97938e9cd..42263cb84 100644 --- a/src/repository/Repository.ts +++ b/src/repository/Repository.ts @@ -126,7 +126,17 @@ export class Repository { return this.findNotLoadedIds(entityWithIds, allPersistedEntities); }) // need to find db entities that were not loaded by initialize method .then(allDbEntities => { - return this.entityPersistOperationBuilder.buildFullPersistment(this.metadata, loadedDbEntity, entity, allDbEntities, allPersistedEntities); + const persistedEntity: EntityWithId = { + id: this.metadata.getEntityId(entity), + entityTarget: this.metadata.target, + entity: entity + }; + const dbEntity: EntityWithId = { + id: this.metadata.getEntityId(loadedDbEntity), + entityTarget: this.metadata.target, + entity: loadedDbEntity + }; + return this.entityPersistOperationBuilder.buildFullPersistment(this.metadata, dbEntity, persistedEntity, allDbEntities, allPersistedEntities); }) .then(persistOperation => { return this.persistOperationExecutor.executePersistOperation(persistOperation); @@ -151,7 +161,18 @@ export class Repository { }) // .then(([dbEntities, allPersistedEntities]: [EntityWithId[], EntityWithId[]]) => { .then(results => { - const persistOperation = this.entityPersistOperationBuilder.buildOnlyRemovement(this.metadata, dbEntity, entity, results[0], results[1]); + const entityWithId: EntityWithId = { + id: this.metadata.getEntityId(entity), + entityTarget: this.metadata.target, + entity: entity + }; + const dbEntityWithId: EntityWithId = { + id: this.metadata.getEntityId(dbEntity), + entityTarget: this.metadata.target, + entity: dbEntity + }; + + const persistOperation = this.entityPersistOperationBuilder.buildOnlyRemovement(this.metadata, dbEntityWithId, entityWithId, results[0], results[1]); return this.persistOperationExecutor.executePersistOperation(persistOperation); }).then(() => entity); } @@ -438,10 +459,10 @@ export class Repository { private findNotLoadedIds(dbEntities: EntityWithId[], persistedEntities: EntityWithId[]): Promise { const missingDbEntitiesLoad = persistedEntities .filter(entityWithId => entityWithId.id !== null && entityWithId.id !== undefined) - .filter(entityWithId => !dbEntities.find(dbEntity => dbEntity.entity.constructor === entityWithId.entity.constructor && dbEntity.id === entityWithId.id)) + .filter(entityWithId => !dbEntities.find(dbEntity => dbEntity.entityTarget === entityWithId.entityTarget && dbEntity.id === entityWithId.id)) .map(entityWithId => { - const metadata = this.entityMetadatas.findByTarget(entityWithId.entity.constructor); - const repository = this.connection.getRepository(entityWithId.entity.constructor); + const metadata = this.entityMetadatas.findByTarget(entityWithId.entityTarget); + const repository = this.connection.getRepository(entityWithId.entityTarget as any); // todo: fix type return repository.findOneById(entityWithId.id).then(loadedEntity => { if (!loadedEntity) return undefined; diff --git a/src/subscriber/Broadcaster.ts b/src/subscriber/Broadcaster.ts index 23666e85a..bb600c4b9 100644 --- a/src/subscriber/Broadcaster.ts +++ b/src/subscriber/Broadcaster.ts @@ -139,22 +139,22 @@ export class Broadcaster { return Promise.all(subscribers.concat(listeners)).then(() => {}); } - broadcastLoadEvents(entity: any): Promise { + broadcastLoadEvents(target: Function|string, entity: any): Promise { if (entity instanceof Promise) return Promise.resolve(); - const metadata = this.entityMetadatas.findByTarget(entity.constructor); + const metadata = this.entityMetadatas.findByTarget(target); let promises: Promise[] = []; metadata .relations .filter(relation => entity.hasOwnProperty(relation.propertyName)) - .map(relation => entity[relation.propertyName]) - .map(value => { + .map(relation => { + const value = entity[relation.propertyName]; if (value instanceof Array) { - promises = promises.concat(this.broadcastLoadEventsForAll(value)); + promises = promises.concat(this.broadcastLoadEventsForAll(relation.inverseEntityMetadata.target, value)); } else { - promises.push(this.broadcastLoadEvents(value)); + promises.push(this.broadcastLoadEvents(relation.inverseEntityMetadata.target, value)); } }); @@ -173,8 +173,8 @@ export class Broadcaster { return Promise.all(promises).then(() => {}); } - broadcastLoadEventsForAll(entities: any[]): Promise { - const promises = entities.map(entity => this.broadcastLoadEvents(entity)); + broadcastLoadEventsForAll(target: Function|string, entities: any[]): Promise { + const promises = entities.map(entity => this.broadcastLoadEvents(target, entity)); return Promise.all(promises).then(() => {}); }