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

typeorm / typeorm / 19549987525

20 Nov 2025 08:11PM UTC coverage: 80.769% (+4.3%) from 76.433%
19549987525

push

github

web-flow
ci: run tests on commits to master and next (#11783)

Co-authored-by: Oleg "OSA413" Sokolov <OSA413@users.noreply.github.com>

26500 of 32174 branches covered (82.36%)

Branch coverage included in aggregate %.

91252 of 113615 relevant lines covered (80.32%)

88980.79 hits per line

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

89.63
/src/persistence/SubjectExecutor.ts
1
import { QueryRunner } from "../query-runner/QueryRunner"
26✔
2
import { Subject } from "./Subject"
26✔
3
import { SubjectTopologicalSorter } from "./SubjectTopologicalSorter"
26✔
4
import { SubjectChangedColumnsComputer } from "./SubjectChangedColumnsComputer"
26✔
5
import { SubjectWithoutIdentifierError } from "../error/SubjectWithoutIdentifierError"
26✔
6
import { SubjectRemovedAndUpdatedError } from "../error/SubjectRemovedAndUpdatedError"
26✔
7
import { MongoEntityManager } from "../entity-manager/MongoEntityManager"
26✔
8
import { ObjectLiteral } from "../common/ObjectLiteral"
26✔
9
import { SaveOptions } from "../repository/SaveOptions"
26✔
10
import { RemoveOptions } from "../repository/RemoveOptions"
26✔
11
import { BroadcasterResult } from "../subscriber/BroadcasterResult"
26✔
12
import { NestedSetSubjectExecutor } from "./tree/NestedSetSubjectExecutor"
26✔
13
import { ClosureSubjectExecutor } from "./tree/ClosureSubjectExecutor"
26✔
14
import { MaterializedPathSubjectExecutor } from "./tree/MaterializedPathSubjectExecutor"
26✔
15
import { OrmUtils } from "../util/OrmUtils"
26✔
16
import { UpdateResult } from "../query-builder/result/UpdateResult"
26✔
17
import { ObjectUtils } from "../util/ObjectUtils"
26✔
18
import { InstanceChecker } from "../util/InstanceChecker"
26✔
19

26✔
20
/**
26✔
21
 * Executes all database operations (inserts, updated, deletes) that must be executed
26✔
22
 * with given persistence subjects.
26✔
23
 */
26✔
24
export class SubjectExecutor {
26✔
25
    // -------------------------------------------------------------------------
26✔
26
    // Public Properties
26✔
27
    // -------------------------------------------------------------------------
26✔
28

26✔
29
    /**
26✔
30
     * Indicates if executor has any operations to execute (e.g. has insert / update / delete operations to be executed).
26✔
31
     */
26✔
32
    hasExecutableOperations: boolean = false
26✔
33

26✔
34
    // -------------------------------------------------------------------------
26✔
35
    // Protected Properties
26✔
36
    // -------------------------------------------------------------------------
26✔
37

26✔
38
    /**
26✔
39
     * QueryRunner used to execute all queries with a given subjects.
26✔
40
     */
26✔
41
    protected queryRunner: QueryRunner
26✔
42

26✔
43
    /**
26✔
44
     * Persistence options.
26✔
45
     */
26✔
46
    protected options?: SaveOptions & RemoveOptions
26✔
47

26✔
48
    /**
26✔
49
     * All subjects that needs to be operated.
26✔
50
     */
26✔
51
    protected allSubjects: Subject[]
26✔
52

26✔
53
    /**
26✔
54
     * Subjects that must be inserted.
26✔
55
     */
26✔
56
    protected insertSubjects: Subject[] = []
26✔
57

26✔
58
    /**
26✔
59
     * Subjects that must be updated.
26✔
60
     */
26✔
61
    protected updateSubjects: Subject[] = []
26✔
62

26✔
63
    /**
26✔
64
     * Subjects that must be removed.
26✔
65
     */
26✔
66
    protected removeSubjects: Subject[] = []
26✔
67

26✔
68
    /**
26✔
69
     * Subjects that must be soft-removed.
26✔
70
     */
26✔
71
    protected softRemoveSubjects: Subject[] = []
26✔
72

26✔
73
    /**
26✔
74
     * Subjects that must be recovered.
26✔
75
     */
26✔
76
    protected recoverSubjects: Subject[] = []
26✔
77

26✔
78
    // -------------------------------------------------------------------------
26✔
79
    // Constructor
26✔
80
    // -------------------------------------------------------------------------
26✔
81

26✔
82
    constructor(
26✔
83
        queryRunner: QueryRunner,
182,030✔
84
        subjects: Subject[],
182,030✔
85
        options?: SaveOptions & RemoveOptions,
182,030✔
86
    ) {
182,030✔
87
        this.queryRunner = queryRunner
182,030✔
88
        this.allSubjects = subjects
182,030✔
89
        this.options = options
182,030✔
90
        this.validate()
182,030✔
91
        this.recompute()
182,030✔
92
    }
182,030✔
93

26✔
94
    // -------------------------------------------------------------------------
26✔
95
    // Public Methods
26✔
96
    // -------------------------------------------------------------------------
26✔
97

26✔
98
    /**
26✔
99
     * Executes all operations over given array of subjects.
26✔
100
     * Executes queries using given query runner.
26✔
101
     */
26✔
102
    async execute(): Promise<void> {
26✔
103
        // console.time("SubjectExecutor.execute");
181,477✔
104

181,477✔
105
        // broadcast "before" events before we start insert / update / remove operations
181,477✔
106
        let broadcasterResult: BroadcasterResult | undefined = undefined
181,477✔
107
        if (!this.options || this.options.listeners !== false) {
181,477✔
108
            // console.time(".broadcastBeforeEventsForAll");
181,393✔
109
            broadcasterResult = this.broadcastBeforeEventsForAll()
181,393✔
110
            if (broadcasterResult.promises.length > 0)
181,393✔
111
                await Promise.all(broadcasterResult.promises)
181,393!
112
            // console.timeEnd(".broadcastBeforeEventsForAll");
181,393✔
113
        }
181,393✔
114

181,477✔
115
        // since event listeners and subscribers can call save methods and/or trigger entity changes we need to recompute operational subjects
181,477✔
116
        // recompute only in the case if any listener or subscriber was really executed
181,477✔
117
        if (broadcasterResult && broadcasterResult.count > 0) {
181,477✔
118
            // console.time(".recompute");
562✔
119
            this.insertSubjects.forEach((subject) => subject.recompute())
562✔
120
            this.updateSubjects.forEach((subject) => subject.recompute())
562✔
121
            this.removeSubjects.forEach((subject) => subject.recompute())
562✔
122
            this.softRemoveSubjects.forEach((subject) => subject.recompute())
562✔
123
            this.recoverSubjects.forEach((subject) => subject.recompute())
562✔
124
            this.recompute()
562✔
125
            // console.timeEnd(".recompute");
562✔
126
        }
562✔
127

181,477✔
128
        // make sure our insert subjects are sorted (using topological sorting) to make cascade inserts work properly
181,477✔
129

181,477✔
130
        // console.timeEnd("prepare");
181,477✔
131

181,477✔
132
        // execute all insert operations
181,477✔
133
        // console.time(".insertion");
181,477✔
134
        this.insertSubjects = new SubjectTopologicalSorter(
181,477✔
135
            this.insertSubjects,
181,477✔
136
        ).sort("insert")
181,477✔
137
        await this.executeInsertOperations()
181,477✔
138
        // console.timeEnd(".insertion");
181,319✔
139

181,319✔
140
        // recompute update operations since insertion can create updation operations for the
181,319✔
141
        // properties it wasn't able to handle on its own (referenced columns)
181,319✔
142
        this.updateSubjects = this.allSubjects.filter(
181,319✔
143
            (subject) => subject.mustBeUpdated,
181,319✔
144
        )
181,319✔
145

181,319✔
146
        // execute update operations
181,319✔
147
        // console.time(".updation");
181,319✔
148
        await this.executeUpdateOperations()
181,319✔
149
        // console.timeEnd(".updation");
181,295✔
150

181,295✔
151
        // make sure our remove subjects are sorted (using topological sorting) when multiple entities are passed for the removal
181,295✔
152
        // console.time(".removal");
181,295✔
153
        this.removeSubjects = new SubjectTopologicalSorter(
181,295✔
154
            this.removeSubjects,
181,295✔
155
        ).sort("delete")
181,295✔
156
        await this.executeRemoveOperations()
181,295✔
157
        // console.timeEnd(".removal");
181,295✔
158

181,295✔
159
        // recompute soft-remove operations
181,295✔
160
        this.softRemoveSubjects = this.allSubjects.filter(
181,295✔
161
            (subject) => subject.mustBeSoftRemoved,
181,295✔
162
        )
181,295✔
163

181,295✔
164
        // execute soft-remove operations
181,295✔
165
        await this.executeSoftRemoveOperations()
181,295✔
166

181,239✔
167
        // recompute recover operations
181,239✔
168
        this.recoverSubjects = this.allSubjects.filter(
181,239✔
169
            (subject) => subject.mustBeRecovered,
181,239✔
170
        )
181,239✔
171

181,239✔
172
        // execute recover operations
181,239✔
173
        await this.executeRecoverOperations()
181,239✔
174

181,183✔
175
        // update all special columns in persisted entities, like inserted id or remove ids from the removed entities
181,183✔
176
        // console.time(".updateSpecialColumnsInPersistedEntities");
181,183✔
177
        this.updateSpecialColumnsInPersistedEntities()
181,183✔
178
        // console.timeEnd(".updateSpecialColumnsInPersistedEntities");
181,183✔
179

181,183✔
180
        // finally broadcast "after" events after we finish insert / update / remove operations
181,183✔
181
        if (!this.options || this.options.listeners !== false) {
181,477!
182
            // console.time(".broadcastAfterEventsForAll");
181,099✔
183
            broadcasterResult = this.broadcastAfterEventsForAll()
181,099✔
184
            if (broadcasterResult.promises.length > 0)
181,099✔
185
                await Promise.all(broadcasterResult.promises)
181,099!
186
            // console.timeEnd(".broadcastAfterEventsForAll");
181,099✔
187
        }
181,099✔
188
        // console.timeEnd("SubjectExecutor.execute");
181,477✔
189
    }
181,477✔
190

26✔
191
    // -------------------------------------------------------------------------
26✔
192
    // Protected Methods
26✔
193
    // -------------------------------------------------------------------------
26✔
194

26✔
195
    /**
26✔
196
     * Validates all given subjects.
26✔
197
     */
26✔
198
    protected validate() {
26✔
199
        this.allSubjects.forEach((subject) => {
182,030✔
200
            if (subject.mustBeUpdated && subject.mustBeRemoved)
503,129!
201
                throw new SubjectRemovedAndUpdatedError(subject)
503,129!
202
        })
182,030✔
203
    }
182,030✔
204

26✔
205
    /**
26✔
206
     * Performs entity re-computations - finds changed columns, re-builds insert/update/remove subjects.
26✔
207
     */
26✔
208
    protected recompute(): void {
26✔
209
        new SubjectChangedColumnsComputer().compute(this.allSubjects)
182,592✔
210
        this.insertSubjects = this.allSubjects.filter(
182,592✔
211
            (subject) => subject.mustBeInserted,
182,592✔
212
        )
182,592✔
213
        this.updateSubjects = this.allSubjects.filter(
182,592✔
214
            (subject) => subject.mustBeUpdated,
182,592✔
215
        )
182,592✔
216
        this.removeSubjects = this.allSubjects.filter(
182,592✔
217
            (subject) => subject.mustBeRemoved,
182,592✔
218
        )
182,592✔
219
        this.softRemoveSubjects = this.allSubjects.filter(
182,592✔
220
            (subject) => subject.mustBeSoftRemoved,
182,592✔
221
        )
182,592✔
222
        this.recoverSubjects = this.allSubjects.filter(
182,592✔
223
            (subject) => subject.mustBeRecovered,
182,592✔
224
        )
182,592✔
225
        this.hasExecutableOperations =
182,592✔
226
            this.insertSubjects.length > 0 ||
182,592✔
227
            this.updateSubjects.length > 0 ||
182,592✔
228
            this.removeSubjects.length > 0 ||
182,592✔
229
            this.softRemoveSubjects.length > 0 ||
182,592!
230
            this.recoverSubjects.length > 0
777✔
231
    }
182,592✔
232

26✔
233
    /**
26✔
234
     * Broadcasts "BEFORE_INSERT", "BEFORE_UPDATE", "BEFORE_REMOVE", "BEFORE_SOFT_REMOVE", "BEFORE_RECOVER" events for all given subjects.
26✔
235
     */
26✔
236
    protected broadcastBeforeEventsForAll(): BroadcasterResult {
26✔
237
        const result = new BroadcasterResult()
181,393✔
238
        if (this.insertSubjects.length)
181,393✔
239
            this.insertSubjects.forEach((subject) =>
181,393✔
240
                this.queryRunner.broadcaster.broadcastBeforeInsertEvent(
486,630✔
241
                    result,
486,630✔
242
                    subject.metadata,
486,630✔
243
                    subject.entity!,
486,630✔
244
                ),
176,571✔
245
            )
176,571✔
246
        if (this.updateSubjects.length)
181,393✔
247
            this.updateSubjects.forEach((subject) =>
181,393✔
248
                this.queryRunner.broadcaster.broadcastBeforeUpdateEvent(
10,340✔
249
                    result,
10,340✔
250
                    subject.metadata,
10,340✔
251
                    subject.entity!,
10,340✔
252
                    subject.databaseEntity,
10,340✔
253
                    subject.diffColumns,
10,340✔
254
                    subject.diffRelations,
10,340✔
255
                ),
6,678✔
256
            )
6,678✔
257
        if (this.removeSubjects.length)
181,393✔
258
            this.removeSubjects.forEach((subject) =>
181,393✔
259
                this.queryRunner.broadcaster.broadcastBeforeRemoveEvent(
2,498✔
260
                    result,
2,498✔
261
                    subject.metadata,
2,498✔
262
                    subject.entity!,
2,498✔
263
                    subject.databaseEntity,
2,498✔
264
                    subject.identifier,
2,498✔
265
                ),
1,730✔
266
            )
1,730✔
267
        if (this.softRemoveSubjects.length)
181,393✔
268
            this.softRemoveSubjects.forEach((subject) =>
181,393✔
269
                this.queryRunner.broadcaster.broadcastBeforeSoftRemoveEvent(
454✔
270
                    result,
454✔
271
                    subject.metadata,
454✔
272
                    subject.entity!,
454✔
273
                    subject.databaseEntity,
454✔
274
                    subject.identifier,
454✔
275
                ),
454✔
276
            )
454✔
277
        if (this.recoverSubjects.length)
181,393✔
278
            this.recoverSubjects.forEach((subject) =>
181,393!
279
                this.queryRunner.broadcaster.broadcastBeforeRecoverEvent(
168✔
280
                    result,
168✔
281
                    subject.metadata,
168✔
282
                    subject.entity!,
168✔
283
                    subject.databaseEntity,
168✔
284
                    subject.identifier,
168✔
285
                ),
168✔
286
            )
168✔
287
        return result
181,393✔
288
    }
181,393✔
289

26✔
290
    /**
26✔
291
     * Broadcasts "AFTER_INSERT", "AFTER_UPDATE", "AFTER_REMOVE", "AFTER_SOFT_REMOVE", "AFTER_RECOVER" events for all given subjects.
26✔
292
     * Returns void if there wasn't any listener or subscriber executed.
26✔
293
     * Note: this method has a performance-optimized code organization.
26✔
294
     */
26✔
295
    protected broadcastAfterEventsForAll(): BroadcasterResult {
26✔
296
        const result = new BroadcasterResult()
181,099✔
297
        if (this.insertSubjects.length)
181,099✔
298
            this.insertSubjects.forEach((subject) =>
181,099✔
299
                this.queryRunner.broadcaster.broadcastAfterInsertEvent(
486,464✔
300
                    result,
486,464✔
301
                    subject.metadata,
486,464✔
302
                    subject.entity!,
486,464✔
303
                    subject.identifier,
486,464✔
304
                ),
176,409✔
305
            )
176,409✔
306
        if (this.updateSubjects.length)
181,099✔
307
            this.updateSubjects.forEach((subject) =>
181,099✔
308
                this.queryRunner.broadcaster.broadcastAfterUpdateEvent(
10,402✔
309
                    result,
10,402✔
310
                    subject.metadata,
10,402✔
311
                    subject.entity!,
10,402✔
312
                    subject.databaseEntity,
10,402✔
313
                    subject.diffColumns,
10,402✔
314
                    subject.diffRelations,
10,402✔
315
                ),
6,744✔
316
            )
6,744✔
317
        if (this.removeSubjects.length)
181,099✔
318
            this.removeSubjects.forEach((subject) =>
181,099✔
319
                this.queryRunner.broadcaster.broadcastAfterRemoveEvent(
2,498✔
320
                    result,
2,498✔
321
                    subject.metadata,
2,498✔
322
                    subject.entity!,
2,498✔
323
                    subject.databaseEntity,
2,498✔
324
                    subject.identifier,
2,498✔
325
                ),
1,730✔
326
            )
1,730✔
327
        if (this.softRemoveSubjects.length)
181,099✔
328
            this.softRemoveSubjects.forEach((subject) =>
181,099✔
329
                this.queryRunner.broadcaster.broadcastAfterSoftRemoveEvent(
398✔
330
                    result,
398✔
331
                    subject.metadata,
398✔
332
                    subject.entity!,
398✔
333
                    subject.databaseEntity,
398✔
334
                    subject.identifier,
398✔
335
                ),
398✔
336
            )
398✔
337
        if (this.recoverSubjects.length)
181,099✔
338
            this.recoverSubjects.forEach((subject) =>
181,099!
339
                this.queryRunner.broadcaster.broadcastAfterRecoverEvent(
112✔
340
                    result,
112✔
341
                    subject.metadata,
112✔
342
                    subject.entity!,
112✔
343
                    subject.databaseEntity,
112✔
344
                    subject.identifier,
112✔
345
                ),
112✔
346
            )
112✔
347
        return result
181,099✔
348
    }
181,099✔
349

26✔
350
    /**
26✔
351
     * Executes insert operations.
26✔
352
     */
26✔
353
    protected async executeInsertOperations(): Promise<void> {
26✔
354
        // group insertion subjects to make bulk insertions
181,477✔
355
        const [groupedInsertSubjects, groupedInsertSubjectKeys] =
181,477✔
356
            this.groupBulkSubjects(this.insertSubjects, "insert")
181,477✔
357

181,477✔
358
        // then we run insertion in the sequential order which is important since we have an ordered subjects
181,477✔
359
        for (const groupName of groupedInsertSubjectKeys) {
181,477✔
360
            const subjects = groupedInsertSubjects[groupName]
224,566✔
361

224,566✔
362
            // we must separately insert entities which does not have any values to insert
224,566✔
363
            // because its not possible to insert multiple entities with only default values in bulk
224,566✔
364
            const bulkInsertMaps: ObjectLiteral[] = []
224,566✔
365
            const bulkInsertSubjects: Subject[] = []
224,566✔
366
            const singleInsertSubjects: Subject[] = []
224,566✔
367
            if (this.queryRunner.connection.driver.options.type === "mongodb") {
224,566!
368
                subjects.forEach((subject) => {
192✔
369
                    if (subject.metadata.createDateColumn && subject.entity) {
504✔
370
                        subject.entity[
2✔
371
                            subject.metadata.createDateColumn.databaseName
2✔
372
                        ] = new Date()
2✔
373
                    }
2✔
374

504✔
375
                    if (subject.metadata.updateDateColumn && subject.entity) {
504✔
376
                        subject.entity[
10✔
377
                            subject.metadata.updateDateColumn.databaseName
10✔
378
                        ] = new Date()
10✔
379
                    }
10✔
380

504✔
381
                    subject.createValueSetAndPopChangeMap()
504✔
382

504✔
383
                    bulkInsertSubjects.push(subject)
504✔
384
                    bulkInsertMaps.push(subject.entity!)
504✔
385
                })
192✔
386
            } else if (
224,566!
387
                this.queryRunner.connection.driver.options.type === "oracle"
224,374✔
388
            ) {
224,374!
389
                subjects.forEach((subject) => {
13,874✔
390
                    singleInsertSubjects.push(subject)
18,520✔
391
                })
13,874✔
392
            } else {
224,374!
393
                subjects.forEach((subject) => {
210,500✔
394
                    // we do not insert in bulk in following cases:
467,634✔
395
                    // - when there is no values in insert (only defaults are inserted), since we cannot use DEFAULT VALUES expression for multiple inserted rows
467,634✔
396
                    // - when entity is a tree table, since tree tables require extra operation per each inserted row
467,634✔
397
                    // - when oracle is used, since oracle's bulk insertion is very bad
467,634✔
398
                    if (
467,634✔
399
                        subject.changeMaps.length === 0 ||
467,634✔
400
                        subject.metadata.treeType ||
467,634✔
401
                        this.queryRunner.connection.driver.options.type ===
455,329✔
402
                            "oracle" ||
467,634✔
403
                        this.queryRunner.connection.driver.options.type ===
455,329✔
404
                            "sap"
455,329✔
405
                    ) {
467,634✔
406
                        singleInsertSubjects.push(subject)
30,133✔
407
                    } else {
467,634!
408
                        bulkInsertSubjects.push(subject)
437,501✔
409
                        bulkInsertMaps.push(
437,501✔
410
                            subject.createValueSetAndPopChangeMap(),
437,501✔
411
                        )
437,501✔
412
                    }
437,501✔
413
                })
210,500✔
414
            }
210,500✔
415

224,566✔
416
            // for mongodb we have a bit different insertion logic
224,566✔
417
            if (
224,566✔
418
                InstanceChecker.isMongoEntityManager(this.queryRunner.manager)
224,566✔
419
            ) {
224,566!
420
                const insertResult = await this.queryRunner.manager.insert(
192✔
421
                    subjects[0].metadata.target,
192✔
422
                    bulkInsertMaps,
192✔
423
                )
192✔
424
                subjects.forEach((subject, index) => {
192✔
425
                    subject.identifier = insertResult.identifiers[index]
504✔
426
                    subject.generatedMap = insertResult.generatedMaps[index]
504✔
427
                    subject.insertedValueSet = bulkInsertMaps[index]
504✔
428
                })
192✔
429
            } else {
224,566!
430
                // here we execute our insertion query
224,374✔
431
                // we need to enable entity updation because we DO need to have updated insertedMap
224,374✔
432
                // which is not same object as our entity that's why we don't need to worry about our entity to get dirty
224,374✔
433
                // also, we disable listeners because we call them on our own in persistence layer
224,374✔
434
                if (bulkInsertMaps.length > 0) {
224,374✔
435
                    const insertResult = await this.queryRunner.manager
184,571✔
436
                        .createQueryBuilder()
184,571✔
437
                        .insert()
184,571✔
438
                        .into(subjects[0].metadata.target)
184,571✔
439
                        .values(bulkInsertMaps)
184,571✔
440
                        .updateEntity(
184,571✔
441
                            this.options && this.options.reload === false
184,571✔
442
                                ? false
184,571!
443
                                : true,
184,571✔
444
                        )
184,571✔
445
                        .callListeners(false)
184,571✔
446
                        .execute()
184,571✔
447

184,515✔
448
                    bulkInsertSubjects.forEach((subject, index) => {
184,515✔
449
                        subject.identifier = insertResult.identifiers[index]
437,445✔
450
                        subject.generatedMap = insertResult.generatedMaps[index]
437,445✔
451
                        subject.insertedValueSet = bulkInsertMaps[index]
437,445✔
452
                    })
184,515✔
453
                }
184,515✔
454

224,318✔
455
                // insert subjects which must be inserted in separate requests (all default values)
224,318✔
456
                if (singleInsertSubjects.length > 0) {
224,374✔
457
                    for (const subject of singleInsertSubjects) {
39,803✔
458
                        subject.insertedValueSet =
48,653✔
459
                            subject.createValueSetAndPopChangeMap() // important to have because query builder sets inserted values into it
48,653✔
460

48,653✔
461
                        // for nested set we execute additional queries
48,653✔
462
                        if (subject.metadata.treeType === "nested-set")
48,653✔
463
                            await new NestedSetSubjectExecutor(
48,653✔
464
                                this.queryRunner,
2,818✔
465
                            ).insert(subject)
2,818✔
466

48,577✔
467
                        await this.queryRunner.manager
48,577✔
468
                            .createQueryBuilder()
48,577✔
469
                            .insert()
48,577✔
470
                            .into(subject.metadata.target)
48,577✔
471
                            .values(subject.insertedValueSet)
48,577✔
472
                            .updateEntity(
48,577✔
473
                                this.options && this.options.reload === false
48,653!
474
                                    ? false
48,653!
475
                                    : true,
48,653✔
476
                            )
48,653✔
477
                            .callListeners(false)
48,653✔
478
                            .execute()
48,653✔
479
                            .then((insertResult) => {
48,653✔
480
                                subject.identifier = insertResult.identifiers[0]
48,551✔
481
                                subject.generatedMap =
48,551✔
482
                                    insertResult.generatedMaps[0]
48,551✔
483
                            })
48,653✔
484

48,551✔
485
                        // for tree tables we execute additional queries
48,551✔
486
                        if (subject.metadata.treeType === "closure-table") {
48,653✔
487
                            await new ClosureSubjectExecutor(
3,836✔
488
                                this.queryRunner,
3,836✔
489
                            ).insert(subject)
3,836✔
490
                        } else if (
48,653✔
491
                            subject.metadata.treeType === "materialized-path"
44,715✔
492
                        ) {
44,715✔
493
                            await new MaterializedPathSubjectExecutor(
5,318✔
494
                                this.queryRunner,
5,318✔
495
                            ).insert(subject)
5,318✔
496
                        }
5,318✔
497
                    }
48,653✔
498
                }
39,701✔
499
            }
224,374✔
500

224,408✔
501
            subjects.forEach((subject) => {
224,408✔
502
                if (subject.generatedMap) {
486,500✔
503
                    subject.metadata.columns.forEach((column) => {
486,500✔
504
                        const value = column.getEntityValue(
1,697,050✔
505
                            subject.generatedMap!,
1,697,050✔
506
                        )
1,697,050✔
507
                        if (value !== undefined && value !== null) {
1,697,050✔
508
                            const preparedValue =
266,259✔
509
                                this.queryRunner.connection.driver.prepareHydratedValue(
266,259✔
510
                                    value,
266,259✔
511
                                    column,
266,259✔
512
                                )
266,259✔
513
                            column.setEntityValue(
266,259✔
514
                                subject.generatedMap!,
266,259✔
515
                                preparedValue,
266,259✔
516
                            )
266,259✔
517
                        }
266,259✔
518
                    })
486,500✔
519
                }
486,500✔
520
            })
224,408✔
521
        }
224,408✔
522
    }
181,319✔
523

26✔
524
    /**
26✔
525
     * Updates all given subjects in the database.
26✔
526
     */
26✔
527
    protected async executeUpdateOperations(): Promise<void> {
26✔
528
        const updateSubject = async (subject: Subject) => {
181,319✔
529
            if (!subject.identifier)
10,430✔
530
                throw new SubjectWithoutIdentifierError(subject)
10,430!
531

10,430✔
532
            // for mongodb we have a bit different updation logic
10,430✔
533
            if (
10,430✔
534
                InstanceChecker.isMongoEntityManager(this.queryRunner.manager)
10,430✔
535
            ) {
10,430!
536
                const partialEntity = this.cloneMongoSubjectEntity(subject)
24✔
537
                if (
24✔
538
                    subject.metadata.objectIdColumn &&
24✔
539
                    subject.metadata.objectIdColumn.propertyName
24✔
540
                ) {
24✔
541
                    delete partialEntity[
24✔
542
                        subject.metadata.objectIdColumn.propertyName
24✔
543
                    ]
24✔
544
                }
24✔
545

24✔
546
                if (
24✔
547
                    subject.metadata.createDateColumn &&
24✔
548
                    subject.metadata.createDateColumn.propertyName
2✔
549
                ) {
24✔
550
                    delete partialEntity[
2✔
551
                        subject.metadata.createDateColumn.propertyName
2✔
552
                    ]
2✔
553
                }
2✔
554

24✔
555
                if (
24✔
556
                    subject.metadata.updateDateColumn &&
24✔
557
                    subject.metadata.updateDateColumn.propertyName
14✔
558
                ) {
24✔
559
                    partialEntity[
14✔
560
                        subject.metadata.updateDateColumn.propertyName
14✔
561
                    ] = new Date()
14✔
562
                }
14✔
563

24✔
564
                const manager = this.queryRunner.manager as MongoEntityManager
24✔
565

24✔
566
                await manager.update(
24✔
567
                    subject.metadata.target,
24✔
568
                    subject.identifier,
24✔
569
                    partialEntity,
24✔
570
                )
24✔
571
            } else {
10,430!
572
                const updateMap: ObjectLiteral =
10,406✔
573
                    subject.createValueSetAndPopChangeMap()
10,406✔
574

10,406✔
575
                // for tree tables we execute additional queries
10,406✔
576
                switch (subject.metadata.treeType) {
10,406✔
577
                    case "nested-set":
10,406!
578
                        await new NestedSetSubjectExecutor(
190✔
579
                            this.queryRunner,
190✔
580
                        ).update(subject)
190✔
581
                        break
170✔
582

10,406✔
583
                    case "closure-table":
10,406!
584
                        await new ClosureSubjectExecutor(
130✔
585
                            this.queryRunner,
130✔
586
                        ).update(subject)
130✔
587
                        break
130✔
588

10,406✔
589
                    case "materialized-path":
10,406✔
590
                        await new MaterializedPathSubjectExecutor(
238✔
591
                            this.queryRunner,
238✔
592
                        ).update(subject)
238✔
593
                        break
238✔
594
                }
10,406✔
595

10,386✔
596
                // here we execute our updation query
10,386✔
597
                // we need to enable entity updation because we update a subject identifier
10,386✔
598
                // which is not same object as our entity that's why we don't need to worry about our entity to get dirty
10,386✔
599
                // also, we disable listeners because we call them on our own in persistence layer
10,386✔
600
                const updateQueryBuilder = this.queryRunner.manager
10,386✔
601
                    .createQueryBuilder()
10,386✔
602
                    .update(subject.metadata.target)
10,386✔
603
                    .set(updateMap)
10,386✔
604
                    .updateEntity(
10,386✔
605
                        this.options && this.options.reload === false
10,406✔
606
                            ? false
10,406!
607
                            : true,
10,406✔
608
                    )
10,406✔
609
                    .callListeners(false)
10,406✔
610

10,406✔
611
                if (subject.entity) {
10,406✔
612
                    updateQueryBuilder.whereEntity(subject.identifier)
2,916✔
613
                } else {
10,406✔
614
                    // in this case identifier is just conditions object to update by
7,470✔
615
                    updateQueryBuilder.where(subject.identifier)
7,470✔
616
                }
7,470✔
617

10,386✔
618
                const updateResult = await updateQueryBuilder.execute()
10,386✔
619
                const updateGeneratedMap = updateResult.generatedMaps[0]
10,378✔
620
                if (updateGeneratedMap) {
10,406✔
621
                    subject.metadata.columns.forEach((column) => {
294✔
622
                        const value = column.getEntityValue(updateGeneratedMap!)
1,582✔
623
                        if (value !== undefined && value !== null) {
1,582✔
624
                            const preparedValue =
548✔
625
                                this.queryRunner.connection.driver.prepareHydratedValue(
548✔
626
                                    value,
548✔
627
                                    column,
548✔
628
                                )
548✔
629
                            column.setEntityValue(
548✔
630
                                updateGeneratedMap!,
548✔
631
                                preparedValue,
548✔
632
                            )
548✔
633
                        }
548✔
634
                    })
294✔
635
                    if (!subject.generatedMap) {
294✔
636
                        subject.generatedMap = {}
262✔
637
                    }
262✔
638
                    Object.assign(subject.generatedMap, updateGeneratedMap)
294✔
639
                }
294✔
640
            }
10,406✔
641
        }
10,430✔
642

181,319✔
643
        // Nested sets need to be updated one by one
181,319✔
644
        // Split array in two, one with nested set subjects and the other with the remaining subjects
181,319✔
645
        const nestedSetSubjects: Subject[] = []
181,319✔
646
        const remainingSubjects: Subject[] = []
181,319✔
647

181,319✔
648
        for (const subject of this.updateSubjects) {
181,319✔
649
            if (subject.metadata.treeType === "nested-set") {
10,430!
650
                nestedSetSubjects.push(subject)
190✔
651
            } else {
10,430✔
652
                remainingSubjects.push(subject)
10,240✔
653
            }
10,240✔
654
        }
10,430✔
655

181,319✔
656
        // Run nested set updates one by one
181,319✔
657
        const updateNestSetSubjects = async () => {
181,319✔
658
            for (const subject of nestedSetSubjects) {
181,319!
659
                await updateSubject(subject)
190✔
660
            }
170✔
661
        }
181,299✔
662

181,319✔
663
        // Run all remaining subjects in parallel
181,319✔
664
        await Promise.all([
181,319✔
665
            ...remainingSubjects.map(updateSubject),
181,319✔
666
            updateNestSetSubjects(),
181,319✔
667
        ])
181,319✔
668
    }
181,295✔
669

26✔
670
    /**
26✔
671
     * Removes all given subjects from the database.
26✔
672
     *
26✔
673
     * todo: we need to apply topological sort here as well
26✔
674
     */
26✔
675
    protected async executeRemoveOperations(): Promise<void> {
26✔
676
        // group insertion subjects to make bulk insertions
181,295✔
677
        const [groupedRemoveSubjects, groupedRemoveSubjectKeys] =
181,295✔
678
            this.groupBulkSubjects(this.removeSubjects, "delete")
181,295✔
679

181,295✔
680
        for (const groupName of groupedRemoveSubjectKeys) {
181,295✔
681
            const subjects = groupedRemoveSubjects[groupName]
2,234✔
682
            const deleteMaps = subjects.map((subject) => {
2,234✔
683
                if (!subject.identifier)
2,526✔
684
                    throw new SubjectWithoutIdentifierError(subject)
2,526!
685

2,526✔
686
                return subject.identifier
2,526✔
687
            })
2,234✔
688

2,234✔
689
            // for mongodb we have a bit different updation logic
2,234✔
690
            if (
2,234✔
691
                InstanceChecker.isMongoEntityManager(this.queryRunner.manager)
2,234✔
692
            ) {
2,234!
693
                const manager = this.queryRunner.manager as MongoEntityManager
8✔
694
                await manager.delete(subjects[0].metadata.target, deleteMaps)
8✔
695
            } else {
2,234!
696
                // for tree tables we execute additional queries
2,226✔
697
                switch (subjects[0].metadata.treeType) {
2,226✔
698
                    case "nested-set":
2,226!
699
                        await new NestedSetSubjectExecutor(
52✔
700
                            this.queryRunner,
52✔
701
                        ).remove(subjects)
52✔
702
                        break
52✔
703

2,226✔
704
                    case "closure-table":
2,226!
705
                        await new ClosureSubjectExecutor(
26✔
706
                            this.queryRunner,
26✔
707
                        ).remove(subjects)
26✔
708
                        break
26✔
709
                }
2,226✔
710

2,226✔
711
                // here we execute our deletion query
2,226✔
712
                // we don't need to specify entities and set update entity to true since the only thing query builder
2,226✔
713
                // will do for use is a primary keys deletion which is handled by us later once persistence is finished
2,226✔
714
                // also, we disable listeners because we call them on our own in persistence layer
2,226✔
715
                await this.queryRunner.manager
2,226✔
716
                    .createQueryBuilder()
2,226✔
717
                    .delete()
2,226✔
718
                    .from(subjects[0].metadata.target)
2,226✔
719
                    .where(deleteMaps)
2,226✔
720
                    .callListeners(false)
2,226✔
721
                    .execute()
2,226✔
722
            }
2,226✔
723
        }
2,234✔
724
    }
181,295✔
725

26✔
726
    private cloneMongoSubjectEntity(subject: Subject): ObjectLiteral {
26✔
727
        const target: ObjectLiteral = {}
30✔
728

30✔
729
        if (subject.entity) {
30✔
730
            for (const column of subject.metadata.columns) {
30✔
731
                OrmUtils.mergeDeep(
144✔
732
                    target,
144✔
733
                    column.getEntityValueMap(subject.entity),
144✔
734
                )
144✔
735
            }
144✔
736
        }
30✔
737

30✔
738
        return target
30✔
739
    }
30✔
740

26✔
741
    /**
26✔
742
     * Soft-removes all given subjects in the database.
26✔
743
     */
26✔
744
    protected async executeSoftRemoveOperations(): Promise<void> {
26✔
745
        await Promise.all(
181,295✔
746
            this.softRemoveSubjects.map(async (subject) => {
181,295✔
747
                if (!subject.identifier)
482✔
748
                    throw new SubjectWithoutIdentifierError(subject)
482!
749

482✔
750
                let updateResult: UpdateResult
482✔
751

482✔
752
                // for mongodb we have a bit different updation logic
482✔
753
                if (
482✔
754
                    InstanceChecker.isMongoEntityManager(
482✔
755
                        this.queryRunner.manager,
482✔
756
                    )
482✔
757
                ) {
482!
758
                    const partialEntity = this.cloneMongoSubjectEntity(subject)
6✔
759
                    if (
6✔
760
                        subject.metadata.objectIdColumn &&
6✔
761
                        subject.metadata.objectIdColumn.propertyName
6✔
762
                    ) {
6✔
763
                        delete partialEntity[
6✔
764
                            subject.metadata.objectIdColumn.propertyName
6✔
765
                        ]
6✔
766
                    }
6✔
767

6✔
768
                    if (
6✔
769
                        subject.metadata.createDateColumn &&
6!
770
                        subject.metadata.createDateColumn.propertyName
×
771
                    ) {
6!
772
                        delete partialEntity[
×
773
                            subject.metadata.createDateColumn.propertyName
×
774
                        ]
×
775
                    }
×
776

6✔
777
                    if (
6✔
778
                        subject.metadata.updateDateColumn &&
6!
779
                        subject.metadata.updateDateColumn.propertyName
×
780
                    ) {
6!
781
                        partialEntity[
×
782
                            subject.metadata.updateDateColumn.propertyName
×
783
                        ] = new Date()
×
784
                    }
×
785

6✔
786
                    if (
6✔
787
                        subject.metadata.deleteDateColumn &&
6✔
788
                        subject.metadata.deleteDateColumn.propertyName
6✔
789
                    ) {
6✔
790
                        partialEntity[
6✔
791
                            subject.metadata.deleteDateColumn.propertyName
6✔
792
                        ] = new Date()
6✔
793
                    }
6✔
794

6✔
795
                    const manager = this.queryRunner
6✔
796
                        .manager as MongoEntityManager
6✔
797

6✔
798
                    updateResult = await manager.update(
6✔
799
                        subject.metadata.target,
6✔
800
                        subject.identifier,
6✔
801
                        partialEntity,
6✔
802
                    )
6✔
803
                } else {
482!
804
                    // here we execute our soft-deletion query
476✔
805
                    // we need to enable entity soft-deletion because we update a subject identifier
476✔
806
                    // which is not same object as our entity that's why we don't need to worry about our entity to get dirty
476✔
807
                    // also, we disable listeners because we call them on our own in persistence layer
476✔
808
                    const softDeleteQueryBuilder = this.queryRunner.manager
476✔
809
                        .createQueryBuilder()
476✔
810
                        .softDelete()
476✔
811
                        .from(subject.metadata.target)
476✔
812
                        .updateEntity(
476✔
813
                            this.options && this.options.reload === false
476✔
814
                                ? false
476!
815
                                : true,
476✔
816
                        )
476✔
817
                        .callListeners(false)
476✔
818

476✔
819
                    if (subject.entity) {
476✔
820
                        softDeleteQueryBuilder.whereEntity(subject.identifier)
448✔
821
                    } else {
476✔
822
                        // in this case identifier is just conditions object to update by
28✔
823
                        softDeleteQueryBuilder.where(subject.identifier)
28✔
824
                    }
28✔
825

476✔
826
                    updateResult = await softDeleteQueryBuilder.execute()
476✔
827
                }
420✔
828

426✔
829
                subject.generatedMap = updateResult.generatedMaps[0]
426✔
830
                if (subject.generatedMap) {
482!
831
                    subject.metadata.columns.forEach((column) => {
392✔
832
                        const value = column.getEntityValue(
1,680✔
833
                            subject.generatedMap!,
1,680✔
834
                        )
1,680✔
835
                        if (value !== undefined && value !== null) {
1,680✔
836
                            const preparedValue =
686✔
837
                                this.queryRunner.connection.driver.prepareHydratedValue(
686✔
838
                                    value,
686✔
839
                                    column,
686✔
840
                                )
686✔
841
                            column.setEntityValue(
686✔
842
                                subject.generatedMap!,
686✔
843
                                preparedValue,
686✔
844
                            )
686✔
845
                        }
686✔
846
                    })
392✔
847
                }
392✔
848

482✔
849
                // experiments, remove probably, need to implement tree tables children removal
482✔
850
                // if (subject.updatedRelationMaps.length > 0) {
482✔
851
                //     await Promise.all(subject.updatedRelationMaps.map(async updatedRelation => {
482✔
852
                //         if (!updatedRelation.relation.isTreeParent) return;
482✔
853
                //         if (!updatedRelation.value !== null) return;
482✔
854
                //
482✔
855
                //         if (subject.metadata.treeType === "closure-table") {
482✔
856
                //             await new ClosureSubjectExecutor(this.queryRunner).deleteChildrenOf(subject);
482✔
857
                //         }
482✔
858
                //     }));
482✔
859
                // }
482✔
860
            }),
181,295✔
861
        )
181,295✔
862
    }
181,239✔
863

26✔
864
    /**
26✔
865
     * Recovers all given subjects in the database.
26✔
866
     */
26✔
867
    protected async executeRecoverOperations(): Promise<void> {
26✔
868
        await Promise.all(
181,239✔
869
            this.recoverSubjects.map(async (subject) => {
181,239✔
870
                if (!subject.identifier)
168✔
871
                    throw new SubjectWithoutIdentifierError(subject)
168!
872

168✔
873
                let updateResult: UpdateResult
168✔
874

168✔
875
                // for mongodb we have a bit different updation logic
168✔
876
                if (
168✔
877
                    InstanceChecker.isMongoEntityManager(
168✔
878
                        this.queryRunner.manager,
168✔
879
                    )
168✔
880
                ) {
168!
881
                    const partialEntity = this.cloneMongoSubjectEntity(subject)
×
882
                    if (
×
883
                        subject.metadata.objectIdColumn &&
×
884
                        subject.metadata.objectIdColumn.propertyName
×
885
                    ) {
×
886
                        delete partialEntity[
×
887
                            subject.metadata.objectIdColumn.propertyName
×
888
                        ]
×
889
                    }
×
890

×
891
                    if (
×
892
                        subject.metadata.createDateColumn &&
×
893
                        subject.metadata.createDateColumn.propertyName
×
894
                    ) {
×
895
                        delete partialEntity[
×
896
                            subject.metadata.createDateColumn.propertyName
×
897
                        ]
×
898
                    }
×
899

×
900
                    if (
×
901
                        subject.metadata.updateDateColumn &&
×
902
                        subject.metadata.updateDateColumn.propertyName
×
903
                    ) {
×
904
                        partialEntity[
×
905
                            subject.metadata.updateDateColumn.propertyName
×
906
                        ] = new Date()
×
907
                    }
×
908

×
909
                    if (
×
910
                        subject.metadata.deleteDateColumn &&
×
911
                        subject.metadata.deleteDateColumn.propertyName
×
912
                    ) {
×
913
                        partialEntity[
×
914
                            subject.metadata.deleteDateColumn.propertyName
×
915
                        ] = null
×
916
                    }
×
917

×
918
                    const manager = this.queryRunner
×
919
                        .manager as MongoEntityManager
×
920

×
921
                    updateResult = await manager.update(
×
922
                        subject.metadata.target,
×
923
                        subject.identifier,
×
924
                        partialEntity,
×
925
                    )
×
926
                } else {
168✔
927
                    // here we execute our restory query
168✔
928
                    // we need to enable entity restory because we update a subject identifier
168✔
929
                    // which is not same object as our entity that's why we don't need to worry about our entity to get dirty
168✔
930
                    // also, we disable listeners because we call them on our own in persistence layer
168✔
931
                    const softDeleteQueryBuilder = this.queryRunner.manager
168✔
932
                        .createQueryBuilder()
168✔
933
                        .restore()
168✔
934
                        .from(subject.metadata.target)
168✔
935
                        .updateEntity(
168✔
936
                            this.options && this.options.reload === false
168✔
937
                                ? false
168!
938
                                : true,
168✔
939
                        )
168✔
940
                        .callListeners(false)
168✔
941

168✔
942
                    if (subject.entity) {
168✔
943
                        softDeleteQueryBuilder.whereEntity(subject.identifier)
168✔
944
                    } else {
168!
945
                        // in this case identifier is just conditions object to update by
×
946
                        softDeleteQueryBuilder.where(subject.identifier)
×
947
                    }
×
948

168✔
949
                    updateResult = await softDeleteQueryBuilder.execute()
168✔
950
                }
112✔
951

112✔
952
                subject.generatedMap = updateResult.generatedMaps[0]
112✔
953
                if (subject.generatedMap) {
112✔
954
                    subject.metadata.columns.forEach((column) => {
112✔
955
                        const value = column.getEntityValue(
672✔
956
                            subject.generatedMap!,
672✔
957
                        )
672✔
958
                        if (value !== undefined && value !== null) {
672✔
959
                            const preparedValue =
104✔
960
                                this.queryRunner.connection.driver.prepareHydratedValue(
104✔
961
                                    value,
104✔
962
                                    column,
104✔
963
                                )
104✔
964
                            column.setEntityValue(
104✔
965
                                subject.generatedMap!,
104✔
966
                                preparedValue,
104✔
967
                            )
104✔
968
                        }
104✔
969
                    })
112✔
970
                }
112✔
971

168✔
972
                // experiments, remove probably, need to implement tree tables children removal
168✔
973
                // if (subject.updatedRelationMaps.length > 0) {
168✔
974
                //     await Promise.all(subject.updatedRelationMaps.map(async updatedRelation => {
168✔
975
                //         if (!updatedRelation.relation.isTreeParent) return;
168✔
976
                //         if (!updatedRelation.value !== null) return;
168✔
977
                //
168✔
978
                //         if (subject.metadata.treeType === "closure-table") {
168✔
979
                //             await new ClosureSubjectExecutor(this.queryRunner).deleteChildrenOf(subject);
168✔
980
                //         }
168✔
981
                //     }));
168✔
982
                // }
168✔
983
            }),
181,239✔
984
        )
181,239✔
985
    }
181,183✔
986

26✔
987
    /**
26✔
988
     * Updates all special columns of the saving entities (create date, update date, version, etc.).
26✔
989
     * Also updates nullable columns and columns with default values.
26✔
990
     */
26✔
991
    protected updateSpecialColumnsInPersistedEntities(): void {
26✔
992
        // update inserted entity properties
181,183✔
993
        if (this.insertSubjects.length)
181,183✔
994
            this.updateSpecialColumnsInInsertedAndUpdatedEntities(
181,183✔
995
                this.insertSubjects,
176,437✔
996
            )
176,437✔
997

181,183✔
998
        // update updated entity properties
181,183✔
999
        if (this.updateSubjects.length)
181,183✔
1000
            this.updateSpecialColumnsInInsertedAndUpdatedEntities(
181,183✔
1001
                this.updateSubjects,
6,744✔
1002
            )
6,744✔
1003

181,183✔
1004
        // update soft-removed entity properties
181,183✔
1005
        if (this.softRemoveSubjects.length)
181,183✔
1006
            this.updateSpecialColumnsInInsertedAndUpdatedEntities(
181,183✔
1007
                this.softRemoveSubjects,
426✔
1008
            )
426✔
1009

181,183✔
1010
        // update recovered entity properties
181,183✔
1011
        if (this.recoverSubjects.length)
181,183✔
1012
            this.updateSpecialColumnsInInsertedAndUpdatedEntities(
181,183!
1013
                this.recoverSubjects,
112✔
1014
            )
112✔
1015

181,183✔
1016
        // remove ids from the entities that were removed
181,183✔
1017
        if (this.removeSubjects.length) {
181,183✔
1018
            this.removeSubjects.forEach((subject) => {
1,758✔
1019
                if (!subject.entity) return
2,526!
1020

1,502✔
1021
                subject.metadata.primaryColumns.forEach((primaryColumn) => {
1,502✔
1022
                    primaryColumn.setEntityValue(subject.entity!, undefined)
1,934✔
1023
                })
1,502✔
1024
            })
1,758✔
1025
        }
1,758✔
1026

181,183✔
1027
        // other post-persist updations
181,183✔
1028
        this.allSubjects.forEach((subject) => {
181,183✔
1029
            if (!subject.entity) return
502,088!
1030

441,096✔
1031
            subject.metadata.relationIds.forEach((relationId) => {
441,096✔
1032
                relationId.setValue(subject.entity!)
8,742✔
1033
            })
441,096✔
1034

441,096✔
1035
            // mongo _id remove
441,096✔
1036
            if (
441,096✔
1037
                InstanceChecker.isMongoEntityManager(this.queryRunner.manager)
441,096✔
1038
            ) {
502,088!
1039
                if (
542✔
1040
                    subject.metadata.objectIdColumn &&
542✔
1041
                    subject.metadata.objectIdColumn.databaseName &&
542✔
1042
                    subject.metadata.objectIdColumn.databaseName !==
542✔
1043
                        subject.metadata.objectIdColumn.propertyName
542✔
1044
                ) {
542✔
1045
                    delete subject.entity[
494✔
1046
                        subject.metadata.objectIdColumn.databaseName
494✔
1047
                    ]
494✔
1048
                }
494✔
1049
            }
542✔
1050
        })
181,183✔
1051
    }
181,183✔
1052

26✔
1053
    /**
26✔
1054
     * Updates all special columns of the saving entities (create date, update date, version, etc.).
26✔
1055
     * Also updates nullable columns and columns with default values.
26✔
1056
     */
26✔
1057
    protected updateSpecialColumnsInInsertedAndUpdatedEntities(
26✔
1058
        subjects: Subject[],
183,719✔
1059
    ): void {
183,719✔
1060
        subjects.forEach((subject) => {
183,719✔
1061
            if (!subject.entity) return
497,432!
1062

437,464✔
1063
            // set values to "null" for nullable columns that did not have values
437,464✔
1064
            subject.metadata.columns.forEach((column) => {
437,464✔
1065
                // if table inheritance is used make sure this column is not child's column
1,586,526✔
1066
                if (
1,586,526✔
1067
                    subject.metadata.childEntityMetadatas.length > 0 &&
1,586,526!
1068
                    subject.metadata.childEntityMetadatas
528✔
1069
                        .map((metadata) => metadata.target)
528✔
1070
                        .indexOf(column.target) !== -1
528✔
1071
                )
1,586,526✔
1072
                    return
1,586,526!
1073

1,586,354✔
1074
                // entities does not have virtual columns
1,586,354✔
1075
                if (column.isVirtual) return
1,586,354!
1076

1,500,106✔
1077
                // if column is deletedAt
1,500,106✔
1078
                if (column.isDeleteDate) return
1,500,106✔
1079

1,497,382✔
1080
                // update nullable columns
1,497,382✔
1081
                if (column.isNullable) {
1,586,526✔
1082
                    const columnValue = column.getEntityValue(subject.entity!)
17,957✔
1083
                    if (columnValue === undefined)
17,957✔
1084
                        column.setEntityValue(subject.entity!, null)
17,957!
1085
                }
17,957✔
1086

1,497,382✔
1087
                // update relational columns
1,497,382✔
1088
                if (subject.updatedRelationMaps.length > 0) {
1,586,526!
1089
                    subject.updatedRelationMaps.forEach(
81,463✔
1090
                        (updatedRelationMap) => {
81,463✔
1091
                            updatedRelationMap.relation.joinColumns.forEach(
89,458✔
1092
                                (column) => {
89,458✔
1093
                                    if (column.isVirtual === true) return
97,505✔
1094

7,162✔
1095
                                    column.setEntityValue(
7,162✔
1096
                                        subject.entity!,
7,162✔
1097
                                        ObjectUtils.isObject(
7,162✔
1098
                                            updatedRelationMap.value,
7,162✔
1099
                                        )
7,162✔
1100
                                            ? column.referencedColumn!.getEntityValue(
97,505✔
1101
                                                  updatedRelationMap.value,
7,050✔
1102
                                              )
97,505✔
1103
                                            : updatedRelationMap.value,
97,505✔
1104
                                    )
97,505✔
1105
                                },
89,458✔
1106
                            )
89,458✔
1107
                        },
81,463✔
1108
                    )
81,463✔
1109
                }
81,463✔
1110
            })
437,464✔
1111

437,464✔
1112
            // merge into entity all generated values returned by a database
437,464✔
1113
            if (subject.generatedMap)
437,464✔
1114
                this.queryRunner.manager.merge(
497,432✔
1115
                    subject.metadata.target,
434,898✔
1116
                    subject.entity,
434,898✔
1117
                    subject.generatedMap,
434,898✔
1118
                )
434,898✔
1119
        })
183,719✔
1120
    }
183,719✔
1121

26✔
1122
    /**
26✔
1123
     * Groups subjects by metadata names (by tables) to make bulk insertions and deletions possible.
26✔
1124
     * However there are some limitations with bulk insertions of data into tables with generated (increment) columns
26✔
1125
     * in some drivers. Some drivers like mysql and sqlite does not support returning multiple generated columns
26✔
1126
     * after insertion and can only return a single generated column value, that's why its not possible to do bulk insertion,
26✔
1127
     * because it breaks insertion result's generatedMap and leads to problems when this subject is used in other subjects saves.
26✔
1128
     * That's why we only support bulking in junction tables for those drivers.
26✔
1129
     *
26✔
1130
     * Other drivers like postgres and sql server support RETURNING / OUTPUT statement which allows to return generated
26✔
1131
     * id for each inserted row, that's why bulk insertion is not limited to junction tables in there.
26✔
1132
     */
26✔
1133
    protected groupBulkSubjects(
26✔
1134
        subjects: Subject[],
362,772✔
1135
        type: "insert" | "delete",
362,772✔
1136
    ): [{ [key: string]: Subject[] }, string[]] {
362,772✔
1137
        const group: { [key: string]: Subject[] } = {}
362,772✔
1138
        const keys: string[] = []
362,772✔
1139
        const hasReturningDependColumns = subjects.some((subject) => {
362,772✔
1140
            return subject.metadata.getInsertionReturningColumns().length > 0
269,396✔
1141
        })
362,772✔
1142
        const groupingAllowed =
362,772✔
1143
            type === "delete" ||
362,772✔
1144
            this.queryRunner.connection.driver.isReturningSqlSupported(
181,477✔
1145
                "insert",
181,477✔
1146
            ) ||
362,772!
1147
            hasReturningDependColumns === false
99,193✔
1148

362,772✔
1149
        subjects.forEach((subject, index) => {
362,772✔
1150
            const key =
489,184✔
1151
                groupingAllowed || subject.metadata.isJunction
489,184!
1152
                    ? subject.metadata.name
489,184✔
1153
                    : subject.metadata.name + "_" + index
489,184!
1154
            if (!group[key]) {
489,184✔
1155
                group[key] = [subject]
226,800✔
1156
                keys.push(key)
226,800✔
1157
            } else {
489,184✔
1158
                group[key].push(subject)
262,384✔
1159
            }
262,384✔
1160
        })
362,772✔
1161

362,772✔
1162
        return [group, keys]
362,772✔
1163
    }
362,772✔
1164
}
26✔
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

© 2026 Coveralls, Inc