• 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

29.49
/src/persistence/SubjectChangedColumnsComputer.ts
1
import { Subject } from "./Subject"
2
import { DateUtils } from "../util/DateUtils"
1✔
3
import { ObjectLiteral } from "../common/ObjectLiteral"
4
import { OrmUtils } from "../util/OrmUtils"
1✔
5
import { ApplyValueTransformers } from "../util/ApplyValueTransformers"
1✔
6
import { ObjectUtils } from "../util/ObjectUtils"
1✔
7

8
/**
9
 * Finds what columns are changed in the subject entities.
10
 */
11
export class SubjectChangedColumnsComputer {
1✔
12
    // -------------------------------------------------------------------------
13
    // Public Methods
14
    // -------------------------------------------------------------------------
15

16
    /**
17
     * Finds what columns are changed in the subject entities.
18
     */
19
    compute(subjects: Subject[]) {
20
        subjects.forEach((subject) => {
120✔
21
            this.computeDiffColumns(subject)
280✔
22
            this.computeDiffRelationalColumns(subjects, subject)
280✔
23
        })
24
    }
25

26
    // -------------------------------------------------------------------------
27
    // Protected Methods
28
    // -------------------------------------------------------------------------
29

30
    /**
31
     * Differentiate columns from the updated entity and entity stored in the database.
32
     */
33
    protected computeDiffColumns(subject: Subject): void {
34
        // if there is no persisted entity then nothing to compute changed in it
35
        if (!subject.entity) return
280!
36

37
        subject.metadata.columns.forEach((column) => {
280✔
38
            // ignore special columns
39
            if (
1,829!
40
                column.isVirtual ||
3,658✔
41
                column.isDiscriminator // ||
42
                // column.isUpdateDate ||
43
                // column.isVersion ||
44
                // column.isCreateDate
45
            )
UNCOV
46
                return
×
47

48
            const changeMap = subject.changeMaps.find(
1,829✔
49
                (changeMap) => changeMap.column === column,
2,528✔
50
            )
51
            if (changeMap) {
1,829✔
52
                subject.changeMaps.splice(
23✔
53
                    subject.changeMaps.indexOf(changeMap),
54
                    1,
55
                )
56
            }
57

58
            // get user provided value - column value from the user provided persisted entity
59
            const entityValue = column.getEntityValue(subject.entity!)
1,829✔
60

61
            // we don't perform operation over undefined properties (but we DO need null properties!)
62
            if (entityValue === undefined) return
1,829✔
63

64
            // if there is no database entity then all columns are treated as new, e.g. changed
65
            if (subject.databaseEntity) {
662✔
66
                // skip transform database value for json / jsonb for comparison later on
67
                const shouldTransformDatabaseEntity =
68
                    column.type !== "json" && column.type !== "jsonb"
103✔
69

70
                // get database value of the column
71
                let databaseValue = column.getEntityValue(
103✔
72
                    subject.databaseEntity,
73
                    shouldTransformDatabaseEntity,
74
                )
75

76
                // filter out "relational columns" only in the case if there is a relation object in entity
77
                if (column.relationMetadata) {
103!
UNCOV
78
                    const value = column.relationMetadata.getEntityValue(
×
79
                        subject.entity!,
80
                    )
UNCOV
81
                    if (value !== null && value !== undefined) return
×
82
                }
83
                let normalizedValue = entityValue
103✔
84
                // normalize special values to make proper comparision
85
                if (entityValue !== null) {
103✔
86
                    switch (column.type) {
103!
87
                        case "date":
UNCOV
88
                            normalizedValue = column.isArray
×
89
                                ? entityValue.map((date: Date) =>
UNCOV
90
                                      DateUtils.mixedDateToDateString(date),
×
91
                                  )
92
                                : DateUtils.mixedDateToDateString(entityValue)
UNCOV
93
                            databaseValue = column.isArray
×
94
                                ? databaseValue.map((date: Date) =>
UNCOV
95
                                      DateUtils.mixedDateToDateString(date),
×
96
                                  )
97
                                : DateUtils.mixedDateToDateString(databaseValue)
UNCOV
98
                            break
×
99

100
                        case "time":
101
                        case "time with time zone":
102
                        case "time without time zone":
103
                        case "timetz":
104
                            normalizedValue = column.isArray
×
105
                                ? entityValue.map((date: Date) =>
106
                                      DateUtils.mixedDateToTimeString(date),
×
107
                                  )
108
                                : DateUtils.mixedDateToTimeString(entityValue)
109
                            databaseValue = column.isArray
×
110
                                ? databaseValue.map((date: Date) =>
111
                                      DateUtils.mixedDateToTimeString(date),
×
112
                                  )
113
                                : DateUtils.mixedDateToTimeString(databaseValue)
114
                            break
×
115

116
                        case "datetime":
117
                        case "datetime2":
118
                        case Date:
119
                        case "timestamp":
120
                        case "timestamp without time zone":
121
                        case "timestamp with time zone":
122
                        case "timestamp with local time zone":
123
                        case "timestamptz":
UNCOV
124
                            normalizedValue = column.isArray
×
125
                                ? entityValue.map((date: Date) =>
126
                                      DateUtils.mixedDateToUtcDatetimeString(
×
127
                                          date,
128
                                      ),
129
                                  )
130
                                : DateUtils.mixedDateToUtcDatetimeString(
131
                                      entityValue,
132
                                  )
133

UNCOV
134
                            databaseValue = column.isArray
×
135
                                ? databaseValue.map((date: Date) =>
136
                                      DateUtils.mixedDateToUtcDatetimeString(
×
137
                                          date,
138
                                      ),
139
                                  )
140
                                : DateUtils.mixedDateToUtcDatetimeString(
141
                                      databaseValue,
142
                                  )
143

UNCOV
144
                            break
×
145

146
                        case "json":
147
                        case "jsonb":
148
                            // JSON.stringify doesn't work because postgresql sorts jsonb before save.
149
                            // If you try to save json '[{"messages": "", "attribute Key": "", "level":""}] ' as jsonb,
150
                            // then postgresql will save it as '[{"level": "", "message":"", "attributeKey": ""}]'
UNCOV
151
                            if (
×
152
                                OrmUtils.deepCompare(entityValue, databaseValue)
153
                            )
UNCOV
154
                                return
×
UNCOV
155
                            break
×
156

157
                        case "simple-array":
158
                            normalizedValue =
×
159
                                DateUtils.simpleArrayToString(entityValue)
160
                            databaseValue =
×
161
                                DateUtils.simpleArrayToString(databaseValue)
162
                            break
×
163
                        case "simple-enum":
164
                            normalizedValue =
×
165
                                DateUtils.simpleEnumToString(entityValue)
166
                            databaseValue =
×
167
                                DateUtils.simpleEnumToString(databaseValue)
168
                            break
×
169
                        case "simple-json":
170
                            normalizedValue =
×
171
                                DateUtils.simpleJsonToString(entityValue)
172
                            databaseValue =
×
173
                                DateUtils.simpleJsonToString(databaseValue)
174
                            break
×
175
                    }
176

177
                    if (column.transformer) {
103!
UNCOV
178
                        normalizedValue = ApplyValueTransformers.transformTo(
×
179
                            column.transformer,
180
                            entityValue,
181
                        )
182
                    }
183
                }
184

185
                // if value is not changed - then do nothing
186
                if (column.isArray) {
103!
UNCOV
187
                    if (OrmUtils.deepCompare(normalizedValue, databaseValue))
×
UNCOV
188
                        return
×
189
                } else if (
103!
190
                    Buffer.isBuffer(normalizedValue) &&
103!
191
                    Buffer.isBuffer(databaseValue)
192
                ) {
UNCOV
193
                    if (normalizedValue.equals(databaseValue)) {
×
UNCOV
194
                        return
×
195
                    }
196
                } else {
197
                    if (normalizedValue === databaseValue) return
103✔
198
                }
199
            }
200

201
            if (!subject.diffColumns.includes(column))
624✔
202
                subject.diffColumns.push(column)
601✔
203

204
            subject.changeMaps.push({
624✔
205
                column: column,
206
                value: entityValue,
207
            })
208
        })
209
    }
210

211
    /**
212
     * Difference columns of the owning one-to-one and many-to-one columns.
213
     */
214
    protected computeDiffRelationalColumns(
215
        allSubjects: Subject[],
216
        subject: Subject,
217
    ): void {
218
        // if there is no persisted entity then nothing to compute changed in it
219
        if (!subject.entity) return
280!
220

221
        subject.metadata.relationsWithJoinColumns.forEach((relation) => {
280✔
222
            // get the related entity from the persisted entity
UNCOV
223
            let relatedEntity = relation.getEntityValue(subject.entity!)
×
224

225
            // we don't perform operation over undefined properties (but we DO need null properties!)
UNCOV
226
            if (relatedEntity === undefined) return
×
227

228
            // if there is no database entity then all relational columns are treated as new, e.g. changed
UNCOV
229
            if (subject.databaseEntity) {
×
230
                // here we cover two scenarios:
231
                // 1. related entity can be another entity which is natural way
232
                // 2. related entity can be just an entity id
233
                // if relation entity is just a relation id set (for example post.tag = 1)
234
                // then we create an id map from it to make a proper comparision
UNCOV
235
                let relatedEntityRelationIdMap: ObjectLiteral = relatedEntity
×
UNCOV
236
                if (
×
237
                    relatedEntityRelationIdMap !== null &&
×
238
                    ObjectUtils.isObject(relatedEntityRelationIdMap)
239
                )
UNCOV
240
                    relatedEntityRelationIdMap = relation.getRelationIdMap(
×
241
                        relatedEntityRelationIdMap,
242
                    )!
243

244
                // get database related entity. Since loadRelationIds are used on databaseEntity
245
                // related entity will contain only its relation ids
246
                const databaseRelatedEntityRelationIdMap =
UNCOV
247
                    relation.getEntityValue(subject.databaseEntity)
×
248

249
                // if relation ids are equal then we don't need to update anything
UNCOV
250
                const areRelatedIdsEqual = OrmUtils.compareIds(
×
251
                    relatedEntityRelationIdMap,
252
                    databaseRelatedEntityRelationIdMap,
253
                )
UNCOV
254
                if (areRelatedIdsEqual) {
×
UNCOV
255
                    return
×
256
                } else {
UNCOV
257
                    subject.diffRelations.push(relation)
×
258
                }
259
            }
260

261
            // if there is an inserted subject for the related entity of the persisted entity then use it as related entity
262
            // this code is used for related entities without ids to be properly inserted (and then updated if needed)
UNCOV
263
            const valueSubject = allSubjects.find(
×
264
                (subject) =>
UNCOV
265
                    subject.mustBeInserted && subject.entity === relatedEntity,
×
266
            )
UNCOV
267
            if (valueSubject) relatedEntity = valueSubject
×
268

269
            // find if there is already a relation to be changed
UNCOV
270
            const changeMap = subject.changeMaps.find(
×
UNCOV
271
                (changeMap) => changeMap.relation === relation,
×
272
            )
UNCOV
273
            if (changeMap) {
×
274
                // and update its value if it was found
UNCOV
275
                changeMap.value = relatedEntity
×
276
            } else {
277
                // if it wasn't found add a new relation for change
UNCOV
278
                subject.changeMaps.push({
×
279
                    relation: relation,
280
                    value: relatedEntity,
281
                })
282
            }
283
        })
284
    }
285
}
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