mirror of
https://github.com/knex/knex.git
synced 2025-07-31 04:41:59 +00:00

The underlying issue was that query *always* committed, even during a transaction. The previous fix was to just disable commitAsync in a transaction but then that also disabled explicit commit calls. Instead, this fix disables query's commits during transactions so that explicit commits still work.
1056 lines
33 KiB
JavaScript
1056 lines
33 KiB
JavaScript
/*global expect*/
|
|
'use strict';
|
|
|
|
const equal = require('assert').equal;
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const rimraf = require('rimraf');
|
|
const Bluebird = require('bluebird');
|
|
const knexLib = require('../../../knex');
|
|
const logger = require('../logger');
|
|
const config = require('../../knexfile');
|
|
const _ = require('lodash');
|
|
const testMemoryMigrations = require('./memory-migrations');
|
|
|
|
module.exports = function(knex) {
|
|
require('rimraf').sync(path.join(__dirname, './migration'));
|
|
|
|
before(function() {
|
|
// make sure lock was not left from previous failed test run
|
|
return knex.migrate.forceFreeMigrationsLock({
|
|
directory: 'test/integration/migrate/test',
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate', function() {
|
|
it('should not fail on null default for timestamp', () => {
|
|
return knex.schema
|
|
.dropTableIfExists('null_date')
|
|
.then(() => {
|
|
return knex.migrate.latest({
|
|
directory: 'test/integration/migrate/null_timestamp_default',
|
|
});
|
|
})
|
|
.then(() => {
|
|
return knex.into('null_date').insert({
|
|
dummy: 'cannot insert empty object',
|
|
});
|
|
})
|
|
.then(() => {
|
|
return knex('null_date').first();
|
|
})
|
|
.then((rows) => {
|
|
expect(rows.deleted_at).to.equal(null);
|
|
})
|
|
.finally(() => {
|
|
return knex.migrate.rollback({
|
|
directory: 'test/integration/migrate/null_timestamp_default',
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should not fail drop-and-recreate-column operation when using promise chain', () => {
|
|
return knex.migrate
|
|
.latest({
|
|
directory: 'test/integration/migrate/drop-and-recreate',
|
|
})
|
|
.then(() => {
|
|
return knex.migrate.rollback({
|
|
directory: 'test/integration/migrate/drop-and-recreate',
|
|
});
|
|
});
|
|
});
|
|
|
|
if (knex.client.driverName === 'pg') {
|
|
it('should not fail drop-and-recreate-column operation when using promise chain and schema', () => {
|
|
return knex.migrate
|
|
.latest({
|
|
directory: 'test/integration/migrate/drop-and-recreate-with-schema',
|
|
})
|
|
.then(() => {
|
|
return knex.migrate.rollback({
|
|
directory:
|
|
'test/integration/migrate/drop-and-recreate-with-schema',
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
if (knex.client.driverName === 'sqlite3') {
|
|
it('should not fail rename-and-drop-column with multiline sql from legacy db', async () => {
|
|
const knexConfig = _.extend({}, config.sqlite3, {
|
|
connection: {
|
|
filename: __dirname + '/../../multilineCreateMaster.sqlite3',
|
|
},
|
|
migrations: {
|
|
directory:
|
|
'test/integration/migrate/rename-and-drop-column-with-multiline-sql-from-legacy-db',
|
|
},
|
|
});
|
|
|
|
const db = logger(knexLib(knexConfig));
|
|
|
|
await db.migrate.latest();
|
|
await db.migrate.rollback();
|
|
});
|
|
}
|
|
|
|
it('should not fail drop-and-recreate-column operation when using async/await', () => {
|
|
return knex.migrate
|
|
.latest({
|
|
directory: 'test/integration/migrate/async-await-drop-and-recreate',
|
|
})
|
|
.then(() => {
|
|
return knex.migrate.rollback({
|
|
directory: 'test/integration/migrate/async-await-drop-and-recreate',
|
|
});
|
|
});
|
|
});
|
|
|
|
if (knex.client.driverName === 'pg') {
|
|
it('should not fail drop-and-recreate-column operation when using async/await and schema', () => {
|
|
return knex.migrate
|
|
.latest({
|
|
directory:
|
|
'test/integration/migrate/async-await-drop-and-recreate-with-schema',
|
|
})
|
|
.then(() => {
|
|
return knex.migrate.rollback({
|
|
directory:
|
|
'test/integration/migrate/async-await-drop-and-recreate-with-schema',
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
it('should create a new migration file with the create method', function() {
|
|
return knex.migrate.make('test').then(function(name) {
|
|
name = path.basename(name);
|
|
expect(name.split('_')[0]).to.have.length(14);
|
|
expect(name.split('_')[1].split('.')[0]).to.equal('test');
|
|
});
|
|
});
|
|
|
|
it('should list the current migration state with the currentVersion method', function() {
|
|
return knex.migrate.currentVersion().then(function(version) {
|
|
equal(version, 'none');
|
|
});
|
|
});
|
|
|
|
const tables = [
|
|
'migration_test_1',
|
|
'migration_test_2',
|
|
'migration_test_2_1',
|
|
];
|
|
|
|
describe('knex.migrate.status', function() {
|
|
this.timeout(process.env.KNEX_TEST_TIMEOUT || 30000);
|
|
|
|
beforeEach(function() {
|
|
return knex.migrate.latest({
|
|
directory: 'test/integration/migrate/test',
|
|
});
|
|
});
|
|
|
|
afterEach(function() {
|
|
return knex.migrate.rollback({
|
|
directory: 'test/integration/migrate/test',
|
|
});
|
|
});
|
|
|
|
it('should create a migrations lock table', function() {
|
|
return knex.schema
|
|
.hasTable('knex_migrations_lock')
|
|
.then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
|
|
return knex.schema
|
|
.hasColumn('knex_migrations_lock', 'is_locked')
|
|
.then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should return 0 if code matches DB', function() {
|
|
return knex.migrate
|
|
.status({ directory: 'test/integration/migrate/test' })
|
|
.then(function(migrationLevel) {
|
|
expect(migrationLevel).to.equal(0);
|
|
});
|
|
});
|
|
|
|
it('should return a negative number if the DB is behind', function() {
|
|
return knex.migrate
|
|
.rollback({ directory: 'test/integration/migrate/test' })
|
|
.then(function() {
|
|
return knex.migrate
|
|
.status({ directory: 'test/integration/migrate/test' })
|
|
.then(function(migrationLevel) {
|
|
expect(migrationLevel).to.equal(-2);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should return a positive number if the DB is ahead', function() {
|
|
return Bluebird.all([
|
|
knex('knex_migrations')
|
|
.returning('id')
|
|
.insert({
|
|
name: 'foobar',
|
|
batch: 5,
|
|
migration_time: new Date(),
|
|
}),
|
|
knex('knex_migrations')
|
|
.returning('id')
|
|
.insert({
|
|
name: 'foobar',
|
|
batch: 5,
|
|
migration_time: new Date(),
|
|
}),
|
|
knex('knex_migrations')
|
|
.returning('id')
|
|
.insert({
|
|
name: 'foobarbaz',
|
|
batch: 6,
|
|
migration_time: new Date(),
|
|
}),
|
|
]).then(([migration1, migration2, migration3]) => {
|
|
return knex.migrate
|
|
.status({ directory: 'test/integration/migrate/test' })
|
|
.then(function(migrationLevel) {
|
|
expect(migrationLevel).to.equal(3);
|
|
})
|
|
.then(function() {
|
|
// Cleanup the added migrations
|
|
if (/redshift/.test(knex.client.driverName)) {
|
|
return knex('knex_migrations')
|
|
.where('name', 'like', '%foobar%')
|
|
.del();
|
|
}
|
|
return knex('knex_migrations')
|
|
.where('id', migration1[0])
|
|
.orWhere('id', migration2[0])
|
|
.orWhere('id', migration3[0])
|
|
.del();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate.latest', function() {
|
|
before(function() {
|
|
return knex.migrate.latest({
|
|
directory: 'test/integration/migrate/test',
|
|
});
|
|
});
|
|
|
|
it('should remove the record in the lock table once finished', function() {
|
|
return knex('knex_migrations_lock')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data[0]).to.have.property('is_locked');
|
|
expect(data[0].is_locked).to.not.be.ok;
|
|
});
|
|
});
|
|
|
|
it('should throw error if the migrations are already running', function() {
|
|
return knex('knex_migrations_lock')
|
|
.update({ is_locked: 1 })
|
|
.then(function() {
|
|
return knex.migrate
|
|
.latest({ directory: 'test/integration/migrate/test' })
|
|
.then(function() {
|
|
throw new Error('then should not execute');
|
|
});
|
|
})
|
|
.catch(function(error) {
|
|
expect(error).to.have.property(
|
|
'message',
|
|
'Migration table is already locked'
|
|
);
|
|
return knex('knex_migrations_lock').select('*');
|
|
})
|
|
.then(function(data) {
|
|
expect(data[0].is_locked).to.equal(1);
|
|
|
|
// Clean up lock for other tests
|
|
return knex('knex_migrations_lock').update({ is_locked: 0 });
|
|
});
|
|
});
|
|
|
|
it('should work with concurent calls to _lockMigrations', async function() {
|
|
if (knex.client.driverName == 'sqlite3') {
|
|
// sqlite doesn't support concurrency
|
|
this.skip();
|
|
return;
|
|
}
|
|
|
|
const migrator = knex.migrate;
|
|
try {
|
|
// Start two transactions and call _lockMigrations in each of them.
|
|
// Simulate a race condition by waiting until both are started before
|
|
// attempting to commit either one. Exactly one should succeed.
|
|
//
|
|
// Both orderings are legitimate, but in practice the first transaction
|
|
// to start will be the one that succeeds in all currently supported
|
|
// databases (CockroachDB 1.x is an example of a database where the
|
|
// second transaction would win, but this changed in 2.0). This test
|
|
// assumes the first transaction wins, but could be refactored to support
|
|
// both orderings if desired.
|
|
const trx1 = await knex.transaction();
|
|
await migrator._lockMigrations(trx1);
|
|
const trx2 = await knex.transaction();
|
|
// trx1 has a pending write lock, so the second call to _lockMigrations
|
|
// will block (unless we're on a DB that resolves the transaction in
|
|
// the other order as mentioned above).
|
|
// Save the promise, then wait a short time to ensure it's had time
|
|
// to start its query and get blocked.
|
|
const trx2Promise = migrator._lockMigrations(trx2);
|
|
await Bluebird.delay(100);
|
|
if (!trx2Promise.isPending()) {
|
|
throw new Error('expected trx2 to be pending');
|
|
}
|
|
await trx1.commit();
|
|
// trx1 has completed and unblocked trx2, which should now fail.
|
|
try {
|
|
await trx2Promise;
|
|
throw new Error('expected trx2 to fail');
|
|
} catch (error) {
|
|
expect(error)
|
|
.to.have.property('message')
|
|
.that.includes('already locked');
|
|
await trx2.rollback();
|
|
}
|
|
} finally {
|
|
// Clean up after ourselves (I'm not sure why the before() at the
|
|
// top of this file isn't doing it, but if this test fails without
|
|
// this call it tends to cause cascading failures).
|
|
await migrator._freeLock();
|
|
}
|
|
});
|
|
|
|
it('should report failing migration', function() {
|
|
const migrator = knex.migrate;
|
|
return migrator
|
|
.latest({ directory: 'test/integration/migrate/test_with_invalid' })
|
|
.then(function() {
|
|
throw new Error('then should not execute');
|
|
})
|
|
.catch(function(error) {
|
|
// This will fail because of the invalid migration
|
|
expect(error)
|
|
.to.have.property('message')
|
|
.that.includes('unknown_table');
|
|
expect(migrator)
|
|
.to.have.property('_activeMigration')
|
|
.to.have.property(
|
|
'fileName',
|
|
'20150109002832_invalid_migration.js'
|
|
);
|
|
})
|
|
.then(function(data) {
|
|
// Clean up lock for other tests
|
|
// TODO: Remove this code to reproduce https://github.com/tgriesser/knex/issues/2925
|
|
// It is only relevant for Oracle, most likely there is a bug somewhere that needs fixing
|
|
return knex('knex_migrations_lock').update({ is_locked: 0 });
|
|
});
|
|
});
|
|
|
|
it('should release lock if non-locking related error is thrown', function() {
|
|
return knex('knex_migrations_lock')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data[0].is_locked).to.not.be.ok;
|
|
});
|
|
});
|
|
|
|
it('should run all migration files in the specified directory', function() {
|
|
return knex('knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data.length).to.equal(2);
|
|
});
|
|
});
|
|
|
|
it('should run the migrations from oldest to newest', function() {
|
|
if (knex.client.driverName === 'oracledb') {
|
|
return knex('knex_migrations')
|
|
.orderBy('migration_time', 'asc')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(path.basename(data[0].name)).to.equal(
|
|
'20131019235242_migration_1.js'
|
|
);
|
|
expect(path.basename(data[1].name)).to.equal(
|
|
'20131019235306_migration_2.js'
|
|
);
|
|
});
|
|
} else {
|
|
return knex('knex_migrations')
|
|
.orderBy('id', 'asc')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(path.basename(data[0].name)).to.equal(
|
|
'20131019235242_migration_1.js'
|
|
);
|
|
expect(path.basename(data[1].name)).to.equal(
|
|
'20131019235306_migration_2.js'
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
it('should create all specified tables and columns', function() {
|
|
// Map the table names to promises that evaluate chai expectations to
|
|
// confirm that the table exists and the 'id' and 'name' columns exist
|
|
// within the table
|
|
return Promise.all(
|
|
tables.map(function(table) {
|
|
return knex.schema.hasTable(table).then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
if (exists) {
|
|
return Promise.all([
|
|
knex.schema.hasColumn(table, 'id').then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
}),
|
|
knex.schema.hasColumn(table, 'name').then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
}),
|
|
]);
|
|
}
|
|
});
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate.latest - multiple directories', () => {
|
|
before(() => {
|
|
return knex.migrate.latest({
|
|
directory: [
|
|
'test/integration/migrate/test',
|
|
'test/integration/migrate/test2',
|
|
],
|
|
});
|
|
});
|
|
|
|
after(() => {
|
|
return knex.migrate.rollback({
|
|
directory: [
|
|
'test/integration/migrate/test',
|
|
'test/integration/migrate/test2',
|
|
],
|
|
});
|
|
});
|
|
|
|
it('should create tables specified in both directories', () => {
|
|
// Map the table names to promises that evaluate chai expectations to
|
|
// confirm that the table exists
|
|
const expectedTables = [
|
|
'migration_test_1',
|
|
'migration_test_2',
|
|
'migration_test_2_1',
|
|
'migration_test_3',
|
|
'migration_test_4',
|
|
'migration_test_4_1',
|
|
];
|
|
|
|
return Promise.all(
|
|
expectedTables.map(function(table) {
|
|
return knex.schema.hasTable(table).then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
});
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate.rollback', function() {
|
|
it('should delete the most recent batch from the migration log', function() {
|
|
return knex.migrate
|
|
.rollback({ directory: 'test/integration/migrate/test' })
|
|
.then(([batchNo, log]) => {
|
|
expect(batchNo).to.equal(1);
|
|
expect(log).to.have.length(2);
|
|
expect(log[0]).to.contain(batchNo);
|
|
return knex('knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data.length).to.equal(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should drop tables as specified in the batch', function() {
|
|
return Promise.all(
|
|
tables.map(function(table) {
|
|
return knex.schema.hasTable(table).then(function(exists) {
|
|
expect(!!exists).to.equal(false);
|
|
});
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate.rollback - all', () => {
|
|
before(() => {
|
|
return knex.migrate
|
|
.latest({
|
|
directory: ['test/integration/migrate/test'],
|
|
})
|
|
.then(function() {
|
|
return knex.migrate.latest({
|
|
directory: [
|
|
'test/integration/migrate/test',
|
|
'test/integration/migrate/test2',
|
|
],
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should delete all batches from the migration log', () => {
|
|
return knex.migrate
|
|
.rollback(
|
|
{
|
|
directory: [
|
|
'test/integration/migrate/test',
|
|
'test/integration/migrate/test2',
|
|
],
|
|
},
|
|
true
|
|
)
|
|
.then(([batchNo, log]) => {
|
|
expect(batchNo).to.equal(2);
|
|
expect(log).to.have.length(4);
|
|
return knex('knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data.length).to.equal(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should drop tables as specified in the batch', () => {
|
|
return Promise.all(
|
|
tables.map(function(table) {
|
|
return knex.schema.hasTable(table).then(function(exists) {
|
|
expect(!!exists).to.equal(false);
|
|
});
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate.rollback - all', () => {
|
|
before(() => {
|
|
return knex.migrate.latest({
|
|
directory: ['test/integration/migrate/test'],
|
|
});
|
|
});
|
|
|
|
it('should only rollback migrations that have been completed and in reverse chronological order', () => {
|
|
return knex.migrate
|
|
.rollback(
|
|
{
|
|
directory: [
|
|
'test/integration/migrate/test',
|
|
'test/integration/migrate/test2',
|
|
],
|
|
},
|
|
true
|
|
)
|
|
.then(([batchNo, log]) => {
|
|
expect(batchNo).to.equal(1);
|
|
expect(log).to.have.length(2);
|
|
|
|
fs.readdirSync('test/integration/migrate/test')
|
|
.reverse()
|
|
.forEach((fileName, index) => {
|
|
expect(fileName).to.equal(log[index]);
|
|
});
|
|
|
|
return knex('knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data.length).to.equal(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should drop tables as specified in the batch', () => {
|
|
return Promise.all(
|
|
tables.map(function(table) {
|
|
return knex.schema.hasTable(table).then(function(exists) {
|
|
expect(!!exists).to.equal(false);
|
|
});
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate.up', () => {
|
|
afterEach(() => {
|
|
return knex.migrate.rollback(
|
|
{ directory: 'test/integration/migrate/test' },
|
|
true
|
|
);
|
|
});
|
|
|
|
it('should only run the first migration if no migrations have run', function() {
|
|
return knex.migrate
|
|
.up({
|
|
directory: 'test/integration/migrate/test',
|
|
})
|
|
.then(() => {
|
|
return knex('knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data).to.have.length(1);
|
|
expect(path.basename(data[0].name)).to.equal(
|
|
'20131019235242_migration_1.js'
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should only run the next migration that has not yet run if other migrations have already run', function() {
|
|
return knex.migrate
|
|
.up({
|
|
directory: 'test/integration/migrate/test',
|
|
})
|
|
.then(() => {
|
|
return knex.migrate
|
|
.up({
|
|
directory: 'test/integration/migrate/test',
|
|
})
|
|
.then(() => {
|
|
return knex('knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data).to.have.length(2);
|
|
expect(path.basename(data[0].name)).to.equal(
|
|
'20131019235242_migration_1.js'
|
|
);
|
|
expect(path.basename(data[1].name)).to.equal(
|
|
'20131019235306_migration_2.js'
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should not error if all migrations have already been run', function() {
|
|
return knex.migrate
|
|
.latest({
|
|
directory: 'test/integration/migrate/test',
|
|
})
|
|
.then(() => {
|
|
return knex.migrate
|
|
.up({
|
|
directory: 'test/integration/migrate/test',
|
|
})
|
|
.then((data) => {
|
|
expect(data).to.be.an('array');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
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'));
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate.latest in parallel', function() {
|
|
afterEach(function() {
|
|
return knex.migrate.rollback({
|
|
directory: 'test/integration/migrate/test',
|
|
});
|
|
});
|
|
|
|
if (knex.client.driverName === 'pg' || knex.client.driverName === 'mssql') {
|
|
it('is able to run two migrations in parallel (if no implicit DDL commits)', function() {
|
|
return Promise.all([
|
|
knex.migrate.latest({ directory: 'test/integration/migrate/test' }),
|
|
knex.migrate.latest({ directory: 'test/integration/migrate/test' }),
|
|
]).then(function() {
|
|
return knex('knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data.length).to.equal(2);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
it('is not able to run two migrations in parallel when transactions are disabled', function() {
|
|
const migrations = [
|
|
knex.migrate
|
|
.latest({
|
|
directory: 'test/integration/migrate/test',
|
|
disableTransactions: true,
|
|
})
|
|
.catch(function(err) {
|
|
return err;
|
|
}),
|
|
knex.migrate
|
|
.latest({
|
|
directory: 'test/integration/migrate/test',
|
|
disableTransactions: true,
|
|
})
|
|
.catch(function(err) {
|
|
return err;
|
|
}),
|
|
];
|
|
|
|
return Promise.all(
|
|
migrations.map((migration) => migration.then((res) => res && res.name))
|
|
).then(function(res) {
|
|
// One should fail:
|
|
const hasLockError =
|
|
res[0] === 'MigrationLocked' || res[1] === 'MigrationLocked';
|
|
expect(hasLockError).to.equal(true);
|
|
|
|
// But the other should succeed:
|
|
return knex('knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data.length).to.equal(2);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate (transactions disabled)', function() {
|
|
describe('knex.migrate.latest (all transactions disabled)', function() {
|
|
before(function() {
|
|
return knex.migrate
|
|
.latest({
|
|
directory: 'test/integration/migrate/test_with_invalid',
|
|
disableTransactions: true,
|
|
})
|
|
.catch(function() {});
|
|
});
|
|
|
|
// Same test as before, but this time, because
|
|
// transactions are off, the column gets created for all dialects always.
|
|
it('should create column even in invalid migration', function() {
|
|
return knex.schema
|
|
.hasColumn('migration_test_1', 'transaction')
|
|
.then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
});
|
|
});
|
|
|
|
after(function() {
|
|
return knex.migrate.rollback({
|
|
directory: 'test/integration/migrate/test_with_invalid',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate.latest (per-migration transaction disabled)', function() {
|
|
before(function() {
|
|
return knex.migrate
|
|
.latest({
|
|
directory:
|
|
'test/integration/migrate/test_per_migration_trx_disabled',
|
|
})
|
|
.catch(function() {});
|
|
});
|
|
|
|
it('should run all working migration files in the specified directory', function() {
|
|
return knex('knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data.length).to.equal(1);
|
|
});
|
|
});
|
|
|
|
it('should create column in invalid migration with transaction disabled', function() {
|
|
return knex.schema
|
|
.hasColumn('migration_test_trx_1', 'transaction')
|
|
.then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
});
|
|
});
|
|
|
|
after(function() {
|
|
return knex.migrate.rollback({
|
|
directory: 'test/integration/migrate/test_per_migration_trx_disabled',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate.latest (per-migration transaction enabled)', function() {
|
|
before(function() {
|
|
return knex.migrate
|
|
.latest({
|
|
directory:
|
|
'test/integration/migrate/test_per_migration_trx_enabled',
|
|
disableTransactions: true,
|
|
})
|
|
.catch(function() {});
|
|
});
|
|
|
|
it('should run all working migration files in the specified directory', function() {
|
|
return knex('knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data.length).to.equal(1);
|
|
});
|
|
});
|
|
|
|
it('should not create column for invalid migration with transaction enabled', function() {
|
|
return knex.schema
|
|
.hasColumn('migration_test_trx_1', 'transaction')
|
|
.then(function(exists) {
|
|
// MySQL / Oracle commit transactions implicit for most common
|
|
// migration statements (e.g. CREATE TABLE, ALTER TABLE, DROP TABLE),
|
|
// so we need to check for dialect
|
|
if (
|
|
knex.client.driverName === 'mysql' ||
|
|
knex.client.driverName === 'mysql2' ||
|
|
knex.client.driverName === 'oracledb'
|
|
) {
|
|
expect(exists).to.equal(true);
|
|
} else {
|
|
expect(exists).to.equal(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
after(function() {
|
|
return knex.migrate.rollback({
|
|
directory: 'test/integration/migrate/test_per_migration_trx_enabled',
|
|
});
|
|
});
|
|
});
|
|
|
|
if (knex.client.driverName === 'pg') {
|
|
describe('knex.migrate.latest with specific changelog schema', function() {
|
|
before(() => {
|
|
return knex.raw(`CREATE SCHEMA IF NOT EXISTS "testschema"`);
|
|
});
|
|
after(() => {
|
|
return knex.raw(`DROP SCHEMA "testschema" CASCADE`);
|
|
});
|
|
|
|
it('should create changelog in the correct schema without transactions', function(done) {
|
|
knex.migrate
|
|
.latest({
|
|
directory: 'test/integration/migrate/test',
|
|
disableTransactions: true,
|
|
schemaName: 'testschema',
|
|
})
|
|
.then(() => {
|
|
return knex('testschema.knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data.length).to.equal(2);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should create changelog in the correct schema with transactions', function(done) {
|
|
knex.migrate
|
|
.latest({
|
|
directory: 'test/integration/migrate/test',
|
|
disableTransactions: false,
|
|
schemaName: 'testschema',
|
|
})
|
|
.then(() => {
|
|
return knex('testschema.knex_migrations')
|
|
.select('*')
|
|
.then(function(data) {
|
|
expect(data.length).to.equal(2);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
afterEach(function() {
|
|
return knex.migrate.rollback({
|
|
directory: 'test/integration/migrate/test',
|
|
disableTransactions: true,
|
|
schemaName: 'testschema',
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('migrationSource config', function() {
|
|
const migrationSource = {
|
|
getMigrations() {
|
|
return Promise.resolve(Object.keys(testMemoryMigrations).sort());
|
|
},
|
|
getMigrationName(migration) {
|
|
return migration;
|
|
},
|
|
getMigration(migration) {
|
|
return testMemoryMigrations[migration];
|
|
},
|
|
};
|
|
|
|
before(() => {
|
|
return knex.migrate.latest({
|
|
migrationSource,
|
|
});
|
|
});
|
|
|
|
after(() => {
|
|
return knex.migrate.rollback({
|
|
migrationSource,
|
|
});
|
|
});
|
|
|
|
it('can accept a custom migration source', function() {
|
|
return knex.schema
|
|
.hasColumn('migration_source_test_1', 'name')
|
|
.then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate.latest with custom config parameter for table name', function() {
|
|
before(function() {
|
|
return knex
|
|
.withUserParams({ customTableName: 'migration_test_2_1a' })
|
|
.migrate.latest({
|
|
directory: 'test/integration/migrate/test',
|
|
});
|
|
});
|
|
|
|
it('should create all specified tables and columns', function() {
|
|
const tables = [
|
|
'migration_test_1',
|
|
'migration_test_2',
|
|
'migration_test_2_1a',
|
|
];
|
|
return Promise.all(
|
|
tables.map(function(table) {
|
|
return knex.schema.hasTable(table).then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
if (exists) {
|
|
return Promise.all([
|
|
knex.schema.hasColumn(table, 'id').then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
}),
|
|
knex.schema.hasColumn(table, 'name').then(function(exists) {
|
|
expect(exists).to.equal(true);
|
|
}),
|
|
]);
|
|
}
|
|
});
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should not create unexpected tables', function() {
|
|
const table = 'migration_test_2_1';
|
|
return knex.schema.hasTable(table).then(function(exists) {
|
|
expect(exists).to.equal(false);
|
|
});
|
|
});
|
|
|
|
after(function() {
|
|
return knex
|
|
.withUserParams({ customTableName: 'migration_test_2_1a' })
|
|
.migrate.rollback({
|
|
directory: 'test/integration/migrate/test',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('knex.migrate.latest with disableValidateMigrationList', function() {
|
|
it('should not fail if there is a missing migration', () => {
|
|
return knex.migrate
|
|
.latest({
|
|
directory: 'test/integration/migrate/test',
|
|
})
|
|
.then(() => {
|
|
return knex.migrate.latest({
|
|
directory:
|
|
'test/integration/migrate/test_with_missing_first_migration',
|
|
disableMigrationsListValidation: true,
|
|
});
|
|
})
|
|
.finally(() => {
|
|
return knex.migrate.rollback({
|
|
directory: 'test/integration/migrate/test',
|
|
});
|
|
});
|
|
});
|
|
});
|
|
};
|