• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

typeorm / typeorm / 15219332477

23 May 2025 09:13PM UTC coverage: 17.216% (-59.1%) from 76.346%
15219332477

Pull #11332

github

naorpeled
cr comments - move if block
Pull Request #11332: feat: add new undefined and null behavior flags

1603 of 12759 branches covered (12.56%)

Branch coverage included in aggregate %.

0 of 31 new or added lines in 3 files covered. (0.0%)

14132 existing lines in 166 files now uncovered.

4731 of 24033 relevant lines covered (19.69%)

60.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

12.0
/src/persistence/subject-builder/OneToOneInverseSideSubjectBuilder.ts
1
import { Subject } from "../Subject"
1✔
2
import { OrmUtils } from "../../util/OrmUtils"
1✔
3
import { ObjectLiteral } from "../../common/ObjectLiteral"
4
import { RelationMetadata } from "../../metadata/RelationMetadata"
5

6
/**
7
 * Builds operations needs to be executed for one-to-one non-owner relations of the given subjects.
8
 *
9
 * by example: post contains one-to-one non-owner relation with category in the property called "category", e.g.
10
 *             @OneToOne(type => Category, category => category.post) category: Category
11
 *             If user sets a category into the post and saves post we need to bind them.
12
 *             This operation requires updation of category table since its owner of the relation and contains a join column.
13
 *
14
 * note: this class shares lot of things with OneToManyUpdateBuilder, so when you change this class
15
 *       make sure to reflect changes there as well.
16
 */
