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

typeorm / typeorm / 20153290793

12 Dec 2025 01:29AM UTC coverage: 80.807% (+0.04%) from 80.764%
20153290793

push

github

alumni
refactor(mysql)!: drop support for mysql package and default to mysql2 (#11766)

Co-authored-by: Lucian Mocanu <alumni@users.noreply.github.com>

26912 of 32666 branches covered (82.39%)

Branch coverage included in aggregate %.

13 of 15 new or added lines in 2 files covered. (86.67%)

694 existing lines in 23 files now uncovered.

91350 of 113685 relevant lines covered (80.35%)

68942.63 hits per line

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

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

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

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

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

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

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

26✔
45
    // -------------------------------------------------------------------------
26✔
46
    // Constructor
26✔
47
    // -------------------------------------------------------------------------
26✔
48

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

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

26✔
70
    /**
26✔
71
     * Tries to execute a single migration given.
26✔
72
     */
26✔
73
    public async executeMigration(migration: Migration): Promise<Migration> {
26✔
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

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

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

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

26✔
110
    /**
26✔
111
     * Returns an array of all pending migrations.
26✔
112
     */
26✔
113
    public async getPendingMigrations(): Promise<Migration[]> {
26✔
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

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

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

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

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

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

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

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

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

54✔
183
        return hasUnappliedMigrations
54✔
184
    }
54✔
185

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

60✔
412
        // if no migrations found in the database then nothing to revert
60✔
413
        if (!lastTimeExecutedMigration) {
60!
414
            this.connection.logger.logSchemaBuild(
2✔
415
                `No migrations were found in the database. Nothing to revert!`,
2✔
416
            )
2✔
417
            // if query runner was created by us then release it
2✔
418
            if (!this.queryRunner) await queryRunner.release()
2✔
419
            return
2✔
420
        }
2✔
421

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

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

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

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

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

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

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

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

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

26✔
488
    // -------------------------------------------------------------------------
26✔
489
    // Protected Methods
26✔
490
    // -------------------------------------------------------------------------
26✔
491

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

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

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

404✔
590
            return new Migration(
404✔
591
                undefined,
404✔
592
                migrationTimestamp,
404✔
593
                migrationClassName,
404✔
594
                migration,
404✔
595
            )
404✔
596
        })
333✔
597

333✔
598
        this.checkForDuplicateMigrations(migrations)
333✔
599

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

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

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

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

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

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

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

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

×
734
        try {
×
735
            return await callback(queryRunner)
×
736
        } finally {
×
737
            if (!this.queryRunner) {
×
738
                await queryRunner.release()
×
739
            }
×
740
        }
×
741
    }
×
742
}
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc