typeorm
Version:
Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, MongoDB databases.
661 lines (659 loc) • 36.5 kB
JavaScript
import { __awaiter, __generator, __values } from "tslib";
import { Table } from "../schema-builder/table/Table";
import { Migration } from "./Migration";
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 how migrations should be run in transactions.
* all: all migrations are run in a single transaction
* none: all migrations are run without a transaction
* each: each migration is run in a separate transaction
*/
this.transaction = "all";
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
// -------------------------------------------------------------------------
/**
* Tries to execute a single migration given.
*/
MigrationExecutor.prototype.executeMigration = function (migration) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, this.withQueryRunner(function (queryRunner) { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.createMigrationsTableIfNotExist(queryRunner)];
case 1:
_a.sent();
return [4 /*yield*/, migration.instance.up(queryRunner)];
case 2:
_a.sent();
return [4 /*yield*/, this.insertExecutedMigration(queryRunner, migration)];
case 3:
_a.sent();
return [2 /*return*/, migration];
}
});
}); })];
});
});
};
/**
* Returns an array of all migrations.
*/
MigrationExecutor.prototype.getAllMigrations = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, Promise.resolve(this.getMigrations())];
});
});
};
/**
* Returns an array of all executed migrations.
*/
MigrationExecutor.prototype.getExecutedMigrations = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, this.withQueryRunner(function (queryRunner) { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.createMigrationsTableIfNotExist(queryRunner)];
case 1:
_a.sent();
return [4 /*yield*/, this.loadExecutedMigrations(queryRunner)];
case 2: return [2 /*return*/, _a.sent()];
}
});
}); })];
});
});
};
/**
* Returns an array of all pending migrations.
*/
MigrationExecutor.prototype.getPendingMigrations = function () {
return __awaiter(this, void 0, void 0, function () {
var allMigrations, executedMigrations;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getAllMigrations()];
case 1:
allMigrations = _a.sent();
return [4 /*yield*/, this.getExecutedMigrations()];
case 2:
executedMigrations = _a.sent();
return [2 /*return*/, allMigrations.filter(function (migration) {
return !executedMigrations.find(function (executedMigration) {
return executedMigration.name === migration.name;
});
})];
}
});
});
};
/**
* Inserts an executed migration.
*/
MigrationExecutor.prototype.insertMigration = function (migration) {
var _this = this;
return new Promise(function (resolve, reject) {
_this.withQueryRunner(function (queryRunner) {
_this.insertExecutedMigration(queryRunner, migration)
.then(resolve)
.catch(reject);
});
});
};
/**
* Deletes an executed migration.
*/
MigrationExecutor.prototype.deleteMigration = function (migration) {
var _this = this;
return new Promise(function (resolve, reject) {
_this.withQueryRunner(function (queryRunner) {
_this.deleteExecutedMigration(queryRunner, migration)
.then(resolve)
.catch(reject);
});
});
};
/**
* Lists all migrations and whether they have been executed or not
* returns true if there are unapplied migrations
*/
MigrationExecutor.prototype.showMigrations = function () {
return __awaiter(this, void 0, void 0, function () {
var hasUnappliedMigrations, queryRunner, executedMigrations, allMigrations, _loop_1, this_1, allMigrations_1, allMigrations_1_1, migration;
var e_1, _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
hasUnappliedMigrations = false;
queryRunner = this.queryRunner || this.connection.createQueryRunner();
// 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 = __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 __awaiter(this, void 0, void 0, function () {
var queryRunner, executedMigrations, lastTimeExecutedMigration, allMigrations, successMigrations, pendingMigrations, transactionStartedByUs, _loop_2, this_2, pendingMigrations_1, pendingMigrations_1_1, migration, e_2_1, err_1, rollbackError_1;
var e_2, _a;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
queryRunner = this.queryRunner || this.connection.createQueryRunner();
// 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();
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:
_b.sent();
_b.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 === "all" && !queryRunner.isTransactionActive)) return [3 /*break*/, 7];
return [4 /*yield*/, queryRunner.startTransaction()];
case 6:
_b.sent();
transactionStartedByUs = true;
_b.label = 7;
case 7:
_b.trys.push([7, 18, 23, 26]);
_loop_2 = function (migration) {
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (!(this_2.transaction === "each" && !queryRunner.isTransactionActive)) return [3 /*break*/, 2];
return [4 /*yield*/, queryRunner.startTransaction()];
case 1:
_c.sent();
transactionStartedByUs = true;
_c.label = 2;
case 2: return [4 /*yield*/, migration.instance.up(queryRunner)
.then(function () { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: // now when migration is executed we need to insert record about it into the database
return [4 /*yield*/, this.insertExecutedMigration(queryRunner, migration)];
case 1:
_a.sent();
if (!(this.transaction === "each" && transactionStartedByUs)) return [3 /*break*/, 3];
return [4 /*yield*/, queryRunner.commitTransaction()];
case 2:
_a.sent();
_a.label = 3;
case 3: return [2 /*return*/];
}
});
}); })
.then(function () {
successMigrations.push(migration);
_this.connection.logger.logSchemaBuild("Migration " + migration.name + " has been executed successfully.");
})];
case 3:
_c.sent();
return [2 /*return*/];
}
});
};
this_2 = this;
_b.label = 8;
case 8:
_b.trys.push([8, 13, 14, 15]);
pendingMigrations_1 = __values(pendingMigrations), pendingMigrations_1_1 = pendingMigrations_1.next();
_b.label = 9;
case 9:
if (!!pendingMigrations_1_1.done) return [3 /*break*/, 12];
migration = pendingMigrations_1_1.value;
return [5 /*yield**/, _loop_2(migration)];
case 10:
_b.sent();
_b.label = 11;
case 11:
pendingMigrations_1_1 = pendingMigrations_1.next();
return [3 /*break*/, 9];
case 12: return [3 /*break*/, 15];
case 13:
e_2_1 = _b.sent();
e_2 = { error: e_2_1 };
return [3 /*break*/, 15];
case 14:
try {
if (pendingMigrations_1_1 && !pendingMigrations_1_1.done && (_a = pendingMigrations_1.return)) _a.call(pendingMigrations_1);
}
finally { if (e_2) throw e_2.error; }
return [7 /*endfinally*/];
case 15:
if (!(this.transaction === "all" && transactionStartedByUs)) return [3 /*break*/, 17];
return [4 /*yield*/, queryRunner.commitTransaction()];
case 16:
_b.sent();
_b.label = 17;
case 17: return [3 /*break*/, 26];
case 18:
err_1 = _b.sent();
if (!transactionStartedByUs) return [3 /*break*/, 22];
_b.label = 19;
case 19:
_b.trys.push([19, 21, , 22]);
return [4 /*yield*/, queryRunner.rollbackTransaction()];
case 20:
_b.sent();
return [3 /*break*/, 22];
case 21:
rollbackError_1 = _b.sent();
return [3 /*break*/, 22];
case 22: throw err_1;
case 23:
if (!!this.queryRunner) return [3 /*break*/, 25];
return [4 /*yield*/, queryRunner.release()];
case 24:
_b.sent();
_b.label = 25;
case 25: return [7 /*endfinally*/];
case 26: return [2 /*return*/, successMigrations];
}
});
});
};
/**
* Reverts last migration that were run.
*/
MigrationExecutor.prototype.undoLastMigration = function () {
return __awaiter(this, void 0, void 0, function () {
var queryRunner, executedMigrations, lastTimeExecutedMigration, allMigrations, migrationToRevert, transactionStartedByUs, err_2, rollbackError_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
queryRunner = this.queryRunner || this.connection.createQueryRunner();
// 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 !== "none") && !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 __awaiter(this, void 0, void 0, function () {
var tableExist;
return __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 (sorts by id).
*/
MigrationExecutor.prototype.loadExecutedMigrations = function (queryRunner) {
return __awaiter(this, void 0, void 0, function () {
var mongoRunner, migrationsRaw;
return __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()
.sort({ "_id": -1 })
.toArray()];
case 1: return [2 /*return*/, _a.sent()];
case 2: return [4 /*yield*/, this.connection.manager
.createQueryBuilder(queryRunner)
.select()
.orderBy(this.connection.driver.escape("id"), "DESC")
.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.name || migration.constructor.name;
var migrationTimestamp = parseInt(migrationClassName.substr(-13), 10);
if (!migrationTimestamp || isNaN(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);
});
this.checkForDuplicateMigrations(migrations);
// sort them by timestamp
return migrations.sort(function (a, b) { return a.timestamp - b.timestamp; });
};
MigrationExecutor.prototype.checkForDuplicateMigrations = function (migrations) {
var migrationNames = migrations.map(function (migration) { return migration.name; });
var duplicates = Array.from(new Set(migrationNames.filter(function (migrationName, index) { return migrationNames.indexOf(migrationName) < index; })));
if (duplicates.length > 0) {
throw Error("Duplicate migrations: " + duplicates.join(", "));
}
};
/**
* 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 in the given array of migrations.
* PRE: Migration array must be sorted by descending id.
*/
MigrationExecutor.prototype.getLatestExecutedMigration = function (sortedMigrations) {
return sortedMigrations.length > 0 ? sortedMigrations[0] : undefined;
};
/**
* Inserts new executed migration's data into migrations table.
*/
MigrationExecutor.prototype.insertExecutedMigration = function (queryRunner, migration) {
return __awaiter(this, void 0, void 0, function () {
var values, mongoRunner, qb;
return __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*/, 2];
mongoRunner = queryRunner;
return [4 /*yield*/, mongoRunner.databaseConnection.db(this.connection.driver.database).collection(this.migrationsTableName).insertOne(values)];
case 1:
_a.sent();
return [3 /*break*/, 4];
case 2:
qb = queryRunner.manager.createQueryBuilder();
return [4 /*yield*/, qb.insert()
.into(this.migrationsTable)
.values(values)
.execute()];
case 3:
_a.sent();
_a.label = 4;
case 4: return [2 /*return*/];
}
});
});
};
/**
* Delete previously executed migration's data from the migrations table.
*/
MigrationExecutor.prototype.deleteExecutedMigration = function (queryRunner, migration) {
return __awaiter(this, void 0, void 0, function () {
var conditions, mongoRunner, qb;
return __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*/, 2];
mongoRunner = queryRunner;
return [4 /*yield*/, mongoRunner.databaseConnection.db(this.connection.driver.database).collection(this.migrationsTableName).deleteOne(conditions)];
case 1:
_a.sent();
return [3 /*break*/, 4];
case 2:
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 3:
_a.sent();
_a.label = 4;
case 4: return [2 /*return*/];
}
});
});
};
MigrationExecutor.prototype.withQueryRunner = function (callback) {
return __awaiter(this, void 0, void 0, function () {
var queryRunner;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
queryRunner = this.queryRunner || this.connection.createQueryRunner();
_a.label = 1;
case 1:
_a.trys.push([1, , 2, 5]);
return [2 /*return*/, callback(queryRunner)];
case 2:
if (!!this.queryRunner) return [3 /*break*/, 4];
return [4 /*yield*/, queryRunner.release()];
case 3:
_a.sent();
_a.label = 4;
case 4: return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
});
};
return MigrationExecutor;
}());
export { MigrationExecutor };
//# sourceMappingURL=MigrationExecutor.js.map