"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReturningResultsEntityUpdator = void 0;
const error_1 = require("../error");
/**
 * Updates entity with returning results in the entity insert and update operations.
 */
class ReturningResultsEntityUpdator {
    // -------------------------------------------------------------------------
    // Constructor
    // -------------------------------------------------------------------------
    constructor(queryRunner, expressionMap) {
        this.queryRunner = queryRunner;
        this.expressionMap = expressionMap;
    }
    // -------------------------------------------------------------------------
    // Public Methods
    // -------------------------------------------------------------------------
    /**
     * Updates entities with a special columns after updation query execution.
     * @param updateResult
     * @param entities
     */
    async update(updateResult, entities) {
        const metadata = this.expressionMap.mainAlias.metadata;
        await Promise.all(entities.map(async (entity, entityIndex) => {
            // if database supports returning/output statement then we already should have updating values in the raw data returned by insert query
            if (this.queryRunner.dataSource.driver.isReturningSqlSupported("update")) {
                if (this.queryRunner.dataSource.driver.options.type ===
                    "oracle" &&
                    Array.isArray(updateResult.raw) &&
                    this.expressionMap.extraReturningColumns.length > 0) {
                    updateResult.raw = updateResult.raw.reduce((newRaw, rawItem, rawItemIndex) => {
                        newRaw[this.expressionMap.extraReturningColumns[rawItemIndex].databaseName] = rawItem[0];
                        return newRaw;
                    }, {});
                }
                const result = Array.isArray(updateResult.raw)
                    ? updateResult.raw[entityIndex]
                    : updateResult.raw;
                const returningColumns = this.queryRunner.dataSource.driver.createGeneratedMap(metadata, result);
                if (returningColumns) {
                    this.queryRunner.manager.merge(metadata.target, entity, returningColumns);
                    updateResult.generatedMaps.push(returningColumns);
                }
            }
            else {
                // for driver which do not support returning/output statement we need to perform separate query and load what we need
                const updationColumns = this.expressionMap.extraReturningColumns;
                if (updationColumns.length > 0) {
                    // get entity id by which we will get needed data
                    const entityId = this.expressionMap.mainAlias.metadata.getEntityIdMap(entity);
                    if (!entityId)
                        throw new error_1.TypeORMError(`Cannot update entity because entity id is not set in the entity.`);
                    // execute query to get needed data
                    const loadedReturningColumns = (await this.queryRunner.manager
                        .createQueryBuilder()
                        .select(metadata.primaryColumns.map((column) => metadata.targetName +
                        "." +
                        column.propertyPath))
                        .addSelect(updationColumns.map((column) => metadata.targetName +
                        "." +
                        column.propertyPath))
                        .from(metadata.target, metadata.targetName)
                        .where(entityId)
                        .withDeleted()
                        .setOption("create-pojo") // use POJO because created object can contain default values, e.g. property = null and those properties might be overridden by merge process
                        .getOne());
                    if (loadedReturningColumns) {
                        this.queryRunner.manager.merge(metadata.target, entity, loadedReturningColumns);
                        updateResult.generatedMaps.push(loadedReturningColumns);
                    }
                }
            }
        }));
    }
    /**
     * Updates entities with a special columns after insertion query execution.
     * @param insertResult
     * @param entities
     */
    async insert(insertResult, entities) {
        const metadata = this.expressionMap.mainAlias.metadata;
        let insertionColumns = metadata.getInsertionReturningColumns();
        // to prevent extra select SQL execution for databases not supporting RETURNING
        // in the case if we have generated column and it's value returned by underlying driver
        // we remove this column from the insertionColumns list
        const needToCheckGenerated = this.queryRunner.dataSource.driver.isReturningSqlSupported("insert");
        insertionColumns = insertionColumns.filter((column) => {
            if (!column.isGenerated)
                return true;
            return needToCheckGenerated === true;
        });
        const generatedMaps = entities.map((entity, entityIndex) => {
            if (Array.isArray(insertResult.raw) &&
                this.expressionMap.extraReturningColumns.length > 0) {
                if (this.queryRunner.dataSource.driver.options.type === "oracle") {
                    insertResult.raw = insertResult.raw.reduce((newRaw, rawItem, rawItemIndex) => {
                        newRaw[this.expressionMap.extraReturningColumns[rawItemIndex].databaseName] = rawItem[0];
                        return newRaw;
                    }, {});
                }
                else if (this.queryRunner.dataSource.driver.options.type ===
                    "spanner") {
                    insertResult.raw = insertResult.raw[0];
                }
            }
            // get all values generated by a database for us
            const result = Array.isArray(insertResult.raw)
                ? insertResult.raw[entityIndex]
                : insertResult.raw;
            const generatedMap = this.queryRunner.dataSource.driver.createGeneratedMap(metadata, result, entityIndex, entities.length) || {};
            if (entityIndex in this.expressionMap.locallyGenerated) {
                this.queryRunner.manager.merge(metadata.target, generatedMap, this.expressionMap.locallyGenerated[entityIndex]);
            }
            this.queryRunner.manager.merge(metadata.target, entity, generatedMap);
            return generatedMap;
        });
        // for postgres and mssql we use returning/output statement to get values of inserted default and generated values
        // for other drivers we have to re-select this data from the database
        if (insertionColumns.length > 0 &&
            !this.queryRunner.dataSource.driver.isReturningSqlSupported("insert")) {
            const entityIds = entities.map((entity) => {
                const entityId = metadata.getEntityIdMap(entity);
                // We have to check for an empty `entityId` - if we don't, the query against the database
                // effectively drops the `where` clause entirely and the first record will be returned -
                // not what we want at all.
                if (!entityId)
                    throw new error_1.TypeORMError(`Cannot update entity because entity id is not set in the entity.`);
                return entityId;
            });
            // to select just inserted entities we need a criteria to select by.
            // for newly inserted entities in drivers which do not support returning statement
            // row identifier can only be an increment column
            // (since its the only thing that can be generated by those databases)
            // or (and) other primary key which is defined by a user and inserted value has it
            const returningResult = await this.queryRunner.manager
                .createQueryBuilder()
                .select(metadata.primaryColumns.map((column) => metadata.targetName + "." + column.propertyPath))
                .addSelect(insertionColumns.map((column) => metadata.targetName + "." + column.propertyPath))
                .from(metadata.target, metadata.targetName)
                .where(entityIds)
                .setOption("create-pojo") // use POJO because created object can contain default values, e.g. property = null and those properties might be overridden by merge process
                .getMany();
            entities.forEach((entity, entityIndex) => {
                this.queryRunner.manager.merge(metadata.target, generatedMaps[entityIndex], returningResult[entityIndex]);
                this.queryRunner.manager.merge(metadata.target, entity, returningResult[entityIndex]);
            });
        }
        entities.forEach((entity, entityIndex) => {
            const entityId = metadata.getEntityIdMap(entity);
            insertResult.identifiers.push(entityId);
            insertResult.generatedMaps.push(generatedMaps[entityIndex]);
        });
    }
    /**
     * Columns we need to be returned from the database when we update entity.
     */
    getUpdationReturningColumns() {
        return this.expressionMap.mainAlias.metadata.columns.filter((column) => {
            return (column.asExpression !== undefined ||
                column.isUpdateDate ||
                column.isVersion);
        });
    }
    /**
     * Columns we need to be returned from the database when we soft delete and restore entity.
     */
    getSoftDeletionReturningColumns() {
        return this.expressionMap.mainAlias.metadata.columns.filter((column) => {
            return (column.asExpression !== undefined ||
                column.isUpdateDate ||
                column.isVersion ||
                column.isDeleteDate);
        });
    }
}
exports.ReturningResultsEntityUpdator = ReturningResultsEntityUpdator;
//# sourceMappingURL=ReturningResultsEntityUpdator.js.map