UNPKG

26.7 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3var tslib_1 = require("tslib");
4var Table_1 = require("../schema-builder/table/Table");
5var Migration_1 = require("./Migration");
6var PromiseUtils_1 = require("../util/PromiseUtils");
7var SqlServerDriver_1 = require("../driver/sqlserver/SqlServerDriver");
8var MssqlParameter_1 = require("../driver/sqlserver/MssqlParameter");
9var MongoDriver_1 = require("../driver/mongodb/MongoDriver");
10/**
11 * Executes migrations: runs pending and reverts previously executed migrations.
12 */
13var MigrationExecutor = /** @class */ (function () {
14 // -------------------------------------------------------------------------
15 // Constructor
16 // -------------------------------------------------------------------------
17 function MigrationExecutor(connection, queryRunner) {
18 this.connection = connection;
19 this.queryRunner = queryRunner;
20 // -------------------------------------------------------------------------
21 // Public Properties
22 // -------------------------------------------------------------------------
23 /**
24 * Indicates if migrations must be executed in a transaction.
25 */
26 this.transaction = true;
27 var options = this.connection.driver.options;
28 this.migrationsTableName = connection.options.migrationsTableName || "migrations";
29 this.migrationsTable = this.connection.driver.buildTableName(this.migrationsTableName, options.schema, options.database);
30 }
31 // -------------------------------------------------------------------------
32 // Public Methods
33 // -------------------------------------------------------------------------
34 /**
35 * Lists all migrations and whether they have been executed or not
36 * returns true if there are unapplied migrations
37 */
38 MigrationExecutor.prototype.showMigrations = function () {
39 return tslib_1.__awaiter(this, void 0, void 0, function () {
40 var e_1, _a, hasUnappliedMigrations, queryRunner, executedMigrations, allMigrations, _loop_1, this_1, allMigrations_1, allMigrations_1_1, migration;
41 return tslib_1.__generator(this, function (_b) {
42 switch (_b.label) {
43 case 0:
44 hasUnappliedMigrations = false;
45 queryRunner = this.queryRunner || this.connection.createQueryRunner("master");
46 // create migrations table if its not created yet
47 return [4 /*yield*/, this.createMigrationsTableIfNotExist(queryRunner)];
48 case 1:
49 // create migrations table if its not created yet
50 _b.sent();
51 return [4 /*yield*/, this.loadExecutedMigrations(queryRunner)];
52 case 2:
53 executedMigrations = _b.sent();
54 allMigrations = this.getMigrations();
55 _loop_1 = function (migration) {
56 var executedMigration = executedMigrations.find(function (executedMigration) { return executedMigration.name === migration.name; });
57 if (executedMigration) {
58 this_1.connection.logger.logSchemaBuild(" [X] " + migration.name);
59 }
60 else {
61 hasUnappliedMigrations = true;
62 this_1.connection.logger.logSchemaBuild(" [ ] " + migration.name);
63 }
64 };
65 this_1 = this;
66 try {
67 for (allMigrations_1 = tslib_1.__values(allMigrations), allMigrations_1_1 = allMigrations_1.next(); !allMigrations_1_1.done; allMigrations_1_1 = allMigrations_1.next()) {
68 migration = allMigrations_1_1.value;
69 _loop_1(migration);
70 }
71 }
72 catch (e_1_1) { e_1 = { error: e_1_1 }; }
73 finally {
74 try {
75 if (allMigrations_1_1 && !allMigrations_1_1.done && (_a = allMigrations_1.return)) _a.call(allMigrations_1);
76 }
77 finally { if (e_1) throw e_1.error; }
78 }
79 if (!!this.queryRunner) return [3 /*break*/, 4];
80 return [4 /*yield*/, queryRunner.release()];
81 case 3:
82 _b.sent();
83 _b.label = 4;
84 case 4: return [2 /*return*/, hasUnappliedMigrations];
85 }
86 });
87 });
88 };
89 /**
90 * Executes all pending migrations. Pending migrations are migrations that are not yet executed,
91 * thus not saved in the database.
92 */
93 MigrationExecutor.prototype.executePendingMigrations = function () {
94 return tslib_1.__awaiter(this, void 0, void 0, function () {
95 var queryRunner, executedMigrations, lastTimeExecutedMigration, allMigrations, successMigrations, pendingMigrations, transactionStartedByUs, err_1, rollbackError_1;
96 var _this = this;
97 return tslib_1.__generator(this, function (_a) {
98 switch (_a.label) {
99 case 0:
100 queryRunner = this.queryRunner || this.connection.createQueryRunner("master");
101 // create migrations table if its not created yet
102 return [4 /*yield*/, this.createMigrationsTableIfNotExist(queryRunner)];
103 case 1:
104 // create migrations table if its not created yet
105 _a.sent();
106 return [4 /*yield*/, this.loadExecutedMigrations(queryRunner)];
107 case 2:
108 executedMigrations = _a.sent();
109 lastTimeExecutedMigration = this.getLatestTimestampMigration(executedMigrations);
110 allMigrations = this.getMigrations();
111 successMigrations = [];
112 pendingMigrations = allMigrations.filter(function (migration) {
113 // check if we already have executed migration
114 var executedMigration = executedMigrations.find(function (executedMigration) { return executedMigration.name === migration.name; });
115 if (executedMigration)
116 return false;
117 // migration is new and not executed. now check if its timestamp is correct
118 // if (lastTimeExecutedMigration && migration.timestamp < lastTimeExecutedMigration.timestamp)
119 // 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.`);
120 // every check is passed means that migration was not run yet and we need to run it
121 return true;
122 });
123 if (!!pendingMigrations.length) return [3 /*break*/, 5];
124 this.connection.logger.logSchemaBuild("No migrations are pending");
125 if (!!this.queryRunner) return [3 /*break*/, 4];
126 return [4 /*yield*/, queryRunner.release()];
127 case 3:
128 _a.sent();
129 _a.label = 4;
130 case 4: return [2 /*return*/, []];
131 case 5:
132 // log information about migration execution
133 this.connection.logger.logSchemaBuild(executedMigrations.length + " migrations are already loaded in the database.");
134 this.connection.logger.logSchemaBuild(allMigrations.length + " migrations were found in the source code.");
135 if (lastTimeExecutedMigration)
136 this.connection.logger.logSchemaBuild(lastTimeExecutedMigration.name + " is the last executed migration. It was executed on " + new Date(lastTimeExecutedMigration.timestamp).toString() + ".");
137 this.connection.logger.logSchemaBuild(pendingMigrations.length + " migrations are new migrations that needs to be executed.");
138 transactionStartedByUs = false;
139 if (!(this.transaction && !queryRunner.isTransactionActive)) return [3 /*break*/, 7];
140 return [4 /*yield*/, queryRunner.startTransaction()];
141 case 6:
142 _a.sent();
143 transactionStartedByUs = true;
144 _a.label = 7;
145 case 7:
146 _a.trys.push([7, 11, 16, 19]);
147 return [4 /*yield*/, PromiseUtils_1.PromiseUtils.runInSequence(pendingMigrations, function (migration) {
148 return migration.instance.up(queryRunner)
149 .then(function () {
150 return _this.insertExecutedMigration(queryRunner, migration);
151 })
152 .then(function () {
153 successMigrations.push(migration);
154 _this.connection.logger.logSchemaBuild("Migration " + migration.name + " has been executed successfully.");
155 });
156 })];
157 case 8:
158 _a.sent();
159 if (!transactionStartedByUs) return [3 /*break*/, 10];
160 return [4 /*yield*/, queryRunner.commitTransaction()];
161 case 9:
162 _a.sent();
163 _a.label = 10;
164 case 10: return [3 /*break*/, 19];
165 case 11:
166 err_1 = _a.sent();
167 if (!transactionStartedByUs) return [3 /*break*/, 15];
168 _a.label = 12;
169 case 12:
170 _a.trys.push([12, 14, , 15]);
171 return [4 /*yield*/, queryRunner.rollbackTransaction()];
172 case 13:
173 _a.sent();
174 return [3 /*break*/, 15];
175 case 14:
176 rollbackError_1 = _a.sent();
177 return [3 /*break*/, 15];
178 case 15: throw err_1;
179 case 16:
180 if (!!this.queryRunner) return [3 /*break*/, 18];
181 return [4 /*yield*/, queryRunner.release()];
182 case 17:
183 _a.sent();
184 _a.label = 18;
185 case 18: return [7 /*endfinally*/];
186 case 19: return [2 /*return*/, successMigrations];
187 }
188 });
189 });
190 };
191 /**
192 * Reverts last migration that were run.
193 */
194 MigrationExecutor.prototype.undoLastMigration = function () {
195 return tslib_1.__awaiter(this, void 0, void 0, function () {
196 var queryRunner, executedMigrations, lastTimeExecutedMigration, allMigrations, migrationToRevert, transactionStartedByUs, err_2, rollbackError_2;
197 return tslib_1.__generator(this, function (_a) {
198 switch (_a.label) {
199 case 0:
200 queryRunner = this.queryRunner || this.connection.createQueryRunner("master");
201 // create migrations table if its not created yet
202 return [4 /*yield*/, this.createMigrationsTableIfNotExist(queryRunner)];
203 case 1:
204 // create migrations table if its not created yet
205 _a.sent();
206 return [4 /*yield*/, this.loadExecutedMigrations(queryRunner)];
207 case 2:
208 executedMigrations = _a.sent();
209 lastTimeExecutedMigration = this.getLatestExecutedMigration(executedMigrations);
210 // if no migrations found in the database then nothing to revert
211 if (!lastTimeExecutedMigration) {
212 this.connection.logger.logSchemaBuild("No migrations was found in the database. Nothing to revert!");
213 return [2 /*return*/];
214 }
215 allMigrations = this.getMigrations();
216 migrationToRevert = allMigrations.find(function (migration) { return migration.name === lastTimeExecutedMigration.name; });
217 // if no migrations found in the database then nothing to revert
218 if (!migrationToRevert)
219 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.");
220 // log information about migration execution
221 this.connection.logger.logSchemaBuild(executedMigrations.length + " migrations are already loaded in the database.");
222 this.connection.logger.logSchemaBuild(lastTimeExecutedMigration.name + " is the last executed migration. It was executed on " + new Date(lastTimeExecutedMigration.timestamp).toString() + ".");
223 this.connection.logger.logSchemaBuild("Now reverting it...");
224 transactionStartedByUs = false;
225 if (!(this.transaction && !queryRunner.isTransactionActive)) return [3 /*break*/, 4];
226 return [4 /*yield*/, queryRunner.startTransaction()];
227 case 3:
228 _a.sent();
229 transactionStartedByUs = true;
230 _a.label = 4;
231 case 4:
232 _a.trys.push([4, 9, 14, 17]);
233 return [4 /*yield*/, migrationToRevert.instance.down(queryRunner)];
234 case 5:
235 _a.sent();
236 return [4 /*yield*/, this.deleteExecutedMigration(queryRunner, migrationToRevert)];
237 case 6:
238 _a.sent();
239 this.connection.logger.logSchemaBuild("Migration " + migrationToRevert.name + " has been reverted successfully.");
240 if (!transactionStartedByUs) return [3 /*break*/, 8];
241 return [4 /*yield*/, queryRunner.commitTransaction()];
242 case 7:
243 _a.sent();
244 _a.label = 8;
245 case 8: return [3 /*break*/, 17];
246 case 9:
247 err_2 = _a.sent();
248 if (!transactionStartedByUs) return [3 /*break*/, 13];
249 _a.label = 10;
250 case 10:
251 _a.trys.push([10, 12, , 13]);
252 return [4 /*yield*/, queryRunner.rollbackTransaction()];
253 case 11:
254 _a.sent();
255 return [3 /*break*/, 13];
256 case 12:
257 rollbackError_2 = _a.sent();
258 return [3 /*break*/, 13];
259 case 13: throw err_2;
260 case 14:
261 if (!!this.queryRunner) return [3 /*break*/, 16];
262 return [4 /*yield*/, queryRunner.release()];
263 case 15:
264 _a.sent();
265 _a.label = 16;
266 case 16: return [7 /*endfinally*/];
267 case 17: return [2 /*return*/];
268 }
269 });
270 });
271 };
272 // -------------------------------------------------------------------------
273 // Protected Methods
274 // -------------------------------------------------------------------------
275 /**
276 * Creates table "migrations" that will store information about executed migrations.
277 */
278 MigrationExecutor.prototype.createMigrationsTableIfNotExist = function (queryRunner) {
279 return tslib_1.__awaiter(this, void 0, void 0, function () {
280 var tableExist;
281 return tslib_1.__generator(this, function (_a) {
282 switch (_a.label) {
283 case 0:
284 // If driver is mongo no need to create
285 if (this.connection.driver instanceof MongoDriver_1.MongoDriver) {
286 return [2 /*return*/];
287 }
288 return [4 /*yield*/, queryRunner.hasTable(this.migrationsTable)];
289 case 1:
290 tableExist = _a.sent();
291 if (!!tableExist) return [3 /*break*/, 3];
292 return [4 /*yield*/, queryRunner.createTable(new Table_1.Table({
293 name: this.migrationsTable,
294 columns: [
295 {
296 name: "id",
297 type: this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationId }),
298 isGenerated: true,
299 generationStrategy: "increment",
300 isPrimary: true,
301 isNullable: false
302 },
303 {
304 name: "timestamp",
305 type: this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationTimestamp }),
306 isPrimary: false,
307 isNullable: false
308 },
309 {
310 name: "name",
311 type: this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationName }),
312 isNullable: false
313 },
314 ]
315 }))];
316 case 2:
317 _a.sent();
318 _a.label = 3;
319 case 3: return [2 /*return*/];
320 }
321 });
322 });
323 };
324 /**
325 * Loads all migrations that were executed and saved into the database.
326 */
327 MigrationExecutor.prototype.loadExecutedMigrations = function (queryRunner) {
328 return tslib_1.__awaiter(this, void 0, void 0, function () {
329 var mongoRunner, migrationsRaw;
330 return tslib_1.__generator(this, function (_a) {
331 switch (_a.label) {
332 case 0:
333 if (!(this.connection.driver instanceof MongoDriver_1.MongoDriver)) return [3 /*break*/, 2];
334 mongoRunner = queryRunner;
335 return [4 /*yield*/, mongoRunner.databaseConnection.db(this.connection.driver.database).collection(this.migrationsTableName).find().toArray()];
336 case 1: return [2 /*return*/, _a.sent()];
337 case 2: return [4 /*yield*/, this.connection.manager
338 .createQueryBuilder(queryRunner)
339 .select()
340 .from(this.migrationsTable, this.migrationsTableName)
341 .getRawMany()];
342 case 3:
343 migrationsRaw = _a.sent();
344 return [2 /*return*/, migrationsRaw.map(function (migrationRaw) {
345 return new Migration_1.Migration(parseInt(migrationRaw["id"]), parseInt(migrationRaw["timestamp"]), migrationRaw["name"]);
346 })];
347 }
348 });
349 });
350 };
351 /**
352 * Gets all migrations that setup for this connection.
353 */
354 MigrationExecutor.prototype.getMigrations = function () {
355 var migrations = this.connection.migrations.map(function (migration) {
356 var migrationClassName = migration.constructor.name;
357 var migrationTimestamp = parseInt(migrationClassName.substr(-13));
358 if (!migrationTimestamp)
359 throw new Error(migrationClassName + " migration name is wrong. Migration class name should have a JavaScript timestamp appended.");
360 return new Migration_1.Migration(undefined, migrationTimestamp, migrationClassName, migration);
361 });
362 // sort them by timestamp
363 return migrations.sort(function (a, b) { return a.timestamp - b.timestamp; });
364 };
365 /**
366 * Finds the latest migration (sorts by timestamp) in the given array of migrations.
367 */
368 MigrationExecutor.prototype.getLatestTimestampMigration = function (migrations) {
369 var sortedMigrations = migrations.map(function (migration) { return migration; }).sort(function (a, b) { return (a.timestamp - b.timestamp) * -1; });
370 return sortedMigrations.length > 0 ? sortedMigrations[0] : undefined;
371 };
372 /**
373 * Finds the latest migration (sorts by id) in the given array of migrations.
374 */
375 MigrationExecutor.prototype.getLatestExecutedMigration = function (migrations) {
376 var sortedMigrations = migrations.map(function (migration) { return migration; }).sort(function (a, b) { return ((a.id || 0) - (b.id || 0)) * -1; });
377 return sortedMigrations.length > 0 ? sortedMigrations[0] : undefined;
378 };
379 /**
380 * Inserts new executed migration's data into migrations table.
381 */
382 MigrationExecutor.prototype.insertExecutedMigration = function (queryRunner, migration) {
383 return tslib_1.__awaiter(this, void 0, void 0, function () {
384 var values, mongoRunner, qb;
385 return tslib_1.__generator(this, function (_a) {
386 switch (_a.label) {
387 case 0:
388 values = {};
389 if (this.connection.driver instanceof SqlServerDriver_1.SqlServerDriver) {
390 values["timestamp"] = new MssqlParameter_1.MssqlParameter(migration.timestamp, this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationTimestamp }));
391 values["name"] = new MssqlParameter_1.MssqlParameter(migration.name, this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationName }));
392 }
393 else {
394 values["timestamp"] = migration.timestamp;
395 values["name"] = migration.name;
396 }
397 if (!(this.connection.driver instanceof MongoDriver_1.MongoDriver)) return [3 /*break*/, 1];
398 mongoRunner = queryRunner;
399 mongoRunner.databaseConnection.db(this.connection.driver.database).collection(this.migrationsTableName).insert(values);
400 return [3 /*break*/, 3];
401 case 1:
402 qb = queryRunner.manager.createQueryBuilder();
403 return [4 /*yield*/, qb.insert()
404 .into(this.migrationsTable)
405 .values(values)
406 .execute()];
407 case 2:
408 _a.sent();
409 _a.label = 3;
410 case 3: return [2 /*return*/];
411 }
412 });
413 });
414 };
415 /**
416 * Delete previously executed migration's data from the migrations table.
417 */
418 MigrationExecutor.prototype.deleteExecutedMigration = function (queryRunner, migration) {
419 return tslib_1.__awaiter(this, void 0, void 0, function () {
420 var conditions, mongoRunner, qb;
421 return tslib_1.__generator(this, function (_a) {
422 switch (_a.label) {
423 case 0:
424 conditions = {};
425 if (this.connection.driver instanceof SqlServerDriver_1.SqlServerDriver) {
426 conditions["timestamp"] = new MssqlParameter_1.MssqlParameter(migration.timestamp, this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationTimestamp }));
427 conditions["name"] = new MssqlParameter_1.MssqlParameter(migration.name, this.connection.driver.normalizeType({ type: this.connection.driver.mappedDataTypes.migrationName }));
428 }
429 else {
430 conditions["timestamp"] = migration.timestamp;
431 conditions["name"] = migration.name;
432 }
433 if (!(this.connection.driver instanceof MongoDriver_1.MongoDriver)) return [3 /*break*/, 1];
434 mongoRunner = queryRunner;
435 mongoRunner.databaseConnection.db(this.connection.driver.database).collection(this.migrationsTableName).deleteOne(conditions);
436 return [3 /*break*/, 3];
437 case 1:
438 qb = queryRunner.manager.createQueryBuilder();
439 return [4 /*yield*/, qb.delete()
440 .from(this.migrationsTable)
441 .where(qb.escape("timestamp") + " = :timestamp")
442 .andWhere(qb.escape("name") + " = :name")
443 .setParameters(conditions)
444 .execute()];
445 case 2:
446 _a.sent();
447 _a.label = 3;
448 case 3: return [2 /*return*/];
449 }
450 });
451 });
452 };
453 return MigrationExecutor;
454}());
455exports.MigrationExecutor = MigrationExecutor;
456
457//# sourceMappingURL=MigrationExecutor.js.map