import {QueryBuilder} from "./QueryBuilder"; import {ObjectLiteral} from "../common/ObjectLiteral"; import {QueryExpressionMap} from "./QueryExpressionMap"; import {OracleDriver} from "../driver/oracle/OracleDriver"; /** * Allows to work with entity relations and perform specific operations with those relations. * * todo: add transactions everywhere */ export class RelationUpdater { // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- constructor(protected queryBuilder: QueryBuilder, protected expressionMap: QueryExpressionMap) { } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- /** * Performs set or add operation on a relation. */ async update(value: any|any[]): Promise { const relation = this.expressionMap.relationMetadata; if (relation.isManyToOne || relation.isOneToOneOwner) { const updateSet = relation.joinColumns.reduce((updateSet, joinColumn) => { const relationValue = value instanceof Object ? joinColumn.referencedColumn!.getEntityValue(value) : value; joinColumn.setEntityValue(updateSet, relationValue); return updateSet; }, {} as any); if (!this.expressionMap.of || (this.expressionMap.of instanceof Array && !this.expressionMap.of.length)) return; await this.queryBuilder .createQueryBuilder() .update(relation.entityMetadata.target) .set(updateSet) .whereInIds(this.expressionMap.of) .execute(); } else if ((relation.isOneToOneNotOwner || relation.isOneToMany) && value === null) { // we handle null a bit different way const updateSet: ObjectLiteral = {}; relation.inverseRelation!.joinColumns.forEach(column => { updateSet[column.propertyName] = null; }); const ofs = this.expressionMap.of instanceof Array ? this.expressionMap.of : [this.expressionMap.of]; const parameters: ObjectLiteral = {}; const conditions: string[] = []; ofs.forEach((of, ofIndex) => { relation.inverseRelation!.joinColumns.map((column, columnIndex) => { const parameterName = "joinColumn_" + ofIndex + "_" + columnIndex; parameters[parameterName] = of instanceof Object ? column.referencedColumn!.getEntityValue(of) : of; conditions.push(`${column.propertyPath} = :${parameterName}`); }); }); const condition = conditions.map(str => "(" + str + ")").join(" OR "); if (!condition) return; await this.queryBuilder .createQueryBuilder() .update(relation.inverseEntityMetadata.target) .set(updateSet) .where(condition) .setParameters(parameters) .execute(); } else if (relation.isOneToOneNotOwner || relation.isOneToMany) { if (this.expressionMap.of instanceof Array) throw new Error(`You cannot update relations of multiple entities with the same related object. Provide a single entity into .of method.`); const of = this.expressionMap.of; const updateSet = relation.inverseRelation!.joinColumns.reduce((updateSet, joinColumn) => { const relationValue = of instanceof Object ? joinColumn.referencedColumn!.getEntityValue(of) : of; joinColumn.setEntityValue(updateSet, relationValue); return updateSet; }, {} as any); if (!value || (value instanceof Array && !value.length)) return; await this.queryBuilder .createQueryBuilder() .update(relation.inverseEntityMetadata.target) .set(updateSet) .whereInIds(value) .execute(); } else { // many to many const junctionMetadata = relation.junctionEntityMetadata!; const ofs = this.expressionMap.of instanceof Array ? this.expressionMap.of : [this.expressionMap.of]; const values = value instanceof Array ? value : [value]; const firstColumnValues = relation.isManyToManyOwner ? ofs : values; const secondColumnValues = relation.isManyToManyOwner ? values : ofs; const bulkInserted: ObjectLiteral[] = []; firstColumnValues.forEach(firstColumnVal => { secondColumnValues.forEach(secondColumnVal => { const inserted: ObjectLiteral = {}; junctionMetadata.ownerColumns.forEach(column => { inserted[column.databaseName] = firstColumnVal instanceof Object ? column.referencedColumn!.getEntityValue(firstColumnVal) : firstColumnVal; }); junctionMetadata.inverseColumns.forEach(column => { inserted[column.databaseName] = secondColumnVal instanceof Object ? column.referencedColumn!.getEntityValue(secondColumnVal) : secondColumnVal; }); bulkInserted.push(inserted); }); }); if (!bulkInserted.length) return; if (this.queryBuilder.connection.driver instanceof OracleDriver) { await Promise.all(bulkInserted.map(value => { return this.queryBuilder .createQueryBuilder() .insert() .into(junctionMetadata.tableName) .values(value) .execute(); })); } else { await this.queryBuilder .createQueryBuilder() .insert() .into(junctionMetadata.tableName) .values(bulkInserted) .execute(); } } } }