import { OrmUtils } from "../util/OrmUtils";
/**
 * Loads database entities for all operate subjects which do not have database entity set.
 * All entities that we load database entities for are marked as updated or inserted.
 * To understand which of them really needs to be inserted or updated we need to load
 * their original representations from the database.
 */
export class SubjectDatabaseEntityLoader {
    // ---------------------------------------------------------------------
    // Constructor
    // ---------------------------------------------------------------------
    constructor(queryRunner, subjects) {
        this.queryRunner = queryRunner;
        this.subjects = subjects;
    }
    // ---------------------------------------------------------------------
    // Public Methods
    // ---------------------------------------------------------------------
    /**
     * Loads database entities for all subjects.
     *
     * loadAllRelations flag is used to load all relation ids of the object, no matter if they present in subject entity or not.
     * This option is used for deletion.
     */
    async load(operationType) {
        // we are grouping subjects by target to perform more optimized queries using WHERE IN operator
        // go through the groups and perform loading of database entities of each subject in the group
        const promises = this.groupByEntityTargets().map(async (subjectGroup) => {
            // prepare entity ids of the subjects we need to load
            const allIds = [];
            const allSubjects = [];
            subjectGroup.subjects.forEach((subject) => {
                // we don't load if subject already has a database entity loaded
                if (subject.databaseEntity || !subject.identifier)
                    return;
                allIds.push(subject.identifier);
                allSubjects.push(subject);
            });
            // if there no ids found (means all entities are new and have generated ids) - then nothing to load there
            if (!allIds.length)
                return;
            const loadRelationPropertyPaths = [];
            // for the save, soft-remove and recover operation
            // extract all property paths of the relations we need to load relation ids for
            // this is for optimization purpose - this way we don't load relation ids for entities
            // whose relations are undefined, and since they are undefined its really pointless to
            // load something for them, since undefined properties are skipped by the orm
            if (operationType === "save" ||
                operationType === "soft-remove" ||
                operationType === "recover") {
                subjectGroup.subjects.forEach((subject) => {
                    // gets all relation property paths that exist in the persisted entity.
                    subject.metadata.relations.forEach((relation) => {
                        const value = relation.getEntityValue(subject.entityWithFulfilledIds);
                        if (value === undefined)
                            return;
                        if (loadRelationPropertyPaths.indexOf(relation.propertyPath) === -1)
                            loadRelationPropertyPaths.push(relation.propertyPath);
                    });
                });
            }
            else {
                // remove
                // for remove operation
                // we only need to load junction relation ids since only they are removed by cascades
                loadRelationPropertyPaths.push(...subjectGroup.subjects[0].metadata.manyToManyRelations.map((relation) => relation.propertyPath));
            }
            const findOptions = {
                loadEagerRelations: false,
                loadRelationIds: {
                    relations: loadRelationPropertyPaths,
                    disableMixedMap: true,
                },
                // the soft-deleted entities should be included in the loaded entities for recover operation
                withDeleted: true,
            };
            // load database entities for all given ids
            let entities = [];
            if (this.queryRunner.connection.driver.options.type ===
                "mongodb") {
                const mongoRepo = this.queryRunner.manager.getRepository(subjectGroup.target);
                entities = await mongoRepo.findByIds(allIds, findOptions);
            }
            else {
                entities = await this.queryRunner.manager
                    .getRepository(subjectGroup.target)
                    .createQueryBuilder()
                    .setFindOptions(findOptions)
                    .whereInIds(allIds)
                    .getMany();
            }
            // Now when we have entities we need to find subject of each entity
            // and insert that entity into database entity of the found subjects.
            // A single entity can be applied to many subjects as there might be duplicates.
            // This will likely result in the same row being updated multiple times during a transaction.
            entities.forEach((entity) => {
                const entityId = allSubjects[0].metadata.getEntityIdMap(entity);
                allSubjects.forEach((subject) => {
                    if (subject.databaseEntity)
                        return;
                    if (OrmUtils.compareIds(subject.identifier, entityId))
                        subject.databaseEntity = entity;
                });
            });
            // this way we tell what subjects we tried to load database entities of
            for (const subject of allSubjects) {
                subject.databaseEntityLoaded = true;
            }
        });
        await Promise.all(promises);
    }
    // ---------------------------------------------------------------------
    // Protected Methods
    // ---------------------------------------------------------------------
    /**
     * Groups given Subject objects into groups separated by entity targets.
     */
    groupByEntityTargets() {
        return this.subjects.reduce((groups, operatedEntity) => {
            let group = groups.find((group) => group.target === operatedEntity.metadata.target);
            if (!group) {
                group = { target: operatedEntity.metadata.target, subjects: [] };
                groups.push(group);
            }
            group.subjects.push(operatedEntity);
            return groups;
        }, []);
    }
}

//# sourceMappingURL=SubjectDatabaseEntityLoader.js.map
