333
schangxiang@126.com
2025-09-19 18966e02fb573c7e2bb0c6426ed792b38b910940
1
{"version":3,"sources":["../../src/persistence/subject-builder/OneToManySubjectBuilder.ts"],"names":[],"mappings":"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","file":"OneToManySubjectBuilder.js","sourcesContent":["import {Subject} from \"../Subject\";\nimport {OrmUtils} from \"../../util/OrmUtils\";\nimport {ObjectLiteral} from \"../../common/ObjectLiteral\";\nimport {EntityMetadata} from \"../../metadata/EntityMetadata\";\nimport {RelationMetadata} from \"../../metadata/RelationMetadata\";\n\n/**\n * Builds operations needs to be executed for one-to-many relations of the given subjects.\n *\n * by example: post contains one-to-many relation with category in the property called \"categories\", e.g.\n *             @OneToMany(type => Category, category => category.post) categories: Category[]\n *             If user adds categories into the post and saves post we need to bind them.\n *             This operation requires updation of category table since its owner of the relation and contains a join column.\n *\n * note: this class shares lot of things with OneToOneInverseSideOperationBuilder, so when you change this class\n *       make sure to reflect changes there as well.\n */\nexport class OneToManySubjectBuilder {\n\n    // ---------------------------------------------------------------------\n    // Constructor\n    // ---------------------------------------------------------------------\n\n    constructor(protected subjects: Subject[]) {\n    }\n\n    // ---------------------------------------------------------------------\n    // Public Methods\n    // ---------------------------------------------------------------------\n\n    /**\n     * Builds all required operations.\n     */\n    build(): void {\n        this.subjects.forEach(subject => {\n            subject.metadata.oneToManyRelations.forEach(relation => {\n\n                // skip relations for which persistence is disabled\n                if (relation.persistenceEnabled === false)\n                    return;\n\n                this.buildForSubjectRelation(subject, relation);\n            });\n        });\n    }\n\n    // ---------------------------------------------------------------------\n    // Protected Methods\n    // ---------------------------------------------------------------------\n\n    /**\n     * Builds operations for a given subject and relation.\n     *\n     * by example: subject is \"post\" entity we are saving here and relation is \"categories\" inside it here.\n     */\n    protected buildForSubjectRelation(subject: Subject, relation: RelationMetadata) {\n\n        // prepare objects (relation id maps) for the database entity\n        // note: subject.databaseEntity contains relations with loaded relation ids only\n        // by example: since subject is a post, we are expecting to get all post's categories saved in the database here,\n        //             particularly their relation ids, e.g. category ids stored in the database\n        let relatedEntityDatabaseRelationIds: ObjectLiteral[] = [];\n        if (subject.databaseEntity) { // related entities in the database can exist only if this entity (post) is saved\n            relatedEntityDatabaseRelationIds = relation.getEntityValue(subject.databaseEntity);\n        }\n\n        // get related entities of persisted entity\n        // by example: get categories from the passed to persist post entity\n        let relatedEntities: ObjectLiteral[] = relation.getEntityValue(subject.entity!);\n        if (relatedEntities === null) // we treat relations set to null as removed, so we don't skip it\n            relatedEntities = [] as ObjectLiteral[];\n        if (relatedEntities === undefined) // if relation is undefined then nothing to update\n            return;\n\n        // extract only relation ids from the related entities, since we only need them for comparision\n        // by example: extract from categories only relation ids (category id, or let's say category title, depend on join column options)\n        const relatedPersistedEntityRelationIds: ObjectLiteral[] = [];\n        relatedEntities.forEach(relatedEntity => { // by example: relatedEntity is a category here\n            let relationIdMap = relation.inverseEntityMetadata!.getEntityIdMap(relatedEntity); // by example: relationIdMap is category.id map here, e.g. { id: ... }\n\n            // try to find a subject of this related entity, maybe it was loaded or was marked for persistence\n            let relatedEntitySubject = this.subjects.find(subject => {\n                return subject.entity === relatedEntity;\n            });\n\n            // if subject with entity was found take subject identifier as relation id map since it may contain extra properties resolved\n            if (relatedEntitySubject)\n                relationIdMap = relatedEntitySubject.identifier;\n\n            // if relationIdMap is undefined then it means user binds object which is not saved in the database yet\n            // by example: if post contains categories which does not have ids yet (because they are new)\n            //             it means they are always newly inserted and relation update operation always must be created for them\n            //             it does not make sense to perform difference operation for them for both add and remove actions\n            if (!relationIdMap) {\n\n                // we decided to remove this error because it brings complications when saving object with non-saved entities\n                // if (!relatedEntitySubject)\n                //     throw new Error(`One-to-many relation \"${relation.entityMetadata.name}.${relation.propertyPath}\" contains ` +\n                //         `entities which do not exist in the database yet, thus they cannot be bind in the database. ` +\n                //         `Please setup cascade insertion or save entities before binding it.`);\n                if (!relatedEntitySubject)\n                    return;\n\n                // okay, so related subject exist and its marked for insertion, then add a new change map\n                // by example: this will tell category to insert into its post relation our post we are working with\n                //             relatedEntitySubject is newly inserted CategorySubject\n                //             relation.inverseRelation is ManyToOne relation inside Category\n                //             subject is Post needs to be inserted into Category\n                relatedEntitySubject.changeMaps.push({\n                    relation: relation.inverseRelation!,\n                    value: subject\n                });\n\n                return;\n            }\n\n            // check if this binding really exist in the database\n            // by example: find our category if its already bind in the database\n            const relationIdInDatabaseSubjectRelation = relatedEntityDatabaseRelationIds.find(relatedDatabaseEntityRelationId => {\n                return OrmUtils.deepCompare(relationIdMap, relatedDatabaseEntityRelationId);\n            });\n\n            // if relationIdMap DOES NOT exist in the subject's relation in the database it means its a new relation and we need to \"bind\" them\n            // by example: this will tell category to insert into its post relation our post we are working with\n            //             relatedEntitySubject is newly inserted CategorySubject\n            //             relation.inverseRelation is ManyToOne relation inside Category\n            //             subject is Post needs to be inserted into Category\n            if (!relationIdInDatabaseSubjectRelation) {\n\n                // if there is no relatedEntitySubject then it means \"category\" wasn't persisted,\n                // but since we are going to update \"category\" table (since its an owning side of relation with join column)\n                // we create a new subject here:\n                if (!relatedEntitySubject) {\n                    relatedEntitySubject = new Subject({\n                        metadata: relation.inverseEntityMetadata,\n                        parentSubject: subject,\n                        canBeUpdated: true,\n                        identifier: relationIdMap\n                    });\n                    this.subjects.push(relatedEntitySubject);\n                }\n\n                relatedEntitySubject.changeMaps.push({\n                    relation: relation.inverseRelation!,\n                    value: subject\n                });\n            }\n\n            // if related entity has relation id then we add it to the list of relation ids\n            // this list will be used later to compare with database relation ids to find a difference\n            // what exist in this array and does not exist in the database are newly inserted relations\n            // what does not exist in this array, but exist in the database are removed relations\n            // removed relations are set to null from inverse side of relation\n            relatedPersistedEntityRelationIds.push(relationIdMap);\n        });\n\n        // find what related entities were added and what were removed based on difference between what we save and what database has\n        EntityMetadata\n            .difference(relatedEntityDatabaseRelationIds, relatedPersistedEntityRelationIds)\n            .forEach(removedRelatedEntityRelationId => { // by example: removedRelatedEntityRelationId is category that was bind in the database before, but now its unbind\n\n                // todo: probably we can improve this in the future by finding entity with column those values,\n                // todo: maybe it was already in persistence process. This is possible due to unique requirements of join columns\n                // we create a new subject which operations will be executed in subject operation executor\n                const removedRelatedEntitySubject = new Subject({\n                    metadata: relation.inverseEntityMetadata,\n                    parentSubject: subject,\n                    canBeUpdated: true,\n                    identifier: removedRelatedEntityRelationId,\n                    changeMaps: [{\n                        relation: relation.inverseRelation!,\n                        value: null\n                    }]\n                });\n                this.subjects.push(removedRelatedEntitySubject);\n            });\n    }\n\n}"],"sourceRoot":"../.."}