333
schangxiang@126.com
2025-09-19 18966e02fb573c7e2bb0c6426ed792b38b910940
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Subject_1 = require("../Subject");
var OrmUtils_1 = require("../../util/OrmUtils");
var EntityMetadata_1 = require("../../metadata/EntityMetadata");
/**
 * Builds operations needs to be executed for many-to-many relations of the given subjects.
 *
 * by example: post contains owner many-to-many relation with categories in the property called "categories", e.g.
 *             @ManyToMany(type => Category, category => category.posts) categories: Category[]
 *             If user adds categories into the post and saves post we need to bind them.
 *             This operation requires updation of junction table.
 */
var ManyToManySubjectBuilder = /** @class */ (function () {
    // ---------------------------------------------------------------------
    // Constructor
    // ---------------------------------------------------------------------
    function ManyToManySubjectBuilder(subjects) {
        this.subjects = subjects;
    }
    // ---------------------------------------------------------------------
    // Public Methods
    // ---------------------------------------------------------------------
    /**
     * Builds operations for any changes in the many-to-many relations of the subjects.
     */
    ManyToManySubjectBuilder.prototype.build = function () {
        var _this = this;
        this.subjects.forEach(function (subject) {
            // if subject doesn't have entity then no need to find something that should be inserted or removed
            if (!subject.entity)
                return;
            // go through all persistence enabled many-to-many relations and build subject operations for them
            subject.metadata.manyToManyRelations.forEach(function (relation) {
                // skip relations for which persistence is disabled
                if (relation.persistenceEnabled === false)
                    return;
                _this.buildForSubjectRelation(subject, relation);
            });
        });
    };
    /**
     * Builds operations for removal of all many-to-many records of all many-to-many relations of the given subject.
     */
    ManyToManySubjectBuilder.prototype.buildForAllRemoval = function (subject) {
        var _this = this;
        // if subject does not have a database entity then it means it does not exist in the database
        // if it does not exist in the database then we don't have anything for deletion
        if (!subject.databaseEntity)
            return;
        // go through all persistence enabled many-to-many relations and build subject operations for them
        subject.metadata.manyToManyRelations.forEach(function (relation) {
            // skip relations for which persistence is disabled
            if (relation.persistenceEnabled === false)
                return;
            // get all related entities (actually related entity relation ids) bind to this subject entity
            // by example: returns category ids of the post we are currently working with (subject.entity is post)
            var relatedEntityRelationIdsInDatabase = relation.getEntityValue(subject.databaseEntity);
            // go through all related entities and create a new junction subject for each row in junction table
            relatedEntityRelationIdsInDatabase.forEach(function (relationId) {
                var junctionSubject = new Subject_1.Subject({
                    metadata: relation.junctionEntityMetadata,
                    parentSubject: subject,
                    mustBeRemoved: true,
                    identifier: _this.buildJunctionIdentifier(subject, relation, relationId)
                });
                // we use unshift because we need to perform those operations before post deletion is performed
                // but post deletion was already added as an subject
                // this is temporary solution, later we need to implement proper sorting of subjects before their removal
                _this.subjects.push(junctionSubject);
            });
        });
    };
    // ---------------------------------------------------------------------
    // Protected Methods
    // ---------------------------------------------------------------------
    /**
     * Builds operations for a given subject and relation.
     *
     * by example: subject is "post" entity we are saving here and relation is "categories" inside it here.
     */
    ManyToManySubjectBuilder.prototype.buildForSubjectRelation = function (subject, relation) {
        var _this = this;
        // load from db all relation ids of inverse entities that are "bind" to the subject's entity
        // this way we gonna check which relation ids are missing and which are new (e.g. inserted or removed)
        var databaseRelatedEntityIds = [];
        // if subject don't have database entity it means all related entities in persisted subject are new and must be bind
        // and we don't need to remove something that is not exist
        if (subject.databaseEntity)
            databaseRelatedEntityIds = relation.getEntityValue(subject.databaseEntity);
        // extract entity's relation value
        // by example: categories inside our post (subject.entity is post)
        var relatedEntities = relation.getEntityValue(subject.entity);
        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
            relatedEntities = [];
        if (!(relatedEntities instanceof Array))
            return;
        // from all related entities find only those which aren't found in the db - for them we will create operation subjects
        relatedEntities.forEach(function (relatedEntity) {
            // todo: check how it will work for entities which are saved by cascades, but aren't saved in the database yet
            // extract only relation id from the related entities, since we only need it for comparision
            // by example: extract from category only relation id (category id, or let's say category title, depend on join column options)
            var relatedEntityRelationIdMap = relation.inverseEntityMetadata.getEntityIdMap(relatedEntity);
            // try to find a subject of this related entity, maybe it was loaded or was marked for persistence
            var relatedEntitySubject = _this.subjects.find(function (subject) {
                return subject.entity === relatedEntity;
            });
            // if subject with entity was found take subject identifier as relation id map since it may contain extra properties resolved
            if (relatedEntitySubject)
                relatedEntityRelationIdMap = relatedEntitySubject.identifier;
            // if related entity relation id map is empty it means related entity is newly persisted
            if (!relatedEntityRelationIdMap) {
                // we decided to remove this error because it brings complications when saving object with non-saved entities
                // if related entity does not have a subject then it means user tries to bind entity which wasn't saved
                // in this persistence because he didn't pass this entity for save or he did not set cascades
                // but without entity being inserted we cannot bind it in the relation operation, so we throw an exception here
                // we decided to remove this error because it brings complications when saving object with non-saved entities
                // if (!relatedEntitySubject)
                //     throw new Error(`Many-to-many relation "${relation.entityMetadata.name}.${relation.propertyPath}" contains ` +
                //         `entities which do not exist in the database yet, thus they cannot be bind in the database. ` +
                //         `Please setup cascade insertion or save entities before binding it.`);
                if (!relatedEntitySubject)
                    return;
            }
            // try to find related entity in the database
            // by example: find post's category in the database post's categories
            var relatedEntityExistInDatabase = databaseRelatedEntityIds.find(function (databaseRelatedEntityRelationId) {
                return EntityMetadata_1.EntityMetadata.compareIds(databaseRelatedEntityRelationId, relatedEntityRelationIdMap);
            });
            // if entity is found then don't do anything - it means binding in junction table already exist, we don't need to add anything
            if (relatedEntityExistInDatabase)
                return;
            var ownerValue = relation.isOwning ? subject : (relatedEntitySubject || relatedEntity); // by example: ownerEntityMap is post from subject here
            var inverseValue = relation.isOwning ? (relatedEntitySubject || relatedEntity) : subject; // by example: inverseEntityMap is category from categories array here
            // create a new subject for insert operation of junction rows
            var junctionSubject = new Subject_1.Subject({
                metadata: relation.junctionEntityMetadata,
                parentSubject: subject,
                canBeInserted: true,
            });
            _this.subjects.push(junctionSubject);
            relation.junctionEntityMetadata.ownerColumns.forEach(function (column) {
                junctionSubject.changeMaps.push({
                    column: column,
                    value: ownerValue,
                });
            });
            relation.junctionEntityMetadata.inverseColumns.forEach(function (column) {
                junctionSubject.changeMaps.push({
                    column: column,
                    value: inverseValue,
                });
            });
        });
        // get all inverse entities relation ids that are "bind" to the currently persisted entity
        var changedInverseEntityRelationIds = [];
        relatedEntities.forEach(function (relatedEntity) {
            // relation.inverseEntityMetadata!.getEntityIdMap(relatedEntity)
            var relatedEntityRelationIdMap = relation.inverseEntityMetadata.getEntityIdMap(relatedEntity);
            // try to find a subject of this related entity, maybe it was loaded or was marked for persistence
            var relatedEntitySubject = _this.subjects.find(function (subject) {
                return subject.entity === relatedEntity;
            });
            // if subject with entity was found take subject identifier as relation id map since it may contain extra properties resolved
            if (relatedEntitySubject)
                relatedEntityRelationIdMap = relatedEntitySubject.identifier;
            if (relatedEntityRelationIdMap !== undefined && relatedEntityRelationIdMap !== null)
                changedInverseEntityRelationIds.push(relatedEntityRelationIdMap);
        });
        // now from all entities in the persisted entity find only those which aren't found in the db
        var removedJunctionEntityIds = databaseRelatedEntityIds.filter(function (existRelationId) {
            return !changedInverseEntityRelationIds.find(function (changedRelationId) {
                return EntityMetadata_1.EntityMetadata.compareIds(changedRelationId, existRelationId);
            });
        });
        // finally create a new junction remove operations for missing related entities
        removedJunctionEntityIds.forEach(function (removedEntityRelationId) {
            var junctionSubject = new Subject_1.Subject({
                metadata: relation.junctionEntityMetadata,
                parentSubject: subject,
                mustBeRemoved: true,
                identifier: _this.buildJunctionIdentifier(subject, relation, removedEntityRelationId)
            });
            _this.subjects.push(junctionSubject);
        });
    };
    /**
     * Creates identifiers for junction table.
     * Example: { postId: 1, categoryId: 2 }
     */
    ManyToManySubjectBuilder.prototype.buildJunctionIdentifier = function (subject, relation, relationId) {
        var ownerEntityMap = relation.isOwning ? subject.entity : relationId;
        var inverseEntityMap = relation.isOwning ? relationId : subject.entity;
        var identifier = {};
        relation.junctionEntityMetadata.ownerColumns.forEach(function (column) {
            OrmUtils_1.OrmUtils.mergeDeep(identifier, column.createValueMap(column.referencedColumn.getEntityValue(ownerEntityMap)));
        });
        relation.junctionEntityMetadata.inverseColumns.forEach(function (column) {
            OrmUtils_1.OrmUtils.mergeDeep(identifier, column.createValueMap(column.referencedColumn.getEntityValue(inverseEntityMap)));
        });
        return identifier;
    };
    return ManyToManySubjectBuilder;
}());
exports.ManyToManySubjectBuilder = ManyToManySubjectBuilder;
 
//# sourceMappingURL=ManyToManySubjectBuilder.js.map