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

typeorm / typeorm / 14796576772

02 May 2025 01:52PM UTC coverage: 45.367% (-30.9%) from 76.309%
14796576772

Pull #11434

github

web-flow
Merge ec4ce2d00 into fadad1a74
Pull Request #11434: feat: release PR releases using pkg.pr.new

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

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

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

8
/**
9
 * Finds what columns are changed in the subject entities.
10
 */
11
export class SubjectChangedColumnsComputer {
4✔
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) => {
25,791✔
21
            this.computeDiffColumns(subject)
50,392✔
22
            this.computeDiffRelationalColumns(subjects, subject)
50,392✔
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
50,392✔
36

37
        subject.metadata.columns.forEach((column) => {
41,855✔
38
            // ignore special columns
39
            if (
122,393✔
40
                column.isVirtual ||
234,531✔
41
                column.isDiscriminator // ||
42
                // column.isUpdateDate ||
43
                // column.isVersion ||
44
                // column.isCreateDate
45
            )
46
                return
10,316✔
47

48
            const changeMap = subject.changeMaps.find(
112,077✔
49
                (changeMap) => changeMap.column === column,
94,140✔
50
            )
51
            if (changeMap) {
112,077✔
52
                subject.changeMaps.splice(
168✔
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!)
112,077✔
60

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

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

70
                // get database value of the column
71
                let databaseValue = column.getEntityValue(
3,741✔
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) {
3,741✔
78
                    const value = column.relationMetadata.getEntityValue(
82✔
79
                        subject.entity!,
80
                    )
81
                    if (value !== null && value !== undefined) return
82✔
82
                }
83
                let normalizedValue = entityValue
3,731✔
84
                // normalize special values to make proper comparision
85
                if (entityValue !== null) {
3,731✔
86
                    switch (column.type) {
3,603!
87
                        case "date":
88
                            normalizedValue = column.isArray
2!
89
                                ? entityValue.map((date: Date) =>
90
                                      DateUtils.mixedDateToDateString(date),
×
91
                                  )
92
                                : DateUtils.mixedDateToDateString(entityValue)
93
                            databaseValue = column.isArray
2!
94
                                ? databaseValue.map((date: Date) =>
95
                                      DateUtils.mixedDateToDateString(date),
×
96
                                  )
97
                                : DateUtils.mixedDateToDateString(databaseValue)
98
                            break
2✔
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":
124
                            normalizedValue = column.isArray
117!
125
                                ? entityValue.map((date: Date) =>
126
                                      DateUtils.mixedDateToUtcDatetimeString(
×
127
                                          date,
128
                                      ),
129
                                  )
130
                                : DateUtils.mixedDateToUtcDatetimeString(
131
                                      entityValue,
132
                                  )
133

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

144
                            break
117✔
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": ""}]'
151
                            if (
×
152
                                OrmUtils.deepCompare(entityValue, databaseValue)
153
                            )
154
                                return
×
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) {
3,603✔
178
                        normalizedValue = ApplyValueTransformers.transformTo(
42✔
179
                            column.transformer,
180
                            entityValue,
181
                        )
182
                    }
183
                }
184

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

201
            if (!subject.diffColumns.includes(column))
92,074✔
202
                subject.diffColumns.push(column)
91,910✔
203

204
            subject.changeMaps.push({
92,074✔
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
50,392✔
220

221
        subject.metadata.relationsWithJoinColumns.forEach((relation) => {
41,855✔
222
            // get the related entity from the persisted entity
223
            let relatedEntity = relation.getEntityValue(subject.entity!)
8,522✔
224

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

228
            // if there is no database entity then all relational columns are treated as new, e.g. changed
229
            if (subject.databaseEntity) {
2,918✔
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
235
                let relatedEntityRelationIdMap: ObjectLiteral = relatedEntity
145✔
236
                if (
145✔
237
                    relatedEntityRelationIdMap !== null &&
274✔
238
                    ObjectUtils.isObject(relatedEntityRelationIdMap)
239
                )
240
                    relatedEntityRelationIdMap = relation.getRelationIdMap(
121✔
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 =
247
                    relation.getEntityValue(subject.databaseEntity)
145✔
248

249
                // if relation ids are equal then we don't need to update anything
250
                const areRelatedIdsEqual = OrmUtils.compareIds(
145✔
251
                    relatedEntityRelationIdMap,
252
                    databaseRelatedEntityRelationIdMap,
253
                )
254
                if (areRelatedIdsEqual) {
145✔
255
                    return
29✔
256
                } else {
257
                    subject.diffRelations.push(relation)
116✔
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)
263
            const valueSubject = allSubjects.find(
2,889✔
264
                (subject) =>
265
                    subject.mustBeInserted && subject.entity === relatedEntity,
5,493✔
266
            )
267
            if (valueSubject) relatedEntity = valueSubject
2,889✔
268

269
            // find if there is already a relation to be changed
270
            const changeMap = subject.changeMaps.find(
2,889✔
271
                (changeMap) => changeMap.relation === relation,
6,521✔
272
            )
273
            if (changeMap) {
2,889✔
274
                // and update its value if it was found
275
                changeMap.value = relatedEntity
24✔
276
            } else {
277
                // if it wasn't found add a new relation for change
278
                subject.changeMaps.push({
2,865✔
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