innomize-typeorm
Version:
Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, MongoDB databases.
456 lines (454 loc) • 26.3 kB
JavaScript
import * as tslib_1 from "tslib";
import { Table } from "../schema-builder/table/Table";
import { Migration } from "./Migration";
import { PromiseUtils } from "../util/PromiseUtils";
import { SqlServerDriver } from "../driver/sqlserver/SqlServerDriver";
import { MssqlParameter } from "../driver/sqlserver/MssqlParameter";
import { MongoDriver } from "../driver/mongodb/MongoDriver";
/**
* Executes migrations: runs pending and reverts previously executed migrations.
*/
var MigrationExecutor = /** @class */ (function () {
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
function MigrationExecutor(connection, queryRunner) {
this.connection = connection;
this.queryRunner = queryRunner;
// -------------------------------------------------------------------------
// Public Properties
// -------------------------------------------------------------------------
/**
* Indicates if migrations must be executed in a transaction.
*/
this.transaction = true;
var options = this.connection.driver.options;
this.migrationsTableName = connection.options.migrationsTableName || "migrations";
this.migrationsTable = this.connection.driver.buildTableName(this.migrationsTableName, options.schema, options.database);
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Lists all migrations and whether they have been executed or not
* returns true if there are unapplied migrations
*/
MigrationExecutor.prototype.showMigrations = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var e_1, _a, hasUnappliedMigrations, queryRunner, executedMigrations, allMigrations, _loop_1, this_1, allMigrations_1, allMigrations_1_1, migration;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
hasUnappliedMigrations = false;
queryRunner = this.queryRunner || this.connection.createQueryRunner("master");
// create migrations table if its not created yet
return [4 /*yield*/, this.createMigrationsTableIfNotExist(queryRunner)];
case 1:
// create migrations table if its not created yet
_b.sent();
return [4 /*yield*/, this.loadExecutedMigrations(queryRunner)];
case 2:
executedMigrations = _b.sent();
allMigrations = this.getMigrations();
_loop_1 = function (migration) {
var executedMigration = executedMigrations.find(function (executedMigration) { return executedMigration.name === migration.name; });
if (executedMigration) {
this_1.connection.logger.logSchemaBuild(" [X] " + migration.name);
}
else {
hasUnappliedMigrations = true;
this_1.connection.logger.logSchemaBuild(" [ ] " + migration.name);
}
};
this_1 = this;
try {
for (allMigrations_1 = tslib_1.__values(allMigrations), allMigrations_1_1 = allMigrations_1.next(); !allMigrations_1_1.done; allMigrations_1_1 = allMigrations_1.next()) {
migration = allMigrations_1_1.value;
_loop_1(migration);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (allMigrations_1_1 && !allMigrations_1_1.done && (_a = allMigrations_1.return)) _a.call(allMigrations_1);
}
finally { if (e_1) throw e_1.error; }
}
if (!!this.queryRunner) return [3 /*break*/, 4];
return [4 /*yield*/, queryRunner.release()];
case 3:
_b.sent();
_b.label = 4;
case 4: return [2 /*return*/, hasUnappliedMigrations];
}
});
});
};
/**
* Executes all pending migrations. Pending migrations are migrations that are not yet executed,
* thus not saved in the database.
*/
MigrationExecutor.prototype.executePendingMigrations = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var queryRunner, executedMigrations, lastTimeExecutedMigration, allMigrations, successMigrations, pendingMigrations, transactionStartedByUs, err_1, rollbackError_1;
var _this = this;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
queryRunner = this.queryRunner || this.connection.createQueryRunner("master");
// create migrations table if its not created yet
return [4 /*yield*/, this.createMigrationsTableIfNotExist(queryRunner)];
case 1:
// create migrations table if its not created yet
_a.sent();
return [4 /*yield*/, this.loadExecutedMigrations(queryRunner)];
case 2:
executedMigrations = _a.sent();
lastTimeExecutedMigration = this.getLatestTimestampMigration(executedMigrations);
allMigrations = this.getMigrations();
successMigrations = [];
pendingMigrations = allMigrations.filter(function (migration) {
// check if we already have executed migration
var executedMigration = executedMigrations.find(function (executedMigration) { return executedMigration.name === migration.name; });
if (executedMigration)
return false;
// migration is new and not executed. now check if its timestamp is correct
// if (lastTimeExecutedMigration && migration.timestamp < lastTimeExecutedMigration.timestamp)
// throw new Error(`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.`);
// every check is passed means that migration was not run yet and we need to run it
return true;
});
if (!!pendingMigrations.length) return [3 /*break*/, 5];
this.connection.logger.logSchemaBuild("No migrations are pending");
if (!!this.queryRunner) return [3 /*break*/, 4];
return [4 /*yield*/, queryRunner.release()];
case 3:
_a.sent();
_a.label = 4;
case 4: return [2 /*return*/, []];
case 5:
// log information about migration execution
this.connection.logger.logSchemaBuild(executedMigrations.length + " migrations are already loaded in the database.");
this.connection.logger.logSchemaBuild(allMigrations.length + " migrations were found in the source code.");
if (lastTimeExecutedMigration)
this.connection.logger.logSchemaBuild(lastTimeExecutedMigration.name + " is the last executed migration. It was executed on " + new Date(lastTimeExecutedMigration.timestamp).toString() + ".");
this.connection.logger.logSchemaBuild(pendingMigrations.length + " migrations are new migrations that needs to be executed.");
transactionStartedByUs = false;
if (!(this.transaction && !queryRunner.isTransactionActive)) return [3 /*break*/, 7];
return [4 /*yield*/, queryRunner.startTransaction()];
case 6:
_a.sent();
transactionStartedByUs = true;
_a.label = 7;
case 7:
_a.trys.push([7, 11, 16, 19]);
return [4 /*yield*/, PromiseUtils.runInSequence(pendingMigrations, function (migration) {
return migration.instance.up(queryRunner)
.then(function () {
return _this.insertExecutedMigration(queryRunner, migration);
})
.then(function () {
successMigrations.push(migration);
_this.connection.logger.logSchemaBuild("Migration " + migration.name + " has been executed successfully.");
});
})];
case 8:
_a.sent();
if (!transactionStartedByUs) return [3 /*break*/, 10];
return [4 /*yield*/, queryRunner.commitTransaction()];
case 9:
_a.sent();
_a.label = 10;
case 10: return [3 /*break*/, 19];
case 11:
err_1 = _a.sent();
if (!transactionStartedByUs) return [3 /*break*/, 15];
_a.label = 12;
case 12:
_a.trys.push([12, 14, , 15]);
return [4 /*yield*/, queryRunner.rollbackTransaction()];
case 13:
_a.sent();
return [3 /*break*/, 15];
case 14:
rollbackError_1 = _a.sent();
return [3 /*break*/, 15];
case 15: throw err_1;
case 16:
if (!!this.queryRunner) return [3 /*break*/, 18];
return [4 /*yield*/, queryRunner.release()];
case 17:
_a.sent();
_a.label = 18;
case 18: return [7 /*endfinally*/];
case 19: return [2 /*return*/, successMigrations];
}
});
});
};
/**
* Reverts last migration that were run.
*/
MigrationExecutor.prototype.undoLastMigration = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var queryRunner, executedMigrations, lastTimeExecutedMigration, allMigrations, migrationToRevert, transactionStartedByUs, err_2, rollbackError_2;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
queryRunner = this.queryRunner || this.connection.createQueryRunner("master");
// create migrations table if its not created yet
return [4 /*yield*/, this.createMigrationsTableIfNotExist(queryRunner)];
case 1:
// create migrations table if its not created yet
_a.sent();
return [4 /*yield*/, this.loadExecutedMigrations(queryRunner)];
case 2:
executedMigrations = _a.sent();
lastTimeExecutedMigration = this.getLatestExecutedMigration(executedMigrations);
// if no migrations found in the database then nothing to revert
if (!lastTimeExecutedMigration) {
this.connection.logger.logSchemaBuild("No migrations was found in the database. Nothing to revert!");
return [2 /*return*/];
}
allMigrations = this.getMigrations();
migrationToRevert = allMigrations.find(function (migration) { return migration.name === lastTimeExecutedMigration.name; });
// if no migrations found in the database then nothing to revert
if (!migrationToRevert)
throw new Error("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.");
// log information about migration execution
this.connection.logger.logSchemaBuild(executedMigrations.length + " migrations are already loaded in the database.");
this.connection.logger.logSchemaBuild(lastTimeExecutedMigration.name + " is the last executed migration. It was executed on " + new Date(lastTimeExecutedMigration.timestamp).toString() + ".");
this.connection.logger.logSchemaBuild("Now reverting it...");
transactionStartedByUs = false;
if (!(this.transaction && !queryRunner.isTransactionActive)) return [3 /*break*/, 4];
return [4 /*yield*/, queryRunner.startTransaction()];
case 3:
_a.sent();
transactionStartedByUs = true;
_a.label = 4;
case 4:
_a.trys.push([4, 9, 14, 17]);
return [4 /*yield*/, migrationToRevert.instance.down(queryRunner)];
case 5:
_a.sent();
return [4 /*yield*/, this.deleteExecutedMigration(queryRunner, migrationToRevert)];
case 6:
_a.sent();
this.connection.logger.logSchemaBuild("Migration " + migrationToRevert.name + " has been reverted successfully.");
if (!transactionStartedByUs) return [3 /*break*/, 8];
return [4 /*yield*/, queryRunner.commitTransaction()];
case 7:
_a.sent();
_a.label = 8;
case 8: return [3 /*break*/, 17];
case 9:
err_2 = _a.sent();
if (!transactionStartedByUs) return [3 /*break*/, 13];
_a.label = 10;
case 10:
_a.trys.push([10, 12, , 13]);
return [4 /*yield*/, queryRunner.rollbackTransaction()];
case 11:
_a.sent();
return [3 /*break*/, 13];
case 12:
rollbackError_2 = _a.sent();
return [3 /*break*/, 13];
case 13: throw err_2;
case 14:
if (!!this.queryRunner) return [3 /*break*/, 16];
return [4 /*yield*/, queryRunner.release()];
case 15:
_a.sent();
_a.label = 16;
case 16: return [7 /*endfinally*/];
case 17: return [2 /*return*/];
}
});
});
};
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
/**
* Creates table "migrations" that will store information about executed migrations.
*/
MigrationExecutor.prototype.createMigrationsTableIfNotExist = function (queryRunner) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var tableExist;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
// If driver is mongo no need to create
if (this.connection.driver instanceof MongoDriver) {
return [2 /*return*/];
}
return [4 /*yield*/, queryRunner.hasTable(this.migrationsTable)];
case 1:
tableExist = _a.sent();
if (!!tableExist) return [3 /*break*/, 3];
return [4 /*yield*/, queryRunner.createTable(new Table({
name: this.migrationsTable,
columns: [
{
name: "id",
type: this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationId }),
isGenerated: true,
generationStrategy: "increment",
isPrimary: true,
isNullable: false
},
{
name: "timestamp",
type: this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationTimestamp }),
isPrimary: false,
isNullable: false
},
{
name: "name",
type: this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationName }),
isNullable: false
},
]
}))];
case 2:
_a.sent();
_a.label = 3;
case 3: return [2 /*return*/];
}
});
});
};
/**
* Loads all migrations that were executed and saved into the database.
*/
MigrationExecutor.prototype.loadExecutedMigrations = function (queryRunner) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var mongoRunner, migrationsRaw;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(this.connection.driver instanceof MongoDriver)) return [3 /*break*/, 2];
mongoRunner = queryRunner;
return [4 /*yield*/, mongoRunner.databaseConnection.db(this.connection.driver.database).collection(this.migrationsTableName).find().toArray()];
case 1: return [2 /*return*/, _a.sent()];
case 2: return [4 /*yield*/, this.connection.manager
.createQueryBuilder(queryRunner)
.select()
.from(this.migrationsTable, this.migrationsTableName)
.getRawMany()];
case 3:
migrationsRaw = _a.sent();
return [2 /*return*/, migrationsRaw.map(function (migrationRaw) {
return new Migration(parseInt(migrationRaw["id"]), parseInt(migrationRaw["timestamp"]), migrationRaw["name"]);
})];
}
});
});
};
/**
* Gets all migrations that setup for this connection.
*/
MigrationExecutor.prototype.getMigrations = function () {
var migrations = this.connection.migrations.map(function (migration) {
var migrationClassName = migration.constructor.name;
var migrationTimestamp = parseInt(migrationClassName.substr(-13));
if (!migrationTimestamp)
throw new Error(migrationClassName + " migration name is wrong. Migration class name should have a JavaScript timestamp appended.");
return new Migration(undefined, migrationTimestamp, migrationClassName, migration);
});
// sort them by timestamp
return migrations.sort(function (a, b) { return a.timestamp - b.timestamp; });
};
/**
* Finds the latest migration (sorts by timestamp) in the given array of migrations.
*/
MigrationExecutor.prototype.getLatestTimestampMigration = function (migrations) {
var sortedMigrations = migrations.map(function (migration) { return migration; }).sort(function (a, b) { return (a.timestamp - b.timestamp) * -1; });
return sortedMigrations.length > 0 ? sortedMigrations[0] : undefined;
};
/**
* Finds the latest migration (sorts by id) in the given array of migrations.
*/
MigrationExecutor.prototype.getLatestExecutedMigration = function (migrations) {
var sortedMigrations = migrations.map(function (migration) { return migration; }).sort(function (a, b) { return ((a.id || 0) - (b.id || 0)) * -1; });
return sortedMigrations.length > 0 ? sortedMigrations[0] : undefined;
};
/**
* Inserts new executed migration's data into migrations table.
*/
MigrationExecutor.prototype.insertExecutedMigration = function (queryRunner, migration) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var values, mongoRunner, qb;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
values = {};
if (this.connection.driver instanceof SqlServerDriver) {
values["timestamp"] = new MssqlParameter(migration.timestamp, this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationTimestamp }));
values["name"] = new MssqlParameter(migration.name, this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationName }));
}
else {
values["timestamp"] = migration.timestamp;
values["name"] = migration.name;
}
if (!(this.connection.driver instanceof MongoDriver)) return [3 /*break*/, 1];
mongoRunner = queryRunner;
mongoRunner.databaseConnection.db(this.connection.driver.database).collection(this.migrationsTableName).insert(values);
return [3 /*break*/, 3];
case 1:
qb = queryRunner.manager.createQueryBuilder();
return [4 /*yield*/, qb.insert()
.into(this.migrationsTable)
.values(values)
.execute()];
case 2:
_a.sent();
_a.label = 3;
case 3: return [2 /*return*/];
}
});
});
};
/**
* Delete previously executed migration's data from the migrations table.
*/
MigrationExecutor.prototype.deleteExecutedMigration = function (queryRunner, migration) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var conditions, mongoRunner, qb;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
conditions = {};
if (this.connection.driver instanceof SqlServerDriver) {
conditions["timestamp"] = new MssqlParameter(migration.timestamp, this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationTimestamp }));
conditions["name"] = new MssqlParameter(migration.name, this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationName }));
}
else {
conditions["timestamp"] = migration.timestamp;
conditions["name"] = migration.name;
}
if (!(this.connection.driver instanceof MongoDriver)) return [3 /*break*/, 1];
mongoRunner = queryRunner;
mongoRunner.databaseConnection.db(this.connection.driver.database).collection(this.migrationsTableName).deleteOne(conditions);
return [3 /*break*/, 3];
case 1:
qb = queryRunner.manager.createQueryBuilder();
return [4 /*yield*/, qb.delete()
.from(this.migrationsTable)
.where(qb.escape("timestamp") + " = :timestamp")
.andWhere(qb.escape("name") + " = :name")
.setParameters(conditions)
.execute()];
case 2:
_a.sent();
_a.label = 3;
case 3: return [2 /*return*/];
}
});
});
};
return MigrationExecutor;
}());
export { MigrationExecutor };
//# sourceMappingURL=MigrationExecutor.js.map