schangxiang@126.com
2025-09-19 0821aa23eabe557c0d9ef5dbe6989c68be35d1fe
1
{"version":3,"sources":["../browser/src/persistence/subject-builder/ManyToManySubjectBuilder.ts"],"names":[],"mappings":"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","file":"ManyToManySubjectBuilder.js","sourcesContent":["import {Subject} from \"../Subject\";\nimport {OrmUtils} from \"../../util/OrmUtils\";\nimport {ObjectLiteral} from \"../../common/ObjectLiteral\";\nimport {RelationMetadata} from \"../../metadata/RelationMetadata\";\nimport {EntityMetadata} from \"../../metadata/EntityMetadata\";\n\n/**\n * Builds operations needs to be executed for many-to-many relations of the given subjects.\n *\n * by example: post contains owner many-to-many relation with categories in the property called \"categories\", e.g.\n *             @ManyToMany(type => Category, category => category.posts) categories: Category[]\n *             If user adds categories into the post and saves post we need to bind them.\n *             This operation requires updation of junction table.\n */\nexport class ManyToManySubjectBuilder {\n\n    // ---------------------------------------------------------------------\n    // Constructor\n    // ---------------------------------------------------------------------\n\n    constructor(protected subjects: Subject[]) {\n    }\n\n    // ---------------------------------------------------------------------\n    // Public Methods\n    // ---------------------------------------------------------------------\n\n    /**\n     * Builds operations for any changes in the many-to-many relations of the subjects.\n     */\n    build(): void {\n        this.subjects.forEach(subject => {\n\n            // if subject doesn't have entity then no need to find something that should be inserted or removed\n            if (!subject.entity)\n                return;\n\n            // go through all persistence enabled many-to-many relations and build subject operations for them\n            subject.metadata.manyToManyRelations.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     * Builds operations for removal of all many-to-many records of all many-to-many relations of the given subject.\n     */\n    buildForAllRemoval(subject: Subject) {\n\n        // if subject does not have a database entity then it means it does not exist in the database\n        // if it does not exist in the database then we don't have anything for deletion\n        if (!subject.databaseEntity)\n            return;\n\n        // go through all persistence enabled many-to-many relations and build subject operations for them\n        subject.metadata.manyToManyRelations.forEach(relation => {\n\n            // skip relations for which persistence is disabled\n            if (relation.persistenceEnabled === false)\n                return;\n\n            // get all related entities (actually related entity relation ids) bind to this subject entity\n            // by example: returns category ids of the post we are currently working with (subject.entity is post)\n            const relatedEntityRelationIdsInDatabase: ObjectLiteral[] = relation.getEntityValue(subject.databaseEntity!);\n\n            // go through all related entities and create a new junction subject for each row in junction table\n            relatedEntityRelationIdsInDatabase.forEach(relationId => {\n                const junctionSubject = new Subject({\n                    metadata: relation.junctionEntityMetadata!,\n                    parentSubject: subject,\n                    mustBeRemoved: true,\n                    identifier: this.buildJunctionIdentifier(subject, relation, relationId)\n                });\n\n                // we use unshift because we need to perform those operations before post deletion is performed\n                // but post deletion was already added as an subject\n                // this is temporary solution, later we need to implement proper sorting of subjects before their removal\n                this.subjects.push(junctionSubject);\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        // load from db all relation ids of inverse entities that are \"bind\" to the subject's entity\n        // this way we gonna check which relation ids are missing and which are new (e.g. inserted or removed)\n        let databaseRelatedEntityIds: ObjectLiteral[] = [];\n\n        // if subject don't have database entity it means all related entities in persisted subject are new and must be bind\n        // and we don't need to remove something that is not exist\n        if (subject.databaseEntity)\n            databaseRelatedEntityIds = relation.getEntityValue(subject.databaseEntity);\n\n        // extract entity's relation value\n        // by example: categories inside our post (subject.entity is post)\n        let relatedEntities: ObjectLiteral[] = relation.getEntityValue(subject.entity!);\n        if (relatedEntities === null) // if value set to null its equal if we set it to empty array - all items must be removed from the database\n            relatedEntities = [];\n        if (!(relatedEntities instanceof Array))\n            return;\n\n        // from all related entities find only those which aren't found in the db - for them we will create operation subjects\n        relatedEntities.forEach(relatedEntity => { // by example: relatedEntity is category from categories saved with post\n\n            // todo: check how it will work for entities which are saved by cascades, but aren't saved in the database yet\n\n            // extract only relation id from the related entities, since we only need it for comparision\n            // by example: extract from category only relation id (category id, or let's say category title, depend on join column options)\n            let relatedEntityRelationIdMap = relation.inverseEntityMetadata!.getEntityIdMap(relatedEntity);\n\n            // try to find a subject of this related entity, maybe it was loaded or was marked for persistence\n            const 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                relatedEntityRelationIdMap = relatedEntitySubject.identifier;\n\n            // if related entity relation id map is empty it means related entity is newly persisted\n            if (!relatedEntityRelationIdMap) {\n\n                // we decided to remove this error because it brings complications when saving object with non-saved entities\n                // if related entity does not have a subject then it means user tries to bind entity which wasn't saved\n                // in this persistence because he didn't pass this entity for save or he did not set cascades\n                // but without entity being inserted we cannot bind it in the relation operation, so we throw an exception here\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(`Many-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\n            // try to find related entity in the database\n            // by example: find post's category in the database post's categories\n            const relatedEntityExistInDatabase = databaseRelatedEntityIds.find(databaseRelatedEntityRelationId => {\n                return EntityMetadata.compareIds(databaseRelatedEntityRelationId, relatedEntityRelationIdMap);\n            });\n\n            // if entity is found then don't do anything - it means binding in junction table already exist, we don't need to add anything\n            if (relatedEntityExistInDatabase)\n                return;\n\n            const ownerValue = relation.isOwning ? subject : (relatedEntitySubject || relatedEntity); // by example: ownerEntityMap is post from subject here\n            const inverseValue = relation.isOwning ? (relatedEntitySubject || relatedEntity) : subject; // by example: inverseEntityMap is category from categories array here\n\n            // create a new subject for insert operation of junction rows\n            const junctionSubject = new Subject({\n                metadata: relation.junctionEntityMetadata!,\n                parentSubject: subject,\n                canBeInserted: true,\n            });\n            this.subjects.push(junctionSubject);\n\n            relation.junctionEntityMetadata!.ownerColumns.forEach(column => {\n                junctionSubject.changeMaps.push({\n                    column: column,\n                    value: ownerValue,\n                    // valueFactory: (value) => column.referencedColumn!.getEntityValue(value) // column.referencedColumn!.getEntityValue(ownerEntityMap),\n                });\n            });\n\n            relation.junctionEntityMetadata!.inverseColumns.forEach(column => {\n                junctionSubject.changeMaps.push({\n                    column: column,\n                    value: inverseValue,\n                    // valueFactory: (value) => column.referencedColumn!.getEntityValue(value) // column.referencedColumn!.getEntityValue(inverseEntityMap),\n                });\n            });\n        });\n\n        // get all inverse entities relation ids that are \"bind\" to the currently persisted entity\n        const changedInverseEntityRelationIds: ObjectLiteral[] = [];\n        relatedEntities.forEach(relatedEntity => {\n            // relation.inverseEntityMetadata!.getEntityIdMap(relatedEntity)\n            let relatedEntityRelationIdMap = relation.inverseEntityMetadata!.getEntityIdMap(relatedEntity);\n\n            // try to find a subject of this related entity, maybe it was loaded or was marked for persistence\n            const 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                relatedEntityRelationIdMap = relatedEntitySubject.identifier;\n\n            if (relatedEntityRelationIdMap !== undefined && relatedEntityRelationIdMap !== null)\n                changedInverseEntityRelationIds.push(relatedEntityRelationIdMap);\n        });\n\n        // now from all entities in the persisted entity find only those which aren't found in the db\n        const removedJunctionEntityIds = databaseRelatedEntityIds.filter(existRelationId => {\n            return !changedInverseEntityRelationIds.find(changedRelationId => {\n                return EntityMetadata.compareIds(changedRelationId, existRelationId);\n            });\n        });\n\n        // finally create a new junction remove operations for missing related entities\n        removedJunctionEntityIds.forEach(removedEntityRelationId => {\n            const junctionSubject = new Subject({\n                metadata: relation.junctionEntityMetadata!,\n                parentSubject: subject,\n                mustBeRemoved: true,\n                identifier: this.buildJunctionIdentifier(subject, relation, removedEntityRelationId)\n            });\n            this.subjects.push(junctionSubject);\n        });\n    }\n\n    /**\n     * Creates identifiers for junction table.\n     * Example: { postId: 1, categoryId: 2 }\n     */\n    protected buildJunctionIdentifier(subject: Subject, relation: RelationMetadata, relationId: ObjectLiteral) {\n        const ownerEntityMap = relation.isOwning ? subject.entity! : relationId;\n        const inverseEntityMap = relation.isOwning ? relationId : subject.entity!;\n\n        const identifier: ObjectLiteral = {};\n        relation.junctionEntityMetadata!.ownerColumns.forEach(column => {\n            OrmUtils.mergeDeep(identifier, column.createValueMap(column.referencedColumn!.getEntityValue(ownerEntityMap)));\n        });\n        relation.junctionEntityMetadata!.inverseColumns.forEach(column => {\n            OrmUtils.mergeDeep(identifier, column.createValueMap(column.referencedColumn!.getEntityValue(inverseEntityMap)));\n        });\n        return identifier;\n    }\n\n}\n"],"sourceRoot":"../.."}