• 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

57.09
/src/migration/MigrationExecutor.ts
1
import { Table } from "../schema-builder/table/Table"
1✔
2
import { DataSource } from "../data-source/DataSource"
3
import { Migration } from "./Migration"
1✔
4
import { ObjectLiteral } from "../common/ObjectLiteral"
5
import { QueryRunner } from "../query-runner/QueryRunner"
6
import { MssqlParameter } from "../driver/sqlserver/MssqlParameter"
1✔
7
import { MongoQueryRunner } from "../driver/mongodb/MongoQueryRunner"
8
import { ForbiddenTransactionModeOverrideError, TypeORMError } from "../error"
1✔
9
import { InstanceChecker } from "../util/InstanceChecker"
1✔
10

11
/**
12
 * Executes migrations: runs pending and reverts previously executed migrations.
13
 */
14
export class MigrationExecutor {
1✔
15
    // -------------------------------------------------------------------------
16
    // Public Properties
17
    // -------------------------------------------------------------------------
18

19
    /**
20
     * Indicates how migrations should be run in transactions.
21
     *   all: all migrations are run in a single transaction
22
     *   none: all migrations are run without a transaction
23
     *   each: each migration is run in a separate transaction
24
     */
25
    transaction: "all" | "none" | "each" = "all"
3✔
26

27
    /**
28
     * Option to fake-run or fake-revert a migration, adding to the
29
     * executed migrations table, but not actually running it. This feature is
30
     * useful for when migrations are added after the fact or for
31
     * interoperability between applications which are desired to each keep
32
     * a consistent migration history.
33
     */
34
    fake: boolean
35

36
    // -------------------------------------------------------------------------
37
    // Private Properties
38
    // -------------------------------------------------------------------------
39

40
    private readonly migrationsDatabase?: string
41
    private readonly migrationsSchema?: string
42
    private readonly migrationsTable: string
43
    private readonly migrationsTableName: string
44

45
    // -------------------------------------------------------------------------
46
    // Constructor
47
    // -------------------------------------------------------------------------
48

49
    constructor(
50
        protected connection: DataSource,
3✔
51
        protected queryRunner?: QueryRunner,
3✔
52
    ) {
53
        const { schema } = this.connection.driver.options as any
3✔
54
        const database = this.connection.driver.database
3✔
55
        this.migrationsDatabase = database
3✔
56
        this.migrationsSchema = schema
3✔
57
        this.migrationsTableName =
3✔
58
            connection.options.migrationsTableName || "migrations"
6✔
59
        this.migrationsTable = this.connection.driver.buildTableName(
3✔
60
            this.migrationsTableName,
61
            schema,
62
            database,
63
        )
64
    }
65

66
    // -------------------------------------------------------------------------
67
    // Public Methods
68
    // -------------------------------------------------------------------------
69

70
    /**
71
     * Tries to execute a single migration given.
72
     */
73
    public async executeMigration(migration: Migration): Promise<Migration> {
74
        return this.withQueryRunner(async (queryRunner) => {
×
75
            await this.createMigrationsTableIfNotExist(queryRunner)
×
76

77
            // create typeorm_metadata table if it's not created yet
78
            const schemaBuilder = this.connection.driver.createSchemaBuilder()
×
79
            if (InstanceChecker.isRdbmsSchemaBuilder(schemaBuilder)) {
×
80
                await schemaBuilder.createMetadataTableIfNecessary(queryRunner)
×
81
            }
82

83
            await queryRunner.beforeMigration()
×
84
            await (migration.instance as any).up(queryRunner)
×
85
            await queryRunner.afterMigration()
×
86
            await this.insertExecutedMigration(queryRunner, migration)
×
87

88
            return migration
×
89
        })
90
    }
91

92
    /**
93
     * Returns an array of all migrations.
94
     */
95
    public async getAllMigrations(): Promise<Migration[]> {
96
        return Promise.resolve(this.getMigrations())
×
97
    }
98

99
    /**
100
     * Returns an array of all executed migrations.
101
     */
102
    public async getExecutedMigrations(): Promise<Migration[]> {
103
        return this.withQueryRunner(async (queryRunner) => {
×
104
            await this.createMigrationsTableIfNotExist(queryRunner)
×
105

106
            return await this.loadExecutedMigrations(queryRunner)
×
107
        })
108
    }
109

110
    /**
111
     * Returns an array of all pending migrations.
112
     */
113
    public async getPendingMigrations(): Promise<Migration[]> {
114
        const allMigrations = await this.getAllMigrations()
×
115
        const executedMigrations = await this.getExecutedMigrations()
×
116

117
        return allMigrations.filter(
×
118
            (migration) =>
119
                !executedMigrations.find(
×
120
                    (executedMigration) =>
121
                        executedMigration.name === migration.name,
×
122
                ),
123
        )
124
    }
125

126
    /**
127
     * Inserts an executed migration.
128
     */
129
    public insertMigration(migration: Migration): Promise<void> {
130
        return this.withQueryRunner((q) =>
×
131
            this.insertExecutedMigration(q, migration),
×
132
        )
133
    }
134

135
    /**
136
     * Deletes an executed migration.
137
     */
138
    public deleteMigration(migration: Migration): Promise<void> {
139
        return this.withQueryRunner((q) =>
×
140
            this.deleteExecutedMigration(q, migration),
×
141
        )
142
    }
143

144
    /**
145
     * Lists all migrations and whether they have been executed or not
146
     * returns true if there are unapplied migrations
147
     */
148
    async showMigrations(): Promise<boolean> {
UNCOV
149
        let hasUnappliedMigrations = false
×
150
        const queryRunner =
UNCOV
151
            this.queryRunner || this.connection.createQueryRunner()
×
152
        // create migrations table if its not created yet
UNCOV
153
        await this.createMigrationsTableIfNotExist(queryRunner)
×
154

155
        // get all migrations that are executed and saved in the database
UNCOV
156
        const executedMigrations = await this.loadExecutedMigrations(
×
157
            queryRunner,
158
        )
159

160
        // get all user's migrations in the source code
UNCOV
161
        const allMigrations = this.getMigrations()
×
162

UNCOV
163
        for (const migration of allMigrations) {
×
UNCOV
164
            const executedMigration = executedMigrations.find(
×
165
                (executedMigration) =>
UNCOV
166
                    executedMigration.name === migration.name,
×
167
            )
168

UNCOV
169
            if (executedMigration) {
×
UNCOV
170
                this.connection.logger.logSchemaBuild(
×
171
                    `[X] ${executedMigration.id} ${migration.name}`,
172
                )
173
            } else {
UNCOV
174
                hasUnappliedMigrations = true
×
UNCOV
175
                this.connection.logger.logSchemaBuild(`[ ] ${migration.name}`)
×
176
            }
177
        }
178

179
        // if query runner was created by us then release it
UNCOV
180
        if (!this.queryRunner) {
×
UNCOV
181
            await queryRunner.release()
×
182
        }
183

UNCOV
184
        return hasUnappliedMigrations
×
185
    }
186

187
    /**
188
     * Executes all pending migrations. Pending migrations are migrations that are not yet executed,
189
     * thus not saved in the database.
190
     */
191
    async executePendingMigrations(): Promise<Migration[]> {
192
        const queryRunner =
193
            this.queryRunner || this.connection.createQueryRunner()
2✔
194
        // create migrations table if it's not created yet
195
        await this.createMigrationsTableIfNotExist(queryRunner)
2✔
196

197
        // create the typeorm_metadata table if it's not created yet
198
        const schemaBuilder = this.connection.driver.createSchemaBuilder()
2✔
199
        if (InstanceChecker.isRdbmsSchemaBuilder(schemaBuilder)) {
2!
UNCOV
200
            await schemaBuilder.createMetadataTableIfNecessary(queryRunner)
×
201
        }
202

203
        // get all migrations that are executed and saved in the database
204
        const executedMigrations = await this.loadExecutedMigrations(
2✔
205
            queryRunner,
206
        )
207

208
        // get the time when last migration was executed
209
        const lastTimeExecutedMigration =
210
            this.getLatestTimestampMigration(executedMigrations)
2✔
211

212
        // get all user's migrations in the source code
213
        const allMigrations = this.getMigrations()
2✔
214

215
        // variable to store all migrations we did successfully
216
        const successMigrations: Migration[] = []
2✔
217

218
        // find all migrations that needs to be executed
219
        const pendingMigrations = allMigrations.filter((migration) => {
2✔
220
            // check if we already have executed migration
221
            const executedMigration = executedMigrations.find(
4✔
222
                (executedMigration) =>
223
                    executedMigration.name === migration.name,
2✔
224
            )
225
            if (executedMigration) return false
4✔
226

227
            // migration is new and not executed. now check if its timestamp is correct
228
            // if (lastTimeExecutedMigration && migration.timestamp < lastTimeExecutedMigration.timestamp)
229
            //     throw new TypeORMError(`New migration found: ${migration.name}, however this migration's timestamp is not valid. Migration's timestamp should not be older then migrations already executed in the database.`);
230

231
            // every check is passed means that migration was not run yet and we need to run it
232
            return true
3✔
233
        })
234

235
        // if no migrations are pending then nothing to do here
236
        if (!pendingMigrations.length) {
2!
UNCOV
237
            this.connection.logger.logSchemaBuild(`No migrations are pending`)
×
238
            // if query runner was created by us then release it
UNCOV
239
            if (!this.queryRunner) await queryRunner.release()
×
UNCOV
240
            return []
×
241
        }
242

243
        // log information about migration execution
244
        this.connection.logger.logSchemaBuild(
2✔
245
            `${executedMigrations.length} migrations are already loaded in the database.`,
246
        )
247
        this.connection.logger.logSchemaBuild(
2✔
248
            `${allMigrations.length} migrations were found in the source code.`,
249
        )
250
        if (lastTimeExecutedMigration)
2✔
251
            this.connection.logger.logSchemaBuild(
1✔
252
                `${
253
                    lastTimeExecutedMigration.name
254
                } is the last executed migration. It was executed on ${new Date(
255
                    lastTimeExecutedMigration.timestamp,
256
                ).toString()}.`,
257
            )
258
        this.connection.logger.logSchemaBuild(
2✔
259
            `${pendingMigrations.length} migrations are new migrations must be executed.`,
260
        )
261

262
        if (this.transaction === "all") {
2✔
263
            // If we desire to run all migrations in a single transaction
264
            // but there is a migration that explicitly overrides the transaction mode
265
            // then we have to fail since we cannot properly resolve that intent
266
            // In theory we could support overrides that are set to `true`,
267
            // however to keep the interface more rigid, we fail those too
268
            const migrationsOverridingTransactionMode =
269
                pendingMigrations.filter(
2✔
270
                    (migration) =>
271
                        !(migration.instance?.transaction === undefined),
3✔
272
                )
273

274
            if (migrationsOverridingTransactionMode.length > 0) {
2!
UNCOV
275
                const error = new ForbiddenTransactionModeOverrideError(
×
276
                    migrationsOverridingTransactionMode,
277
                )
UNCOV
278
                this.connection.logger.logMigration(
×
279
                    `Migrations failed, error: ${error.message}`,
280
                )
UNCOV
281
                throw error
×
282
            }
283
        }
284

285
        // Set the per-migration defaults for the transaction mode
286
        // so that we have one centralized place that controls this behavior
287

288
        // When transaction mode is `each` the default is to run in a transaction
289
        // When transaction mode is `none` the default is to not run in a transaction
290
        // When transaction mode is `all` the default is to not run in a transaction
291
        // since all the migrations are already running in one single transaction
292

293
        const txModeDefault = {
2✔
294
            each: true,
295
            none: false,
296
            all: false,
297
        }[this.transaction]
298

299
        for (const migration of pendingMigrations) {
2✔
300
            if (migration.instance) {
3✔
301
                const instanceTx = migration.instance.transaction
3✔
302

303
                if (instanceTx === undefined) {
3!
304
                    migration.transaction = txModeDefault
3✔
305
                } else {
UNCOV
306
                    migration.transaction = instanceTx
×
307
                }
308
            }
309
        }
310

311
        // start transaction if its not started yet
312
        let transactionStartedByUs = false
2✔
313
        if (this.transaction === "all" && !queryRunner.isTransactionActive) {
2✔
314
            await queryRunner.beforeMigration()
2✔
315
            await queryRunner.startTransaction()
2✔
316
            transactionStartedByUs = true
2✔
317
        }
318

319
        // run all pending migrations in a sequence
320
        try {
2✔
321
            for (const migration of pendingMigrations) {
2✔
322
                if (this.fake) {
3!
323
                    // directly insert migration record into the database if it is fake
UNCOV
324
                    await this.insertExecutedMigration(queryRunner, migration)
×
325

326
                    // nothing else needs to be done, continue to next migration
UNCOV
327
                    continue
×
328
                }
329

330
                if (migration.transaction && !queryRunner.isTransactionActive) {
3!
UNCOV
331
                    await queryRunner.beforeMigration()
×
UNCOV
332
                    await queryRunner.startTransaction()
×
UNCOV
333
                    transactionStartedByUs = true
×
334
                }
335

336
                await migration
3✔
337
                    .instance!.up(queryRunner)
338
                    .catch((error) => {
339
                        // informative log about migration failure
UNCOV
340
                        this.connection.logger.logMigration(
×
341
                            `Migration "${migration.name}" failed, error: ${error?.message}`,
342
                        )
UNCOV
343
                        throw error
×
344
                    })
345
                    .then(async () => {
346
                        // now when migration is executed we need to insert record about it into the database
347
                        await this.insertExecutedMigration(
3✔
348
                            queryRunner,
349
                            migration,
350
                        )
351
                        // commit transaction if we started it
352
                        if (migration.transaction && transactionStartedByUs) {
3!
UNCOV
353
                            await queryRunner.commitTransaction()
×
UNCOV
354
                            await queryRunner.afterMigration()
×
355
                        }
356
                    })
357
                    .then(() => {
358
                        // informative log about migration success
359
                        successMigrations.push(migration)
3✔
360
                        this.connection.logger.logSchemaBuild(
3✔
361
                            `Migration ${migration.name} has been ${
362
                                this.fake ? "(fake) " : ""
3!
363
                            }executed successfully.`,
364
                        )
365
                    })
366
            }
367

368
            // commit transaction if we started it
369
            if (this.transaction === "all" && transactionStartedByUs) {
2✔
370
                await queryRunner.commitTransaction()
2✔
371
                await queryRunner.afterMigration()
2✔
372
            }
373
        } catch (err) {
374
            // rollback transaction if we started it
UNCOV
375
            if (transactionStartedByUs) {
×
UNCOV
376
                try {
×
377
                    // we throw original error even if rollback thrown an error
UNCOV
378
                    await queryRunner.rollbackTransaction()
×
379
                } catch (rollbackError) {}
380
            }
381

UNCOV
382
            throw err
×
383
        } finally {
384
            // if query runner was created by us then release it
385
            if (!this.queryRunner) await queryRunner.release()
2✔
386
        }
387
        return successMigrations
2✔
388
    }
389

390
    /**
391
     * Reverts last migration that were run.
392
     */
393
    async undoLastMigration(): Promise<void> {
394
        const queryRunner =
395
            this.queryRunner || this.connection.createQueryRunner()
1✔
396

397
        // create migrations table if it's not created yet
398
        await this.createMigrationsTableIfNotExist(queryRunner)
1✔
399

400
        // create typeorm_metadata table if it's not created yet
401
        const schemaBuilder = this.connection.driver.createSchemaBuilder()
1✔
402
        if (InstanceChecker.isRdbmsSchemaBuilder(schemaBuilder)) {
1!
UNCOV
403
            await schemaBuilder.createMetadataTableIfNecessary(queryRunner)
×
404
        }
405

406
        // get all migrations that are executed and saved in the database
407
        const executedMigrations = await this.loadExecutedMigrations(
1✔
408
            queryRunner,
409
        )
410

411
        // get the time when last migration was executed
412
        const lastTimeExecutedMigration =
413
            this.getLatestExecutedMigration(executedMigrations)
1✔
414

415
        // if no migrations found in the database then nothing to revert
416
        if (!lastTimeExecutedMigration) {
1!
417
            this.connection.logger.logSchemaBuild(
×
418
                `No migrations were found in the database. Nothing to revert!`,
419
            )
420
            return
×
421
        }
422

423
        // get all user's migrations in the source code
424
        const allMigrations = this.getMigrations()
1✔
425

426
        // find the instance of the migration we need to remove
427
        const migrationToRevert = allMigrations.find(
1✔
428
            (migration) => migration.name === lastTimeExecutedMigration!.name,
2✔
429
        )
430

431
        // if no migrations found in the database then nothing to revert
432
        if (!migrationToRevert)
1!
433
            throw new TypeORMError(
×
434
                `No migration ${lastTimeExecutedMigration.name} was found in the source code. Make sure you have this migration in your codebase and its included in the connection options.`,
435
            )
436

437
        // log information about migration execution
438
        this.connection.logger.logSchemaBuild(
1✔
439
            `${executedMigrations.length} migrations are already loaded in the database.`,
440
        )
441
        this.connection.logger.logSchemaBuild(
1✔
442
            `${
443
                lastTimeExecutedMigration.name
444
            } is the last executed migration. It was executed on ${new Date(
445
                lastTimeExecutedMigration.timestamp,
446
            ).toString()}.`,
447
        )
448
        this.connection.logger.logSchemaBuild(`Now reverting it...`)
1✔
449

450
        // start transaction if its not started yet
451
        let transactionStartedByUs = false
1✔
452
        if (this.transaction !== "none" && !queryRunner.isTransactionActive) {
1✔
453
            await queryRunner.startTransaction()
1✔
454
            transactionStartedByUs = true
1✔
455
        }
456

457
        try {
1✔
458
            if (!this.fake) {
1✔
459
                await queryRunner.beforeMigration()
1✔
460
                await migrationToRevert.instance!.down(queryRunner)
1✔
461
                await queryRunner.afterMigration()
1✔
462
            }
463

464
            await this.deleteExecutedMigration(queryRunner, migrationToRevert)
1✔
465
            this.connection.logger.logSchemaBuild(
1✔
466
                `Migration ${migrationToRevert.name} has been ${
467
                    this.fake ? "(fake) " : ""
1!
468
                }reverted successfully.`,
469
            )
470

471
            // commit transaction if we started it
472
            if (transactionStartedByUs) await queryRunner.commitTransaction()
1✔
473
        } catch (err) {
474
            // rollback transaction if we started it
UNCOV
475
            if (transactionStartedByUs) {
×
UNCOV
476
                try {
×
477
                    // we throw original error even if rollback thrown an error
UNCOV
478
                    await queryRunner.rollbackTransaction()
×
479
                } catch (rollbackError) {}
480
            }
481

UNCOV
482
            throw err
×
483
        } finally {
484
            // if query runner was created by us then release it
485
            if (!this.queryRunner) await queryRunner.release()
1✔
486
        }
487
    }
488

489
    // -------------------------------------------------------------------------
490
    // Protected Methods
491
    // -------------------------------------------------------------------------
492

493
    /**
494
     * Creates table "migrations" that will store information about executed migrations.
495
     */
496
    protected async createMigrationsTableIfNotExist(
497
        queryRunner: QueryRunner,
498
    ): Promise<void> {
499
        // If driver is mongo no need to create
500
        if (this.connection.driver.options.type === "mongodb") {
3✔
501
            return
3✔
502
        }
UNCOV
503
        const tableExist = await queryRunner.hasTable(this.migrationsTable) // todo: table name should be configurable
×
UNCOV
504
        if (!tableExist) {
×
UNCOV
505
            await queryRunner.createTable(
×
506
                new Table({
507
                    database: this.migrationsDatabase,
508
                    schema: this.migrationsSchema,
509
                    name: this.migrationsTable,
510
                    columns: [
511
                        {
512
                            name: "id",
513
                            type: this.connection.driver.normalizeType({
514
                                type: this.connection.driver.mappedDataTypes
515
                                    .migrationId,
516
                            }),
517
                            isGenerated: true,
518
                            generationStrategy: "increment",
519
                            isPrimary: true,
520
                            isNullable: false,
521
                        },
522
                        {
523
                            name: "timestamp",
524
                            type: this.connection.driver.normalizeType({
525
                                type: this.connection.driver.mappedDataTypes
526
                                    .migrationTimestamp,
527
                            }),
528
                            isPrimary: false,
529
                            isNullable: false,
530
                        },
531
                        {
532
                            name: "name",
533
                            type: this.connection.driver.normalizeType({
534
                                type: this.connection.driver.mappedDataTypes
535
                                    .migrationName,
536
                            }),
537
                            isNullable: false,
538
                        },
539
                    ],
540
                }),
541
            )
542
        }
543
    }
544

545
    /**
546
     * Loads all migrations that were executed and saved into the database (sorts by id).
547
     */
548
    protected async loadExecutedMigrations(
549
        queryRunner: QueryRunner,
550
    ): Promise<Migration[]> {
551
        if (this.connection.driver.options.type === "mongodb") {
3!
552
            const mongoRunner = queryRunner as MongoQueryRunner
3✔
553
            return mongoRunner
3✔
554
                .cursor(this.migrationsTableName, {})
555
                .sort({ _id: -1 })
556
                .toArray()
557
        } else {
UNCOV
558
            const migrationsRaw: ObjectLiteral[] = await this.connection.manager
×
559
                .createQueryBuilder(queryRunner)
560
                .select()
561
                .orderBy(this.connection.driver.escape("id"), "DESC")
562
                .from(this.migrationsTable, this.migrationsTableName)
563
                .getRawMany()
UNCOV
564
            return migrationsRaw.map((migrationRaw) => {
×
UNCOV
565
                return new Migration(
×
566
                    parseInt(migrationRaw["id"]),
567
                    parseInt(migrationRaw["timestamp"]),
568
                    migrationRaw["name"],
569
                )
570
            })
571
        }
572
    }
573

574
    /**
575
     * Gets all migrations that setup for this connection.
576
     */
577
    protected getMigrations(): Migration[] {
578
        const migrations = this.connection.migrations.map((migration) => {
3✔
579
            const migrationClassName =
580
                migration.name || (migration.constructor as any).name
6✔
581
            const migrationTimestamp = parseInt(
6✔
582
                migrationClassName.substr(-13),
583
                10,
584
            )
585
            if (!migrationTimestamp || isNaN(migrationTimestamp)) {
6!
586
                throw new TypeORMError(
×
587
                    `${migrationClassName} migration name is wrong. Migration class name should have a JavaScript timestamp appended.`,
588
                )
589
            }
590

591
            return new Migration(
6✔
592
                undefined,
593
                migrationTimestamp,
594
                migrationClassName,
595
                migration,
596
            )
597
        })
598

599
        this.checkForDuplicateMigrations(migrations)
3✔
600

601
        // sort them by timestamp
602
        return migrations.sort((a, b) => a.timestamp - b.timestamp)
3✔
603
    }
604

605
    protected checkForDuplicateMigrations(migrations: Migration[]) {
606
        const migrationNames = migrations.map((migration) => migration.name)
6✔
607
        const duplicates = Array.from(
3✔
608
            new Set(
609
                migrationNames.filter(
610
                    (migrationName, index) =>
611
                        migrationNames.indexOf(migrationName) < index,
6✔
612
                ),
613
            ),
614
        )
615
        if (duplicates.length > 0) {
3!
UNCOV
616
            throw Error(`Duplicate migrations: ${duplicates.join(", ")}`)
×
617
        }
618
    }
619

620
    /**
621
     * Finds the latest migration (sorts by timestamp) in the given array of migrations.
622
     */
623
    protected getLatestTimestampMigration(
624
        migrations: Migration[],
625
    ): Migration | undefined {
626
        const sortedMigrations = migrations
2✔
627
            .map((migration) => migration)
1✔
628
            .sort((a, b) => (a.timestamp - b.timestamp) * -1)
×
629
        return sortedMigrations.length > 0 ? sortedMigrations[0] : undefined
2✔
630
    }
631

632
    /**
633
     * Finds the latest migration in the given array of migrations.
634
     * PRE: Migration array must be sorted by descending id.
635
     */
636
    protected getLatestExecutedMigration(
637
        sortedMigrations: Migration[],
638
    ): Migration | undefined {
639
        return sortedMigrations.length > 0 ? sortedMigrations[0] : undefined
1!
640
    }
641

642
    /**
643
     * Inserts new executed migration's data into migrations table.
644
     */
645
    protected async insertExecutedMigration(
646
        queryRunner: QueryRunner,
647
        migration: Migration,
648
    ): Promise<void> {
649
        const values: ObjectLiteral = {}
3✔
650
        if (this.connection.driver.options.type === "mssql") {
3!
UNCOV
651
            values["timestamp"] = new MssqlParameter(
×
652
                migration.timestamp,
653
                this.connection.driver.normalizeType({
654
                    type: this.connection.driver.mappedDataTypes
655
                        .migrationTimestamp,
656
                }) as any,
657
            )
UNCOV
658
            values["name"] = new MssqlParameter(
×
659
                migration.name,
660
                this.connection.driver.normalizeType({
661
                    type: this.connection.driver.mappedDataTypes.migrationName,
662
                }) as any,
663
            )
664
        } else {
665
            values["timestamp"] = migration.timestamp
3✔
666
            values["name"] = migration.name
3✔
667
        }
668
        if (this.connection.driver.options.type === "mongodb") {
3!
669
            const mongoRunner = queryRunner as MongoQueryRunner
3✔
670
            await mongoRunner.databaseConnection
3✔
671
                .db(this.connection.driver.database!)
672
                .collection(this.migrationsTableName)
673
                .insertOne(values)
674
        } else {
UNCOV
675
            const qb = queryRunner.manager.createQueryBuilder()
×
UNCOV
676
            await qb
×
677
                .insert()
678
                .into(this.migrationsTable)
679
                .values(values)
680
                .execute()
681
        }
682
    }
683

684
    /**
685
     * Delete previously executed migration's data from the migrations table.
686
     */
687
    protected async deleteExecutedMigration(
688
        queryRunner: QueryRunner,
689
        migration: Migration,
690
    ): Promise<void> {
691
        const conditions: ObjectLiteral = {}
1✔
692
        if (this.connection.driver.options.type === "mssql") {
1!
UNCOV
693
            conditions["timestamp"] = new MssqlParameter(
×
694
                migration.timestamp,
695
                this.connection.driver.normalizeType({
696
                    type: this.connection.driver.mappedDataTypes
697
                        .migrationTimestamp,
698
                }) as any,
699
            )
UNCOV
700
            conditions["name"] = new MssqlParameter(
×
701
                migration.name,
702
                this.connection.driver.normalizeType({
703
                    type: this.connection.driver.mappedDataTypes.migrationName,
704
                }) as any,
705
            )
706
        } else {
707
            conditions["timestamp"] = migration.timestamp
1✔
708
            conditions["name"] = migration.name
1✔
709
        }
710

711
        if (this.connection.driver.options.type === "mongodb") {
1!
712
            const mongoRunner = queryRunner as MongoQueryRunner
1✔
713
            await mongoRunner.databaseConnection
1✔
714
                .db(this.connection.driver.database!)
715
                .collection(this.migrationsTableName)
716
                .deleteOne(conditions)
717
        } else {
UNCOV
718
            const qb = queryRunner.manager.createQueryBuilder()
×
UNCOV
719
            await qb
×
720
                .delete()
721
                .from(this.migrationsTable)
722
                .where(`${qb.escape("timestamp")} = :timestamp`)
723
                .andWhere(`${qb.escape("name")} = :name`)
724
                .setParameters(conditions)
725
                .execute()
726
        }
727
    }
728

729
    protected async withQueryRunner<T extends any>(
730
        callback: (queryRunner: QueryRunner) => T | Promise<T>,
731
    ) {
732
        const queryRunner =
733
            this.queryRunner || this.connection.createQueryRunner()
×
734

735
        try {
×
736
            return await callback(queryRunner)
×
737
        } finally {
738
            if (!this.queryRunner) {
×
739
                await queryRunner.release()
×
740
            }
741
        }
742
    }
743
}
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