Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rollback #57

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ To create and then execute migration, use:

`runmigration`
* To select a revision, use `--rev <x>`
* If migration fails, you can continue, use `--pos <x>`
* To prevent execution next migrations, use `--one`
* To prevent execution of next migrations, use `--one`
* To rollback/downgrade to the selected revision, use `--rollback`

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 <x>`.


For more information, use `makemigration --help`, `runmigration --help`

## TODO:
* Migration action sorting procedure need some fixes. When many foreign keys in tables, there is a bug with action order. Now, please check it manually (`--preview` option)
* Need to check (and maybe fix) field types: `BLOB`, `RANGE`, `ARRAY`, `GEOMETRY`, `GEOGRAPHY`
* Downgrade is not supported, add it
* This module tested with postgresql (I use it with my projects). Test with mysql and sqlite.
12 changes: 7 additions & 5 deletions bin/makemigration.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ let models = sequelize.models;

currentState.tables = migrate.reverseModels(sequelize, models);

let actions = migrate.parseDifference(previousState.tables, currentState.tables);
let upActions = migrate.parseDifference(previousState.tables, currentState.tables);
let downActions = migrate.parseDifference(currentState.tables, previousState.tables);

// sort actions
migrate.sortActions(actions);
// sort actions
migrate.sortActions(upActions);
migrate.sortActions(downActions);

let migration = migrate.getMigration(actions);
let migration = migrate.getMigration(upActions, downActions);

if (migration.commandsUp.length === 0)
{
Expand Down Expand Up @@ -120,7 +122,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, false, (err) => {
if (!err)
console.log("Migration has been executed successfully");
else
Expand Down
15 changes: 12 additions & 3 deletions bin/runmigration.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ const pathConfig = require('../lib/pathconfig');

const optionDefinitions = [
{ name: 'rev', alias: 'r', type: Number, description: 'Set migration revision (default: 0)', defaultValue: 0 },
{ name: 'rollback', alias: 'b', type: Boolean, description: 'Rollback to specified revision', defaultValue: false },
{ 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' },
Expand Down Expand Up @@ -57,6 +59,8 @@ const queryInterface = sequelize.getQueryInterface();
let fromRevision = options.rev;
let fromPos = parseInt(options.pos);
let stop = options.one;
let rollback = options.rollback;
let noTransaction = options['no-transaction'];

let migrationFiles = fs.readdirSync(migrationsDir)
// filter JS files
Expand All @@ -67,8 +71,13 @@ let migrationFiles = fs.readdirSync(migrationsDir)
.sort( (a, b) => {
let revA = parseInt( path.basename(a).split('-',2)[0]),
revB = parseInt( path.basename(b).split('-',2)[0]);
if (revA < revB) return -1;
if (revA > revB) return 1;
if (rollback) {
if (revA < revB) return 1;
if (revA > revB) return -1;
} else {
if (revA < revB) return -1;
if (revA > revB) return 1;
}
return 0;
})
// remove all migrations before fromRevision
Expand All @@ -89,7 +98,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, rollback, (err) => {
if (stop)
return cb("Stopped");

Expand Down
170 changes: 113 additions & 57 deletions lib/migrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ const sortActions = function(actions)
};


const getMigration = function(actions)
const getPartialMigration = function(actions)
{
let propertyToStr = (obj) => {
let vals = [];
Expand Down Expand Up @@ -654,115 +654,138 @@ const getMigration = function(actions)
return " { \n" + ret.join(", \n") + "\n }";
};

let commandsUp = [];
let addTransactionToOptions = (options) => {
let ret = JSON.stringify({...options, transaction: '###TRANSACTION###'});
ret = ret.replace('"###TRANSACTION###"', 'transaction');
return ret;
};

let commands = [];
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);
commands.push(resUp);

consoleOut.push(`createTable "${action.tableName}", deps: [${action.depends.join(', ')}]`);
}
break;

case 'dropTable':
{
let res = `{ fn: "dropTable", params: ["${action.tableName}"] }`;
commandsUp.push(res);
let res = `{ fn: "dropTable", params: ["${action.tableName}", {transaction: transaction}] }`;
commands.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);
commands.push(resUp);

consoleOut.push(`addColumn "${action.attributeName}" to table "${action.tableName}"`);
}
break;

case 'removeColumn':
{
let res = `{ fn: "removeColumn", params: ["${action.tableName}", "${(action.options && action.options.field) ? action.options.field : action.columnName}"] }`;
commandsUp.push(res);

let res = `{ fn: "removeColumn", params: [
"${action.tableName}",
"${(action.options && action.options.field) ? action.options.field : action.columnName}",
{transaction: transaction}
]
}`;
commands.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);
commands.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);
commands.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);
commands.push(res);

consoleOut.push(`removeIndex ${nameOrAttrs} from table "${action.tableName}"`);
}

default:
// code
}
}

return { commandsUp, consoleOut };
return { commands, consoleOut };
};

const getMigration = function(upActions, downActions)
{
let { commands: commandsUp, consoleOut } = getPartialMigration(upActions);
let { commands: commandsDown } = getPartialMigration(downActions);
return { commandsUp, commandsDown, consoleOut };
};


const writeMigration = function(revision, migration, migrationsDir, name = '', comment = '')
{
let _commands = "var migrationCommands = [ \n" + migration.commandsUp.join(", \n") +' \n];\n';
let _commandsUp = "var migrationCommands = function(transaction) {return [ \n" + migration.commandsUp.join(", \n") +' \n];};\n';
let _commandsDown = "var rollbackCommands = function(transaction) {return [ \n" + migration.commandsDown.join(", \n") +' \n];};\n';
let _actions = ' * ' + migration.consoleOut.join("\n * ");

_commands = beautify(_commands);

_commandsUp = beautify(_commandsUp);
_commandsDown = beautify(_commandsDown);
let info = {
revision,
name,
Expand All @@ -783,27 +806,45 @@ ${_actions}

var info = ${JSON.stringify(info, null, 4)};

${_commands}
${_commandsUp}
${_commandsDown}

module.exports = {
pos: 0,
up: function(queryInterface, Sequelize)
useTransaction: true,
execute: function(queryInterface, Sequelize, _commands)
{
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 = _commands(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);
}
},
up: function(queryInterface, Sequelize)
{
return this.execute(queryInterface, Sequelize, migrationCommands);
},
down: function(queryInterface, Sequelize)
{
return this.execute(queryInterface, Sequelize, rollbackCommands);
},
info: info
};
Expand All @@ -817,7 +858,7 @@ module.exports = {
return {filename, info};
};

const executeMigration = function(queryInterface, filename, pos, cb)
const executeMigration = function(queryInterface, filename, useTransaction, pos, rollback, cb)
{
let mig = require(filename);

Expand All @@ -829,15 +870,30 @@ const executeMigration = function(queryInterface, filename, pos, cb)
console.log("Set position to "+pos);
mig.pos = pos;
}
mig.useTransaction = useTransaction;

mig.up(queryInterface, Sequelize).then(
() => {
cb();
},
(err) => {
cb(err);
if (rollback) {
if (typeof mig.down !== 'function') {
return cb("No rollback command");
}
);
mig.down(queryInterface, Sequelize).then(
() => {
cb();
},
(err) => {
cb(err);
}
);
} else {
mig.up(queryInterface, Sequelize).then(
() => {
cb();
},
(err) => {
cb(err);
}
);
}
};

module.exports = { writeMigration, getMigration, sortActions, parseDifference, reverseModels, executeMigration };