• 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

86.96
/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

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

18
    constructor(
19
        protected queryRunner: QueryRunner,
25,715✔
20
        protected subjects: Subject[],
25,715✔
21
    ) {}
22

23
    // ---------------------------------------------------------------------
24
    // Public Methods
25
    // ---------------------------------------------------------------------
26

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

47
                    allIds.push(subject.identifier)
26,992✔
48
                    allSubjects.push(subject)
26,992✔
49
                })
50

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

54
                const loadRelationPropertyPaths: string[] = []
16,485✔
55

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

74
                            if (
4,076✔
75
                                loadRelationPropertyPaths.indexOf(
76
                                    relation.propertyPath,
77
                                ) === -1
78
                            )
79
                                loadRelationPropertyPaths.push(
3,992✔
80
                                    relation.propertyPath,
81
                                )
82
                        })
83
                    })
84
                } else {
85
                    // remove
86

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

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

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

126
                // now when we have entities we need to find subject of each entity
127
                // and insert that entity into database entity of the found subjects
128
                entities.forEach((entity) => {
16,485✔
129
                    const subjects = this.findByPersistEntityLike(
1,054✔
130
                        subjectGroup.target,
131
                        entity,
132
                    )
133
                    subjects.forEach((subject) => {
1,054✔
134
                        subject.databaseEntity = entity
1,062✔
135
                        if (!subject.identifier)
1,062!
136
                            subject.identifier =
×
137
                                subject.metadata.hasAllPrimaryKeys(entity)
×
138
                                    ? subject.metadata.getEntityIdMap(entity)
139
                                    : undefined
140
                    })
141
                })
142

143
                // this way we tell what subjects we tried to load database entities of
144
                for (const subject of allSubjects) {
16,485✔
145
                    subject.databaseEntityLoaded = true
26,992✔
146
                }
147
            },
148
        )
149

150
        await Promise.all(promises)
25,715✔
151
    }
152

153
    // ---------------------------------------------------------------------
154
    // Protected Methods
155
    // ---------------------------------------------------------------------
156

157
    /**
158
     * Finds subjects where entity like given subject's entity.
159
     * Comparison made by entity id.
160
     * Multiple subjects may be returned if duplicates are present in the subject array.
161
     * This will likely result in the same row being updated multiple times during a transaction.
162
     */
163
    protected findByPersistEntityLike(
164
        entityTarget: Function | string,
165
        entity: ObjectLiteral,
166
    ): Subject[] {
167
        return this.subjects.filter((subject) => {
1,054✔
168
            if (!subject.entity) return false
1,796!
169

170
            if (subject.entity === entity) return true
1,796!
171

172
            return (
1,796✔
173
                subject.metadata.target === entityTarget &&
3,094✔
174
                subject.metadata.compareEntities(
175
                    subject.entityWithFulfilledIds!,
176
                    entity,
177
                )
178
            )
179
        })
180
    }
181

182
    /**
183
     * Groups given Subject objects into groups separated by entity targets.
184
     */
185
    protected groupByEntityTargets(): {
186
        target: Function | string
187
        subjects: Subject[]
188
    }[] {
189
        return this.subjects.reduce((groups, operatedEntity) => {
25,715✔
190
            let group = groups.find(
41,763✔
191
                (group) => group.target === operatedEntity.metadata.target,
19,516✔
192
            )
193
            if (!group) {
41,763✔
194
                group = { target: operatedEntity.metadata.target, subjects: [] }
26,978✔
195
                groups.push(group)
26,978✔
196
            }
197
            group.subjects.push(operatedEntity)
41,763✔
198
            return groups
41,763✔
199
        }, [] as { target: Function | string; subjects: Subject[] }[])
200
    }
201
}
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