Skip to content

Commit

Permalink
Merge 6d7ec60 into 33656be
Browse files Browse the repository at this point in the history
  • Loading branch information
leeallen337 committed May 29, 2019
2 parents 33656be + 6d7ec60 commit fa1b48a
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 2 deletions.
22 changes: 22 additions & 0 deletions bin/cli.js
Expand Up @@ -253,6 +253,28 @@ function invoke(env) {
.catch(exit);
});

commander
.command('migrate:down')
.description(' Undo the last migration performed.')
.action(() => {
pending = initKnex(env, commander.opts())
.migrate.down()
.spread((batchNo, log) => {
if (log.length === 0) {
success(color.cyan('Already at the base migration'));
}

success(
color.green(
`Batch ${batchNo} rolled back the following migrations:\n${log.join(
'\n'
)}`
)
);
})
.catch(exit);
});

commander
.command('migrate:currentVersion')
.description(' View the current version for the migration.')
Expand Down
23 changes: 23 additions & 0 deletions src/migrate/Migrator.js
Expand Up @@ -169,6 +169,29 @@ export default class Migrator {
});
}

down(config) {
this._disableProcessing();
this.config = getMergedConfig(config, this.config);

return migrationListResolver
.listAllAndCompleted(this.config, this.knex)
.tap((value) => {
return validateMigrationList(this.config.migrationSource, value);
})
.spread((all, completed) => {
const migrationToRun = all
.filter((migration) => {
return completed.includes(
this.config.migrationSource.getMigrationName(migration)
);
})
.reverse()
.slice(0, 1);

return this._runBatch(migrationToRun, 'down');
});
}

status(config) {
this._disableProcessing();
this.config = getMergedConfig(config, this.config);
Expand Down
1 change: 1 addition & 0 deletions src/migrate/migrate-stub.js
Expand Up @@ -15,4 +15,5 @@ StubMigrate.prototype = {
rollback: noSuchMethod,
currentVersion: noSuchMethod,
up: noSuchMethod,
down: noSuchMethod,
};
66 changes: 66 additions & 0 deletions test/integration/migrate/index.js
Expand Up @@ -582,6 +582,72 @@ module.exports = function(knex) {
});
});

describe('knex.migrate.down', () => {
beforeEach(() => {
return knex.migrate.latest({
directory: ['test/integration/migrate/test'],
});
});

afterEach(() => {
return knex.migrate.rollback(
{ directory: ['test/integration/migrate/test'] },
true
);
});

it('should only undo the last migration that was run if all migrations have run', function() {
return knex.migrate
.down({
directory: ['test/integration/migrate/test'],
})
.then(() => {
return knex('knex_migrations')
.select('*')
.then((data) => {
expect(data).to.have.length(1);
expect(path.basename(data[0].name)).to.equal(
'20131019235242_migration_1.js'
);
});
});
});

it('should only undo the last migration that was run if there are other migrations that have not yet run', function() {
return knex.migrate
.down({
directory: ['test/integration/migrate/test'],
})
.then(() => {
return knex.migrate
.down({
directory: ['test/integration/migrate/test'],
})
.then(() => {
return knex('knex_migrations')
.select('*')
.then((data) => {
expect(data).to.have.length(0);
});
});
});
});

it('should not error if all migrations have already been undone', function() {
return knex.migrate
.rollback({ directory: ['test/integration/migrate/test'] }, true)
.then(() => {
return knex.migrate
.down({
directory: ['test/integration/migrate/test'],
})
.then((data) => {
expect(data).to.be.an('array');
});
});
});
});

after(function() {
rimraf.sync(path.join(__dirname, './migration'));
});
Expand Down
117 changes: 115 additions & 2 deletions test/jake/jakelib/migrate-test.js
Expand Up @@ -380,7 +380,7 @@ test('migrate:up runs only the next unrun migration', (temp) => {
--client=sqlite3 \
--connection=${temp}/db \
--migrations-directory=${temp}/migrations`,
'create_books_table'
'update_books_table'
).then(({ stdout }) => {
assert.include(
stdout,
Expand Down Expand Up @@ -423,13 +423,126 @@ test('migrate:up runs only the next unrun migration', (temp) => {
--client=sqlite3 \
--connection=${temp}/db \
--migrations-directory=${temp}/migrations`,
'create_books_table'
'already_up_to_date'
).then(({ stdout }) => {
assert.include(stdout, 'Already up to date');
});
});
});

test('migrate:down undos only the last run migration', (temp) => {
const migrationFile1 = '001_create_address_table.js';
const migrationFile2 = '002_add_zip_to_address_table.js';

fs.writeFileSync(
`${temp}/migrations/${migrationFile1}`,
`
exports.up = (knex) => knex.schema
.createTable('address', (table) => {
table.string('street');
});
exports.down = (knex) => knex.schema.dropTable('address');
`
);

fs.writeFileSync(
`${temp}/migrations/${migrationFile2}`,
`
exports.up = (knex) => knex.schema
.table('address', (table) => {
table.integer('zip_code');
});
exports.down = (knex) => knex.schema
.table('address', (table) => {
table.dropColumn('zip_code');
});
`
);

return assertExec(
`node ${KNEX} migrate:latest \
--client=sqlite3 \
--connection=${temp}/db \
--migrations-directory=${temp}/migrations`,
'run_all_migrations'
)
.then(() => {
return assertExec(
`node ${KNEX} migrate:down \
--client=sqlite3 \
--connection=${temp}/db \
--migrations-directory=${temp}/migrations`,
'undo_migration_002'
).then(({ stdout }) => {
assert.include(
stdout,
`Batch 1 rolled back the following migrations:\n${migrationFile2}`
);

const db = new sqlite3.Database(`${temp}/db`);

return new Promise((resolve, reject) => {
db.all('SELECT * FROM knex_migrations', (err, rows) => {
const migrationsWithoutMigrationTime = rows.map((row) => {
return {
id: row.id,
name: row.name,
batch: row.batch,
};
});

assert.includeDeepOrderedMembers(migrationsWithoutMigrationTime, [
{
id: 1,
name: migrationFile1,
batch: 1,
},
]);

err ? reject(err) : resolve();
});
});
});
})
.then(() => {
return assertExec(
`node ${KNEX} migrate:down \
--client=sqlite3 \
--connection=${temp}/db \
--migrations-directory=${temp}/migrations`,
'undo_migration_001'
).then(({ stdout }) => {
assert.include(
stdout,
`Batch 1 rolled back the following migrations:\n${migrationFile1}`
);

const db = new sqlite3.Database(`${temp}/db`);

return new Promise((resolve, reject) => {
db.all('SELECT * FROM knex_migrations', (err, rows) => {
assert.isEmpty(rows);

err ? reject(err) : resolve();
});
});
});
})
.then(() => {
return assertExec(
`node ${KNEX} migrate:down \
--client=sqlite3 \
--connection=${temp}/db \
--migrations-directory=${temp}/migrations`,
'already_at_the_base_migration'
).then(({ stdout }) => {
assert.include(stdout, 'Already at the base migration');
});
});
});

module.exports = {
taskList,
};
1 change: 1 addition & 0 deletions types/index.d.ts
Expand Up @@ -1727,6 +1727,7 @@ declare namespace Knex {
status(config?: MigratorConfig): Bluebird<number>;
currentVersion(config?: MigratorConfig): Bluebird<string>;
up(config?: MigratorConfig): Bluebird<any>;
down(config?: MigratorConfig): Bluebird<any>;
}

interface SeederConfig {
Expand Down

0 comments on commit fa1b48a

Please sign in to comment.