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

typeorm / typeorm / 14796576772

02 May 2025 01:52PM UTC coverage: 45.367% (-30.9%) from 76.309%
14796576772

Pull #11434

github

web-flow
Merge ec4ce2d00 into fadad1a74
Pull Request #11434: feat: release PR releases using pkg.pr.new

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

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

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

11
/**
12
 * Executes migrations: runs pending and reverts previously executed migrations.
13
 */
14
export class MigrationExecutor {
4✔
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"
45✔
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,
45✔
51
        protected queryRunner?: QueryRunner,
45✔
52
    ) {
53
        const { schema } = this.connection.driver.options as any
45✔
54
        const database = this.connection.driver.database
45✔
55
        this.migrationsDatabase = database
45✔
56
        this.migrationsSchema = schema
45✔
57
        this.migrationsTableName =
45✔
58
            connection.options.migrationsTableName || "migrations"
90✔
59
        this.migrationsTable = this.connection.driver.buildTableName(
45✔
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> {
149
        let hasUnappliedMigrations = false
10✔
150
        const queryRunner =
151
            this.queryRunner || this.connection.createQueryRunner()
10✔
152
        // create migrations table if its not created yet
153
        await this.createMigrationsTableIfNotExist(queryRunner)
10✔
154

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

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

163
        for (const migration of allMigrations) {
10✔
164
            const executedMigration = executedMigrations.find(
12✔
165
                (executedMigration) =>
166
                    executedMigration.name === migration.name,
5✔
167
            )
168

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

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

184
        return hasUnappliedMigrations
10✔
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()
27✔
194
        // create migrations table if it's not created yet
195
        await this.createMigrationsTableIfNotExist(queryRunner)
27✔
196

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

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

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

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

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

218
        // find all migrations that needs to be executed
219
        const pendingMigrations = allMigrations.filter((migration) => {
27✔
220
            // check if we already have executed migration
221
            const executedMigration = executedMigrations.find(
30✔
222
                (executedMigration) =>
223
                    executedMigration.name === migration.name,
4✔
224
            )
225
            if (executedMigration) return false
30✔
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
26✔
233
        })
234

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

243
        // log information about migration execution
244
        this.connection.logger.logSchemaBuild(
21✔
245
            `${executedMigrations.length} migrations are already loaded in the database.`,
246
        )
247
        this.connection.logger.logSchemaBuild(
21✔
248
            `${allMigrations.length} migrations were found in the source code.`,
249
        )
250
        if (lastTimeExecutedMigration)
21!
251
            this.connection.logger.logSchemaBuild(
×
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(
21✔
259
            `${pendingMigrations.length} migrations are new migrations must be executed.`,
260
        )
261

262
        if (this.transaction === "all") {
21✔
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(
21✔
270
                    (migration) =>
271
                        !(migration.instance?.transaction === undefined),
26✔
272
                )
273

274
            if (migrationsOverridingTransactionMode.length > 0) {
21!
275
                const error = new ForbiddenTransactionModeOverrideError(
×
276
                    migrationsOverridingTransactionMode,
277
                )
278
                this.connection.logger.logMigration(
×
279
                    `Migrations failed, error: ${error.message}`,
280
                )
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 = {
21✔
294
            each: true,
295
            none: false,
296
            all: false,
297
        }[this.transaction]
298

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

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

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

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

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

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

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

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

382
            throw err
8✔
383
        } finally {
384
            // if query runner was created by us then release it
385
            if (!this.queryRunner) await queryRunner.release()
21✔
386
        }
387
        return successMigrations
13✔
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()
8✔
396

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

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

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

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

415
        // if no migrations found in the database then nothing to revert
416
        if (!lastTimeExecutedMigration) {
8!
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()
8✔
425

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

431
        // if no migrations found in the database then nothing to revert
432
        if (!migrationToRevert)
8!
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(
8✔
439
            `${executedMigrations.length} migrations are already loaded in the database.`,
440
        )
441
        this.connection.logger.logSchemaBuild(
8✔
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...`)
8✔
449

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

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

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

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

482
            throw err
4✔
483
        } finally {
484
            // if query runner was created by us then release it
485
            if (!this.queryRunner) await queryRunner.release()
8✔
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") {
45!
501
            return
×
502
        }
503
        const tableExist = await queryRunner.hasTable(this.migrationsTable) // todo: table name should be configurable
45✔
504
        if (!tableExist) {
45✔
505
            await queryRunner.createTable(
20✔
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") {
45!
552
            const mongoRunner = queryRunner as MongoQueryRunner
×
553
            return mongoRunner
×
554
                .cursor(this.migrationsTableName, {})
555
                .sort({ _id: -1 })
556
                .toArray()
557
        } else {
558
            const migrationsRaw: ObjectLiteral[] = await this.connection.manager
45✔
559
                .createQueryBuilder(queryRunner)
560
                .select()
561
                .orderBy(this.connection.driver.escape("id"), "DESC")
562
                .from(this.migrationsTable, this.migrationsTableName)
563
                .getRawMany()
564
            return migrationsRaw.map((migrationRaw) => {
45✔
565
                return new Migration(
16✔
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) => {
45✔
579
            const migrationClassName =
580
                migration.name || (migration.constructor as any).name
50✔
581
            const migrationTimestamp = parseInt(
50✔
582
                migrationClassName.substr(-13),
583
                10,
584
            )
585
            if (!migrationTimestamp || isNaN(migrationTimestamp)) {
50!
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(
50✔
592
                undefined,
593
                migrationTimestamp,
594
                migrationClassName,
595
                migration,
596
            )
597
        })
598

599
        this.checkForDuplicateMigrations(migrations)
45✔
600

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

605
    protected checkForDuplicateMigrations(migrations: Migration[]) {
606
        const migrationNames = migrations.map((migration) => migration.name)
51✔
607
        const duplicates = Array.from(
45✔
608
            new Set(
609
                migrationNames.filter(
610
                    (migrationName, index) =>
611
                        migrationNames.indexOf(migrationName) < index,
50✔
612
                ),
613
            ),
614
        )
615
        if (duplicates.length > 0) {
45!
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
27✔
627
            .map((migration) => migration)
4✔
628
            .sort((a, b) => (a.timestamp - b.timestamp) * -1)
×
629
        return sortedMigrations.length > 0 ? sortedMigrations[0] : undefined
27✔
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
8!
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 = {}
18✔
650
        if (this.connection.driver.options.type === "mssql") {
18!
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
            )
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
18✔
666
            values["name"] = migration.name
18✔
667
        }
668
        if (this.connection.driver.options.type === "mongodb") {
18!
669
            const mongoRunner = queryRunner as MongoQueryRunner
×
670
            await mongoRunner.databaseConnection
×
671
                .db(this.connection.driver.database!)
672
                .collection(this.migrationsTableName)
673
                .insertOne(values)
674
        } else {
675
            const qb = queryRunner.manager.createQueryBuilder()
18✔
676
            await qb
18✔
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 = {}
4✔
692
        if (this.connection.driver.options.type === "mssql") {
4!
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
            )
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
4✔
708
            conditions["name"] = migration.name
4✔
709
        }
710

711
        if (this.connection.driver.options.type === "mongodb") {
4!
712
            const mongoRunner = queryRunner as MongoQueryRunner
×
713
            await mongoRunner.databaseConnection
×
714
                .db(this.connection.driver.database!)
715
                .collection(this.migrationsTableName)
716
                .deleteOne(conditions)
717
        } else {
718
            const qb = queryRunner.manager.createQueryBuilder()
4✔
719
            await qb
4✔
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