17
export class OneToOneInverseSideSubjectBuilder {
1✔
18
    // ---------------------------------------------------------------------
19
    // Constructor
20
    // ---------------------------------------------------------------------
21

22
    constructor(protected subjects: Subject[]) {}
107✔
23

24
    // ---------------------------------------------------------------------
25
    // Public Methods
26
    // ---------------------------------------------------------------------
27

28
    /**
29
     * Builds all required operations.
30
     */
31
    build(): void {
32
        this.subjects.forEach((subject) => {
107✔
33
            subject.metadata.oneToOneRelations.forEach((relation) => {
267✔
34
                // we don't need owning relations, this operation is only for inverse side of one-to-one relations
35
                // skip relations for which persistence is disabled
UNCOV
36
                if (relation.isOwning || relation.persistenceEnabled === false)
×
UNCOV
37
                    return
×
38

UNCOV
39
                this.buildForSubjectRelation(subject, relation)
×
40
            })
41
        })
42
    }
43

44
    // ---------------------------------------------------------------------
45
    // Protected Methods
46
    // ---------------------------------------------------------------------
47

48
    /**
49
     * Builds operations for a given subject and relation.
50
     *
51
     * by example: subject is "post" entity we are saving here and relation is "category" inside it here.
52
     */
53
    protected buildForSubjectRelation(
54
        subject: Subject,
55
        relation: RelationMetadata,
56
    ) {
57
        // prepare objects (relation id map) for the database entity
58
        // note: subject.databaseEntity contains relation with loaded relation id only (id map)
59
        // by example: since subject is a post, we are expecting to get post's category saved in the database here,
60
        //             particularly its relation id, e.g. category id stored in the database
61
        let relatedEntityDatabaseRelationId: ObjectLiteral | undefined =
UNCOV
62
            undefined
×
UNCOV
63
        if (subject.databaseEntity)
×
64
            // related entity in the database can exist only if this entity (post) is saved
65
            relatedEntityDatabaseRelationId = relation.getEntityValue(
×
66
                subject.databaseEntity,
67
            )
68

69
        // get related entities of persisted entity
70
        // by example: get category from the passed to persist post entity
UNCOV
71
        const relatedEntity: ObjectLiteral | null = relation.getEntityValue(
×
72
            subject.entity!,
73
        ) // by example: relatedEntity is a category here
UNCOV
74
        if (relatedEntity === undefined)
×
75
            // if relation is undefined then nothing to update
UNCOV
76
            return
×
77

78
        // if related entity is null then we need to check if there a bind in the database and unset it
79
        // if there is no bind in the entity then we don't need to do anything
80
        // by example: if post.category = null and category has this post in the database then we unset it
UNCOV
81
        if (relatedEntity === null) {
×
82
            // it makes sense to update database only there is a previously set value in the database
83
            if (relatedEntityDatabaseRelationId) {
×
84
                // todo: probably we can improve this in the future by finding entity with column those values,
85
                // todo: maybe it was already in persistence process. This is possible due to unique requirements of join columns
86
                // we create a new subject which operations will be executed in subject operation executor
87

88
                const removedRelatedEntitySubject = new Subject({
×
89
                    metadata: relation.inverseEntityMetadata,
90
                    parentSubject: subject,
91
                    canBeUpdated: true,
92
                    identifier: relatedEntityDatabaseRelationId,
93
                    changeMaps: [
94
                        {
95
                            relation: relation.inverseRelation!,
96
                            value: null,
97
                        },
98
                    ],
99
                })
100
                this.subjects.push(removedRelatedEntitySubject)
×
101
            }
102

103
            return
×
104
        } // else means entity is bind in the database
105

106
        // extract only relation id from the related entities, since we only need it for comparison
107
        // by example: extract from category only relation id (category id, or let's say category title, depend on join column options)
108
        let relationIdMap =
UNCOV
109
            relation.inverseEntityMetadata!.getEntityIdMap(relatedEntity) // by example: relationIdMap is category.id map here, e.g. { id: ... }
×
110

111
        // try to find a subject of this related entity, maybe it was loaded or was marked for persistence
UNCOV
112
        let relatedEntitySubject = this.subjects.find((operateSubject) => {
×
UNCOV
113
            return (
×
114
                !!operateSubject.entity &&
×
115
                operateSubject.entity === relatedEntity
116
            )
117
        })
118

119
        // if subject with entity was found take subject identifier as relation id map since it may contain extra properties resolved
UNCOV
120
        if (relatedEntitySubject)
×
UNCOV
121
            relationIdMap = relatedEntitySubject.identifier
×
122

123
        // if relationIdMap is undefined then it means user binds object which is not saved in the database yet
124
        // by example: if post contains category which does not have id(s) yet (because its a new category)
125
        //             it means its always newly inserted and relation update operation always must be created for it
126
        //             it does not make sense to perform difference operation for it for both add and remove actions
UNCOV
127
        if (!relationIdMap) {
×
128
            // we decided to remove this error because it brings complications when saving object with non-saved entities
129
            // if related entity does not have a subject then it means user tries to bind entity which wasn't saved
130
            // in this persistence because he didn't pass this entity for save or he did not set cascades
131
            // but without entity being inserted we cannot bind it in the relation operation, so we throw an exception here
132
            // if (!relatedEntitySubject)
133
            //     throw new TypeORMError(`One-to-one inverse relation "${relation.entityMetadata.name}.${relation.propertyPath}" contains ` +
134
            //         `entity which does not exist in the database yet, thus cannot be bind in the database. ` +
135
            //         `Please setup cascade insertion or save entity before binding it.`);
UNCOV
136
            if (!relatedEntitySubject) return
×
137

138
            // okay, so related subject exist and its marked for insertion, then add a new change map
139
            // by example: this will tell category to insert into its post relation our post we are working with
140
            //             relatedEntitySubject is newly inserted CategorySubject
141
            //             relation.inverseRelation is OneToOne owner relation inside Category
142
            //             subject is Post needs to be inserted into Category
UNCOV
143
            relatedEntitySubject.changeMaps.push({
×
144
                relation: relation.inverseRelation!,
145
                value: subject,
146
            })
147
        }
148

149
        // check if this binding really exist in the database
150
        // by example: find our post if its already bind to category in the database and its not equal to what user tries to set
151
        const areRelatedIdEqualWithDatabase =
UNCOV
152
            relatedEntityDatabaseRelationId &&
×
153
            OrmUtils.compareIds(relationIdMap, relatedEntityDatabaseRelationId)
154

155
        // if they aren't equal it means its a new relation and we need to "bind" them
156
        // by example: this will tell category to insert into its post relation our post we are working with
157
        //             relatedEntitySubject is newly inserted CategorySubject
158
        //             relation.inverseRelation is ManyToOne relation inside Category
159
        //             subject is Post needs to be inserted into Category
UNCOV
160
        if (!areRelatedIdEqualWithDatabase) {
×
161
            // if there is no relatedEntitySubject then it means "category" wasn't persisted,
162
            // but since we are going to update "category" table (since its an owning side of relation with join column)
163
            // we create a new subject here:
UNCOV
164
            if (!relatedEntitySubject) {
×
UNCOV
165
                relatedEntitySubject = new Subject({
×
166
                    metadata: relation.inverseEntityMetadata,
167
                    canBeUpdated: true,
168
                    identifier: relationIdMap,
169
                })
UNCOV
170
                this.subjects.push(relatedEntitySubject)
×
171
            }
172

UNCOV
173
            relatedEntitySubject.changeMaps.push({
×
174
                relation: relation.inverseRelation!,
175
                value: subject,
176
            })
177
        }
178
    }
179
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc