• 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

76.77
/src/persistence/EntityPersistExecutor.ts
1
import { ObjectLiteral } from "../common/ObjectLiteral"
2
import { SaveOptions } from "../repository/SaveOptions"
3
import { RemoveOptions } from "../repository/RemoveOptions"
4
import { MustBeEntityError } from "../error/MustBeEntityError"
1✔
5
import { SubjectExecutor } from "./SubjectExecutor"
1✔
6
import { CannotDetermineEntityError } from "../error/CannotDetermineEntityError"
1✔
7
import { QueryRunner } from "../query-runner/QueryRunner"
8
import { DataSource } from "../data-source/DataSource"
9
import { Subject } from "./Subject"
1✔
10
import { OneToManySubjectBuilder } from "./subject-builder/OneToManySubjectBuilder"
1✔
11
import { OneToOneInverseSideSubjectBuilder } from "./subject-builder/OneToOneInverseSideSubjectBuilder"
1✔
12
import { ManyToManySubjectBuilder } from "./subject-builder/ManyToManySubjectBuilder"
1✔
13
import { SubjectDatabaseEntityLoader } from "./SubjectDatabaseEntityLoader"
1✔
14
import { CascadesSubjectBuilder } from "./subject-builder/CascadesSubjectBuilder"
1✔
15
import { OrmUtils } from "../util/OrmUtils"
1✔
16

17
/**
18
 * Persists a single entity or multiple entities - saves or removes them.
19
 */
20
export class EntityPersistExecutor {
1✔
21
    // -------------------------------------------------------------------------
22
    // Constructor
23
    // -------------------------------------------------------------------------
24

25
    constructor(
26
        protected connection: DataSource,
111✔
27
        protected queryRunner: QueryRunner | undefined,
111✔
28
        protected mode: "save" | "remove" | "soft-remove" | "recover",
111✔
29
        protected target: Function | string | undefined,
111✔
30
        protected entity: ObjectLiteral | ObjectLiteral[],
111✔
31
        protected options?: SaveOptions & RemoveOptions,
111✔
32
    ) {}
33

34
    // -------------------------------------------------------------------------
35
    // Public Methods
36
    // -------------------------------------------------------------------------
37

38
    /**
39
     * Executes persistence operation ob given entity or entities.
40
     */
41
    async execute(): Promise<void> {
42
        // check if entity we are going to save is valid and is an object
43
        if (!this.entity || typeof this.entity !== "object")
111!
UNCOV
44
            return Promise.reject(new MustBeEntityError(this.mode, this.entity))
×
45

46
        // we MUST call "fake" resolve here to make sure all properties of lazily loaded relations are resolved
47
        await Promise.resolve()
111✔
48

49
        // if query runner is already defined in this class, it means this entity manager was already created for a single connection
50
        // if its not defined we create a new query runner - single connection where we'll execute all our operations
51
        const queryRunner =
52
            this.queryRunner || this.connection.createQueryRunner()
111✔
53

54
        // save data in the query runner - this is useful functionality to share data from outside of the world
55
        // with third classes - like subscribers and listener methods
56
        const oldQueryRunnerData = queryRunner.data
111✔
57
        if (this.options && this.options.data) {
111!
UNCOV
58
            queryRunner.data = this.options.data
×
59
        }
60

61
        try {
111✔
62
            // collect all operate subjects
63
            const entities: ObjectLiteral[] = Array.isArray(this.entity)
111✔
64
                ? this.entity
65
                : [this.entity]
66
            const entitiesInChunks =
67
                this.options && this.options.chunk && this.options.chunk > 0
111!
68
                    ? OrmUtils.chunk(entities, this.options.chunk)
69
                    : [entities]
70

71
            // console.time("building subject executors...");
72
            const executors = await Promise.all(
111✔
73
                entitiesInChunks.map(async (entities) => {
74
                    const subjects: Subject[] = []
111✔
75

76
                    // create subjects for all entities we received for the persistence
77
                    entities.forEach((entity) => {
111✔
78
                        const entityTarget = this.target
271✔
79
                            ? this.target
80
                            : entity.constructor
81
                        if (entityTarget === Object)
271!
UNCOV
82
                            throw new CannotDetermineEntityError(this.mode)
×
83

84
                        const metadata = this.connection
271✔
85
                            .getMetadata(entityTarget)
86
                            .findInheritanceMetadata(entity)
87

88
                        subjects.push(
271✔
89
                            new Subject({
90
                                metadata,
91
                                entity: entity,
92
                                canBeInserted: this.mode === "save",
93
                                canBeUpdated: this.mode === "save",
94
                                mustBeRemoved: this.mode === "remove",
95
                                canBeSoftRemoved: this.mode === "soft-remove",
96
                                canBeRecovered: this.mode === "recover",
97
                            }),
98
                        )
99
                    })
100

101
                    // console.time("building cascades...");
102
                    // go through each entity with metadata and create subjects and subjects by cascades for them
103
                    const cascadesSubjectBuilder = new CascadesSubjectBuilder(
111✔
104
                        subjects,
105
                    )
106
                    subjects.forEach((subject) => {
111✔
107
                        // next step we build list of subjects we will operate with
108
                        // these subjects are subjects that we need to insert or update alongside with main persisted entity
109
                        cascadesSubjectBuilder.build(subject, this.mode)
271✔
110
                    })
111
                    // console.timeEnd("building cascades...");
112

113
                    // load database entities for all subjects we have
114
                    // next step is to load database entities for all operate subjects
115
                    // console.time("loading...");
116
                    await new SubjectDatabaseEntityLoader(
111✔
117
                        queryRunner,
118
                        subjects,
119
                    ).load(this.mode)
120
                    // console.timeEnd("loading...");
121

122
                    // console.time("other subjects...");
123
                    // build all related subjects and change maps
124
                    if (
111✔
125
                        this.mode === "save" ||
122✔
126
                        this.mode === "soft-remove" ||
127
                        this.mode === "recover"
128
                    ) {
129
                        new OneToManySubjectBuilder(subjects).build()
107✔
130
                        new OneToOneInverseSideSubjectBuilder(subjects).build()
107✔
131
                        new ManyToManySubjectBuilder(subjects).build()
107✔
132
                    } else {
133
                        subjects.forEach((subject) => {
4✔
134
                            if (subject.mustBeRemoved) {
4✔
135
                                new ManyToManySubjectBuilder(
4✔
136
                                    subjects,
137
                                ).buildForAllRemoval(subject)
138
                            }
139
                        })
140
                    }
141
                    // console.timeEnd("other subjects...");
142
                    // console.timeEnd("building subjects...");
143
                    // console.log("subjects", subjects);
144

145
                    // create a subject executor
146
                    return new SubjectExecutor(
111✔
147
                        queryRunner,
148
                        subjects,
149
                        this.options,
150
                    )
151
                }),
152
            )
153
            // console.timeEnd("building subject executors...");
154

155
            // make sure we have at least one executable operation before we create a transaction and proceed
156
            // if we don't have operations it means we don't really need to update or remove something
157
            const executorsWithExecutableOperations = executors.filter(
111✔
158
                (executor) => executor.hasExecutableOperations,
111✔
159
            )
160
            if (executorsWithExecutableOperations.length === 0) return
111!
161

162
            // start execute queries in a transaction
163
            // if transaction is already opened in this query runner then we don't touch it
164
            // if its not opened yet then we open it here, and once we finish - we close it
165
            let isTransactionStartedByUs = false
111✔
166
            try {
111✔
167
                // open transaction if its not opened yet
168
                if (!queryRunner.isTransactionActive) {
111✔
169
                    if (
111!
170
                        this.connection.driver.transactionSupport !== "none" &&
111!
171
                        (!this.options || this.options.transaction !== false)
172
                    ) {
173
                        // start transaction until it was not explicitly disabled
UNCOV
174
                        isTransactionStartedByUs = true
×
UNCOV
175
                        await queryRunner.startTransaction()
×
176
                    }
177
                }
178

179
                // execute all persistence operations for all entities we have
180
                // console.time("executing subject executors...");
181
                for (const executor of executorsWithExecutableOperations) {
111✔
182
                    await executor.execute()
111✔
183
                }
184
                // console.timeEnd("executing subject executors...");
185

186
                // commit transaction if it was started by us
187
                // console.time("commit");
188
                if (isTransactionStartedByUs === true)
111!
UNCOV
189
                    await queryRunner.commitTransaction()
×
190
                // console.timeEnd("commit");
191
            } catch (error) {
192
                // rollback transaction if it was started by us
UNCOV
193
                if (isTransactionStartedByUs) {
×
UNCOV
194
                    try {
×
UNCOV
195
                        await queryRunner.rollbackTransaction()
×
196
                    } catch (rollbackError) {}
197
                }
UNCOV
198
                throw error
×
199
            }
200
        } finally {
201
            queryRunner.data = oldQueryRunnerData
111✔
202

203
            // release query runner only if its created by us
204
            if (!this.queryRunner) await queryRunner.release()
111✔
205
        }
206
    }
207
}
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