import {Connection} from "../connection/Connection"; import {EntityMetadata} from "../metadata/EntityMetadata"; import {OperateEntity} from "../persistment/operation/PersistOperation"; import {ObjectLiteral} from "../common/ObjectLiteral"; import {Repository} from "./Repository"; import {QueryRunnerProvider} from "../query-runner/QueryRunnerProvider"; /** * Repository for more specific operations. */ export class SpecificRepository { // ------------------------------------------------------------------------- // Protected Properties // ------------------------------------------------------------------------- protected queryRunnerProvider: QueryRunnerProvider; // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- constructor(protected connection: Connection, protected metadata: EntityMetadata, protected repository: Repository, queryRunnerProvider?: QueryRunnerProvider) { if (queryRunnerProvider) { this.queryRunnerProvider = queryRunnerProvider; } else { this.queryRunnerProvider = new QueryRunnerProvider(connection.driver); } } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- /** * Sets given relatedEntityId to the value of the relation of the entity with entityId id. * Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async setRelation(relationName: string, entityId: any, relatedEntityId: any): Promise; /** * Sets given relatedEntityId to the value of the relation of the entity with entityId id. * Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async setRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityId: any): Promise; /** * Sets given relatedEntityId to the value of the relation of the entity with entityId id. * Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async setRelation(relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityId: any): Promise { const propertyName = this.metadata.computePropertyName(relationName); if (!this.metadata.hasRelationWithPropertyName(propertyName)) throw new Error(`Relation ${propertyName} was not found in the ${this.metadata.name} entity.`); const relation = this.metadata.findRelationWithPropertyName(propertyName); // if (relation.isManyToMany || relation.isOneToMany || relation.isOneToOneNotOwner) // throw new Error(`Only many-to-one and one-to-one with join column are supported for this operation. ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`); if (relation.isManyToMany) throw new Error(`Many-to-many relation is not supported for this operation. Use #addToRelation method for many-to-many relations.`); let table: string, values: any = {}, conditions: any = {}; if (relation.isOwning) { table = relation.entityMetadata.table.name; values[relation.name] = relatedEntityId; conditions[relation.joinColumn.referencedColumn.name] = entityId; } else { table = relation.inverseEntityMetadata.table.name; values[relation.inverseRelation.name] = relatedEntityId; conditions[relation.inverseRelation.joinColumn.referencedColumn.name] = entityId; } const queryRunner = await this.queryRunnerProvider.provide(); await queryRunner.update(table, values, conditions); await this.queryRunnerProvider.release(queryRunner); } /** * Sets given relatedEntityId to the value of the relation of the entity with entityId id. * Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async setInverseRelation(relationName: string, relatedEntityId: any, entityId: any): Promise; /** * Sets given relatedEntityId to the value of the relation of the entity with entityId id. * Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async setInverseRelation(relationName: ((t: Entity) => string|any), relatedEntityId: any, entityId: any): Promise; /** * Sets given relatedEntityId to the value of the relation of the entity with entityId id. * Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async setInverseRelation(relationName: string|((t: Entity) => string|any), relatedEntityId: any, entityId: any): Promise { const propertyName = this.metadata.computePropertyName(relationName); if (!this.metadata.hasRelationWithPropertyName(propertyName)) throw new Error(`Relation ${propertyName} was not found in the ${this.metadata.name} entity.`); const relation = this.metadata.findRelationWithPropertyName(propertyName); // if (relation.isManyToMany || relation.isOneToMany || relation.isOneToOneNotOwner) // throw new Error(`Only many-to-one and one-to-one with join column are supported for this operation. ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`); if (relation.isManyToMany) throw new Error(`Many-to-many relation is not supported for this operation. Use #addToRelation method for many-to-many relations.`); let table: string, values: any = {}, conditions: any = {}; if (relation.isOwning) { table = relation.inverseEntityMetadata.table.name; values[relation.inverseRelation.name] = relatedEntityId; conditions[relation.inverseRelation.joinColumn.referencedColumn.name] = entityId; } else { table = relation.entityMetadata.table.name; values[relation.name] = relatedEntityId; conditions[relation.joinColumn.referencedColumn.name] = entityId; } const queryRunner = await this.queryRunnerProvider.provide(); await queryRunner.update(table, values, conditions); await this.queryRunnerProvider.release(queryRunner); } /** * Adds a new relation between two entities into relation's many-to-many table. * Should be used when you want quickly and efficiently add a relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addToRelation(relationName: string, entityId: any, relatedEntityIds: any[]): Promise; /** * Adds a new relation between two entities into relation's many-to-many table. * Should be used when you want quickly and efficiently add a relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addToRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise; /** * Adds a new relation between two entities into relation's many-to-many table. * Should be used when you want quickly and efficiently add a relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addToRelation(relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise { const propertyName = this.metadata.computePropertyName(relationName); if (!this.metadata.hasRelationWithPropertyName(propertyName)) throw new Error(`Relation ${propertyName} was not found in the ${this.metadata.name} entity.`); const relation = this.metadata.findRelationWithPropertyName(propertyName); if (!relation.isManyToMany) throw new Error(`Only many-to-many relation supported for this operation. However ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`); const queryRunner = await this.queryRunnerProvider.provide(); const insertPromises = relatedEntityIds.map(relatedEntityId => { const values: any = { }; if (relation.isOwning) { values[relation.junctionEntityMetadata.columns[0].name] = entityId; values[relation.junctionEntityMetadata.columns[1].name] = relatedEntityId; } else { values[relation.junctionEntityMetadata.columns[1].name] = entityId; values[relation.junctionEntityMetadata.columns[0].name] = relatedEntityId; } return queryRunner.insert(relation.junctionEntityMetadata.table.name, values); }); await Promise.all(insertPromises); await this.queryRunnerProvider.release(queryRunner); } /** * Adds a new relation between two entities into relation's many-to-many table from inverse side of the given relation. * Should be used when you want quickly and efficiently add a relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addToInverseRelation(relationName: string, relatedEntityId: any, entityIds: any[]): Promise; /** * Adds a new relation between two entities into relation's many-to-many table from inverse side of the given relation. * Should be used when you want quickly and efficiently add a relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addToInverseRelation(relationName: ((t: Entity) => string|any), relatedEntityId: any, entityIds: any[]): Promise; /** * Adds a new relation between two entities into relation's many-to-many table from inverse side of the given relation. * Should be used when you want quickly and efficiently add a relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addToInverseRelation(relationName: string|((t: Entity) => string|any), relatedEntityId: any, entityIds: any[]): Promise { const propertyName = this.metadata.computePropertyName(relationName); if (!this.metadata.hasRelationWithPropertyName(propertyName)) throw new Error(`Relation ${propertyName} was not found in the ${this.metadata.name} entity.`); const relation = this.metadata.findRelationWithPropertyName(propertyName); if (!relation.isManyToMany) throw new Error(`Only many-to-many relation supported for this operation. However ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`); const queryRunner = await this.queryRunnerProvider.provide(); try { const insertPromises = entityIds.map(entityId => { const values: any = { }; if (relation.isOwning) { values[relation.junctionEntityMetadata.columns[0].name] = entityId; values[relation.junctionEntityMetadata.columns[1].name] = relatedEntityId; } else { values[relation.junctionEntityMetadata.columns[1].name] = entityId; values[relation.junctionEntityMetadata.columns[0].name] = relatedEntityId; } return queryRunner.insert(relation.junctionEntityMetadata.table.name, values); }); await Promise.all(insertPromises); } finally { await this.queryRunnerProvider.release(queryRunner); } } /** * Removes a relation between two entities from relation's many-to-many table. * Should be used when you want quickly and efficiently remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async removeFromRelation(relationName: string, entityId: any, relatedEntityIds: any[]): Promise; /** * Removes a relation between two entities from relation's many-to-many table. * Should be used when you want quickly and efficiently remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async removeFromRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise; /** * Removes a relation between two entities from relation's many-to-many table. * Should be used when you want quickly and efficiently remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async removeFromRelation(relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise { const propertyName = this.metadata.computePropertyName(relationName); if (!this.metadata.hasRelationWithPropertyName(propertyName)) throw new Error(`Relation ${propertyName} was not found in the ${this.metadata.name} entity.`); const relation = this.metadata.findRelationWithPropertyName(propertyName); if (!relation.isManyToMany) throw new Error(`Only many-to-many relation supported for this operation. However ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`); // check if given relation entity ids is empty - then nothing to do here (otherwise next code will remove all ids) if (!relatedEntityIds || !relatedEntityIds.length) return Promise.resolve(); const qb = this.repository.createQueryBuilder("junctionEntity") .delete(relation.junctionEntityMetadata.table.name); const firstColumnName = relation.isOwning ? relation.junctionEntityMetadata.columns[0].name : relation.junctionEntityMetadata.columns[1].name; const secondColumnName = relation.isOwning ? relation.junctionEntityMetadata.columns[1].name : relation.junctionEntityMetadata.columns[0].name; relatedEntityIds.forEach((relatedEntityId, index) => { qb.orWhere(`(${firstColumnName}=:entityId AND ${secondColumnName}=:relatedEntity_${index})`) .setParameter("relatedEntity_" + index, relatedEntityId); }); return qb .setParameter("entityId", entityId) .execute() .then(() => {}); } /** * Removes a relation between two entities from relation's many-to-many table. * Should be used when you want quickly and efficiently remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async removeFromInverseRelation(relationName: string, relatedEntityId: any, entityIds: any[]): Promise; /** * Removes a relation between two entities from relation's many-to-many table. * Should be used when you want quickly and efficiently remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async removeFromInverseRelation(relationName: ((t: Entity) => string|any), relatedEntityId: any, entityIds: any[]): Promise; /** * Removes a relation between two entities from relation's many-to-many table. * Should be used when you want quickly and efficiently remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async removeFromInverseRelation(relationName: string|((t: Entity) => string|any), relatedEntityId: any, entityIds: any[]): Promise { const propertyName = this.metadata.computePropertyName(relationName); if (!this.metadata.hasRelationWithPropertyName(propertyName)) throw new Error(`Relation ${propertyName} was not found in the ${this.metadata.name} entity.`); const relation = this.metadata.findRelationWithPropertyName(propertyName); if (!relation.isManyToMany) throw new Error(`Only many-to-many relation supported for this operation. However ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`); // check if given entity ids is empty - then nothing to do here (otherwise next code will remove all ids) if (!entityIds || !entityIds.length) return Promise.resolve(); const qb = this.repository.createQueryBuilder("junctionEntity") .delete(relation.junctionEntityMetadata.table.name); const firstColumnName = relation.isOwning ? relation.junctionEntityMetadata.columns[1].name : relation.junctionEntityMetadata.columns[0].name; const secondColumnName = relation.isOwning ? relation.junctionEntityMetadata.columns[0].name : relation.junctionEntityMetadata.columns[1].name; entityIds.forEach((entityId, index) => { qb.orWhere(`(${firstColumnName}=:relatedEntityId AND ${secondColumnName}=:entity_${index})`) .setParameter("entity_" + index, entityId); }); await qb.setParameter("relatedEntityId", relatedEntityId).execute(); } /** * Performs both #addToRelation and #removeFromRelation operations. * Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addAndRemoveFromRelation(relation: string, entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise; /** * Performs both #addToRelation and #removeFromRelation operations. * Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addAndRemoveFromRelation(relation: ((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise; /** * Performs both #addToRelation and #removeFromRelation operations. * Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addAndRemoveFromRelation(relation: string|((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise { await Promise.all([ this.addToRelation(relation as any, entityId, addRelatedEntityIds), this.removeFromRelation(relation as any, entityId, removeRelatedEntityIds) ]); } /** * Performs both #addToRelation and #removeFromRelation operations. * Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addAndRemoveFromInverseRelation(relation: string, relatedEntityId: any, addEntityIds: any[], removeEntityIds: any[]): Promise; /** * Performs both #addToRelation and #removeFromRelation operations. * Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addAndRemoveFromInverseRelation(relation: ((t: Entity) => string|any), relatedEntityId: any, addEntityIds: any[], removeEntityIds: any[]): Promise; /** * Performs both #addToRelation and #removeFromRelation operations. * Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async addAndRemoveFromInverseRelation(relation: string|((t: Entity) => string|any), relatedEntityId: any, addEntityIds: any[], removeEntityIds: any[]): Promise { await Promise.all([ this.addToInverseRelation(relation as any, relatedEntityId, addEntityIds), this.removeFromInverseRelation(relation as any, relatedEntityId, removeEntityIds) ]); } /** * Removes entity with the given id. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async removeById(id: any): Promise { const alias = this.metadata.table.name; const parameters: ObjectLiteral = {}; let condition = ""; if (this.metadata.hasMultiplePrimaryKeys) { condition = this.metadata.primaryColumns.map(primaryColumn => { parameters[primaryColumn.propertyName] = id[primaryColumn.propertyName]; return alias + "." + primaryColumn.propertyName + "=:" + primaryColumn.propertyName; }).join(" AND "); } else { condition = alias + "." + this.metadata.firstPrimaryColumn.propertyName + "=:id"; parameters["id"] = id; } await this.repository.createQueryBuilder(alias) .delete() .where(condition, parameters) .execute(); } /** * Removes all entities with the given ids. * Note that event listeners and event subscribers won't work (and will not send any events) when using this operation. */ async removeByIds(ids: any[]): Promise { const alias = this.metadata.table.name; const parameters: ObjectLiteral = {}; let condition = ""; if (this.metadata.hasMultiplePrimaryKeys) { condition = ids.map((id, idIndex) => { this.metadata.primaryColumns.map(primaryColumn => { parameters[primaryColumn.propertyName + "_" + idIndex] = id[primaryColumn.propertyName]; return alias + "." + primaryColumn.propertyName + "=:" + primaryColumn.propertyName + "_" + idIndex; }).join(" AND "); }).join(" OR "); } else { condition = alias + "." + this.metadata.firstPrimaryColumn.propertyName + " IN (:ids)"; parameters["ids"] = ids; } await this.repository.createQueryBuilder(alias) .delete() .where(condition, parameters) .execute(); } // ------------------------------------------------------------------------- // Protected Methods // ------------------------------------------------------------------------- /** * Extracts unique objects from given entity and all its downside relations. */ protected extractObjectsById(entity: any, metadata: EntityMetadata, entityWithIds: OperateEntity[] = []): Promise { const promises = metadata.relations.map(relation => { const relMetadata = relation.inverseEntityMetadata; const value = relation.isLazy ? entity["__" + relation.propertyName + "__"] : entity[relation.propertyName]; if (!value) return undefined; if (value instanceof Array) { const subPromises = value.map((subEntity: any) => { return this.extractObjectsById(subEntity, relMetadata, entityWithIds); }); return Promise.all(subPromises); } else { return this.extractObjectsById(value, relMetadata, entityWithIds); } }); return Promise.all(promises.filter(result => !!result)).then(() => { if (!entityWithIds.find(entityWithId => entityWithId.entity === entity)) { const entityWithId = new OperateEntity(metadata, entity); entityWithIds.push(entityWithId); } return entityWithIds; }); } }