From 9767f6e5df250377c868bfd3801c849c3889b461 Mon Sep 17 00:00:00 2001 From: Scimonster Date: Mon, 17 Jun 2019 16:36:58 +0300 Subject: [PATCH] Wrap migration in transaction Add option to run without transaction Update README --- README.md | 3 +- bin/makemigration.js | 2 +- bin/runmigration.js | 4 +- lib/migrate.js | 98 +++++++++++++++++++++++++++----------------- 4 files changed, 67 insertions(+), 40 deletions(-) mode change 100644 => 100755 bin/makemigration.js mode change 100644 => 100755 bin/runmigration.js diff --git a/README.md b/README.md index 7cba62c..060b686 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,10 @@ To create and then execute migration, use: `runmigration` * To select a revision, use `--rev ` -* If migration fails, you can continue, use `--pos ` * To prevent execution next migrations, use `--one` +Each migration runs in a transaction, so it will be rolled back if part of it fails. To disable, use `--no-transaction`. Then, if it fails, you can continue by using `--pos `. + For more information, use `makemigration --help`, `runmigration --help` diff --git a/bin/makemigration.js b/bin/makemigration.js old mode 100644 new mode 100755 index 046908d..e9a2a09 --- a/bin/makemigration.js +++ b/bin/makemigration.js @@ -120,7 +120,7 @@ console.log(`New migration to revision ${currentState.revision} has been saved t if (options.execute) { - migrate.executeMigration(sequelize.getQueryInterface(), info.filename, 0, (err) => { + migrate.executeMigration(sequelize.getQueryInterface(), info.filename, true, 0, (err) => { if (!err) console.log("Migration has been executed successfully"); else diff --git a/bin/runmigration.js b/bin/runmigration.js old mode 100644 new mode 100755 index 4a54e4e..08f29fd --- a/bin/runmigration.js +++ b/bin/runmigration.js @@ -11,6 +11,7 @@ const pathConfig = require('../lib/pathconfig'); const optionDefinitions = [ { name: 'rev', alias: 'r', type: Number, description: 'Set migration revision (default: 0)', defaultValue: 0 }, { name: 'pos', alias: 'p', type: Number, description: 'Run first migration at pos (default: 0)', defaultValue: 0 }, + { name: 'no-transaction', type: Boolean, description: 'Run each change separately instead of all in a transaction (allows it to fail and continue)', defaultValue: false }, { name: 'one', type: Boolean, description: 'Do not run next migrations', defaultValue: false }, { name: 'list', alias: 'l', type: Boolean, description: 'Show migration file list (without execution)', defaultValue: false }, { name: 'migrations-path', type: String, description: 'The path to the migrations folder' }, @@ -57,6 +58,7 @@ const queryInterface = sequelize.getQueryInterface(); let fromRevision = options.rev; let fromPos = parseInt(options.pos); let stop = options.one; +let noTransaction = options['no-transaction']; let migrationFiles = fs.readdirSync(migrationsDir) // filter JS files @@ -89,7 +91,7 @@ if (options.list) Async.eachSeries(migrationFiles, function (file, cb) { console.log("Execute migration from file: "+file); - migrate.executeMigration(queryInterface, path.join(migrationsDir, file), fromPos, (err) => { + migrate.executeMigration(queryInterface, path.join(migrationsDir, file), !noTransaction, fromPos, (err) => { if (stop) return cb("Stopped"); diff --git a/lib/migrate.js b/lib/migrate.js index a41fab8..f1dfd8f 100644 --- a/lib/migrate.js +++ b/lib/migrate.js @@ -654,42 +654,49 @@ const getMigration = function(actions) return " { \n" + ret.join(", \n") + "\n }"; }; + let addTransactionToOptions = (options) => { + let ret = JSON.stringify({...options, transaction: '###TRANSACTION###'}); + ret = ret.replace('"###TRANSACTION###"', 'transaction'); + return ret; + }; + let commandsUp = []; let consoleOut = []; - + for (let _i in actions) { let action = actions[_i]; - switch (action.actionType) + switch (action.actionType) { case 'createTable': { let resUp =`{ fn: "createTable", params: [ "${action.tableName}", ${getAttributes(action.attributes)}, - ${JSON.stringify(action.options)} + ${addTransactionToOptions(action.options)} ] }`; commandsUp.push(resUp); - + consoleOut.push(`createTable "${action.tableName}", deps: [${action.depends.join(', ')}]`); } break; case 'dropTable': { - let res = `{ fn: "dropTable", params: ["${action.tableName}"] }`; + let res = `{ fn: "dropTable", params: ["${action.tableName}", {transaction: transaction}] }`; commandsUp.push(res); - + consoleOut.push(`dropTable "${action.tableName}"`); } break; - + case 'addColumn': { let resUp = `{ fn: "addColumn", params: [ "${action.tableName}", "${(action.options && action.options.field) ? action.options.field : action.attributeName}", - ${propertyToStr(action.options)} + ${propertyToStr(action.options)}, + {transaction: transaction} ] }`; commandsUp.push(resUp); @@ -700,54 +707,61 @@ let resUp = `{ fn: "addColumn", params: [ case 'removeColumn': { - let res = `{ fn: "removeColumn", params: ["${action.tableName}", "${(action.options && action.options.field) ? action.options.field : action.columnName}"] }`; +let res = `{ fn: "removeColumn", params: [ + "${action.tableName}", + "${(action.options && action.options.field) ? action.options.field : action.columnName}", + {transaction: transaction} + ] +}`; commandsUp.push(res); - + consoleOut.push(`removeColumn "${(action.options && action.options.field) ? action.options.field : action.columnName}" from table "${action.tableName}"`); } break; - + case 'changeColumn': { let res = `{ fn: "changeColumn", params: [ "${action.tableName}", "${(action.options && action.options.field) ? action.options.field : action.attributeName}", - ${propertyToStr(action.options)} + ${propertyToStr(action.options)}, + {transaction: transaction} ] }`; commandsUp.push(res); - + consoleOut.push(`changeColumn "${action.attributeName}" on table "${action.tableName}"`); } break; - + case 'addIndex': { let res = `{ fn: "addIndex", params: [ "${action.tableName}", ${JSON.stringify(action.fields)}, - ${JSON.stringify(action.options)} + ${addTransactionToOptions(action.options)} ] }`; commandsUp.push(res); - + let nameOrAttrs = (action.options && action.options.indexName && action.options.indexName != '') ? `"${action.options.indexName}"` : JSON.stringify(action.fields); consoleOut.push(`addIndex ${nameOrAttrs} to table "${action.tableName}"`); } break; - + case 'removeIndex': { // log(action) let nameOrAttrs = (action.options && action.options.indexName && action.options.indexName != '') ? `"${action.options.indexName}"` : JSON.stringify(action.fields); - + let res = `{ fn: "removeIndex", params: [ "${action.tableName}", - ${nameOrAttrs} + ${nameOrAttrs}, + {transaction: transaction} ] }`; commandsUp.push(res); - + consoleOut.push(`removeIndex ${nameOrAttrs} from table "${action.tableName}"`); } - + default: // code } @@ -759,9 +773,9 @@ let res = `{ fn: "removeIndex", params: [ const writeMigration = function(revision, migration, migrationsDir, name = '', comment = '') { - let _commands = "var migrationCommands = [ \n" + migration.commandsUp.join(", \n") +' \n];\n'; + let _commands = "var migrationCommands = function(transaction) {return [ \n" + migration.commandsUp.join(", \n") +' \n];};\n'; let _actions = ' * ' + migration.consoleOut.join("\n * "); - + _commands = beautify(_commands); let info = { revision, @@ -787,23 +801,32 @@ ${_commands} module.exports = { pos: 0, + useTransaction: true, up: function(queryInterface, Sequelize) { var index = this.pos; - return new Promise(function(resolve, reject) { - function next() { - if (index < migrationCommands.length) - { - let command = migrationCommands[index]; - console.log("[#"+index+"] execute: " + command.fn); - index++; - queryInterface[command.fn].apply(queryInterface, command.params).then(next, reject); + function run(transaction) { + const commands = migrationCommands(transaction); + return new Promise(function(resolve, reject) { + function next() { + if (index < commands.length) + { + let command = commands[index]; + console.log("[#"+index+"] execute: " + command.fn); + index++; + queryInterface[command.fn].apply(queryInterface, command.params).then(next, reject); + } + else + resolve(); } - else - resolve(); - } - next(); - }); + next(); + }); + } + if (this.useTransaction) { + return queryInterface.sequelize.transaction(run); + } else { + return run(null); + } }, info: info }; @@ -817,7 +840,7 @@ module.exports = { return {filename, info}; }; -const executeMigration = function(queryInterface, filename, pos, cb) +const executeMigration = function(queryInterface, filename, useTransaction, pos, cb) { let mig = require(filename); @@ -829,6 +852,7 @@ const executeMigration = function(queryInterface, filename, pos, cb) console.log("Set position to "+pos); mig.pos = pos; } + mig.useTransaction = useTransaction; mig.up(queryInterface, Sequelize).then( () => {