2015-05-09 13:58:18 -04:00
|
|
|
// Migrator
|
|
|
|
// -------
|
2016-05-17 01:01:34 +10:00
|
|
|
import fs from 'fs';
|
|
|
|
import path from 'path';
|
|
|
|
import mkdirp from 'mkdirp';
|
2016-08-09 17:23:07 -04:00
|
|
|
import Promise from 'bluebird';
|
2016-05-17 01:01:34 +10:00
|
|
|
import * as helpers from '../helpers';
|
|
|
|
import {
|
2016-08-09 17:03:08 -04:00
|
|
|
assign, bind, difference, each, filter, get, includes, isBoolean,
|
|
|
|
isEmpty, isUndefined, map, max, template
|
2016-05-17 01:01:34 +10:00
|
|
|
} from 'lodash'
|
|
|
|
import inherits from 'inherits';
|
2015-11-16 10:22:01 -06:00
|
|
|
|
2015-12-08 23:28:37 +02:00
|
|
|
function LockError(msg) {
|
2015-11-16 10:22:01 -06:00
|
|
|
this.name = 'MigrationLocked';
|
2015-12-08 23:28:37 +02:00
|
|
|
this.message = msg;
|
2015-11-16 10:22:01 -06:00
|
|
|
}
|
|
|
|
inherits(LockError, Error);
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-08-30 11:33:12 +03:00
|
|
|
const CONFIG_DEFAULT = Object.freeze({
|
|
|
|
extension: 'js',
|
2017-07-24 14:19:17 +03:00
|
|
|
loadExtensions: [
|
|
|
|
'.co', '.coffee', '.eg', '.iced', '.js', '.litcoffee', '.ls', '.ts'
|
|
|
|
],
|
2015-08-30 11:33:12 +03:00
|
|
|
tableName: 'knex_migrations',
|
2018-04-05 01:19:08 +03:00
|
|
|
schemaName: null,
|
2015-08-30 11:33:12 +03:00
|
|
|
directory: './migrations',
|
|
|
|
disableTransactions: false
|
|
|
|
});
|
|
|
|
|
2015-05-09 13:58:18 -04:00
|
|
|
// The new migration we're performing, typically called from the `knex.migrate`
|
|
|
|
// interface on the main `knex` object. Passes the `knex` instance performing
|
|
|
|
// the migration.
|
2015-05-20 11:08:27 -04:00
|
|
|
export default class Migrator {
|
2015-07-28 02:06:24 -05:00
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
constructor(knex) {
|
2018-04-05 01:19:08 +03:00
|
|
|
this.knex = knex;
|
2015-05-20 11:08:27 -04:00
|
|
|
this.config = this.setConfig(knex.client.config.migrations);
|
2018-04-05 01:19:08 +03:00
|
|
|
|
2017-10-18 03:50:30 -04:00
|
|
|
this._activeMigration = {
|
|
|
|
fileName: null
|
|
|
|
}
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
// Migrators to the latest configuration.
|
|
|
|
latest(config) {
|
|
|
|
this.config = this.setConfig(config);
|
|
|
|
return this._migrationData()
|
|
|
|
.tap(validateMigrationList)
|
|
|
|
.spread((all, completed) => {
|
2017-03-27 17:39:08 +03:00
|
|
|
const migrations = difference(all, completed);
|
|
|
|
|
|
|
|
const transactionForAll = !this.config.disableTransactions
|
|
|
|
&& isEmpty(filter(migrations, name => {
|
|
|
|
const migration = require(path.join(this._absoluteConfigDir(), name));
|
|
|
|
return !this._useTransaction(migration);
|
|
|
|
}));
|
|
|
|
|
|
|
|
if (transactionForAll) {
|
|
|
|
return this.knex.transaction(trx => this._runBatch(migrations, 'up', trx));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return this._runBatch(migrations, 'up');
|
|
|
|
}
|
2015-05-20 11:08:27 -04:00
|
|
|
})
|
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
// Rollback the last "batch" of migrations that were run.
|
|
|
|
rollback(config) {
|
|
|
|
return Promise.try(() => {
|
|
|
|
this.config = this.setConfig(config);
|
|
|
|
return this._migrationData()
|
|
|
|
.tap(validateMigrationList)
|
|
|
|
.then((val) => this._getLastBatch(val))
|
|
|
|
.then((migrations) => {
|
2016-03-02 16:52:32 +01:00
|
|
|
return this._runBatch(map(migrations, 'name'), 'down');
|
2015-05-20 11:08:27 -04:00
|
|
|
});
|
|
|
|
})
|
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-11-18 16:31:54 -06:00
|
|
|
status(config) {
|
|
|
|
this.config = this.setConfig(config);
|
|
|
|
|
|
|
|
return Promise.all([
|
2018-04-05 01:19:08 +03:00
|
|
|
getTable(this.knex, this.config.tableName, this.config.schemaName)
|
|
|
|
.select('*'),
|
2015-11-18 16:31:54 -06:00
|
|
|
this._listAll()
|
|
|
|
])
|
2017-03-27 17:39:08 +03:00
|
|
|
.spread((db, code) => db.length - code.length);
|
2015-11-18 16:31:54 -06:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// Retrieves and returns the current migration version we're on, as a promise.
|
|
|
|
// If no migrations have been run yet, return "none".
|
2015-05-20 11:08:27 -04:00
|
|
|
currentVersion(config) {
|
|
|
|
this.config = this.setConfig(config);
|
2017-11-01 01:10:26 +03:00
|
|
|
return this._listCompleted()
|
2015-05-20 11:08:27 -04:00
|
|
|
.then((completed) => {
|
2016-08-09 17:03:08 -04:00
|
|
|
const val = max(map(completed, value => value.split('_')[0]));
|
2016-03-02 16:52:32 +01:00
|
|
|
return (isUndefined(val) ? 'none' : val);
|
2015-05-20 11:08:27 -04:00
|
|
|
})
|
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-12-13 15:05:31 +02:00
|
|
|
forceFreeMigrationsLock(config) {
|
|
|
|
this.config = this.setConfig(config);
|
2016-05-17 01:01:34 +10:00
|
|
|
const lockTable = this._getLockTableName();
|
2018-04-05 01:19:08 +03:00
|
|
|
return getSchemaBuilder(this.knex, this.config.schemaName).hasTable(lockTable)
|
2017-03-27 17:39:08 +03:00
|
|
|
.then(exist => exist && this._freeLock());
|
2015-12-13 15:05:31 +02:00
|
|
|
}
|
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
// Creates a new migration, with a given name.
|
|
|
|
make(name, config) {
|
|
|
|
this.config = this.setConfig(config);
|
2016-10-11 11:00:46 +02:00
|
|
|
if (!name) {
|
|
|
|
return Promise.reject(new Error('A name must be specified for the generated migration'));
|
|
|
|
}
|
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
return this._ensureFolder(config)
|
|
|
|
.then((val) => this._generateStubTemplate(val))
|
|
|
|
.then((val) => this._writeNewMigration(name, val));
|
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
// Lists all available migration versions, as a sorted array.
|
|
|
|
_listAll(config) {
|
|
|
|
this.config = this.setConfig(config);
|
2017-07-24 14:19:17 +03:00
|
|
|
const loadExtensions = this.config.loadExtensions;
|
2016-03-15 20:51:27 +01:00
|
|
|
return Promise.promisify(fs.readdir, {context: fs})(this._absoluteConfigDir())
|
2016-05-17 01:01:34 +10:00
|
|
|
.then(migrations => {
|
2016-03-02 16:52:32 +01:00
|
|
|
return filter(migrations, function(value) {
|
2016-05-17 01:01:34 +10:00
|
|
|
const extension = path.extname(value);
|
2017-07-24 14:19:17 +03:00
|
|
|
return includes(loadExtensions, extension);
|
2015-05-20 11:08:27 -04:00
|
|
|
}).sort();
|
|
|
|
})
|
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// Ensures a folder for the migrations exist, dependent on the migration
|
|
|
|
// config settings.
|
2015-05-20 11:08:27 -04:00
|
|
|
_ensureFolder() {
|
2016-05-17 01:01:34 +10:00
|
|
|
const dir = this._absoluteConfigDir();
|
2016-03-15 20:51:27 +01:00
|
|
|
return Promise.promisify(fs.stat, {context: fs})(dir)
|
2016-05-17 01:01:34 +10:00
|
|
|
.catch(() => Promise.promisify(mkdirp)(dir));
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// Ensures that a proper table has been created, dependent on the migration
|
|
|
|
// config settings.
|
2017-03-27 17:39:08 +03:00
|
|
|
_ensureTable(trx = this.knex) {
|
2018-04-05 01:19:08 +03:00
|
|
|
const { tableName, schemaName } = this.config;
|
2016-05-17 01:01:34 +10:00
|
|
|
const lockTable = this._getLockTableName();
|
2018-04-05 01:19:08 +03:00
|
|
|
const lockTableWithSchema = this._getLockTableNameWithSchema();
|
|
|
|
return getSchemaBuilder(trx, schemaName).hasTable(tableName)
|
|
|
|
.then(exists => !exists && this._createMigrationTable(tableName, schemaName, trx))
|
|
|
|
.then(() => getSchemaBuilder(trx, schemaName).hasTable(lockTable))
|
2017-03-27 17:39:08 +03:00
|
|
|
.then(exists => !exists && this._createMigrationLockTable(lockTable, trx))
|
2018-04-05 01:19:08 +03:00
|
|
|
.then(() => getTable(trx, lockTable, this.config.schemaName).select('*'))
|
|
|
|
.then(data => !data.length && trx.into(lockTableWithSchema).insert({ is_locked: 0 }));
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2018-04-05 01:19:08 +03:00
|
|
|
_createMigrationTable(tableName, schemaName, trx = this.knex) {
|
|
|
|
return getSchemaBuilder(trx, schemaName).createTable(getTableName(tableName), function(t) {
|
2015-05-20 11:08:27 -04:00
|
|
|
t.increments();
|
|
|
|
t.string('name');
|
|
|
|
t.integer('batch');
|
|
|
|
t.timestamp('migration_time');
|
2015-05-09 13:58:18 -04:00
|
|
|
});
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2017-03-27 17:39:08 +03:00
|
|
|
_createMigrationLockTable(tableName, trx = this.knex) {
|
2018-04-05 01:19:08 +03:00
|
|
|
return getSchemaBuilder(trx, this.config.schemaName).createTable(tableName, function(t) {
|
2018-04-25 10:56:28 -07:00
|
|
|
t.increments('index').primary();
|
2015-11-16 10:22:01 -06:00
|
|
|
t.integer('is_locked');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-12-08 23:28:37 +02:00
|
|
|
_getLockTableName() {
|
|
|
|
return this.config.tableName + '_lock';
|
2015-11-16 10:22:01 -06:00
|
|
|
}
|
|
|
|
|
2018-04-05 01:19:08 +03:00
|
|
|
_getLockTableNameWithSchema() {
|
|
|
|
return this.config.schemaName ?
|
|
|
|
this.config.schemaName + '.' + this._getLockTableName()
|
|
|
|
: this._getLockTableName();
|
|
|
|
}
|
|
|
|
|
2015-11-16 10:22:01 -06:00
|
|
|
_isLocked(trx) {
|
2016-05-17 01:01:34 +10:00
|
|
|
const tableName = this._getLockTableName();
|
2018-04-05 01:19:08 +03:00
|
|
|
return getTable(this.knex, tableName, this.config.schemaName)
|
2015-11-16 10:22:01 -06:00
|
|
|
.transacting(trx)
|
|
|
|
.forUpdate()
|
|
|
|
.select('*')
|
|
|
|
.then(data => data[0].is_locked);
|
|
|
|
}
|
|
|
|
|
|
|
|
_lockMigrations(trx) {
|
2016-05-17 01:01:34 +10:00
|
|
|
const tableName = this._getLockTableName();
|
2018-04-05 01:19:08 +03:00
|
|
|
return getTable(this.knex, tableName, this.config.schemaName)
|
2015-11-16 10:22:01 -06:00
|
|
|
.transacting(trx)
|
|
|
|
.update({ is_locked: 1 });
|
|
|
|
}
|
|
|
|
|
2017-03-27 17:39:08 +03:00
|
|
|
_getLock(trx) {
|
|
|
|
const transact = trx ? fn => fn(trx) : fn => this.knex.transaction(fn);
|
|
|
|
|
|
|
|
return transact(trx => {
|
2015-11-16 10:22:01 -06:00
|
|
|
return this._isLocked(trx)
|
|
|
|
.then(isLocked => {
|
|
|
|
if (isLocked) {
|
2015-12-15 21:43:57 +02:00
|
|
|
throw new Error("Migration table is already locked");
|
2015-11-16 10:22:01 -06:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.then(() => this._lockMigrations(trx));
|
2015-12-15 21:43:57 +02:00
|
|
|
}).catch(err => {
|
|
|
|
throw new LockError(err.message);
|
2015-12-08 23:28:37 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-03-27 17:39:08 +03:00
|
|
|
_freeLock(trx = this.knex) {
|
2016-05-17 01:01:34 +10:00
|
|
|
const tableName = this._getLockTableName();
|
2018-04-05 01:19:08 +03:00
|
|
|
return getTable(trx, tableName, this.config.schemaName)
|
2015-12-13 15:05:31 +02:00
|
|
|
.update({ is_locked: 0 });
|
2015-12-08 23:28:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run a batch of current migrations, in sequence.
|
2017-03-27 17:39:08 +03:00
|
|
|
_runBatch(migrations, direction, trx) {
|
|
|
|
return this._getLock(trx)
|
|
|
|
// When there is a wrapping transaction, some migrations
|
|
|
|
// could have been done while waiting for the lock:
|
|
|
|
.then(() => trx ? this._listCompleted(trx) : [])
|
|
|
|
.then(completed => migrations = difference(migrations, completed))
|
|
|
|
.then(() => Promise.all(map(migrations, bind(this._validateMigrationStructure, this))))
|
|
|
|
.then(() => this._latestBatchNumber(trx))
|
|
|
|
.then(batchNo => {
|
|
|
|
if (direction === 'up') batchNo++;
|
|
|
|
return batchNo;
|
|
|
|
})
|
|
|
|
.then(batchNo => {
|
|
|
|
return this._waterfallBatch(batchNo, migrations, direction, trx)
|
|
|
|
})
|
|
|
|
.tap(() => this._freeLock(trx))
|
|
|
|
.catch(error => {
|
|
|
|
let cleanupReady = Promise.resolve();
|
|
|
|
|
|
|
|
if (error instanceof LockError) {
|
|
|
|
// If locking error do not free the lock.
|
|
|
|
helpers.warn(`Can't take lock to run migrations: ${error.message}`);
|
|
|
|
helpers.warn(
|
|
|
|
'If you are sure migrations are not running you can release the ' +
|
|
|
|
'lock manually by deleting all the rows from migrations lock ' +
|
2018-04-05 01:19:08 +03:00
|
|
|
'table: ' + this._getLockTableNameWithSchema()
|
2017-03-27 17:39:08 +03:00
|
|
|
);
|
|
|
|
} else {
|
2017-12-08 12:26:14 +01:00
|
|
|
if (this._activeMigration.fileName) {
|
|
|
|
helpers.warn(`migration file "${this._activeMigration.fileName}" failed`)
|
|
|
|
}
|
2017-10-18 03:50:30 -04:00
|
|
|
helpers.warn(`migration failed with error: ${error.message}`)
|
2017-03-27 17:39:08 +03:00
|
|
|
// If the error was not due to a locking issue, then remove the lock.
|
|
|
|
cleanupReady = this._freeLock(trx);
|
|
|
|
}
|
|
|
|
|
|
|
|
return cleanupReady.finally(function() {
|
|
|
|
throw error;
|
|
|
|
});
|
2015-12-08 23:28:37 +02:00
|
|
|
});
|
2016-05-17 01:01:34 +10:00
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// Validates some migrations by requiring and checking for an `up` and `down`
|
|
|
|
// function.
|
2015-05-20 11:08:27 -04:00
|
|
|
_validateMigrationStructure(name) {
|
2016-05-17 01:01:34 +10:00
|
|
|
const migration = require(path.join(this._absoluteConfigDir(), name));
|
2015-05-20 11:08:27 -04:00
|
|
|
if (typeof migration.up !== 'function' || typeof migration.down !== 'function') {
|
2016-05-17 01:01:34 +10:00
|
|
|
throw new Error(`Invalid migration: ${name} must have both an up and down function`);
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
|
|
|
return name;
|
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// Lists all migrations that have been completed for the current db, as an
|
|
|
|
// array.
|
2017-03-27 17:39:08 +03:00
|
|
|
_listCompleted(trx = this.knex) {
|
2018-04-05 01:19:08 +03:00
|
|
|
const { tableName, schemaName } = this.config;
|
2017-03-27 17:39:08 +03:00
|
|
|
return this._ensureTable(trx)
|
2018-04-05 01:19:08 +03:00
|
|
|
.then(() => trx.from(getTableName(tableName, schemaName)).orderBy('id').select('name'))
|
2016-03-02 16:52:32 +01:00
|
|
|
.then((migrations) => map(migrations, 'name'))
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// Gets the migration list from the specified migration directory, as well as
|
|
|
|
// the list of completed migrations to check what should be run.
|
2015-05-20 11:08:27 -04:00
|
|
|
_migrationData() {
|
|
|
|
return Promise.all([
|
|
|
|
this._listAll(),
|
|
|
|
this._listCompleted()
|
|
|
|
]);
|
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// Generates the stub template for the current migration, returning a compiled
|
|
|
|
// template.
|
2015-05-20 11:08:27 -04:00
|
|
|
_generateStubTemplate() {
|
2016-05-17 01:01:34 +10:00
|
|
|
const stubPath = this.config.stub ||
|
|
|
|
path.join(__dirname, 'stub', this.config.extension + '.stub');
|
|
|
|
return Promise.promisify(fs.readFile, {context: fs})(stubPath).then(stub =>
|
|
|
|
template(stub.toString(), {variable: 'd'})
|
|
|
|
);
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
// Write a new migration to disk, using the config and generated filename,
|
|
|
|
// passing any `variables` given in the config to the template.
|
|
|
|
_writeNewMigration(name, tmpl) {
|
2016-05-17 01:01:34 +10:00
|
|
|
const { config } = this;
|
|
|
|
const dir = this._absoluteConfigDir();
|
2015-05-09 13:58:18 -04:00
|
|
|
if (name[0] === '-') name = name.slice(1);
|
2016-05-18 19:59:24 +10:00
|
|
|
const filename = yyyymmddhhmmss() + '_' + name + '.' + config.extension;
|
2016-03-15 20:51:27 +01:00
|
|
|
return Promise.promisify(fs.writeFile, {context: fs})(
|
2015-05-09 13:58:18 -04:00
|
|
|
path.join(dir, filename),
|
|
|
|
tmpl(config.variables || {})
|
|
|
|
).return(path.join(dir, filename));
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// Get the last batch of migrations, by name, ordered by insert id in reverse
|
|
|
|
// order.
|
2015-05-20 11:08:27 -04:00
|
|
|
_getLastBatch() {
|
2018-04-05 01:19:08 +03:00
|
|
|
const { tableName, schemaName } = this.config;
|
|
|
|
return getTable(this.knex, tableName, schemaName)
|
2015-05-20 11:08:27 -04:00
|
|
|
.where('batch', function(qb) {
|
2018-04-05 01:19:08 +03:00
|
|
|
qb.max('batch').from(getTableName(tableName, schemaName))
|
2015-05-20 11:08:27 -04:00
|
|
|
})
|
|
|
|
.orderBy('id', 'desc');
|
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
// Returns the latest batch number.
|
2017-03-27 17:39:08 +03:00
|
|
|
_latestBatchNumber(trx = this.knex) {
|
2018-04-05 01:19:08 +03:00
|
|
|
return trx.from(getTableName(this.config.tableName, this.config.schemaName))
|
2016-05-17 01:01:34 +10:00
|
|
|
.max('batch as max_batch').then(obj => obj[0].max_batch || 0);
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// If transaction config for a single migration is defined, use that.
|
2015-08-30 14:57:55 +03:00
|
|
|
// Otherwise, rely on the common config. This allows enabling/disabling
|
2016-03-26 09:01:00 +01:00
|
|
|
// transaction for a single migration at will, regardless of the common
|
2015-08-30 14:57:55 +03:00
|
|
|
// config.
|
|
|
|
_useTransaction(migration, allTransactionsDisabled) {
|
2016-05-17 01:01:34 +10:00
|
|
|
const singleTransactionValue = get(migration, 'config.transaction');
|
2015-08-30 14:57:55 +03:00
|
|
|
|
2016-03-02 16:52:32 +01:00
|
|
|
return isBoolean(singleTransactionValue) ?
|
2015-08-30 14:57:55 +03:00
|
|
|
singleTransactionValue :
|
|
|
|
!allTransactionsDisabled;
|
|
|
|
}
|
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// Runs a batch of `migrations` in a specified `direction`, saving the
|
|
|
|
// appropriate database information as the migrations are run.
|
2017-03-27 17:39:08 +03:00
|
|
|
_waterfallBatch(batchNo, migrations, direction, trx) {
|
|
|
|
const trxOrKnex = trx || this.knex;
|
2018-04-05 01:19:08 +03:00
|
|
|
const {tableName, schemaName, disableTransactions} = this.config;
|
2017-03-27 17:39:08 +03:00
|
|
|
const directory = this._absoluteConfigDir();
|
2016-05-18 19:59:24 +10:00
|
|
|
let current = Promise.bind({failed: false, failedOn: 0});
|
|
|
|
const log = [];
|
2016-03-02 16:52:32 +01:00
|
|
|
each(migrations, (migration) => {
|
2016-05-18 19:59:24 +10:00
|
|
|
const name = migration;
|
2017-10-18 03:50:30 -04:00
|
|
|
this._activeMigration.fileName = name;
|
2015-05-20 11:08:27 -04:00
|
|
|
migration = require(directory + '/' + name);
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// We're going to run each of the migrations in the current "up".
|
2015-05-20 11:16:13 -04:00
|
|
|
current = current.then(() => {
|
2017-03-27 17:39:08 +03:00
|
|
|
if (!trx && this._useTransaction(migration, disableTransactions)) {
|
2015-08-30 14:57:55 +03:00
|
|
|
return this._transaction(migration, direction, name)
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2017-03-27 17:39:08 +03:00
|
|
|
return warnPromise(migration[direction](trxOrKnex, Promise), name)
|
2015-05-20 11:08:27 -04:00
|
|
|
})
|
2017-03-27 17:39:08 +03:00
|
|
|
.then(() => {
|
|
|
|
log.push(path.join(directory, name));
|
|
|
|
if (direction === 'up') {
|
2018-04-05 01:19:08 +03:00
|
|
|
return trxOrKnex.into(getTableName(tableName, schemaName)).insert({
|
2017-03-27 17:39:08 +03:00
|
|
|
name,
|
|
|
|
batch: batchNo,
|
|
|
|
migration_time: new Date()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (direction === 'down') {
|
2018-04-05 01:19:08 +03:00
|
|
|
return trxOrKnex.from(getTableName(tableName, schemaName)).where({name}).del();
|
2017-03-27 17:39:08 +03:00
|
|
|
}
|
|
|
|
});
|
2018-04-05 01:19:08 +03:00
|
|
|
});
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
return current.thenReturn([batchNo, log]);
|
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-05-20 11:16:13 -04:00
|
|
|
_transaction(migration, direction, name) {
|
|
|
|
return this.knex.transaction((trx) => {
|
|
|
|
return warnPromise(migration[direction](trx, Promise), name, () => {
|
|
|
|
trx.commit()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
_absoluteConfigDir() {
|
|
|
|
return path.resolve(process.cwd(), this.config.directory);
|
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
setConfig(config) {
|
2015-08-30 11:33:12 +03:00
|
|
|
return assign({}, CONFIG_DEFAULT, this.config || {}, config);
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
|
|
|
// Validates that migrations are present in the appropriate directories.
|
|
|
|
function validateMigrationList(migrations) {
|
2016-05-17 01:01:34 +10:00
|
|
|
const all = migrations[0];
|
|
|
|
const completed = migrations[1];
|
|
|
|
const diff = difference(completed, all);
|
2016-03-02 16:52:32 +01:00
|
|
|
if (!isEmpty(diff)) {
|
2015-05-09 13:58:18 -04:00
|
|
|
throw new Error(
|
2016-05-17 01:01:34 +10:00
|
|
|
`The migration directory is corrupt, the following files are missing: ${diff.join(', ')}`
|
2015-05-09 13:58:18 -04:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-20 11:16:13 -04:00
|
|
|
function warnPromise(value, name, fn) {
|
2015-05-09 13:58:18 -04:00
|
|
|
if (!value || typeof value.then !== 'function') {
|
2015-05-20 11:16:13 -04:00
|
|
|
helpers.warn(`migration ${name} did not return a promise`);
|
|
|
|
if (fn && typeof fn === 'function') fn()
|
2015-05-09 13:58:18 -04:00
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// Ensure that we have 2 places for each of the date segments.
|
2015-05-20 11:08:27 -04:00
|
|
|
function padDate(segment) {
|
2015-05-09 13:58:18 -04:00
|
|
|
segment = segment.toString();
|
2016-05-17 01:01:34 +10:00
|
|
|
return segment[1] ? segment : `0${segment}`;
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2015-05-09 13:58:18 -04:00
|
|
|
|
2016-03-26 09:01:00 +01:00
|
|
|
// Get a date object in the correct format, without requiring a full out library
|
|
|
|
// like "moment.js".
|
2015-05-20 11:08:27 -04:00
|
|
|
function yyyymmddhhmmss() {
|
2016-05-17 01:01:34 +10:00
|
|
|
const d = new Date();
|
2015-05-09 13:58:18 -04:00
|
|
|
return d.getFullYear().toString() +
|
2017-03-27 17:39:08 +03:00
|
|
|
padDate(d.getMonth() + 1) +
|
|
|
|
padDate(d.getDate()) +
|
|
|
|
padDate(d.getHours()) +
|
|
|
|
padDate(d.getMinutes()) +
|
|
|
|
padDate(d.getSeconds());
|
2015-05-20 11:08:27 -04:00
|
|
|
}
|
2018-04-05 01:19:08 +03:00
|
|
|
|
|
|
|
//Get schema-aware table name
|
|
|
|
function getTableName(tableName, schemaName) {
|
|
|
|
return schemaName ?
|
|
|
|
`${schemaName}.${tableName}`
|
|
|
|
: tableName;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Get schema-aware query builder for a given table and schema name
|
|
|
|
function getTable(trxOrKnex, tableName, schemaName) {
|
|
|
|
return schemaName ? trxOrKnex(tableName).withSchema(schemaName)
|
|
|
|
: trxOrKnex(tableName);
|
|
|
|
}
|
|
|
|
|
|
|
|
//Get schema-aware schema builder for a given schema nam
|
|
|
|
function getSchemaBuilder(trxOrKnex, schemaName) {
|
|
|
|
return schemaName ? trxOrKnex.schema.withSchema(schemaName)
|
|
|
|
: trxOrKnex.schema;
|
|
|
|
}
|