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

typeorm / typeorm / 17898222693

21 Sep 2025 07:52PM UTC coverage: 76.421% (-0.006%) from 76.427%
17898222693

Pull #11672

github

web-flow
Merge 8cea6475d into d4f7b44fd
Pull Request #11672: fix: getPendingMigrations unnecessarily creating migrations table

9397 of 12988 branches covered (72.35%)

Branch coverage included in aggregate %.

0 of 4 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

19098 of 24299 relevant lines covered (78.6%)

140874.02 hits per line

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

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

11
/**
12
 * Executes migrations: runs pending and reverts previously executed migrations.
13
 */
14
export class MigrationExecutor {
26✔
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"
333✔
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,
333✔
51
        protected queryRunner?: QueryRunner,
333✔
52
    ) {
53
        const { schema } = this.connection.driver.options as any
333✔
54
        const database = this.connection.driver.database
333✔
55
        this.migrationsDatabase = database
333✔
56
        this.migrationsSchema = schema
333✔
57
        this.migrationsTableName =
333✔
58
            connection.options.migrationsTableName || "migrations"
666✔
59
        this.migrationsTable = this.connection.driver.buildTableName(
333✔
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 executed migrations.
94
     */
95
    public async getExecutedMigrations(): Promise<Migration[]> {
96
        return this.withQueryRunner(async (queryRunner) => {
×
NEW
97
            const exist = await queryRunner.hasTable(this.migrationsTable)
×
98

NEW
99
            if (!exist) return []
×
100

101
            return await this.loadExecutedMigrations(queryRunner)
×
102
        })
103
    }
104

105
    /**
106
     * Returns an array of all pending migrations.
107
     */
108
    public async getPendingMigrations(): Promise<Migration[]> {
NEW
109
        const allMigrations = this.getMigrations()
×
110
        const executedMigrations = await this.getExecutedMigrations()
×
111

NEW
112
        if (executedMigrations.length === 0) return allMigrations
×
113

UNCOV
114
        return allMigrations.filter(
×
115
            (migration) =>
116
                !executedMigrations.find(
×
117
                    (executedMigration) =>
118
                        executedMigration.name === migration.name,
×
119
                ),
120
        )
121
    }
122

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

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

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

152
        // get all migrations that are executed and saved in the database
153
        const executedMigrations = await this.loadExecutedMigrations(
54✔
154
            queryRunner,
155
        )
156

157
        // get all user's migrations in the source code
158
        const allMigrations = this.getMigrations()
54✔
159

160
        for (const migration of allMigrations) {
54✔
161
            const executedMigration = executedMigrations.find(
68✔
162
                (executedMigration) =>
163
                    executedMigration.name === migration.name,
27✔
164
            )
165

166
            if (executedMigration) {
68✔
167
                this.connection.logger.logSchemaBuild(
20✔
168
                    `[X] ${executedMigration.id} ${migration.name}`,
169
                )
170
            } else {
171
                hasUnappliedMigrations = true
48✔
172
                this.connection.logger.logSchemaBuild(`[ ] ${migration.name}`)
48✔
173
            }
174
        }
175

176
        // if query runner was created by us then release it
177
        if (!this.queryRunner) {
54✔
178
            await queryRunner.release()
54✔
179
        }
180

181
        return hasUnappliedMigrations
54✔
182
    }
183

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

194
        // create the typeorm_metadata table if it's not created yet
195
        const schemaBuilder = this.connection.driver.createSchemaBuilder()
221✔
196
        if (InstanceChecker.isRdbmsSchemaBuilder(schemaBuilder)) {
221✔
197
            await schemaBuilder.createMetadataTableIfNecessary(queryRunner)
217✔
198
        }
199

200
        // get all migrations that are executed and saved in the database
201
        const executedMigrations = await this.loadExecutedMigrations(
221✔
202
            queryRunner,
203
        )
204

205
        // get the time when last migration was executed
206
        const lastTimeExecutedMigration =
207
            this.getLatestTimestampMigration(executedMigrations)
221✔
208

209
        // get all user's migrations in the source code
210
        const allMigrations = this.getMigrations()
221✔
211

212
        // variable to store all migrations we did successfully
213
        const successMigrations: Migration[] = []
217✔
214

215
        // find all migrations that needs to be executed
216
        const pendingMigrations = allMigrations.filter((migration) => {
217✔
217
            // check if we already have executed migration
218
            const executedMigration = executedMigrations.find(
264✔
219
                (executedMigration) =>
220
                    executedMigration.name === migration.name,
32✔
221
            )
222
            if (executedMigration) return false
264✔
223

224
            // migration is new and not executed. now check if its timestamp is correct
225
            // if (lastTimeExecutedMigration && migration.timestamp < lastTimeExecutedMigration.timestamp)
226
            //     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.`);
227

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

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

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

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

271
            if (migrationsOverridingTransactionMode.length > 0) {
161✔
272
                const error = new ForbiddenTransactionModeOverrideError(
4✔
273
                    migrationsOverridingTransactionMode,
274
                )
275
                this.connection.logger.logMigration(
4✔
276
                    `Migrations failed, error: ${error.message}`,
277
                )
278
                throw error
4✔
279
            }
280
        }
281

282
        // Set the per-migration defaults for the transaction mode
283
        // so that we have one centralized place that controls this behavior
284

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

290
        const txModeDefault = {
169✔
291
            each: true,
292
            none: false,
293
            all: false,
294
        }[this.transaction]
295

296
        for (const migration of pendingMigrations) {
169✔
297
            if (migration.instance) {
222✔
298
                const instanceTx = migration.instance.transaction
222✔
299

300
                if (instanceTx === undefined) {
222✔
301
                    migration.transaction = txModeDefault
206✔
302
                } else {
303
                    migration.transaction = instanceTx
16✔
304
                }
305
            }
306
        }
307

308
        // start transaction if its not started yet
309
        let transactionStartedByUs = false
169✔
310
        if (this.transaction === "all" && !queryRunner.isTransactionActive) {
169✔
311
            await queryRunner.beforeMigration()
157✔
312
            await queryRunner.startTransaction()
157✔
313
            transactionStartedByUs = true
157✔
314
        }
315

316
        // run all pending migrations in a sequence
317
        try {
169✔
318
            for (const migration of pendingMigrations) {
169✔
319
                if (this.fake) {
222✔
320
                    // directly insert migration record into the database if it is fake
321
                    await this.insertExecutedMigration(queryRunner, migration)
28✔
322

323
                    // nothing else needs to be done, continue to next migration
324
                    continue
28✔
325
                }
326

327
                if (migration.transaction && !queryRunner.isTransactionActive) {
194✔
328
                    await queryRunner.beforeMigration()
24✔
329
                    await queryRunner.startTransaction()
24✔
330
                    transactionStartedByUs = true
24✔
331
                }
332

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

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

379
            throw err
60✔
380
        } finally {
381
            // if query runner was created by us then release it
382
            if (!this.queryRunner) await queryRunner.release()
169✔
383
        }
384
        return successMigrations
109✔
385
    }
386

387
    /**
388
     * Reverts last migration that were run.
389
     */
390
    async undoLastMigration(): Promise<void> {
391
        const queryRunner =
392
            this.queryRunner || this.connection.createQueryRunner()
58✔
393

394
        // create migrations table if it's not created yet
395
        await this.createMigrationsTableIfNotExist(queryRunner)
58✔
396

397
        // create typeorm_metadata table if it's not created yet
398
        const schemaBuilder = this.connection.driver.createSchemaBuilder()
58✔
399
        if (InstanceChecker.isRdbmsSchemaBuilder(schemaBuilder)) {
58✔
400
            await schemaBuilder.createMetadataTableIfNecessary(queryRunner)
56✔
401
        }
402

403
        // get all migrations that are executed and saved in the database
404
        const executedMigrations = await this.loadExecutedMigrations(
58✔
405
            queryRunner,
406
        )
407

408
        // get the time when last migration was executed
409
        const lastTimeExecutedMigration =
410
            this.getLatestExecutedMigration(executedMigrations)
58✔
411

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

420
        // get all user's migrations in the source code
421
        const allMigrations = this.getMigrations()
58✔
422

423
        // find the instance of the migration we need to remove
424
        const migrationToRevert = allMigrations.find(
58✔
425
            (migration) => migration.name === lastTimeExecutedMigration!.name,
60✔
426
        )
427

428
        // if no migrations found in the database then nothing to revert
429
        if (!migrationToRevert)
58!
430
            throw new TypeORMError(
×
431
                `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.`,
432
            )
433

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

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

454
        try {
58✔
455
            if (!this.fake) {
58✔
456
                await queryRunner.beforeMigration()
30✔
457
                await migrationToRevert.instance!.down(queryRunner)
30✔
458
                await queryRunner.afterMigration()
2✔
459
            }
460

461
            await this.deleteExecutedMigration(queryRunner, migrationToRevert)
30✔
462
            this.connection.logger.logSchemaBuild(
30✔
463
                `Migration ${migrationToRevert.name} has been ${
464
                    this.fake ? "(fake) " : ""
30✔
465
                }reverted successfully.`,
466
            )
467

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

479
            throw err
28✔
480
        } finally {
481
            // if query runner was created by us then release it
482
            if (!this.queryRunner) await queryRunner.release()
58✔
483
        }
484
    }
485

486
    // -------------------------------------------------------------------------
487
    // Protected Methods
488
    // -------------------------------------------------------------------------
489

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

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

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

588
            return new Migration(
404✔
589
                undefined,
590
                migrationTimestamp,
591
                migrationClassName,
592
                migration,
593
            )
594
        })
595

596
        this.checkForDuplicateMigrations(migrations)
333✔
597

598
        // sort them by timestamp
599
        return migrations.sort((a, b) => a.timestamp - b.timestamp)
329✔
600
    }
601

602
    protected checkForDuplicateMigrations(migrations: Migration[]) {
603
        const migrationNames = migrations.map((migration) => migration.name)
413✔
604
        const duplicates = Array.from(
333✔
605
            new Set(
606
                migrationNames.filter(
607
                    (migrationName, index) =>
608
                        migrationNames.indexOf(migrationName) < index,
404✔
609
                ),
610
            ),
611
        )
612
        if (duplicates.length > 0) {
333✔
613
            throw Error(`Duplicate migrations: ${duplicates.join(", ")}`)
4✔
614
        }
615
    }
616

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

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

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

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

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

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

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