Add disableTransactions option, #834

This commit is contained in:
Tim Griesser 2015-05-20 11:08:27 -04:00
parent 234a26e501
commit 01bae6a598
4 changed files with 450 additions and 401 deletions

View File

@ -1,3 +1,9 @@
'use strict';
exports.__esModule = true;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
// Migrator // Migrator
// ------- // -------
'use strict'; 'use strict';
@ -8,35 +14,51 @@ var _ = require('lodash');
var mkdirp = require('mkdirp'); var mkdirp = require('mkdirp');
var Promise = require('../promise'); var Promise = require('../promise');
var helpers = require('../helpers'); var helpers = require('../helpers');
var assign = require('lodash/object/assign');
// The new migration we're performing, typically called from the `knex.migrate` // The new migration we're performing, typically called from the `knex.migrate`
// interface on the main `knex` object. Passes the `knex` instance performing // interface on the main `knex` object. Passes the `knex` instance performing
// the migration. // the migration.
var Migrator = (function () {
function Migrator(knex) { function Migrator(knex) {
_classCallCheck(this, Migrator);
this.knex = knex; this.knex = knex;
this.config = this.setConfig(knex.client.config.migrations); this.config = this.setConfig(knex.client.config.migrations);
} }
// Migrators to the latest configuration. // Migrators to the latest configuration.
Migrator.prototype.latest = Promise.method(function (config) {
Migrator.prototype.latest = function latest(config) {
var _this = this;
this.config = this.setConfig(config); this.config = this.setConfig(config);
return this._migrationData().tap(validateMigrationList).bind(this).spread(function (all, completed) { return this._migrationData().tap(validateMigrationList).spread(function (all, completed) {
return this._runBatch(_.difference(all, completed), 'up'); return _this._runBatch(_.difference(all, completed), 'up');
});
}); });
};
// Rollback the last "batch" of migrations that were run. // Rollback the last "batch" of migrations that were run.
Migrator.prototype.rollback = Promise.method(function (config) {
this.config = this.setConfig(config); Migrator.prototype.rollback = function rollback(config) {
return this._migrationData().tap(validateMigrationList).bind(this).then(this._getLastBatch).then(function (migrations) { var _this2 = this;
return this._runBatch(_.pluck(migrations, 'name'), 'down');
return Promise['try'](function () {
_this2.config = _this2.setConfig(config);
return _this2._migrationData().tap(validateMigrationList).then(function (val) {
return _this2._getLastBatch(val);
}).then(function (migrations) {
return _this2._runBatch(_.pluck(migrations, 'name'), 'down');
}); });
}); });
};
// Retrieves and returns the current migration version // Retrieves and returns the current migration version
// we're on, as a promise. If there aren't any migrations run yet, // we're on, as a promise. If there aren't any migrations run yet,
// return "none" as the value for the `currentVersion`. // return "none" as the value for the `currentVersion`.
Migrator.prototype.currentVersion = function (config) {
Migrator.prototype.currentVersion = function currentVersion(config) {
this.config = this.setConfig(config); this.config = this.setConfig(config);
return this._listCompleted(config).then(function (completed) { return this._listCompleted(config).then(function (completed) {
var val = _.chain(completed).map(function (value) { var val = _.chain(completed).map(function (value) {
@ -47,26 +69,35 @@ Migrator.prototype.currentVersion = function (config) {
}; };
// Creates a new migration, with a given name. // Creates a new migration, with a given name.
Migrator.prototype.make = function (name, config) {
Migrator.prototype.make = function make(name, config) {
var _this3 = this;
this.config = this.setConfig(config); this.config = this.setConfig(config);
if (!name) Promise.rejected(new Error('A name must be specified for the generated migration')); if (!name) Promise.rejected(new Error('A name must be specified for the generated migration'));
return this._ensureFolder(config).bind(this).then(this._generateStubTemplate).then(this._writeNewMigration(name)); return this._ensureFolder(config).then(function (val) {
return _this3._generateStubTemplate(val);
}).then(function (val) {
return _this3._writeNewMigration(name, val);
});
}; };
// Lists all available migration versions, as a sorted array. // Lists all available migration versions, as a sorted array.
Migrator.prototype._listAll = Promise.method(function (config) {
Migrator.prototype._listAll = function _listAll(config) {
this.config = this.setConfig(config); this.config = this.setConfig(config);
return Promise.promisify(fs.readdir, fs)(this._absoluteConfigDir()).bind(this).then(function (migrations) { return Promise.promisify(fs.readdir, fs)(this._absoluteConfigDir()).then(function (migrations) {
return _.filter(migrations, function (value) { return _.filter(migrations, function (value) {
var extension = path.extname(value); var extension = path.extname(value);
return _.contains(['.co', '.coffee', '.iced', '.js', '.litcoffee', '.ls'], extension); return _.contains(['.co', '.coffee', '.iced', '.js', '.litcoffee', '.ls'], extension);
}).sort(); }).sort();
}); });
}); };
// Ensures a folder for the migrations exist, dependent on the // Ensures a folder for the migrations exist, dependent on the
// migration config settings. // migration config settings.
Migrator.prototype._ensureFolder = function () {
Migrator.prototype._ensureFolder = function _ensureFolder() {
var dir = this._absoluteConfigDir(); var dir = this._absoluteConfigDir();
return Promise.promisify(fs.stat, fs)(dir)['catch'](function () { return Promise.promisify(fs.stat, fs)(dir)['catch'](function () {
return Promise.promisify(mkdirp)(dir); return Promise.promisify(mkdirp)(dir);
@ -75,15 +106,19 @@ Migrator.prototype._ensureFolder = function () {
// Ensures that the proper table has been created, // Ensures that the proper table has been created,
// dependent on the migration config settings. // dependent on the migration config settings.
Migrator.prototype._ensureTable = Promise.method(function () {
Migrator.prototype._ensureTable = function _ensureTable() {
var _this4 = this;
var table = this.config.tableName; var table = this.config.tableName;
return this.knex.schema.hasTable(table).bind(this).then(function (exists) { return this.knex.schema.hasTable(table).then(function (exists) {
if (!exists) return this._createMigrationTable(table); if (!exists) return _this4._createMigrationTable(table);
});
}); });
};
// Create the migration table, if it doesn't already exist. // Create the migration table, if it doesn't already exist.
Migrator.prototype._createMigrationTable = function (tableName) {
Migrator.prototype._createMigrationTable = function _createMigrationTable(tableName) {
return this.knex.schema.createTable(tableName, function (t) { return this.knex.schema.createTable(tableName, function (t) {
t.increments(); t.increments();
t.string('name'); t.string('name');
@ -93,21 +128,26 @@ Migrator.prototype._createMigrationTable = function (tableName) {
}; };
// Run a batch of current migrations, in sequence. // Run a batch of current migrations, in sequence.
Migrator.prototype._runBatch = function (migrations, direction) {
return Promise.all(_.map(migrations, this._validateMigrationStructure, this)).bind(this).then(function (migrations) { Migrator.prototype._runBatch = function _runBatch(migrations, direction) {
return Promise.bind(this).then(this._latestBatchNumber).then(function (batchNo) { var _this5 = this;
return Promise.all(_.map(migrations, this._validateMigrationStructure, this)).then(function () {
return _this5._latestBatchNumber();
}).then(function (batchNo) {
if (direction === 'up') batchNo++; if (direction === 'up') batchNo++;
return batchNo; return batchNo;
}).then(function (batchNo) { }).then(function (batchNo) {
return this._waterfallBatch(batchNo, migrations, direction); return _this5._waterfallBatch(batchNo, migrations, direction);
})['catch'](function (error) { })['catch'](function (error) {
helpers.warn('migrations failed with error: ' + error.message); helpers.warn('migrations failed with error: ' + error.message);
}); throw error;
}); });
}; };
// Validates some migrations by requiring and checking for an `up` and `down` function. // Validates some migrations by requiring and checking for an `up` and `down` function.
Migrator.prototype._validateMigrationStructure = function (name) {
Migrator.prototype._validateMigrationStructure = function _validateMigrationStructure(name) {
var migration = require(path.join(this._absoluteConfigDir(), name)); var migration = require(path.join(this._absoluteConfigDir(), name));
if (typeof migration.up !== 'function' || typeof migration.down !== 'function') { if (typeof migration.up !== 'function' || typeof migration.down !== 'function') {
throw new Error('Invalid migration: ' + name + ' must have both an up and down function'); throw new Error('Invalid migration: ' + name + ' must have both an up and down function');
@ -116,24 +156,29 @@ Migrator.prototype._validateMigrationStructure = function (name) {
}; };
// Lists all migrations that have been completed for the current db, as an array. // Lists all migrations that have been completed for the current db, as an array.
Migrator.prototype._listCompleted = Promise.method(function () {
Migrator.prototype._listCompleted = function _listCompleted() {
var _this6 = this;
var tableName = this.config.tableName; var tableName = this.config.tableName;
return this._ensureTable(tableName).bind(this).then(function () { return this._ensureTable(tableName).then(function () {
return this.knex(tableName).orderBy('id').select('name'); return _this6.knex(tableName).orderBy('id').select('name');
}).then(function (migrations) { }).then(function (migrations) {
return _.pluck(migrations, 'name'); return _.pluck(migrations, 'name');
}); });
}); };
// Gets the migration list from the specified migration directory, // Gets the migration list from the specified migration directory,
// as well as the list of completed migrations to check what // as well as the list of completed migrations to check what
// should be run. // should be run.
Migrator.prototype._migrationData = function () {
Migrator.prototype._migrationData = function _migrationData() {
return Promise.all([this._listAll(), this._listCompleted()]); return Promise.all([this._listAll(), this._listCompleted()]);
}; };
// Generates the stub template for the current migration, returning a compiled template. // Generates the stub template for the current migration, returning a compiled template.
Migrator.prototype._generateStubTemplate = function () {
Migrator.prototype._generateStubTemplate = function _generateStubTemplate() {
var stubPath = this.config.stub || path.join(__dirname, 'stub', this.config.extension + '.stub'); var stubPath = this.config.stub || path.join(__dirname, 'stub', this.config.extension + '.stub');
return Promise.promisify(fs.readFile, fs)(stubPath).then(function (stub) { return Promise.promisify(fs.readFile, fs)(stubPath).then(function (stub) {
return _.template(stub.toString(), null, { variable: 'd' }); return _.template(stub.toString(), null, { variable: 'd' });
@ -142,19 +187,19 @@ Migrator.prototype._generateStubTemplate = function () {
// Write a new migration to disk, using the config and generated filename, // Write a new migration to disk, using the config and generated filename,
// passing any `variables` given in the config to the template. // passing any `variables` given in the config to the template.
Migrator.prototype._writeNewMigration = function (name) {
Migrator.prototype._writeNewMigration = function _writeNewMigration(name, tmpl) {
var config = this.config; var config = this.config;
var dir = this._absoluteConfigDir(); var dir = this._absoluteConfigDir();
return function (tmpl) {
if (name[0] === '-') name = name.slice(1); if (name[0] === '-') name = name.slice(1);
var filename = yyyymmddhhmmss() + '_' + name + '.' + config.extension; var filename = yyyymmddhhmmss() + '_' + name + '.' + config.extension;
return Promise.promisify(fs.writeFile, fs)(path.join(dir, filename), tmpl(config.variables || {}))['return'](path.join(dir, filename)); return Promise.promisify(fs.writeFile, fs)(path.join(dir, filename), tmpl(config.variables || {}))['return'](path.join(dir, filename));
}; };
};
// Get the last batch of migrations, by name, ordered by insert id // Get the last batch of migrations, by name, ordered by insert id
// in reverse order. // in reverse order.
Migrator.prototype._getLastBatch = function () {
Migrator.prototype._getLastBatch = function _getLastBatch() {
var tableName = this.config.tableName; var tableName = this.config.tableName;
return this.knex(tableName).where('batch', function (qb) { return this.knex(tableName).where('batch', function (qb) {
qb.max('batch').from(tableName); qb.max('batch').from(tableName);
@ -162,7 +207,8 @@ Migrator.prototype._getLastBatch = function () {
}; };
// Returns the latest batch number. // Returns the latest batch number.
Migrator.prototype._latestBatchNumber = function () {
Migrator.prototype._latestBatchNumber = function _latestBatchNumber() {
return this.knex(this.config.tableName).max('batch as max_batch').then(function (obj) { return this.knex(this.config.tableName).max('batch as max_batch').then(function (obj) {
return obj[0].max_batch || 0; return obj[0].max_batch || 0;
}); });
@ -170,9 +216,13 @@ Migrator.prototype._latestBatchNumber = function () {
// Runs a batch of `migrations` in a specified `direction`, // Runs a batch of `migrations` in a specified `direction`,
// saving the appropriate database information as the migrations are run. // saving the appropriate database information as the migrations are run.
Migrator.prototype._waterfallBatch = function (batchNo, migrations, direction) {
Migrator.prototype._waterfallBatch = function _waterfallBatch(batchNo, migrations, direction) {
var knex = this.knex; var knex = this.knex;
var tableName = this.config.tableName; var _config = this.config;
var tableName = _config.tableName;
var disableTransactions = _config.disableTransactions;
var directory = this._absoluteConfigDir(); var directory = this._absoluteConfigDir();
var current = Promise.bind({ failed: false, failedOn: 0 }); var current = Promise.bind({ failed: false, failedOn: 0 });
var log = []; var log = [];
@ -182,6 +232,9 @@ Migrator.prototype._waterfallBatch = function (batchNo, migrations, direction) {
// We're going to run each of the migrations in the current "up" // We're going to run each of the migrations in the current "up"
current = current.then(function () { current = current.then(function () {
if (disableTransactions) {
return warnPromise(migration[direction](knex, Promise), 'migration ' + name + ' did not return a promise');
}
return knex.transaction(function (trx) { return knex.transaction(function (trx) {
return warnPromise(migration[direction](trx, Promise), 'migration ' + name + ' did not return a promise'); return warnPromise(migration[direction](trx, Promise), 'migration ' + name + ' did not return a promise');
}); });
@ -203,18 +256,23 @@ Migrator.prototype._waterfallBatch = function (batchNo, migrations, direction) {
return current.thenReturn([batchNo, log]); return current.thenReturn([batchNo, log]);
}; };
Migrator.prototype._absoluteConfigDir = function () { Migrator.prototype._absoluteConfigDir = function _absoluteConfigDir() {
return path.resolve(process.cwd(), this.config.directory); return path.resolve(process.cwd(), this.config.directory);
}; };
Migrator.prototype.setConfig = function (config) { Migrator.prototype.setConfig = function setConfig(config) {
return _.extend({ return assign({
extension: 'js', extension: 'js',
tableName: 'knex_migrations', tableName: 'knex_migrations',
directory: './migrations' directory: './migrations'
}, this.config || {}, config); }, this.config || {}, config);
}; };
return Migrator;
})();
exports['default'] = Migrator;
// Validates that migrations are present in the appropriate directories. // Validates that migrations are present in the appropriate directories.
function validateMigrationList(migrations) { function validateMigrationList(migrations) {
var all = migrations[0]; var all = migrations[0];
@ -233,16 +291,15 @@ function warnPromise(value, message) {
} }
// Ensure that we have 2 places for each of the date segments // Ensure that we have 2 places for each of the date segments
var padDate = function padDate(segment) { function padDate(segment) {
segment = segment.toString(); segment = segment.toString();
return segment[1] ? segment : '0' + segment; return segment[1] ? segment : '0' + segment;
}; }
// Get a date object in the correct format, without requiring // Get a date object in the correct format, without requiring
// a full out library like "moment.js". // a full out library like "moment.js".
var yyyymmddhhmmss = function yyyymmddhhmmss() { function yyyymmddhhmmss() {
var d = new Date(); var d = new Date();
return d.getFullYear().toString() + padDate(d.getMonth() + 1) + padDate(d.getDate()) + padDate(d.getHours()) + padDate(d.getMinutes()) + padDate(d.getSeconds()); return d.getFullYear().toString() + padDate(d.getMonth() + 1) + padDate(d.getDate()) + padDate(d.getHours()) + padDate(d.getMinutes()) + padDate(d.getSeconds());
}; }
module.exports = exports['default'];
module.exports = Migrator;

View File

@ -42,7 +42,7 @@
"through": "^2.3.4" "through": "^2.3.4"
}, },
"scripts": { "scripts": {
"dev": "babel -D -w src/ --out-dir lib/", "dev": "babel -L -D -w src/ --out-dir lib/",
"build": "./scripts/build.sh", "build": "./scripts/build.sh",
"tape": "node test/tape/index.js", "tape": "node test/tape/index.js",
"test": "npm run jshint && istanbul --config=test/.istanbul.yml cover _mocha -- --check-leaks -t 5000 -b -R spec test/index.js && npm run tape", "test": "npm run jshint && istanbul --config=test/.istanbul.yml cover _mocha -- --check-leaks -t 5000 -b -R spec test/index.js && npm run tape",

View File

@ -8,206 +8,196 @@ var _ = require('lodash');
var mkdirp = require('mkdirp'); var mkdirp = require('mkdirp');
var Promise = require('../promise'); var Promise = require('../promise');
var helpers = require('../helpers'); var helpers = require('../helpers');
var assign = require('lodash/object/assign');
// The new migration we're performing, typically called from the `knex.migrate` // The new migration we're performing, typically called from the `knex.migrate`
// interface on the main `knex` object. Passes the `knex` instance performing // interface on the main `knex` object. Passes the `knex` instance performing
// the migration. // the migration.
function Migrator(knex) { export default class Migrator {
constructor(knex) {
this.knex = knex this.knex = knex
this.config = this.setConfig(knex.client.config.migrations); this.config = this.setConfig(knex.client.config.migrations);
} }
// Migrators to the latest configuration. // Migrators to the latest configuration.
Migrator.prototype.latest = Promise.method(function(config) { latest(config) {
this.config = this.setConfig(config); this.config = this.setConfig(config);
return this._migrationData() return this._migrationData()
.tap(validateMigrationList) .tap(validateMigrationList)
.bind(this) .spread((all, completed) => {
.spread(function(all, completed) {
return this._runBatch(_.difference(all, completed), 'up'); return this._runBatch(_.difference(all, completed), 'up');
}); })
}); }
// Rollback the last "batch" of migrations that were run. // Rollback the last "batch" of migrations that were run.
Migrator.prototype.rollback = Promise.method(function(config) { rollback(config) {
return Promise.try(() => {
this.config = this.setConfig(config); this.config = this.setConfig(config);
return this._migrationData() return this._migrationData()
.tap(validateMigrationList) .tap(validateMigrationList)
.bind(this) .then((val) => this._getLastBatch(val))
.then(this._getLastBatch) .then((migrations) => {
.then(function(migrations) {
return this._runBatch(_.pluck(migrations, 'name'), 'down'); return this._runBatch(_.pluck(migrations, 'name'), 'down');
}); });
}); })
}
// Retrieves and returns the current migration version // Retrieves and returns the current migration version
// we're on, as a promise. If there aren't any migrations run yet, // we're on, as a promise. If there aren't any migrations run yet,
// return "none" as the value for the `currentVersion`. // return "none" as the value for the `currentVersion`.
Migrator.prototype.currentVersion = function(config) { currentVersion(config) {
this.config = this.setConfig(config); this.config = this.setConfig(config);
return this._listCompleted(config).then(function(completed) { return this._listCompleted(config)
.then((completed) => {
var val = _.chain(completed).map(function(value) { var val = _.chain(completed).map(function(value) {
return value.split('_')[0]; return value.split('_')[0];
}).max().value(); }).max().value();
return (val === -Infinity ? 'none' : val); return (val === -Infinity ? 'none' : val);
}); })
}; }
// Creates a new migration, with a given name. // Creates a new migration, with a given name.
Migrator.prototype.make = function(name, config) { make(name, config) {
this.config = this.setConfig(config); this.config = this.setConfig(config);
if (!name) Promise.rejected(new Error('A name must be specified for the generated migration')); if (!name) Promise.rejected(new Error('A name must be specified for the generated migration'));
return this._ensureFolder(config) return this._ensureFolder(config)
.bind(this) .then((val) => this._generateStubTemplate(val))
.then(this._generateStubTemplate) .then((val) => this._writeNewMigration(name, val));
.then(this._writeNewMigration(name)); }
};
// Lists all available migration versions, as a sorted array. // Lists all available migration versions, as a sorted array.
Migrator.prototype._listAll = Promise.method(function(config) { _listAll(config) {
this.config = this.setConfig(config); this.config = this.setConfig(config);
return Promise.promisify(fs.readdir, fs)(this._absoluteConfigDir()) return Promise.promisify(fs.readdir, fs)(this._absoluteConfigDir())
.bind(this) .then((migrations) => {
.then(function(migrations) {
return _.filter(migrations, function(value) { return _.filter(migrations, function(value) {
var extension = path.extname(value); var extension = path.extname(value);
return _.contains(['.co', '.coffee', '.iced', '.js', '.litcoffee', '.ls'], extension); return _.contains(['.co', '.coffee', '.iced', '.js', '.litcoffee', '.ls'], extension);
}).sort(); }).sort();
}); })
}); }
// Ensures a folder for the migrations exist, dependent on the // Ensures a folder for the migrations exist, dependent on the
// migration config settings. // migration config settings.
Migrator.prototype._ensureFolder = function() { _ensureFolder() {
var dir = this._absoluteConfigDir(); var dir = this._absoluteConfigDir();
return Promise.promisify(fs.stat, fs)(dir) return Promise.promisify(fs.stat, fs)(dir)
.catch(function() { .catch(function() {
return Promise.promisify(mkdirp)(dir); return Promise.promisify(mkdirp)(dir);
}); });
}; }
// Ensures that the proper table has been created, // Ensures that the proper table has been created,
// dependent on the migration config settings. // dependent on the migration config settings.
Migrator.prototype._ensureTable = Promise.method(function() { _ensureTable() {
var table = this.config.tableName; var table = this.config.tableName;
return this.knex.schema.hasTable(table) return this.knex.schema.hasTable(table)
.bind(this) .then((exists) => {
.then(function(exists) {
if (!exists) return this._createMigrationTable(table); if (!exists) return this._createMigrationTable(table);
}); });
}); }
// Create the migration table, if it doesn't already exist. // Create the migration table, if it doesn't already exist.
Migrator.prototype._createMigrationTable = function(tableName) { _createMigrationTable(tableName) {
return this.knex.schema.createTable(tableName, function(t) { return this.knex.schema.createTable(tableName, function(t) {
t.increments(); t.increments();
t.string('name'); t.string('name');
t.integer('batch'); t.integer('batch');
t.timestamp('migration_time'); t.timestamp('migration_time');
}); });
}; }
// Run a batch of current migrations, in sequence. // Run a batch of current migrations, in sequence.
Migrator.prototype._runBatch = function(migrations, direction) { _runBatch(migrations, direction) {
return Promise.all(_.map(migrations, this._validateMigrationStructure, this)) return Promise.all(_.map(migrations, this._validateMigrationStructure, this))
.bind(this) .then(() => this._latestBatchNumber())
.then(function(migrations) { .then((batchNo) => {
return Promise.bind(this)
.then(this._latestBatchNumber)
.then(function(batchNo) {
if (direction === 'up') batchNo++; if (direction === 'up') batchNo++;
return batchNo; return batchNo;
}) })
.then(function(batchNo) { .then((batchNo) => {
return this._waterfallBatch(batchNo, migrations, direction) return this._waterfallBatch(batchNo, migrations, direction)
}) })
.catch(function(error) { .catch((error) => {
helpers.warn('migrations failed with error: ' + error.message) helpers.warn('migrations failed with error: ' + error.message)
throw error throw error
}); })
}); }
};
// Validates some migrations by requiring and checking for an `up` and `down` function. // Validates some migrations by requiring and checking for an `up` and `down` function.
Migrator.prototype._validateMigrationStructure = function(name) { _validateMigrationStructure(name) {
var migration = require(path.join(this._absoluteConfigDir(), name)); var migration = require(path.join(this._absoluteConfigDir(), name));
if (typeof migration.up !== 'function' || typeof migration.down !== 'function') { if (typeof migration.up !== 'function' || typeof migration.down !== 'function') {
throw new Error('Invalid migration: ' + name + ' must have both an up and down function'); throw new Error('Invalid migration: ' + name + ' must have both an up and down function');
} }
return name; return name;
}; }
// Lists all migrations that have been completed for the current db, as an array. // Lists all migrations that have been completed for the current db, as an array.
Migrator.prototype._listCompleted = Promise.method(function() { _listCompleted() {
var tableName = this.config.tableName; var tableName = this.config.tableName
return this._ensureTable(tableName) return this._ensureTable(tableName)
.bind(this) .then(() => this.knex(tableName).orderBy('id').select('name'))
.then(function () { .then((migrations) => _.pluck(migrations, 'name'))
return this.knex(tableName).orderBy('id').select('name'); }
})
.then(function(migrations) {
return _.pluck(migrations, 'name');
});
});
// Gets the migration list from the specified migration directory, // Gets the migration list from the specified migration directory,
// as well as the list of completed migrations to check what // as well as the list of completed migrations to check what
// should be run. // should be run.
Migrator.prototype._migrationData = function() { _migrationData() {
return Promise.all([ return Promise.all([
this._listAll(), this._listAll(),
this._listCompleted() this._listCompleted()
]); ]);
}; }
// Generates the stub template for the current migration, returning a compiled template. // Generates the stub template for the current migration, returning a compiled template.
Migrator.prototype._generateStubTemplate = function() { _generateStubTemplate() {
var stubPath = this.config.stub || path.join(__dirname, 'stub', this.config.extension + '.stub'); var stubPath = this.config.stub || path.join(__dirname, 'stub', this.config.extension + '.stub');
return Promise.promisify(fs.readFile, fs)(stubPath).then(function(stub) { return Promise.promisify(fs.readFile, fs)(stubPath).then(function(stub) {
return _.template(stub.toString(), null, {variable: 'd'}); return _.template(stub.toString(), null, {variable: 'd'});
}); });
}; }
// Write a new migration to disk, using the config and generated filename, // Write a new migration to disk, using the config and generated filename,
// passing any `variables` given in the config to the template. // passing any `variables` given in the config to the template.
Migrator.prototype._writeNewMigration = function(name) { _writeNewMigration(name, tmpl) {
var config = this.config; var config = this.config;
var dir = this._absoluteConfigDir(); var dir = this._absoluteConfigDir();
return function(tmpl) {
if (name[0] === '-') name = name.slice(1); if (name[0] === '-') name = name.slice(1);
var filename = yyyymmddhhmmss() + '_' + name + '.' + config.extension; var filename = yyyymmddhhmmss() + '_' + name + '.' + config.extension;
return Promise.promisify(fs.writeFile, fs)( return Promise.promisify(fs.writeFile, fs)(
path.join(dir, filename), path.join(dir, filename),
tmpl(config.variables || {}) tmpl(config.variables || {})
).return(path.join(dir, filename)); ).return(path.join(dir, filename));
}; }
};
// Get the last batch of migrations, by name, ordered by insert id // Get the last batch of migrations, by name, ordered by insert id
// in reverse order. // in reverse order.
Migrator.prototype._getLastBatch = function() { _getLastBatch() {
var tableName = this.config.tableName; var tableName = this.config.tableName;
return this.knex(tableName) return this.knex(tableName)
.where('batch', function(qb) { .where('batch', function(qb) {
qb.max('batch').from(tableName) qb.max('batch').from(tableName)
}) })
.orderBy('id', 'desc'); .orderBy('id', 'desc');
}; }
// Returns the latest batch number. // Returns the latest batch number.
Migrator.prototype._latestBatchNumber = function() { _latestBatchNumber() {
return this.knex(this.config.tableName) return this.knex(this.config.tableName)
.max('batch as max_batch').then(function(obj) { .max('batch as max_batch').then(function(obj) {
return (obj[0].max_batch || 0); return (obj[0].max_batch || 0);
}); });
}; }
// Runs a batch of `migrations` in a specified `direction`, // Runs a batch of `migrations` in a specified `direction`,
// saving the appropriate database information as the migrations are run. // saving the appropriate database information as the migrations are run.
Migrator.prototype._waterfallBatch = function(batchNo, migrations, direction) { _waterfallBatch(batchNo, migrations, direction) {
var knex = this.knex; var knex = this.knex;
var tableName = this.config.tableName; var {tableName, disableTransactions} = this.config
var directory = this._absoluteConfigDir(); var directory = this._absoluteConfigDir()
var current = Promise.bind({failed: false, failedOn: 0}); var current = Promise.bind({failed: false, failedOn: 0});
var log = []; var log = [];
_.each(migrations, function(migration) { _.each(migrations, function(migration) {
@ -216,10 +206,14 @@ Migrator.prototype._waterfallBatch = function(batchNo, migrations, direction) {
// We're going to run each of the migrations in the current "up" // We're going to run each of the migrations in the current "up"
current = current.then(function() { current = current.then(function() {
if (disableTransactions) {
return warnPromise(migration[direction](knex, Promise), 'migration ' + name + ' did not return a promise')
}
return knex.transaction(function(trx) { return knex.transaction(function(trx) {
return warnPromise(migration[direction](trx, Promise), 'migration ' + name + ' did not return a promise'); return warnPromise(migration[direction](trx, Promise), 'migration ' + name + ' did not return a promise');
}); });
}).then(function() { })
.then(() => {
log.push(path.join(directory, name)); log.push(path.join(directory, name));
if (direction === 'up') { if (direction === 'up') {
return knex(tableName).insert({ return knex(tableName).insert({
@ -232,23 +226,24 @@ Migrator.prototype._waterfallBatch = function(batchNo, migrations, direction) {
return knex(tableName).where({name: name}).del(); return knex(tableName).where({name: name}).del();
} }
}); });
}); })
return current.thenReturn([batchNo, log]); return current.thenReturn([batchNo, log]);
}; }
Migrator.prototype._absoluteConfigDir = function() { _absoluteConfigDir() {
return path.resolve(process.cwd(), this.config.directory); return path.resolve(process.cwd(), this.config.directory);
}; }
Migrator.prototype.setConfig = function(config) { setConfig(config) {
return _.extend({ return assign({
extension: 'js', extension: 'js',
tableName: 'knex_migrations', tableName: 'knex_migrations',
directory: './migrations' directory: './migrations'
}, this.config || {}, config); }, this.config || {}, config);
}; }
}
// Validates that migrations are present in the appropriate directories. // Validates that migrations are present in the appropriate directories.
function validateMigrationList(migrations) { function validateMigrationList(migrations) {
@ -270,14 +265,14 @@ function warnPromise(value, message) {
} }
// Ensure that we have 2 places for each of the date segments // Ensure that we have 2 places for each of the date segments
var padDate = function(segment) { function padDate(segment) {
segment = segment.toString(); segment = segment.toString();
return segment[1] ? segment : '0' + segment; return segment[1] ? segment : '0' + segment;
}; }
// Get a date object in the correct format, without requiring // Get a date object in the correct format, without requiring
// a full out library like "moment.js". // a full out library like "moment.js".
var yyyymmddhhmmss = function() { function yyyymmddhhmmss() {
var d = new Date(); var d = new Date();
return d.getFullYear().toString() + return d.getFullYear().toString() +
padDate(d.getMonth() + 1) + padDate(d.getMonth() + 1) +
@ -285,7 +280,4 @@ var yyyymmddhhmmss = function() {
padDate(d.getHours()) + padDate(d.getHours()) +
padDate(d.getMinutes()) + padDate(d.getMinutes()) +
padDate(d.getSeconds()); padDate(d.getSeconds());
}; }
module.exports = Migrator;

View File

@ -31,7 +31,7 @@ module.exports = function(knex) {
describe('knex.migrate.latest', function() { describe('knex.migrate.latest', function() {
before(function() { before(function() {
return knex.migrate.latest({directory: 'test/integration/migrate/test'}); return knex.migrate.latest({directory: 'test/integration/migrate/test'}).catch(function() {});
}); });
it('should run all migration files in the specified directory', function() { it('should run all migration files in the specified directory', function() {