• 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

83.61
/src/persistence/SubjectDatabaseEntityLoader.ts
1
import { Subject } from "./Subject"
2
import { ObjectLiteral } from "../common/ObjectLiteral"
3
import { QueryRunner } from "../query-runner/QueryRunner"
4
import { FindManyOptions } from "../find-options/FindManyOptions"
5
import { MongoRepository } from "../repository/MongoRepository"
6
import { OrmUtils } from "../util/OrmUtils"
1✔
7

8
/**
9
 * Loads database entities for all operate subjects which do not have database entity set.
10
 * All entities that we load database entities for are marked as updated or inserted.
11
 * To understand which of them really needs to be inserted or updated we need to load
12
 * their original representations from the database.
13
 */
14
export class SubjectDatabaseEntityLoader {
1✔
15
    // ---------------------------------------------------------------------
16
    // Constructor
17
    // ---------------------------------------------------------------------
18

19
    constructor(
20
        protected queryRunner: QueryRunner,
111✔
21
        protected subjects: Subject[],
111✔
22
    ) {}
23

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

28
    /**
29
     * Loads database entities for all subjects.
30
     *
31
     * loadAllRelations flag is used to load all relation ids of the object, no matter if they present in subject entity or not.
32
     * This option is used for deletion.
33
     */
34
    async load(
35
        operationType: "save" | "remove" | "soft-remove" | "recover",
36
    ): Promise<void> {
37
        // we are grouping subjects by target to perform more optimized queries using WHERE IN operator
38
        // go through the groups and perform loading of database entities of each subject in the group
39
        const promises = this.groupByEntityTargets().map(
111✔
40
            async (subjectGroup) => {
41
                // prepare entity ids of the subjects we need to load
42
                const allIds: ObjectLiteral[] = []
111✔
43
                const allSubjects: Subject[] = []
111✔
44
                subjectGroup.subjects.forEach((subject) => {
111✔
45
                    // we don't load if subject already has a database entity loaded
46
                    if (subject.databaseEntity || !subject.identifier) return
271✔
47

48
                    allIds.push(subject.identifier)
19✔
49
                    allSubjects.push(subject)
19✔
50
                })
51

52
                // if there no ids found (means all entities are new and have generated ids) - then nothing to load there
53
                if (!allIds.length) return
111✔
54

55
                const loadRelationPropertyPaths: string[] = []
19✔
56

57
                // for the save, soft-remove and recover operation
58
                // extract all property paths of the relations we need to load relation ids for
59
                // this is for optimization purpose - this way we don't load relation ids for entities
60
                // whose relations are undefined, and since they are undefined its really pointless to
61
                // load something for them, since undefined properties are skipped by the orm
62
                if (
19✔
63
                    operationType === "save" ||
30✔
64
                    operationType === "soft-remove" ||
65
                    operationType === "recover"
66
                ) {
67
                    subjectGroup.subjects.forEach((subject) => {
15✔
68
                        // gets all relation property paths that exist in the persisted entity.
69
                        subject.metadata.relations.forEach((relation) => {
15✔
UNCOV
70
                            const value = relation.getEntityValue(
×
71
                                subject.entityWithFulfilledIds!,
72
                            )
UNCOV
73
                            if (value === undefined) return
×
74

UNCOV
75
                            if (
×
76
                                loadRelationPropertyPaths.indexOf(
77
                                    relation.propertyPath,
78
                                ) === -1
79
                            )
UNCOV
80
                                loadRelationPropertyPaths.push(
×
81
                                    relation.propertyPath,
82
                                )
83
                        })
84
                    })
85
                } else {
86
                    // remove
87

88
                    // for remove operation
89
                    // we only need to load junction relation ids since only they are removed by cascades
90
                    loadRelationPropertyPaths.push(
4✔
91
                        ...subjectGroup.subjects[0].metadata.manyToManyRelations.map(
UNCOV
92
                            (relation) => relation.propertyPath,
×
93
                        ),
94
                    )
95
                }
96

97
                const findOptions: FindManyOptions<any> = {
19✔
98
                    loadEagerRelations: false,
99
                    loadRelationIds: {
100
                        relations: loadRelationPropertyPaths,
101
                        disableMixedMap: true,
102
                    },
103
                    // the soft-deleted entities should be included in the loaded entities for recover operation
104
                    withDeleted: true,
105
                }
106

107
                // load database entities for all given ids
108
                let entities: any[] = []
19✔
109
                if (
19!
110
                    this.queryRunner.connection.driver.options.type ===
111
                    "mongodb"
112
                ) {
113
                    const mongoRepo =
114
                        this.queryRunner.manager.getRepository<ObjectLiteral>(
19✔
115
                            subjectGroup.target,
116
                        ) as MongoRepository<ObjectLiteral>
117
                    entities = await mongoRepo.findByIds(allIds, findOptions)
19✔
118
                } else {
UNCOV
119
                    entities = await this.queryRunner.manager
×
120
                        .getRepository<ObjectLiteral>(subjectGroup.target)
121
                        .createQueryBuilder()
122
                        .setFindOptions(findOptions)
123
                        .whereInIds(allIds)
124
                        .getMany()
125
                }
126

127
                // Now when we have entities we need to find subject of each entity
128
                // and insert that entity into database entity of the found subjects.
129
                // A single entity can be applied to many subjects as there might be duplicates.
130
                // This will likely result in the same row being updated multiple times during a transaction.
131
                entities.forEach((entity) => {
19✔
132
                    const entityId =
133
                        allSubjects[0].metadata.getEntityIdMap(entity)
19✔
134
                    allSubjects.forEach((subject) => {
19✔
135
                        if (subject.databaseEntity) return
19!
136
                        if (OrmUtils.compareIds(subject.identifier, entityId))
19✔
137
                            subject.databaseEntity = entity
19✔
138
                    })
139
                })
140

141
                // this way we tell what subjects we tried to load database entities of
142
                for (const subject of allSubjects) {
19✔
143
                    subject.databaseEntityLoaded = true
19✔
144
                }
145
            },
146
        )
147

148
        await Promise.all(promises)
111✔
149
    }
150

151
    // ---------------------------------------------------------------------
152
    // Protected Methods
153
    // ---------------------------------------------------------------------
154

155
    /**
156
     * Groups given Subject objects into groups separated by entity targets.
157
     */
158
    protected groupByEntityTargets(): {
159
        target: Function | string
160
        subjects: Subject[]
161
    }[] {
162
        return this.subjects.reduce((groups, operatedEntity) => {
111✔
163
            let group = groups.find(
271✔
164
                (group) => group.target === operatedEntity.metadata.target,
160✔
165
            )
166
            if (!group) {
271✔
167
                group = { target: operatedEntity.metadata.target, subjects: [] }
111✔
168
                groups.push(group)
111✔
169
            }
170
            group.subjects.push(operatedEntity)
271✔
171
            return groups
271✔
172
        }, [] as { target: Function | string; subjects: Subject[] }[])
173
    }
174
}
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